@draht/ai 2026.5.12 → 2026.6.11
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/dist/api-registry.d.ts +1 -1
- package/dist/api-registry.d.ts.map +1 -1
- package/dist/api-registry.js.map +1 -1
- package/dist/bedrock-provider.d.ts +2 -2
- package/dist/bedrock-provider.d.ts.map +1 -1
- package/dist/bedrock-provider.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts +10 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +110 -36
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.d.ts +10 -0
- package/dist/image-models.d.ts.map +1 -0
- package/dist/image-models.generated.d.ts +485 -0
- package/dist/image-models.generated.d.ts.map +1 -0
- package/dist/image-models.generated.js +487 -0
- package/dist/image-models.generated.js.map +1 -0
- package/dist/image-models.js +23 -0
- package/dist/image-models.js.map +1 -0
- package/dist/images-api-registry.d.ts +14 -0
- package/dist/images-api-registry.d.ts.map +1 -0
- package/dist/images-api-registry.js +22 -0
- package/dist/images-api-registry.js.map +1 -0
- package/dist/images.d.ts +4 -0
- package/dist/images.d.ts.map +1 -0
- package/dist/images.js +14 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +31 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +5 -8
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +4665 -1252
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +4877 -2833
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +33 -6
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +19 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +278 -89
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +37 -6
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +300 -114
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts +1 -1
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +68 -21
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/cloudflare.d.ts +13 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +26 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/faux.d.ts +1 -1
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +1 -0
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/github-copilot-headers.d.ts +1 -1
- package/dist/providers/github-copilot-headers.d.ts.map +1 -1
- package/dist/providers/github-copilot-headers.js.map +1 -1
- package/dist/providers/google-shared.d.ts +8 -3
- package/dist/providers/google-shared.d.ts.map +1 -1
- package/dist/providers/google-shared.js +34 -17
- package/dist/providers/google-shared.js.map +1 -1
- package/dist/providers/google-vertex.d.ts +2 -2
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +45 -18
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts +2 -2
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +9 -6
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts +3 -0
- package/dist/providers/images/openrouter.d.ts.map +1 -0
- package/dist/providers/images/openrouter.js +128 -0
- package/dist/providers/images/openrouter.js.map +1 -0
- package/dist/providers/images/register-builtins.d.ts +4 -0
- package/dist/providers/images/register-builtins.d.ts.map +1 -0
- package/dist/providers/images/register-builtins.js +34 -0
- package/dist/providers/images/register-builtins.js.map +1 -0
- package/dist/providers/mistral.d.ts +4 -1
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +43 -10
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts +22 -1
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +542 -111
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +6 -2
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +447 -229
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-prompt-cache.d.ts +3 -0
- package/dist/providers/openai-prompt-cache.d.ts.map +1 -0
- package/dist/providers/openai-prompt-cache.js +10 -0
- package/dist/providers/openai-prompt-cache.js.map +1 -0
- package/dist/providers/openai-responses-shared.d.ts +3 -2
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +41 -15
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +1 -1
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +85 -40
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/register-builtins.d.ts +10 -13
- package/dist/providers/register-builtins.d.ts.map +1 -1
- package/dist/providers/register-builtins.js +13 -20
- package/dist/providers/register-builtins.js.map +1 -1
- package/dist/providers/simple-options.d.ts +2 -2
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js +8 -2
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/providers/transform-messages.d.ts +1 -1
- package/dist/providers/transform-messages.d.ts.map +1 -1
- package/dist/providers/transform-messages.js +63 -34
- package/dist/providers/transform-messages.js.map +1 -1
- package/dist/session-resources.d.ts +4 -0
- package/dist/session-resources.d.ts.map +1 -0
- package/dist/session-resources.js +22 -0
- package/dist/session-resources.js.map +1 -0
- package/dist/stream.d.ts +3 -3
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +14 -2
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +177 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/abort-signals.d.ts +6 -0
- package/dist/utils/abort-signals.d.ts.map +1 -0
- package/dist/utils/abort-signals.js +34 -0
- package/dist/utils/abort-signals.js.map +1 -0
- package/dist/utils/diagnostics.d.ts +19 -0
- package/dist/utils/diagnostics.d.ts.map +1 -0
- package/dist/utils/diagnostics.js +25 -0
- package/dist/utils/diagnostics.js.map +1 -0
- package/dist/utils/event-stream.d.ts +3 -3
- package/dist/utils/event-stream.d.ts.map +1 -1
- package/dist/utils/event-stream.js +2 -2
- package/dist/utils/event-stream.js.map +1 -1
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.d.ts.map +1 -0
- package/dist/utils/headers.js +8 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/json-parse.d.ts +8 -1
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +89 -5
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/node-http-proxy.d.ts +10 -0
- package/dist/utils/node-http-proxy.d.ts.map +1 -0
- package/dist/utils/node-http-proxy.js +97 -0
- package/dist/utils/node-http-proxy.js.map +1 -0
- package/dist/utils/oauth/anthropic.d.ts +1 -1
- package/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/dist/utils/oauth/anthropic.js +1 -1
- package/dist/utils/oauth/anthropic.js.map +1 -1
- package/dist/utils/oauth/device-code.d.ts +21 -0
- package/dist/utils/oauth/device-code.d.ts.map +1 -0
- package/dist/utils/oauth/device-code.js +56 -0
- package/dist/utils/oauth/device-code.js.map +1 -0
- package/dist/utils/oauth/github-copilot.d.ts +3 -3
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +58 -70
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +8 -11
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +2 -11
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +11 -2
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +187 -73
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/dist/utils/oauth/types.d.ts +18 -1
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/dist/utils/overflow.d.ts +7 -3
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +25 -3
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/typebox-helpers.d.ts +1 -1
- package/dist/utils/typebox-helpers.d.ts.map +1 -1
- package/dist/utils/typebox-helpers.js +1 -1
- package/dist/utils/typebox-helpers.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +242 -41
- package/dist/utils/validation.js.map +1 -1
- package/package.json +14 -15
- package/dist/providers/google-gemini-cli.d.ts +0 -74
- package/dist/providers/google-gemini-cli.d.ts.map +0 -1
- package/dist/providers/google-gemini-cli.js +0 -776
- package/dist/providers/google-gemini-cli.js.map +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
-
import {
|
|
3
|
-
import { calculateCost, supportsMax } from "../models.js";
|
|
2
|
+
import { calculateCost } from "../models.js";
|
|
4
3
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
5
|
-
import {
|
|
4
|
+
import { headersToRecord } from "../utils/headers.js";
|
|
5
|
+
import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse.js";
|
|
6
6
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
7
|
+
import { resolveCloudflareBaseUrl } from "./cloudflare.js";
|
|
7
8
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
8
9
|
import { adjustMaxTokensForThinking, buildBaseOptions } from "./simple-options.js";
|
|
9
10
|
import { transformMessages } from "./transform-messages.js";
|
|
@@ -20,58 +21,40 @@ function resolveCacheRetention(cacheRetention) {
|
|
|
20
21
|
}
|
|
21
22
|
return "short";
|
|
22
23
|
}
|
|
23
|
-
function getCacheControl(
|
|
24
|
+
function getCacheControl(model, cacheRetention) {
|
|
24
25
|
const retention = resolveCacheRetention(cacheRetention);
|
|
25
26
|
if (retention === "none") {
|
|
26
27
|
return { retention };
|
|
27
28
|
}
|
|
28
|
-
const ttl = retention === "long" &&
|
|
29
|
+
const ttl = retention === "long" && getAnthropicCompat(model).supportsLongCacheRetention ? "1h" : undefined;
|
|
29
30
|
return {
|
|
30
31
|
retention,
|
|
31
32
|
cacheControl: { type: "ephemeral", ...(ttl && { ttl }) },
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
|
-
// Stealth mode: Mimic Claude Code's
|
|
35
|
-
|
|
35
|
+
// Stealth mode: Mimic Claude Code's tool naming exactly
|
|
36
|
+
const claudeCodeVersion = "2.1.75";
|
|
37
|
+
// Claude Code 2.x tool names (canonical casing)
|
|
38
|
+
// Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md
|
|
36
39
|
// To update: https://github.com/badlogic/cchistory
|
|
37
|
-
const claudeCodeVersion = "2.1.100";
|
|
38
|
-
// Build hash suffix that Claude Code appends to the billing header's cc_version.
|
|
39
|
-
// Each real release has its own hash (e.g. 2.1.100 -> "1af", 2.1.90 -> "232").
|
|
40
|
-
// This value only needs to parse; it is not cryptographically verified server-side.
|
|
41
|
-
const claudeCodeBuild = "1af";
|
|
42
|
-
// First line of Claude Code's system prompt. Anthropic uses this to classify
|
|
43
|
-
// traffic as first-party Claude Code (vs third-party SDK usage) for billing.
|
|
44
|
-
const claudeCodeBillingHeader = `x-anthropic-billing-header: cc_version=${claudeCodeVersion}.${claudeCodeBuild}; cc_entrypoint=sdk-cli; cch=00000;`;
|
|
45
|
-
// Identity statement that immediately follows the billing header in real Claude Code.
|
|
46
|
-
const claudeCodeIdentity = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
47
|
-
// Claude Code 2.1.100 tool names (canonical casing), both active and deferred
|
|
48
|
-
// (deferred tools are loaded on-demand via ToolSearch but still use these names).
|
|
49
40
|
const claudeCodeTools = [
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"Bash",
|
|
53
|
-
"CronCreate",
|
|
54
|
-
"CronDelete",
|
|
55
|
-
"CronList",
|
|
41
|
+
"Read",
|
|
42
|
+
"Write",
|
|
56
43
|
"Edit",
|
|
44
|
+
"Bash",
|
|
45
|
+
"Grep",
|
|
46
|
+
"Glob",
|
|
47
|
+
"AskUserQuestion",
|
|
57
48
|
"EnterPlanMode",
|
|
58
|
-
"EnterWorktree",
|
|
59
49
|
"ExitPlanMode",
|
|
60
|
-
"
|
|
61
|
-
"Glob",
|
|
62
|
-
"Grep",
|
|
63
|
-
"Monitor",
|
|
50
|
+
"KillShell",
|
|
64
51
|
"NotebookEdit",
|
|
65
|
-
"Read",
|
|
66
|
-
"RemoteTrigger",
|
|
67
52
|
"Skill",
|
|
53
|
+
"Task",
|
|
68
54
|
"TaskOutput",
|
|
69
|
-
"TaskStop",
|
|
70
55
|
"TodoWrite",
|
|
71
|
-
"ToolSearch",
|
|
72
56
|
"WebFetch",
|
|
73
57
|
"WebSearch",
|
|
74
|
-
"Write",
|
|
75
58
|
];
|
|
76
59
|
const ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));
|
|
77
60
|
// Convert tool name to CC canonical casing if it matches (case-insensitive)
|
|
@@ -121,6 +104,21 @@ function convertContentBlocks(content) {
|
|
|
121
104
|
}
|
|
122
105
|
return blocks;
|
|
123
106
|
}
|
|
107
|
+
const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
|
|
108
|
+
const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
109
|
+
function getAnthropicCompat(model) {
|
|
110
|
+
// Auto-detect session affinity and cache control support from provider
|
|
111
|
+
const isFireworks = model.provider === "fireworks";
|
|
112
|
+
const isCloudflareAiGatewayAnthropic = model.provider === "cloudflare-ai-gateway" && model.baseUrl.includes("anthropic");
|
|
113
|
+
return {
|
|
114
|
+
supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? !isFireworks,
|
|
115
|
+
supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? !isFireworks,
|
|
116
|
+
sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? !!(isFireworks || isCloudflareAiGatewayAnthropic),
|
|
117
|
+
supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? !isFireworks,
|
|
118
|
+
supportsTemperature: model.compat?.supportsTemperature ?? true,
|
|
119
|
+
allowEmptySignature: model.compat?.allowEmptySignature ?? false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
124
122
|
function mergeHeaders(...headerSources) {
|
|
125
123
|
const merged = {};
|
|
126
124
|
for (const headers of headerSources) {
|
|
@@ -130,6 +128,157 @@ function mergeHeaders(...headerSources) {
|
|
|
130
128
|
}
|
|
131
129
|
return merged;
|
|
132
130
|
}
|
|
131
|
+
const ANTHROPIC_MESSAGE_EVENTS = new Set([
|
|
132
|
+
"message_start",
|
|
133
|
+
"message_delta",
|
|
134
|
+
"message_stop",
|
|
135
|
+
"content_block_start",
|
|
136
|
+
"content_block_delta",
|
|
137
|
+
"content_block_stop",
|
|
138
|
+
]);
|
|
139
|
+
function flushSseEvent(state) {
|
|
140
|
+
if (!state.event && state.data.length === 0) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const event = {
|
|
144
|
+
event: state.event,
|
|
145
|
+
data: state.data.join("\n"),
|
|
146
|
+
raw: [...state.raw],
|
|
147
|
+
};
|
|
148
|
+
state.event = null;
|
|
149
|
+
state.data = [];
|
|
150
|
+
state.raw = [];
|
|
151
|
+
return event;
|
|
152
|
+
}
|
|
153
|
+
function decodeSseLine(line, state) {
|
|
154
|
+
if (line === "") {
|
|
155
|
+
return flushSseEvent(state);
|
|
156
|
+
}
|
|
157
|
+
state.raw.push(line);
|
|
158
|
+
if (line.startsWith(":")) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const delimiterIndex = line.indexOf(":");
|
|
162
|
+
const fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex);
|
|
163
|
+
let value = delimiterIndex === -1 ? "" : line.slice(delimiterIndex + 1);
|
|
164
|
+
if (value.startsWith(" ")) {
|
|
165
|
+
value = value.slice(1);
|
|
166
|
+
}
|
|
167
|
+
if (fieldName === "event") {
|
|
168
|
+
state.event = value;
|
|
169
|
+
}
|
|
170
|
+
else if (fieldName === "data") {
|
|
171
|
+
state.data.push(value);
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function nextLineBreakIndex(text) {
|
|
176
|
+
const carriageReturnIndex = text.indexOf("\r");
|
|
177
|
+
const newlineIndex = text.indexOf("\n");
|
|
178
|
+
if (carriageReturnIndex === -1) {
|
|
179
|
+
return newlineIndex;
|
|
180
|
+
}
|
|
181
|
+
if (newlineIndex === -1) {
|
|
182
|
+
return carriageReturnIndex;
|
|
183
|
+
}
|
|
184
|
+
return Math.min(carriageReturnIndex, newlineIndex);
|
|
185
|
+
}
|
|
186
|
+
function consumeLine(text) {
|
|
187
|
+
const lineBreakIndex = nextLineBreakIndex(text);
|
|
188
|
+
if (lineBreakIndex === -1) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
let nextIndex = lineBreakIndex + 1;
|
|
192
|
+
if (text[lineBreakIndex] === "\r" && text[nextIndex] === "\n") {
|
|
193
|
+
nextIndex += 1;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
line: text.slice(0, lineBreakIndex),
|
|
197
|
+
rest: text.slice(nextIndex),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function* iterateSseMessages(body, signal) {
|
|
201
|
+
const reader = body.getReader();
|
|
202
|
+
const decoder = new TextDecoder();
|
|
203
|
+
const state = { event: null, data: [], raw: [] };
|
|
204
|
+
let buffer = "";
|
|
205
|
+
try {
|
|
206
|
+
while (true) {
|
|
207
|
+
if (signal?.aborted) {
|
|
208
|
+
throw new Error("Request was aborted");
|
|
209
|
+
}
|
|
210
|
+
const { value, done } = await reader.read();
|
|
211
|
+
if (done) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
buffer += decoder.decode(value, { stream: true });
|
|
215
|
+
let consumed = consumeLine(buffer);
|
|
216
|
+
while (consumed) {
|
|
217
|
+
buffer = consumed.rest;
|
|
218
|
+
const event = decodeSseLine(consumed.line, state);
|
|
219
|
+
if (event) {
|
|
220
|
+
yield event;
|
|
221
|
+
}
|
|
222
|
+
consumed = consumeLine(buffer);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
buffer += decoder.decode();
|
|
226
|
+
let consumed = consumeLine(buffer);
|
|
227
|
+
while (consumed) {
|
|
228
|
+
buffer = consumed.rest;
|
|
229
|
+
const event = decodeSseLine(consumed.line, state);
|
|
230
|
+
if (event) {
|
|
231
|
+
yield event;
|
|
232
|
+
}
|
|
233
|
+
consumed = consumeLine(buffer);
|
|
234
|
+
}
|
|
235
|
+
if (buffer.length > 0) {
|
|
236
|
+
const event = decodeSseLine(buffer, state);
|
|
237
|
+
if (event) {
|
|
238
|
+
yield event;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const trailingEvent = flushSseEvent(state);
|
|
242
|
+
if (trailingEvent) {
|
|
243
|
+
yield trailingEvent;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
reader.releaseLock();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function* iterateAnthropicEvents(response, signal) {
|
|
251
|
+
if (!response.body) {
|
|
252
|
+
throw new Error("Attempted to iterate over an Anthropic response with no body");
|
|
253
|
+
}
|
|
254
|
+
let sawMessageStart = false;
|
|
255
|
+
let sawMessageEnd = false;
|
|
256
|
+
for await (const sse of iterateSseMessages(response.body, signal)) {
|
|
257
|
+
if (sse.event === "error") {
|
|
258
|
+
throw new Error(sse.data);
|
|
259
|
+
}
|
|
260
|
+
if (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? "")) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const event = parseJsonWithRepair(sse.data);
|
|
265
|
+
if (event.type === "message_start") {
|
|
266
|
+
sawMessageStart = true;
|
|
267
|
+
}
|
|
268
|
+
else if (event.type === "message_stop") {
|
|
269
|
+
sawMessageEnd = true;
|
|
270
|
+
}
|
|
271
|
+
yield event;
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
275
|
+
throw new Error(`Could not parse Anthropic SSE event ${sse.event}: ${message}; data=${sse.data}; raw=${sse.raw.join("\\n")}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (sawMessageStart && !sawMessageEnd) {
|
|
279
|
+
throw new Error("Anthropic stream ended before message_stop");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
133
282
|
export const streamAnthropic = (model, context, options) => {
|
|
134
283
|
const stream = new AssistantMessageEventStream();
|
|
135
284
|
(async () => {
|
|
@@ -158,7 +307,10 @@ export const streamAnthropic = (model, context, options) => {
|
|
|
158
307
|
isOAuth = false;
|
|
159
308
|
}
|
|
160
309
|
else {
|
|
161
|
-
const apiKey = options?.apiKey
|
|
310
|
+
const apiKey = options?.apiKey;
|
|
311
|
+
if (!apiKey) {
|
|
312
|
+
throw new Error(`No API key for provider: ${model.provider}`);
|
|
313
|
+
}
|
|
162
314
|
let copilotDynamicHeaders;
|
|
163
315
|
if (model.provider === "github-copilot") {
|
|
164
316
|
const hasImages = hasCopilotVisionInput(context.messages);
|
|
@@ -167,7 +319,9 @@ export const streamAnthropic = (model, context, options) => {
|
|
|
167
319
|
hasImages,
|
|
168
320
|
});
|
|
169
321
|
}
|
|
170
|
-
const
|
|
322
|
+
const cacheRetention = options?.cacheRetention ?? resolveCacheRetention();
|
|
323
|
+
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
324
|
+
const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId);
|
|
171
325
|
client = created.client;
|
|
172
326
|
isOAuth = created.isOAuthToken;
|
|
173
327
|
}
|
|
@@ -176,10 +330,16 @@ export const streamAnthropic = (model, context, options) => {
|
|
|
176
330
|
if (nextParams !== undefined) {
|
|
177
331
|
params = nextParams;
|
|
178
332
|
}
|
|
179
|
-
const
|
|
333
|
+
const requestOptions = {
|
|
334
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
335
|
+
...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
|
|
336
|
+
maxRetries: options?.maxRetries ?? 0,
|
|
337
|
+
};
|
|
338
|
+
const response = await client.messages.create({ ...params, stream: true }, requestOptions).asResponse();
|
|
339
|
+
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
180
340
|
stream.push({ type: "start", partial: output });
|
|
181
341
|
const blocks = output.content;
|
|
182
|
-
for await (const event of
|
|
342
|
+
for await (const event of iterateAnthropicEvents(response, options?.signal)) {
|
|
183
343
|
if (event.type === "message_start") {
|
|
184
344
|
output.responseId = event.message.id;
|
|
185
345
|
// Capture initial token usage from message_start event
|
|
@@ -312,6 +472,8 @@ export const streamAnthropic = (model, context, options) => {
|
|
|
312
472
|
}
|
|
313
473
|
else if (block.type === "toolCall") {
|
|
314
474
|
block.arguments = parseStreamingJson(block.partialJson);
|
|
475
|
+
// Finalize in-place and strip the scratch buffer so replay only
|
|
476
|
+
// carries parsed arguments.
|
|
315
477
|
delete block.partialJson;
|
|
316
478
|
stream.push({
|
|
317
479
|
type: "toolcall_end",
|
|
@@ -356,8 +518,11 @@ export const streamAnthropic = (model, context, options) => {
|
|
|
356
518
|
stream.end();
|
|
357
519
|
}
|
|
358
520
|
catch (error) {
|
|
359
|
-
for (const block of output.content)
|
|
521
|
+
for (const block of output.content) {
|
|
360
522
|
delete block.index;
|
|
523
|
+
// partialJson is only a streaming scratch buffer; never persist it.
|
|
524
|
+
delete block.partialJson;
|
|
525
|
+
}
|
|
361
526
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
362
527
|
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
363
528
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
@@ -366,41 +531,28 @@ export const streamAnthropic = (model, context, options) => {
|
|
|
366
531
|
})();
|
|
367
532
|
return stream;
|
|
368
533
|
};
|
|
369
|
-
/**
|
|
370
|
-
* Check if a model supports adaptive thinking (Opus 4.6 and Sonnet 4.6)
|
|
371
|
-
*/
|
|
372
|
-
function supportsAdaptiveThinking(modelId) {
|
|
373
|
-
// Opus 4.6 and Sonnet 4.6 model IDs (with or without date suffix)
|
|
374
|
-
return (modelId.includes("opus-4-6") ||
|
|
375
|
-
modelId.includes("opus-4.6") ||
|
|
376
|
-
modelId.includes("sonnet-4-6") ||
|
|
377
|
-
modelId.includes("sonnet-4.6"));
|
|
378
|
-
}
|
|
379
534
|
/**
|
|
380
535
|
* Map ThinkingLevel to Anthropic effort levels for adaptive thinking.
|
|
381
|
-
* Note: effort "max" is only valid on Opus
|
|
536
|
+
* Note: effort "max" is only valid on Opus 4.6, while Opus 4.7+ and Fable 5 support "xhigh".
|
|
382
537
|
*/
|
|
383
|
-
function mapThinkingLevelToEffort(
|
|
384
|
-
const
|
|
538
|
+
function mapThinkingLevelToEffort(model, level) {
|
|
539
|
+
const mapped = level ? model.thinkingLevelMap?.[level] : undefined;
|
|
540
|
+
if (typeof mapped === "string")
|
|
541
|
+
return mapped;
|
|
385
542
|
switch (level) {
|
|
386
543
|
case "minimal":
|
|
387
|
-
return "low";
|
|
388
544
|
case "low":
|
|
389
545
|
return "low";
|
|
390
546
|
case "medium":
|
|
391
547
|
return "medium";
|
|
392
548
|
case "high":
|
|
393
549
|
return "high";
|
|
394
|
-
case "xhigh":
|
|
395
|
-
return supportsMaxEffort ? "max" : "high";
|
|
396
|
-
case "max":
|
|
397
|
-
return supportsMaxEffort ? "max" : "high";
|
|
398
550
|
default:
|
|
399
551
|
return "high";
|
|
400
552
|
}
|
|
401
553
|
}
|
|
402
554
|
export const streamSimpleAnthropic = (model, context, options) => {
|
|
403
|
-
const apiKey = options?.apiKey
|
|
555
|
+
const apiKey = options?.apiKey;
|
|
404
556
|
if (!apiKey) {
|
|
405
557
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
406
558
|
}
|
|
@@ -408,17 +560,19 @@ export const streamSimpleAnthropic = (model, context, options) => {
|
|
|
408
560
|
if (!options?.reasoning) {
|
|
409
561
|
return streamAnthropic(model, context, { ...base, thinkingEnabled: false });
|
|
410
562
|
}
|
|
411
|
-
// For
|
|
412
|
-
// For older models: use budget-based thinking
|
|
413
|
-
if (
|
|
414
|
-
const effort = mapThinkingLevelToEffort(options.reasoning
|
|
563
|
+
// For models with adaptive thinking: use an effort level.
|
|
564
|
+
// For older models: use budget-based thinking.
|
|
565
|
+
if (model.compat?.forceAdaptiveThinking === true) {
|
|
566
|
+
const effort = mapThinkingLevelToEffort(model, options.reasoning);
|
|
415
567
|
return streamAnthropic(model, context, {
|
|
416
568
|
...base,
|
|
417
569
|
thinkingEnabled: true,
|
|
418
570
|
effort,
|
|
419
571
|
});
|
|
420
572
|
}
|
|
421
|
-
|
|
573
|
+
// Undefined means the caller did not request an output cap; let the helper use the model cap.
|
|
574
|
+
// Do not coerce to 0 here, or the thinking budget would become the entire max_tokens value.
|
|
575
|
+
const adjusted = adjustMaxTokensForThinking(base.maxTokens, model.maxTokens, options.reasoning, options.thinkingBudgets);
|
|
422
576
|
return streamAnthropic(model, context, {
|
|
423
577
|
...base,
|
|
424
578
|
maxTokens: adjusted.maxTokens,
|
|
@@ -429,16 +583,35 @@ export const streamSimpleAnthropic = (model, context, options) => {
|
|
|
429
583
|
function isOAuthToken(apiKey) {
|
|
430
584
|
return apiKey.includes("sk-ant-oat");
|
|
431
585
|
}
|
|
432
|
-
function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynamicHeaders) {
|
|
433
|
-
// Adaptive thinking models
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
586
|
+
function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, dynamicHeaders, sessionId) {
|
|
587
|
+
// Adaptive thinking models have interleaved thinking built in, so skip the beta header.
|
|
588
|
+
const needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;
|
|
589
|
+
const betaFeatures = [];
|
|
590
|
+
if (useFineGrainedToolStreamingBeta) {
|
|
591
|
+
betaFeatures.push(FINE_GRAINED_TOOL_STREAMING_BETA);
|
|
592
|
+
}
|
|
593
|
+
if (needsInterleavedBeta) {
|
|
594
|
+
betaFeatures.push(INTERLEAVED_THINKING_BETA);
|
|
595
|
+
}
|
|
596
|
+
if (model.provider === "cloudflare-ai-gateway") {
|
|
597
|
+
const client = new Anthropic({
|
|
598
|
+
apiKey: null,
|
|
599
|
+
authToken: null,
|
|
600
|
+
baseURL: resolveCloudflareBaseUrl(model),
|
|
601
|
+
dangerouslyAllowBrowser: true,
|
|
602
|
+
defaultHeaders: mergeHeaders({
|
|
603
|
+
accept: "application/json",
|
|
604
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
605
|
+
"cf-aig-authorization": `Bearer ${apiKey}`,
|
|
606
|
+
"x-api-key": null,
|
|
607
|
+
Authorization: null,
|
|
608
|
+
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
|
609
|
+
}, model.headers, optionsHeaders),
|
|
610
|
+
});
|
|
611
|
+
return { client, isOAuthToken: false };
|
|
612
|
+
}
|
|
613
|
+
// Copilot: Bearer auth, selective betas.
|
|
437
614
|
if (model.provider === "github-copilot") {
|
|
438
|
-
const betaFeatures = [];
|
|
439
|
-
if (needsInterleavedBeta) {
|
|
440
|
-
betaFeatures.push("interleaved-thinking-2025-05-14");
|
|
441
|
-
}
|
|
442
615
|
const client = new Anthropic({
|
|
443
616
|
apiKey: null,
|
|
444
617
|
authToken: apiKey,
|
|
@@ -452,10 +625,6 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
|
|
|
452
625
|
});
|
|
453
626
|
return { client, isOAuthToken: false };
|
|
454
627
|
}
|
|
455
|
-
const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"];
|
|
456
|
-
if (needsInterleavedBeta) {
|
|
457
|
-
betaFeatures.push("interleaved-thinking-2025-05-14");
|
|
458
|
-
}
|
|
459
628
|
// OAuth: Bearer auth, Claude Code identity headers
|
|
460
629
|
if (isOAuthToken(apiKey)) {
|
|
461
630
|
const client = new Anthropic({
|
|
@@ -466,7 +635,7 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
|
|
|
466
635
|
defaultHeaders: mergeHeaders({
|
|
467
636
|
accept: "application/json",
|
|
468
637
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
469
|
-
"anthropic-beta":
|
|
638
|
+
"anthropic-beta": ["claude-code-20250219", "oauth-2025-04-20", ...betaFeatures].join(","),
|
|
470
639
|
"user-agent": `claude-cli/${claudeCodeVersion}`,
|
|
471
640
|
"x-app": "cli",
|
|
472
641
|
}, model.headers, optionsHeaders),
|
|
@@ -474,36 +643,35 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
|
|
|
474
643
|
return { client, isOAuthToken: true };
|
|
475
644
|
}
|
|
476
645
|
// API key auth
|
|
646
|
+
const sessionAffinityHeaders = sessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { "x-session-affinity": sessionId } : {};
|
|
477
647
|
const client = new Anthropic({
|
|
478
648
|
apiKey,
|
|
649
|
+
authToken: null,
|
|
479
650
|
baseURL: model.baseUrl,
|
|
480
651
|
dangerouslyAllowBrowser: true,
|
|
481
652
|
defaultHeaders: mergeHeaders({
|
|
482
653
|
accept: "application/json",
|
|
483
654
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
484
|
-
"anthropic-beta": betaFeatures.join(","),
|
|
485
|
-
}, model.headers, optionsHeaders),
|
|
655
|
+
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
|
656
|
+
}, sessionAffinityHeaders, model.headers, optionsHeaders),
|
|
486
657
|
});
|
|
487
658
|
return { client, isOAuthToken: false };
|
|
488
659
|
}
|
|
489
660
|
function buildParams(model, context, isOAuthToken, options) {
|
|
490
|
-
const { cacheControl } = getCacheControl(model
|
|
661
|
+
const { cacheControl } = getCacheControl(model, options?.cacheRetention);
|
|
662
|
+
const compat = getAnthropicCompat(model);
|
|
491
663
|
const params = {
|
|
492
664
|
model: model.id,
|
|
493
|
-
messages: convertMessages(context.messages, model, isOAuthToken, cacheControl),
|
|
494
|
-
max_tokens: options?.maxTokens
|
|
665
|
+
messages: convertMessages(context.messages, model, isOAuthToken, cacheControl, compat.allowEmptySignature),
|
|
666
|
+
max_tokens: options?.maxTokens ?? model.maxTokens,
|
|
495
667
|
stream: true,
|
|
496
668
|
};
|
|
497
|
-
// For OAuth tokens, we MUST
|
|
498
|
-
// Anthropic inspects the first system block to classify traffic as first-party
|
|
499
|
-
// Claude Code (billed against the Claude subscription) vs third-party SDK usage
|
|
500
|
-
// (billed as extra usage per token). Without these exact strings, Pro/Max/Team
|
|
501
|
-
// subscriptions are metered as third-party.
|
|
669
|
+
// For OAuth tokens, we MUST include Claude Code identity
|
|
502
670
|
if (isOAuthToken) {
|
|
503
671
|
params.system = [
|
|
504
672
|
{
|
|
505
673
|
type: "text",
|
|
506
|
-
text:
|
|
674
|
+
text: "You are Claude Code, Anthropic's official CLI for Claude.",
|
|
507
675
|
...(cacheControl ? { cache_control: cacheControl } : {}),
|
|
508
676
|
},
|
|
509
677
|
];
|
|
@@ -525,22 +693,29 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
525
693
|
},
|
|
526
694
|
];
|
|
527
695
|
}
|
|
528
|
-
// Temperature is incompatible with extended thinking
|
|
529
|
-
if (options?.temperature !== undefined && !options?.thinkingEnabled) {
|
|
696
|
+
// Temperature is incompatible with extended thinking and unsupported on Claude Opus 4.7+.
|
|
697
|
+
if (options?.temperature !== undefined && !options?.thinkingEnabled && compat.supportsTemperature) {
|
|
530
698
|
params.temperature = options.temperature;
|
|
531
699
|
}
|
|
532
|
-
if (context.tools) {
|
|
533
|
-
|
|
700
|
+
if (context.tools && context.tools.length > 0) {
|
|
701
|
+
const compat = getAnthropicCompat(model);
|
|
702
|
+
params.tools = convertTools(context.tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsCacheControlOnTools ? cacheControl : undefined);
|
|
534
703
|
}
|
|
535
|
-
// Configure thinking mode: adaptive
|
|
536
|
-
// budget-based (older models), or explicitly disabled.
|
|
704
|
+
// Configure thinking mode: adaptive, budget-based, or explicitly disabled.
|
|
537
705
|
if (model.reasoning) {
|
|
538
706
|
if (options?.thinkingEnabled) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
707
|
+
// Default to "summarized" so Opus 4.7 and Mythos Preview behave like
|
|
708
|
+
// older Claude 4 models (whose API default is also "summarized").
|
|
709
|
+
const display = options.thinkingDisplay ?? "summarized";
|
|
710
|
+
if (model.compat?.forceAdaptiveThinking === true) {
|
|
711
|
+
// Adaptive thinking: Claude decides when and how much to think.
|
|
712
|
+
params.thinking = { type: "adaptive", display };
|
|
542
713
|
if (options.effort) {
|
|
543
|
-
|
|
714
|
+
// The Anthropic SDK types can lag newly supported effort values such as "xhigh".
|
|
715
|
+
params.output_config =
|
|
716
|
+
options.effort === "xhigh"
|
|
717
|
+
? { effort: options.effort }
|
|
718
|
+
: { effort: options.effort };
|
|
544
719
|
}
|
|
545
720
|
}
|
|
546
721
|
else {
|
|
@@ -548,10 +723,11 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
548
723
|
params.thinking = {
|
|
549
724
|
type: "enabled",
|
|
550
725
|
budget_tokens: options.thinkingBudgetTokens || 1024,
|
|
726
|
+
display,
|
|
551
727
|
};
|
|
552
728
|
}
|
|
553
729
|
}
|
|
554
|
-
else if (options?.thinkingEnabled === false) {
|
|
730
|
+
else if (options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null) {
|
|
555
731
|
params.thinking = { type: "disabled" };
|
|
556
732
|
}
|
|
557
733
|
}
|
|
@@ -575,7 +751,7 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
575
751
|
function normalizeToolCallId(id) {
|
|
576
752
|
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
577
753
|
}
|
|
578
|
-
function convertMessages(messages, model, isOAuthToken, cacheControl) {
|
|
754
|
+
function convertMessages(messages, model, isOAuthToken, cacheControl, allowEmptySignature = false) {
|
|
579
755
|
const params = [];
|
|
580
756
|
// Transform messages for cross-provider compatibility
|
|
581
757
|
const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
|
|
@@ -609,8 +785,7 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
|
|
|
609
785
|
};
|
|
610
786
|
}
|
|
611
787
|
});
|
|
612
|
-
|
|
613
|
-
filteredBlocks = filteredBlocks.filter((b) => {
|
|
788
|
+
const filteredBlocks = blocks.filter((b) => {
|
|
614
789
|
if (b.type === "text") {
|
|
615
790
|
return b.text.trim().length > 0;
|
|
616
791
|
}
|
|
@@ -647,13 +822,19 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
|
|
|
647
822
|
if (block.thinking.trim().length === 0)
|
|
648
823
|
continue;
|
|
649
824
|
// If thinking signature is missing/empty (e.g., from aborted stream),
|
|
650
|
-
// convert to plain text
|
|
651
|
-
// and
|
|
825
|
+
// convert to plain text for Anthropic. Some compatible providers emit
|
|
826
|
+
// and accept empty signatures, so let marked models preserve the block.
|
|
652
827
|
if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
|
|
653
|
-
blocks.push(
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
828
|
+
blocks.push(allowEmptySignature
|
|
829
|
+
? {
|
|
830
|
+
type: "thinking",
|
|
831
|
+
thinking: sanitizeSurrogates(block.thinking),
|
|
832
|
+
signature: "",
|
|
833
|
+
}
|
|
834
|
+
: {
|
|
835
|
+
type: "text",
|
|
836
|
+
text: sanitizeSurrogates(block.thinking),
|
|
837
|
+
});
|
|
657
838
|
}
|
|
658
839
|
else {
|
|
659
840
|
blocks.push({
|
|
@@ -734,19 +915,24 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
|
|
|
734
915
|
}
|
|
735
916
|
return params;
|
|
736
917
|
}
|
|
737
|
-
function
|
|
918
|
+
function shouldUseFineGrainedToolStreamingBeta(model, context) {
|
|
919
|
+
return !!context.tools?.length && !getAnthropicCompat(model).supportsEagerToolInputStreaming;
|
|
920
|
+
}
|
|
921
|
+
function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, cacheControl) {
|
|
738
922
|
if (!tools)
|
|
739
923
|
return [];
|
|
740
|
-
return tools.map((tool) => {
|
|
741
|
-
const
|
|
924
|
+
return tools.map((tool, index) => {
|
|
925
|
+
const schema = tool.parameters;
|
|
742
926
|
return {
|
|
743
927
|
name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
|
|
744
928
|
description: tool.description,
|
|
929
|
+
...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),
|
|
745
930
|
input_schema: {
|
|
746
931
|
type: "object",
|
|
747
|
-
properties:
|
|
748
|
-
required:
|
|
932
|
+
properties: schema.properties ?? {},
|
|
933
|
+
required: schema.required ?? [],
|
|
749
934
|
},
|
|
935
|
+
...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),
|
|
750
936
|
};
|
|
751
937
|
});
|
|
752
938
|
}
|