@xiaoso/dsh-tool-plus 0.1.0-beta.2 → 0.1.0-beta.4
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.en.md +122 -0
- package/README.md +3 -2
- package/THIRD_PARTY_NOTICES.md +4 -35
- package/lib/abort-B7izMMna.mjs +51 -0
- package/lib/abort-D1_Z5Ut5.mjs +51 -0
- package/lib/abort-dIkZBV7Q.mjs +51 -0
- package/lib/amazon-bedrock-B0nxT06F.mjs +917 -0
- package/lib/amazon-bedrock-D-shAr1X.mjs +917 -0
- package/lib/amazon-bedrock-WQhvjHYu.mjs +917 -0
- package/lib/anthropic-BWsZFzU8.mjs +262 -0
- package/lib/anthropic-CJCauvYz.mjs +3115 -0
- package/lib/anthropic-CMT1Qj1t.mjs +3115 -0
- package/lib/anthropic-CZghe_9U.mjs +3115 -0
- package/lib/anthropic-sCMH7JID.mjs +262 -0
- package/lib/anthropic-tTntD8BD.mjs +262 -0
- package/lib/api-key-login-BzqNdM2y.mjs +157 -0
- package/lib/api-key-login-CWVwvHT1.mjs +157 -0
- package/lib/api-key-login-CpzSezsi.mjs +157 -0
- package/lib/aws-credentials-8Mgn5MAy.mjs +867 -0
- package/lib/aws-credentials-CXryMobh.mjs +867 -0
- package/lib/aws-credentials-ChG79ey-.mjs +867 -0
- package/lib/azure-openai-responses-CNkHiLUh.mjs +274 -0
- package/lib/azure-openai-responses-Cgmc-rbL.mjs +274 -0
- package/lib/azure-openai-responses-DuukbD0F.mjs +274 -0
- package/lib/bun-shim.mjs +252 -12
- package/lib/bundled-references-CSuBco5p.mjs +55 -0
- package/lib/bundled-references-Cavaffah.mjs +55 -0
- package/lib/bundled-references-sLN0YdXU.mjs +55 -0
- package/lib/callback-server-BEFNf-jf.mjs +398 -0
- package/lib/callback-server-CQ5Jo8F-.mjs +398 -0
- package/lib/callback-server-DDSrZ6l_.mjs +398 -0
- package/lib/client.js +111 -41
- package/lib/cursor-B3zg7EHj.mjs +273 -0
- package/lib/cursor-B6TUEmgP.mjs +135 -0
- package/lib/cursor-BIQfBC6V.mjs +135 -0
- package/lib/cursor-D8gvtTL3.mjs +135 -0
- package/lib/cursor-DNk6eXDd.mjs +5470 -0
- package/lib/cursor-DkSX2h3H.mjs +5441 -0
- package/lib/cursor-MjSm74IJ.mjs +273 -0
- package/lib/cursor-aJrXeYCb.mjs +273 -0
- package/lib/cursor-mJQwUKXK.mjs +5441 -0
- package/lib/device-code-DBo3B5nh.mjs +51 -0
- package/lib/device-code-DIb0FU-H.mjs +51 -0
- package/lib/device-code-OI_tQcmc.mjs +51 -0
- package/lib/devin-BHZZyMhO.mjs +95 -0
- package/lib/devin-BK7DHeca.mjs +114 -0
- package/lib/devin-CfMp3cXS.mjs +95 -0
- package/lib/devin-CldyPD8K.mjs +95 -0
- package/lib/devin-CzRpTUOA.mjs +114 -0
- package/lib/devin-D9zR5B_R.mjs +542 -0
- package/lib/devin-DXOeNyU7.mjs +542 -0
- package/lib/devin-Q1yem3lp.mjs +114 -0
- package/lib/devin-vNZTNUON.mjs +542 -0
- package/lib/error-B3bevhrs.mjs +1449 -0
- package/lib/error-Dd8DIqs-.mjs +1449 -0
- package/lib/error-yCSG0mGC.mjs +1449 -0
- package/lib/event-stream-BDPi64mK.mjs +151 -0
- package/lib/event-stream-BbFZJy-z.mjs +151 -0
- package/lib/event-stream-loUa0s1T.mjs +151 -0
- package/lib/fs-promises-shim-Bx0fpxuk.mjs +57 -0
- package/lib/github-copilot-4GaMtQmg.mjs +208 -0
- package/lib/github-copilot-B1yiyVRh.mjs +104 -0
- package/lib/github-copilot-B2HGSIJK.mjs +104 -0
- package/lib/github-copilot-BWT3eAOF.mjs +208 -0
- package/lib/github-copilot-CYkjeTi3.mjs +208 -0
- package/lib/github-copilot-jscUZ8Hf.mjs +104 -0
- package/lib/gitlab-duo-CM7Ref1H.mjs +155 -0
- package/lib/gitlab-duo-akICyfeD.mjs +155 -0
- package/lib/gitlab-duo-gubO4FNw.mjs +155 -0
- package/lib/gitlab-duo-workflow-BGAF6JDw.mjs +91 -0
- package/lib/gitlab-duo-workflow-CSEgYk3N.mjs +91 -0
- package/lib/gitlab-duo-workflow-D78tdn8E.mjs +91 -0
- package/lib/google-BS4T25Pn.mjs +30 -0
- package/lib/google-TFe-7-of.mjs +30 -0
- package/lib/google-antigravity-DGmwC2KV.mjs +198 -0
- package/lib/google-antigravity-DNYhgTpA.mjs +198 -0
- package/lib/google-antigravity-Dlkdhahq.mjs +198 -0
- package/lib/google-auth-B6gjsZvg.mjs +232 -0
- package/lib/google-auth-De7_s3eo.mjs +232 -0
- package/lib/google-auth-DhqpOYrX.mjs +232 -0
- package/lib/google-gemini-cli-BG_LRdIV.mjs +850 -0
- package/lib/google-gemini-cli-CWu8HvXW.mjs +211 -0
- package/lib/google-gemini-cli-Cb97bzQc.mjs +850 -0
- package/lib/google-gemini-cli-Cqds_Q5T.mjs +850 -0
- package/lib/google-gemini-cli-Dsztq0N5.mjs +211 -0
- package/lib/google-gemini-cli-zJhiOcND.mjs +211 -0
- package/lib/google-oauth-shared-CuEHaDDj.mjs +147 -0
- package/lib/google-oauth-shared-CwNWYdmR.mjs +147 -0
- package/lib/google-oauth-shared-yqlyqPPC.mjs +147 -0
- package/lib/google-shared-CgKmmdO3.mjs +760 -0
- package/lib/google-shared-Dr-VG-ap.mjs +760 -0
- package/lib/google-shared-DzhCb9b0.mjs +760 -0
- package/lib/google-vertex-BDN2tLaD.mjs +99 -0
- package/lib/google-vertex-DopVOQOG.mjs +99 -0
- package/lib/google-vertex-kscbkyRb.mjs +99 -0
- package/lib/google-zyDOG9x7.mjs +30 -0
- package/lib/identity-Da1GPU9a.mjs +219 -0
- package/lib/identity-DnB63pS7.mjs +219 -0
- package/lib/identity-xRP7_eSR.mjs +219 -0
- package/lib/idle-iterator-Baoyn3Hp.mjs +366 -0
- package/lib/idle-iterator-C6XbWcuB.mjs +366 -0
- package/lib/idle-iterator-DUjKQEGT.mjs +366 -0
- package/lib/index.mjs +4608 -3935
- package/lib/kimi--wUOWER5.mjs +236 -0
- package/lib/kimi-DMVI-zrZ.mjs +236 -0
- package/lib/kimi-Dh0pv3WZ.mjs +236 -0
- package/lib/markit-BL3cH47U.mjs +1417 -0
- package/lib/markit-BNiNY6SE.mjs +1417 -0
- package/lib/markit-M0jhziqU.mjs +1417 -0
- package/lib/minimax-code-BK-3eBbz.mjs +53 -0
- package/lib/minimax-code-DCbuA6Yk.mjs +53 -0
- package/lib/minimax-code-Uq8Qu9LW.mjs +53 -0
- package/lib/models-CfT4coYT.mjs +104660 -0
- package/lib/models-D_XnT6ay.mjs +104660 -0
- package/lib/models-SGASygJ3.mjs +104660 -0
- package/lib/ollama-BQMGPxtV.mjs +589 -0
- package/lib/ollama-DPzBmq89.mjs +589 -0
- package/lib/ollama-DoBz5SFm.mjs +589 -0
- package/lib/openai-codex-Bi-4S4PX.mjs +255 -0
- package/lib/openai-codex-CQL_kvYL.mjs +255 -0
- package/lib/openai-codex-Ch8CkT3p.mjs +255 -0
- package/lib/openai-codex-responses-CvxL_FMF.mjs +3144 -0
- package/lib/openai-codex-responses-Cz-2qpC7.mjs +3144 -0
- package/lib/openai-codex-responses-pejZgdZo.mjs +3144 -0
- package/lib/openai-completions-CF8IyNtP.mjs +1360 -0
- package/lib/openai-completions-DIf361kF.mjs +1360 -0
- package/lib/openai-completions-i8doczAk.mjs +1360 -0
- package/lib/openai-reasoning-fallback-DGVVLgd1.mjs +286 -0
- package/lib/openai-reasoning-fallback-Nj6yB2rY.mjs +286 -0
- package/lib/openai-reasoning-fallback-OdJREP1o.mjs +286 -0
- package/lib/openai-responses-BqXJInHX.mjs +867 -0
- package/lib/openai-responses-DTkBiiy-.mjs +867 -0
- package/lib/openai-responses-Db_a-U4-.mjs +867 -0
- package/lib/opencode-DaJjcUbf.mjs +38 -0
- package/lib/opencode-DhMolSoB.mjs +38 -0
- package/lib/opencode-Rq_Q2vAO.mjs +38 -0
- package/lib/perplexity-9b-RQ0mV.mjs +194 -0
- package/lib/perplexity-C4iLVMUj.mjs +194 -0
- package/lib/perplexity-Uu7Jzg5b.mjs +194 -0
- package/lib/proxy-DDqrmjjC.mjs +190 -0
- package/lib/proxy-ZtUcl6r5.mjs +190 -0
- package/lib/proxy-pM_NIv-H.mjs +190 -0
- package/lib/request-debug-HJMoJncL.mjs +183 -0
- package/lib/sanitize-text-CfcXzw5X.mjs +33 -0
- package/lib/scrapers-BkFfcPHj.mjs +9198 -0
- package/lib/scrapers-CL-WgdU1.mjs +9198 -0
- package/lib/scrapers-CVptboMs.mjs +9198 -0
- package/lib/scrapers-CipyKyCH.mjs +9198 -0
- package/lib/scrapers-DGfrYhtw.mjs +9198 -0
- package/lib/scrapers-DSUPYoUF.mjs +9198 -0
- package/lib/scrapers-DZ2GXmp7.mjs +9198 -0
- package/lib/scrapers-nhQAvc8d.mjs +9198 -0
- package/lib/shell-snapshot-fn-env.sh +67 -43
- package/lib/src-BgpusnsB.mjs +5076 -0
- package/lib/src-DTid0g4K.mjs +5001 -0
- package/lib/src-lDmkSRbm.mjs +5006 -0
- package/lib/stream-BlldLkVq.mjs +10236 -0
- package/lib/stream-CRtexYLe.mjs +10236 -0
- package/lib/stream-C_RAoxJC.mjs +10236 -0
- package/lib/stream-markup-healing-2toHXWXM.mjs +491 -0
- package/lib/stream-markup-healing-DMsxBx1L.mjs +491 -0
- package/lib/stream-markup-healing-KMkiAA00.mjs +491 -0
- package/lib/turndown-DN7ByzEI.mjs +667 -0
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/config/fields.d.ts +1 -1
- package/lib/types/config/settings.d.ts +25 -3
- package/lib/types/config/truncate.d.ts +9 -5
- package/lib/types/tools/bash/background.d.ts +1 -1
- package/lib/types/tools/bash/bash-executor.d.ts +4 -0
- package/lib/types/tools/bash/bash-interceptor.d.ts +10 -8
- package/lib/types/tools/bash/cd-workdir.d.ts +35 -0
- package/lib/types/tools/bash/non-interactive-env.d.ts +2 -0
- package/lib/types/tools/bash/render.d.ts +4 -3
- package/lib/types/tools/bash/sanitize-text.d.ts +5 -0
- package/lib/types/tools/bash/shell-snapshot.d.ts +4 -1
- package/lib/types/tools/bash/shell-tokenize.d.ts +60 -0
- package/lib/types/tools/bash/streaming-output.d.ts +12 -212
- package/lib/types/tools/bash/which.d.ts +20 -0
- package/lib/types/tools/bash/windows-shell.d.ts +31 -0
- package/lib/types/tools/edit/adapter/index.d.ts +25 -3
- package/lib/types/tools/edit/adapter/tools/auto-generated-guard.d.ts +7 -17
- package/lib/types/tools/glob/adapter/index.d.ts +27 -1
- package/lib/types/tools/grep/adapter/index.d.ts +29 -1
- package/lib/types/tools/omp/edit/dispatch.d.ts +43 -0
- package/lib/types/tools/omp/edit/hashline/noop-loop-guard.d.ts +3 -1
- package/lib/types/tools/omp/sdk.d.ts +6 -0
- package/lib/types/tools/omp/tools/auto-generated-guard.d.ts +20 -0
- package/lib/types/tools/omp/tools/fetch.d.ts +2 -0
- package/lib/types/tools/omp/tools/read.d.ts +2 -8
- package/lib/types/tools/omp/tools/write.d.ts +4 -4
- package/lib/types/tools/omp/utils/image-loading.d.ts +10 -36
- package/lib/types/tools/read/adapter/index.d.ts +4 -1
- package/lib/types/tools/read-image/index.d.ts +4 -0
- package/lib/types/tools/shared/auto-generated-guard.dsh.d.ts +16 -4
- package/lib/types/tools/shared/bun-shim.d.ts +39 -3
- package/lib/types/tools/shared/fs-promises-shim.d.ts +3 -1
- package/lib/types/tools/shared/image-bridge.d.ts +98 -0
- package/lib/types/tools/shared/omp-prompt.d.ts +17 -9
- package/lib/types/tools/shared/session-state.d.ts +9 -3
- package/lib/utils-2Wq2eATG.mjs +8119 -0
- package/lib/utils-BnX8O3vi.mjs +12303 -0
- package/lib/utils-Bw139LTz.mjs +8119 -0
- package/lib/utils-CE8N8P4Q.mjs +12315 -0
- package/lib/utils-CZw4tVZQ.mjs +12315 -0
- package/lib/utils-C_MrQ_0z.mjs +12301 -0
- package/lib/utils-CiGN7OHR.mjs +12315 -0
- package/lib/utils-Cp76gCyk.mjs +8119 -0
- package/lib/utils-D0GRDgAr.mjs +12315 -0
- package/lib/utils-D0Z1fyoO.mjs +12299 -0
- package/lib/utils-D2YknRk4.mjs +51 -0
- package/lib/utils-DppGHXQB.mjs +51 -0
- package/lib/utils-DzXZDfYc.mjs +51 -0
- package/lib/utils-W8WdSBpX.mjs +12315 -0
- package/lib/vision-guard-01qSwFpf.mjs +92 -0
- package/lib/vision-guard-BDD4OrKo.mjs +92 -0
- package/lib/vision-guard-DzZGaOga.mjs +92 -0
- package/lib/wafer-CgtPHerf.mjs +17 -0
- package/lib/wafer-DRU0EjZX.mjs +17 -0
- package/lib/wafer-cBtrn6q9.mjs +17 -0
- package/lib/xai-oauth-Buk8XVvM.mjs +446 -0
- package/lib/xai-oauth-CMal8l1O.mjs +446 -0
- package/lib/xai-oauth-DPHDXKD7.mjs +446 -0
- package/lib/xiaomi-CZgwvX2G.mjs +157 -0
- package/lib/xiaomi-D2qT5cAC.mjs +157 -0
- package/lib/xiaomi-Dd87KSg5.mjs +157 -0
- package/lib/zai-7DWMkEqM.mjs +203 -0
- package/lib/zai-C59YnomG.mjs +203 -0
- package/lib/zai-CFeOt-53.mjs +203 -0
- package/lib/zip-Cg9ANN_U.mjs +1316 -0
- package/lib/zip-D36ZKsP8.mjs +1316 -0
- package/lib/zip-D6QXpvaJ.mjs +1316 -0
- package/package.json +14 -10
- package/presets/package.json +1 -1
- package/presets/tool-plus-ptc/preset.yml +1 -1
- package/presets/tool-plus-standard/preset.yml +1 -1
|
@@ -0,0 +1,3115 @@
|
|
|
1
|
+
import { installBunShim as __installBunShim } from "./bun-shim.mjs";
|
|
2
|
+
__installBunShim();
|
|
3
|
+
import { r as __exportAll } from "./fs-promises-shim-B-9cMxz5.mjs";
|
|
4
|
+
import { P as $env, S as debug, T as warn, _ as parseStreamingJsonThrottled, a as readSseEvents, h as parseJsonWithRepair, q as getInstallId, y as isEnoent } from "./src-lDmkSRbm.mjs";
|
|
5
|
+
import { A as isTransientStreamParseError, D as isFastModeUnsupported, E as isCopilotTransientModelError, F as AnthropicStreamEnvelopeError, M as AnthropicApiError, N as AnthropicConnectionError, O as isGrammarError, P as AnthropicConnectionTimeoutError, a as isTransientStatus, f as getHeadersFromError, i as isProviderRetryableError$1, k as isStreamEnvelopeError, l as finalize, n as StreamTimeoutError, p as getRetryAfterMsFromHeaders, r as ValidationError, s as ProviderResponseError, t as ConfigurationError, u as finalizeErrorMessage, v as AbortError } from "./error-yCSG0mGC.mjs";
|
|
6
|
+
import { A as NO_STRICT, C as kStreamingPartialJson, R as spillToDescription, S as kStreamingLastParseLen, a as normalizeSystemPrompts, h as clearStreamingPartialJson, k as toolWireSchema, m as transformMessages, o as normalizeToolCallId, s as resolveCacheRetention, w as renderDemotedThinking, y as kStreamingBlockIndex, z as COMBINATOR_KEYS } from "./utils-Bw139LTz.mjs";
|
|
7
|
+
import { n as isRecord } from "./type-guards-B9VVjRDK.mjs";
|
|
8
|
+
import { Ct as withEmptyCompletionRetry, K as getOpenAIPromptCacheKey, _t as isForcedToolChoice, gt as resolveGitHubCopilotBaseUrl, ht as hasCopilotVisionInput, mt as buildCopilotDynamicHeaders, n as getEnvApiKey, t as OUTPUT_FALLBACK_BUFFER, wt as isFoundryEnabled } from "./stream-BlldLkVq.mjs";
|
|
9
|
+
import { t as createAbortSourceTracker } from "./abort-D1_Z5Ut5.mjs";
|
|
10
|
+
import { N as isVertexRawPredictUrl, f as isAnthropicSigningProxyUrl, j as hostMatchesUrl, n as getBundledModel, o as mapEffortToAnthropicAdaptiveEffort, p as isOfficialAnthropicApiUrl, t as calculateCost } from "./models-D_XnT6ay.mjs";
|
|
11
|
+
import { r as isAnthropicOAuthToken } from "./utils-DzXZDfYc.mjs";
|
|
12
|
+
import { u as parseGitHubCopilotApiKey } from "./github-copilot-jscUZ8Hf.mjs";
|
|
13
|
+
import { t as NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard-DzZGaOga.mjs";
|
|
14
|
+
import { t as AssistantMessageEventStream } from "./event-stream-loUa0s1T.mjs";
|
|
15
|
+
import { a as coworkUserAgent, i as claudeCodeVersion, n as CLAUDE_CODE_MAX_OUTPUT_TOKENS, r as claudeCodeSystemInstruction } from "./anthropic-sCMH7JID.mjs";
|
|
16
|
+
import { a as getStreamIdleTimeoutMs, i as getStreamFirstEventTimeoutMs, o as iterateWithIdleTimeout } from "./idle-iterator-Baoyn3Hp.mjs";
|
|
17
|
+
import { t as notifyProviderResponse } from "./provider-response-C6vcbQyO.mjs";
|
|
18
|
+
import { t as notifyRawSseEvent } from "./sse-debug-CYdjOC96.mjs";
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import { scheduler } from "node:timers/promises";
|
|
21
|
+
import * as nodeCrypto from "node:crypto";
|
|
22
|
+
import * as tls from "node:tls";
|
|
23
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/providers/anthropic-wire.ts
|
|
24
|
+
/** True when a block is complete Anthropic server-tool history omp can replay. */
|
|
25
|
+
function isAnthropicServerToolHistoryBlock(block) {
|
|
26
|
+
if (block.type === "server_tool_use") return (block.name === "web_search" || block.name === "tool_search_tool_regex" || block.name === "tool_search_tool_bm25") && typeof block.id === "string" && block.id.length > 0;
|
|
27
|
+
if (block.type === "web_search_tool_result" || block.type === "tool_search_tool_result") return typeof block.tool_use_id === "string" && block.tool_use_id.length > 0 && Object.hasOwn(block, "content");
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/utils/sdk-stream-timeout.ts
|
|
32
|
+
/**
|
|
33
|
+
* Shared helpers for mapping `StreamOptions.streamFirstEventTimeoutMs` onto
|
|
34
|
+
* underlying SDK request-timeout options.
|
|
35
|
+
*
|
|
36
|
+
* The hint is intentionally not a watchdog — it just narrows the SDK's
|
|
37
|
+
* "transport timeout" window so a stuck pre-stream request fails fast
|
|
38
|
+
* instead of hanging on the default (often multi-minute) SDK timeout. Once
|
|
39
|
+
* the stream actually starts, silence is not failure; callers must abort
|
|
40
|
+
* to interrupt a quiet stream.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Coerce a caller-supplied `streamFirstEventTimeoutMs` into a positive integer suitable
|
|
44
|
+
* for the SDK's `timeout` option. Returns `undefined` when the caller passed nothing,
|
|
45
|
+
* a non-finite value, or a non-positive value (preserving the SDK's default).
|
|
46
|
+
*/
|
|
47
|
+
function resolveSdkTimeoutMs(streamFirstEventTimeoutMs) {
|
|
48
|
+
if (streamFirstEventTimeoutMs === void 0) return void 0;
|
|
49
|
+
if (!Number.isFinite(streamFirstEventTimeoutMs)) return void 0;
|
|
50
|
+
if (streamFirstEventTimeoutMs <= 0) return void 0;
|
|
51
|
+
return Math.trunc(streamFirstEventTimeoutMs);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build per-request SDK options that combine an abort signal with the optional
|
|
55
|
+
* `streamFirstEventTimeoutMs` request-timeout hint.
|
|
56
|
+
*
|
|
57
|
+
* The returned `{ signal, timeout?, maxRetries? }` shape is compatible with both
|
|
58
|
+
* OpenAI's and Anthropic's `RequestOptions` (and any other SDK that follows the
|
|
59
|
+
* Stainless conventions), so callers from any of those providers can spread the
|
|
60
|
+
* result directly into `client.X.create(params, requestOptions)`.
|
|
61
|
+
*
|
|
62
|
+
* When the hint is set, retries are forced to zero so the SDK does not silently
|
|
63
|
+
* extend the caller's explicit deadline by re-attempting after a timeout.
|
|
64
|
+
*/
|
|
65
|
+
function createSdkStreamRequestOptions(signal, streamFirstEventTimeoutMs) {
|
|
66
|
+
const timeout = resolveSdkTimeoutMs(streamFirstEventTimeoutMs);
|
|
67
|
+
if (timeout === void 0) return { signal };
|
|
68
|
+
return {
|
|
69
|
+
signal,
|
|
70
|
+
timeout,
|
|
71
|
+
maxRetries: 0
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/providers/anthropic-client.ts
|
|
76
|
+
/**
|
|
77
|
+
* Minimal HTTP client for the Anthropic Messages API.
|
|
78
|
+
*
|
|
79
|
+
* pi-ai builds every request header itself (`buildAnthropicHeaders`), serializes
|
|
80
|
+
* the body itself (`buildParams`), and parses SSE frames itself
|
|
81
|
+
* (`iterateAnthropicEvents`), so the only `@anthropic-ai/sdk` surface this
|
|
82
|
+
* package ever exercised was URL assembly, auth-header injection, bounded
|
|
83
|
+
* retries, the pre-response timeout, and HTTP-error-to-status mapping. This
|
|
84
|
+
* module implements exactly that surface and nothing else.
|
|
85
|
+
*
|
|
86
|
+
* Behavioral contract (kept compatible with the SDK so downstream error
|
|
87
|
+
* classification keeps working):
|
|
88
|
+
* - Non-2xx responses throw {@link AnthropicApiError} whose `status` property
|
|
89
|
+
* carries the HTTP status and whose message is `"<status> <body>"`.
|
|
90
|
+
* - Pre-response timeouts throw {@link AnthropicConnectionTimeoutError}
|
|
91
|
+
* ("Request timed out.").
|
|
92
|
+
* - Caller aborts throw an `Error` with message "Request was aborted.".
|
|
93
|
+
* - Retries: connection errors and 408/409/429/5xx (or `x-should-retry: true`)
|
|
94
|
+
* are retried up to `maxRetries` times, honoring `retry-after-ms` /
|
|
95
|
+
* `retry-after`, otherwise exponential backoff (0.5s * 2^n, capped at 8s,
|
|
96
|
+
* with up to 25% jitter).
|
|
97
|
+
*/
|
|
98
|
+
/** Default pre-response timeout, matching the SDK's 10-minute default. */
|
|
99
|
+
const DEFAULT_TIMEOUT_MS = 6e5;
|
|
100
|
+
/** Default retry budget, matching the SDK's default. */
|
|
101
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
102
|
+
const INITIAL_RETRY_DELAY_S = .5;
|
|
103
|
+
const MAX_RETRY_DELAY_S = 8;
|
|
104
|
+
function createAbortError() {
|
|
105
|
+
return new AbortError("Request was aborted.");
|
|
106
|
+
}
|
|
107
|
+
/** `x-should-retry` override, then 408/409/429/5xx. */
|
|
108
|
+
function shouldRetryResponse(response) {
|
|
109
|
+
const shouldRetryHeader = response.headers.get("x-should-retry");
|
|
110
|
+
if (shouldRetryHeader === "true") return true;
|
|
111
|
+
if (shouldRetryHeader === "false") return false;
|
|
112
|
+
const status = response.status;
|
|
113
|
+
return isTransientStatus(status) || status === 409;
|
|
114
|
+
}
|
|
115
|
+
/** Server-suggested delay (`retry-after-ms`, then `retry-after` seconds or HTTP date). */
|
|
116
|
+
function retryDelayFromHeaders(headers) {
|
|
117
|
+
if (!headers) return void 0;
|
|
118
|
+
const retryAfterMs = headers.get("retry-after-ms");
|
|
119
|
+
if (retryAfterMs) {
|
|
120
|
+
const ms = Number.parseFloat(retryAfterMs);
|
|
121
|
+
if (Number.isFinite(ms) && ms >= 0) return ms;
|
|
122
|
+
}
|
|
123
|
+
const retryAfter = headers.get("retry-after");
|
|
124
|
+
if (retryAfter) {
|
|
125
|
+
const seconds = Number.parseFloat(retryAfter);
|
|
126
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
127
|
+
const dateMs = Date.parse(retryAfter) - Date.now();
|
|
128
|
+
if (Number.isFinite(dateMs) && dateMs >= 0) return dateMs;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function calculateAnthropicRetryDelayMs(attempt) {
|
|
132
|
+
return Math.min(INITIAL_RETRY_DELAY_S * 2 ** attempt, MAX_RETRY_DELAY_S) * (1 - Math.random() * .25) * 1e3;
|
|
133
|
+
}
|
|
134
|
+
function hasHeaderCaseInsensitive(headers, lowerName) {
|
|
135
|
+
for (const key in headers) if (key.toLowerCase() === lowerName) return true;
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Lazy in-flight request handle. The HTTP request starts on the first
|
|
140
|
+
* `asResponse()` call; subsequent calls return the same promise.
|
|
141
|
+
*
|
|
142
|
+
* Shape-compatible with the SDK's `APIPromise.asResponse()` so
|
|
143
|
+
* `getAnthropicStreamResponse` treats internal and injected clients uniformly.
|
|
144
|
+
*/
|
|
145
|
+
var AnthropicApiRequest = class {
|
|
146
|
+
#start;
|
|
147
|
+
#response;
|
|
148
|
+
constructor(start) {
|
|
149
|
+
this.#start = start;
|
|
150
|
+
}
|
|
151
|
+
asResponse() {
|
|
152
|
+
this.#response ??= this.#start();
|
|
153
|
+
return this.#response;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* `messages` resource. `create` lives on the prototype so tests can intercept
|
|
158
|
+
* every outgoing request with `vi.spyOn(AnthropicMessages.prototype, "create")`.
|
|
159
|
+
*/
|
|
160
|
+
var AnthropicMessages = class {
|
|
161
|
+
#client;
|
|
162
|
+
#path;
|
|
163
|
+
constructor(client, path) {
|
|
164
|
+
this.#client = client;
|
|
165
|
+
this.#path = path;
|
|
166
|
+
}
|
|
167
|
+
create(params, options) {
|
|
168
|
+
return this.#client.request(this.#path, params, options);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var AnthropicMessagesClient = class {
|
|
172
|
+
messages;
|
|
173
|
+
beta;
|
|
174
|
+
#options;
|
|
175
|
+
constructor(options) {
|
|
176
|
+
this.#options = options;
|
|
177
|
+
this.messages = new AnthropicMessages(this, "/v1/messages");
|
|
178
|
+
this.beta = { messages: new AnthropicMessages(this, "/v1/messages?beta=true") };
|
|
179
|
+
}
|
|
180
|
+
request(path, params, options) {
|
|
181
|
+
return new AnthropicApiRequest(() => this.#send(path, params, options));
|
|
182
|
+
}
|
|
183
|
+
#buildHeaders(requestHeaders) {
|
|
184
|
+
const opts = this.#options;
|
|
185
|
+
const defaults = opts.defaultHeaders ?? {};
|
|
186
|
+
const headers = {};
|
|
187
|
+
if (opts.apiKey != null && !hasHeaderCaseInsensitive(defaults, "x-api-key")) headers["X-Api-Key"] = opts.apiKey;
|
|
188
|
+
if (opts.authToken != null && !hasHeaderCaseInsensitive(defaults, "authorization")) headers.Authorization = `Bearer ${opts.authToken}`;
|
|
189
|
+
Object.assign(headers, defaults);
|
|
190
|
+
Object.assign(headers, requestHeaders);
|
|
191
|
+
return headers;
|
|
192
|
+
}
|
|
193
|
+
async #send(path, params, options) {
|
|
194
|
+
const opts = this.#options;
|
|
195
|
+
const fetchFn = opts.fetch ?? fetch;
|
|
196
|
+
const callerSignal = options?.signal;
|
|
197
|
+
const timeoutMs = options?.timeout ?? opts.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
198
|
+
const maxRetries = Math.max(0, options?.maxRetries ?? opts.maxRetries ?? DEFAULT_MAX_RETRIES);
|
|
199
|
+
const maxRetryDelayMs = options?.maxRetryDelayMs ?? opts.maxRetryDelayMs ?? 6e4;
|
|
200
|
+
const url = `${opts.baseURL ?? "https://api.anthropic.com"}${path}`;
|
|
201
|
+
const headers = this.#buildHeaders(options?.headers);
|
|
202
|
+
const body = JSON.stringify(params);
|
|
203
|
+
for (let attempt = 0;; attempt++) {
|
|
204
|
+
if (callerSignal?.aborted) throw createAbortError();
|
|
205
|
+
let response;
|
|
206
|
+
try {
|
|
207
|
+
response = await this.#fetchOnce(fetchFn, url, headers, body, timeoutMs, callerSignal);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (callerSignal?.aborted) throw createAbortError();
|
|
210
|
+
if (attempt < maxRetries) {
|
|
211
|
+
await this.#backoff(attempt, void 0, callerSignal);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (error instanceof AnthropicConnectionTimeoutError) throw error;
|
|
215
|
+
throw new AnthropicConnectionError(error);
|
|
216
|
+
}
|
|
217
|
+
if (response.ok) return response;
|
|
218
|
+
if (attempt < maxRetries && shouldRetryResponse(response)) {
|
|
219
|
+
const headerDelayMs = retryDelayFromHeaders(response.headers);
|
|
220
|
+
if (headerDelayMs !== void 0 && maxRetryDelayMs > 0 && headerDelayMs > maxRetryDelayMs) throw await AnthropicApiError.fromResponse(response, callerSignal);
|
|
221
|
+
await response.body?.cancel().catch(() => {});
|
|
222
|
+
await this.#backoff(attempt, response.headers, callerSignal);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
throw await AnthropicApiError.fromResponse(response, callerSignal);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async #fetchOnce(fetchFn, url, headers, body, timeoutMs, callerSignal) {
|
|
229
|
+
const controller = new AbortController();
|
|
230
|
+
let timedOut = false;
|
|
231
|
+
const timer = setTimeout(() => {
|
|
232
|
+
timedOut = true;
|
|
233
|
+
controller.abort();
|
|
234
|
+
}, timeoutMs);
|
|
235
|
+
const onAbort = () => controller.abort();
|
|
236
|
+
callerSignal?.addEventListener("abort", onAbort, { once: true });
|
|
237
|
+
try {
|
|
238
|
+
return await fetchFn(url, {
|
|
239
|
+
...this.#options.fetchOptions ?? {},
|
|
240
|
+
method: "POST",
|
|
241
|
+
headers,
|
|
242
|
+
body,
|
|
243
|
+
signal: controller.signal
|
|
244
|
+
});
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (timedOut && !callerSignal?.aborted) throw new AnthropicConnectionTimeoutError();
|
|
247
|
+
throw error;
|
|
248
|
+
} finally {
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
callerSignal?.removeEventListener("abort", onAbort);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async #backoff(attempt, responseHeaders, signal) {
|
|
254
|
+
const delayMs = retryDelayFromHeaders(responseHeaders) ?? calculateAnthropicRetryDelayMs(attempt);
|
|
255
|
+
try {
|
|
256
|
+
await scheduler.wait(delayMs, { signal });
|
|
257
|
+
} catch {
|
|
258
|
+
throw createAbortError();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/providers/anthropic.ts
|
|
264
|
+
var anthropic_exports = /* @__PURE__ */ __exportAll({
|
|
265
|
+
CLAUDE_CODE_MAX_OUTPUT_TOKENS: () => CLAUDE_CODE_MAX_OUTPUT_TOKENS,
|
|
266
|
+
applyAnthropicUsageExtras: () => applyAnthropicUsageExtras,
|
|
267
|
+
applyClaudeToolPrefix: () => applyClaudeToolPrefix,
|
|
268
|
+
buildAnthropicClientOptions: () => buildAnthropicClientOptions,
|
|
269
|
+
buildAnthropicHeaders: () => buildAnthropicHeaders,
|
|
270
|
+
buildAnthropicSystemBlocks: () => buildAnthropicSystemBlocks,
|
|
271
|
+
buildBetaHeader: () => buildBetaHeader,
|
|
272
|
+
claudeCodeSystemInstruction: () => claudeCodeSystemInstruction,
|
|
273
|
+
claudeCodeVersion: () => claudeCodeVersion,
|
|
274
|
+
claudeToolPrefix: () => "_",
|
|
275
|
+
convertAnthropicMessages: () => convertAnthropicMessages,
|
|
276
|
+
coworkHeaders: () => coworkHeaders,
|
|
277
|
+
coworkUserAgent: () => coworkUserAgent,
|
|
278
|
+
deriveClaudeDeviceId: () => deriveClaudeDeviceId,
|
|
279
|
+
isClaudeCloakingUserId: () => isClaudeCloakingUserId,
|
|
280
|
+
isInvalidThinkingSignatureError: () => isInvalidThinkingSignatureError,
|
|
281
|
+
isProviderRetryableError: () => isProviderRetryableError,
|
|
282
|
+
mapStainlessArch: () => mapStainlessArch,
|
|
283
|
+
maybeAddReplayUnsignedThinkingHint: () => maybeAddReplayUnsignedThinkingHint,
|
|
284
|
+
normalizeAnthropicBaseUrl: () => normalizeAnthropicBaseUrl,
|
|
285
|
+
normalizeAnthropicToolSchema: () => normalizeAnthropicToolSchema,
|
|
286
|
+
normalizeExtraBetas: () => normalizeExtraBetas,
|
|
287
|
+
resolveAnthropicCustomHeadersForBaseUrl: () => resolveAnthropicCustomHeadersForBaseUrl,
|
|
288
|
+
resolveAnthropicMetadataUserId: () => resolveAnthropicMetadataUserId,
|
|
289
|
+
streamAnthropic: () => streamAnthropic,
|
|
290
|
+
stripClaudeToolPrefix: () => stripClaudeToolPrefix,
|
|
291
|
+
wrapFetchForCch: () => wrapFetchForCch
|
|
292
|
+
});
|
|
293
|
+
function normalizeAnthropicBaseUrl(baseUrl) {
|
|
294
|
+
const trimmed = baseUrl?.trim();
|
|
295
|
+
if (!trimmed) return;
|
|
296
|
+
const withoutTrailingSlashes = trimmed.replace(/\/+$/, "");
|
|
297
|
+
return withoutTrailingSlashes.endsWith("/v1") ? withoutTrailingSlashes.slice(0, -3) : withoutTrailingSlashes;
|
|
298
|
+
}
|
|
299
|
+
function buildBetaHeader(baseBetas, extraBetas) {
|
|
300
|
+
const seen = /* @__PURE__ */ new Set();
|
|
301
|
+
const result = [];
|
|
302
|
+
for (const beta of [...baseBetas, ...extraBetas]) {
|
|
303
|
+
const trimmed = beta.trim();
|
|
304
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
305
|
+
seen.add(trimmed);
|
|
306
|
+
result.push(trimmed);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return result.join(",");
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Merge an extra Anthropic beta into a caller-provided `anthropic-beta` header,
|
|
313
|
+
* preserving the caller's key casing and deduping the tokens. Returns a
|
|
314
|
+
* single-entry header record for a per-request `headers` override — used to
|
|
315
|
+
* attach a required beta to injected SDK clients that bypass the client-level
|
|
316
|
+
* beta construction.
|
|
317
|
+
*/
|
|
318
|
+
function mergeAnthropicBetaHeader(callerHeaders, beta) {
|
|
319
|
+
for (const key in callerHeaders) if (key.toLowerCase() === "anthropic-beta") return { [key]: buildBetaHeader(normalizeExtraBetas(callerHeaders[key]), [beta]) };
|
|
320
|
+
return { "anthropic-beta": beta };
|
|
321
|
+
}
|
|
322
|
+
const midConversationSystemBeta = "mid-conversation-system-2026-04-07";
|
|
323
|
+
const contextManagementBeta = "context-management-2025-06-27";
|
|
324
|
+
const structuredOutputsBeta = "structured-outputs-2025-12-15";
|
|
325
|
+
const thinkingTokenCountBeta = "thinking-token-count-2026-05-13";
|
|
326
|
+
const fallbackCreditBeta = "fallback-credit-2026-06-01";
|
|
327
|
+
const coworkUtilityBetaDefaults = [
|
|
328
|
+
"interleaved-thinking-2025-05-14",
|
|
329
|
+
thinkingTokenCountBeta,
|
|
330
|
+
contextManagementBeta,
|
|
331
|
+
"prompt-caching-scope-2026-01-05",
|
|
332
|
+
structuredOutputsBeta
|
|
333
|
+
];
|
|
334
|
+
const coworkAgentBetaDefaults = [
|
|
335
|
+
"claude-code-20250219",
|
|
336
|
+
"interleaved-thinking-2025-05-14",
|
|
337
|
+
thinkingTokenCountBeta,
|
|
338
|
+
contextManagementBeta,
|
|
339
|
+
"prompt-caching-scope-2026-01-05",
|
|
340
|
+
midConversationSystemBeta,
|
|
341
|
+
"advanced-tool-use-2025-11-20"
|
|
342
|
+
];
|
|
343
|
+
const extendedCacheTtlBeta = "extended-cache-ttl-2025-04-11";
|
|
344
|
+
const fineGrainedToolStreamingBeta = "fine-grained-tool-streaming-2025-05-14";
|
|
345
|
+
const interleavedThinkingBeta = "interleaved-thinking-2025-05-14";
|
|
346
|
+
const fastModeBeta = "fast-mode-2026-02-01";
|
|
347
|
+
const taskBudgetBeta = "task-budgets-2026-03-13";
|
|
348
|
+
const effortBeta = "effort-2025-11-24";
|
|
349
|
+
const serverSideFallbackBeta = "server-side-fallback-2026-06-01";
|
|
350
|
+
function buildCoworkBetas(agentRequest, thinkingRequest, disableStrictTools = false) {
|
|
351
|
+
if (!agentRequest && !disableStrictTools) return coworkUtilityBetaDefaults;
|
|
352
|
+
const betas = [];
|
|
353
|
+
for (const beta of agentRequest ? coworkAgentBetaDefaults : coworkUtilityBetaDefaults) {
|
|
354
|
+
if (disableStrictTools && beta === structuredOutputsBeta) continue;
|
|
355
|
+
betas.push(beta);
|
|
356
|
+
}
|
|
357
|
+
if (!agentRequest) return betas;
|
|
358
|
+
if (thinkingRequest) betas.push(effortBeta);
|
|
359
|
+
betas.push(fallbackCreditBeta);
|
|
360
|
+
return betas;
|
|
361
|
+
}
|
|
362
|
+
function getHeaderCaseInsensitive(headers, headerName) {
|
|
363
|
+
if (!headers) return void 0;
|
|
364
|
+
const normalizedName = headerName.toLowerCase();
|
|
365
|
+
for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === normalizedName) return value;
|
|
366
|
+
}
|
|
367
|
+
function isClaudeCodeClientUserAgent(userAgent) {
|
|
368
|
+
if (!userAgent) return false;
|
|
369
|
+
return userAgent.toLowerCase().startsWith("claude-cli");
|
|
370
|
+
}
|
|
371
|
+
const sharedHeaders = {
|
|
372
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
373
|
+
Connection: "keep-alive",
|
|
374
|
+
"Content-Type": "application/json",
|
|
375
|
+
"anthropic-version": "2023-06-01",
|
|
376
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
377
|
+
"x-app": "cli"
|
|
378
|
+
};
|
|
379
|
+
function buildAnthropicHeaders(options) {
|
|
380
|
+
const oauthToken = options.isOAuth ?? isAnthropicOAuthToken(options.apiKey);
|
|
381
|
+
const extraBetas = options.extraBetas ?? [];
|
|
382
|
+
const stream = options.stream ?? false;
|
|
383
|
+
const incomingUserAgent = getHeaderCaseInsensitive(options.modelHeaders, "User-Agent");
|
|
384
|
+
const incomingAuthorization = getHeaderCaseInsensitive(options.modelHeaders, "Authorization");
|
|
385
|
+
const incomingApiKey = getHeaderCaseInsensitive(options.modelHeaders, "X-Api-Key");
|
|
386
|
+
const betaHeader = buildBetaHeader(options.coworkBetas ?? (oauthToken ? buildCoworkBetas(true, true) : []), extraBetas);
|
|
387
|
+
const acceptHeader = oauthToken ? "application/json" : stream ? "text/event-stream" : "application/json";
|
|
388
|
+
const isCloudflare = options.isCloudflareAiGateway ?? false;
|
|
389
|
+
const honorAuthorization = !oauthToken && !isCloudflare;
|
|
390
|
+
const allowAnthropicHeaderOverrides = oauthToken && options.allowAnthropicHeaderOverrides === true && !isCloudflare && !isOfficialAnthropicApiUrl(options.baseUrl);
|
|
391
|
+
const honorApiKey = !isCloudflare;
|
|
392
|
+
const modelHeaders = {};
|
|
393
|
+
const anthropicHeaderOverrides = {};
|
|
394
|
+
const filteredEnforcedKeys = [];
|
|
395
|
+
const headerSource = options.modelHeaders;
|
|
396
|
+
if (headerSource) for (const key in headerSource) {
|
|
397
|
+
const value = headerSource[key];
|
|
398
|
+
const lowerKey = key.toLowerCase();
|
|
399
|
+
if (enforcedHeaderKeys.has(lowerKey)) {
|
|
400
|
+
if (allowAnthropicHeaderOverrides && overridableAnthropicHeaderKeys.has(lowerKey)) {
|
|
401
|
+
anthropicHeaderOverrides[key] = value;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (lowerKey === "user-agent") continue;
|
|
405
|
+
if (lowerKey === "authorization" && honorAuthorization) continue;
|
|
406
|
+
if (lowerKey === "x-api-key" && honorApiKey) continue;
|
|
407
|
+
filteredEnforcedKeys.push(key);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
modelHeaders[key] = value;
|
|
411
|
+
}
|
|
412
|
+
if (filteredEnforcedKeys.length > 0) debug("anthropic: ignoring caller-supplied enforced headers", { headers: filteredEnforcedKeys });
|
|
413
|
+
if (isCloudflare) return {
|
|
414
|
+
...modelHeaders,
|
|
415
|
+
Accept: acceptHeader,
|
|
416
|
+
...sharedHeaders,
|
|
417
|
+
...incomingUserAgent ? { "User-Agent": incomingUserAgent } : {},
|
|
418
|
+
...betaHeader ? { "anthropic-beta": betaHeader } : {},
|
|
419
|
+
"cf-aig-authorization": `Bearer ${options.apiKey}`
|
|
420
|
+
};
|
|
421
|
+
if (oauthToken) {
|
|
422
|
+
const userAgent = isClaudeCodeClientUserAgent(incomingUserAgent) ? incomingUserAgent : coworkUserAgent;
|
|
423
|
+
const headers = {
|
|
424
|
+
...modelHeaders,
|
|
425
|
+
Accept: acceptHeader,
|
|
426
|
+
"Content-Type": "application/json",
|
|
427
|
+
"User-Agent": userAgent,
|
|
428
|
+
...options.claudeCodeSessionId ? { "X-Claude-Code-Session-Id": options.claudeCodeSessionId } : {},
|
|
429
|
+
...coworkHeaders,
|
|
430
|
+
...betaHeader ? { "anthropic-beta": betaHeader } : {},
|
|
431
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
432
|
+
"anthropic-version": "2023-06-01",
|
|
433
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
434
|
+
"x-app": "cli",
|
|
435
|
+
"x-client-request-id": nodeCrypto.randomUUID(),
|
|
436
|
+
Connection: "keep-alive",
|
|
437
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
438
|
+
...incomingApiKey ? { "X-Api-Key": incomingApiKey } : {}
|
|
439
|
+
};
|
|
440
|
+
return allowAnthropicHeaderOverrides ? mergeHeaders(headers, anthropicHeaderOverrides) : headers;
|
|
441
|
+
} else if (!isOfficialAnthropicApiUrl(options.baseUrl)) return {
|
|
442
|
+
...modelHeaders,
|
|
443
|
+
Accept: acceptHeader,
|
|
444
|
+
Authorization: incomingAuthorization ?? `Bearer ${options.apiKey}`,
|
|
445
|
+
...sharedHeaders,
|
|
446
|
+
...incomingUserAgent ? { "User-Agent": incomingUserAgent } : {},
|
|
447
|
+
...betaHeader ? { "anthropic-beta": betaHeader } : {},
|
|
448
|
+
...incomingApiKey ? { "X-Api-Key": incomingApiKey } : {}
|
|
449
|
+
};
|
|
450
|
+
else return {
|
|
451
|
+
...modelHeaders,
|
|
452
|
+
Accept: acceptHeader,
|
|
453
|
+
...sharedHeaders,
|
|
454
|
+
...incomingUserAgent ? { "User-Agent": incomingUserAgent } : {},
|
|
455
|
+
...betaHeader ? { "anthropic-beta": betaHeader } : {},
|
|
456
|
+
...incomingAuthorization ? { Authorization: incomingAuthorization } : {},
|
|
457
|
+
"X-Api-Key": incomingApiKey ?? options.apiKey
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function normalizeAnthropicImageMediaType(mimeType) {
|
|
461
|
+
const normalized = mimeType.trim().toLowerCase();
|
|
462
|
+
if (normalized === "image/jpg") return "image/jpeg";
|
|
463
|
+
if (normalized === "image/jpeg" || normalized === "image/png" || normalized === "image/gif" || normalized === "image/webp") return normalized;
|
|
464
|
+
}
|
|
465
|
+
function cloneAnthropicCacheControl(cacheControl) {
|
|
466
|
+
return { ...cacheControl };
|
|
467
|
+
}
|
|
468
|
+
const ANTHROPIC_STOP_SEQUENCES_MAX = 4;
|
|
469
|
+
let warnedStopSequencesTrim = false;
|
|
470
|
+
const ANTHROPIC_PROVIDER_SESSION_STATE_KEY = "anthropic-messages";
|
|
471
|
+
function createAnthropicProviderSessionState() {
|
|
472
|
+
const state = {
|
|
473
|
+
strictToolsDisabled: false,
|
|
474
|
+
fastModeDisabled: false,
|
|
475
|
+
replayUnsignedThinkingDisabled: false,
|
|
476
|
+
close: () => {
|
|
477
|
+
state.strictToolsDisabled = false;
|
|
478
|
+
state.fastModeDisabled = false;
|
|
479
|
+
state.replayUnsignedThinkingDisabled = false;
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
return state;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Key the sticky strict-tools / fast-mode learning per endpoint+model. A
|
|
486
|
+
* grammar-too-large 400 or a fast-mode rejection is specific to the model (its
|
|
487
|
+
* tool grammar / entitlement) and the endpoint (direct Anthropic vs a gateway /
|
|
488
|
+
* Foundry / Bedrock proxy), so it MUST NOT bleed onto unrelated anthropic-messages
|
|
489
|
+
* requests in the same session. NUL separates the two components so neither can
|
|
490
|
+
* forge the boundary.
|
|
491
|
+
*/
|
|
492
|
+
function anthropicProviderSessionStateKey(baseUrl, modelId) {
|
|
493
|
+
return `${ANTHROPIC_PROVIDER_SESSION_STATE_KEY}:${baseUrl}\u0000${modelId}`;
|
|
494
|
+
}
|
|
495
|
+
function getAnthropicProviderSessionState(providerSessionState, baseUrl, modelId) {
|
|
496
|
+
if (!providerSessionState) return void 0;
|
|
497
|
+
const key = anthropicProviderSessionStateKey(baseUrl, modelId);
|
|
498
|
+
const existing = providerSessionState.get(key);
|
|
499
|
+
if (existing) return existing;
|
|
500
|
+
const created = createAnthropicProviderSessionState();
|
|
501
|
+
providerSessionState.set(key, created);
|
|
502
|
+
return created;
|
|
503
|
+
}
|
|
504
|
+
function hasStrictAnthropicTools(params) {
|
|
505
|
+
return params.tools?.some((tool) => tool.strict === true) ?? false;
|
|
506
|
+
}
|
|
507
|
+
function dropAnthropicFastMode(params) {
|
|
508
|
+
delete params.speed;
|
|
509
|
+
}
|
|
510
|
+
function dropAnthropicStrictTools(params) {
|
|
511
|
+
if (!params.tools) return;
|
|
512
|
+
for (const tool of params.tools) delete tool.strict;
|
|
513
|
+
}
|
|
514
|
+
function getCacheControl(model, cacheRetention) {
|
|
515
|
+
const retention = resolveCacheRetention(cacheRetention, "short");
|
|
516
|
+
if (retention === "none") return { retention };
|
|
517
|
+
const ttl = retention === "long" && model.compat.supportsLongCacheRetention ? "1h" : void 0;
|
|
518
|
+
return {
|
|
519
|
+
retention,
|
|
520
|
+
cacheControl: {
|
|
521
|
+
type: "ephemeral",
|
|
522
|
+
...ttl && { ttl }
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function mapStainlessArch(arch) {
|
|
527
|
+
switch (arch.toLowerCase()) {
|
|
528
|
+
case "amd64":
|
|
529
|
+
case "x64": return "x64";
|
|
530
|
+
case "arm64":
|
|
531
|
+
case "aarch64": return "arm64";
|
|
532
|
+
case "386":
|
|
533
|
+
case "x86":
|
|
534
|
+
case "ia32": return "x86";
|
|
535
|
+
default: return `other::${arch.toLowerCase()}`;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/** Static headers emitted by Cowork's Linux Claude runtime. */
|
|
539
|
+
const coworkHeaders = {
|
|
540
|
+
"X-Stainless-Arch": mapStainlessArch(process.arch),
|
|
541
|
+
"X-Stainless-Lang": "js",
|
|
542
|
+
"X-Stainless-OS": "Linux",
|
|
543
|
+
"X-Stainless-Package-Version": "0.94.0",
|
|
544
|
+
"X-Stainless-Retry-Count": "0",
|
|
545
|
+
"X-Stainless-Runtime": "node",
|
|
546
|
+
"X-Stainless-Runtime-Version": "v26.3.0",
|
|
547
|
+
"X-Stainless-Timeout": "600"
|
|
548
|
+
};
|
|
549
|
+
const enforcedHeaderKeys = new Set([
|
|
550
|
+
...Object.keys(coworkHeaders),
|
|
551
|
+
"Accept",
|
|
552
|
+
"Accept-Encoding",
|
|
553
|
+
"Connection",
|
|
554
|
+
"Content-Type",
|
|
555
|
+
"anthropic-version",
|
|
556
|
+
"anthropic-dangerous-direct-browser-access",
|
|
557
|
+
"anthropic-beta",
|
|
558
|
+
"User-Agent",
|
|
559
|
+
"x-app",
|
|
560
|
+
"Authorization",
|
|
561
|
+
"X-Api-Key",
|
|
562
|
+
"X-Claude-Code-Session-Id",
|
|
563
|
+
"x-client-request-id",
|
|
564
|
+
"cf-aig-authorization"
|
|
565
|
+
].map((key) => key.toLowerCase()));
|
|
566
|
+
const overridableAnthropicHeaderKeys = new Set([
|
|
567
|
+
...Object.keys(coworkHeaders),
|
|
568
|
+
"anthropic-beta",
|
|
569
|
+
"User-Agent",
|
|
570
|
+
"x-app"
|
|
571
|
+
].map((key) => key.toLowerCase()));
|
|
572
|
+
const CLAUDE_BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
|
|
573
|
+
function createClaudeBillingHeader(firstUserMessageText) {
|
|
574
|
+
const k = [
|
|
575
|
+
4,
|
|
576
|
+
7,
|
|
577
|
+
20
|
|
578
|
+
].map((i) => firstUserMessageText[i] ?? "0").join("");
|
|
579
|
+
const versionSuffix = nodeCrypto.createHash("sha256").update(`59cf53e54c78${k}${claudeCodeVersion}`).digest("hex").slice(0, 3);
|
|
580
|
+
return `${CLAUDE_BILLING_HEADER_PREFIX} cc_version=${claudeCodeVersion}.${versionSuffix}; cc_entrypoint=claude-desktop; ${CCH_PLACEHOLDER_STR};`;
|
|
581
|
+
}
|
|
582
|
+
const CCH_SEED = 5577024349156291176n;
|
|
583
|
+
const CCH_PLACEHOLDER_STR = "cch=00000";
|
|
584
|
+
const cchEncoder = new TextEncoder();
|
|
585
|
+
const CCH_PLACEHOLDER = cchEncoder.encode(CCH_PLACEHOLDER_STR);
|
|
586
|
+
const BILLING_SYSTEM_MARKER = cchEncoder.encode(`"system":[{"type":"text","text":"${CLAUDE_BILLING_HEADER_PREFIX}`);
|
|
587
|
+
const CCH_BILLING_SEARCH_WINDOW = 150;
|
|
588
|
+
function patchCch(body) {
|
|
589
|
+
const view = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
590
|
+
const markerIdx = view.indexOf(BILLING_SYSTEM_MARKER);
|
|
591
|
+
if (markerIdx === -1) return "no-billing-header";
|
|
592
|
+
const searchFrom = markerIdx + BILLING_SYSTEM_MARKER.length;
|
|
593
|
+
const idx = view.indexOf(CCH_PLACEHOLDER, searchFrom);
|
|
594
|
+
if (idx === -1 || idx - searchFrom > CCH_BILLING_SEARCH_WINDOW) return "unanchored";
|
|
595
|
+
const cch = (Bun.hash.xxHash64(body, CCH_SEED) & 1048575n).toString(16).padStart(5, "0");
|
|
596
|
+
for (let i = 0; i < 5; i++) body[idx + 4 + i] = cch.charCodeAt(i);
|
|
597
|
+
return "patched";
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Wraps a fetch implementation to patch the Claude Code billing-header `cch`
|
|
601
|
+
* attestation into outgoing request bodies. Bodies without the placeholder
|
|
602
|
+
* pass through untouched, so installing it on every OAuth flow is safe.
|
|
603
|
+
*/
|
|
604
|
+
function wrapFetchForCch(base) {
|
|
605
|
+
return (input, init) => {
|
|
606
|
+
if (init?.body && typeof init.body === "string" && init.body.includes(CCH_PLACEHOLDER_STR)) {
|
|
607
|
+
const encoded = cchEncoder.encode(init.body);
|
|
608
|
+
if (patchCch(encoded) === "unanchored") warn("anthropic: cch billing placeholder present but not patched; sending unattested request");
|
|
609
|
+
return base(input, {
|
|
610
|
+
...init,
|
|
611
|
+
body: encoded
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
return base(input, init);
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const CLAUDE_CLOAKING_USER_ID_REGEX = /^user_[0-9a-fA-F]{64}_account_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}_session_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
618
|
+
function isClaudeCloakingUserId(userId) {
|
|
619
|
+
return CLAUDE_CLOAKING_USER_ID_REGEX.test(userId);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Real Claude Code sends `metadata.user_id` as a JSON-stringified object of the
|
|
623
|
+
* shape `{ device_id, account_uuid, session_id, ...extra }` (see
|
|
624
|
+
* services/api/claude.ts → getAPIMetadata). Accept that shape so callers that
|
|
625
|
+
* supply a stable `session_id` aren't silently overwritten with fresh entropy
|
|
626
|
+
* on every request, which would inflate the backend session count.
|
|
627
|
+
*/
|
|
628
|
+
function isClaudeJsonUserId(userId) {
|
|
629
|
+
if (userId.length === 0 || userId[0] !== "{") return false;
|
|
630
|
+
let parsed;
|
|
631
|
+
try {
|
|
632
|
+
parsed = JSON.parse(userId);
|
|
633
|
+
} catch {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
637
|
+
const obj = parsed;
|
|
638
|
+
return typeof obj.session_id === "string" && obj.session_id.length > 0;
|
|
639
|
+
}
|
|
640
|
+
function extractClaudeMetadataSessionId(userId) {
|
|
641
|
+
if (typeof userId !== "string") return void 0;
|
|
642
|
+
if (isClaudeCloakingUserId(userId)) return userId.slice(userId.lastIndexOf("_session_") + 9);
|
|
643
|
+
if (userId.length === 0 || userId[0] !== "{") return void 0;
|
|
644
|
+
let parsed;
|
|
645
|
+
try {
|
|
646
|
+
parsed = JSON.parse(userId);
|
|
647
|
+
} catch {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
651
|
+
const sessionId = parsed.session_id;
|
|
652
|
+
return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
|
|
653
|
+
}
|
|
654
|
+
const CLAUDE_DEVICE_ID_INSTALL_HASH_DOMAIN = "omp-claude-device-id-v1:";
|
|
655
|
+
const CLAUDE_DEVICE_ID_ACCOUNT_HASH_DOMAIN = "omp-claude-device-id-v2";
|
|
656
|
+
function deriveClaudeDeviceId(installId, accountId) {
|
|
657
|
+
const hash = nodeCrypto.createHash("sha256");
|
|
658
|
+
if (accountId && accountId.length > 0) return hash.update(CLAUDE_DEVICE_ID_ACCOUNT_HASH_DOMAIN).update("\0").update(installId).update("\0").update(accountId).digest("hex");
|
|
659
|
+
return hash.update(CLAUDE_DEVICE_ID_INSTALL_HASH_DOMAIN).update(installId).digest("hex");
|
|
660
|
+
}
|
|
661
|
+
function readMetadataString(metadata, key) {
|
|
662
|
+
const value = metadata?.[key];
|
|
663
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
664
|
+
}
|
|
665
|
+
function readAnthropicMetadataAccountId(metadata) {
|
|
666
|
+
return readMetadataString(metadata, "account_uuid") ?? readMetadataString(metadata, "accountId") ?? readMetadataString(metadata, "account_id");
|
|
667
|
+
}
|
|
668
|
+
function deriveClaudeDeviceIdFromInstallId(accountId) {
|
|
669
|
+
return deriveClaudeDeviceId(getInstallId(), accountId);
|
|
670
|
+
}
|
|
671
|
+
function generateClaudeJsonUserId(sessionId, accountId) {
|
|
672
|
+
const userId = {
|
|
673
|
+
device_id: deriveClaudeDeviceIdFromInstallId(accountId),
|
|
674
|
+
session_id: sessionId ?? nodeCrypto.randomUUID().toLowerCase()
|
|
675
|
+
};
|
|
676
|
+
if (accountId && accountId.length > 0) userId.account_uuid = accountId;
|
|
677
|
+
return JSON.stringify(userId);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Resolve the `metadata.user_id` field for an Anthropic Messages request.
|
|
681
|
+
*
|
|
682
|
+
* For API-key tokens, an explicit caller-supplied `userId` is forwarded
|
|
683
|
+
* verbatim and `undefined` yields no metadata. For OAuth tokens the value
|
|
684
|
+
* must match the Claude Code attribution shape (`isClaudeCloakingUserId` or
|
|
685
|
+
* the `{session_id, account_uuid?, device_id?}` JSON envelope) — anything
|
|
686
|
+
* else is dropped and a fresh Claude-Code-style JSON id is generated from
|
|
687
|
+
* `sessionId`/`accountId` so attribution stays consistent across the main
|
|
688
|
+
* streaming path and provider-specific request builders (e.g. web search).
|
|
689
|
+
*/
|
|
690
|
+
function resolveAnthropicMetadataUserId(userId, isOAuthToken, sessionId, accountId) {
|
|
691
|
+
if (typeof userId === "string") {
|
|
692
|
+
if (!isOAuthToken || isClaudeCloakingUserId(userId) || isClaudeJsonUserId(userId)) return userId;
|
|
693
|
+
}
|
|
694
|
+
if (!isOAuthToken) return void 0;
|
|
695
|
+
return generateClaudeJsonUserId(sessionId, accountId);
|
|
696
|
+
}
|
|
697
|
+
const ANTHROPIC_BUILTIN_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
698
|
+
"web_search",
|
|
699
|
+
"code_execution",
|
|
700
|
+
"text_editor",
|
|
701
|
+
"computer"
|
|
702
|
+
]);
|
|
703
|
+
const UMANS_WEBSEARCH_PROVIDER_HEADER = "X-Umans-Websearch-Provider";
|
|
704
|
+
const UMANS_WEBSEARCH_TOOL_NAME = "web_search";
|
|
705
|
+
const applyClaudeToolPrefix = (name) => {
|
|
706
|
+
if (ANTHROPIC_BUILTIN_TOOL_NAMES.has(name.toLowerCase())) return name;
|
|
707
|
+
return `_${name}`;
|
|
708
|
+
};
|
|
709
|
+
const stripClaudeToolPrefix = (name) => {
|
|
710
|
+
if (!name.toLowerCase().startsWith("_".toLowerCase())) return name;
|
|
711
|
+
return name.slice(1);
|
|
712
|
+
};
|
|
713
|
+
function normalizeUmansWebSearchProvider(value) {
|
|
714
|
+
const normalized = value?.trim().toLowerCase();
|
|
715
|
+
return normalized === "native" || normalized === "exa" ? normalized : void 0;
|
|
716
|
+
}
|
|
717
|
+
function getUmansWebSearchProvider(headers) {
|
|
718
|
+
const explicit = getHeaderCaseInsensitive(headers, UMANS_WEBSEARCH_PROVIDER_HEADER);
|
|
719
|
+
if (explicit !== void 0) return normalizeUmansWebSearchProvider(explicit);
|
|
720
|
+
return normalizeUmansWebSearchProvider($env.UMANS_WEBSEARCH_PROVIDER);
|
|
721
|
+
}
|
|
722
|
+
function isUmansAnthropicModel(model) {
|
|
723
|
+
return model.provider === "umans" || model.baseUrl.toLowerCase().includes("api.code.umans.ai");
|
|
724
|
+
}
|
|
725
|
+
function getUmansWebSearchHeader(model, headers) {
|
|
726
|
+
if (!isUmansAnthropicModel(model)) return void 0;
|
|
727
|
+
const provider = getUmansWebSearchProvider(headers);
|
|
728
|
+
return provider ? { [UMANS_WEBSEARCH_PROVIDER_HEADER]: provider } : void 0;
|
|
729
|
+
}
|
|
730
|
+
function shouldUseUmansGatewayWebSearch(name, enabled) {
|
|
731
|
+
return enabled && name.toLowerCase() === UMANS_WEBSEARCH_TOOL_NAME;
|
|
732
|
+
}
|
|
733
|
+
function encodeAnthropicToolName(name, isOAuthToken, escapeBuiltinToolNames, useUmansGatewayWebSearch = false) {
|
|
734
|
+
if (shouldUseUmansGatewayWebSearch(name, useUmansGatewayWebSearch)) return name;
|
|
735
|
+
if (escapeBuiltinToolNames) return `_${name}`;
|
|
736
|
+
return isOAuthToken ? applyClaudeToolPrefix(name) : name;
|
|
737
|
+
}
|
|
738
|
+
function decodeAnthropicToolName(name, isOAuthToken, escapeBuiltinToolNames) {
|
|
739
|
+
if (isOAuthToken || escapeBuiltinToolNames) return stripClaudeToolPrefix(name);
|
|
740
|
+
return name;
|
|
741
|
+
}
|
|
742
|
+
const ANTHROPIC_MANY_IMAGE_THRESHOLD = 20;
|
|
743
|
+
const ANTHROPIC_MANY_IMAGE_MAX_DIMENSION = 2e3;
|
|
744
|
+
function countAnthropicImageBlocks(messages) {
|
|
745
|
+
let count = 0;
|
|
746
|
+
for (const message of messages) {
|
|
747
|
+
if (message.role !== "user" && message.role !== "developer" && message.role !== "toolResult") continue;
|
|
748
|
+
if (!Array.isArray(message.content)) continue;
|
|
749
|
+
for (const block of message.content) if (block.type === "image") count++;
|
|
750
|
+
}
|
|
751
|
+
return count;
|
|
752
|
+
}
|
|
753
|
+
const ANTHROPIC_IMAGE_RESIZE_CONCURRENCY = 4;
|
|
754
|
+
/**
|
|
755
|
+
* Memoized resize results keyed on ImageContent identity. Callers keep message
|
|
756
|
+
* objects stable across turns, so without this every request (and every
|
|
757
|
+
* in-provider retry of a fresh turn) re-decodes and re-encodes the same
|
|
758
|
+
* oversized screenshots. A cached value identical to the key means "already
|
|
759
|
+
* within bounds / unresizable — skip the decode".
|
|
760
|
+
*/
|
|
761
|
+
const anthropicManyImageResizeCache = /* @__PURE__ */ new WeakMap();
|
|
762
|
+
/**
|
|
763
|
+
* Bounded-concurrency gate for image decode/encode work. The many-image path
|
|
764
|
+
* fans out over every block of every message; unbounded, 100+ oversized images
|
|
765
|
+
* would decode concurrently (two encode pipelines each) and spike memory by
|
|
766
|
+
* gigabytes. Slots are handed off directly to the next waiter on release.
|
|
767
|
+
*/
|
|
768
|
+
function createResizeLimiter(limit) {
|
|
769
|
+
let active = 0;
|
|
770
|
+
const queue = [];
|
|
771
|
+
return async (fn) => {
|
|
772
|
+
if (active >= limit) {
|
|
773
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
774
|
+
queue.push(resolve);
|
|
775
|
+
await promise;
|
|
776
|
+
} else active++;
|
|
777
|
+
try {
|
|
778
|
+
return await fn();
|
|
779
|
+
} finally {
|
|
780
|
+
const next = queue.shift();
|
|
781
|
+
if (next) next();
|
|
782
|
+
else active--;
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
async function resizeAnthropicManyImageBlock(block) {
|
|
787
|
+
try {
|
|
788
|
+
const inputBuffer = Buffer.from(block.data, "base64");
|
|
789
|
+
const { width, height } = await new Bun.Image(inputBuffer).metadata();
|
|
790
|
+
if (!width || !height) return block;
|
|
791
|
+
if (width <= ANTHROPIC_MANY_IMAGE_MAX_DIMENSION && height <= ANTHROPIC_MANY_IMAGE_MAX_DIMENSION) return block;
|
|
792
|
+
const scale = Math.min(ANTHROPIC_MANY_IMAGE_MAX_DIMENSION / width, ANTHROPIC_MANY_IMAGE_MAX_DIMENSION / height);
|
|
793
|
+
const targetWidth = Math.max(1, Math.min(ANTHROPIC_MANY_IMAGE_MAX_DIMENSION, Math.round(width * scale)));
|
|
794
|
+
const targetHeight = Math.max(1, Math.min(ANTHROPIC_MANY_IMAGE_MAX_DIMENSION, Math.round(height * scale)));
|
|
795
|
+
const [png, jpeg] = await Promise.all([new Bun.Image(inputBuffer).resize(targetWidth, targetHeight).png().bytes(), new Bun.Image(inputBuffer).resize(targetWidth, targetHeight).jpeg({ quality: 85 }).bytes()]);
|
|
796
|
+
const best = png.length <= jpeg.length ? {
|
|
797
|
+
buffer: png,
|
|
798
|
+
mimeType: "image/png"
|
|
799
|
+
} : {
|
|
800
|
+
buffer: jpeg,
|
|
801
|
+
mimeType: "image/jpeg"
|
|
802
|
+
};
|
|
803
|
+
return {
|
|
804
|
+
type: "image",
|
|
805
|
+
data: Buffer.from(best.buffer).toString("base64"),
|
|
806
|
+
mimeType: best.mimeType
|
|
807
|
+
};
|
|
808
|
+
} catch (error) {
|
|
809
|
+
warn("anthropic: failed to resize oversized image for many-image request", {
|
|
810
|
+
mimeType: block.mimeType,
|
|
811
|
+
error: error instanceof Error ? error.message : String(error)
|
|
812
|
+
});
|
|
813
|
+
return block;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async function resizeAnthropicManyImageContent(content, state, limit) {
|
|
817
|
+
let changed = false;
|
|
818
|
+
const next = await Promise.all(content.map(async (block) => {
|
|
819
|
+
if (block.type !== "image") return block;
|
|
820
|
+
let resized = anthropicManyImageResizeCache.get(block);
|
|
821
|
+
if (resized === void 0) {
|
|
822
|
+
resized = await limit(() => resizeAnthropicManyImageBlock(block));
|
|
823
|
+
anthropicManyImageResizeCache.set(block, resized);
|
|
824
|
+
}
|
|
825
|
+
if (resized !== block) {
|
|
826
|
+
changed = true;
|
|
827
|
+
state.resized++;
|
|
828
|
+
}
|
|
829
|
+
return resized;
|
|
830
|
+
}));
|
|
831
|
+
return changed ? next : content;
|
|
832
|
+
}
|
|
833
|
+
async function resizeAnthropicManyImageMessage(message, state, limit) {
|
|
834
|
+
if (message.role === "user" || message.role === "developer") {
|
|
835
|
+
if (!Array.isArray(message.content)) return message;
|
|
836
|
+
const content = await resizeAnthropicManyImageContent(message.content, state, limit);
|
|
837
|
+
return content === message.content ? message : {
|
|
838
|
+
...message,
|
|
839
|
+
content
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
if (message.role === "toolResult") {
|
|
843
|
+
const content = await resizeAnthropicManyImageContent(message.content, state, limit);
|
|
844
|
+
return content === message.content ? message : {
|
|
845
|
+
...message,
|
|
846
|
+
content
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
return message;
|
|
850
|
+
}
|
|
851
|
+
async function prepareAnthropicManyImageContext(context, supportsImages) {
|
|
852
|
+
if (!supportsImages) return context;
|
|
853
|
+
const imageCount = countAnthropicImageBlocks(context.messages);
|
|
854
|
+
if (imageCount <= ANTHROPIC_MANY_IMAGE_THRESHOLD) return context;
|
|
855
|
+
let changed = false;
|
|
856
|
+
const state = { resized: 0 };
|
|
857
|
+
const limit = createResizeLimiter(ANTHROPIC_IMAGE_RESIZE_CONCURRENCY);
|
|
858
|
+
const messages = await Promise.all(context.messages.map(async (message) => {
|
|
859
|
+
const next = await resizeAnthropicManyImageMessage(message, state, limit);
|
|
860
|
+
if (next !== message) changed = true;
|
|
861
|
+
return next;
|
|
862
|
+
}));
|
|
863
|
+
if (!changed) return context;
|
|
864
|
+
debug("anthropic: resized oversized images for many-image request", {
|
|
865
|
+
imageCount,
|
|
866
|
+
resized: state.resized,
|
|
867
|
+
maxDimension: ANTHROPIC_MANY_IMAGE_MAX_DIMENSION
|
|
868
|
+
});
|
|
869
|
+
return {
|
|
870
|
+
...context,
|
|
871
|
+
messages
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Convert content blocks to Anthropic API format
|
|
876
|
+
*/
|
|
877
|
+
function convertContentBlocks(content, supportsImages = true) {
|
|
878
|
+
const blocks = [];
|
|
879
|
+
let sawText = false;
|
|
880
|
+
let sawImage = false;
|
|
881
|
+
for (const block of content) {
|
|
882
|
+
if (block.type === "text") {
|
|
883
|
+
const text = block.text.toWellFormed();
|
|
884
|
+
if (text.trim().length === 0) continue;
|
|
885
|
+
sawText = true;
|
|
886
|
+
blocks.push({
|
|
887
|
+
type: "text",
|
|
888
|
+
text
|
|
889
|
+
});
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (!supportsImages) {
|
|
893
|
+
blocks.push({
|
|
894
|
+
type: "text",
|
|
895
|
+
text: NON_VISION_IMAGE_PLACEHOLDER
|
|
896
|
+
});
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const mediaType = normalizeAnthropicImageMediaType(block.mimeType);
|
|
900
|
+
if (!mediaType) {
|
|
901
|
+
blocks.push({
|
|
902
|
+
type: "text",
|
|
903
|
+
text: `[unsupported image: ${block.mimeType}]`
|
|
904
|
+
});
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
sawImage = true;
|
|
908
|
+
blocks.push({
|
|
909
|
+
type: "image",
|
|
910
|
+
source: {
|
|
911
|
+
type: "base64",
|
|
912
|
+
media_type: mediaType,
|
|
913
|
+
data: block.data
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
if (!supportsImages) return blocks.filter((block) => block.type === "text").map((block) => block.text).join("\n").toWellFormed();
|
|
918
|
+
if (sawImage && !sawText) blocks.unshift({
|
|
919
|
+
type: "text",
|
|
920
|
+
text: "(see attached image)"
|
|
921
|
+
});
|
|
922
|
+
return blocks;
|
|
923
|
+
}
|
|
924
|
+
const COWORK_TLS_CIPHERS = tls.DEFAULT_CIPHERS;
|
|
925
|
+
const foundryTlsOptionsCache = /* @__PURE__ */ new Map();
|
|
926
|
+
function foundryTlsCacheKeyComponent(value) {
|
|
927
|
+
if (!value) return null;
|
|
928
|
+
const trimmed = value.trim();
|
|
929
|
+
if (trimmed && !trimmed.includes("-----BEGIN") && looksLikeFilePath(trimmed)) try {
|
|
930
|
+
return `${trimmed}@${fs.statSync(trimmed).mtimeMs}`;
|
|
931
|
+
} catch {
|
|
932
|
+
return trimmed;
|
|
933
|
+
}
|
|
934
|
+
return value;
|
|
935
|
+
}
|
|
936
|
+
function foundryTlsOptionsCacheKey() {
|
|
937
|
+
return JSON.stringify([
|
|
938
|
+
foundryTlsCacheKeyComponent($env.NODE_EXTRA_CA_CERTS),
|
|
939
|
+
foundryTlsCacheKeyComponent($env.CLAUDE_CODE_CLIENT_CERT),
|
|
940
|
+
foundryTlsCacheKeyComponent($env.CLAUDE_CODE_CLIENT_KEY)
|
|
941
|
+
]);
|
|
942
|
+
}
|
|
943
|
+
function resolveAnthropicBaseUrl(model, apiKey) {
|
|
944
|
+
if (model.provider === "github-copilot") return normalizeAnthropicBaseUrl(resolveGitHubCopilotBaseUrl(model.baseUrl, apiKey) ?? model.baseUrl);
|
|
945
|
+
if (model.provider === "anthropic" && isFoundryEnabled()) {
|
|
946
|
+
const foundryBaseUrl = normalizeAnthropicBaseUrl($env.FOUNDRY_BASE_URL);
|
|
947
|
+
if (foundryBaseUrl) return foundryBaseUrl;
|
|
948
|
+
}
|
|
949
|
+
if (model.provider === "anthropic") {
|
|
950
|
+
const configured = normalizeAnthropicBaseUrl(model.baseUrl);
|
|
951
|
+
if (configured && !isOfficialAnthropicApiUrl(configured)) return configured;
|
|
952
|
+
return normalizeAnthropicBaseUrl($env.ANTHROPIC_BASE_URL) ?? configured ?? "https://api.anthropic.com";
|
|
953
|
+
}
|
|
954
|
+
return normalizeAnthropicBaseUrl(model.baseUrl);
|
|
955
|
+
}
|
|
956
|
+
function resolveEagerToolInputStreamingSupport(model, effectiveBaseUrl) {
|
|
957
|
+
if (!model.compat.supportsEagerToolInputStreaming) return false;
|
|
958
|
+
if (isOfficialAnthropicApiUrl(effectiveBaseUrl)) return true;
|
|
959
|
+
return !model.compat.officialEndpoint;
|
|
960
|
+
}
|
|
961
|
+
function parseAnthropicCustomHeaders(rawHeaders) {
|
|
962
|
+
const source = rawHeaders?.trim();
|
|
963
|
+
if (!source) return void 0;
|
|
964
|
+
const parsed = {};
|
|
965
|
+
for (const token of source.split(/\r?\n|,/)) {
|
|
966
|
+
const entry = token.trim();
|
|
967
|
+
if (!entry) continue;
|
|
968
|
+
const separatorIndex = entry.indexOf(":");
|
|
969
|
+
if (separatorIndex <= 0) continue;
|
|
970
|
+
const key = entry.slice(0, separatorIndex).trim();
|
|
971
|
+
const value = entry.slice(separatorIndex + 1).trim();
|
|
972
|
+
if (!key || !value) continue;
|
|
973
|
+
parsed[key] = value;
|
|
974
|
+
}
|
|
975
|
+
return Object.keys(parsed).length > 0 ? parsed : void 0;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Returns env-supplied custom headers (`ANTHROPIC_CUSTOM_HEADERS`) when they
|
|
979
|
+
* should be forwarded to the upstream endpoint.
|
|
980
|
+
*
|
|
981
|
+
* Foundry mode forwards them unconditionally. Outside Foundry, they're applied
|
|
982
|
+
* only when the configured base URL is a non-Anthropic host — i.e. an
|
|
983
|
+
* enterprise/corporate gateway that may require its own proprietary auth
|
|
984
|
+
* header. Stock `api.anthropic.com` would reject unknown headers, so they're
|
|
985
|
+
* omitted there.
|
|
986
|
+
*/
|
|
987
|
+
function resolveAnthropicCustomHeadersForBaseUrl(baseUrl) {
|
|
988
|
+
if (!isFoundryEnabled() && isOfficialAnthropicApiUrl(baseUrl)) return void 0;
|
|
989
|
+
return parseAnthropicCustomHeaders($env.ANTHROPIC_CUSTOM_HEADERS);
|
|
990
|
+
}
|
|
991
|
+
function resolveAnthropicCustomHeaders(model, baseUrl) {
|
|
992
|
+
if (model.provider !== "anthropic") return void 0;
|
|
993
|
+
return resolveAnthropicCustomHeadersForBaseUrl(baseUrl);
|
|
994
|
+
}
|
|
995
|
+
function looksLikeFilePath(value) {
|
|
996
|
+
return value.includes("/") || value.includes("\\") || /\.(pem|crt|cer|key)$/i.test(value);
|
|
997
|
+
}
|
|
998
|
+
function resolvePemValue(value, name) {
|
|
999
|
+
const trimmed = value?.trim();
|
|
1000
|
+
if (!trimmed) return void 0;
|
|
1001
|
+
const inline = trimmed.replace(/\\n/g, "\n");
|
|
1002
|
+
if (inline.includes("-----BEGIN")) return inline;
|
|
1003
|
+
if (looksLikeFilePath(trimmed)) try {
|
|
1004
|
+
return fs.readFileSync(trimmed, "utf8");
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
if (isEnoent(error)) throw new ValidationError(`${name} path does not exist: ${trimmed}`);
|
|
1007
|
+
throw error;
|
|
1008
|
+
}
|
|
1009
|
+
return inline;
|
|
1010
|
+
}
|
|
1011
|
+
function resolveFoundryTlsOptions(model) {
|
|
1012
|
+
if (model.provider !== "anthropic") return void 0;
|
|
1013
|
+
if (!isFoundryEnabled()) return void 0;
|
|
1014
|
+
const cacheKey = foundryTlsOptionsCacheKey();
|
|
1015
|
+
if (foundryTlsOptionsCache.has(cacheKey)) return foundryTlsOptionsCache.get(cacheKey);
|
|
1016
|
+
const ca = resolvePemValue($env.NODE_EXTRA_CA_CERTS, "NODE_EXTRA_CA_CERTS");
|
|
1017
|
+
const cert = resolvePemValue($env.CLAUDE_CODE_CLIENT_CERT, "CLAUDE_CODE_CLIENT_CERT");
|
|
1018
|
+
const key = resolvePemValue($env.CLAUDE_CODE_CLIENT_KEY, "CLAUDE_CODE_CLIENT_KEY");
|
|
1019
|
+
if (cert && !key || !cert && key) throw new ConfigurationError("Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must be set for mTLS.");
|
|
1020
|
+
const options = {};
|
|
1021
|
+
if (ca) options.ca = [...tls.rootCertificates, ca];
|
|
1022
|
+
if (cert) options.cert = cert;
|
|
1023
|
+
if (key) options.key = key;
|
|
1024
|
+
const resolved = Object.keys(options).length > 0 ? options : void 0;
|
|
1025
|
+
foundryTlsOptionsCache.set(cacheKey, resolved);
|
|
1026
|
+
return resolved;
|
|
1027
|
+
}
|
|
1028
|
+
function buildCoworkTlsFetchOptions(model, baseUrl) {
|
|
1029
|
+
if (model.provider !== "anthropic") return void 0;
|
|
1030
|
+
if (!baseUrl) return void 0;
|
|
1031
|
+
let serverName;
|
|
1032
|
+
try {
|
|
1033
|
+
serverName = new URL(baseUrl).hostname;
|
|
1034
|
+
} catch {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (!serverName) return void 0;
|
|
1038
|
+
const foundryTlsOptions = resolveFoundryTlsOptions(model);
|
|
1039
|
+
return { tls: {
|
|
1040
|
+
rejectUnauthorized: true,
|
|
1041
|
+
serverName,
|
|
1042
|
+
...COWORK_TLS_CIPHERS ? { ciphers: COWORK_TLS_CIPHERS } : {},
|
|
1043
|
+
...foundryTlsOptions ?? {}
|
|
1044
|
+
} };
|
|
1045
|
+
}
|
|
1046
|
+
function mergeHeaders(...headerSources) {
|
|
1047
|
+
const merged = {};
|
|
1048
|
+
const keyByLower = /* @__PURE__ */ new Map();
|
|
1049
|
+
for (const headers of headerSources) {
|
|
1050
|
+
if (!headers) continue;
|
|
1051
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1052
|
+
const lower = key.toLowerCase();
|
|
1053
|
+
const existing = keyByLower.get(lower);
|
|
1054
|
+
if (existing !== void 0 && existing !== key) delete merged[existing];
|
|
1055
|
+
keyByLower.set(lower, key);
|
|
1056
|
+
merged[key] = value;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return merged;
|
|
1060
|
+
}
|
|
1061
|
+
const ANTHROPIC_MESSAGE_EVENTS = /* @__PURE__ */ new Set([
|
|
1062
|
+
"message_start",
|
|
1063
|
+
"message_delta",
|
|
1064
|
+
"message_stop",
|
|
1065
|
+
"content_block_start",
|
|
1066
|
+
"content_block_delta",
|
|
1067
|
+
"content_block_stop"
|
|
1068
|
+
]);
|
|
1069
|
+
const ANTHROPIC_PING_EVENT = { type: "ping" };
|
|
1070
|
+
/**
|
|
1071
|
+
* In-stream `error` SSE frames carry an Anthropic error envelope:
|
|
1072
|
+
* `{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}`.
|
|
1073
|
+
* Surface the structured type + message instead of the raw JSON blob; the
|
|
1074
|
+
* error type token (e.g. `overloaded_error`, `rate_limit_error`) is kept in
|
|
1075
|
+
* the message so `isProviderRetryableError`'s classification keys off the
|
|
1076
|
+
* structured type rather than incidental JSON substrings.
|
|
1077
|
+
*/
|
|
1078
|
+
function createAnthropicSseStreamError(data) {
|
|
1079
|
+
try {
|
|
1080
|
+
const parsed = JSON.parse(data);
|
|
1081
|
+
const errorType = typeof parsed?.error?.type === "string" ? parsed.error.type : void 0;
|
|
1082
|
+
const message = typeof parsed?.error?.message === "string" ? parsed.error.message : void 0;
|
|
1083
|
+
if (message) return new ProviderResponseError(errorType ? `Anthropic stream error (${errorType}): ${message}` : `Anthropic stream error: ${message}`, {
|
|
1084
|
+
provider: "anthropic",
|
|
1085
|
+
kind: "output"
|
|
1086
|
+
});
|
|
1087
|
+
} catch {}
|
|
1088
|
+
return new ProviderResponseError(data, {
|
|
1089
|
+
provider: "anthropic",
|
|
1090
|
+
kind: "output"
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
async function* iterateAnthropicEvents(response, signal, onSseEvent) {
|
|
1094
|
+
if (!response.body) throw new AnthropicStreamEnvelopeError("Attempted to iterate over an Anthropic response with no body");
|
|
1095
|
+
let sawMessageStart = false;
|
|
1096
|
+
let sawMessageEnd = false;
|
|
1097
|
+
for await (const sse of readSseEvents(response.body, signal)) {
|
|
1098
|
+
notifyRawSseEvent(onSseEvent, sse);
|
|
1099
|
+
if (sse.event === "error") throw createAnthropicSseStreamError(sse.data);
|
|
1100
|
+
if (sse.event === "ping") {
|
|
1101
|
+
yield ANTHROPIC_PING_EVENT;
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
if (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? "")) continue;
|
|
1105
|
+
try {
|
|
1106
|
+
const event = JSON.parse(sse.data);
|
|
1107
|
+
if (event.type !== sse.event) reportAnthropicEnvelopeAnomaly(`event type ${event.type} does not match SSE event ${sse.event}`);
|
|
1108
|
+
if (event.type === "message_start") sawMessageStart = true;
|
|
1109
|
+
else if (event.type === "message_stop") sawMessageEnd = true;
|
|
1110
|
+
yield event;
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1113
|
+
reportAnthropicEnvelopeAnomaly(`could not parse SSE event ${sse.event}: ${message}; skipping frame; data=${sse.data}`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (sawMessageStart && !sawMessageEnd && !signal?.aborted) reportAnthropicEnvelopeAnomaly("stream ended before message_stop");
|
|
1117
|
+
}
|
|
1118
|
+
function hasAnthropicRawResponseRequest(request) {
|
|
1119
|
+
return isRecord(request) && typeof request.asResponse === "function";
|
|
1120
|
+
}
|
|
1121
|
+
function hasAnthropicStreamWithResponseRequest(request) {
|
|
1122
|
+
return isRecord(request) && typeof request.withResponse === "function";
|
|
1123
|
+
}
|
|
1124
|
+
async function getAnthropicStreamResponse(request, signal, onSseEvent) {
|
|
1125
|
+
if (hasAnthropicRawResponseRequest(request)) {
|
|
1126
|
+
const response = await request.asResponse();
|
|
1127
|
+
return {
|
|
1128
|
+
events: iterateAnthropicEvents(response, signal, onSseEvent),
|
|
1129
|
+
response,
|
|
1130
|
+
requestId: response.headers.get("request-id"),
|
|
1131
|
+
recordsRawSseEvents: true
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
if (hasAnthropicStreamWithResponseRequest(request)) {
|
|
1135
|
+
const { data, response, request_id } = await request.withResponse();
|
|
1136
|
+
return {
|
|
1137
|
+
events: data,
|
|
1138
|
+
response,
|
|
1139
|
+
requestId: request_id,
|
|
1140
|
+
recordsRawSseEvents: false
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
throw new AnthropicStreamEnvelopeError("Anthropic SDK request did not expose a stream response");
|
|
1144
|
+
}
|
|
1145
|
+
async function* observeDecodedAnthropicSdkEvents(events, observer) {
|
|
1146
|
+
for await (const event of events) {
|
|
1147
|
+
const data = JSON.stringify(event);
|
|
1148
|
+
notifyRawSseEvent(observer, {
|
|
1149
|
+
event: event.type,
|
|
1150
|
+
data,
|
|
1151
|
+
raw: [`event: ${event.type}`, `data: ${data}`]
|
|
1152
|
+
});
|
|
1153
|
+
yield event;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const PROVIDER_MAX_RETRIES = 10;
|
|
1157
|
+
/**
|
|
1158
|
+
* Flat delay between attempts when Copilot 400s a model its own `/models`
|
|
1159
|
+
* catalog advertises. Part of the fleet carries the model and part doesn't, so
|
|
1160
|
+
* the retry is a reroll rather than a wait for capacity to free up.
|
|
1161
|
+
*/
|
|
1162
|
+
const COPILOT_MODEL_FLAP_RETRY_DELAY_MS = 400;
|
|
1163
|
+
/**
|
|
1164
|
+
* How long `ping` keepalives may keep extending the idle deadline without any
|
|
1165
|
+
* semantic stream progress, as a multiple of the idle timeout. Anthropic pings
|
|
1166
|
+
* across legitimate generation gaps, so pings count as liveness — but a wedged
|
|
1167
|
+
* upstream that pings forever while producing no events must eventually trip
|
|
1168
|
+
* the idle watchdog instead of hanging an active tool-call stream without a
|
|
1169
|
+
* recovery path (#4900).
|
|
1170
|
+
*/
|
|
1171
|
+
const PING_PROGRESS_MAX_IDLE_MULTIPLIER = 3;
|
|
1172
|
+
/**
|
|
1173
|
+
* Log a malformed-stream-envelope anomaly without aborting the turn. The strict
|
|
1174
|
+
* parser would `throw new AnthropicStreamEnvelopeError(...)` here; we instead
|
|
1175
|
+
* surface a warning and let the caller skip the offending event (or finalize what
|
|
1176
|
+
* already streamed) so a non-conforming endpoint degrades to best-effort content
|
|
1177
|
+
* rather than failing the request.
|
|
1178
|
+
*/
|
|
1179
|
+
function reportAnthropicEnvelopeAnomaly(detail) {
|
|
1180
|
+
warn(`anthropic: ignoring malformed stream envelope: ${detail}`);
|
|
1181
|
+
}
|
|
1182
|
+
function shouldIgnoreAnthropicPreambleEvent(eventType) {
|
|
1183
|
+
if (typeof eventType !== "string") return false;
|
|
1184
|
+
if (eventType === "ping") return true;
|
|
1185
|
+
return !ANTHROPIC_MESSAGE_EVENTS.has(eventType);
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Whether an Anthropic (or Copilot-over-Anthropic) stream error should be
|
|
1189
|
+
* retried. The classification lives in {@link AIError.isProviderRetryableError};
|
|
1190
|
+
* this wrapper injects the Copilot-specific model-availability transient check,
|
|
1191
|
+
* which the error module must not import directly.
|
|
1192
|
+
*/
|
|
1193
|
+
function isProviderRetryableError(error, provider) {
|
|
1194
|
+
return isProviderRetryableError$1(error, {
|
|
1195
|
+
provider,
|
|
1196
|
+
isProviderTransient: provider === "github-copilot" ? (err) => isCopilotTransientModelError(err) : void 0
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
const THINKING_ENVELOPE_OPEN = "<thinking>";
|
|
1200
|
+
const THINKING_ENVELOPE_CLOSE = "</thinking>";
|
|
1201
|
+
function unwrapAnthropicThinkingEnvelope(text) {
|
|
1202
|
+
let current = text.trim();
|
|
1203
|
+
let stripped = false;
|
|
1204
|
+
while (current.startsWith(THINKING_ENVELOPE_OPEN) && current.endsWith(THINKING_ENVELOPE_CLOSE)) {
|
|
1205
|
+
current = current.slice(10, current.length - 11).trim();
|
|
1206
|
+
stripped = true;
|
|
1207
|
+
}
|
|
1208
|
+
return stripped ? current : void 0;
|
|
1209
|
+
}
|
|
1210
|
+
function createEmptyUsage(premiumRequests) {
|
|
1211
|
+
return {
|
|
1212
|
+
input: 0,
|
|
1213
|
+
output: 0,
|
|
1214
|
+
cacheRead: 0,
|
|
1215
|
+
cacheWrite: 0,
|
|
1216
|
+
totalTokens: 0,
|
|
1217
|
+
...premiumRequests === void 0 ? {} : { premiumRequests },
|
|
1218
|
+
cost: {
|
|
1219
|
+
input: 0,
|
|
1220
|
+
output: 0,
|
|
1221
|
+
cacheRead: 0,
|
|
1222
|
+
cacheWrite: 0,
|
|
1223
|
+
total: 0
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Capture Anthropic's optional cache-creation TTL breakdown and server-tool-use
|
|
1229
|
+
* counters into the harness Usage shape. Omitted/null fields are no-ops; explicit
|
|
1230
|
+
* zero-valued objects clear prior extras from earlier stream usage snapshots.
|
|
1231
|
+
*/
|
|
1232
|
+
function applyAnthropicUsageExtras(usage, source) {
|
|
1233
|
+
const cacheCreation = source.cache_creation;
|
|
1234
|
+
if (cacheCreation != null) {
|
|
1235
|
+
const fiveMinute = cacheCreation.ephemeral_5m_input_tokens ?? 0;
|
|
1236
|
+
const oneHour = cacheCreation.ephemeral_1h_input_tokens ?? 0;
|
|
1237
|
+
if (fiveMinute > 0 || oneHour > 0) usage.cttl = {
|
|
1238
|
+
...fiveMinute > 0 ? { ephemeral5m: fiveMinute } : {},
|
|
1239
|
+
...oneHour > 0 ? { ephemeral1h: oneHour } : {}
|
|
1240
|
+
};
|
|
1241
|
+
else delete usage.cttl;
|
|
1242
|
+
}
|
|
1243
|
+
const serverToolUse = source.server_tool_use;
|
|
1244
|
+
if (serverToolUse != null) {
|
|
1245
|
+
const webSearch = serverToolUse.web_search_requests ?? 0;
|
|
1246
|
+
const webFetch = serverToolUse.web_fetch_requests ?? 0;
|
|
1247
|
+
if (webSearch > 0 || webFetch > 0) usage.server = {
|
|
1248
|
+
...webSearch > 0 ? { webSearch } : {},
|
|
1249
|
+
...webFetch > 0 ? { webFetch } : {}
|
|
1250
|
+
};
|
|
1251
|
+
else delete usage.server;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
function parseAnthropicWireUsage(value) {
|
|
1255
|
+
if (!isRecord(value)) return void 0;
|
|
1256
|
+
const cacheCreation = isRecord(value.cache_creation) ? {
|
|
1257
|
+
...typeof value.cache_creation.ephemeral_5m_input_tokens === "number" ? { ephemeral_5m_input_tokens: value.cache_creation.ephemeral_5m_input_tokens } : {},
|
|
1258
|
+
...typeof value.cache_creation.ephemeral_1h_input_tokens === "number" ? { ephemeral_1h_input_tokens: value.cache_creation.ephemeral_1h_input_tokens } : {}
|
|
1259
|
+
} : void 0;
|
|
1260
|
+
return {
|
|
1261
|
+
...typeof value.input_tokens === "number" ? { input_tokens: value.input_tokens } : {},
|
|
1262
|
+
...typeof value.output_tokens === "number" ? { output_tokens: value.output_tokens } : {},
|
|
1263
|
+
...typeof value.cache_read_input_tokens === "number" ? { cache_read_input_tokens: value.cache_read_input_tokens } : {},
|
|
1264
|
+
...typeof value.cache_creation_input_tokens === "number" ? { cache_creation_input_tokens: value.cache_creation_input_tokens } : {},
|
|
1265
|
+
...cacheCreation === void 0 ? {} : { cache_creation: cacheCreation }
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function parseAnthropicFallbackWireBlock(value) {
|
|
1269
|
+
if (!isRecord(value) || value.type !== "fallback") return void 0;
|
|
1270
|
+
const from = isRecord(value.from) && typeof value.from.model === "string" ? value.from.model : void 0;
|
|
1271
|
+
const to = isRecord(value.to) && typeof value.to.model === "string" ? value.to.model : void 0;
|
|
1272
|
+
if (!from?.trim() || !to?.trim()) return void 0;
|
|
1273
|
+
return {
|
|
1274
|
+
type: "fallback",
|
|
1275
|
+
from: { model: from },
|
|
1276
|
+
to: { model: to }
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* The definitive "served by fallback" signal per Anthropic's fallback
|
|
1281
|
+
* billing cookbook (§4): a `fallback_message` iteration in `usage.iterations`.
|
|
1282
|
+
* Any other iteration type is per-attempt bookkeeping for the requested model
|
|
1283
|
+
* (including its dated snapshot alias) and MUST NOT retag the assistant turn.
|
|
1284
|
+
*/
|
|
1285
|
+
function fallbackServedModelFromUsage(source) {
|
|
1286
|
+
const iterations = source.iterations ?? [];
|
|
1287
|
+
for (let index = iterations.length - 1; index >= 0; index -= 1) {
|
|
1288
|
+
const iteration = iterations[index];
|
|
1289
|
+
if (iteration?.type === "fallback_message" && iteration.model?.trim()) return iteration.model;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Price a fallback turn per the fallback billing cookbook §4:
|
|
1294
|
+
* • A pre-served attempt with zero output/cache-creation is not billed
|
|
1295
|
+
* (waived classifier block); its iteration is skipped.
|
|
1296
|
+
* • Mid-stream refusals bill their attempting model's input+output at
|
|
1297
|
+
* that model's normal rates.
|
|
1298
|
+
* • The `fallback_message` attempt's input tokens are rebilled at the
|
|
1299
|
+
* served model's cache-read rate (fallback credit — 10% of base input).
|
|
1300
|
+
*
|
|
1301
|
+
* Top-level `usage.input/output/cacheRead/cacheWrite` stay Anthropic's raw
|
|
1302
|
+
* served-attempt counts; `usage.cost` reflects the per-iteration attributed
|
|
1303
|
+
* total. Non-fallback turns skip this path entirely and use the requested
|
|
1304
|
+
* model at the normal `calculateCost` call.
|
|
1305
|
+
*/
|
|
1306
|
+
/**
|
|
1307
|
+
* Resolve a served/iteration model id to its bundled catalog entry when
|
|
1308
|
+
* possible so the per-iteration cost uses the served model's pricing
|
|
1309
|
+
* (e.g. Opus 4.8 rates for a Fable→Opus fallback). Falls back to
|
|
1310
|
+
* `requestModel` when the id is empty, matches the request, or the
|
|
1311
|
+
* catalog has no entry under it — the caller keeps the requested-model
|
|
1312
|
+
* pricing as the safe default and logs at the source.
|
|
1313
|
+
*/
|
|
1314
|
+
function resolveIterationModel(requestModel, iterationModelId) {
|
|
1315
|
+
const id = iterationModelId?.trim();
|
|
1316
|
+
if (!id || id === requestModel.id) return requestModel;
|
|
1317
|
+
if (requestModel.provider === "anthropic") {
|
|
1318
|
+
const bundled = getBundledModel("anthropic", id);
|
|
1319
|
+
if (bundled?.api === "anthropic-messages") return bundled;
|
|
1320
|
+
}
|
|
1321
|
+
return requestModel;
|
|
1322
|
+
}
|
|
1323
|
+
function calculateFallbackTurnCost(requestModel, usage, source) {
|
|
1324
|
+
const iterations = source.iterations ?? [];
|
|
1325
|
+
if (iterations.length === 0) return false;
|
|
1326
|
+
const cost = {
|
|
1327
|
+
input: 0,
|
|
1328
|
+
output: 0,
|
|
1329
|
+
cacheRead: 0,
|
|
1330
|
+
cacheWrite: 0,
|
|
1331
|
+
total: 0
|
|
1332
|
+
};
|
|
1333
|
+
const hasFallbackMessage = iterations.some((iter) => iter.type === "fallback_message");
|
|
1334
|
+
let applied = false;
|
|
1335
|
+
for (const iteration of iterations) {
|
|
1336
|
+
const inputTokens = iteration.input_tokens ?? 0;
|
|
1337
|
+
const outputTokens = iteration.output_tokens ?? 0;
|
|
1338
|
+
const cacheReadTokens = iteration.cache_read_input_tokens ?? 0;
|
|
1339
|
+
const cacheWriteTokens = iteration.cache_creation_input_tokens ?? 0;
|
|
1340
|
+
const isFallback = iteration.type === "fallback_message";
|
|
1341
|
+
if (hasFallbackMessage && !isFallback && outputTokens === 0 && cacheWriteTokens === 0) continue;
|
|
1342
|
+
const iterationUsage = createEmptyUsage();
|
|
1343
|
+
if (isFallback) {
|
|
1344
|
+
iterationUsage.input = 0;
|
|
1345
|
+
iterationUsage.cacheRead = cacheReadTokens + inputTokens;
|
|
1346
|
+
} else {
|
|
1347
|
+
iterationUsage.input = inputTokens;
|
|
1348
|
+
iterationUsage.cacheRead = cacheReadTokens;
|
|
1349
|
+
}
|
|
1350
|
+
iterationUsage.output = outputTokens;
|
|
1351
|
+
iterationUsage.cacheWrite = cacheWriteTokens;
|
|
1352
|
+
iterationUsage.totalTokens = iterationUsage.input + iterationUsage.output + iterationUsage.cacheRead + iterationUsage.cacheWrite;
|
|
1353
|
+
calculateCost(resolveIterationModel(requestModel, iteration.model), iterationUsage);
|
|
1354
|
+
cost.input += iterationUsage.cost.input;
|
|
1355
|
+
cost.output += iterationUsage.cost.output;
|
|
1356
|
+
cost.cacheRead += iterationUsage.cost.cacheRead;
|
|
1357
|
+
cost.cacheWrite += iterationUsage.cost.cacheWrite;
|
|
1358
|
+
cost.total += iterationUsage.cost.total;
|
|
1359
|
+
applied = true;
|
|
1360
|
+
}
|
|
1361
|
+
if (!applied) return false;
|
|
1362
|
+
usage.cost = cost;
|
|
1363
|
+
return true;
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Detects the Anthropic `400 Invalid `signature` in `thinking` block` failure
|
|
1367
|
+
* a signing proxy returns when a stripped/unsigned prior thinking block is
|
|
1368
|
+
* replayed as `signature: ""`. Exported for the compat tests.
|
|
1369
|
+
*/
|
|
1370
|
+
const INVALID_THINKING_SIGNATURE_PATTERN = /invalid\s+`?signature`?\s+in\s+`?thinking`?(?:\s+block)?/i;
|
|
1371
|
+
function isInvalidThinkingSignatureError(message) {
|
|
1372
|
+
return INVALID_THINKING_SIGNATURE_PATTERN.test(message);
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Prepend a pointed remediation to Anthropic's `Invalid signature in thinking
|
|
1376
|
+
* block` 400 when the model looks like an unmarked custom signing proxy
|
|
1377
|
+
* (opaque baseUrl, `spec.reasoning: true`, no explicit
|
|
1378
|
+
* `compat.replayUnsignedThinking` override). The default is native replay for
|
|
1379
|
+
* the 3p reasoning majority (#2005); this hint turns the misconfigured-proxy
|
|
1380
|
+
* case into a one-line fix instead of a silent retry loop (#4297).
|
|
1381
|
+
*/
|
|
1382
|
+
function maybeAddReplayUnsignedThinkingHint(model, message) {
|
|
1383
|
+
if (!isInvalidThinkingSignatureError(message)) return message;
|
|
1384
|
+
if (model.compat.officialEndpoint) return message;
|
|
1385
|
+
if (model.compatConfig?.replayUnsignedThinking !== void 0) return message;
|
|
1386
|
+
return `${`Provider "${model.provider}" looks like an Anthropic-compatible signing proxy: it rejected a replayed unsigned thinking block. Set \`compat.replayUnsignedThinking: false\` under \`providers.${model.provider}\` in your models.yml and retry. See https://github.com/can1357/oh-my-pi/issues/4297.`}\n\n${message}`;
|
|
1387
|
+
}
|
|
1388
|
+
const streamAnthropicOnce = (model, context, options) => {
|
|
1389
|
+
const stream = new AssistantMessageEventStream();
|
|
1390
|
+
(async () => {
|
|
1391
|
+
const startTime = performance.now();
|
|
1392
|
+
let firstTokenTime;
|
|
1393
|
+
const output = {
|
|
1394
|
+
role: "assistant",
|
|
1395
|
+
content: [],
|
|
1396
|
+
api: model.api,
|
|
1397
|
+
provider: model.provider,
|
|
1398
|
+
model: model.id,
|
|
1399
|
+
usage: createEmptyUsage(),
|
|
1400
|
+
stopReason: "stop",
|
|
1401
|
+
timestamp: Date.now()
|
|
1402
|
+
};
|
|
1403
|
+
let rawRequestDump;
|
|
1404
|
+
let activeAbortTracker = createAbortSourceTracker(options?.signal);
|
|
1405
|
+
const onSseEvent = options?.onSseEvent;
|
|
1406
|
+
const rawSseObserver = onSseEvent ? (event) => onSseEvent(event, model) : void 0;
|
|
1407
|
+
try {
|
|
1408
|
+
const copilotDynamicHeaders = model.provider === "github-copilot" ? buildCopilotDynamicHeaders({
|
|
1409
|
+
messages: context.messages,
|
|
1410
|
+
hasImages: hasCopilotVisionInput(context.messages),
|
|
1411
|
+
premiumMultiplier: model.premiumMultiplier,
|
|
1412
|
+
headers: {
|
|
1413
|
+
...model.headers ?? {},
|
|
1414
|
+
...options?.headers ?? {}
|
|
1415
|
+
},
|
|
1416
|
+
initiatorOverride: options?.initiatorOverride
|
|
1417
|
+
}) : void 0;
|
|
1418
|
+
if (copilotDynamicHeaders?.premiumRequests !== void 0) output.usage.premiumRequests = copilotDynamicHeaders.premiumRequests;
|
|
1419
|
+
const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
|
|
1420
|
+
const baseUrl = resolveAnthropicBaseUrl(model, apiKey) ?? "https://api.anthropic.com";
|
|
1421
|
+
const supportsEagerToolInputStreaming = resolveEagerToolInputStreamingSupport(model, baseUrl);
|
|
1422
|
+
const providerSessionState = getAnthropicProviderSessionState(options?.providerSessionState, baseUrl, model.id);
|
|
1423
|
+
let disableStrictTools = (providerSessionState?.strictToolsDisabled ?? false) || (model.compat?.disableStrictTools ?? false);
|
|
1424
|
+
let dropFastMode = providerSessionState?.fastModeDisabled ?? false;
|
|
1425
|
+
let forceDemoteUnsignedThinking = providerSessionState?.replayUnsignedThinkingDisabled ?? false;
|
|
1426
|
+
const mergedCallerHeaders = mergeHeaders(model.headers, options?.headers);
|
|
1427
|
+
const umansGatewayWebSearchHeader = getUmansWebSearchHeader(model, mergedCallerHeaders);
|
|
1428
|
+
let fallbacks = options?.fallbacks;
|
|
1429
|
+
if (model.provider === "google-vertex" && fallbacks?.some((entry) => entry.output_config?.effort !== void 0)) fallbacks = fallbacks.map((entry) => {
|
|
1430
|
+
const outputConfig = entry.output_config;
|
|
1431
|
+
if (outputConfig?.effort === void 0) return entry;
|
|
1432
|
+
return {
|
|
1433
|
+
...entry,
|
|
1434
|
+
output_config: outputConfig.task_budget === void 0 ? void 0 : { task_budget: outputConfig.task_budget }
|
|
1435
|
+
};
|
|
1436
|
+
});
|
|
1437
|
+
const zeroOutputCacheRefresh = options?.anthropicCacheRefreshRequest === true;
|
|
1438
|
+
let client;
|
|
1439
|
+
let isOAuthToken;
|
|
1440
|
+
if (options?.client) {
|
|
1441
|
+
client = options.client;
|
|
1442
|
+
isOAuthToken = false;
|
|
1443
|
+
} else {
|
|
1444
|
+
const extraBetas = normalizeExtraBetas(options?.betas);
|
|
1445
|
+
if (model.provider === "anthropic" && options?.serviceTier === "priority" && !dropFastMode && !extraBetas.includes(fastModeBeta)) extraBetas.push(fastModeBeta);
|
|
1446
|
+
if (options?.taskBudget && !extraBetas.includes(taskBudgetBeta)) extraBetas.push(taskBudgetBeta);
|
|
1447
|
+
const sendsAdaptiveEffortPin = isAdaptiveOnlyThinking(model) && (options?.thinkingEnabled === false || model.compat.supportsForcedToolChoice && isForcedToolChoice(options?.toolChoice));
|
|
1448
|
+
if (model.reasoning && model.provider !== "google-vertex" && (options?.thinkingEnabled && options.effort !== "adaptive" || sendsAdaptiveEffortPin) && !extraBetas.includes(effortBeta)) extraBetas.push(effortBeta);
|
|
1449
|
+
if (model.compat.supportsMidConversationSystem && !extraBetas.includes(midConversationSystemBeta)) extraBetas.push(midConversationSystemBeta);
|
|
1450
|
+
if (model.reasoning && options?.thinkingEnabled && model.provider !== "github-copilot" && model.provider !== "google-vertex" && model.provider !== "opencode-zen" && !extraBetas.includes(contextManagementBeta)) extraBetas.push(contextManagementBeta);
|
|
1451
|
+
if (!(options?.isOAuth ?? isAnthropicOAuthToken(apiKey)) && getCacheControl(model, options?.cacheRetention).cacheControl?.ttl === "1h" && !extraBetas.includes(extendedCacheTtlBeta)) extraBetas.push(extendedCacheTtlBeta);
|
|
1452
|
+
if (fallbacks?.length) {
|
|
1453
|
+
if (!extraBetas.includes(serverSideFallbackBeta)) extraBetas.push(serverSideFallbackBeta);
|
|
1454
|
+
for (const entry of fallbacks) {
|
|
1455
|
+
if (entry.speed === "fast" && !extraBetas.includes(fastModeBeta)) extraBetas.push(fastModeBeta);
|
|
1456
|
+
if (entry.output_config?.effort && !extraBetas.includes(effortBeta)) extraBetas.push(effortBeta);
|
|
1457
|
+
if (entry.output_config?.task_budget && !extraBetas.includes(taskBudgetBeta)) extraBetas.push(taskBudgetBeta);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
const created = createClient(model, {
|
|
1461
|
+
model,
|
|
1462
|
+
apiKey,
|
|
1463
|
+
extraBetas,
|
|
1464
|
+
stream: !zeroOutputCacheRefresh,
|
|
1465
|
+
interleavedThinking: options?.interleavedThinking ?? true,
|
|
1466
|
+
headers: options?.headers,
|
|
1467
|
+
dynamicHeaders: copilotDynamicHeaders?.headers,
|
|
1468
|
+
isOAuth: options?.isOAuth,
|
|
1469
|
+
hasTools: !!context.tools?.length,
|
|
1470
|
+
thinkingEnabled: options?.thinkingEnabled,
|
|
1471
|
+
thinkingDisplay: options?.thinkingDisplay,
|
|
1472
|
+
fetch: options?.fetch,
|
|
1473
|
+
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
1474
|
+
claudeCodeSessionId: options?.sessionId ?? extractClaudeMetadataSessionId(options?.metadata?.user_id),
|
|
1475
|
+
disableStrictTools
|
|
1476
|
+
});
|
|
1477
|
+
client = created.client;
|
|
1478
|
+
isOAuthToken = created.isOAuthToken;
|
|
1479
|
+
}
|
|
1480
|
+
const preparedContext = await prepareAnthropicManyImageContext(context, model.input.includes("image"));
|
|
1481
|
+
const prepareParams = async () => {
|
|
1482
|
+
let nextParams = buildParams(model, preparedContext, isOAuthToken, options, {
|
|
1483
|
+
disableStrictTools,
|
|
1484
|
+
useUmansGatewayWebSearch: umansGatewayWebSearchHeader !== void 0,
|
|
1485
|
+
forceDemoteUnsignedThinking,
|
|
1486
|
+
supportsEagerToolInputStreaming,
|
|
1487
|
+
fallbacks
|
|
1488
|
+
});
|
|
1489
|
+
if (disableStrictTools) dropAnthropicStrictTools(nextParams);
|
|
1490
|
+
if (dropFastMode) dropAnthropicFastMode(nextParams);
|
|
1491
|
+
const replacementPayload = await options?.onPayload?.(nextParams, model);
|
|
1492
|
+
if (replacementPayload !== void 0) nextParams = replacementPayload;
|
|
1493
|
+
nextParams = toWellFormedDeep(nextParams);
|
|
1494
|
+
rawRequestDump = {
|
|
1495
|
+
provider: model.provider,
|
|
1496
|
+
api: output.api,
|
|
1497
|
+
model: model.id,
|
|
1498
|
+
method: "POST",
|
|
1499
|
+
url: `${baseUrl}/v1/messages${isOAuthToken ? "?beta=true" : ""}`,
|
|
1500
|
+
body: nextParams
|
|
1501
|
+
};
|
|
1502
|
+
return nextParams;
|
|
1503
|
+
};
|
|
1504
|
+
let params = await prepareParams();
|
|
1505
|
+
const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getStreamIdleTimeoutMs(model.compat.streamIdleTimeoutMs);
|
|
1506
|
+
const firstEventTimeoutMs = options?.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs(idleTimeoutMs);
|
|
1507
|
+
const requestTimeoutMs = firstEventTimeoutMs !== void 0 && firstEventTimeoutMs > 0 ? firstEventTimeoutMs : void 0;
|
|
1508
|
+
if (zeroOutputCacheRefresh) {
|
|
1509
|
+
const refreshParams = {
|
|
1510
|
+
...params,
|
|
1511
|
+
max_tokens: 0,
|
|
1512
|
+
stream: false
|
|
1513
|
+
};
|
|
1514
|
+
rawRequestDump = {
|
|
1515
|
+
provider: model.provider,
|
|
1516
|
+
api: output.api,
|
|
1517
|
+
model: model.id,
|
|
1518
|
+
method: "POST",
|
|
1519
|
+
url: `${baseUrl}/v1/messages${isOAuthToken ? "?beta=true" : ""}`,
|
|
1520
|
+
body: refreshParams
|
|
1521
|
+
};
|
|
1522
|
+
const { requestSignal } = activeAbortTracker;
|
|
1523
|
+
const requestOptions = {
|
|
1524
|
+
...createSdkStreamRequestOptions(requestSignal, requestTimeoutMs),
|
|
1525
|
+
maxRetries: 0
|
|
1526
|
+
};
|
|
1527
|
+
const request = isOAuthToken && client.beta ? client.beta.messages.create(refreshParams, requestOptions) : client.messages.create(refreshParams, requestOptions);
|
|
1528
|
+
if (!hasAnthropicRawResponseRequest(request)) throw new AnthropicStreamEnvelopeError("Anthropic cache refresh request did not expose a raw response");
|
|
1529
|
+
const response = await request.asResponse();
|
|
1530
|
+
await notifyProviderResponse(options, response, model, response.headers.get("request-id"));
|
|
1531
|
+
const body = await response.json();
|
|
1532
|
+
if (!isRecord(body)) throw new AnthropicStreamEnvelopeError("Anthropic cache refresh returned a malformed response");
|
|
1533
|
+
const wireUsage = parseAnthropicWireUsage(body.usage);
|
|
1534
|
+
if (!wireUsage) throw new AnthropicStreamEnvelopeError("Anthropic cache refresh response omitted usage");
|
|
1535
|
+
if (typeof body.id === "string") output.responseId = body.id;
|
|
1536
|
+
output.usage.input = wireUsage.input_tokens ?? 0;
|
|
1537
|
+
output.usage.output = wireUsage.output_tokens ?? 0;
|
|
1538
|
+
output.usage.cacheRead = wireUsage.cache_read_input_tokens ?? 0;
|
|
1539
|
+
output.usage.cacheWrite = wireUsage.cache_creation_input_tokens ?? 0;
|
|
1540
|
+
applyAnthropicUsageExtras(output.usage, wireUsage);
|
|
1541
|
+
output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
1542
|
+
calculateCost(model, output.usage);
|
|
1543
|
+
output.duration = performance.now() - startTime;
|
|
1544
|
+
stream.push({
|
|
1545
|
+
type: "start",
|
|
1546
|
+
partial: output
|
|
1547
|
+
});
|
|
1548
|
+
stream.push({
|
|
1549
|
+
type: "done",
|
|
1550
|
+
reason: "stop",
|
|
1551
|
+
message: output
|
|
1552
|
+
});
|
|
1553
|
+
stream.end();
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
const serverSideFallback = !!fallbacks?.length;
|
|
1557
|
+
const blocks = output.content;
|
|
1558
|
+
const finalizeStreamBlock = (block, contentIndex) => {
|
|
1559
|
+
if (block.type === "text") stream.push({
|
|
1560
|
+
type: "text_end",
|
|
1561
|
+
contentIndex,
|
|
1562
|
+
content: block.text,
|
|
1563
|
+
partial: output
|
|
1564
|
+
});
|
|
1565
|
+
else if (block.type === "thinking") {
|
|
1566
|
+
const unwrappedThinking = unwrapAnthropicThinkingEnvelope(block.thinking);
|
|
1567
|
+
if (unwrappedThinking !== void 0) {
|
|
1568
|
+
block.thinking = unwrappedThinking;
|
|
1569
|
+
block.thinkingSignature = void 0;
|
|
1570
|
+
}
|
|
1571
|
+
stream.push({
|
|
1572
|
+
type: "thinking_end",
|
|
1573
|
+
contentIndex,
|
|
1574
|
+
content: block.thinking,
|
|
1575
|
+
partial: output
|
|
1576
|
+
});
|
|
1577
|
+
} else if (block.type === "anthropicServerTool" && block.block.type === "server_tool_use") {
|
|
1578
|
+
const partialJson = block[kStreamingPartialJson];
|
|
1579
|
+
if (partialJson) try {
|
|
1580
|
+
const input = parseJsonWithRepair(partialJson);
|
|
1581
|
+
if (isRecord(input)) block.block.input = input;
|
|
1582
|
+
else reportAnthropicEnvelopeAnomaly("server_tool_use input is not a JSON object");
|
|
1583
|
+
} catch (parseError) {
|
|
1584
|
+
reportAnthropicEnvelopeAnomaly(`server_tool_use ${block.block.id} input is not valid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
1585
|
+
}
|
|
1586
|
+
clearStreamingPartialJson(block);
|
|
1587
|
+
} else if (block.type === "toolCall") {
|
|
1588
|
+
const finalJson = block[kStreamingPartialJson].length > 0 ? block[kStreamingPartialJson] : JSON.stringify(block.arguments ?? {});
|
|
1589
|
+
try {
|
|
1590
|
+
block.arguments = parseJsonWithRepair(finalJson);
|
|
1591
|
+
} catch (parseError) {
|
|
1592
|
+
reportAnthropicEnvelopeAnomaly(`tool_use ${block.id} arguments are not valid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
1593
|
+
if (Object.keys(block.arguments ?? {}).length === 0) {
|
|
1594
|
+
const maxLen = 512;
|
|
1595
|
+
const truncatedJson = finalJson.length <= maxLen ? finalJson : `${finalJson.slice(0, maxLen)}… [truncated ${finalJson.length - maxLen} chars]`;
|
|
1596
|
+
block.arguments = {
|
|
1597
|
+
__parseError: parseError instanceof Error ? parseError.message : String(parseError),
|
|
1598
|
+
__rawJson: truncatedJson
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
clearStreamingPartialJson(block);
|
|
1603
|
+
stream.push({
|
|
1604
|
+
type: "toolcall_end",
|
|
1605
|
+
contentIndex,
|
|
1606
|
+
toolCall: block,
|
|
1607
|
+
partial: output
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
stream.push({
|
|
1612
|
+
type: "start",
|
|
1613
|
+
partial: output
|
|
1614
|
+
});
|
|
1615
|
+
let providerRetryAttempt = 0;
|
|
1616
|
+
const firstEventTimeoutAbortError = new StreamTimeoutError("Anthropic stream timed out while waiting for the first event");
|
|
1617
|
+
const idleTimeoutAbortError = new StreamTimeoutError("Anthropic stream stalled while waiting for the next event");
|
|
1618
|
+
while (true) {
|
|
1619
|
+
activeAbortTracker = createAbortSourceTracker(options?.signal);
|
|
1620
|
+
const { requestSignal } = activeAbortTracker;
|
|
1621
|
+
const injectedClientEffortHeaders = options?.client !== void 0 && params.output_config?.effort !== void 0 ? mergeAnthropicBetaHeader(mergedCallerHeaders, effortBeta) : void 0;
|
|
1622
|
+
const perRequestHeaders = umansGatewayWebSearchHeader || injectedClientEffortHeaders ? {
|
|
1623
|
+
...umansGatewayWebSearchHeader,
|
|
1624
|
+
...injectedClientEffortHeaders
|
|
1625
|
+
} : void 0;
|
|
1626
|
+
const requestOptions = {
|
|
1627
|
+
...createSdkStreamRequestOptions(requestSignal, requestTimeoutMs),
|
|
1628
|
+
maxRetries: 0,
|
|
1629
|
+
...perRequestHeaders ? { headers: perRequestHeaders } : {}
|
|
1630
|
+
};
|
|
1631
|
+
const anthropicRequest = isOAuthToken && client.beta ? client.beta.messages.create({
|
|
1632
|
+
...params,
|
|
1633
|
+
stream: true
|
|
1634
|
+
}, requestOptions) : client.messages.create({
|
|
1635
|
+
...params,
|
|
1636
|
+
stream: true
|
|
1637
|
+
}, requestOptions);
|
|
1638
|
+
let streamedReplayUnsafeContent = false;
|
|
1639
|
+
try {
|
|
1640
|
+
let requestTimeout;
|
|
1641
|
+
if (requestTimeoutMs !== void 0) requestTimeout = setTimeout(() => activeAbortTracker.abortLocally(firstEventTimeoutAbortError), requestTimeoutMs);
|
|
1642
|
+
let anthropicStream;
|
|
1643
|
+
let response;
|
|
1644
|
+
let requestId;
|
|
1645
|
+
let recordsRawSseEvents;
|
|
1646
|
+
try {
|
|
1647
|
+
({events: anthropicStream, response, requestId, recordsRawSseEvents} = await getAnthropicStreamResponse(anthropicRequest, requestSignal, rawSseObserver));
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
if (error instanceof AnthropicConnectionTimeoutError && !activeAbortTracker.wasCallerAbort()) throw firstEventTimeoutAbortError;
|
|
1650
|
+
throw error;
|
|
1651
|
+
} finally {
|
|
1652
|
+
if (requestTimeout !== void 0) clearTimeout(requestTimeout);
|
|
1653
|
+
}
|
|
1654
|
+
await notifyProviderResponse(options, response, model, requestId);
|
|
1655
|
+
let sawEvent = false;
|
|
1656
|
+
let sawMessageStart = false;
|
|
1657
|
+
let sawTerminalEnvelope = false;
|
|
1658
|
+
let sawMessageStop = false;
|
|
1659
|
+
let sawSplicedEnvelope = false;
|
|
1660
|
+
const closedBlockIndexes = /* @__PURE__ */ new Set();
|
|
1661
|
+
const openBlocks = /* @__PURE__ */ new Map();
|
|
1662
|
+
let sawNonPingEvent = false;
|
|
1663
|
+
let lastNonPingProgressAtMs = 0;
|
|
1664
|
+
const pingProgressCapMs = idleTimeoutMs !== void 0 && idleTimeoutMs > 0 ? idleTimeoutMs * PING_PROGRESS_MAX_IDLE_MULTIPLIER : void 0;
|
|
1665
|
+
const timedAnthropicStream = iterateWithIdleTimeout(anthropicStream, {
|
|
1666
|
+
idleTimeoutMs,
|
|
1667
|
+
firstItemTimeoutMs: firstEventTimeoutMs,
|
|
1668
|
+
errorMessage: idleTimeoutAbortError.message,
|
|
1669
|
+
firstItemErrorMessage: firstEventTimeoutAbortError.message,
|
|
1670
|
+
onIdle: () => activeAbortTracker.abortLocally(idleTimeoutAbortError),
|
|
1671
|
+
onFirstItemTimeout: () => activeAbortTracker.abortLocally(firstEventTimeoutAbortError),
|
|
1672
|
+
abortSignal: options?.signal,
|
|
1673
|
+
isProgressItem: (item) => {
|
|
1674
|
+
if (item.type === "ping") {
|
|
1675
|
+
if (!sawNonPingEvent) return false;
|
|
1676
|
+
if (pingProgressCapMs === void 0) return true;
|
|
1677
|
+
return Date.now() - lastNonPingProgressAtMs < pingProgressCapMs;
|
|
1678
|
+
}
|
|
1679
|
+
sawNonPingEvent = true;
|
|
1680
|
+
lastNonPingProgressAtMs = Date.now();
|
|
1681
|
+
return true;
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
const observedAnthropicStream = rawSseObserver && !recordsRawSseEvents ? observeDecodedAnthropicSdkEvents(timedAnthropicStream, rawSseObserver) : timedAnthropicStream;
|
|
1685
|
+
for await (const event of observedAnthropicStream) {
|
|
1686
|
+
sawEvent = true;
|
|
1687
|
+
if (event.type === "message_start") {
|
|
1688
|
+
if (sawMessageStart) {
|
|
1689
|
+
reportAnthropicEnvelopeAnomaly("duplicate message_start event");
|
|
1690
|
+
sawSplicedEnvelope = true;
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1693
|
+
sawMessageStart = true;
|
|
1694
|
+
const startMessage = event.message;
|
|
1695
|
+
if (startMessage?.id) output.responseId = startMessage.id;
|
|
1696
|
+
const startUsage = startMessage?.usage;
|
|
1697
|
+
if (startUsage) {
|
|
1698
|
+
applyAnthropicUsageExtras(output.usage, startUsage);
|
|
1699
|
+
output.usage.input = startUsage.input_tokens || 0;
|
|
1700
|
+
output.usage.output = startUsage.output_tokens || 0;
|
|
1701
|
+
output.usage.cacheRead = startUsage.cache_read_input_tokens || 0;
|
|
1702
|
+
output.usage.cacheWrite = startUsage.cache_creation_input_tokens || 0;
|
|
1703
|
+
output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
1704
|
+
if (serverSideFallback) {
|
|
1705
|
+
const served = fallbackServedModelFromUsage(startUsage);
|
|
1706
|
+
if (served) output.model = served;
|
|
1707
|
+
if (!calculateFallbackTurnCost(model, output.usage, startUsage)) calculateCost(model, output.usage);
|
|
1708
|
+
} else calculateCost(model, output.usage);
|
|
1709
|
+
} else reportAnthropicEnvelopeAnomaly("message_start missing usage");
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
if (!sawMessageStart) {
|
|
1713
|
+
if (shouldIgnoreAnthropicPreambleEvent(event.type)) continue;
|
|
1714
|
+
throw new AnthropicStreamEnvelopeError(`received ${event.type} before message_start`);
|
|
1715
|
+
}
|
|
1716
|
+
if (event.type === "content_block_start") {
|
|
1717
|
+
if (sawTerminalEnvelope) {
|
|
1718
|
+
reportAnthropicEnvelopeAnomaly(`received ${event.type} after terminal stop signal`);
|
|
1719
|
+
continue;
|
|
1720
|
+
}
|
|
1721
|
+
if (openBlocks.has(event.index)) {
|
|
1722
|
+
reportAnthropicEnvelopeAnomaly(`duplicate content_block_start index ${event.index}`);
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
if (sawSplicedEnvelope && closedBlockIndexes.has(event.index)) {
|
|
1726
|
+
reportAnthropicEnvelopeAnomaly(`replayed content_block_start index ${event.index} after duplicate message_start`);
|
|
1727
|
+
openBlocks.set(event.index, {
|
|
1728
|
+
contentIndex: -1,
|
|
1729
|
+
kind: "ignored"
|
|
1730
|
+
});
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
if (!event.content_block?.type) {
|
|
1734
|
+
reportAnthropicEnvelopeAnomaly("content_block_start missing content_block payload");
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
1738
|
+
if (event.content_block.type === "fallback") {
|
|
1739
|
+
const fallback = parseAnthropicFallbackWireBlock(event.content_block);
|
|
1740
|
+
if (!serverSideFallback || !fallback) {
|
|
1741
|
+
if (!fallback) reportAnthropicEnvelopeAnomaly("fallback content_block missing model refs");
|
|
1742
|
+
openBlocks.set(event.index, {
|
|
1743
|
+
contentIndex: -1,
|
|
1744
|
+
kind: "ignored"
|
|
1745
|
+
});
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
const block = {
|
|
1749
|
+
...fallback,
|
|
1750
|
+
[kStreamingBlockIndex]: event.index
|
|
1751
|
+
};
|
|
1752
|
+
output.content.push(block);
|
|
1753
|
+
openBlocks.set(event.index, {
|
|
1754
|
+
contentIndex: output.content.length - 1,
|
|
1755
|
+
kind: "fallback"
|
|
1756
|
+
});
|
|
1757
|
+
output.model = fallback.to.model;
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
if (event.content_block.type === "text") {
|
|
1761
|
+
streamedReplayUnsafeContent = true;
|
|
1762
|
+
const block = {
|
|
1763
|
+
type: "text",
|
|
1764
|
+
text: "",
|
|
1765
|
+
[kStreamingBlockIndex]: event.index
|
|
1766
|
+
};
|
|
1767
|
+
output.content.push(block);
|
|
1768
|
+
const contentIndex = output.content.length - 1;
|
|
1769
|
+
openBlocks.set(event.index, {
|
|
1770
|
+
contentIndex,
|
|
1771
|
+
kind: "text"
|
|
1772
|
+
});
|
|
1773
|
+
stream.push({
|
|
1774
|
+
type: "text_start",
|
|
1775
|
+
contentIndex,
|
|
1776
|
+
partial: output
|
|
1777
|
+
});
|
|
1778
|
+
} else if (event.content_block.type === "thinking") {
|
|
1779
|
+
streamedReplayUnsafeContent = true;
|
|
1780
|
+
const block = {
|
|
1781
|
+
type: "thinking",
|
|
1782
|
+
thinking: event.content_block.thinking ?? "",
|
|
1783
|
+
thinkingSignature: "",
|
|
1784
|
+
[kStreamingBlockIndex]: event.index
|
|
1785
|
+
};
|
|
1786
|
+
output.content.push(block);
|
|
1787
|
+
const contentIndex = output.content.length - 1;
|
|
1788
|
+
openBlocks.set(event.index, {
|
|
1789
|
+
contentIndex,
|
|
1790
|
+
kind: "thinking"
|
|
1791
|
+
});
|
|
1792
|
+
stream.push({
|
|
1793
|
+
type: "thinking_start",
|
|
1794
|
+
contentIndex,
|
|
1795
|
+
partial: output
|
|
1796
|
+
});
|
|
1797
|
+
if (block.thinking) stream.push({
|
|
1798
|
+
type: "thinking_delta",
|
|
1799
|
+
contentIndex,
|
|
1800
|
+
delta: block.thinking,
|
|
1801
|
+
partial: output
|
|
1802
|
+
});
|
|
1803
|
+
} else if (event.content_block.type === "redacted_thinking") {
|
|
1804
|
+
streamedReplayUnsafeContent = true;
|
|
1805
|
+
const block = {
|
|
1806
|
+
type: "redactedThinking",
|
|
1807
|
+
data: event.content_block.data,
|
|
1808
|
+
[kStreamingBlockIndex]: event.index
|
|
1809
|
+
};
|
|
1810
|
+
output.content.push(block);
|
|
1811
|
+
openBlocks.set(event.index, {
|
|
1812
|
+
contentIndex: output.content.length - 1,
|
|
1813
|
+
kind: "redactedThinking"
|
|
1814
|
+
});
|
|
1815
|
+
} else if (isAnthropicServerToolHistoryBlock(event.content_block) && (umansGatewayWebSearchHeader === void 0 || (event.content_block.type === "server_tool_use" ? event.content_block.name !== "web_search" : event.content_block.type !== "web_search_tool_result"))) {
|
|
1816
|
+
streamedReplayUnsafeContent = true;
|
|
1817
|
+
const block = {
|
|
1818
|
+
type: "anthropicServerTool",
|
|
1819
|
+
block: { ...event.content_block },
|
|
1820
|
+
[kStreamingPartialJson]: "",
|
|
1821
|
+
[kStreamingBlockIndex]: event.index
|
|
1822
|
+
};
|
|
1823
|
+
output.content.push(block);
|
|
1824
|
+
openBlocks.set(event.index, {
|
|
1825
|
+
contentIndex: output.content.length - 1,
|
|
1826
|
+
kind: "anthropicServerTool"
|
|
1827
|
+
});
|
|
1828
|
+
} else if (event.content_block.type === "tool_use") {
|
|
1829
|
+
streamedReplayUnsafeContent = true;
|
|
1830
|
+
const block = {
|
|
1831
|
+
type: "toolCall",
|
|
1832
|
+
id: event.content_block.id,
|
|
1833
|
+
name: decodeAnthropicToolName(event.content_block.name, isOAuthToken, model.compat.escapeBuiltinToolNames),
|
|
1834
|
+
arguments: event.content_block.input ?? {},
|
|
1835
|
+
[kStreamingPartialJson]: "",
|
|
1836
|
+
[kStreamingBlockIndex]: event.index
|
|
1837
|
+
};
|
|
1838
|
+
output.content.push(block);
|
|
1839
|
+
const contentIndex = output.content.length - 1;
|
|
1840
|
+
openBlocks.set(event.index, {
|
|
1841
|
+
contentIndex,
|
|
1842
|
+
kind: "toolCall"
|
|
1843
|
+
});
|
|
1844
|
+
stream.push({
|
|
1845
|
+
type: "toolcall_start",
|
|
1846
|
+
contentIndex,
|
|
1847
|
+
partial: output
|
|
1848
|
+
});
|
|
1849
|
+
} else openBlocks.set(event.index, {
|
|
1850
|
+
contentIndex: -1,
|
|
1851
|
+
kind: "ignored"
|
|
1852
|
+
});
|
|
1853
|
+
} else if (event.type === "content_block_delta") {
|
|
1854
|
+
if (sawTerminalEnvelope) {
|
|
1855
|
+
reportAnthropicEnvelopeAnomaly(`received ${event.type} after terminal stop signal`);
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
const openBlock = openBlocks.get(event.index);
|
|
1859
|
+
if (!openBlock) {
|
|
1860
|
+
reportAnthropicEnvelopeAnomaly(`received content_block_delta for unopened index ${event.index}`);
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
if (openBlock.kind === "ignored") continue;
|
|
1864
|
+
if (!event.delta?.type) {
|
|
1865
|
+
reportAnthropicEnvelopeAnomaly("content_block_delta missing delta payload");
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
const block = blocks[openBlock.contentIndex];
|
|
1869
|
+
if (event.delta.type === "text_delta") {
|
|
1870
|
+
if (openBlock.kind !== "text" || block?.type !== "text") {
|
|
1871
|
+
reportAnthropicEnvelopeAnomaly(`received text_delta for ${openBlock.kind} block`);
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
streamedReplayUnsafeContent = true;
|
|
1875
|
+
block.text += event.delta.text;
|
|
1876
|
+
stream.push({
|
|
1877
|
+
type: "text_delta",
|
|
1878
|
+
contentIndex: openBlock.contentIndex,
|
|
1879
|
+
delta: event.delta.text,
|
|
1880
|
+
partial: output
|
|
1881
|
+
});
|
|
1882
|
+
} else if (event.delta.type === "thinking_delta") {
|
|
1883
|
+
if (openBlock.kind !== "thinking" || block?.type !== "thinking") {
|
|
1884
|
+
reportAnthropicEnvelopeAnomaly(`received thinking_delta for ${openBlock.kind} block`);
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
streamedReplayUnsafeContent = true;
|
|
1888
|
+
block.thinking += event.delta.thinking;
|
|
1889
|
+
stream.push({
|
|
1890
|
+
type: "thinking_delta",
|
|
1891
|
+
contentIndex: openBlock.contentIndex,
|
|
1892
|
+
delta: event.delta.thinking,
|
|
1893
|
+
partial: output
|
|
1894
|
+
});
|
|
1895
|
+
} else if (event.delta.type === "input_json_delta") {
|
|
1896
|
+
if (openBlock.kind === "anthropicServerTool" && block?.type === "anthropicServerTool" && block.block.type === "server_tool_use") {
|
|
1897
|
+
block[kStreamingPartialJson] = (block[kStreamingPartialJson] ?? "") + event.delta.partial_json;
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
if (openBlock.kind !== "toolCall" || block?.type !== "toolCall") {
|
|
1901
|
+
reportAnthropicEnvelopeAnomaly(`received input_json_delta for ${openBlock.kind} block`);
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1904
|
+
streamedReplayUnsafeContent = true;
|
|
1905
|
+
block[kStreamingPartialJson] += event.delta.partial_json;
|
|
1906
|
+
const throttled = parseStreamingJsonThrottled(block[kStreamingPartialJson], block[kStreamingLastParseLen] ?? 0);
|
|
1907
|
+
if (throttled) {
|
|
1908
|
+
block.arguments = throttled.value;
|
|
1909
|
+
block[kStreamingLastParseLen] = throttled.parsedLen;
|
|
1910
|
+
}
|
|
1911
|
+
stream.push({
|
|
1912
|
+
type: "toolcall_delta",
|
|
1913
|
+
contentIndex: openBlock.contentIndex,
|
|
1914
|
+
delta: event.delta.partial_json,
|
|
1915
|
+
partial: output
|
|
1916
|
+
});
|
|
1917
|
+
} else if (event.delta.type === "signature_delta") {
|
|
1918
|
+
if (openBlock.kind !== "thinking" || block?.type !== "thinking") {
|
|
1919
|
+
reportAnthropicEnvelopeAnomaly(`received signature_delta for ${openBlock.kind} block`);
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
streamedReplayUnsafeContent = true;
|
|
1923
|
+
block.thinkingSignature = block.thinkingSignature || "";
|
|
1924
|
+
block.thinkingSignature += event.delta.signature;
|
|
1925
|
+
}
|
|
1926
|
+
} else if (event.type === "content_block_stop") {
|
|
1927
|
+
if (sawTerminalEnvelope) {
|
|
1928
|
+
reportAnthropicEnvelopeAnomaly(`received ${event.type} after terminal stop signal`);
|
|
1929
|
+
continue;
|
|
1930
|
+
}
|
|
1931
|
+
const openBlock = openBlocks.get(event.index);
|
|
1932
|
+
if (!openBlock) {
|
|
1933
|
+
reportAnthropicEnvelopeAnomaly(`received content_block_stop for unopened index ${event.index}`);
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
if (openBlock.kind === "ignored") {
|
|
1937
|
+
openBlocks.delete(event.index);
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
const block = blocks[openBlock.contentIndex];
|
|
1941
|
+
if (!block || block.type !== openBlock.kind) {
|
|
1942
|
+
reportAnthropicEnvelopeAnomaly(`content_block_stop kind mismatch for index ${event.index}`);
|
|
1943
|
+
openBlocks.delete(event.index);
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
openBlocks.delete(event.index);
|
|
1947
|
+
closedBlockIndexes.add(event.index);
|
|
1948
|
+
finalizeStreamBlock(block, openBlock.contentIndex);
|
|
1949
|
+
} else if (event.type === "message_delta") {
|
|
1950
|
+
if (sawTerminalEnvelope) {
|
|
1951
|
+
reportAnthropicEnvelopeAnomaly("received message_delta after terminal stop signal");
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
const delta = event.delta;
|
|
1955
|
+
const rawStopReason = delta?.stop_reason;
|
|
1956
|
+
if (rawStopReason) {
|
|
1957
|
+
output.stopReason = mapStopReason(rawStopReason);
|
|
1958
|
+
sawTerminalEnvelope = true;
|
|
1959
|
+
}
|
|
1960
|
+
if (output.stopReason === "error") {
|
|
1961
|
+
const stopDetails = delta?.stop_details;
|
|
1962
|
+
output.stopDetails = stopDetails ?? (rawStopReason ? { type: rawStopReason } : null);
|
|
1963
|
+
if (stopDetails?.type === "refusal") {
|
|
1964
|
+
const explanation = stopDetails.explanation?.trim();
|
|
1965
|
+
const category = stopDetails.category;
|
|
1966
|
+
const label = category ? `Refusal (${category})` : "Refusal";
|
|
1967
|
+
output.errorMessage = explanation ? `${label}: ${explanation}` : label;
|
|
1968
|
+
} else if (!output.errorMessage) output.errorMessage = rawStopReason === "refusal" ? "Refusal (no details provided)" : rawStopReason === "sensitive" ? "Content flagged by safety filters" : `Anthropic stream ended with stop_reason: ${rawStopReason ?? "unknown"}`;
|
|
1969
|
+
}
|
|
1970
|
+
const deltaUsage = event.usage;
|
|
1971
|
+
if (deltaUsage) {
|
|
1972
|
+
if (deltaUsage.input_tokens != null) output.usage.input = deltaUsage.input_tokens;
|
|
1973
|
+
if (deltaUsage.output_tokens != null) output.usage.output = deltaUsage.output_tokens;
|
|
1974
|
+
if (deltaUsage.cache_read_input_tokens != null) output.usage.cacheRead = deltaUsage.cache_read_input_tokens;
|
|
1975
|
+
if (deltaUsage.cache_creation_input_tokens != null) output.usage.cacheWrite = deltaUsage.cache_creation_input_tokens;
|
|
1976
|
+
applyAnthropicUsageExtras(output.usage, deltaUsage);
|
|
1977
|
+
output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
1978
|
+
if (serverSideFallback) {
|
|
1979
|
+
const served = fallbackServedModelFromUsage(deltaUsage);
|
|
1980
|
+
if (served) output.model = served;
|
|
1981
|
+
if (!calculateFallbackTurnCost(model, output.usage, deltaUsage)) calculateCost(model, output.usage);
|
|
1982
|
+
} else calculateCost(model, output.usage);
|
|
1983
|
+
}
|
|
1984
|
+
} else if (event.type === "message_stop") {
|
|
1985
|
+
sawTerminalEnvelope = true;
|
|
1986
|
+
sawMessageStop = true;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
const firstEventTimeoutError = activeAbortTracker.getLocalAbortReason();
|
|
1990
|
+
if (firstEventTimeoutError) throw firstEventTimeoutError;
|
|
1991
|
+
if (activeAbortTracker.wasCallerAbort()) throw new AbortError();
|
|
1992
|
+
if (!sawEvent || !sawMessageStart) throw new AnthropicStreamEnvelopeError("stream ended before message_start");
|
|
1993
|
+
if (!sawTerminalEnvelope) throw new AnthropicStreamEnvelopeError("stream ended before message_stop");
|
|
1994
|
+
if (!sawMessageStop) reportAnthropicEnvelopeAnomaly("stream ended before message_stop");
|
|
1995
|
+
if (openBlocks.size > 0) {
|
|
1996
|
+
for (const [openIndex, openBlock] of openBlocks) {
|
|
1997
|
+
reportAnthropicEnvelopeAnomaly(`stream ended with an unterminated ${openBlock.kind} block at index ${openIndex}`);
|
|
1998
|
+
if (openBlock.kind === "ignored" || openBlock.contentIndex < 0) continue;
|
|
1999
|
+
const danglingBlock = blocks[openBlock.contentIndex];
|
|
2000
|
+
if (danglingBlock) finalizeStreamBlock(danglingBlock, openBlock.contentIndex);
|
|
2001
|
+
}
|
|
2002
|
+
openBlocks.clear();
|
|
2003
|
+
}
|
|
2004
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") throw new ProviderResponseError(output.errorMessage ?? "An unknown error occurred", {
|
|
2005
|
+
provider: model.provider,
|
|
2006
|
+
kind: "output"
|
|
2007
|
+
});
|
|
2008
|
+
break;
|
|
2009
|
+
} catch (streamError) {
|
|
2010
|
+
const streamFailure = activeAbortTracker.getLocalAbortReason() ?? streamError;
|
|
2011
|
+
if (!disableStrictTools && firstTokenTime === void 0 && hasStrictAnthropicTools(params) && isGrammarError(streamFailure)) {
|
|
2012
|
+
warn("anthropic: strict tools rejected, retrying without strict tools", {
|
|
2013
|
+
model: model.id,
|
|
2014
|
+
error: await finalizeErrorMessage(streamFailure, rawRequestDump)
|
|
2015
|
+
});
|
|
2016
|
+
if (providerSessionState) providerSessionState.strictToolsDisabled = true;
|
|
2017
|
+
disableStrictTools = true;
|
|
2018
|
+
params = await prepareParams();
|
|
2019
|
+
providerRetryAttempt = 0;
|
|
2020
|
+
output.content.length = 0;
|
|
2021
|
+
output.model = model.id;
|
|
2022
|
+
output.responseId = void 0;
|
|
2023
|
+
output.errorMessage = void 0;
|
|
2024
|
+
output.providerPayload = void 0;
|
|
2025
|
+
output.usage = createEmptyUsage(copilotDynamicHeaders?.premiumRequests);
|
|
2026
|
+
output.stopReason = "stop";
|
|
2027
|
+
firstTokenTime = void 0;
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
if (!forceDemoteUnsignedThinking && firstTokenTime === void 0 && !streamedReplayUnsafeContent && isInvalidThinkingSignatureError(streamFailure instanceof Error ? streamFailure.message : String(streamFailure))) {
|
|
2031
|
+
warn("anthropic: signing proxy detected (Invalid signature in thinking block), demoting unsigned thinking and retrying", {
|
|
2032
|
+
provider: model.provider,
|
|
2033
|
+
model: model.id,
|
|
2034
|
+
baseUrl,
|
|
2035
|
+
error: streamFailure instanceof Error ? streamFailure.message : String(streamFailure)
|
|
2036
|
+
});
|
|
2037
|
+
if (providerSessionState) providerSessionState.replayUnsignedThinkingDisabled = true;
|
|
2038
|
+
forceDemoteUnsignedThinking = true;
|
|
2039
|
+
params = await prepareParams();
|
|
2040
|
+
providerRetryAttempt = 0;
|
|
2041
|
+
output.content.length = 0;
|
|
2042
|
+
output.model = model.id;
|
|
2043
|
+
output.responseId = void 0;
|
|
2044
|
+
output.errorMessage = void 0;
|
|
2045
|
+
output.providerPayload = void 0;
|
|
2046
|
+
output.usage = createEmptyUsage(copilotDynamicHeaders?.premiumRequests);
|
|
2047
|
+
output.stopReason = "stop";
|
|
2048
|
+
firstTokenTime = void 0;
|
|
2049
|
+
continue;
|
|
2050
|
+
}
|
|
2051
|
+
if (!dropFastMode && model.provider === "anthropic" && options?.serviceTier === "priority" && firstTokenTime === void 0 && isFastModeUnsupported(streamFailure)) {
|
|
2052
|
+
debug("anthropic: fast mode unsupported, retrying without speed", {
|
|
2053
|
+
model: model.id,
|
|
2054
|
+
error: streamFailure instanceof Error ? streamFailure.message : String(streamFailure)
|
|
2055
|
+
});
|
|
2056
|
+
if (providerSessionState) providerSessionState.fastModeDisabled = true;
|
|
2057
|
+
dropFastMode = true;
|
|
2058
|
+
params = await prepareParams();
|
|
2059
|
+
providerRetryAttempt = 0;
|
|
2060
|
+
output.content.length = 0;
|
|
2061
|
+
output.model = model.id;
|
|
2062
|
+
output.responseId = void 0;
|
|
2063
|
+
output.errorMessage = void 0;
|
|
2064
|
+
output.providerPayload = void 0;
|
|
2065
|
+
output.usage = createEmptyUsage(copilotDynamicHeaders?.premiumRequests);
|
|
2066
|
+
output.stopReason = "stop";
|
|
2067
|
+
firstTokenTime = void 0;
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
const isTransientEnvelopeFailure = isTransientStreamParseError(streamFailure) || isStreamEnvelopeError(streamFailure);
|
|
2071
|
+
const isLocalIdleTimeout = streamFailure === idleTimeoutAbortError || streamFailure instanceof Error && streamFailure.message === idleTimeoutAbortError.message;
|
|
2072
|
+
const canRetryTransientEnvelopeFailure = isTransientEnvelopeFailure && !streamedReplayUnsafeContent;
|
|
2073
|
+
const canRetryProviderFailure = !isLocalIdleTimeout && firstTokenTime === void 0 && !streamedReplayUnsafeContent && isProviderRetryableError(streamFailure, model.provider);
|
|
2074
|
+
if (activeAbortTracker.wasCallerAbort() || providerRetryAttempt >= PROVIDER_MAX_RETRIES || !canRetryTransientEnvelopeFailure && !canRetryProviderFailure) throw streamFailure;
|
|
2075
|
+
providerRetryAttempt++;
|
|
2076
|
+
const backoffDelayMs = isCopilotTransientModelError(streamFailure) ? COPILOT_MODEL_FLAP_RETRY_DELAY_MS : calculateAnthropicRetryDelayMs(providerRetryAttempt - 1);
|
|
2077
|
+
const headerDelayMs = getRetryAfterMsFromHeaders(getHeadersFromError(streamFailure));
|
|
2078
|
+
const maxRetryDelayMs = options?.maxRetryDelayMs ?? 6e4;
|
|
2079
|
+
if (headerDelayMs !== void 0 && maxRetryDelayMs > 0 && headerDelayMs > maxRetryDelayMs) throw streamFailure;
|
|
2080
|
+
const delayMs = headerDelayMs !== void 0 ? Math.max(headerDelayMs, backoffDelayMs) : backoffDelayMs;
|
|
2081
|
+
if (options?.providerRetryWait) await options.providerRetryWait(delayMs, options.signal);
|
|
2082
|
+
else await scheduler.wait(delayMs, { signal: options?.signal });
|
|
2083
|
+
output.content.length = 0;
|
|
2084
|
+
output.model = model.id;
|
|
2085
|
+
output.responseId = void 0;
|
|
2086
|
+
output.errorMessage = void 0;
|
|
2087
|
+
output.stopDetails = void 0;
|
|
2088
|
+
output.providerPayload = void 0;
|
|
2089
|
+
output.usage = createEmptyUsage(copilotDynamicHeaders?.premiumRequests);
|
|
2090
|
+
output.stopReason = "stop";
|
|
2091
|
+
firstTokenTime = void 0;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
output.duration = performance.now() - startTime;
|
|
2095
|
+
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
2096
|
+
if (dropFastMode && model.provider === "anthropic" && options?.serviceTier === "priority") output.disabledFeatures = [...output.disabledFeatures ?? [], "priority"];
|
|
2097
|
+
if (forceDemoteUnsignedThinking && model.compat.replayUnsignedThinking) output.disabledFeatures = [...output.disabledFeatures ?? [], "unsigned-thinking-replay"];
|
|
2098
|
+
stream.push({
|
|
2099
|
+
type: "done",
|
|
2100
|
+
reason: output.stopReason,
|
|
2101
|
+
message: output
|
|
2102
|
+
});
|
|
2103
|
+
stream.end();
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
for (const block of output.content) if (block.type === "toolCall") clearStreamingPartialJson(block);
|
|
2106
|
+
const result = await finalize(error, {
|
|
2107
|
+
api: model.api,
|
|
2108
|
+
provider: model.provider,
|
|
2109
|
+
abortTracker: activeAbortTracker,
|
|
2110
|
+
rawRequestDump
|
|
2111
|
+
});
|
|
2112
|
+
output.stopReason = result.stopReason;
|
|
2113
|
+
output.errorStatus = result.status;
|
|
2114
|
+
output.errorId = result.id;
|
|
2115
|
+
output.errorMessage = maybeAddReplayUnsignedThinkingHint(model, result.message);
|
|
2116
|
+
output.duration = performance.now() - startTime;
|
|
2117
|
+
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
2118
|
+
stream.push({
|
|
2119
|
+
type: "error",
|
|
2120
|
+
reason: output.stopReason,
|
|
2121
|
+
error: output
|
|
2122
|
+
});
|
|
2123
|
+
stream.end();
|
|
2124
|
+
}
|
|
2125
|
+
})();
|
|
2126
|
+
return stream;
|
|
2127
|
+
};
|
|
2128
|
+
/**
|
|
2129
|
+
* Public entry: wrap the single-attempt streamer with bounded empty-completion
|
|
2130
|
+
* retries (a benign terminal stop carrying no content/usage would otherwise
|
|
2131
|
+
* stall the agent loop). The inner attempt keeps its own provider-failure retry
|
|
2132
|
+
* loop; this layer only re-issues a fresh request on an empty success. Shared
|
|
2133
|
+
* with the OpenAI-completions provider via `withEmptyCompletionRetry`.
|
|
2134
|
+
*/
|
|
2135
|
+
const streamAnthropic = (model, context, options) => withEmptyCompletionRetry(model, context, options, streamAnthropicOnce);
|
|
2136
|
+
function buildAnthropicSystemBlocks(systemPrompt, options = {}) {
|
|
2137
|
+
const { includeClaudeCodeInstruction = false, extraInstructions = [], firstUserMessageText } = options;
|
|
2138
|
+
const sanitizedPrompts = normalizeSystemPrompts(systemPrompt);
|
|
2139
|
+
const trimmedInstructions = extraInstructions.map((instruction) => instruction.trim()).filter(Boolean);
|
|
2140
|
+
const hasBillingHeader = sanitizedPrompts.some((prompt) => prompt.startsWith(CLAUDE_BILLING_HEADER_PREFIX));
|
|
2141
|
+
if (includeClaudeCodeInstruction && !hasBillingHeader) {
|
|
2142
|
+
const blocks = [{
|
|
2143
|
+
type: "text",
|
|
2144
|
+
text: createClaudeBillingHeader(firstUserMessageText ?? "")
|
|
2145
|
+
}, {
|
|
2146
|
+
type: "text",
|
|
2147
|
+
text: claudeCodeSystemInstruction
|
|
2148
|
+
}];
|
|
2149
|
+
for (const instruction of trimmedInstructions) blocks.push({
|
|
2150
|
+
type: "text",
|
|
2151
|
+
text: instruction
|
|
2152
|
+
});
|
|
2153
|
+
for (const prompt of sanitizedPrompts) blocks.push({
|
|
2154
|
+
type: "text",
|
|
2155
|
+
text: prompt
|
|
2156
|
+
});
|
|
2157
|
+
return blocks;
|
|
2158
|
+
}
|
|
2159
|
+
const blocks = [];
|
|
2160
|
+
for (const instruction of trimmedInstructions) blocks.push({
|
|
2161
|
+
type: "text",
|
|
2162
|
+
text: instruction
|
|
2163
|
+
});
|
|
2164
|
+
for (const prompt of sanitizedPrompts) blocks.push({
|
|
2165
|
+
type: "text",
|
|
2166
|
+
text: prompt
|
|
2167
|
+
});
|
|
2168
|
+
return blocks.length > 0 ? blocks : void 0;
|
|
2169
|
+
}
|
|
2170
|
+
function normalizeExtraBetas(betas) {
|
|
2171
|
+
if (!betas) return [];
|
|
2172
|
+
return (Array.isArray(betas) ? betas : betas.split(",")).map((beta) => beta.trim()).filter((beta) => beta.length > 0);
|
|
2173
|
+
}
|
|
2174
|
+
function buildAnthropicClientOptions(args) {
|
|
2175
|
+
const { model, apiKey, extraBetas = [], stream = true, interleavedThinking = true, headers, dynamicHeaders, hasTools = false, thinkingEnabled = false, isOAuth, maxRetryDelayMs, claudeCodeSessionId, disableStrictTools: disableStrictToolsOverride } = args;
|
|
2176
|
+
const compat = model.compat;
|
|
2177
|
+
const disableStrictTools = disableStrictToolsOverride ?? compat.disableStrictTools;
|
|
2178
|
+
const baseUrl = resolveAnthropicBaseUrl(model, apiKey);
|
|
2179
|
+
const needsInterleavedBeta = interleavedThinking && (!model.thinking?.supportsDisplay || !isOfficialAnthropicApiUrl(baseUrl) && (isAnthropicSigningProxyUrl(baseUrl) || compat.signingEndpoint && !compat.officialEndpoint) && !isVertexRawPredictUrl(baseUrl ?? "") && !hostMatchesUrl(baseUrl, "githubCopilot"));
|
|
2180
|
+
const oauthToken = isOAuth ?? isAnthropicOAuthToken(apiKey);
|
|
2181
|
+
const supportsEagerToolInputStreaming = resolveEagerToolInputStreamingSupport(model, baseUrl);
|
|
2182
|
+
const needsFineGrainedToolStreamingBeta = hasTools && isOfficialAnthropicApiUrl(baseUrl) && !supportsEagerToolInputStreaming;
|
|
2183
|
+
const foundryCustomHeaders = resolveAnthropicCustomHeaders(model, baseUrl);
|
|
2184
|
+
const fetchOptions = {
|
|
2185
|
+
...buildCoworkTlsFetchOptions(model, baseUrl) ?? {},
|
|
2186
|
+
timeout: false
|
|
2187
|
+
};
|
|
2188
|
+
const baseFetch = args.fetch ?? fetch;
|
|
2189
|
+
const cchFetch = oauthToken ? wrapFetchForCch(baseFetch) : baseFetch;
|
|
2190
|
+
if (model.provider === "github-copilot") {
|
|
2191
|
+
const copilotApiKey = parseGitHubCopilotApiKey(apiKey).accessToken;
|
|
2192
|
+
const betaFeatures = [...extraBetas];
|
|
2193
|
+
return {
|
|
2194
|
+
isOAuthToken: false,
|
|
2195
|
+
apiKey: null,
|
|
2196
|
+
authToken: copilotApiKey,
|
|
2197
|
+
baseURL: baseUrl,
|
|
2198
|
+
maxRetries: 5,
|
|
2199
|
+
maxRetryDelayMs,
|
|
2200
|
+
defaultHeaders: mergeHeaders({
|
|
2201
|
+
Accept: stream ? "text/event-stream" : "application/json",
|
|
2202
|
+
"Content-Type": "application/json",
|
|
2203
|
+
"anthropic-version": "2023-06-01",
|
|
2204
|
+
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
|
2205
|
+
Authorization: `Bearer ${copilotApiKey}`,
|
|
2206
|
+
...betaFeatures.length > 0 ? { "anthropic-beta": buildBetaHeader([], betaFeatures) } : {}
|
|
2207
|
+
}, model.headers, dynamicHeaders, headers),
|
|
2208
|
+
fetch: cchFetch,
|
|
2209
|
+
fetchOptions
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
const betaFeatures = [...extraBetas];
|
|
2213
|
+
if (needsFineGrainedToolStreamingBeta) betaFeatures.push(fineGrainedToolStreamingBeta);
|
|
2214
|
+
if (needsInterleavedBeta) betaFeatures.push(interleavedThinkingBeta);
|
|
2215
|
+
const defaultHeaders = buildAnthropicHeaders({
|
|
2216
|
+
apiKey,
|
|
2217
|
+
baseUrl,
|
|
2218
|
+
isOAuth: oauthToken,
|
|
2219
|
+
extraBetas: betaFeatures,
|
|
2220
|
+
stream,
|
|
2221
|
+
modelHeaders: mergeHeaders(model.headers, foundryCustomHeaders, getUmansWebSearchHeader(model, mergeHeaders(model.headers, headers)), headers, dynamicHeaders),
|
|
2222
|
+
isCloudflareAiGateway: model.provider === "cloudflare-ai-gateway",
|
|
2223
|
+
allowAnthropicHeaderOverrides: model.compat.allowAnthropicHeaderOverrides,
|
|
2224
|
+
claudeCodeSessionId,
|
|
2225
|
+
coworkBetas: oauthToken ? buildCoworkBetas(hasTools || thinkingEnabled, thinkingEnabled, disableStrictTools) : []
|
|
2226
|
+
});
|
|
2227
|
+
if (model.provider === "cloudflare-ai-gateway") return {
|
|
2228
|
+
isOAuthToken: false,
|
|
2229
|
+
apiKey: null,
|
|
2230
|
+
authToken: null,
|
|
2231
|
+
baseURL: baseUrl,
|
|
2232
|
+
maxRetries: 5,
|
|
2233
|
+
maxRetryDelayMs,
|
|
2234
|
+
defaultHeaders,
|
|
2235
|
+
fetch: cchFetch,
|
|
2236
|
+
fetchOptions
|
|
2237
|
+
};
|
|
2238
|
+
if (model.provider === "opencode-go" || model.provider === "opencode-zen" || model.provider === "umans") {
|
|
2239
|
+
delete defaultHeaders.Authorization;
|
|
2240
|
+
return {
|
|
2241
|
+
isOAuthToken: false,
|
|
2242
|
+
apiKey,
|
|
2243
|
+
authToken: null,
|
|
2244
|
+
baseURL: baseUrl,
|
|
2245
|
+
maxRetries: 5,
|
|
2246
|
+
maxRetryDelayMs,
|
|
2247
|
+
defaultHeaders,
|
|
2248
|
+
fetch: cchFetch,
|
|
2249
|
+
fetchOptions
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
const authorizationHeader = getHeaderCaseInsensitive(defaultHeaders, "Authorization");
|
|
2253
|
+
const shouldSuppressClientApiKey = !oauthToken && !model.compat.officialEndpoint && typeof authorizationHeader === "string";
|
|
2254
|
+
return {
|
|
2255
|
+
isOAuthToken: oauthToken,
|
|
2256
|
+
apiKey: oauthToken || shouldSuppressClientApiKey ? null : apiKey,
|
|
2257
|
+
authToken: oauthToken ? apiKey : void 0,
|
|
2258
|
+
baseURL: baseUrl,
|
|
2259
|
+
maxRetries: 5,
|
|
2260
|
+
maxRetryDelayMs,
|
|
2261
|
+
defaultHeaders,
|
|
2262
|
+
fetch: cchFetch,
|
|
2263
|
+
fetchOptions
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
function createClient(model, args) {
|
|
2267
|
+
const { isOAuthToken: oauthToken, ...clientOptions } = buildAnthropicClientOptions({
|
|
2268
|
+
...args,
|
|
2269
|
+
model
|
|
2270
|
+
});
|
|
2271
|
+
return {
|
|
2272
|
+
client: new AnthropicMessagesClient(clientOptions),
|
|
2273
|
+
isOAuthToken: oauthToken
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
function disableThinkingIfToolChoiceForced(params, model) {
|
|
2277
|
+
const toolChoice = params.tool_choice;
|
|
2278
|
+
if (!toolChoice) return;
|
|
2279
|
+
if (toolChoice.type !== "any" && toolChoice.type !== "tool") return;
|
|
2280
|
+
delete params.thinking;
|
|
2281
|
+
delete params.context_management;
|
|
2282
|
+
if (isAdaptiveOnlyThinking(model) && model.provider !== "google-vertex") {
|
|
2283
|
+
const outputConfig = params.output_config ?? {};
|
|
2284
|
+
outputConfig.effort = "low";
|
|
2285
|
+
params.output_config = outputConfig;
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
const outputConfig = params.output_config;
|
|
2289
|
+
if (!outputConfig) return;
|
|
2290
|
+
delete outputConfig.effort;
|
|
2291
|
+
if (Object.keys(outputConfig).length === 0) delete params.output_config;
|
|
2292
|
+
}
|
|
2293
|
+
function ensureMaxTokensForThinking(params, maxAllowedTokens) {
|
|
2294
|
+
const thinking = params.thinking;
|
|
2295
|
+
if (thinking?.type !== "enabled") return;
|
|
2296
|
+
const budgetTokens = thinking.budget_tokens ?? 0;
|
|
2297
|
+
if (budgetTokens <= 0) return;
|
|
2298
|
+
const currentMaxTokens = Math.min(params.max_tokens ?? maxAllowedTokens, maxAllowedTokens);
|
|
2299
|
+
const raisedMaxTokens = Math.min(Math.max(currentMaxTokens, budgetTokens + OUTPUT_FALLBACK_BUFFER), maxAllowedTokens);
|
|
2300
|
+
params.max_tokens = raisedMaxTokens;
|
|
2301
|
+
if (budgetTokens + 4e3 <= raisedMaxTokens) return;
|
|
2302
|
+
const clampedBudget = raisedMaxTokens - OUTPUT_FALLBACK_BUFFER;
|
|
2303
|
+
if (clampedBudget <= 0) throw new ConfigurationError(`Anthropic thinking budget requires max_tokens greater than ${OUTPUT_FALLBACK_BUFFER}; got ${raisedMaxTokens}`);
|
|
2304
|
+
thinking.budget_tokens = clampedBudget;
|
|
2305
|
+
}
|
|
2306
|
+
function applyCacheControlToLastBlock(blocks, cacheControl) {
|
|
2307
|
+
for (let index = blocks.length - 1; index >= 0; index--) {
|
|
2308
|
+
const block = blocks[index];
|
|
2309
|
+
if (block.type === "thinking" || block.type === "redacted_thinking" || block.type === "fallback") continue;
|
|
2310
|
+
if ("cache_control" in block && block.cache_control != null) return false;
|
|
2311
|
+
blocks[index] = {
|
|
2312
|
+
...block,
|
|
2313
|
+
cache_control: cloneAnthropicCacheControl(cacheControl)
|
|
2314
|
+
};
|
|
2315
|
+
return true;
|
|
2316
|
+
}
|
|
2317
|
+
return false;
|
|
2318
|
+
}
|
|
2319
|
+
function applyPromptCaching(params, cacheControl) {
|
|
2320
|
+
if (!cacheControl) return;
|
|
2321
|
+
const trailingIndex = params.messages.length - 1;
|
|
2322
|
+
const trailingMessage = params.messages[trailingIndex];
|
|
2323
|
+
const messageEnd = trailingMessage?.role === "user" && trailingMessage.content === "Continue." && params.messages[trailingIndex - 1]?.role === "assistant" ? trailingIndex - 1 : trailingIndex;
|
|
2324
|
+
const start = Math.max(0, messageEnd - 1);
|
|
2325
|
+
for (let index = messageEnd; index >= start; index--) {
|
|
2326
|
+
const message = params.messages[index];
|
|
2327
|
+
if (!message) continue;
|
|
2328
|
+
if (typeof message.content === "string") message.content = [{
|
|
2329
|
+
type: "text",
|
|
2330
|
+
text: message.content,
|
|
2331
|
+
cache_control: cloneAnthropicCacheControl(cacheControl)
|
|
2332
|
+
}];
|
|
2333
|
+
else if (Array.isArray(message.content)) applyCacheControlToLastBlock(message.content, cacheControl);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
function usesAdaptiveThinkingTagOnly(model) {
|
|
2337
|
+
const thinking = model.thinking;
|
|
2338
|
+
if (thinking?.mode !== "anthropic-adaptive") return false;
|
|
2339
|
+
const effortMap = thinking.effortMap;
|
|
2340
|
+
if (!effortMap) return false;
|
|
2341
|
+
for (const effort of thinking.efforts) if (effortMap[effort] !== "adaptive") return false;
|
|
2342
|
+
return thinking.efforts.length > 0;
|
|
2343
|
+
}
|
|
2344
|
+
/**
|
|
2345
|
+
* True for adaptive-only Claude models (Opus 4.6+, Sonnet 4.6+, Fable/Mythos 5)
|
|
2346
|
+
* that reject `thinking.type: "disabled"`. Turning thinking off on these models
|
|
2347
|
+
* means omitting the `thinking` field entirely and pinning the lowest adaptive
|
|
2348
|
+
* effort — a bare omission defaults to adaptive thinking ON. Excludes MiniMax,
|
|
2349
|
+
* which drives adaptive thinking through the `thinking.type: "adaptive"` tag
|
|
2350
|
+
* itself rather than `output_config.effort`.
|
|
2351
|
+
*/
|
|
2352
|
+
function isAdaptiveOnlyThinking(model) {
|
|
2353
|
+
return model.thinking?.mode === "anthropic-adaptive" && !model.compat.disableAdaptiveThinking && !usesAdaptiveThinkingTagOnly(model);
|
|
2354
|
+
}
|
|
2355
|
+
function resolveAnthropicAdaptiveEffort(model, options) {
|
|
2356
|
+
if (options.effort) return usesAdaptiveThinkingTagOnly(model) ? "adaptive" : options.effort;
|
|
2357
|
+
const requestedEffort = options.reasoning;
|
|
2358
|
+
if (!requestedEffort) return void 0;
|
|
2359
|
+
return mapEffortToAnthropicAdaptiveEffort(model, requestedEffort);
|
|
2360
|
+
}
|
|
2361
|
+
function extractClaudeCodeFirstUserMessageText(messages) {
|
|
2362
|
+
for (const message of messages) {
|
|
2363
|
+
if (message.role !== "user") continue;
|
|
2364
|
+
const { content } = message;
|
|
2365
|
+
if (typeof content === "string") return content;
|
|
2366
|
+
if (!Array.isArray(content)) return "";
|
|
2367
|
+
for (const block of content) if (block.type === "text") return block.text;
|
|
2368
|
+
return "";
|
|
2369
|
+
}
|
|
2370
|
+
return "";
|
|
2371
|
+
}
|
|
2372
|
+
function buildParams(model, context, isOAuthToken, options, buildOptions) {
|
|
2373
|
+
const { disableStrictTools, useUmansGatewayWebSearch, forceDemoteUnsignedThinking, supportsEagerToolInputStreaming, fallbacks = options?.fallbacks } = buildOptions;
|
|
2374
|
+
const effectiveModel = forceDemoteUnsignedThinking && model.compat.replayUnsignedThinking ? {
|
|
2375
|
+
...model,
|
|
2376
|
+
compat: {
|
|
2377
|
+
...model.compat,
|
|
2378
|
+
replayUnsignedThinking: false
|
|
2379
|
+
}
|
|
2380
|
+
} : model;
|
|
2381
|
+
const { cacheControl } = getCacheControl(model, options?.cacheRetention);
|
|
2382
|
+
const shouldInjectClaudeCodeInstruction = isOAuthToken && !model.id.startsWith("claude-3-5-haiku");
|
|
2383
|
+
const firstUserMessageText = shouldInjectClaudeCodeInstruction ? extractClaudeCodeFirstUserMessageText(context.messages) : "";
|
|
2384
|
+
const systemBlocks = buildAnthropicSystemBlocks(context.systemPrompt, {
|
|
2385
|
+
includeClaudeCodeInstruction: shouldInjectClaudeCodeInstruction,
|
|
2386
|
+
firstUserMessageText
|
|
2387
|
+
});
|
|
2388
|
+
let tools;
|
|
2389
|
+
if (context.tools) tools = convertTools(context.tools, isOAuthToken, disableStrictTools || model.provider === "github-copilot", supportsEagerToolInputStreaming, model.compat.escapeBuiltinToolNames, useUmansGatewayWebSearch);
|
|
2390
|
+
else if (isOAuthToken) tools = [];
|
|
2391
|
+
const metadataAccountId = readAnthropicMetadataAccountId(options?.metadata);
|
|
2392
|
+
const metadataUserId = resolveAnthropicMetadataUserId(readMetadataString(options?.metadata, "user_id") ?? (model.provider === "kimi-code" ? getOpenAIPromptCacheKey(options) : void 0), isOAuthToken, options?.sessionId, metadataAccountId);
|
|
2393
|
+
const metadata = metadataUserId ? { user_id: metadataUserId } : void 0;
|
|
2394
|
+
let thinking;
|
|
2395
|
+
let outputConfigEffort;
|
|
2396
|
+
if (model.reasoning) {
|
|
2397
|
+
if (options?.thinkingEnabled || model.compat.requiresThinkingEnabled) {
|
|
2398
|
+
const thinkingOptions = options ?? {};
|
|
2399
|
+
const mode = model.thinking?.mode;
|
|
2400
|
+
const effort = resolveAnthropicAdaptiveEffort(model, thinkingOptions);
|
|
2401
|
+
const compat = model.compat;
|
|
2402
|
+
if (mode === "anthropic-adaptive" && !compat.disableAdaptiveThinking) {
|
|
2403
|
+
const adaptive = { type: "adaptive" };
|
|
2404
|
+
if (model.thinking?.supportsDisplay) adaptive.display = thinkingOptions.thinkingDisplay ?? "summarized";
|
|
2405
|
+
thinking = adaptive;
|
|
2406
|
+
if (effort && effort !== "adaptive") outputConfigEffort = effort;
|
|
2407
|
+
} else {
|
|
2408
|
+
thinking = {
|
|
2409
|
+
type: "enabled",
|
|
2410
|
+
budget_tokens: thinkingOptions.thinkingBudgetTokens || 1024,
|
|
2411
|
+
display: thinkingOptions.thinkingDisplay ?? "summarized"
|
|
2412
|
+
};
|
|
2413
|
+
if (mode === "anthropic-budget-effort" && effort && effort !== "adaptive") outputConfigEffort = effort;
|
|
2414
|
+
}
|
|
2415
|
+
} else if (options?.thinkingEnabled === false) {
|
|
2416
|
+
if (isAdaptiveOnlyThinking(model)) outputConfigEffort = "low";
|
|
2417
|
+
else thinking = { type: "disabled" };
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
const contextManagement = !options?.client && model.provider !== "github-copilot" && model.provider !== "google-vertex" && model.provider !== "opencode-zen" && (thinking?.type === "adaptive" || thinking?.type === "enabled") ? { edits: [{
|
|
2421
|
+
type: "clear_thinking_20251015",
|
|
2422
|
+
keep: "all"
|
|
2423
|
+
}] } : void 0;
|
|
2424
|
+
const outputConfigEntries = {};
|
|
2425
|
+
if (outputConfigEffort && model.provider !== "google-vertex") outputConfigEntries.effort = outputConfigEffort;
|
|
2426
|
+
if (options?.taskBudget) outputConfigEntries.task_budget = options.taskBudget;
|
|
2427
|
+
const outputConfig = Object.keys(outputConfigEntries).length ? outputConfigEntries : void 0;
|
|
2428
|
+
const modelMaxTokens = model.maxTokens ?? 64e3;
|
|
2429
|
+
const maxOutputTokens = isOAuthToken ? Math.min(CLAUDE_CODE_MAX_OUTPUT_TOKENS, modelMaxTokens) : modelMaxTokens;
|
|
2430
|
+
const params = {
|
|
2431
|
+
model: options?.requestModelId ?? model.requestModelId ?? model.id,
|
|
2432
|
+
messages: convertAnthropicMessages(context.messages, effectiveModel, isOAuthToken, { serverSideFallbackEnabled: !!fallbacks?.length }),
|
|
2433
|
+
...systemBlocks && { system: systemBlocks },
|
|
2434
|
+
...tools !== void 0 && { tools },
|
|
2435
|
+
...metadata && { metadata },
|
|
2436
|
+
max_tokens: Math.min(maxOutputTokens, options?.maxTokens ?? modelMaxTokens),
|
|
2437
|
+
...thinking && { thinking },
|
|
2438
|
+
...contextManagement && { context_management: contextManagement },
|
|
2439
|
+
...outputConfig && { output_config: outputConfig },
|
|
2440
|
+
...fallbacks?.length ? { fallbacks } : {},
|
|
2441
|
+
stream: true
|
|
2442
|
+
};
|
|
2443
|
+
const thinkingType = params.thinking?.type;
|
|
2444
|
+
const allowSamplingParams = model.compat.supportsSamplingParams && (thinkingType === void 0 || thinkingType === "disabled");
|
|
2445
|
+
if (allowSamplingParams && options?.temperature !== void 0) params.temperature = options.temperature;
|
|
2446
|
+
if (allowSamplingParams && options?.topP !== void 0) params.top_p = options.topP;
|
|
2447
|
+
if (allowSamplingParams && options?.topK !== void 0) params.top_k = options.topK;
|
|
2448
|
+
if (options?.stopSequences?.length) {
|
|
2449
|
+
const seqs = options.stopSequences;
|
|
2450
|
+
if (seqs.length > ANTHROPIC_STOP_SEQUENCES_MAX && !warnedStopSequencesTrim) {
|
|
2451
|
+
warnedStopSequencesTrim = true;
|
|
2452
|
+
warn("anthropic: stop_sequences exceeds 4; extra entries dropped", {
|
|
2453
|
+
received: seqs.length,
|
|
2454
|
+
kept: ANTHROPIC_STOP_SEQUENCES_MAX
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
params.stop_sequences = seqs.length > ANTHROPIC_STOP_SEQUENCES_MAX ? seqs.slice(0, ANTHROPIC_STOP_SEQUENCES_MAX) : seqs;
|
|
2458
|
+
}
|
|
2459
|
+
if (model.provider === "anthropic" && options?.serviceTier === "priority") params.speed = "fast";
|
|
2460
|
+
if (options?.toolChoice) {
|
|
2461
|
+
if (typeof options.toolChoice === "string") params.tool_choice = { type: options.toolChoice };
|
|
2462
|
+
else if (options.toolChoice.name) params.tool_choice = {
|
|
2463
|
+
...options.toolChoice,
|
|
2464
|
+
name: encodeAnthropicToolName(options.toolChoice.name, isOAuthToken, model.compat.escapeBuiltinToolNames, useUmansGatewayWebSearch)
|
|
2465
|
+
};
|
|
2466
|
+
const choiceType = params.tool_choice?.type;
|
|
2467
|
+
if ((choiceType === "any" || choiceType === "tool") && !model.compat.supportsForcedToolChoice) params.tool_choice = { type: "auto" };
|
|
2468
|
+
}
|
|
2469
|
+
disableThinkingIfToolChoiceForced(params, model);
|
|
2470
|
+
ensureMaxTokensForThinking(params, maxOutputTokens);
|
|
2471
|
+
applyPromptCaching(params, cacheControl);
|
|
2472
|
+
return params;
|
|
2473
|
+
}
|
|
2474
|
+
const EMPTY_ERROR_TOOL_RESULT_TEXT = "Tool failed with no output.";
|
|
2475
|
+
function isEmptyToolResultWireContent(content) {
|
|
2476
|
+
if (typeof content === "string") return content.trim().length === 0;
|
|
2477
|
+
return content.length === 0;
|
|
2478
|
+
}
|
|
2479
|
+
function ensureErrorToolResultWireContent(content, isError) {
|
|
2480
|
+
if (!isError || !isEmptyToolResultWireContent(content)) return content;
|
|
2481
|
+
return typeof content === "string" ? EMPTY_ERROR_TOOL_RESULT_TEXT : [{
|
|
2482
|
+
type: "text",
|
|
2483
|
+
text: EMPTY_ERROR_TOOL_RESULT_TEXT
|
|
2484
|
+
}];
|
|
2485
|
+
}
|
|
2486
|
+
function buildToolResultBlock(model, msg, hoistedImages) {
|
|
2487
|
+
let content = convertContentBlocks(msg.content, model.input.includes("image"));
|
|
2488
|
+
if (msg.isError && typeof content !== "string" && content.some((block) => block.type === "image")) {
|
|
2489
|
+
for (const block of content) if (block.type === "image") hoistedImages.push(block);
|
|
2490
|
+
content = content.filter((block) => block.type === "text");
|
|
2491
|
+
}
|
|
2492
|
+
content = ensureErrorToolResultWireContent(content, msg.isError);
|
|
2493
|
+
const block = {
|
|
2494
|
+
type: "tool_result",
|
|
2495
|
+
tool_use_id: msg.toolCallId,
|
|
2496
|
+
content,
|
|
2497
|
+
is_error: msg.isError
|
|
2498
|
+
};
|
|
2499
|
+
if (model.compat.requiresToolResultId) block.id = msg.toolCallId;
|
|
2500
|
+
return block;
|
|
2501
|
+
}
|
|
2502
|
+
/**
|
|
2503
|
+
* Recursively replace lone surrogates in string leaves. Identity-preserving:
|
|
2504
|
+
* returns the input object/array when nothing changed.
|
|
2505
|
+
*/
|
|
2506
|
+
function toWellFormedDeep(value) {
|
|
2507
|
+
if (typeof value === "string") {
|
|
2508
|
+
const wellFormed = value.toWellFormed();
|
|
2509
|
+
return wellFormed === value ? value : wellFormed;
|
|
2510
|
+
}
|
|
2511
|
+
if (Array.isArray(value)) {
|
|
2512
|
+
let changed = false;
|
|
2513
|
+
const next = value.map((entry) => {
|
|
2514
|
+
const sanitized = toWellFormedDeep(entry);
|
|
2515
|
+
if (sanitized !== entry) changed = true;
|
|
2516
|
+
return sanitized;
|
|
2517
|
+
});
|
|
2518
|
+
return changed ? next : value;
|
|
2519
|
+
}
|
|
2520
|
+
if (isRecord(value)) {
|
|
2521
|
+
let changed = false;
|
|
2522
|
+
const next = {};
|
|
2523
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2524
|
+
const sanitized = toWellFormedDeep(entry);
|
|
2525
|
+
if (sanitized !== entry) changed = true;
|
|
2526
|
+
next[key] = sanitized;
|
|
2527
|
+
}
|
|
2528
|
+
return changed ? next : value;
|
|
2529
|
+
}
|
|
2530
|
+
return value;
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Serialize omp {@link Message}s to Anthropic wire messages.
|
|
2534
|
+
*
|
|
2535
|
+
* `opts.serverSideFallbackEnabled` — when the CURRENT request itself
|
|
2536
|
+
* opts into the server-side-fallback beta chain. Only then may a persisted
|
|
2537
|
+
* `fallback` content block from a prior turn be replayed on the wire;
|
|
2538
|
+
* otherwise the block is dropped to avoid a 400 on non-fallback requests
|
|
2539
|
+
* that don't send the beta.
|
|
2540
|
+
*/
|
|
2541
|
+
function convertAnthropicMessages(messages, model, isOAuthToken, opts) {
|
|
2542
|
+
const developerParamIndices = [];
|
|
2543
|
+
const params = [];
|
|
2544
|
+
const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
|
|
2545
|
+
for (let i = 0; i < transformedMessages.length; i++) {
|
|
2546
|
+
const msg = transformedMessages[i];
|
|
2547
|
+
if (msg.role === "user" || msg.role === "developer") {
|
|
2548
|
+
if (!msg.content) continue;
|
|
2549
|
+
let content;
|
|
2550
|
+
if (typeof msg.content === "string") {
|
|
2551
|
+
if (msg.content.trim().length === 0) continue;
|
|
2552
|
+
content = msg.content.toWellFormed();
|
|
2553
|
+
} else {
|
|
2554
|
+
const contentBlocks = convertContentBlocks(msg.content, model.input.includes("image"));
|
|
2555
|
+
if (typeof contentBlocks === "string") {
|
|
2556
|
+
if (contentBlocks.trim().length === 0) continue;
|
|
2557
|
+
content = contentBlocks;
|
|
2558
|
+
} else {
|
|
2559
|
+
if (contentBlocks.length === 0) continue;
|
|
2560
|
+
content = contentBlocks;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
if (msg.role === "developer") developerParamIndices.push(params.length);
|
|
2564
|
+
params.push({
|
|
2565
|
+
role: "user",
|
|
2566
|
+
content
|
|
2567
|
+
});
|
|
2568
|
+
} else if (msg.role === "assistant") {
|
|
2569
|
+
const blocks = [];
|
|
2570
|
+
const hasSignedThinking = msg.content.some((block) => block.type === "thinking" && !!block.thinkingSignature && block.thinkingSignature.trim().length > 0);
|
|
2571
|
+
for (const block of msg.content) if (block.type === "text") {
|
|
2572
|
+
if (block.text.trim().length === 0) continue;
|
|
2573
|
+
blocks.push({
|
|
2574
|
+
type: "text",
|
|
2575
|
+
text: block.text.toWellFormed()
|
|
2576
|
+
});
|
|
2577
|
+
} else if (block.type === "thinking") {
|
|
2578
|
+
if (hasSignedThinking) {
|
|
2579
|
+
if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
|
|
2580
|
+
if (block.thinking.trim().length === 0) continue;
|
|
2581
|
+
blocks.push({
|
|
2582
|
+
type: "text",
|
|
2583
|
+
text: renderDemotedThinking(model.id, block.thinking)
|
|
2584
|
+
});
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
blocks.push({
|
|
2588
|
+
type: "thinking",
|
|
2589
|
+
thinking: block.thinking,
|
|
2590
|
+
signature: block.thinkingSignature
|
|
2591
|
+
});
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
if (block.thinking.trim().length === 0) continue;
|
|
2595
|
+
if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
|
|
2596
|
+
if (model.compat.replayUnsignedThinking) blocks.push({
|
|
2597
|
+
type: "thinking",
|
|
2598
|
+
thinking: block.thinking.toWellFormed(),
|
|
2599
|
+
signature: ""
|
|
2600
|
+
});
|
|
2601
|
+
else blocks.push({
|
|
2602
|
+
type: "text",
|
|
2603
|
+
text: renderDemotedThinking(model.id, block.thinking)
|
|
2604
|
+
});
|
|
2605
|
+
} else blocks.push({
|
|
2606
|
+
type: "thinking",
|
|
2607
|
+
thinking: block.thinking.toWellFormed(),
|
|
2608
|
+
signature: block.thinkingSignature
|
|
2609
|
+
});
|
|
2610
|
+
} else if (block.type === "redactedThinking") {
|
|
2611
|
+
if (block.data.trim().length === 0) continue;
|
|
2612
|
+
blocks.push({
|
|
2613
|
+
type: "redacted_thinking",
|
|
2614
|
+
data: block.data
|
|
2615
|
+
});
|
|
2616
|
+
} else if (block.type === "anthropicServerTool") blocks.push(block.block);
|
|
2617
|
+
else if (block.type === "fallback") {
|
|
2618
|
+
if (!opts?.serverSideFallbackEnabled || !model.compat.officialEndpoint) continue;
|
|
2619
|
+
blocks.push({
|
|
2620
|
+
type: "fallback",
|
|
2621
|
+
from: block.from,
|
|
2622
|
+
to: block.to
|
|
2623
|
+
});
|
|
2624
|
+
} else if (block.type === "toolCall") blocks.push({
|
|
2625
|
+
type: "tool_use",
|
|
2626
|
+
id: block.id,
|
|
2627
|
+
name: encodeAnthropicToolName(block.name, isOAuthToken, model.compat.escapeBuiltinToolNames),
|
|
2628
|
+
input: toWellFormedDeep(block.arguments ?? {})
|
|
2629
|
+
});
|
|
2630
|
+
let sawToolUse = false;
|
|
2631
|
+
let needsPartition = false;
|
|
2632
|
+
for (const block of blocks) if (block.type === "tool_use") sawToolUse = true;
|
|
2633
|
+
else if (sawToolUse) {
|
|
2634
|
+
needsPartition = true;
|
|
2635
|
+
break;
|
|
2636
|
+
}
|
|
2637
|
+
if (needsPartition) {
|
|
2638
|
+
const nonToolUse = [];
|
|
2639
|
+
const toolUse = [];
|
|
2640
|
+
for (const block of blocks) if (block.type === "tool_use") toolUse.push(block);
|
|
2641
|
+
else nonToolUse.push(block);
|
|
2642
|
+
blocks.length = 0;
|
|
2643
|
+
blocks.push(...nonToolUse, ...toolUse);
|
|
2644
|
+
}
|
|
2645
|
+
if (blocks.length === 0) continue;
|
|
2646
|
+
params.push({
|
|
2647
|
+
role: "assistant",
|
|
2648
|
+
content: blocks
|
|
2649
|
+
});
|
|
2650
|
+
} else if (msg.role === "toolResult") {
|
|
2651
|
+
const toolResults = [];
|
|
2652
|
+
const hoistedImages = [];
|
|
2653
|
+
toolResults.push(buildToolResultBlock(model, msg, hoistedImages));
|
|
2654
|
+
let j = i + 1;
|
|
2655
|
+
while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
|
|
2656
|
+
const nextMsg = transformedMessages[j];
|
|
2657
|
+
toolResults.push(buildToolResultBlock(model, nextMsg, hoistedImages));
|
|
2658
|
+
j++;
|
|
2659
|
+
}
|
|
2660
|
+
i = j - 1;
|
|
2661
|
+
if (hoistedImages.length > 0) toolResults.push({
|
|
2662
|
+
type: "text",
|
|
2663
|
+
text: "Attached image(s) from the tool result(s) above:"
|
|
2664
|
+
}, ...hoistedImages);
|
|
2665
|
+
params.push({
|
|
2666
|
+
role: "user",
|
|
2667
|
+
content: toolResults
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
if (developerParamIndices.length > 0 && model.compat.supportsMidConversationSystem) for (const idx of developerParamIndices) {
|
|
2672
|
+
const followsUser = idx > 0 && params[idx - 1]?.role === "user";
|
|
2673
|
+
const next = params[idx + 1];
|
|
2674
|
+
const lastOrBeforeAssistant = idx === params.length - 1 || next?.role === "assistant";
|
|
2675
|
+
const content = params[idx].content;
|
|
2676
|
+
const textOnly = typeof content === "string" || content.every((block) => block.type === "text");
|
|
2677
|
+
if (followsUser && lastOrBeforeAssistant && textOnly) params[idx] = {
|
|
2678
|
+
role: "system",
|
|
2679
|
+
content
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
for (let i = params.length - 1; i > 0; i--) if (params[i].role === "assistant" && params[i - 1]?.role === "assistant") params.splice(i, 0, {
|
|
2683
|
+
role: "user",
|
|
2684
|
+
content: "Continue."
|
|
2685
|
+
});
|
|
2686
|
+
if (params.length > 0 && params[params.length - 1]?.role === "assistant") params.push({
|
|
2687
|
+
role: "user",
|
|
2688
|
+
content: "Continue."
|
|
2689
|
+
});
|
|
2690
|
+
return params;
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2693
|
+
* JSON Schema whitelist for Anthropic tool `input_schema` nodes.
|
|
2694
|
+
*
|
|
2695
|
+
* Tracks the Anthropic Python SDK's `lib/_parse/_transform.py::transform_schema`,
|
|
2696
|
+
* with live Messages API guardrails for keywords the SDK preserves but the API rejects.
|
|
2697
|
+
* We keep only structural/metadata keywords Anthropic's validator honors, and demote
|
|
2698
|
+
* anything else into the node's `description` as `\n\n{key: value, ...}` so the model
|
|
2699
|
+
* still sees the constraint as a natural-language hint.
|
|
2700
|
+
*
|
|
2701
|
+
* `Set` (not `Record<string, true>`) because membership is probed against arbitrary
|
|
2702
|
+
* user/Zod-derived schema keys: a literal Record would falsely match prototype names
|
|
2703
|
+
* like `"toString"` and silently strip valid properties.
|
|
2704
|
+
*/
|
|
2705
|
+
const ANTHROPIC_TOOL_SCHEMA_UNIVERSAL_KEEP = /* @__PURE__ */ new Set([
|
|
2706
|
+
"$ref",
|
|
2707
|
+
"$defs",
|
|
2708
|
+
"$schema",
|
|
2709
|
+
"definitions",
|
|
2710
|
+
"type",
|
|
2711
|
+
"anyOf",
|
|
2712
|
+
"allOf",
|
|
2713
|
+
"enum",
|
|
2714
|
+
"const",
|
|
2715
|
+
"description",
|
|
2716
|
+
"title",
|
|
2717
|
+
"default",
|
|
2718
|
+
"nullable"
|
|
2719
|
+
]);
|
|
2720
|
+
/** Keys preserved on `type: "object"` nodes (in addition to the universal set). */
|
|
2721
|
+
const ANTHROPIC_TOOL_SCHEMA_OBJECT_KEEP = /* @__PURE__ */ new Set([
|
|
2722
|
+
"properties",
|
|
2723
|
+
"required",
|
|
2724
|
+
"additionalProperties"
|
|
2725
|
+
]);
|
|
2726
|
+
/** Keys preserved on `type: "array"` nodes; `minItems` only when its value is 0 or 1. */
|
|
2727
|
+
const ANTHROPIC_TOOL_SCHEMA_ARRAY_KEEP = /* @__PURE__ */ new Set([
|
|
2728
|
+
"items",
|
|
2729
|
+
"prefixItems",
|
|
2730
|
+
"minItems"
|
|
2731
|
+
]);
|
|
2732
|
+
/** Keys preserved on `type: "string"` nodes; `format` only when its value is in the supported list. */
|
|
2733
|
+
const ANTHROPIC_TOOL_SCHEMA_STRING_KEEP = /* @__PURE__ */ new Set(["format"]);
|
|
2734
|
+
/**
|
|
2735
|
+
* String `format` values Anthropic accepts; everything else (including `pattern`-style
|
|
2736
|
+
* format hints) gets demoted into `description`. Matches `SupportedStringFormats` in the
|
|
2737
|
+
* Anthropic SDK's `_transform.py`.
|
|
2738
|
+
*/
|
|
2739
|
+
const ANTHROPIC_TOOL_SCHEMA_STRING_FORMATS = /* @__PURE__ */ new Set([
|
|
2740
|
+
"date-time",
|
|
2741
|
+
"time",
|
|
2742
|
+
"date",
|
|
2743
|
+
"duration",
|
|
2744
|
+
"email",
|
|
2745
|
+
"hostname",
|
|
2746
|
+
"uri",
|
|
2747
|
+
"ipv4",
|
|
2748
|
+
"ipv6",
|
|
2749
|
+
"uuid"
|
|
2750
|
+
]);
|
|
2751
|
+
const ANTHROPIC_STRICT_TOOL_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
2752
|
+
"bash",
|
|
2753
|
+
"python",
|
|
2754
|
+
"edit",
|
|
2755
|
+
"find"
|
|
2756
|
+
]);
|
|
2757
|
+
const MAX_ANTHROPIC_STRICT_TOOLS = 20;
|
|
2758
|
+
const MAX_ANTHROPIC_STRICT_OPTIONAL_PARAMETERS = 24;
|
|
2759
|
+
const MAX_ANTHROPIC_STRICT_UNION_PARAMETERS = 16;
|
|
2760
|
+
/** `minItems` / `maxItems` apply to arrays; Anthropic rejects them on `type: "object"` (including `minItems: 0`/`1`). */
|
|
2761
|
+
function isJsonSchemaArrayNode(schema) {
|
|
2762
|
+
const t = schema.type;
|
|
2763
|
+
if (t === "array") return true;
|
|
2764
|
+
if (Array.isArray(t) && t.includes("array") && !t.includes("object")) return true;
|
|
2765
|
+
if (schema.items !== void 0 || Array.isArray(schema.prefixItems)) return true;
|
|
2766
|
+
return false;
|
|
2767
|
+
}
|
|
2768
|
+
function isJsonSchemaObjectNode(schema) {
|
|
2769
|
+
if (isJsonSchemaArrayNode(schema)) return false;
|
|
2770
|
+
if (schema.type === "object") return true;
|
|
2771
|
+
if (Array.isArray(schema.type) && schema.type.includes("object")) return true;
|
|
2772
|
+
if (isRecord(schema.properties)) return true;
|
|
2773
|
+
return false;
|
|
2774
|
+
}
|
|
2775
|
+
/**
|
|
2776
|
+
* Pick the principal non-null scalar type from a `type` keyword. Anthropic accepts
|
|
2777
|
+
* `type` as either a single string or an array (e.g. `["number", "null"]` for a
|
|
2778
|
+
* nullable value); the SDK whitelist is keyed off the scalar type, with `"null"`
|
|
2779
|
+
* ignored so nullable variants are normalized as their underlying type.
|
|
2780
|
+
*/
|
|
2781
|
+
function pickAnthropicScalarType(type) {
|
|
2782
|
+
if (typeof type === "string") return type;
|
|
2783
|
+
if (Array.isArray(type)) {
|
|
2784
|
+
for (const entry of type) if (typeof entry === "string" && entry !== "null") return entry;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
function pickAnthropicEffectiveScalarType(schema) {
|
|
2788
|
+
const explicit = pickAnthropicScalarType(schema.type);
|
|
2789
|
+
if (explicit) return explicit;
|
|
2790
|
+
if (isRecord(schema.properties)) return "object";
|
|
2791
|
+
if (schema.items !== void 0 || Array.isArray(schema.prefixItems)) return "array";
|
|
2792
|
+
}
|
|
2793
|
+
function anthropicPerTypeKeep(scalarType) {
|
|
2794
|
+
switch (scalarType) {
|
|
2795
|
+
case "object": return ANTHROPIC_TOOL_SCHEMA_OBJECT_KEEP;
|
|
2796
|
+
case "array": return ANTHROPIC_TOOL_SCHEMA_ARRAY_KEEP;
|
|
2797
|
+
case "string": return ANTHROPIC_TOOL_SCHEMA_STRING_KEEP;
|
|
2798
|
+
default: return;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Normalize a JSON Schema node for Anthropic tool `input_schema`.
|
|
2803
|
+
*
|
|
2804
|
+
* Applies the full whitelist semantics from the Anthropic Python SDK's
|
|
2805
|
+
* `lib/_parse/_transform.py::transform_schema`:
|
|
2806
|
+
*
|
|
2807
|
+
* 1. Universal keys (`$ref`, `$defs`, `type`, `anyOf`, `allOf`, `enum`, `const`,
|
|
2808
|
+
* `description`, `title`, `default`, `nullable`) are preserved on every node, with
|
|
2809
|
+
* one position-dependent exception: the combinator keys. Root `anyOf`/`allOf` are
|
|
2810
|
+
* spilled (recent Anthropic Messages validators reject combinators at the tool
|
|
2811
|
+
* `input_schema` root) but kept when nested; `oneOf` is spilled at every position
|
|
2812
|
+
* (it is not in the documented supported subset).
|
|
2813
|
+
* 2. Per-type keys are kept additively (object → `properties`/`required`/`additionalProperties`,
|
|
2814
|
+
* array → `items`/`prefixItems` plus `minItems` only when 0 or 1, string → `format`
|
|
2815
|
+
* only when in the supported value set).
|
|
2816
|
+
* 3. Everything else is demoted into the node's `description` as `\n\n{key: value, ...}`
|
|
2817
|
+
*
|
|
2818
|
+
* Object nodes default to `additionalProperties: false`, but explicit open-map
|
|
2819
|
+
* declarations (`additionalProperties: true` or a schema literal — Zod's
|
|
2820
|
+
* `z.record(z.string(), z.unknown())` produces `{}`) are preserved. The strict-mode
|
|
2821
|
+
* pass downstream demotes those shapes to non-strict instead of fabricating a closed
|
|
2822
|
+
* object, so callers like the resolve tool keep working open-map semantics.
|
|
2823
|
+
*/
|
|
2824
|
+
function normalizeAnthropicToolSchemaNode(schema, cache, isRoot = false) {
|
|
2825
|
+
if (Array.isArray(schema)) return schema.map((entry) => normalizeAnthropicToolSchemaNode(entry, cache));
|
|
2826
|
+
if (!isRecord(schema)) return schema;
|
|
2827
|
+
const existing = cache.get(schema);
|
|
2828
|
+
if (existing !== void 0) return existing;
|
|
2829
|
+
const result = {};
|
|
2830
|
+
cache.set(schema, result);
|
|
2831
|
+
const scalarType = pickAnthropicEffectiveScalarType(schema);
|
|
2832
|
+
const perTypeKeep = anthropicPerTypeKeep(scalarType);
|
|
2833
|
+
const spill = [];
|
|
2834
|
+
for (const key in schema) {
|
|
2835
|
+
if (!Object.hasOwn(schema, key)) continue;
|
|
2836
|
+
const value = schema[key];
|
|
2837
|
+
if (!(isRoot && COMBINATOR_KEYS.includes(key)) && (ANTHROPIC_TOOL_SCHEMA_UNIVERSAL_KEEP.has(key) || perTypeKeep?.has(key))) result[key] = value;
|
|
2838
|
+
else spill.push([key, value]);
|
|
2839
|
+
}
|
|
2840
|
+
if (scalarType === "string") {
|
|
2841
|
+
const format = result.format;
|
|
2842
|
+
if (typeof format === "string" && !ANTHROPIC_TOOL_SCHEMA_STRING_FORMATS.has(format)) {
|
|
2843
|
+
spill.push(["format", format]);
|
|
2844
|
+
delete result.format;
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
if (scalarType === "array" && result.minItems !== void 0) {
|
|
2848
|
+
const minItems = result.minItems;
|
|
2849
|
+
if (!(typeof minItems === "number" && (minItems === 0 || minItems === 1))) {
|
|
2850
|
+
spill.push(["minItems", minItems]);
|
|
2851
|
+
delete result.minItems;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
if (scalarType === "object" && result.additionalProperties === void 0) result.additionalProperties = false;
|
|
2855
|
+
if (isRecord(result.properties)) {
|
|
2856
|
+
const normalizedProperties = {};
|
|
2857
|
+
const sourceProperties = result.properties;
|
|
2858
|
+
for (const propName in sourceProperties) {
|
|
2859
|
+
if (!Object.hasOwn(sourceProperties, propName)) continue;
|
|
2860
|
+
normalizedProperties[propName] = normalizeAnthropicToolSchemaNode(sourceProperties[propName], cache);
|
|
2861
|
+
}
|
|
2862
|
+
result.properties = normalizedProperties;
|
|
2863
|
+
}
|
|
2864
|
+
if (isRecord(result.additionalProperties)) {
|
|
2865
|
+
const normalized = normalizeAnthropicToolSchemaNode(result.additionalProperties, cache);
|
|
2866
|
+
if (isRecord(normalized) && Object.keys(normalized).length === 0) result.additionalProperties = true;
|
|
2867
|
+
else result.additionalProperties = normalized;
|
|
2868
|
+
}
|
|
2869
|
+
if (Array.isArray(result.items)) result.items = result.items.map((item) => normalizeAnthropicToolSchemaNode(item, cache));
|
|
2870
|
+
else if (isRecord(result.items)) result.items = normalizeAnthropicToolSchemaNode(result.items, cache);
|
|
2871
|
+
if (Array.isArray(result.prefixItems)) result.prefixItems = result.prefixItems.map((item) => normalizeAnthropicToolSchemaNode(item, cache));
|
|
2872
|
+
for (const key of COMBINATOR_KEYS) {
|
|
2873
|
+
const variants = result[key];
|
|
2874
|
+
if (Array.isArray(variants)) result[key] = variants.map((variant) => normalizeAnthropicToolSchemaNode(variant, cache));
|
|
2875
|
+
}
|
|
2876
|
+
for (const defsKey of ["$defs", "definitions"]) {
|
|
2877
|
+
const definitions = result[defsKey];
|
|
2878
|
+
if (!isRecord(definitions)) continue;
|
|
2879
|
+
const normalizedDefs = {};
|
|
2880
|
+
const sourceDefs = definitions;
|
|
2881
|
+
for (const name in sourceDefs) {
|
|
2882
|
+
if (!Object.hasOwn(sourceDefs, name)) continue;
|
|
2883
|
+
normalizedDefs[name] = normalizeAnthropicToolSchemaNode(sourceDefs[name], cache);
|
|
2884
|
+
}
|
|
2885
|
+
result[defsKey] = normalizedDefs;
|
|
2886
|
+
}
|
|
2887
|
+
spillToDescription(result, spill);
|
|
2888
|
+
return result;
|
|
2889
|
+
}
|
|
2890
|
+
function normalizeAnthropicToolSchema(schema) {
|
|
2891
|
+
return normalizeAnthropicToolSchemaNode(schema, /* @__PURE__ */ new WeakMap(), true);
|
|
2892
|
+
}
|
|
2893
|
+
function hasAnthropicUnionType(schema) {
|
|
2894
|
+
return Array.isArray(schema.type) || Array.isArray(schema.anyOf);
|
|
2895
|
+
}
|
|
2896
|
+
function hasNullVariant(schema) {
|
|
2897
|
+
if (Array.isArray(schema.type) && schema.type.includes("null")) return true;
|
|
2898
|
+
return Array.isArray(schema.anyOf) && schema.anyOf.some((variant) => isRecord(variant) && variant.type === "null");
|
|
2899
|
+
}
|
|
2900
|
+
function hasAnthropicSchemaDefiningKeyword(schema) {
|
|
2901
|
+
if (schema.type !== void 0 || schema.properties !== void 0 || schema.additionalProperties !== void 0 || schema.items !== void 0 || schema.prefixItems !== void 0 || schema.enum !== void 0 || schema.const !== void 0 || schema.$ref !== void 0) return true;
|
|
2902
|
+
for (const key of COMBINATOR_KEYS) if (schema[key] !== void 0) return true;
|
|
2903
|
+
return schema.$defs !== void 0 || schema.definitions !== void 0;
|
|
2904
|
+
}
|
|
2905
|
+
function makeAnthropicNullableSchema(schema, budget) {
|
|
2906
|
+
if (isRecord(schema)) {
|
|
2907
|
+
if (hasNullVariant(schema)) return schema;
|
|
2908
|
+
if (Array.isArray(schema.anyOf)) return {
|
|
2909
|
+
...schema,
|
|
2910
|
+
anyOf: [...schema.anyOf, { type: "null" }]
|
|
2911
|
+
};
|
|
2912
|
+
if (Array.isArray(schema.type)) return {
|
|
2913
|
+
...schema,
|
|
2914
|
+
type: [...schema.type, "null"]
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
if (budget.unionRemaining <= 0) return void 0;
|
|
2918
|
+
budget.unionRemaining--;
|
|
2919
|
+
budget.unionCount++;
|
|
2920
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
2921
|
+
}
|
|
2922
|
+
function normalizeAnthropicStrictSchemaNode(schema, budget, cache) {
|
|
2923
|
+
if (Array.isArray(schema)) {
|
|
2924
|
+
const result = [];
|
|
2925
|
+
for (const entry of schema) {
|
|
2926
|
+
const normalized = normalizeAnthropicStrictSchemaNode(entry, budget, cache);
|
|
2927
|
+
if (normalized === void 0) return void 0;
|
|
2928
|
+
result.push(normalized);
|
|
2929
|
+
}
|
|
2930
|
+
return result;
|
|
2931
|
+
}
|
|
2932
|
+
if (!isRecord(schema)) return schema;
|
|
2933
|
+
const cached = cache.get(schema);
|
|
2934
|
+
if (cached) return cached;
|
|
2935
|
+
if (!hasAnthropicSchemaDefiningKeyword(schema)) return void 0;
|
|
2936
|
+
if (isJsonSchemaObjectNode(schema) && schema.additionalProperties !== false) return;
|
|
2937
|
+
const result = { ...schema };
|
|
2938
|
+
cache.set(schema, result);
|
|
2939
|
+
if (hasAnthropicUnionType(result)) {
|
|
2940
|
+
if (budget.unionRemaining <= 0) return void 0;
|
|
2941
|
+
budget.unionRemaining--;
|
|
2942
|
+
budget.unionCount++;
|
|
2943
|
+
}
|
|
2944
|
+
if (isRecord(result.properties)) {
|
|
2945
|
+
const originalRequired = new Set(Array.isArray(result.required) ? result.required.filter((entry) => typeof entry === "string") : []);
|
|
2946
|
+
const properties = {};
|
|
2947
|
+
const required = [];
|
|
2948
|
+
for (const [propertyName, propertySchema] of Object.entries(result.properties)) {
|
|
2949
|
+
const normalizedProperty = normalizeAnthropicStrictSchemaNode(propertySchema, budget, cache);
|
|
2950
|
+
if (normalizedProperty === void 0) return void 0;
|
|
2951
|
+
if (originalRequired.has(propertyName)) {
|
|
2952
|
+
properties[propertyName] = normalizedProperty;
|
|
2953
|
+
required.push(propertyName);
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
if (budget.optionalRemaining > 0) {
|
|
2957
|
+
budget.optionalRemaining--;
|
|
2958
|
+
budget.optionalCount++;
|
|
2959
|
+
properties[propertyName] = normalizedProperty;
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2962
|
+
const nullableProperty = makeAnthropicNullableSchema(normalizedProperty, budget);
|
|
2963
|
+
if (nullableProperty === void 0) return void 0;
|
|
2964
|
+
properties[propertyName] = nullableProperty;
|
|
2965
|
+
required.push(propertyName);
|
|
2966
|
+
}
|
|
2967
|
+
result.properties = properties;
|
|
2968
|
+
result.required = required;
|
|
2969
|
+
}
|
|
2970
|
+
if (Array.isArray(result.items)) {
|
|
2971
|
+
const items = normalizeAnthropicStrictSchemaNode(result.items, budget, cache);
|
|
2972
|
+
if (items === void 0) return void 0;
|
|
2973
|
+
result.items = items;
|
|
2974
|
+
} else if (isRecord(result.items)) {
|
|
2975
|
+
const items = normalizeAnthropicStrictSchemaNode(result.items, budget, cache);
|
|
2976
|
+
if (items === void 0) return void 0;
|
|
2977
|
+
result.items = items;
|
|
2978
|
+
}
|
|
2979
|
+
if (Array.isArray(result.prefixItems)) {
|
|
2980
|
+
const prefixItems = normalizeAnthropicStrictSchemaNode(result.prefixItems, budget, cache);
|
|
2981
|
+
if (prefixItems === void 0) return void 0;
|
|
2982
|
+
result.prefixItems = prefixItems;
|
|
2983
|
+
}
|
|
2984
|
+
for (const key of COMBINATOR_KEYS) {
|
|
2985
|
+
const variants = result[key];
|
|
2986
|
+
if (!Array.isArray(variants)) continue;
|
|
2987
|
+
const normalizedVariants = normalizeAnthropicStrictSchemaNode(variants, budget, cache);
|
|
2988
|
+
if (normalizedVariants === void 0) return void 0;
|
|
2989
|
+
result[key] = normalizedVariants;
|
|
2990
|
+
}
|
|
2991
|
+
for (const defsKey of ["$defs", "definitions"]) {
|
|
2992
|
+
const definitions = result[defsKey];
|
|
2993
|
+
if (!isRecord(definitions)) continue;
|
|
2994
|
+
const normalizedDefinitions = {};
|
|
2995
|
+
for (const [definitionName, definitionSchema] of Object.entries(definitions)) {
|
|
2996
|
+
const normalizedDefinition = normalizeAnthropicStrictSchemaNode(definitionSchema, budget, cache);
|
|
2997
|
+
if (normalizedDefinition === void 0) return void 0;
|
|
2998
|
+
normalizedDefinitions[definitionName] = normalizedDefinition;
|
|
2999
|
+
}
|
|
3000
|
+
result[defsKey] = normalizedDefinitions;
|
|
3001
|
+
}
|
|
3002
|
+
return result;
|
|
3003
|
+
}
|
|
3004
|
+
const ANTHROPIC_STRICT_INCOMPATIBLE_KEYWORDS = [
|
|
3005
|
+
"oneOf",
|
|
3006
|
+
"allOf",
|
|
3007
|
+
"$ref",
|
|
3008
|
+
"patternProperties",
|
|
3009
|
+
"propertyNames"
|
|
3010
|
+
];
|
|
3011
|
+
/**
|
|
3012
|
+
* Anthropic's strict grammar subset supports anyOf/type-array unions only.
|
|
3013
|
+
* oneOf/allOf/$ref compile unpredictably (rejections arrive as 400s the
|
|
3014
|
+
* grammar-too-large fallback does not recognize, so they would hard-fail the
|
|
3015
|
+
* turn), and patternProperties/propertyNames describe open key sets that the
|
|
3016
|
+
* strict pipeline's injected `additionalProperties: false` would contradict.
|
|
3017
|
+
* Runs against the raw wire schema — the base normalizer spills several of
|
|
3018
|
+
* these keywords into the description, erasing the evidence.
|
|
3019
|
+
*/
|
|
3020
|
+
function hasAnthropicStrictIncompatibleKeyword(schema, seen = /* @__PURE__ */ new Set()) {
|
|
3021
|
+
if (Array.isArray(schema)) {
|
|
3022
|
+
if (seen.has(schema)) return false;
|
|
3023
|
+
seen.add(schema);
|
|
3024
|
+
return schema.some((entry) => hasAnthropicStrictIncompatibleKeyword(entry, seen));
|
|
3025
|
+
}
|
|
3026
|
+
if (!isRecord(schema)) return false;
|
|
3027
|
+
if (seen.has(schema)) return false;
|
|
3028
|
+
seen.add(schema);
|
|
3029
|
+
for (const keyword of ANTHROPIC_STRICT_INCOMPATIBLE_KEYWORDS) if (schema[keyword] !== void 0) return true;
|
|
3030
|
+
return Object.values(schema).some((value) => hasAnthropicStrictIncompatibleKeyword(value, seen));
|
|
3031
|
+
}
|
|
3032
|
+
function normalizeAnthropicStrictSchema(schema, optionalRemaining, unionRemaining) {
|
|
3033
|
+
const budget = {
|
|
3034
|
+
optionalRemaining,
|
|
3035
|
+
unionRemaining,
|
|
3036
|
+
optionalCount: 0,
|
|
3037
|
+
unionCount: 0
|
|
3038
|
+
};
|
|
3039
|
+
const normalized = normalizeAnthropicStrictSchemaNode(schema, budget, /* @__PURE__ */ new WeakMap());
|
|
3040
|
+
if (!isRecord(normalized)) return void 0;
|
|
3041
|
+
return {
|
|
3042
|
+
schema: normalized,
|
|
3043
|
+
optionalCount: budget.optionalCount,
|
|
3044
|
+
unionCount: budget.unionCount
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
function buildAnthropicBaseToolInputSchema(tool) {
|
|
3048
|
+
const jsonSchema = toolWireSchema(tool);
|
|
3049
|
+
return normalizeAnthropicToolSchema({
|
|
3050
|
+
...jsonSchema,
|
|
3051
|
+
type: "object",
|
|
3052
|
+
properties: isRecord(jsonSchema.properties) ? jsonSchema.properties : {},
|
|
3053
|
+
required: Array.isArray(jsonSchema.required) ? jsonSchema.required.filter((entry) => typeof entry === "string") : []
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
function buildAnthropicToolSchemaPlans(tools, disableStrictTools = false) {
|
|
3057
|
+
const plans = tools.map((tool) => ({
|
|
3058
|
+
inputSchema: buildAnthropicBaseToolInputSchema(tool),
|
|
3059
|
+
strict: false
|
|
3060
|
+
}));
|
|
3061
|
+
if (NO_STRICT || disableStrictTools) return plans;
|
|
3062
|
+
const candidateIndexes = tools.flatMap((tool, index) => {
|
|
3063
|
+
if (!ANTHROPIC_STRICT_TOOL_ALLOWLIST.has(tool.name)) return [];
|
|
3064
|
+
if (tool.strict === false) return [];
|
|
3065
|
+
if (hasAnthropicStrictIncompatibleKeyword(toolWireSchema(tool))) return [];
|
|
3066
|
+
return [index];
|
|
3067
|
+
});
|
|
3068
|
+
let strictToolCount = 0;
|
|
3069
|
+
let strictOptionalParameterCount = 0;
|
|
3070
|
+
let strictUnionParameterCount = 0;
|
|
3071
|
+
for (const index of candidateIndexes) {
|
|
3072
|
+
if (strictToolCount >= MAX_ANTHROPIC_STRICT_TOOLS) break;
|
|
3073
|
+
const strictResult = normalizeAnthropicStrictSchema(plans[index].inputSchema, MAX_ANTHROPIC_STRICT_OPTIONAL_PARAMETERS - strictOptionalParameterCount, MAX_ANTHROPIC_STRICT_UNION_PARAMETERS - strictUnionParameterCount);
|
|
3074
|
+
if (!strictResult) continue;
|
|
3075
|
+
plans[index] = {
|
|
3076
|
+
inputSchema: strictResult.schema,
|
|
3077
|
+
strict: true
|
|
3078
|
+
};
|
|
3079
|
+
strictToolCount++;
|
|
3080
|
+
strictOptionalParameterCount += strictResult.optionalCount;
|
|
3081
|
+
strictUnionParameterCount += strictResult.unionCount;
|
|
3082
|
+
}
|
|
3083
|
+
return plans;
|
|
3084
|
+
}
|
|
3085
|
+
function convertTools(tools, isOAuthToken, disableStrictTools = false, supportsEagerToolInputStreaming = true, escapeBuiltinToolNames = false, useUmansGatewayWebSearch = false) {
|
|
3086
|
+
if (!tools) return [];
|
|
3087
|
+
const schemaPlans = buildAnthropicToolSchemaPlans(tools, disableStrictTools);
|
|
3088
|
+
return tools.map((tool, index) => {
|
|
3089
|
+
const plan = schemaPlans[index];
|
|
3090
|
+
return {
|
|
3091
|
+
name: encodeAnthropicToolName(tool.name, isOAuthToken, escapeBuiltinToolNames, useUmansGatewayWebSearch),
|
|
3092
|
+
description: tool.description || "",
|
|
3093
|
+
input_schema: plan.inputSchema,
|
|
3094
|
+
...supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {},
|
|
3095
|
+
...plan.strict ? { strict: true } : {}
|
|
3096
|
+
};
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
function mapStopReason(reason) {
|
|
3100
|
+
switch (reason) {
|
|
3101
|
+
case "end_turn": return "stop";
|
|
3102
|
+
case "max_tokens": return "length";
|
|
3103
|
+
case "model_context_window_exceeded": return "length";
|
|
3104
|
+
case "tool_use": return "toolUse";
|
|
3105
|
+
case "refusal": return "error";
|
|
3106
|
+
case "pause_turn": return "stop";
|
|
3107
|
+
case "stop_sequence": return "stop";
|
|
3108
|
+
case "sensitive": return "error";
|
|
3109
|
+
default:
|
|
3110
|
+
reportAnthropicEnvelopeAnomaly(`unhandled stop reason: ${reason}`);
|
|
3111
|
+
return "stop";
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
//#endregion
|
|
3115
|
+
export { anthropic_exports as t };
|