agent-accelerator 0.6.0 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +62 -16
  2. package/package.json +1 -1
  3. package/src/agent/agent.ts +96 -26
  4. package/src/agent/architecture.md +15 -12
  5. package/src/agent/context.ts +1 -0
  6. package/src/agent/defaults.ts +88 -0
  7. package/src/agent/delegation.ts +8 -3
  8. package/src/agent/loop.ts +34 -13
  9. package/src/agent/subagents-tool.ts +14 -21
  10. package/src/agent/usage-limit.ts +92 -0
  11. package/src/agent/worker.ts +17 -3
  12. package/src/agent/workers.ts +13 -1
  13. package/src/architecture.md +1 -0
  14. package/src/index.ts +9 -9
  15. package/src/prompts/additional-context.md +2 -0
  16. package/src/prompts/architecture.md +19 -0
  17. package/src/prompts/prompts.ts +81 -0
  18. package/src/prompts/reasoning-directive.md +5 -0
  19. package/src/prompts/structured-output.md +2 -0
  20. package/src/prompts/tool-bridge.md +1 -0
  21. package/src/prompts/tool-interrupted.md +1 -0
  22. package/src/prompts/tool-not-found.md +1 -0
  23. package/src/prompts/tool-repeat-blocked.md +1 -0
  24. package/src/prompts/tool-schema-error.md +1 -0
  25. package/src/prompts/tool-truncated-arguments.md +1 -0
  26. package/src/prompts/worker-failed.md +1 -0
  27. package/src/prompts/worker-replied.md +2 -0
  28. package/src/providers/architecture.md +8 -8
  29. package/src/providers/google.ts +82 -11
  30. package/src/providers/openai-compat.ts +17 -7
  31. package/src/providers/openai.ts +10 -4
  32. package/src/providers/openrouter.ts +185 -18
  33. package/src/providers.ts +59 -9
  34. package/src/scheduler/architecture.md +7 -7
  35. package/src/scheduler/scheduler.ts +40 -0
  36. package/src/scheduler/tool.ts +5 -9
  37. package/src/session/journal.ts +1 -0
  38. package/src/session/store.ts +4 -0
  39. package/src/tools/architecture.md +7 -6
  40. package/src/tools/executor.ts +60 -15
  41. package/src/tools/tool-content.ts +32 -0
  42. package/src/types/agent.ts +40 -6
  43. package/src/types/core.ts +4 -0
  44. package/src/types/message.ts +7 -0
  45. package/src/types/model.ts +4 -1
  46. package/src/types/scheduler.ts +4 -4
  47. package/src/types/tool.ts +2 -0
  48. package/src/utils/errors.ts +3 -3
  49. package/src/utils/headers.ts +1 -1
  50. /package/src/{scheduler/scheduled-actions.md → prompts/scheduled-actions-tool.md} +0 -0
  51. /package/src/{agent/subagents.md → prompts/subagents-tool.md} +0 -0
@@ -0,0 +1,5 @@
1
+ [Reasoning Directive]
2
+
3
+ 1. Use internal reasoning strictly for private planning and step-by-step thinking.
4
+ 2. Never attempt to execute tools or output final user deliverables inside reasoning.
5
+ 3. Once your reasoning is complete, output your final response or function calls directly in the standard response output.
@@ -0,0 +1,2 @@
1
+ Return ONLY valid JSON matching this JSON Schema, with no markdown fences, no commentary, no extra text.
2
+ Schema: {{schema}}
@@ -0,0 +1 @@
1
+ I have processed the tool results.
@@ -0,0 +1 @@
1
+ Error: tool_call '{{tool}}' ({{id}}) has no result because the run was interrupted before it completed (recovered by ensureToolPairing). It may have partially executed: check the tool trace before repeating side-effecting work.
@@ -0,0 +1 @@
1
+ Error: Tool '{{tool}}' not found.{{suggestion}}
@@ -0,0 +1 @@
1
+ repeat_blocked: Tool '{{tool}}' was called again with identical arguments. The previous result is already available; reuse it or call the tool with different arguments.
@@ -0,0 +1 @@
1
+ Schema validation error for tool '{{tool}}': {{detail}} Received argument keys: [{{keys}}].{{expected}} Fix the arguments and call the tool again with corrected JSON. Do not explain the error, just retry the call.
@@ -0,0 +1 @@
1
+ Error: Tool '{{tool}}' arguments are truncated or incomplete and could not be parsed. Call the tool again with the complete JSON arguments. Do not explain the error, just retry the call.
@@ -0,0 +1 @@
1
+ [worker {{id}}] failed: {{reason}}
@@ -0,0 +1,2 @@
1
+ [worker {{id}}] replied:
2
+ {{text}}
@@ -2,14 +2,14 @@
2
2
 
