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.
- package/README.md +62 -16
- package/package.json +1 -1
- package/src/agent/agent.ts +96 -26
- package/src/agent/architecture.md +15 -12
- package/src/agent/context.ts +1 -0
- package/src/agent/defaults.ts +88 -0
- package/src/agent/delegation.ts +8 -3
- package/src/agent/loop.ts +34 -13
- package/src/agent/subagents-tool.ts +14 -21
- package/src/agent/usage-limit.ts +92 -0
- package/src/agent/worker.ts +17 -3
- package/src/agent/workers.ts +13 -1
- package/src/architecture.md +1 -0
- package/src/index.ts +9 -9
- package/src/prompts/additional-context.md +2 -0
- package/src/prompts/architecture.md +19 -0
- package/src/prompts/prompts.ts +81 -0
- package/src/prompts/reasoning-directive.md +5 -0
- package/src/prompts/structured-output.md +2 -0
- package/src/prompts/tool-bridge.md +1 -0
- package/src/prompts/tool-interrupted.md +1 -0
- package/src/prompts/tool-not-found.md +1 -0
- package/src/prompts/tool-repeat-blocked.md +1 -0
- package/src/prompts/tool-schema-error.md +1 -0
- package/src/prompts/tool-truncated-arguments.md +1 -0
- package/src/prompts/worker-failed.md +1 -0
- package/src/prompts/worker-replied.md +2 -0
- package/src/providers/architecture.md +8 -8
- package/src/providers/google.ts +82 -11
- package/src/providers/openai-compat.ts +17 -7
- package/src/providers/openai.ts +10 -4
- package/src/providers/openrouter.ts +185 -18
- package/src/providers.ts +59 -9
- package/src/scheduler/architecture.md +7 -7
- package/src/scheduler/scheduler.ts +40 -0
- package/src/scheduler/tool.ts +5 -9
- package/src/session/journal.ts +1 -0
- package/src/session/store.ts +4 -0
- package/src/tools/architecture.md +7 -6
- package/src/tools/executor.ts +60 -15
- package/src/tools/tool-content.ts +32 -0
- package/src/types/agent.ts +40 -6
- package/src/types/core.ts +4 -0
- package/src/types/message.ts +7 -0
- package/src/types/model.ts +4 -1
- package/src/types/scheduler.ts +4 -4
- package/src/types/tool.ts +2 -0
- package/src/utils/errors.ts +3 -3
- package/src/utils/headers.ts +1 -1
- /package/src/{scheduler/scheduled-actions.md → prompts/scheduled-actions-tool.md} +0 -0
- /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 @@
|
|
|
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}}
|
|
@@ -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
|
|
package/src/providers/google.ts
CHANGED
|
@@ -29,7 +29,14 @@ import type {
|
|
|
29
29
|
ProviderGenerateResult,
|
|
30
30
|
ProviderRawData,
|
|
31
31
|
} from "../types/model.ts";
|
|
32
|
-
import type {
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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.
|
package/src/providers/openai.ts
CHANGED
|
@@ -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(
|
|
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;
|