{"version":3,"file":"agent-input-builder.mjs","names":[],"sources":["../../../../../../../ai/src/agent/agent-input-builder.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { AgentExecuteOptions } from \"../contracts/agent/agent-options.type\";\nimport type { AttachmentPolicy } from \"../contracts/attachment-policy.type\";\nimport type { Attachment } from \"../contracts/attachment.type\";\nimport type { ContentPart } from \"../contracts/content-part.type\";\nimport type { Message } from \"../contracts/conversation-message.type\";\nimport type { Placeholders } from \"../contracts/placeholders.type\";\nimport { InvalidRequestError } from \"../errors\";\nimport { extractJsonSchema, prepareAttachmentPart } from \"../utils\";\nimport type { AgentConfig } from \"./agent-config.type\";\n\n/**\n * Outcome of `buildAgentInputMessages` — the seeded message list and\n * the JSON Schema cached for every trip's\n * `ModelCallOptions.responseSchema`. `responseSchema` is `undefined`\n * when the caller didn't ask for structured output.\n */\nexport type AgentInputBuildResult = {\n  messages: Message[];\n  responseSchema?: Record<string, unknown>;\n  /**\n   * The resolved system-prompt text actually sent as the `role: \"system\"`\n   * message (persona + instructions + any auto-appended structured-output\n   * instruction). Captured for observability; absent when the agent ran\n   * without a system prompt.\n   */\n  systemPrompt?: string;\n  /**\n   * Registry name of the `SystemPromptContract` the agent resolved, read from\n   * its `meta().name`. Present only when the agent ran against a *named*\n   * prompt (one registered in `ai.prompts`); absent for a raw-string prompt,\n   * an anonymous contract, or no prompt at all. Lets observers attribute a run\n   * to a specific prompt in the registry.\n   */\n  promptName?: string;\n  /**\n   * Registry version label of the named prompt the agent resolved, read from\n   * its `meta().version` (defaulting to `\"1\"` when the prompt carries a name\n   * but no explicit version, mirroring the registry's default). Present only\n   * alongside {@link AgentInputBuildResult.promptName}.\n   */\n  promptVersion?: string;\n};\n\n/**\n * Assemble the seed conversation for an agent execution. Runs exactly\n * once per run — subsequent trips append to the same message list.\n *\n * Responsibilities (previously three methods on `Execution`):\n * 1. Merge factory + per-call placeholders.\n * 2. Resolve the system prompt (string, contract, or absent).\n * 3. When an output schema is supplied:\n *    - cache its JSON Schema form for `ModelCallOptions.responseSchema`\n *      so native-structured-output providers enforce it at the token\n *      level;\n *    - fall back to a soft system-prompt instruction for providers\n *      that don't advertise `structuredOutput` capability.\n * 4. Append caller-supplied `history` (e.g. session-level prior turns).\n * 5. Shape the user message — plain string in the common case,\n *    multipart `ContentPart[]` when `attachments` are present. Image\n *    attachments require model vision capability; mismatch throws\n *    `InvalidRequestError` here rather than failing opaquely at the\n *    provider.\n *\n * Extracted from the `Execution` class to isolate the declarative\n * input-shaping phase from the stateful trip loop.\n */\nexport async function buildAgentInputMessages<TOutput>(params: {\n  config: AgentConfig<TOutput>;\n  input: string;\n  options?: AgentExecuteOptions<TOutput>;\n}): Promise<AgentInputBuildResult> {\n  const { config, input, options } = params;\n\n  const placeholders: Placeholders = {\n    ...config.placeholders,\n    ...options?.placeholders,\n  };\n\n  const systemPrompt = options?.systemPrompt ?? config.systemPrompt;\n  let systemContent = \"\";\n  let promptName: string | undefined;\n  let promptVersion: string | undefined;\n\n  if (typeof systemPrompt === \"string\") {\n    systemContent = systemPrompt;\n  } else if (systemPrompt) {\n    // A lazily-compiled prompt (`systemPrompt.refined(...)`) finishes its\n    // async work here, before the synchronous `resolve()` below — a no-op for\n    // plain builders, and never throws (a failed refinement falls back to the\n    // original text).\n    if (typeof systemPrompt.materialize === \"function\") {\n      await systemPrompt.materialize();\n    }\n\n    systemContent = systemPrompt.resolve(placeholders);\n\n    // Capture prompt-version linkage from the contract's metadata: a *named*\n    // prompt (one addressable in `ai.prompts`) stamps `promptName@version`\n    // onto the run's report so observers can group runs by the exact prompt\n    // version that produced them. Anonymous prompts carry no name and are\n    // left unlinked.\n    const meta = systemPrompt.meta();\n\n    if (meta?.name) {\n      promptName = meta.name;\n      promptVersion = meta.version ?? \"1\";\n    }\n  }\n\n  const { responseSchema, instruction } = resolveStructuredOutput({\n    outputSchema: options?.output ?? config.output,\n    overrideResponseSchema: options?.responseSchema,\n    modelSupportsStructuredOutput: Boolean(config.model.capabilities?.structuredOutput),\n  });\n\n  if (instruction) {\n    systemContent = systemContent ? `${systemContent}\\n\\n${instruction}` : instruction;\n  }\n\n  const messages: Message[] = [];\n\n  if (systemContent) {\n    messages.push({ role: \"system\", content: systemContent });\n  }\n\n  if (options?.history) {\n    messages.push(...options.history);\n  }\n\n  const userContent = await buildUserMessageContent({\n    input,\n    attachments: options?.attachments,\n    attachmentPolicy: options?.attachmentPolicy ?? config.attachmentPolicy,\n    modelName: config.model.name,\n    modelSupportsVision: Boolean(config.model.capabilities?.vision),\n    modelSupportsPdf: Boolean(config.model.capabilities?.pdf),\n    modelSupportsAudio: Boolean(config.model.capabilities?.audio),\n  });\n\n  messages.push({ role: \"user\", content: userContent });\n\n  return {\n    messages,\n    responseSchema,\n    systemPrompt: systemContent || undefined,\n    promptName,\n    promptVersion,\n  };\n}\n\n/**\n * Build the user message `content` field. Plain string when no\n * attachments (the hot path) — keeps wire payloads small. Multipart\n * `ContentPart[]` when attachments exist: input text first, resolved\n * parts in declaration order.\n */\nasync function buildUserMessageContent(params: {\n  input: string;\n  attachments?: Attachment[];\n  attachmentPolicy?: AttachmentPolicy;\n  modelName: string;\n  modelSupportsVision: boolean;\n  modelSupportsPdf: boolean;\n  modelSupportsAudio: boolean;\n}): Promise<string | ContentPart[]> {\n  const {\n    input,\n    attachments,\n    attachmentPolicy,\n    modelName,\n    modelSupportsVision,\n    modelSupportsPdf,\n    modelSupportsAudio,\n  } = params;\n\n  if (!attachments || attachments.length === 0) {\n    return input;\n  }\n\n  const parts: ContentPart[] = await Promise.all(\n    attachments.map((attachment) => prepareAttachmentPart(attachment, attachmentPolicy)),\n  );\n\n  // Capability gate per modality (A2) — reject an attachment the model\n  // can't consume here, with a clear message, rather than failing opaquely\n  // at the provider.\n  assertModality(parts, \"image\", modelSupportsVision, \"vision\", modelName);\n  assertModality(parts, \"pdf\", modelSupportsPdf, \"pdf\", modelName);\n  assertModality(parts, \"audio\", modelSupportsAudio, \"audio\", modelName);\n\n  return [{ type: \"text\", text: input }, ...parts];\n}\n\n/** Throw when a modality is present but the model doesn't declare it. */\nfunction assertModality(\n  parts: ContentPart[],\n  partType: ContentPart[\"type\"],\n  supported: boolean,\n  capability: string,\n  modelName: string,\n): void {\n  if (!supported && parts.some((part) => part.type === partType)) {\n    throw new InvalidRequestError(\n      `Model \"${modelName}\" does not declare ${capability} capability — ${partType} attachments are not supported`,\n      { context: { modelName } },\n    );\n  }\n}\n\n/**\n * When the caller supplied an `output` schema, resolve two artifacts:\n *\n * - `responseSchema` — extracted JSON Schema to attach on every trip.\n *   Adapters that natively support structured output (OpenAI's\n *   `response_format: json_schema`) consume it; others ignore it.\n * - `instruction` — a soft fallback appended to the system prompt\n *   **only** for models without native structured-output capability.\n *   Capable adapters skip it to save tokens and avoid redundancy.\n */\nfunction resolveStructuredOutput(params: {\n  outputSchema?: StandardSchemaV1<unknown>;\n  overrideResponseSchema?: Record<string, unknown>;\n  modelSupportsStructuredOutput: boolean;\n}): {\n  responseSchema?: Record<string, unknown>;\n  instruction?: string;\n} {\n  const { outputSchema, overrideResponseSchema, modelSupportsStructuredOutput } = params;\n\n  if (!outputSchema) {\n    return {};\n  }\n\n  const responseSchema = overrideResponseSchema ?? extractJsonSchema(outputSchema);\n\n  if (modelSupportsStructuredOutput) {\n    return { responseSchema };\n  }\n\n  const schemaHint = responseSchema\n    ? `\\n\\nThe response MUST match this JSON Schema:\\n${JSON.stringify(responseSchema, null, 2)}`\n    : \"\";\n\n  const instruction = [\n    \"You MUST respond with a single valid JSON value only.\",\n    \"Do not wrap it in markdown code fences. Do not include prose, commentary, or explanation — JSON only.\",\n    schemaHint,\n  ]\n    .join(\"\")\n    .trim();\n\n  return { responseSchema, instruction };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,eAAsB,wBAAiC,QAIpB;CACjC,MAAM,EAAE,QAAQ,OAAO,YAAY;CAEnC,MAAM,eAA6B;EACjC,GAAG,OAAO;EACV,GAAG,SAAS;CACd;CAEA,MAAM,eAAe,SAAS,gBAAgB,OAAO;CACrD,IAAI,gBAAgB;CACpB,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO,iBAAiB,UAC1B,gBAAgB;MACX,IAAI,cAAc;EAKvB,IAAI,OAAO,aAAa,gBAAgB,YACtC,MAAM,aAAa,YAAY;EAGjC,gBAAgB,aAAa,QAAQ,YAAY;EAOjD,MAAM,OAAO,aAAa,KAAK;EAE/B,IAAI,MAAM,MAAM;GACd,aAAa,KAAK;GAClB,gBAAgB,KAAK,WAAW;EAClC;CACF;CAEA,MAAM,EAAE,gBAAgB,gBAAgB,wBAAwB;EAC9D,cAAc,SAAS,UAAU,OAAO;EACxC,wBAAwB,SAAS;EACjC,+BAA+B,QAAQ,OAAO,MAAM,cAAc,gBAAgB;CACpF,CAAC;CAED,IAAI,aACF,gBAAgB,gBAAgB,GAAG,cAAc,MAAM,gBAAgB;CAGzE,MAAM,WAAsB,CAAC;CAE7B,IAAI,eACF,SAAS,KAAK;EAAE,MAAM;EAAU,SAAS;CAAc,CAAC;CAG1D,IAAI,SAAS,SACX,SAAS,KAAK,GAAG,QAAQ,OAAO;CAGlC,MAAM,cAAc,MAAM,wBAAwB;EAChD;EACA,aAAa,SAAS;EACtB,kBAAkB,SAAS,oBAAoB,OAAO;EACtD,WAAW,OAAO,MAAM;EACxB,qBAAqB,QAAQ,OAAO,MAAM,cAAc,MAAM;EAC9D,kBAAkB,QAAQ,OAAO,MAAM,cAAc,GAAG;EACxD,oBAAoB,QAAQ,OAAO,MAAM,cAAc,KAAK;CAC9D,CAAC;CAED,SAAS,KAAK;EAAE,MAAM;EAAQ,SAAS;CAAY,CAAC;CAEpD,OAAO;EACL;EACA;EACA,cAAc,iBAAiB;EAC/B;EACA;CACF;AACF;;;;;;;AAQA,eAAe,wBAAwB,QAQH;CAClC,MAAM,EACJ,OACA,aACA,kBACA,WACA,qBACA,kBACA,uBACE;CAEJ,IAAI,CAAC,eAAe,YAAY,WAAW,GACzC,OAAO;CAGT,MAAM,QAAuB,MAAM,QAAQ,IACzC,YAAY,KAAK,eAAe,sBAAsB,YAAY,gBAAgB,CAAC,CACrF;CAKA,eAAe,OAAO,SAAS,qBAAqB,UAAU,SAAS;CACvE,eAAe,OAAO,OAAO,kBAAkB,OAAO,SAAS;CAC/D,eAAe,OAAO,SAAS,oBAAoB,SAAS,SAAS;CAErE,OAAO,CAAC;EAAE,MAAM;EAAQ,MAAM;CAAM,GAAG,GAAG,KAAK;AACjD;;AAGA,SAAS,eACP,OACA,UACA,WACA,YACA,WACM;CACN,IAAI,CAAC,aAAa,MAAM,MAAM,SAAS,KAAK,SAAS,QAAQ,GAC3D,MAAM,IAAI,oBACR,UAAU,UAAU,qBAAqB,WAAW,gBAAgB,SAAS,iCAC7E,EAAE,SAAS,EAAE,UAAU,EAAE,CAC3B;AAEJ;;;;;;;;;;;AAYA,SAAS,wBAAwB,QAO/B;CACA,MAAM,EAAE,cAAc,wBAAwB,kCAAkC;CAEhF,IAAI,CAAC,cACH,OAAO,CAAC;CAGV,MAAM,iBAAiB,0BAA0B,kBAAkB,YAAY;CAE/E,IAAI,+BACF,OAAO,EAAE,eAAe;CAe1B,OAAO;EAAE;EAAgB,aARL;GAClB;GACA;GANiB,iBACf,kDAAkD,KAAK,UAAU,gBAAgB,MAAM,CAAC,MACxF;EAMJ,CAAC,CACE,KAAK,EAAE,CAAC,CACR,KAEgC;CAAE;AACvC"}