3
3
  All HTTP and wire parsing. Canonical mappers (thinking, cache, tool choice, structured output) live in `../providers.ts`, never here.
4
4
 
5
- | File | Wire | Notes |
6
- | :----------------- | :--------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
7
- | `google.ts` | Google Interactions | Stateful chaining (TTL gate, self-heal on `not_found`), exact replay of raw `steps`, media consts at the top, Gemini 4 long decode as background interactions. |
8
- | `openai.ts` | OpenAI Responses | Stateless (`store:false`), full history every turn. |
9
- | `openrouter.ts` | OpenRouter Chat Completions | Every request streams (so it can be cancelled); `generate()` collects its own stream. |
10
- | `openai-compat.ts` | OpenAI-compatible Chat Completions | Serves every custom prefix; strict subset of fields; no default base URL. |
11
- | `registry.ts` | - | Routes `provider/model` to an adapter; the registry is immutable; `ModelProvider` helpers. |
12
- | `shared.ts` | - | Helpers every adapter uses: signals and timers, error payloads, ids, retry-after, SSE scaffold (`linkStreamAbort`, `withAbortTimer`, `readSseData`, `incompleteStreamCheck`, `failStream`). |
5
+ | File | Wire | Notes |
6
+ | :----------------- | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | `google.ts` | Google Interactions | Stateful chaining (TTL gate, self-heal on `not_found`), exact replay of raw `steps`, media consts at the top, Gemini 4 long decode as background interactions. The inline size checks (`assertGoogleInlineSizes`) exist but are not called yet. A video part's `processing` (`static`/`agentic`) is sent as the block's `processing` field. |
8
+ | `openai.ts` | OpenAI Responses | Stateless (`store:false`), full history every turn. |
9
+ | `openrouter.ts` | OpenRouter Chat Completions | Every request streams (so it can be cancelled); `generate()` collects its own stream. A turn's `reasoning_details` blocks are kept as `providerSteps` and sent back verbatim on the next request; parsed-PDF `file` annotations travel the same way, so a PDF is not parsed twice. Routing, `parallel_tool_calls`, `reasoning.exclude` and `user` come from `OPENROUTER_*` env vars (see the file header), never from AgentConfig. |
10
+ | `openai-compat.ts` | OpenAI-compatible Chat Completions | Serves every custom prefix; strict subset of fields; no default base URL. |
11
+ | `registry.ts` | - | Routes `provider/model` to an adapter; the registry is immutable; `ModelProvider` helpers. |
12
+ | `shared.ts` | - | Helpers every adapter uses: signals and timers, error payloads, ids, retry-after, SSE scaffold (`linkStreamAbort`, `withAbortTimer`, `readSseData`, `incompleteStreamCheck`, `failStream`). |
13
13
 
14
14
  ## Contract (`Provider`, in `../types/model.ts`)
15
15
 
@@ -29,7 +29,14 @@ import type {
29
29
  ProviderGenerateResult,
30
30
  ProviderRawData,
31
31
  } from "../types/model.ts";
