@earendil-works/pi-ai 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -39
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +282 -87
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/dist/api/azure-openai-responses.js +1 -7
- package/dist/api/azure-openai-responses.js.map +1 -1
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.js +127 -103
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/bedrock-converse-stream.lazy.d.ts +7 -0
- package/dist/api/bedrock-converse-stream.lazy.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.lazy.js +18 -5
- package/dist/api/bedrock-converse-stream.lazy.js.map +1 -1
- package/dist/api/constrained-sampling.d.ts +13 -1
- package/dist/api/constrained-sampling.d.ts.map +1 -1
- package/dist/api/constrained-sampling.js +38 -18
- package/dist/api/constrained-sampling.js.map +1 -1
- package/dist/api/google-generative-ai.d.ts.map +1 -1
- package/dist/api/google-generative-ai.js +0 -6
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-shared.d.ts +2 -1
- package/dist/api/google-shared.d.ts.map +1 -1
- package/dist/api/google-shared.js +57 -2
- package/dist/api/google-shared.js.map +1 -1
- package/dist/api/google-vertex.d.ts.map +1 -1
- package/dist/api/google-vertex.js +0 -6
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/lazy.d.ts +1 -1
- package/dist/api/lazy.d.ts.map +1 -1
- package/dist/api/lazy.js +9 -8
- package/dist/api/lazy.js.map +1 -1
- package/dist/api/mistral-conversations.d.ts.map +1 -1
- package/dist/api/mistral-conversations.js +17 -44
- package/dist/api/mistral-conversations.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +7 -8
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts +2 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +55 -59
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +58 -57
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +4 -8
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/openrouter-images.js +1 -0
- package/dist/api/openrouter-images.js.map +1 -1
- package/dist/api/pi-messages.d.ts +2 -0
- package/dist/api/pi-messages.d.ts.map +1 -1
- package/dist/api/pi-messages.js +24 -9
- package/dist/api/pi-messages.js.map +1 -1
- package/dist/api/simple-options.d.ts +1 -0
- package/dist/api/simple-options.d.ts.map +1 -1
- package/dist/api/simple-options.js +16 -2
- package/dist/api/simple-options.js.map +1 -1
- package/dist/api/strict-tool-limits.d.ts +13 -0
- package/dist/api/strict-tool-limits.d.ts.map +1 -0
- package/dist/api/strict-tool-limits.js +50 -0
- package/dist/api/strict-tool-limits.js.map +1 -0
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/dist/auth/oauth/github-copilot.js +4 -11
- package/dist/auth/oauth/github-copilot.js.map +1 -1
- package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
- package/dist/auth/oauth/kimi-coding.js +4 -22
- package/dist/auth/oauth/kimi-coding.js.map +1 -1
- package/dist/auth/oauth/load.d.ts.map +1 -1
- package/dist/auth/oauth/load.js +5 -2
- package/dist/auth/oauth/load.js.map +1 -1
- package/dist/auth/oauth/meta.d.ts.map +1 -1
- package/dist/auth/oauth/meta.js +1 -14
- package/dist/auth/oauth/meta.js.map +1 -1
- package/dist/auth/oauth/radius.d.ts.map +1 -1
- package/dist/auth/oauth/radius.js +9 -3
- package/dist/auth/oauth/radius.js.map +1 -1
- package/dist/auth/oauth/trusted-url.d.ts +7 -0
- package/dist/auth/oauth/trusted-url.d.ts.map +1 -0
- package/dist/auth/oauth/trusted-url.js +12 -0
- package/dist/auth/oauth/trusted-url.js.map +1 -0
- package/dist/auth/types.d.ts +4 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/cache-warming.d.ts +53 -0
- package/dist/cache-warming.d.ts.map +1 -0
- package/dist/cache-warming.js +82 -0
- package/dist/cache-warming.js.map +1 -0
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/compat/extension-oauth-types.d.ts +4 -0
- package/dist/compat/extension-oauth-types.d.ts.map +1 -1
- package/dist/compat/extension-oauth-types.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -0
- package/dist/env-api-keys.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/lazy-bedrock-provider.d.ts +10 -0
- package/dist/lazy-bedrock-provider.d.ts.map +1 -0
- package/dist/lazy-bedrock-provider.js +13 -0
- package/dist/lazy-bedrock-provider.js.map +1 -0
- package/dist/lazy-oauth.d.ts +10 -0
- package/dist/lazy-oauth.d.ts.map +1 -0
- package/dist/lazy-oauth.js +23 -0
- package/dist/lazy-oauth.js.map +1 -0
- package/dist/models.d.ts +7 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +4 -0
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +5 -1
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +20 -11
- package/dist/models.js.map +1 -1
- package/dist/providers/all.d.ts.map +1 -1
- package/dist/providers/all.js +2 -0
- package/dist/providers/all.js.map +1 -1
- package/dist/providers/amazon-bedrock.models.js +1 -1
- package/dist/providers/amazon-bedrock.models.js.map +1 -1
- package/dist/providers/ant-ling.models.js +1 -1
- package/dist/providers/ant-ling.models.js.map +1 -1
- package/dist/providers/anthropic.models.js +1 -1
- package/dist/providers/anthropic.models.js.map +1 -1
- package/dist/providers/azure.models.js +1 -1
- package/dist/providers/azure.models.js.map +1 -1
- package/dist/providers/baseten.models.js +1 -1
- package/dist/providers/baseten.models.js.map +1 -1
- package/dist/providers/cerebras.models.js +1 -1
- package/dist/providers/cerebras.models.js.map +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.js +1 -1
- package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
- package/dist/providers/cloudflare-workers-ai.models.js +1 -1
- package/dist/providers/cloudflare-workers-ai.models.js.map +1 -1
- package/dist/providers/compat-schema.d.ts +444 -456
- package/dist/providers/compat-schema.d.ts.map +1 -1
- package/dist/providers/compat-schema.js +166 -112
- package/dist/providers/compat-schema.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/anthropic.json +1 -1
- package/dist/providers/data/azure.json +1 -1
- package/dist/providers/data/baseten.json +1 -1
- package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/dist/providers/data/fireworks.json +1 -1
- package/dist/providers/data/github-copilot.json +1 -1
- package/dist/providers/data/google-vertex.json +1 -1
- package/dist/providers/data/google.json +1 -1
- package/dist/providers/data/huggingface.json +1 -1
- package/dist/providers/data/kimi-coding.json +1 -1
- package/dist/providers/data/mistral.json +1 -1
- package/dist/providers/data/moonshotai-cn.json +1 -1
- package/dist/providers/data/moonshotai.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/openai-codex.json +1 -1
- package/dist/providers/data/openai.json +1 -1
- package/dist/providers/data/opencode-go.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/qwen-token-plan-cn.json +1 -1
- package/dist/providers/data/qwen-token-plan-individual-cn.json +1 -0
- package/dist/providers/data/qwen-token-plan-individual.json +1 -1
- package/dist/providers/data/qwen-token-plan.json +1 -1
- package/dist/providers/data/radius.json +1 -1
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/data/xai.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
- package/dist/providers/data/xiaomi.json +1 -1
- package/dist/providers/data/zai-coding-cn.json +1 -1
- package/dist/providers/data/zai.json +1 -1
- package/dist/providers/deepseek.models.js +1 -1
- package/dist/providers/deepseek.models.js.map +1 -1
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +7 -2
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/fireworks.models.js +1 -1
- package/dist/providers/fireworks.models.js.map +1 -1
- package/dist/providers/github-copilot.d.ts +3 -1
- package/dist/providers/github-copilot.d.ts.map +1 -1
- package/dist/providers/github-copilot.js +13 -2
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/github-copilot.models.js +1 -1
- package/dist/providers/github-copilot.models.js.map +1 -1
- package/dist/providers/google-vertex.models.js +1 -1
- package/dist/providers/google-vertex.models.js.map +1 -1
- package/dist/providers/google.models.js +1 -1
- package/dist/providers/google.models.js.map +1 -1
- package/dist/providers/groq.models.js +1 -1
- package/dist/providers/groq.models.js.map +1 -1
- package/dist/providers/huggingface.models.js +1 -1
- package/dist/providers/huggingface.models.js.map +1 -1
- package/dist/providers/kimi-coding.models.js +1 -1
- package/dist/providers/kimi-coding.models.js.map +1 -1
- package/dist/providers/meta.models.js +1 -1
- package/dist/providers/meta.models.js.map +1 -1
- package/dist/providers/minimax-cn.models.js +1 -1
- package/dist/providers/minimax-cn.models.js.map +1 -1
- package/dist/providers/minimax.models.js +1 -1
- package/dist/providers/minimax.models.js.map +1 -1
- package/dist/providers/mistral.models.js +1 -1
- package/dist/providers/mistral.models.js.map +1 -1
- package/dist/providers/model-schema.d.ts +65 -65
- package/dist/providers/model-schema.d.ts.map +1 -1
- package/dist/providers/model-schema.js +41 -30
- package/dist/providers/model-schema.js.map +1 -1
- package/dist/providers/moonshotai-cn.models.js +1 -1
- package/dist/providers/moonshotai-cn.models.js.map +1 -1
- package/dist/providers/moonshotai.models.js +1 -1
- package/dist/providers/moonshotai.models.js.map +1 -1
- package/dist/providers/nvidia.models.js +1 -1
- package/dist/providers/nvidia.models.js.map +1 -1
- package/dist/providers/openai-codex.js +1 -1
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/openai-codex.models.js +1 -1
- package/dist/providers/openai-codex.models.js.map +1 -1
- package/dist/providers/openai.models.js +1 -1
- package/dist/providers/openai.models.js.map +1 -1
- package/dist/providers/opencode-go.models.js +1 -1
- package/dist/providers/opencode-go.models.js.map +1 -1
- package/dist/providers/opencode.js +1 -1
- package/dist/providers/opencode.js.map +1 -1
- package/dist/providers/opencode.models.js +1 -1
- package/dist/providers/opencode.models.js.map +1 -1
- package/dist/providers/openrouter.models.js +1 -1
- package/dist/providers/openrouter.models.js.map +1 -1
- package/dist/providers/qwen-token-plan-cn.models.js +1 -1
- package/dist/providers/qwen-token-plan-cn.models.js.map +1 -1
- package/dist/providers/qwen-token-plan-individual-cn.d.ts +3 -0
- package/dist/providers/qwen-token-plan-individual-cn.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan-individual-cn.js +15 -0
- package/dist/providers/qwen-token-plan-individual-cn.js.map +1 -0
- package/dist/providers/qwen-token-plan-individual-cn.models.d.ts +6 -0
- package/dist/providers/qwen-token-plan-individual-cn.models.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan-individual-cn.models.js +8 -0
- package/dist/providers/qwen-token-plan-individual-cn.models.js.map +1 -0
- package/dist/providers/qwen-token-plan-individual.models.js +1 -1
- package/dist/providers/qwen-token-plan-individual.models.js.map +1 -1
- package/dist/providers/qwen-token-plan.models.js +1 -1
- package/dist/providers/qwen-token-plan.models.js.map +1 -1
- package/dist/providers/radius-config.d.ts.map +1 -1
- package/dist/providers/radius-config.js +2 -1
- package/dist/providers/radius-config.js.map +1 -1
- package/dist/providers/radius.models.js +1 -1
- package/dist/providers/radius.models.js.map +1 -1
- package/dist/providers/schema-builders.d.ts +10 -0
- package/dist/providers/schema-builders.d.ts.map +1 -0
- package/dist/providers/schema-builders.js +18 -0
- package/dist/providers/schema-builders.js.map +1 -0
- package/dist/providers/together.models.js +1 -1
- package/dist/providers/together.models.js.map +1 -1
- package/dist/providers/typesafe.models.js +1 -1
- package/dist/providers/typesafe.models.js.map +1 -1
- package/dist/providers/vercel-ai-gateway.models.js +1 -1
- package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/dist/providers/xai.models.js +1 -1
- package/dist/providers/xai.models.js.map +1 -1
- package/dist/providers/xiaomi-token-plan-ams.models.js +1 -1
- package/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
- package/dist/providers/xiaomi-token-plan-cn.models.js +1 -1
- package/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
- package/dist/providers/xiaomi-token-plan-sgp.models.js +1 -1
- package/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
- package/dist/providers/xiaomi.models.js +1 -1
- package/dist/providers/xiaomi.models.js.map +1 -1
- package/dist/providers/zai-coding-cn.models.js +1 -1
- package/dist/providers/zai-coding-cn.models.js.map +1 -1
- package/dist/providers/zai.models.js +1 -1
- package/dist/providers/zai.models.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +2 -1
- package/dist/telemetry.js.map +1 -1
- package/dist/types.d.ts +20 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/error-body.d.ts +8 -0
- package/dist/utils/error-body.d.ts.map +1 -1
- package/dist/utils/error-body.js +30 -2
- package/dist/utils/error-body.js.map +1 -1
- package/dist/utils/openrouter-cost.d.ts +24 -0
- package/dist/utils/openrouter-cost.d.ts.map +1 -0
- package/dist/utils/openrouter-cost.js +34 -0
- package/dist/utils/openrouter-cost.js.map +1 -0
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +2 -0
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +6 -0
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/streaming-json.d.ts +84 -0
- package/dist/utils/streaming-json.d.ts.map +1 -0
- package/dist/utils/streaming-json.js +513 -0
- package/dist/utils/streaming-json.js.map +1 -0
- package/dist/utils/{typebox-helpers.d.ts → string-enum.d.ts} +5 -5
- package/dist/utils/string-enum.d.ts.map +1 -0
- package/dist/utils/{typebox-helpers.js → string-enum.js} +5 -6
- package/dist/utils/string-enum.js.map +1 -0
- package/dist/utils/text.d.ts +7 -0
- package/dist/utils/text.d.ts.map +1 -1
- package/dist/utils/text.js +9 -0
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/transcript.d.ts +1 -1
- package/dist/utils/transcript.js +1 -1
- package/dist/utils/transcript.js.map +1 -1
- package/dist/utils/validation.d.ts +5 -3
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +27 -277
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/version.d.ts +1 -1
- package/dist/utils/version.js +1 -1
- package/dist/utils/version.js.map +1 -1
- package/package.json +19 -10
- package/dist/utils/typebox-helpers.d.ts.map +0 -1
- package/dist/utils/typebox-helpers.js.map +0 -1
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import Anthropic, {} from "@anthropic-ai/sdk";
|
|
2
2
|
import { ANTHROPIC_FEDERATION_RULE_ID_ENV, ANTHROPIC_IDENTITY_TOKEN_FILE_ENV, ANTHROPIC_ORGANIZATION_ID_ENV, ANTHROPIC_SERVICE_ACCOUNT_ID_ENV, ANTHROPIC_WORKSPACE_ID_ENV, } from "../env-api-keys.js";
|
|
3
3
|
import { calculateCost } from "../models.js";
|
|
4
|
+
import { registerSessionResourceCleanup } from "../session-resources.js";
|
|
4
5
|
import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
|
|
6
|
+
import { withCauseDetail } from "../utils/error-body.js";
|
|
5
7
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
8
|
+
import { shortHash } from "../utils/hash.js";
|
|
6
9
|
import { headersToRecord } from "../utils/headers.js";
|
|
7
10
|
import { getIdentityHeaders } from "../utils/identity.js";
|
|
8
|
-
import { parseJsonWithRepair
|
|
11
|
+
import { parseJsonWithRepair } from "../utils/json-parse.js";
|
|
12
|
+
import { applyOpenRouterReportedCost, isOpenRouterModel, } from "../utils/openrouter-cost.js";
|
|
9
13
|
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
10
14
|
import { retryProviderRequest } from "../utils/provider-retry.js";
|
|
11
15
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
16
|
+
import { ToolCallArgumentsStream } from "../utils/streaming-json.js";
|
|
12
17
|
import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
|
|
13
18
|
import { getCurrentTools, getInitialSystemMessage, resolveTranscript, } from "../utils/transcript.js";
|
|
14
|
-
import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
|
|
19
|
+
import { getJsonSchemaToolParameters, prefersJsonSchemaStrictSampling, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
|
|
15
20
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
16
21
|
import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
|
|
22
|
+
import { hasExceededStrictToolLimits, isStrictToolLimitErrorMessage, markStrictToolLimitsExceeded, } from "./strict-tool-limits.js";
|
|
17
23
|
import { splitErrorToolResultImages, transformMessages } from "./transform-messages.js";
|
|
18
24
|
/**
|
|
19
25
|
* Resolve cache retention preference.
|
|
@@ -120,6 +126,7 @@ const SERVER_SIDE_FALLBACK_BETA = "server-side-fallback-2026-07-01";
|
|
|
120
126
|
const MID_CONVERSATION_OUTPUT_CONFIG_BETA = "mid-conversation-output-config-2026-07-01";
|
|
121
127
|
const THINKING_BINDING_CONTROLS_BETA = "thinking-binding-controls-2026-08-01";
|
|
122
128
|
const INLINE_TOOLS_BETA = "inline-tools-2026-09-15";
|
|
129
|
+
const MESSAGE_THREADS_BETA = "message-threads-2026-08-12";
|
|
123
130
|
/**
|
|
124
131
|
* Stable deferred tool declared whenever native tool changes are in use. Anthropic adds
|
|
125
132
|
* hidden prompt scaffolding for mid-conversation tool changes; declaring this placeholder
|
|
@@ -149,8 +156,178 @@ function getAnthropicCompat(model) {
|
|
|
149
156
|
supportsStrictTools: model.compat?.supportsStrictTools ?? false,
|
|
150
157
|
supportsMidConvoSystemMessages: model.compat?.supportsMidConvoSystemMessages ?? false,
|
|
151
158
|
supportsMidConvoToolChanges: model.compat?.supportsMidConvoToolChanges ?? false,
|
|
159
|
+
// Gateways that drop unknown request fields would silently turn a `continue` into a request
|
|
160
|
+
// without the earlier conversation, so the default is limited to Anthropic's own endpoint.
|
|
161
|
+
// Widen or remove this restriction once proxies generally forward the `thread` field.
|
|
162
|
+
supportsMessageThreads: model.compat?.supportsMessageThreads ??
|
|
163
|
+
(model.provider === "anthropic" && model.baseUrl.includes("api.anthropic.com")),
|
|
152
164
|
};
|
|
153
165
|
}
|
|
166
|
+
/** Per session: where the next request can continue, or "off" after the endpoint rejected threads. */
|
|
167
|
+
const threadState = new Map();
|
|
168
|
+
const MAX_THREAD_STATE_ENTRIES = 10_000;
|
|
169
|
+
/** Rejections of a `continue` that a new thread fixes: expired, raced, or a fixed setting changed. */
|
|
170
|
+
const NEW_THREAD_ERRORS = new Set([
|
|
171
|
+
"thread_not_found",
|
|
172
|
+
"thread_already_continued",
|
|
173
|
+
"thread_continue_in_flight",
|
|
174
|
+
"thread_fingerprint_mismatch",
|
|
175
|
+
]);
|
|
176
|
+
/**
|
|
177
|
+
* Sets a session's thread state, keeping at most `MAX_THREAD_STATE_ENTRIES` sessions.
|
|
178
|
+
*
|
|
179
|
+
* State is normally freed by `cleanupSessionResources(sessionId)` when a session ends, as pi's
|
|
180
|
+
* coding agent does. The cap bounds memory for library users that never call it, for example a
|
|
181
|
+
* server passing a new `sessionId` per conversation. Dropping the least recently updated session
|
|
182
|
+
* is cheap: its thread has most likely expired on the server already (after a few minutes idle),
|
|
183
|
+
* and otherwise its next request just starts a new thread.
|
|
184
|
+
*/
|
|
185
|
+
function setThreadState(sessionId, value) {
|
|
186
|
+
// Re-insert so Map order stays oldest-first for the cap.
|
|
187
|
+
threadState.delete(sessionId);
|
|
188
|
+
threadState.set(sessionId, value);
|
|
189
|
+
if (threadState.size > MAX_THREAD_STATE_ENTRIES) {
|
|
190
|
+
const oldest = threadState.keys().next().value;
|
|
191
|
+
if (oldest !== undefined)
|
|
192
|
+
threadState.delete(oldest);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
registerSessionResourceCleanup((sessionId) => {
|
|
196
|
+
if (sessionId)
|
|
197
|
+
threadState.delete(sessionId);
|
|
198
|
+
else
|
|
199
|
+
threadState.clear();
|
|
200
|
+
});
|
|
201
|
+
/**
|
|
202
|
+
* Hash of everything a `continue` builds on: the settings that must stay the same within a thread
|
|
203
|
+
* and the messages the server already holds.
|
|
204
|
+
*
|
|
205
|
+
* - Settings, as sent: the endpoint (a thread exists on one endpoint only), and the fixed request
|
|
206
|
+
* fields. A `continue` omits `system` and `tools` and the server keeps the thread's own, so only
|
|
207
|
+
* this check can notice a change to them, including their cache markers. The server rejects
|
|
208
|
+
* changes to the other fields with `thread_fingerprint_mismatch`; checking them here saves that
|
|
209
|
+
* rejected request. A fixed field missing here costs only that rejection, which also starts a
|
|
210
|
+
* new thread.
|
|
211
|
+
* - Messages, normalized so messages that mean the same to the API hash the same.
|
|
212
|
+
*/
|
|
213
|
+
function hashThreadPrefix(model, params, messages) {
|
|
214
|
+
const { system, tools, thinking, output_config, tool_choice, betas, fallbacks } = params;
|
|
215
|
+
const settings = JSON.stringify([
|
|
216
|
+
model.provider,
|
|
217
|
+
model.baseUrl,
|
|
218
|
+
params.model,
|
|
219
|
+
system,
|
|
220
|
+
tools,
|
|
221
|
+
thinking,
|
|
222
|
+
output_config,
|
|
223
|
+
tool_choice,
|
|
224
|
+
betas,
|
|
225
|
+
fallbacks,
|
|
226
|
+
]);
|
|
227
|
+
const conversation = JSON.stringify(messages, (key, value) => {
|
|
228
|
+
// Cache markers don't change what a message means, but they move: pi puts one on the newest
|
|
229
|
+
// message, so the message that had it in the last request has none in this one. Extensions
|
|
230
|
+
// may add markers anywhere too.
|
|
231
|
+
if (key === "cache_control")
|
|
232
|
+
return undefined;
|
|
233
|
+
// To attach that marker, the newest user message's string content is sent as one text block;
|
|
234
|
+
// the next request sends the same message as a string: "hi" vs [{ type: "text", text: "hi" }].
|
|
235
|
+
if (key === "content" && typeof value === "string")
|
|
236
|
+
return [{ type: "text", text: value }];
|
|
237
|
+
return value;
|
|
238
|
+
});
|
|
239
|
+
return shortHash(settings + conversation);
|
|
240
|
+
}
|
|
241
|
+
function createThreadRequest(sessionId, params) {
|
|
242
|
+
return { sessionId, body: { ...params, thread: { type: "create" } } };
|
|
243
|
+
}
|
|
244
|
+
function prepareThreadRequest(model, sessionId, params) {
|
|
245
|
+
const state = threadState.get(sessionId);
|
|
246
|
+
if (state === "off")
|
|
247
|
+
return undefined;
|
|
248
|
+
if (state !== undefined &&
|
|
249
|
+
params.messages.length > state.prefixCount &&
|
|
250
|
+
hashThreadPrefix(model, params, params.messages.slice(0, state.prefixCount)) === state.prefixHash) {
|
|
251
|
+
// Each reply can be continued once; deleting also keeps a concurrent request from trying.
|
|
252
|
+
threadState.delete(sessionId);
|
|
253
|
+
const { system: _system, tools: _tools, ...rest } = params;
|
|
254
|
+
return {
|
|
255
|
+
body: {
|
|
256
|
+
...rest,
|
|
257
|
+
messages: params.messages.slice(state.prefixCount),
|
|
258
|
+
thread: { type: "continue", previous_message_id: state.replyId },
|
|
259
|
+
},
|
|
260
|
+
sessionId,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return createThreadRequest(sessionId, params);
|
|
264
|
+
}
|
|
265
|
+
/** The `error_code` Anthropic puts in `error.details` of thread rejections. */
|
|
266
|
+
function getThreadErrorCode(error) {
|
|
267
|
+
const body = error.error;
|
|
268
|
+
const code = body?.error?.details?.error_code;
|
|
269
|
+
return typeof code === "string" ? code : undefined;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Sends a thread request. A rejection about the thread (never a started stream) is resent once in
|
|
273
|
+
* full: as a new thread when the old one can't be continued, or without `thread` when the endpoint
|
|
274
|
+
* can't use threads, which also turns threads off for the session. Other errors are passed on.
|
|
275
|
+
* Returns the response and the thread request that produced it, if any.
|
|
276
|
+
*/
|
|
277
|
+
async function sendThreadRequest(thread, params, send, output, signal) {
|
|
278
|
+
try {
|
|
279
|
+
return { response: await send(thread.body), thread };
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (signal?.aborted || !(error instanceof Anthropic.APIError))
|
|
283
|
+
throw error;
|
|
284
|
+
const errorCode = getThreadErrorCode(error);
|
|
285
|
+
const startNewThread = errorCode !== undefined && NEW_THREAD_ERRORS.has(errorCode);
|
|
286
|
+
// The endpoint can't use threads: it can't thread this request, or it doesn't know the
|
|
287
|
+
// `thread` field and rejects it without a code ("thread: Extra inputs are not permitted"),
|
|
288
|
+
// e.g. with an `anthropic-beta` header that lacks the threads beta.
|
|
289
|
+
const threadsUnsupported = errorCode === "thread_unsupported_request" ||
|
|
290
|
+
(errorCode === undefined && error.status === 400 && error.message.includes("thread"));
|
|
291
|
+
// Any other error (prompt too long, unknown model, strict tool limits, ...) is not about the
|
|
292
|
+
// thread, and resending would only fail again.
|
|
293
|
+
if (!startNewThread && !threadsUnsupported)
|
|
294
|
+
throw error;
|
|
295
|
+
// Recorded before the resend, so a failed resend keeps it.
|
|
296
|
+
appendAssistantMessageDiagnostic(output, {
|
|
297
|
+
type: "anthropic_thread_fallback",
|
|
298
|
+
timestamp: Date.now(),
|
|
299
|
+
error: { message: error.message },
|
|
300
|
+
details: { reason: errorCode ?? String(error.status) },
|
|
301
|
+
});
|
|
302
|
+
if (startNewThread) {
|
|
303
|
+
const created = createThreadRequest(thread.sessionId, params);
|
|
304
|
+
return { response: await send(created.body), thread: created };
|
|
305
|
+
}
|
|
306
|
+
// With the threads beta, a request without `thread` is a normal request.
|
|
307
|
+
const response = await send(params);
|
|
308
|
+
setThreadState(thread.sessionId, "off");
|
|
309
|
+
return { response, thread: undefined };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Records where the session's next request can continue: after `output`, the clean reply to the
|
|
314
|
+
* thread request `thread`, whose full request was `params`.
|
|
315
|
+
*/
|
|
316
|
+
function recordThreadReply(model, thread, params, output, isOAuth, allowEmptySignature) {
|
|
317
|
+
// `responseModel` is set only when a fallback model answered; the server does not store those
|
|
318
|
+
// replies, so they cannot be continued.
|
|
319
|
+
if (output.responseId === undefined || output.responseModel !== undefined)
|
|
320
|
+
return;
|
|
321
|
+
// Convert the reply like the next request will, keeping only the assistant message
|
|
322
|
+
// (transformMessages adds placeholder results for trailing tool calls).
|
|
323
|
+
const reply = convertMessages(transformMessages([output], model, normalizeToolCallId), isOAuth, undefined, allowEmptySignature).messages.filter((message) => message.role === "assistant");
|
|
324
|
+
const prefix = [...params.messages, ...reply];
|
|
325
|
+
setThreadState(thread.sessionId, {
|
|
326
|
+
prefixCount: prefix.length,
|
|
327
|
+
prefixHash: hashThreadPrefix(model, params, prefix),
|
|
328
|
+
replyId: output.responseId,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
154
331
|
function mergeHeaders(...headerSources) {
|
|
155
332
|
const merged = {};
|
|
156
333
|
for (const headers of headerSources) {
|
|
@@ -375,7 +552,8 @@ async function* iterateAnthropicEvents(response, signal) {
|
|
|
375
552
|
}
|
|
376
553
|
export const stream = (model, context, options) => {
|
|
377
554
|
const stream = new AssistantMessageEventStream();
|
|
378
|
-
const
|
|
555
|
+
const compat = getAnthropicCompat(model);
|
|
556
|
+
const normalizedContext = resolveTranscript(context, compat.supportsMidConvoSystemMessages);
|
|
379
557
|
const currentTools = getCurrentTools(normalizedContext.messages);
|
|
380
558
|
(async () => {
|
|
381
559
|
const output = {
|
|
@@ -400,6 +578,12 @@ export const stream = (model, context, options) => {
|
|
|
400
578
|
let isOAuth;
|
|
401
579
|
let usageModel = model;
|
|
402
580
|
let inputTransformations;
|
|
581
|
+
const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
|
|
582
|
+
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
583
|
+
// Session whose requests may use threads; undefined when threads don't apply. Sessions where
|
|
584
|
+
// the endpoint rejected threads ("off") still send the threads beta, which the resend that
|
|
585
|
+
// turned them off was accepted with; `prepareThreadRequest()` just sends no `thread`.
|
|
586
|
+
const threadSessionId = compat.supportsMessageThreads ? cacheSessionId : undefined;
|
|
403
587
|
if (options?.client) {
|
|
404
588
|
client = options.client;
|
|
405
589
|
isOAuth = false;
|
|
@@ -417,8 +601,6 @@ export const stream = (model, context, options) => {
|
|
|
417
601
|
hasImages,
|
|
418
602
|
});
|
|
419
603
|
}
|
|
420
|
-
const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
|
|
421
|
-
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
422
604
|
const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, federation, options?.identity);
|
|
423
605
|
client = created.client;
|
|
424
606
|
isOAuth = created.isOAuthToken;
|
|
@@ -428,24 +610,38 @@ export const stream = (model, context, options) => {
|
|
|
428
610
|
...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
|
|
429
611
|
maxRetries: 0,
|
|
430
612
|
};
|
|
431
|
-
|
|
613
|
+
const send = (body) => retryProviderRequest(() => client.beta.messages.create(body, requestOptions).asResponse(), {
|
|
614
|
+
maxRetries: options?.maxRetries,
|
|
615
|
+
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
616
|
+
signal: options?.signal,
|
|
617
|
+
});
|
|
618
|
+
let disablePreferredStrictTools = hasExceededStrictToolLimits(options?.sessionId);
|
|
432
619
|
let response;
|
|
620
|
+
let params;
|
|
621
|
+
// The thread request that produced the response; undefined when no `thread` was sent.
|
|
622
|
+
let thread;
|
|
433
623
|
for (;;) {
|
|
434
624
|
options?.signal?.throwIfAborted();
|
|
435
|
-
const built = buildParams(model, normalizedContext, isOAuth, options,
|
|
625
|
+
const built = buildParams(model, normalizedContext, isOAuth, options, {
|
|
626
|
+
disablePreferredStrictTools,
|
|
627
|
+
messageThreads: threadSessionId !== undefined,
|
|
628
|
+
});
|
|
436
629
|
if (built.providerThinkingLevel)
|
|
437
630
|
output.providerThinkingLevel = built.providerThinkingLevel;
|
|
438
|
-
|
|
631
|
+
params = built.params;
|
|
439
632
|
const nextParams = await options?.onPayload?.(params, model);
|
|
440
633
|
if (nextParams !== undefined) {
|
|
441
634
|
params = { ...nextParams, stream: true };
|
|
442
635
|
}
|
|
636
|
+
const prepared = threadSessionId !== undefined && options?.disableContinuation !== true
|
|
637
|
+
? prepareThreadRequest(model, threadSessionId, params)
|
|
638
|
+
: undefined;
|
|
443
639
|
try {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
640
|
+
const sent = prepared
|
|
641
|
+
? await sendThreadRequest(prepared, params, send, output, options?.signal)
|
|
642
|
+
: { response: await send(params), thread: undefined };
|
|
643
|
+
response = sent.response;
|
|
644
|
+
thread = sent.thread;
|
|
449
645
|
break;
|
|
450
646
|
}
|
|
451
647
|
catch (error) {
|
|
@@ -456,14 +652,11 @@ export const stream = (model, context, options) => {
|
|
|
456
652
|
!(error instanceof Anthropic.APIError) ||
|
|
457
653
|
error.status !== 400 ||
|
|
458
654
|
error.type !== "invalid_request_error" ||
|
|
459
|
-
!
|
|
460
|
-
"The compiled grammar is too large",
|
|
461
|
-
"Too many strict tools (",
|
|
462
|
-
"Schemas contains too many parameters with union types (",
|
|
463
|
-
].some((message) => error.message.includes(message))) {
|
|
655
|
+
!isStrictToolLimitErrorMessage(error.message)) {
|
|
464
656
|
throw error;
|
|
465
657
|
}
|
|
466
658
|
disablePreferredStrictTools = true;
|
|
659
|
+
markStrictToolLimitsExceeded(options?.sessionId);
|
|
467
660
|
appendAssistantMessageDiagnostic(output, {
|
|
468
661
|
type: "anthropic_strict_tools_fallback",
|
|
469
662
|
timestamp: Date.now(),
|
|
@@ -474,7 +667,12 @@ export const stream = (model, context, options) => {
|
|
|
474
667
|
}
|
|
475
668
|
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
476
669
|
stream.push({ type: "start", partial: output });
|
|
477
|
-
|
|
670
|
+
// Stream state per Anthropic block index, kept off the content blocks so it never reaches partials.
|
|
671
|
+
const openBlocks = new Map();
|
|
672
|
+
const openBlock = (anthropicIndex) => {
|
|
673
|
+
const open = openBlocks.get(anthropicIndex);
|
|
674
|
+
return open === undefined ? undefined : { ...open, block: output.content[open.contentIndex] };
|
|
675
|
+
};
|
|
478
676
|
for await (const event of iterateAnthropicEvents(response, options?.signal)) {
|
|
479
677
|
await options?.onProviderStreamEvent?.(event, model);
|
|
480
678
|
if (event.type === "message_start") {
|
|
@@ -509,105 +707,92 @@ export const stream = (model, context, options) => {
|
|
|
509
707
|
continue;
|
|
510
708
|
}
|
|
511
709
|
if (event.content_block.type === "text") {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
text: event.content_block.text ?? "",
|
|
515
|
-
index: event.index,
|
|
516
|
-
};
|
|
517
|
-
output.content.push(block);
|
|
710
|
+
output.content.push({ type: "text", text: event.content_block.text ?? "" });
|
|
711
|
+
openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
|
|
518
712
|
stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
|
|
519
713
|
}
|
|
520
714
|
else if (event.content_block.type === "thinking") {
|
|
521
|
-
|
|
715
|
+
output.content.push({
|
|
522
716
|
type: "thinking",
|
|
523
717
|
thinking: event.content_block.thinking ?? "",
|
|
524
718
|
thinkingSignature: event.content_block.signature ?? "",
|
|
525
|
-
|
|
526
|
-
};
|
|
527
|
-
output.content.push(block);
|
|
719
|
+
});
|
|
720
|
+
openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
|
|
528
721
|
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
529
722
|
}
|
|
530
723
|
else if (event.content_block.type === "redacted_thinking") {
|
|
531
|
-
|
|
724
|
+
output.content.push({
|
|
532
725
|
type: "thinking",
|
|
533
726
|
thinking: "[Reasoning redacted]",
|
|
534
727
|
thinkingSignature: event.content_block.data,
|
|
535
728
|
redacted: true,
|
|
536
|
-
|
|
537
|
-
};
|
|
538
|
-
output.content.push(block);
|
|
729
|
+
});
|
|
730
|
+
openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
|
|
539
731
|
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
540
732
|
}
|
|
541
733
|
else if (event.content_block.type === "tool_use") {
|
|
542
|
-
|
|
734
|
+
output.content.push({
|
|
543
735
|
type: "toolCall",
|
|
544
736
|
id: event.content_block.id,
|
|
545
737
|
name: isOAuth
|
|
546
738
|
? fromClaudeCodeName(event.content_block.name, currentTools)
|
|
547
739
|
: event.content_block.name,
|
|
548
740
|
arguments: event.content_block.input ?? {},
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
741
|
+
});
|
|
742
|
+
openBlocks.set(event.index, {
|
|
743
|
+
contentIndex: output.content.length - 1,
|
|
744
|
+
arguments: new ToolCallArgumentsStream(),
|
|
745
|
+
});
|
|
553
746
|
stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
|
|
554
747
|
}
|
|
555
748
|
}
|
|
556
749
|
else if (event.type === "content_block_delta") {
|
|
750
|
+
const open = openBlock(event.index);
|
|
557
751
|
if (event.delta.type === "text_delta") {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (block && block.type === "text") {
|
|
561
|
-
block.text += event.delta.text;
|
|
752
|
+
if (open?.block.type === "text") {
|
|
753
|
+
open.block.text += event.delta.text;
|
|
562
754
|
stream.push({
|
|
563
755
|
type: "text_delta",
|
|
564
|
-
contentIndex:
|
|
756
|
+
contentIndex: open.contentIndex,
|
|
565
757
|
delta: event.delta.text,
|
|
566
758
|
partial: output,
|
|
567
759
|
});
|
|
568
760
|
}
|
|
569
761
|
}
|
|
570
762
|
else if (event.delta.type === "thinking_delta") {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if (block && block.type === "thinking") {
|
|
574
|
-
block.thinking += event.delta.thinking;
|
|
763
|
+
if (open?.block.type === "thinking") {
|
|
764
|
+
open.block.thinking += event.delta.thinking;
|
|
575
765
|
stream.push({
|
|
576
766
|
type: "thinking_delta",
|
|
577
|
-
contentIndex:
|
|
767
|
+
contentIndex: open.contentIndex,
|
|
578
768
|
delta: event.delta.thinking,
|
|
579
769
|
partial: output,
|
|
580
770
|
});
|
|
581
771
|
}
|
|
582
772
|
}
|
|
583
773
|
else if (event.delta.type === "input_json_delta") {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
if (block && block.type === "toolCall") {
|
|
587
|
-
block.partialJson += event.delta.partial_json;
|
|
588
|
-
block.arguments = parseStreamingJson(block.partialJson);
|
|
774
|
+
if (open?.block.type === "toolCall" && open.arguments !== undefined) {
|
|
775
|
+
open.block.arguments = open.arguments.append(event.delta.partial_json);
|
|
589
776
|
stream.push({
|
|
590
777
|
type: "toolcall_delta",
|
|
591
|
-
contentIndex:
|
|
778
|
+
contentIndex: open.contentIndex,
|
|
592
779
|
delta: event.delta.partial_json,
|
|
593
780
|
partial: output,
|
|
594
781
|
});
|
|
595
782
|
}
|
|
596
783
|
}
|
|
597
784
|
else if (event.delta.type === "signature_delta") {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
block.thinkingSignature = block.thinkingSignature || "";
|
|
602
|
-
block.thinkingSignature += event.delta.signature;
|
|
785
|
+
if (open?.block.type === "thinking") {
|
|
786
|
+
open.block.thinkingSignature = open.block.thinkingSignature || "";
|
|
787
|
+
open.block.thinkingSignature += event.delta.signature;
|
|
603
788
|
}
|
|
604
789
|
}
|
|
605
790
|
}
|
|
606
791
|
else if (event.type === "content_block_stop") {
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
if (
|
|
610
|
-
|
|
792
|
+
const open = openBlock(event.index);
|
|
793
|
+
openBlocks.delete(event.index);
|
|
794
|
+
if (open) {
|
|
795
|
+
const { block, contentIndex: index } = open;
|
|
611
796
|
if (block.type === "text") {
|
|
612
797
|
stream.push({
|
|
613
798
|
type: "text_end",
|
|
@@ -624,11 +809,8 @@ export const stream = (model, context, options) => {
|
|
|
624
809
|
partial: output,
|
|
625
810
|
});
|
|
626
811
|
}
|
|
627
|
-
else if (block.type === "toolCall") {
|
|
628
|
-
Object.assign(block,
|
|
629
|
-
// Finalize in-place and strip the scratch buffer so replay only
|
|
630
|
-
// carries parsed arguments.
|
|
631
|
-
delete block.partialJson;
|
|
812
|
+
else if (block.type === "toolCall" && open.arguments !== undefined) {
|
|
813
|
+
Object.assign(block, open.arguments.finish());
|
|
632
814
|
stream.push({
|
|
633
815
|
type: "toolcall_end",
|
|
634
816
|
contentIndex: index,
|
|
@@ -680,6 +862,11 @@ export const stream = (model, context, options) => {
|
|
|
680
862
|
output.usage.totalTokens =
|
|
681
863
|
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
682
864
|
calculateCost(usageModel, output.usage);
|
|
865
|
+
if (isOpenRouterModel(model) && event.usage) {
|
|
866
|
+
// OpenRouter adds billing fields to streamed message_delta usage at runtime,
|
|
867
|
+
// although its OpenAPI schema and the Anthropic SDK do not type those fields.
|
|
868
|
+
applyOpenRouterReportedCost(output.usage, event.usage);
|
|
869
|
+
}
|
|
683
870
|
}
|
|
684
871
|
}
|
|
685
872
|
if (options?.signal?.aborted) {
|
|
@@ -691,6 +878,8 @@ export const stream = (model, context, options) => {
|
|
|
691
878
|
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
692
879
|
throw new Error(output.errorMessage || "An unknown error occurred");
|
|
693
880
|
}
|
|
881
|
+
if (thread)
|
|
882
|
+
recordThreadReply(model, thread, params, output, isOAuth, compat.allowEmptySignature);
|
|
694
883
|
if (inputTransformations && inputTransformations.length > 0) {
|
|
695
884
|
appendAssistantMessageDiagnostic(output, {
|
|
696
885
|
type: "anthropic_input_transformations",
|
|
@@ -708,13 +897,8 @@ export const stream = (model, context, options) => {
|
|
|
708
897
|
stream.end();
|
|
709
898
|
}
|
|
710
899
|
catch (error) {
|
|
711
|
-
for (const block of output.content) {
|
|
712
|
-
delete block.index;
|
|
713
|
-
// partialJson is only a streaming scratch buffer; never persist it.
|
|
714
|
-
delete block.partialJson;
|
|
715
|
-
}
|
|
716
900
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
717
|
-
output.errorMessage = error instanceof Error ? error
|
|
901
|
+
output.errorMessage = error instanceof Error ? withCauseDetail(error) : JSON.stringify(error);
|
|
718
902
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
719
903
|
stream.end();
|
|
720
904
|
}
|
|
@@ -849,7 +1033,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
|
|
|
849
1033
|
});
|
|
850
1034
|
return { client, isOAuthToken: false };
|
|
851
1035
|
}
|
|
852
|
-
function getBetaFeatures(model, context, isOAuthToken, nativeToolChanges,
|
|
1036
|
+
function getBetaFeatures(model, context, options, { isOAuthToken, nativeToolChanges, messageThreads }) {
|
|
853
1037
|
let configuredFeatures;
|
|
854
1038
|
for (const headers of [model.headers, options?.headers]) {
|
|
855
1039
|
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
@@ -885,9 +1069,11 @@ function getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, option
|
|
|
885
1069
|
}
|
|
886
1070
|
if (nativeToolChanges)
|
|
887
1071
|
features.push(INLINE_TOOLS_BETA);
|
|
1072
|
+
if (messageThreads)
|
|
1073
|
+
features.push(MESSAGE_THREADS_BETA);
|
|
888
1074
|
return [...new Set(features)];
|
|
889
1075
|
}
|
|
890
|
-
function buildParams(model, context, isOAuthToken, options,
|
|
1076
|
+
function buildParams(model, context, isOAuthToken, options, flags) {
|
|
891
1077
|
const { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);
|
|
892
1078
|
const compat = getAnthropicCompat(model);
|
|
893
1079
|
const initialSystemMessage = getInitialSystemMessage(context.messages);
|
|
@@ -900,11 +1086,8 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
900
1086
|
// active tool to anchor the placeholder. Otherwise the current tool list is sent.
|
|
901
1087
|
let hasPreferredStrictTools = false;
|
|
902
1088
|
const convertToolDefinitions = (tools, toolCacheControl) => {
|
|
903
|
-
const definitions = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl, disablePreferredStrictTools);
|
|
904
|
-
if (definitions.some((definition, index) => {
|
|
905
|
-
const config = tools[index].constrainedSampling;
|
|
906
|
-
return definition.strict === true && config && config.type === "json_schema" && config.strict === "prefer";
|
|
907
|
-
})) {
|
|
1089
|
+
const definitions = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl, flags.disablePreferredStrictTools);
|
|
1090
|
+
if (definitions.some((definition, index) => definition.strict === true && prefersJsonSchemaStrictSampling(tools[index]))) {
|
|
908
1091
|
hasPreferredStrictTools = true;
|
|
909
1092
|
}
|
|
910
1093
|
return definitions;
|
|
@@ -916,7 +1099,11 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
916
1099
|
const effortMessages = model.compat?.supportsMidConvoEffort === true && !thinkingDisabled;
|
|
917
1100
|
const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, effortMessages ? model.provider : undefined, nativeToolChanges ? convertToolDefinitions : undefined);
|
|
918
1101
|
const activeEffort = options?.effort ?? "high";
|
|
919
|
-
const betaFeatures = getBetaFeatures(model, context,
|
|
1102
|
+
const betaFeatures = getBetaFeatures(model, context, options, {
|
|
1103
|
+
isOAuthToken,
|
|
1104
|
+
nativeToolChanges,
|
|
1105
|
+
messageThreads: flags.messageThreads,
|
|
1106
|
+
});
|
|
920
1107
|
const params = {
|
|
921
1108
|
model: model.id,
|
|
922
1109
|
messages: effortMessages ? insertThinkingLevelMessages(converted, activeEffort) : converted.messages,
|
|
@@ -985,7 +1172,10 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
|
|
|
985
1172
|
display: options?.thinkingDisplay ?? "summarized",
|
|
986
1173
|
block_binding: { prefix_mismatch_behavior: "drop_block" },
|
|
987
1174
|
};
|
|
988
|
-
|
|
1175
|
+
// Effort markers set the level for every reply. The top-level effort is part of the cached
|
|
1176
|
+
// prompt, so it stays fixed. It is "low" because organizations can cap effort and reject
|
|
1177
|
+
// requests containing any level above the cap.
|
|
1178
|
+
params.output_config = { effort: "low" };
|
|
989
1179
|
}
|
|
990
1180
|
else if (model.reasoning && options?.thinkingEnabled) {
|
|
991
1181
|
// Default to "summarized" so Opus 4.7 and Mythos Preview behave like
|
|
@@ -1045,7 +1235,9 @@ convertToolDefinitions) {
|
|
|
1045
1235
|
const params = [];
|
|
1046
1236
|
const assistantLevels = new Map();
|
|
1047
1237
|
// Later system messages are held back and emitted directly before the next assistant
|
|
1048
|
-
// message (or at the end of the transcript).
|
|
1238
|
+
// message that is sent (or at the end of the transcript). Assistant messages that convert to
|
|
1239
|
+
// no blocks are dropped, so holding past them keeps an update from landing before a user
|
|
1240
|
+
// message (#11091). Anthropic requires `tool_result` blocks to
|
|
1049
1241
|
// immediately follow their `tool_use`, so a system message between them is rejected; this
|
|
1050
1242
|
// also mirrors where the managed-effort system messages are inserted. As a result an
|
|
1051
1243
|
// update placed before a user message in the transcript lands after it on the wire.
|
|
@@ -1118,7 +1310,6 @@ convertToolDefinitions) {
|
|
|
1118
1310
|
}
|
|
1119
1311
|
}
|
|
1120
1312
|
else if (msg.role === "assistant") {
|
|
1121
|
-
flushPendingSystemMessages();
|
|
1122
1313
|
const blocks = [];
|
|
1123
1314
|
for (const block of msg.content) {
|
|
1124
1315
|
if (block.type === "text") {
|
|
@@ -1176,6 +1367,7 @@ convertToolDefinitions) {
|
|
|
1176
1367
|
}
|
|
1177
1368
|
if (blocks.length === 0)
|
|
1178
1369
|
continue;
|
|
1370
|
+
flushPendingSystemMessages();
|
|
1179
1371
|
const messageIndex = params.length;
|
|
1180
1372
|
params.push({
|
|
1181
1373
|
role: "assistant",
|
|
@@ -1299,16 +1491,19 @@ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supp
|
|
|
1299
1491
|
if (!tools)
|
|
1300
1492
|
return [];
|
|
1301
1493
|
return tools.map((tool, index) => {
|
|
1302
|
-
const
|
|
1303
|
-
const strict = disablePreferredStrictTools && config && config.type === "json_schema" && config.strict === "prefer"
|
|
1494
|
+
const strict = disablePreferredStrictTools && prefersJsonSchemaStrictSampling(tool)
|
|
1304
1495
|
? undefined
|
|
1305
1496
|
: resolveJsonSchemaStrictSampling(tool, supportsStrictTools, isAnthropicStrictUnsupportedKeyword);
|
|
1306
1497
|
const parameters = getJsonSchemaToolParameters(tool, strict);
|
|
1307
1498
|
const schema = parameters;
|
|
1499
|
+
// Keep shared definitions so property $refs still resolve. Other root keywords stay out:
|
|
1500
|
+
// Anthropic rejects root anyOf/oneOf/allOf with a 400.
|
|
1308
1501
|
const legacyInputSchema = {
|
|
1309
1502
|
type: "object",
|
|
1310
1503
|
properties: schema.properties ?? {},
|
|
1311
1504
|
required: schema.required ?? [],
|
|
1505
|
+
...(schema.$defs === undefined ? {} : { $defs: schema.$defs }),
|
|
1506
|
+
...(schema.definitions === undefined ? {} : { definitions: schema.definitions }),
|
|
1312
1507
|
};
|
|
1313
1508
|
const inputSchema = strict === true
|
|
1314
1509
|
? {
|