agent-accelerator 0.5.0 → 0.5.2
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 +66 -59
- package/package.json +7 -14
- package/src/agent/agent.ts +14 -49
- package/src/agent/context.ts +5 -4
- package/src/agent/delegation.ts +1 -9
- package/src/agent/loop.ts +22 -84
- package/src/agent/subagent.ts +1 -2
- package/src/agent/worker.ts +3 -6
- package/src/index.ts +6 -41
- package/src/providers/google.ts +390 -154
- package/src/providers/openai-compat.ts +12 -27
- package/src/providers/openai.ts +13 -35
- package/src/providers/openrouter.ts +11 -29
- package/src/providers.ts +32 -124
- package/src/session/store.ts +18 -29
- package/src/types/agent.ts +10 -15
- package/src/types/core.ts +5 -32
- package/src/types/message.ts +15 -1
- package/src/types/model.ts +7 -3
- package/src/types/response.ts +5 -0
- package/src/utils/cache.ts +1 -137
- package/src/utils/errors.ts +1 -4
- package/src/utils/headers.ts +3 -19
- package/src/utils/retry.ts +2 -1
- package/src/utils/safe-io.ts +2 -2
- package/src/utils/documents.ts +0 -577
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
* (groq, ollama, …) fail unknown body properties, so anything unsupported
|
|
19
19
|
* is warn+drop, never sent.
|
|
20
20
|
* - `text`, `image_url`, and `input_audio` parts are sent; `video`/`file`
|
|
21
|
-
* parts fail fast with a one-line error naming the remedy
|
|
22
|
-
*
|
|
23
|
-
* confusing provider 400s.
|
|
21
|
+
* parts fail fast with a one-line error naming the remedy. The old transport
|
|
22
|
+
* let these through to confusing provider 400s.
|
|
24
23
|
* - Prior-turn reasoning is NOT resent (chat history is messages + tool calls
|
|
25
24
|
* only), except echoed Gemini thought signatures (see below).
|
|
26
25
|
* - Gemini models served through OpenAI-compatible endpoints keep working:
|
|
@@ -81,7 +80,7 @@ import {
|
|
|
81
80
|
} from "./shared.ts";
|
|
82
81
|
import { getModelFromCatalog, createGenericModelSpec, modelSupportsStructuredOutput } from "../models/catalog.ts";
|
|
83
82
|
import {
|
|
84
|
-
|
|
83
|
+
resolveCache,
|
|
85
84
|
mapToolChoiceToOpenAI,
|
|
86
85
|
emitProviderWarning,
|
|
87
86
|
noteProviderTurn,
|
|
@@ -91,6 +90,7 @@ import {
|
|
|
91
90
|
mapGenerationParamsToChat,
|
|
92
91
|
normalizeStructuredOutput,
|
|
93
92
|
augmentSystemPromptWithStructuredOutput,
|
|
93
|
+
warnIgnoredMediaResolution,
|
|
94
94
|
} from "../providers.ts";
|
|
95
95
|
|
|
96
96
|
// ---------------------------------------------------------------------------
|
|
@@ -182,13 +182,6 @@ interface ChatResponse {
|
|
|
182
182
|
[k: string]: unknown;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
/** Headers that must never leak onto native REST requests. */
|
|
186
|
-
const INTERNAL_HEADERS = new Set([
|
|
187
|
-
"x-thought-signature-map",
|
|
188
|
-
"x-cached-content-id",
|
|
189
|
-
"x-multimodal-user-content",
|
|
190
|
-
]);
|
|
191
|
-
|
|
192
185
|
// ---------------------------------------------------------------------------
|
|
193
186
|
// Request building (canonical -> Chat Completions)
|
|
194
187
|
// ---------------------------------------------------------------------------
|
|
@@ -345,7 +338,7 @@ function assertNoVideoOrFileParts(
|
|
|
345
338
|
const message =
|
|
346
339
|
kind === "video"
|
|
347
340
|
? `[${prefix}/${modelId}] unsupported video input (generic OpenAI-compatible endpoints carry text/image/audio only). Use a video-capable model (e.g. google/gemini-*) or drop the video part.`
|
|
348
|
-
: `[${prefix}/${modelId}] unsupported file input (generic OpenAI-compatible endpoints carry text/image/audio only).
|
|
341
|
+
: `[${prefix}/${modelId}] unsupported file input (generic OpenAI-compatible endpoints carry text/image/audio only). Use a file-capable model (e.g. google/gemini-*) or convert the document to text before sending it.`;
|
|
349
342
|
const err = new Error(message);
|
|
350
343
|
err.name = "AgentAccelProviderError";
|
|
351
344
|
Object.defineProperties(err, {
|
|
@@ -710,7 +703,9 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
710
703
|
fallback: "standard routing (no service_tier payload is sent)",
|
|
711
704
|
});
|
|
712
705
|
}
|
|
713
|
-
|
|
706
|
+
// Strict endpoints reject unknown body fields: implicit caching only.
|
|
707
|
+
resolveCache(this.prefix, options?.cache, ["implicit"], `${this.prefix}/${modelId}`);
|
|
708
|
+
warnIgnoredMediaResolution(this.prefix, context.messages, `${this.prefix}/${modelId}`);
|
|
714
709
|
|
|
715
710
|
// Structured outputs: native `response_format` when supported, else
|
|
716
711
|
// emulated via system prompt (strict endpoints would 400 on json_schema
|
|
@@ -796,13 +791,9 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
796
791
|
): RequestInit {
|
|
797
792
|
const headers: Record<string, string> = buildSessionHeaders(
|
|
798
793
|
this.prefix,
|
|
799
|
-
options?.cache,
|
|
800
794
|
options?.headers,
|
|
801
795
|
sessionId
|
|
802
796
|
);
|
|
803
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
804
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete headers[k];
|
|
805
|
-
}
|
|
806
797
|
// Strict-endpoint opt-out (pi sendSessionAffinityHeaders pattern):
|
|
807
798
|
// `COMPAT_NO_SESSION_HEADERS=1` or per-call `sendSessionHeaders:false`
|
|
808
799
|
// drops affinity headers that trigger CORS preflights / strict 400s.
|
|
@@ -891,18 +882,15 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
891
882
|
const baseUrl = this.baseUrl(options);
|
|
892
883
|
const apiKey = this.requireApiKey(baseUrl, options);
|
|
893
884
|
const url = `${baseUrl}/chat/completions`;
|
|
894
|
-
const sessionId = options?.sessionId
|
|
885
|
+
const sessionId = options?.sessionId;
|
|
895
886
|
const tools = options?.tools as StandardToolDeclaration[] | undefined;
|
|
896
887
|
const body = await this.buildBody(clean, context, options, tools, false);
|
|
897
888
|
|
|
898
889
|
const auditRaw: Record<string, string> = {
|
|
899
|
-
...buildSessionHeaders(this.prefix, options?.
|
|
890
|
+
...buildSessionHeaders(this.prefix, options?.headers, sessionId),
|
|
900
891
|
"Content-Type": "application/json",
|
|
901
892
|
...(apiKey ? { Authorization: "[REDACTED]" } : {}),
|
|
902
893
|
};
|
|
903
|
-
for (const k of Object.keys(auditRaw)) {
|
|
904
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete auditRaw[k];
|
|
905
|
-
}
|
|
906
894
|
const rawRequest = {
|
|
907
895
|
url,
|
|
908
896
|
method: "POST",
|
|
@@ -986,18 +974,15 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
986
974
|
const baseUrl = this.baseUrl(options);
|
|
987
975
|
const apiKey = this.requireApiKey(baseUrl, options);
|
|
988
976
|
const url = `${baseUrl}/chat/completions`;
|
|
989
|
-
const sessionId = options?.sessionId
|
|
977
|
+
const sessionId = options?.sessionId;
|
|
990
978
|
const tools = options?.tools as StandardToolDeclaration[] | undefined;
|
|
991
979
|
const body = await this.buildBody(clean, context, options, tools, true);
|
|
992
980
|
|
|
993
981
|
const streamAuditRaw: Record<string, string> = {
|
|
994
|
-
...buildSessionHeaders(prefix, options?.
|
|
982
|
+
...buildSessionHeaders(prefix, options?.headers, sessionId),
|
|
995
983
|
"Content-Type": "application/json",
|
|
996
984
|
...(apiKey ? { Authorization: "[REDACTED]" } : {}),
|
|
997
985
|
};
|
|
998
|
-
for (const k of Object.keys(streamAuditRaw)) {
|
|
999
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete streamAuditRaw[k];
|
|
1000
|
-
}
|
|
1001
986
|
const rawRequest = {
|
|
1002
987
|
url,
|
|
1003
988
|
method: "POST",
|
package/src/providers/openai.ts
CHANGED
|
@@ -75,7 +75,7 @@ import { getModelFromCatalog, getModelsForProvider, modelSupportsStructuredOutpu
|
|
|
75
75
|
import {
|
|
76
76
|
mapThinkingLevelToOpenAI,
|
|
77
77
|
mapServiceTierToOpenAI,
|
|
78
|
-
|
|
78
|
+
resolveCache,
|
|
79
79
|
mapToolChoiceToOpenAI,
|
|
80
80
|
noteProviderTurn,
|
|
81
81
|
parseStreamedToolArguments,
|
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
normalizeStructuredOutput,
|
|
86
86
|
augmentSystemPromptWithStructuredOutput,
|
|
87
87
|
emitProviderWarning,
|
|
88
|
+
warnIgnoredMediaResolution,
|
|
88
89
|
} from "../providers.ts";
|
|
89
90
|
|
|
90
91
|
// ---------------------------------------------------------------------------
|
|
@@ -156,19 +157,6 @@ interface ResponsesObject {
|
|
|
156
157
|
|
|
157
158
|
const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
158
159
|
|
|
159
|
-
/** Q-18: Responses transport never echoes thought signatures (no wire field).
|
|
160
|
-
* Local helper documents the namespacing decision per provider file. */
|
|
161
|
-
function withProviderSignature(_sig: string | undefined): { thoughtSignature?: undefined } {
|
|
162
|
-
return {};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** Internal headers that must never leak onto native REST requests. */
|
|
166
|
-
const INTERNAL_HEADERS = new Set([
|
|
167
|
-
"x-thought-signature-map",
|
|
168
|
-
"x-cached-content-id",
|
|
169
|
-
"x-multimodal-user-content",
|
|
170
|
-
]);
|
|
171
|
-
|
|
172
160
|
// ---------------------------------------------------------------------------
|
|
173
161
|
// Request building (canonical -> Responses)
|
|
174
162
|
// ---------------------------------------------------------------------------
|
|
@@ -535,7 +523,9 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
535
523
|
): Promise<ResponsesRequestBody> {
|
|
536
524
|
const { effort } = mapThinkingLevelToOpenAI(options?.thinking?.level);
|
|
537
525
|
const serviceTier = mapServiceTierToOpenAI(options?.serviceTier);
|
|
538
|
-
|
|
526
|
+
// `12h` maps to `prompt_cache_retention: "24h"`; everything else stays on implicit prompt_cache_key affinity.
|
|
527
|
+
const cache = resolveCache("openai", options?.cache, ["implicit", "12h"], `openai/${modelId}`);
|
|
528
|
+
warnIgnoredMediaResolution("openai", context.messages, `openai/${modelId}`);
|
|
539
529
|
|
|
540
530
|
// Structured outputs: native `text.format` when supported, else emulated
|
|
541
531
|
// via instructions (catalog `structured_output: false` would 400).
|
|
@@ -598,10 +588,8 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
598
588
|
const cacheKey = clampCacheKey(sessionId);
|
|
599
589
|
if (cacheKey) body.prompt_cache_key = cacheKey;
|
|
600
590
|
}
|
|
601
|
-
// Long retention: `prompt_cache_retention: "24h"` when the catalog
|
|
602
|
-
|
|
603
|
-
// stay warn+drop via applyCacheForOpenAI above.
|
|
604
|
-
if (options?.cache?.retention === "long") {
|
|
591
|
+
// Long retention: `prompt_cache_retention: "24h"` when the catalog supports it.
|
|
592
|
+
if (cache === "12h") {
|
|
605
593
|
let supportsLong = true;
|
|
606
594
|
try {
|
|
607
595
|
const spec = getModelFromCatalog("openai", modelId);
|
|
@@ -614,8 +602,8 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
614
602
|
} else {
|
|
615
603
|
emitProviderWarning({
|
|
616
604
|
provider: "openai",
|
|
617
|
-
capability: "cache
|
|
618
|
-
requested: `
|
|
605
|
+
capability: "cache",
|
|
606
|
+
requested: `12h (openai/${modelId})`,
|
|
619
607
|
reason: "the catalog reports no long-cache support for this model.",
|
|
620
608
|
fallback: "default caching with prompt_cache_key affinity (no retention payload is sent)",
|
|
621
609
|
});
|
|
@@ -635,13 +623,9 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
635
623
|
): RequestInit {
|
|
636
624
|
const headers: Record<string, string> = buildSessionHeaders(
|
|
637
625
|
"openai",
|
|
638
|
-
options?.cache,
|
|
639
626
|
options?.headers,
|
|
640
627
|
sessionId
|
|
641
628
|
);
|
|
642
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
643
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete headers[k];
|
|
644
|
-
}
|
|
645
629
|
// Case-insensitive merge so a lowercase `authorization` custom header
|
|
646
630
|
// never coexists with the canonical bearer. Proxy flows
|
|
647
631
|
// (`apiKey === "unused"`) preserve custom auth instead of overwriting.
|
|
@@ -723,7 +707,7 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
723
707
|
assertNoVideoPartsOnResponses(context, "openai", clean);
|
|
724
708
|
const baseUrl = resolveBaseUrl(options);
|
|
725
709
|
const url = `${baseUrl}/responses`;
|
|
726
|
-
const sessionId = options?.sessionId
|
|
710
|
+
const sessionId = options?.sessionId;
|
|
727
711
|
const tools = options?.tools as StandardToolDeclaration[] | undefined;
|
|
728
712
|
const body = await this.buildBody(clean, context, options, sessionId, tools, false);
|
|
729
713
|
|
|
@@ -731,13 +715,10 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
731
715
|
// redacted. Previously only Content-Type + custom headers were stored,
|
|
732
716
|
// hiding the x-session-id affinity actually sent.
|
|
733
717
|
const auditHeadersRaw: Record<string, string> = {
|
|
734
|
-
...buildSessionHeaders("openai", options?.
|
|
718
|
+
...buildSessionHeaders("openai", options?.headers, sessionId),
|
|
735
719
|
"Content-Type": "application/json",
|
|
736
720
|
Authorization: "[REDACTED]",
|
|
737
721
|
};
|
|
738
|
-
for (const k of Object.keys(auditHeadersRaw)) {
|
|
739
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete auditHeadersRaw[k];
|
|
740
|
-
}
|
|
741
722
|
const auditHeaders = redactedHeaders(auditHeadersRaw);
|
|
742
723
|
const rawRequest = {
|
|
743
724
|
url,
|
|
@@ -825,18 +806,15 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
825
806
|
assertNoVideoPartsOnResponses(context, "openai", clean);
|
|
826
807
|
const baseUrl = resolveBaseUrl(options);
|
|
827
808
|
const url = `${baseUrl}/responses`;
|
|
828
|
-
const sessionId = options?.sessionId
|
|
809
|
+
const sessionId = options?.sessionId;
|
|
829
810
|
const tools = options?.tools as StandardToolDeclaration[] | undefined;
|
|
830
811
|
const body = await this.buildBody(clean, context, options, sessionId, tools, true);
|
|
831
812
|
|
|
832
813
|
const streamAuditRaw: Record<string, string> = {
|
|
833
|
-
...buildSessionHeaders("openai", options?.
|
|
814
|
+
...buildSessionHeaders("openai", options?.headers, sessionId),
|
|
834
815
|
"Content-Type": "application/json",
|
|
835
816
|
Authorization: "[REDACTED]",
|
|
836
817
|
};
|
|
837
|
-
for (const k of Object.keys(streamAuditRaw)) {
|
|
838
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete streamAuditRaw[k];
|
|
839
|
-
}
|
|
840
818
|
const rawRequest = {
|
|
841
819
|
url,
|
|
842
820
|
method: "POST",
|
|
@@ -67,7 +67,7 @@ import { getModelFromCatalog, getModelsForProvider, modelSupportsStructuredOutpu
|
|
|
67
67
|
import {
|
|
68
68
|
mapThinkingLevelToOpenRouterChat,
|
|
69
69
|
mapServiceTierToOpenRouter,
|
|
70
|
-
|
|
70
|
+
resolveCache,
|
|
71
71
|
mapToolChoiceToOpenRouterChat,
|
|
72
72
|
noteProviderTurn,
|
|
73
73
|
parseStreamedToolArguments,
|
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
normalizeStructuredOutput,
|
|
78
78
|
augmentSystemPromptWithStructuredOutput,
|
|
79
79
|
emitProviderWarning,
|
|
80
|
+
warnIgnoredMediaResolution,
|
|
80
81
|
} from "../providers.ts";
|
|
81
82
|
|
|
82
83
|
// ---------------------------------------------------------------------------
|
|
@@ -115,6 +116,7 @@ interface ChatRequestBody {
|
|
|
115
116
|
service_tier?: string;
|
|
116
117
|
session_id?: string;
|
|
117
118
|
plugins?: Array<{ id: string }>;
|
|
119
|
+
cache_control?: { type: "ephemeral"; ttl: "5m" | "1h" };
|
|
118
120
|
response_format?: { type: "json_schema"; json_schema: { name: string; schema: Record<string, unknown>; strict: boolean; description?: string } };
|
|
119
121
|
stream?: boolean;
|
|
120
122
|
[k: string]: unknown;
|
|
@@ -180,19 +182,6 @@ interface ChatResponse {
|
|
|
180
182
|
|
|
181
183
|
const DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
|
|
182
184
|
|
|
183
|
-
/** Q-18: Chat transport never echoes thought signatures (no wire field).
|
|
184
|
-
* Local helper documents the namespacing decision per provider file. */
|
|
185
|
-
function withProviderSignature(_sig: string | undefined): { thoughtSignature?: undefined } {
|
|
186
|
-
return {};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Internal headers that must never leak onto native REST requests. */
|
|
190
|
-
const INTERNAL_HEADERS = new Set([
|
|
191
|
-
"x-thought-signature-map",
|
|
192
|
-
"x-cached-content-id",
|
|
193
|
-
"x-multimodal-user-content",
|
|
194
|
-
]);
|
|
195
|
-
|
|
196
185
|
// ---------------------------------------------------------------------------
|
|
197
186
|
// Request building (canonical -> Chat Completions)
|
|
198
187
|
// ---------------------------------------------------------------------------
|
|
@@ -586,7 +575,9 @@ export class OpenRouterChatCompletionsProvider implements Provider {
|
|
|
586
575
|
): Promise<ChatRequestBody> {
|
|
587
576
|
const { effort } = mapThinkingLevelToOpenRouterChat(options?.thinking?.level);
|
|
588
577
|
const serviceTier = mapServiceTierToOpenRouter(options?.serviceTier);
|
|
589
|
-
|
|
578
|
+
// Anthropic models take a top-level `cache_control` TTL; every other route caches implicitly (docs: prompt-caching.md).
|
|
579
|
+
const cache = resolveCache("openrouter", options?.cache, modelId.startsWith("anthropic/") ? ["implicit", "5m", "1h"] : ["implicit"], `openrouter/${modelId}`);
|
|
580
|
+
warnIgnoredMediaResolution("openrouter", context.messages, `openrouter/${modelId}`);
|
|
590
581
|
|
|
591
582
|
// Structured outputs: native `response_format` when supported, else
|
|
592
583
|
// emulated via system-prompt instruction + client validation (catalog
|
|
@@ -628,6 +619,7 @@ export class OpenRouterChatCompletionsProvider implements Provider {
|
|
|
628
619
|
if (toolChoice !== undefined) body.tool_choice = toolChoice;
|
|
629
620
|
if (effort) body.reasoning = { effort };
|
|
630
621
|
if (serviceTier) body.service_tier = serviceTier;
|
|
622
|
+
if (cache === "5m" || cache === "1h") body.cache_control = { type: "ephemeral", ttl: cache };
|
|
631
623
|
// Documented Chat Completions generation controls (only those the caller set).
|
|
632
624
|
Object.assign(body, mapGenerationParamsToChat(options));
|
|
633
625
|
// Structured outputs: Chat Completions `response_format{type:json_schema,json_schema}`.
|
|
@@ -665,13 +657,9 @@ export class OpenRouterChatCompletionsProvider implements Provider {
|
|
|
665
657
|
// (HTTP-Referer / X-Title / x-session-id / x-client-request-id).
|
|
666
658
|
const headers: Record<string, string> = buildSessionHeaders(
|
|
667
659
|
"openrouter",
|
|
668
|
-
options?.cache,
|
|
669
660
|
options?.headers,
|
|
670
661
|
sessionId
|
|
671
662
|
);
|
|
672
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
673
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete headers[k];
|
|
674
|
-
}
|
|
675
663
|
setHeaderCaseInsensitive(headers, "Content-Type", "application/json");
|
|
676
664
|
if (apiKey !== "unused") setHeaderCaseInsensitive(headers, "Authorization", `Bearer ${apiKey}`);
|
|
677
665
|
return { method: "POST", headers, body: JSON.stringify(body), signal };
|
|
@@ -747,20 +735,17 @@ export class OpenRouterChatCompletionsProvider implements Provider {
|
|
|
747
735
|
assertModalitiesSupported(context, "openrouter", clean);
|
|
748
736
|
const baseUrl = resolveBaseUrl(options);
|
|
749
737
|
const url = `${baseUrl}/chat/completions`;
|
|
750
|
-
const sessionId = options?.sessionId
|
|
738
|
+
const sessionId = options?.sessionId;
|
|
751
739
|
const tools = options?.tools as StandardToolDeclaration[] | undefined;
|
|
752
740
|
const body = await this.buildBody(clean, context, options, sessionId, tools, false);
|
|
753
741
|
|
|
754
742
|
// Audit trail: record the actual wire headers (attribution + session
|
|
755
743
|
// affinity included), redacted.
|
|
756
744
|
const auditRaw: Record<string, string> = {
|
|
757
|
-
...buildSessionHeaders("openrouter", options?.
|
|
745
|
+
...buildSessionHeaders("openrouter", options?.headers, sessionId),
|
|
758
746
|
"Content-Type": "application/json",
|
|
759
747
|
Authorization: "[REDACTED]",
|
|
760
748
|
};
|
|
761
|
-
for (const k of Object.keys(auditRaw)) {
|
|
762
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete auditRaw[k];
|
|
763
|
-
}
|
|
764
749
|
const rawRequest = {
|
|
765
750
|
url,
|
|
766
751
|
method: "POST",
|
|
@@ -846,18 +831,15 @@ export class OpenRouterChatCompletionsProvider implements Provider {
|
|
|
846
831
|
assertModalitiesSupported(context, "openrouter", clean);
|
|
847
832
|
const baseUrl = resolveBaseUrl(options);
|
|
848
833
|
const url = `${baseUrl}/chat/completions`;
|
|
849
|
-
const sessionId = options?.sessionId
|
|
834
|
+
const sessionId = options?.sessionId;
|
|
850
835
|
const tools = options?.tools as StandardToolDeclaration[] | undefined;
|
|
851
836
|
const body = await this.buildBody(clean, context, options, sessionId, tools, true);
|
|
852
837
|
|
|
853
838
|
const streamAuditRaw: Record<string, string> = {
|
|
854
|
-
...buildSessionHeaders("openrouter", options?.
|
|
839
|
+
...buildSessionHeaders("openrouter", options?.headers, sessionId),
|
|
855
840
|
"Content-Type": "application/json",
|
|
856
841
|
Authorization: "[REDACTED]",
|
|
857
842
|
};
|
|
858
|
-
for (const k of Object.keys(streamAuditRaw)) {
|
|
859
|
-
if (INTERNAL_HEADERS.has(k.toLowerCase())) delete streamAuditRaw[k];
|
|
860
|
-
}
|
|
861
843
|
const rawRequest = {
|
|
862
844
|
url,
|
|
863
845
|
method: "POST",
|
package/src/providers.ts
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* Rule: OpenAI, OpenRouter, and future providers must be addable here without
|
|
10
10
|
* redesigning this abstraction — only new per-provider mappers/adapters.
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { CacheOption, ServiceTier, ThinkingLevel } from "./types/core.ts";
|
|
13
13
|
import type { StructuredOutputSpec } from "./types/model.ts";
|
|
14
|
+
import type { Message } from "./types/message.ts";
|
|
14
15
|
import { zodToJsonSchema } from "./tools/schema.ts";
|
|
15
16
|
import { ValidationError } from "./types/errors.ts";
|
|
16
17
|
import { TRUNCATED_ARGS_KEY } from "./types/tool.ts";
|
|
@@ -30,14 +31,14 @@ export type ProviderCapabilityStatus =
|
|
|
30
31
|
* There is no pre-existing logger in `src/` (only CLI logs in
|
|
31
32
|
* `update-models.ts`), so this `console.warn` line IS the mechanism — do not
|
|
32
33
|
* introduce a second one. Used when a requested capability cannot be applied
|
|
33
|
-
* as-is (e.g.
|
|
34
|
+
* as-is (e.g. an unsupported `cache` TTL) so it never silently
|
|
34
35
|
* disappears.
|
|
35
36
|
*
|
|
36
37
|
* Warnings are deduped per provider+capability+requested: agent loops call
|
|
37
38
|
* mappers once per turn, and repeating the same warning every turn is noise.
|
|
38
39
|
* A repeated identical request logs once per process.
|
|
39
40
|
*
|
|
40
|
-
* @example `emitProviderWarning({ provider: "google", capability: "cache
|
|
41
|
+
* @example `emitProviderWarning({ provider: "google", capability: "cache", requested: "12h", reason: "...", fallback: "..." })`
|
|
41
42
|
*/
|
|
42
43
|
const emittedWarnings = new Set<string>();
|
|
43
44
|
|
|
@@ -229,68 +230,37 @@ export function mapServiceTierToOpenRouter(tier: ServiceTier | undefined): "flex
|
|
|
229
230
|
return undefined;
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
/**
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
*/
|
|
238
|
-
export function applyCacheForOpenRouter(
|
|
239
|
-
cache: CacheConfig | undefined,
|
|
233
|
+
/** Returns the `cache` value a provider will honour; an unsupported one warns once and falls back to the provider default (`undefined`). In: `ProviderRequestOptions.cache` from each adapter's buildBody, out: the adapter's wire mapping. */
|
|
234
|
+
export function resolveCache(
|
|
235
|
+
provider: string,
|
|
236
|
+
cache: CacheOption | undefined,
|
|
237
|
+
supported: readonly CacheOption[],
|
|
240
238
|
modelRef: string
|
|
241
|
-
):
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
if (cache.cachedContentId) {
|
|
253
|
-
emitProviderWarning({
|
|
254
|
-
provider: "openrouter",
|
|
255
|
-
capability: "explicit cached content",
|
|
256
|
-
requested: cache.cachedContentId,
|
|
257
|
-
reason: "cached content references do not exist on the Responses API.",
|
|
258
|
-
fallback: "full-history sends instead (cachedContentId is ignored)",
|
|
259
|
-
});
|
|
260
|
-
}
|
|
239
|
+
): CacheOption | undefined {
|
|
240
|
+
if (cache === undefined || supported.includes(cache)) return cache;
|
|
241
|
+
emitProviderWarning({
|
|
242
|
+
provider,
|
|
243
|
+
capability: "cache",
|
|
244
|
+
requested: `${cache} (${modelRef})`,
|
|
245
|
+
reason: `supported values here are: ${supported.map((v) => `"${v}"`).join(", ")}.`,
|
|
246
|
+
fallback: "the provider default (implicit caching)",
|
|
247
|
+
});
|
|
248
|
+
return undefined;
|
|
261
249
|
}
|
|
262
250
|
|
|
263
|
-
/**
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (cache.retention && cache.retention !== "implicit") {
|
|
277
|
-
emitProviderWarning({
|
|
278
|
-
provider: prefix,
|
|
279
|
-
capability: "cache retention",
|
|
280
|
-
requested: `${cache.retention} (${modelRef})`,
|
|
281
|
-
reason: "custom OpenAI-compatible endpoints define no retention control, and strict endpoints reject unknown body properties.",
|
|
282
|
-
fallback: "default caching with headers-only session affinity (no retention payload is sent)",
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
if (cache.cachedContentId) {
|
|
286
|
-
emitProviderWarning({
|
|
287
|
-
provider: prefix,
|
|
288
|
-
capability: "explicit cached content",
|
|
289
|
-
requested: cache.cachedContentId,
|
|
290
|
-
reason: "cached content references do not exist on OpenAI-compatible endpoints.",
|
|
291
|
-
fallback: "full-history sends instead (cachedContentId is ignored)",
|
|
292
|
-
});
|
|
293
|
-
}
|
|
251
|
+
/** Warns once per provider/model that per-media `resolution` is ignored (no such control on that wire); in: an adapter's request messages, out: telemetry bus only. */
|
|
252
|
+
export function warnIgnoredMediaResolution(provider: string, messages: Message[], modelRef: string): void {
|
|
253
|
+
const used = messages.some(
|
|
254
|
+
(m) => Array.isArray(m.content) && m.content.some((p) => (p.type === "image" || p.type === "video") && p.resolution)
|
|
255
|
+
);
|
|
256
|
+
if (!used) return;
|
|
257
|
+
emitProviderWarning({
|
|
258
|
+
provider,
|
|
259
|
+
capability: "media resolution",
|
|
260
|
+
requested: modelRef,
|
|
261
|
+
reason: "this provider has no per-media detail control (Google only).",
|
|
262
|
+
fallback: "the provider's default detail",
|
|
263
|
+
});
|
|
294
264
|
}
|
|
295
265
|
|
|
296
266
|
/** OpenRouter Chat Completions `tool_choice` wire values. */
|
|
@@ -368,38 +338,6 @@ export function mapServiceTierToOpenAI(tier: ServiceTier | undefined): "flex" |
|
|
|
368
338
|
return undefined;
|
|
369
339
|
}
|
|
370
340
|
|
|
371
|
-
/**
|
|
372
|
-
* Applies canonical cache config for OpenAI. Session affinity flows via
|
|
373
|
-
* `prompt_cache_key` (handled by the adapter); `long` retention maps to
|
|
374
|
-
* `prompt_cache_retention: "24h"` when the catalog supports it (handled by
|
|
375
|
-
* the adapter, pi `api/openai-responses.ts:97` rule). Other explicit
|
|
376
|
-
* retention modes and `cachedContentId` are UNSUPPORTED: warn and drop.
|
|
377
|
-
*/
|
|
378
|
-
export function applyCacheForOpenAI(
|
|
379
|
-
cache: CacheConfig | undefined,
|
|
380
|
-
modelRef: string
|
|
381
|
-
): void {
|
|
382
|
-
if (!cache) return;
|
|
383
|
-
if (cache.retention && cache.retention !== "implicit" && cache.retention !== "long") {
|
|
384
|
-
emitProviderWarning({
|
|
385
|
-
provider: "openai",
|
|
386
|
-
capability: "cache retention",
|
|
387
|
-
requested: `${cache.retention} (${modelRef})`,
|
|
388
|
-
reason: "the native Responses adapter uses prompt_cache_key affinity only; explicit retention modes are out of scope.",
|
|
389
|
-
fallback: "default caching with prompt_cache_key affinity (no retention payload is sent)",
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
if (cache.cachedContentId) {
|
|
393
|
-
emitProviderWarning({
|
|
394
|
-
provider: "openai",
|
|
395
|
-
capability: "explicit cached content",
|
|
396
|
-
requested: cache.cachedContentId,
|
|
397
|
-
reason: "cached content references do not exist on the Responses API.",
|
|
398
|
-
fallback: "full-history sends instead (cachedContentId is ignored)",
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
341
|
/** OpenAI Responses `tool_choice` wire values (most-important subset). */
|
|
404
342
|
export type OpenAIToolChoice = "auto" | "none" | "required" | { type: "function"; name: string };
|
|
405
343
|
|
|
@@ -419,36 +357,6 @@ export function mapToolChoiceToOpenAI(
|
|
|
419
357
|
return undefined;
|
|
420
358
|
}
|
|
421
359
|
|
|
422
|
-
/**
|
|
423
|
-
* Applies canonical cache config for Google. The Interactions API supports
|
|
424
|
-
* implicit/automatic caching ONLY — there is no cache payload to send.
|
|
425
|
-
* Explicit retention/cachedContentId are UNSUPPORTED: warn and drop.
|
|
426
|
-
*/
|
|
427
|
-
export function applyCacheForGoogle(
|
|
428
|
-
cache: CacheConfig | undefined,
|
|
429
|
-
modelRef: string
|
|
430
|
-
): void {
|
|
431
|
-
if (!cache) return;
|
|
432
|
-
if (cache.retention && cache.retention !== "implicit") {
|
|
433
|
-
emitProviderWarning({
|
|
434
|
-
provider: "google",
|
|
435
|
-
capability: "cache retention",
|
|
436
|
-
requested: `${cache.retention} (${modelRef})`,
|
|
437
|
-
reason: "the Interactions API does not support user-defined explicit cache retention.",
|
|
438
|
-
fallback: "Google automatic/implicit caching (no cache payload is sent)",
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
if (cache.cachedContentId) {
|
|
442
|
-
emitProviderWarning({
|
|
443
|
-
provider: "google",
|
|
444
|
-
capability: "explicit cached content",
|
|
445
|
-
requested: cache.cachedContentId,
|
|
446
|
-
reason: "cachedContents resources do not exist on the Interactions API.",
|
|
447
|
-
fallback: "stateful interaction chaining / stateless history instead (cachedContentId is ignored)",
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
360
|
/** Normalized tool-choice mode shared by provider adapters. */
|
|
453
361
|
export type CanonicalToolChoiceMode = "auto" | "any" | "none";
|
|
454
362
|
|