32
- import type { ProviderContext, ContentPart, ProviderSteps, ToolResultPart, MediaResolution } from "../types/message.ts";
32
+ import type {
33
+ ProviderContext,
34
+ ContentPart,
35
+ ProviderSteps,
36
+ ToolResultPart,
37
+ MediaResolution,
38
+ VideoProcessing,
39
+ } from "../types/message.ts";
33
40
  import type { StandardToolDeclaration, ToolCallRecord } from "../types/tool.ts";
34
41
  import type { TokenUsage } from "../types/core.ts";
35
42
  import { AssistantMessageEventStream } from "../streaming/event-stream.ts";
@@ -156,7 +163,8 @@ export const GOOGLE_INLINE_MIME_ALIASES: Readonly<Record<string, string>> = {
156
163
  "audio/wave": "audio/wav",
157
164
  "image/jpg": "image/jpeg",
158
165
  };
159
- // Documented size limits. Not enforced yet: oversized inputs get Google's own error.
166
+ // Documented size limits (file-input-methods.md). `assertGoogleInlineSizes` below checks them, but it is NOT called yet:
167
+ // the owner is not sure how Files API uploads should be handled, so oversized inputs still get Google's own error for now.
160
168
  /** Max inline payload per request (bytes). */
161
169
  export const GOOGLE_MAX_INLINE_REQUEST_BYTES = 100 * 1024 * 1024;
162
170
  /** Max inline PDF size (bytes). */
@@ -217,7 +225,8 @@ const INLINE_MIME: Record<MediaBlockType, readonly string[]> = {
217
225
  document: GOOGLE_DOCUMENT_MIME,
218
226
  };
219
227
 
220
- const FILES_API_PREFIX = "https://generativelanguage.googleapis.com/v1beta/files/";
228
+ // A Google Files API link on either API version (`/v1/files/<id>` or `/v1beta/files/<id>`); the host is exact, so no other URL counts.
229
+ const FILES_API_URL = /^https:\/\/generativelanguage\.googleapis\.com\/v1(?:beta)?\/files\/[^/\s]+/;
221
230
  const YOUTUBE_URL = /^https:\/\/(www\.|m\.)?(youtube\.com|youtu\.be)\//i;
222
231
 
223
232
  // ---------------------------------------------------------------------------
@@ -230,6 +239,7 @@ type MediaBlock = {
230
239
  data?: string;
231
240
  uri?: string;
232
241
  resolution?: MediaResolution;
242
+ processing?: VideoProcessing;
233
243
  };
234
244
  type InteractionContentBlock = { type: "text"; text: string } | MediaBlock;
235
245
 
@@ -499,7 +509,7 @@ function uriBlock(type: MediaBlockType, value: string, mimeType?: string): Media
499
509
  const withMime = (mime?: string): MediaBlock => ({ type, uri: value, ...(mime ? { mime_type: mime } : {}) });
500
510
  if (type === "video" && YOUTUBE_URL.test(value)) return withMime(explicit);
501
511
  const mime = explicit ?? bareMime(inferMimeType(value, ""));
502
- if (value.startsWith(FILES_API_PREFIX) || value.startsWith("gs://")) return withMime(mime);
512
+ if (FILES_API_URL.test(value) || value.startsWith("gs://")) return withMime(mime);
503
513
  return value.startsWith("https://") && mime && (GOOGLE_URL_MIME as readonly string[]).includes(mime)
504
514
  ? withMime(mime)
505
515
  : undefined;
@@ -535,11 +545,63 @@ async function contentPartsToBlocks(parts: ContentPart[]): Promise<InteractionCo
535
545
  const block = uri ?? (await inlineBlock(type, value, part.mimeType));
536
546
  // Per-item detail level (image and video blocks only, api-ref `MediaResolution`).
537
547
  const resolution = part.type === "image" || part.type === "video" ? part.resolution : undefined;
538
- blocks.push(resolution ? { ...block, resolution } : block);
548
+ // Per-video processing mode (`static` | `agentic`, api-ref `MediaProcessing`): the model may then add `processing_call` / `processing_result` steps, which the raw-steps replay keeps like any other step.
549
+ const processing = part.type === "video" ? part.processing : undefined;
550
+ if (processing !== undefined && processing !== "static" && processing !== "agentic")
551
+ throw new ValidationError(
552
+ `[Agent Accelerator] Invalid video processing ${JSON.stringify(processing)}: expected "static" or "agentic".`
553
+ );
554
+ blocks.push({ ...block, ...(resolution ? { resolution } : {}), ...(processing ? { processing } : {}) });
539
555
  }
540
556
  return blocks;
541
557
  }
542
558
 
559
+ // Decoded size in bytes of a base64 string, without decoding it; in: assertGoogleInlineSizes, out: a byte count.
560
+ function base64Bytes(b64: string): number {
561
+ const clean = b64.replace(/\s/g, "");
562
+ const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
563
+ return Math.max(0, Math.floor((clean.length * 3) / 4) - padding);
564
+ }
565
+
566
+ // Every inline media block (`data` + `mime_type`) found anywhere in a request `input`, including blocks inside steps and function results; in: assertGoogleInlineSizes, out: the blocks.
567
+ function inlineBlocks(
568
+ value: unknown,
569
+ out: Array<{ mime?: string; bytes: number }> = []
570
+ ): Array<{ mime?: string; bytes: number }> {
571
+ if (Array.isArray(value)) {
572
+ for (const v of value) inlineBlocks(v, out);
573
+ } else if (value && typeof value === "object") {
574
+ const o = value as Record<string, unknown>;
575
+ if (typeof o["data"] === "string" && typeof o["mime_type"] === "string")
576
+ out.push({ mime: bareMime(o["mime_type"]), bytes: base64Bytes(o["data"]) });
577
+ else for (const v of Object.values(o)) inlineBlocks(v, out);
578
+ }
579
+ return out;
580
+ }
581
+
582
+ /**
583
+ * Fails before the network when the inline media of one request is over Google's documented limits: 100 MB in total and
584
+ * 50 MB for each PDF (larger files go through the Files API). Counts decoded bytes, over the whole `input` (the full
585
+ * history on a reseed). NOT called by the adapter yet (see the call sites in `generate` and `stream`).
586
+ */
587
+ export function assertGoogleInlineSizes(input: unknown, modelId: string): void {
588
+ const blocks = inlineBlocks(input);
589
+ const mb = (n: number): string => `${(n / (1024 * 1024)).toFixed(1)} MB`;
590
+ for (const b of blocks) {
591
+ if (b.mime === "application/pdf" && b.bytes > GOOGLE_MAX_INLINE_PDF_BYTES) {
592
+ throw new ValidationError(
593
+ `[Agent Accelerator] [google/${modelId}] A PDF of ${mb(b.bytes)} is over Google's ${mb(GOOGLE_MAX_INLINE_PDF_BYTES)} limit for inline PDFs. Upload it with the Files API and pass its link instead.`
594
+ );
595
+ }
596
+ }
597
+ const total = blocks.reduce((n, b) => n + b.bytes, 0);
598
+ if (total > GOOGLE_MAX_INLINE_REQUEST_BYTES) {
599
+ throw new ValidationError(
600
+ `[Agent Accelerator] [google/${modelId}] The inline media of this request is ${mb(total)}, over Google's ${mb(GOOGLE_MAX_INLINE_REQUEST_BYTES)} limit per request. Upload the large files with the Files API and pass their links instead.`
601
+ );
602
+ }
603
+ }
604
+
543
605
  // Keeps a turn's model-generated steps exactly as received (input echoes and empty signatures dropped); in: REST `steps` or assembled stream steps, out: ProviderGenerateResult.providerSteps.
544
606
  function replaySteps(steps: unknown[] | undefined): ProviderSteps | undefined {
545
607
  const kept = (steps ?? [])
@@ -575,13 +637,14 @@ function resultToText(result: unknown): string {
575
637
  return safeStringify(result);
576
638
  }
577
639
 
578
- // Builds the documented `function_result` step (`is_error` only when the tool failed); in: a canonical tool_result part, out: chained or full-history `input`.
579
- function functionResultStep(part: ToolResultPart): InteractionInputStep {
640
+ // Builds the documented `function_result` step (`is_error` only when the tool failed); text first, then the media the tool returned with `toolContent` (Gemini 3 accepts image and document blocks in `result`); in: a canonical tool_result part, out: chained or full-history `input`.
641
+ async function functionResultStep(part: ToolResultPart): Promise<InteractionInputStep> {
642
+ const media = part.parts && part.parts.length > 0 ? await contentPartsToBlocks(part.parts) : [];
580
643
  return {
581
644
  type: "function_result",
582
645
  name: part.name,
583
646
  call_id: part.id,
584
- result: [{ type: "text", text: resultToText(part.result) }],
647
+ result: [{ type: "text", text: resultToText(part.result) }, ...media],
585
648
  ...(part.isError ? { is_error: true } : {}),
586
649
  };
587
650
  }
@@ -625,7 +688,7 @@ async function latestTurnInput(
625
688
  if (!Array.isArray(m.content)) continue;
626
689
  for (const part of m.content) {
627
690
  if (part.type === "tool_result") {
628
- steps.push(functionResultStep(part));
691
+ steps.push(await functionResultStep(part));
629
692
  }
630
693
  }
631
694
  } else if (m.role === "user") {
@@ -715,7 +778,7 @@ async function fullHistorySteps(context: ProviderContext): Promise<InteractionIn
715
778
  if (!Array.isArray(m.content)) continue;
716
779
  for (const part of m.content) {
717
780
  if (part.type === "tool_result") {
718
- steps.push(functionResultStep(part));
781
+ steps.push(await functionResultStep(part));
719
782
  }
720
783
  }
721
784
  }
@@ -901,7 +964,11 @@ export class GoogleInteractionsProvider implements Provider {
901
964
  let effectiveSystemPrompt = context.systemPrompt;
902
965
  if (outputSpec && !modelSupportsStructuredOutput("google", modelId)) {
903
966
  outputEmulated = true;
904
- effectiveSystemPrompt = augmentSystemPromptWithStructuredOutput(context.systemPrompt, outputSpec);
967
+ effectiveSystemPrompt = augmentSystemPromptWithStructuredOutput(
968
+ context.systemPrompt,
969
+ outputSpec,
970
+ options?.prompts?.structuredOutput
971
+ );
905
972
  emitProviderWarning({
906
973
  provider: "google",
907
974
  capability: "structured output",
@@ -1108,6 +1175,8 @@ export class GoogleInteractionsProvider implements Provider {
1108
1175
  const sessionId = options?.sessionId;
1109
1176
  const tools = options?.tools as StandardToolDeclaration[] | undefined;
1110
1177
  let body = await this.buildBody(clean, context, options, sessionId, tools, false);
1178
+ // Size limits are implemented but switched off for now (the owner is unsure about Files API handling yet):
1179
+ // assertGoogleInlineSizes(body.input, clean);
1111
1180
 
1112
1181
  const makeRawRequest = (b: InteractionRequestBody): ProviderRawData["request"] => ({
1113
1182
  url,
@@ -1220,6 +1289,8 @@ export class GoogleInteractionsProvider implements Provider {
1220
1289
  const sessionId = options?.sessionId;
1221
1290
  const tools = options?.tools as StandardToolDeclaration[] | undefined;
1222
1291
  const body = await this.buildBody(clean, context, options, sessionId, tools, true);
1292
+ // Size limits are implemented but switched off for now (the owner is unsure about Files API handling yet):
1293
+ // assertGoogleInlineSizes(body.input, clean);
1223
1294
  evictSessionId = sessionId;
1224
1295
  usedChain = body.previous_interaction_id !== undefined;
1225
1296
 
@@ -60,6 +60,7 @@ import { safeStringify } from "../utils/serialization.ts";
60
60
  import { toConciseProviderError, assertModalitiesSupported } from "../utils/errors.ts";
61
61
  import { withRetries } from "../utils/retry.ts";
62
62
  import { ConfigError, ValidationError } from "../types/errors.ts";
63
+ import { DEFAULT_PROMPTS } from "../prompts/prompts.ts";
63
64
  import {
64
65
  parseArguments,
65
66
  normalizeThinkingParts,
@@ -96,6 +97,8 @@ import {
96
97
  normalizeStructuredOutput,
97
98
  augmentSystemPromptWithStructuredOutput,
98
99
  warnIgnoredMediaResolution,
100
+ warnIgnoredVideoProcessing,
101
+ warnIgnoredToolMedia,
99
102
  } from "../providers.ts";
100
103
 
101
104
  // ---------------------------------------------------------------------------
@@ -402,7 +405,10 @@ function resultToOutput(result: unknown): string {
402
405
  * `google` (or untagged legacy). Signatures minted by other providers never
403
406
  * leak onto this transport.
404
407
  */
405
- async function fullHistoryMessages(context: ProviderContext): Promise<ChatMessage[]> {
408
+ async function fullHistoryMessages(
409
+ context: ProviderContext,
410
+ bridge = DEFAULT_PROMPTS.toolBridge
411
+ ): Promise<ChatMessage[]> {
406
412
  const pairing = new Map<string, string>();
407
413
  for (const m of context.messages) {
408
414
  if (m.role === "assistant" && Array.isArray(m.content)) {
@@ -423,7 +429,7 @@ async function fullHistoryMessages(context: ProviderContext): Promise<ChatMessag
423
429
  if (m.role === "system") continue;
424
430
  if (m.role === "user") {
425
431
  if (lastRole === "tool") {
426
- messages.push({ role: "assistant", content: "I have processed the tool results." });
432
+ messages.push({ role: "assistant", content: bridge });
427
433
  }
428
434
  if (typeof m.content === "string") {
429
435
  if (m.content) {
@@ -725,6 +731,8 @@ export class OpenAICompatibleChatProvider implements Provider {
725
731
  // Strict endpoints reject unknown body fields: implicit caching only.
726
732
  resolveCache(this.prefix, options?.cache, ["implicit"], `${this.prefix}/${modelId}`);
727
733
  warnIgnoredMediaResolution(this.prefix, context.messages, `${this.prefix}/${modelId}`);
734
+ warnIgnoredVideoProcessing(this.prefix, context.messages, `${this.prefix}/${modelId}`);
735
+ warnIgnoredToolMedia(this.prefix, context.messages, `${this.prefix}/${modelId}`);
728
736
 
729
737
  // Structured outputs: native `response_format` when supported, else
730
738
  // emulated via system prompt (strict endpoints would 400 on json_schema
@@ -741,7 +749,11 @@ export class OpenAICompatibleChatProvider implements Provider {
741
749
  outputEmulated = true;
742
750
  effectiveContext = {
743
751
  ...context,
744
- systemPrompt: augmentSystemPromptWithStructuredOutput(context.systemPrompt, outputSpec),
752
+ systemPrompt: augmentSystemPromptWithStructuredOutput(
753
+ context.systemPrompt,
754
+ outputSpec,
755
+ options?.prompts?.structuredOutput
756
+ ),
745
757
  };
746
758
  emitProviderWarning({
747
759
  provider: this.prefix,
@@ -752,7 +764,7 @@ export class OpenAICompatibleChatProvider implements Provider {
752
764
  fallback: "emulated JSON instruction + client validation (no response_format is sent)",
753
765
  });
754
766
  }
755
- const messages = await fullHistoryMessages(effectiveContext);
767
+ const messages = await fullHistoryMessages(effectiveContext, options?.prompts?.toolBridge);
756
768
  const body: ChatRequestBody = {
757
769
  model: modelId,
758
770
  messages,
@@ -762,9 +774,7 @@ export class OpenAICompatibleChatProvider implements Provider {
762
774
  if (compatTools) body.tools = compatTools;
763
775
  // Standard Chat Completions generation fields, only when the caller set them.
764
776
  Object.assign(body, mapGenerationParamsToChat(options));
765
- const toolChoice = mapToolChoiceToOpenAI(
766
- options?.toolChoice as "auto" | "none" | "required" | { type: "function"; function: { name: string } } | undefined
767
- );
777
+ const toolChoice = mapToolChoiceToOpenAI(options?.toolChoice, this.prefix);
768
778
  if (toolChoice !== undefined) {
769
779
  // Chat Completions nests pinned names under `function`; the canonical
770
780
  // mapper emits the flat Responses shape, so normalize here.
@@ -91,6 +91,8 @@ import {
91
91
  augmentSystemPromptWithStructuredOutput,
92
92
  emitProviderWarning,
93
93
  warnIgnoredMediaResolution,
94
+ warnIgnoredVideoProcessing,
95
+ warnIgnoredToolMedia,
94
96
  } from "../providers.ts";
95
97
 
96
98
  // ---------------------------------------------------------------------------
@@ -551,6 +553,8 @@ export class OpenAIResponsesProvider implements Provider {
551
553
  // `12h` maps to `prompt_cache_retention: "24h"`; everything else stays on implicit prompt_cache_key affinity.
552
554
  const cache = resolveCache("openai", options?.cache, ["implicit", "12h"], `openai/${modelId}`);
553
555
  warnIgnoredMediaResolution("openai", context.messages, `openai/${modelId}`);
556
+ warnIgnoredVideoProcessing("openai", context.messages, `openai/${modelId}`);
557
+ warnIgnoredToolMedia("openai", context.messages, `openai/${modelId}`);
554
558
 
555
559
  // Structured outputs: native `text.format` when supported, else emulated
556
560
  // via instructions (catalog `structured_output: false` would 400).
@@ -564,7 +568,11 @@ export class OpenAIResponsesProvider implements Provider {
564
568
  }
565
569
  if (outputSpec && !modelSupportsStructuredOutput("openai", modelId)) {
566
570
  outputEmulated = true;
567
- effectiveSystemPrompt = augmentSystemPromptWithStructuredOutput(context.systemPrompt, outputSpec);
571
+ effectiveSystemPrompt = augmentSystemPromptWithStructuredOutput(
572
+ context.systemPrompt,
573
+ outputSpec,
574
+ options?.prompts?.structuredOutput
575
+ );
568
576
  emitProviderWarning({
569
577
  provider: "openai",
570
578
  capability: "structured output",
@@ -586,9 +594,7 @@ export class OpenAIResponsesProvider implements Provider {
586
594
  if (effectiveSystemPrompt) body.instructions = effectiveSystemPrompt;
587
595
  const aiTools = toOpenAITools(tools);
588
596
  if (aiTools) body.tools = aiTools;
589
- const toolChoice = mapToolChoiceToOpenAI(
590
- options?.toolChoice as "auto" | "none" | "required" | { type: "function"; function: { name: string } } | undefined
591
- );
597
+ const toolChoice = mapToolChoiceToOpenAI(options?.toolChoice);
592
598
  if (toolChoice !== undefined) body.tool_choice = toolChoice;
593
599
  if (effort) body.reasoning = { effort };
594
600
  if (serviceTier) body.service_tier = serviceTier;