@earendil-works/pi-ai 0.85.1 → 0.86.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 +50 -6
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +130 -86
- 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 +17 -5
- 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 +13 -12
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/google-generative-ai.d.ts.map +1 -1
- package/dist/api/google-generative-ai.js +21 -73
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-shared.d.ts +13 -4
- package/dist/api/google-shared.d.ts.map +1 -1
- package/dist/api/google-shared.js +55 -4
- 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 +25 -65
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/mistral-conversations.d.ts.map +1 -1
- package/dist/api/mistral-conversations.js +19 -12
- 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 +26 -23
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts +6 -4
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +40 -58
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts +7 -5
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +56 -56
- 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 +14 -19
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/pi-messages.d.ts +3 -3
- package/dist/api/pi-messages.d.ts.map +1 -1
- package/dist/api/pi-messages.js +2 -2
- package/dist/api/pi-messages.js.map +1 -1
- package/dist/api/simple-options.d.ts +3 -3
- package/dist/api/simple-options.d.ts.map +1 -1
- package/dist/api/simple-options.js.map +1 -1
- package/dist/api/transform-messages.d.ts.map +1 -1
- package/dist/api/transform-messages.js +21 -7
- package/dist/api/transform-messages.js.map +1 -1
- package/dist/auth/oauth/load.d.ts +2 -0
- package/dist/auth/oauth/load.d.ts.map +1 -1
- package/dist/auth/oauth/load.js +5 -0
- package/dist/auth/oauth/load.js.map +1 -1
- package/dist/auth/oauth/meta.d.ts +17 -0
- package/dist/auth/oauth/meta.d.ts.map +1 -0
- package/dist/auth/oauth/meta.js +190 -0
- package/dist/auth/oauth/meta.js.map +1 -0
- package/dist/bun-oauth.d.ts.map +1 -1
- package/dist/bun-oauth.js +2 -0
- package/dist/bun-oauth.js.map +1 -1
- package/dist/compat.d.ts +3 -3
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +9 -6
- package/dist/compat.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/image-models.generated.d.ts +30 -0
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +34 -4
- package/dist/image-models.generated.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +4 -3
- 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 +4 -0
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +5 -2
- 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/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/baseten.json +1 -1
- package/dist/providers/data/cerebras.json +1 -1
- package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/dist/providers/data/deepseek.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/meta.json +1 -0
- 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.json +1 -1
- package/dist/providers/data/radius.json +1 -0
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/data/zai-coding-cn.json +1 -1
- package/dist/providers/data/zai.json +1 -1
- package/dist/providers/faux.d.ts +2 -2
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +11 -11
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/meta.d.ts +3 -0
- package/dist/providers/meta.d.ts.map +1 -0
- package/dist/providers/meta.js +24 -0
- package/dist/providers/meta.js.map +1 -0
- package/dist/providers/meta.models.d.ts +4 -0
- package/dist/providers/meta.models.d.ts.map +1 -0
- package/dist/providers/meta.models.js +6 -0
- package/dist/providers/meta.models.js.map +1 -0
- package/dist/providers/opencode-go.d.ts.map +1 -1
- package/dist/providers/opencode-go.js +4 -3
- package/dist/providers/opencode-go.js.map +1 -1
- package/dist/providers/opencode-headers.d.ts +4 -0
- package/dist/providers/opencode-headers.d.ts.map +1 -0
- package/dist/providers/opencode-headers.js +22 -0
- package/dist/providers/opencode-headers.js.map +1 -0
- package/dist/providers/opencode.d.ts.map +1 -1
- package/dist/providers/opencode.js +5 -4
- package/dist/providers/opencode.js.map +1 -1
- package/dist/providers/radius.d.ts.map +1 -1
- package/dist/providers/radius.js +19 -5
- package/dist/providers/radius.js.map +1 -1
- package/dist/providers/radius.models.d.ts +4 -0
- package/dist/providers/radius.models.d.ts.map +1 -0
- package/dist/providers/radius.models.js +6 -0
- package/dist/providers/radius.models.js.map +1 -0
- package/dist/types.d.ts +98 -27
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/diagnostics.d.ts +3 -2
- package/dist/utils/diagnostics.d.ts.map +1 -1
- package/dist/utils/diagnostics.js.map +1 -1
- package/dist/utils/estimate.d.ts +2 -2
- package/dist/utils/estimate.d.ts.map +1 -1
- package/dist/utils/estimate.js +8 -29
- package/dist/utils/estimate.js.map +1 -1
- package/dist/utils/event-stream.d.ts.map +1 -1
- package/dist/utils/event-stream.js +25 -7
- package/dist/utils/event-stream.js.map +1 -1
- package/dist/utils/overflow.d.ts +1 -0
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +11 -5
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/retry.d.ts +6 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +9 -1
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/text.d.ts +9 -1
- package/dist/utils/text.d.ts.map +1 -1
- package/dist/utils/text.js +26 -0
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/transcript.d.ts +84 -0
- package/dist/utils/transcript.d.ts.map +1 -0
- package/dist/utils/transcript.js +204 -0
- package/dist/utils/transcript.js.map +1 -0
- package/package.json +9 -9
- package/dist/utils/deferred-tools.d.ts +0 -9
- package/dist/utils/deferred-tools.d.ts.map +0 -1
- package/dist/utils/deferred-tools.js +0 -36
- package/dist/utils/deferred-tools.js.map +0 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
2
|
import { calculateCost } from "../models.js";
|
|
3
|
-
import { splitDeferredTools } from "../utils/deferred-tools.js";
|
|
4
3
|
import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
|
|
5
4
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
6
5
|
import { headersToRecord } from "../utils/headers.js";
|
|
@@ -9,6 +8,8 @@ import { getPiUserAgent } from "../utils/pi-user-agent.js";
|
|
|
9
8
|
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
10
9
|
import { retryProviderRequest } from "../utils/provider-retry.js";
|
|
11
10
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
11
|
+
import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
|
|
12
|
+
import { getCurrentTools, getDeclaredTools, getInitialSystemMessage, hasToolRedefinitions, resolveTranscript, } from "../utils/transcript.js";
|
|
12
13
|
import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling } from "./constrained-sampling.js";
|
|
13
14
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
14
15
|
import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
|
|
@@ -114,36 +115,38 @@ const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
|
114
115
|
const SERVER_SIDE_FALLBACK_BETA = "server-side-fallback-2026-07-01";
|
|
115
116
|
const MID_CONVERSATION_OUTPUT_CONFIG_BETA = "mid-conversation-output-config-2026-07-01";
|
|
116
117
|
const THINKING_BINDING_CONTROLS_BETA = "thinking-binding-controls-2026-08-01";
|
|
118
|
+
const MID_CONVERSATION_TOOL_CHANGES_BETA = "mid-conversation-tool-changes-2026-07-01";
|
|
119
|
+
/**
|
|
120
|
+
* Stable deferred tool declared whenever native tool changes are in use. Anthropic adds
|
|
121
|
+
* hidden prompt scaffolding as soon as any tool has `defer_loading`; declaring this
|
|
122
|
+
* placeholder from the first request keeps that scaffolding in the cached prefix, so the
|
|
123
|
+
* first real late tool does not invalidate the cache (measured: full miss without it).
|
|
124
|
+
* It is never activated and the model cannot see it.
|
|
125
|
+
*/
|
|
126
|
+
const DEFERRED_TOOL_PLACEHOLDER = {
|
|
127
|
+
name: "__pi_deferred_placeholder__",
|
|
128
|
+
description: "Reserved placeholder. Never available. Never call this.",
|
|
129
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
130
|
+
defer_loading: true,
|
|
131
|
+
};
|
|
117
132
|
function shouldUseServerSideFallbackBeta(model) {
|
|
118
133
|
return (model.compat?.allowedFallbackModels?.length ?? 0) > 0;
|
|
119
134
|
}
|
|
120
135
|
function getAnthropicCompat(model) {
|
|
136
|
+
const isOpenRouter = model.provider === "openrouter" || model.baseUrl.includes("openrouter.ai");
|
|
121
137
|
return {
|
|
122
138
|
supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? true,
|
|
123
139
|
supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? true,
|
|
124
|
-
sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ??
|
|
140
|
+
sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? isOpenRouter,
|
|
141
|
+
sessionAffinityFormat: model.compat?.sessionAffinityFormat ?? (isOpenRouter ? "openrouter" : undefined),
|
|
125
142
|
supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? true,
|
|
126
143
|
supportsTemperature: model.compat?.supportsTemperature ?? true,
|
|
127
144
|
allowEmptySignature: model.compat?.allowEmptySignature ?? false,
|
|
128
145
|
supportsStrictTools: model.compat?.supportsStrictTools ?? false,
|
|
129
|
-
|
|
146
|
+
supportsMidConvoSystemMessages: model.compat?.supportsMidConvoSystemMessages ?? false,
|
|
147
|
+
supportsMidConvoToolChanges: model.compat?.supportsMidConvoToolChanges ?? false,
|
|
130
148
|
};
|
|
131
149
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Default for `supportsToolReferences`: first-party Anthropic models except
|
|
134
|
-
* Haiku (rejects client-side tool_reference blocks) and models that predate
|
|
135
|
-
* tool search (Claude 3.x, Opus/Sonnet 4.0, Opus 4.1).
|
|
136
|
-
*/
|
|
137
|
-
function defaultSupportsToolReferences(model) {
|
|
138
|
-
if (model.provider !== "anthropic" || model.id.includes("haiku"))
|
|
139
|
-
return false;
|
|
140
|
-
const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
|
|
141
|
-
if (!version)
|
|
142
|
-
return false;
|
|
143
|
-
const major = Number(version[1]);
|
|
144
|
-
const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
|
|
145
|
-
return major > 4 || (major === 4 && minor >= 5);
|
|
146
|
-
}
|
|
147
150
|
function mergeHeaders(...headerSources) {
|
|
148
151
|
const merged = {};
|
|
149
152
|
for (const headers of headerSources) {
|
|
@@ -329,6 +332,8 @@ async function* iterateAnthropicEvents(response, signal) {
|
|
|
329
332
|
}
|
|
330
333
|
export const stream = (model, context, options) => {
|
|
331
334
|
const stream = new AssistantMessageEventStream();
|
|
335
|
+
const normalizedContext = resolveTranscript(context, getAnthropicCompat(model).supportsMidConvoSystemMessages);
|
|
336
|
+
const currentTools = getCurrentTools(normalizedContext.messages);
|
|
332
337
|
(async () => {
|
|
333
338
|
const providerThinkingLevel = model.compat?.supportsMidConvoEffort ? (options?.effort ?? "high") : undefined;
|
|
334
339
|
const output = {
|
|
@@ -363,9 +368,9 @@ export const stream = (model, context, options) => {
|
|
|
363
368
|
assertRequestAuth(model.provider, apiKey, options?.headers);
|
|
364
369
|
let copilotDynamicHeaders;
|
|
365
370
|
if (model.provider === "github-copilot") {
|
|
366
|
-
const hasImages = hasCopilotVisionInput(
|
|
371
|
+
const hasImages = hasCopilotVisionInput(normalizedContext.messages);
|
|
367
372
|
copilotDynamicHeaders = buildCopilotDynamicHeaders({
|
|
368
|
-
messages:
|
|
373
|
+
messages: normalizedContext.messages,
|
|
369
374
|
hasImages,
|
|
370
375
|
});
|
|
371
376
|
}
|
|
@@ -375,7 +380,7 @@ export const stream = (model, context, options) => {
|
|
|
375
380
|
client = created.client;
|
|
376
381
|
isOAuth = created.isOAuthToken;
|
|
377
382
|
}
|
|
378
|
-
let params = buildParams(model,
|
|
383
|
+
let params = buildParams(model, normalizedContext, isOAuth, options);
|
|
379
384
|
const nextParams = await options?.onPayload?.(params, model);
|
|
380
385
|
if (nextParams !== undefined) {
|
|
381
386
|
params = { ...nextParams, stream: true };
|
|
@@ -399,11 +404,13 @@ export const stream = (model, context, options) => {
|
|
|
399
404
|
const transformations = event.message.input_transformations;
|
|
400
405
|
if (Array.isArray(transformations))
|
|
401
406
|
inputTransformations = transformations;
|
|
402
|
-
|
|
403
|
-
|
|
407
|
+
const responseModel = event.message.model;
|
|
408
|
+
if (responseModel !== model.id)
|
|
409
|
+
output.responseModel = responseModel;
|
|
410
|
+
const fallbackCost = responseModel === model.id
|
|
404
411
|
? undefined
|
|
405
|
-
: model.compat?.allowedFallbackModels?.find((fallback) => fallback.provider === model.provider && fallback.model ===
|
|
406
|
-
usageModel = fallbackCost ? { ...model, id:
|
|
412
|
+
: model.compat?.allowedFallbackModels?.find((fallback) => fallback.provider === model.provider && fallback.model === responseModel)?.cost;
|
|
413
|
+
usageModel = fallbackCost ? { ...model, id: responseModel, cost: fallbackCost } : model;
|
|
407
414
|
// Capture initial token usage from message_start event
|
|
408
415
|
// This ensures we have input token counts even if the stream is aborted early
|
|
409
416
|
output.usage.input = event.message.usage.input_tokens || 0;
|
|
@@ -458,7 +465,7 @@ export const stream = (model, context, options) => {
|
|
|
458
465
|
type: "toolCall",
|
|
459
466
|
id: event.content_block.id,
|
|
460
467
|
name: isOAuth
|
|
461
|
-
? fromClaudeCodeName(event.content_block.name,
|
|
468
|
+
? fromClaudeCodeName(event.content_block.name, currentTools)
|
|
462
469
|
: event.content_block.name,
|
|
463
470
|
arguments: event.content_block.input ?? {},
|
|
464
471
|
partialJson: "",
|
|
@@ -722,7 +729,12 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
|
|
|
722
729
|
return { client, isOAuthToken: true };
|
|
723
730
|
}
|
|
724
731
|
// API key or header-owned auth.
|
|
725
|
-
const
|
|
732
|
+
const compat = getAnthropicCompat(model);
|
|
733
|
+
const sessionAffinityHeaders = {};
|
|
734
|
+
if (sessionId && compat.sendSessionAffinityHeaders) {
|
|
735
|
+
const header = compat.sessionAffinityFormat === "openrouter" ? "x-session-id" : "x-session-affinity";
|
|
736
|
+
sessionAffinityHeaders[header] = sessionId;
|
|
737
|
+
}
|
|
726
738
|
const defaultHeaders = mergeClientHeaders({
|
|
727
739
|
accept: "application/json",
|
|
728
740
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
@@ -737,7 +749,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
|
|
|
737
749
|
});
|
|
738
750
|
return { client, isOAuthToken: false };
|
|
739
751
|
}
|
|
740
|
-
function getBetaFeatures(model, context, isOAuthToken, options) {
|
|
752
|
+
function getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, options) {
|
|
741
753
|
let configuredFeatures;
|
|
742
754
|
for (const headers of [model.headers, options?.headers]) {
|
|
743
755
|
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
@@ -771,24 +783,28 @@ function getBetaFeatures(model, context, isOAuthToken, options) {
|
|
|
771
783
|
if (model.compat?.supportsMidConvoEffort === true) {
|
|
772
784
|
features.push(MID_CONVERSATION_OUTPUT_CONFIG_BETA, THINKING_BINDING_CONTROLS_BETA);
|
|
773
785
|
}
|
|
786
|
+
if (nativeToolChanges)
|
|
787
|
+
features.push(MID_CONVERSATION_TOOL_CHANGES_BETA);
|
|
774
788
|
return [...new Set(features)];
|
|
775
789
|
}
|
|
776
790
|
function buildParams(model, context, isOAuthToken, options) {
|
|
777
791
|
const { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);
|
|
778
792
|
const compat = getAnthropicCompat(model);
|
|
793
|
+
const initialSystemMessage = getInitialSystemMessage(context.messages);
|
|
794
|
+
const initialSystemText = initialSystemMessage ? getSystemMessageText(initialSystemMessage) : "";
|
|
779
795
|
const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const converted = convertMessages(
|
|
796
|
+
const conversationMessages = initialSystemMessage ? transformedMessages.slice(1) : transformedMessages;
|
|
797
|
+
// Native tool changes reference tools by name, so a redefined name cannot be expressed,
|
|
798
|
+
// and Anthropic rejects a tool list where every tool is deferred, so there must be an
|
|
799
|
+
// initial active tool to anchor the deferred ones. Otherwise the current tool list is sent.
|
|
800
|
+
const initialTools = initialSystemMessage?.toolsAdded ?? [];
|
|
801
|
+
const nativeToolChanges = compat.supportsMidConvoSystemMessages &&
|
|
802
|
+
compat.supportsMidConvoToolChanges &&
|
|
803
|
+
initialTools.length > 0 &&
|
|
804
|
+
!hasToolRedefinitions(context.messages);
|
|
805
|
+
const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, model.compat?.supportsMidConvoEffort === true ? model.provider : undefined, nativeToolChanges);
|
|
790
806
|
const activeEffort = options?.effort ?? "high";
|
|
791
|
-
const betaFeatures = getBetaFeatures(model, context, isOAuthToken, options);
|
|
807
|
+
const betaFeatures = getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, options);
|
|
792
808
|
const params = {
|
|
793
809
|
model: model.id,
|
|
794
810
|
messages: model.compat?.supportsMidConvoEffort === true
|
|
@@ -807,20 +823,20 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
807
823
|
...(cacheControl ? { cache_control: cacheControl } : {}),
|
|
808
824
|
},
|
|
809
825
|
];
|
|
810
|
-
if (
|
|
826
|
+
if (initialSystemText) {
|
|
811
827
|
params.system.push({
|
|
812
828
|
type: "text",
|
|
813
|
-
text: sanitizeSurrogates(
|
|
829
|
+
text: sanitizeSurrogates(initialSystemText),
|
|
814
830
|
...(cacheControl ? { cache_control: cacheControl } : {}),
|
|
815
831
|
});
|
|
816
832
|
}
|
|
817
833
|
}
|
|
818
|
-
else if (
|
|
834
|
+
else if (initialSystemText) {
|
|
819
835
|
// Add cache control to system prompt for non-OAuth tokens
|
|
820
836
|
params.system = [
|
|
821
837
|
{
|
|
822
838
|
type: "text",
|
|
823
|
-
text: sanitizeSurrogates(
|
|
839
|
+
text: sanitizeSurrogates(initialSystemText),
|
|
824
840
|
...(cacheControl ? { cache_control: cacheControl } : {}),
|
|
825
841
|
},
|
|
826
842
|
];
|
|
@@ -832,12 +848,26 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
832
848
|
compat.supportsTemperature) {
|
|
833
849
|
params.temperature = options.temperature;
|
|
834
850
|
}
|
|
835
|
-
|
|
851
|
+
const toolCacheControl = compat.supportsCacheControlOnTools ? cacheControl : undefined;
|
|
852
|
+
if (nativeToolChanges) {
|
|
853
|
+
// Initial tools stay active with the cache breakpoint on the last one. Every later
|
|
854
|
+
// declaration is deferred and only surfaced by its `tool_addition` block; removed
|
|
855
|
+
// tools stay declared and are withdrawn by `tool_removal`. The request-level list
|
|
856
|
+
// therefore only grows, keeping the cached prefix intact across tool changes.
|
|
857
|
+
const initialNames = new Set(initialTools.map((tool) => tool.name));
|
|
858
|
+
const laterTools = getDeclaredTools(context.messages).filter((tool) => !initialNames.has(tool.name));
|
|
836
859
|
params.tools = [
|
|
837
|
-
...convertTools(
|
|
838
|
-
|
|
860
|
+
...convertTools(initialTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
|
|
861
|
+
DEFERRED_TOOL_PLACEHOLDER,
|
|
862
|
+
...convertTools(laterTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools).map((tool) => ({ ...tool, defer_loading: true })),
|
|
839
863
|
];
|
|
840
864
|
}
|
|
865
|
+
else {
|
|
866
|
+
const tools = getCurrentTools(context.messages);
|
|
867
|
+
if (tools.length > 0) {
|
|
868
|
+
params.tools = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
841
871
|
// Managed effort models always use adaptive thinking so prefix mismatches can
|
|
842
872
|
// be dropped instead of surfacing as persistent 400 responses.
|
|
843
873
|
if (model.compat?.supportsMidConvoEffort === true) {
|
|
@@ -897,41 +927,54 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
897
927
|
function normalizeToolCallId(id) {
|
|
898
928
|
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
899
929
|
}
|
|
900
|
-
function convertToolResult(msg
|
|
901
|
-
const references = [];
|
|
902
|
-
for (const name of msg.addedToolNames ?? []) {
|
|
903
|
-
const normalizedName = normalizeToolName(name);
|
|
904
|
-
if (!deferredToolNames.has(normalizedName) || loadedToolNames.has(normalizedName))
|
|
905
|
-
continue;
|
|
906
|
-
loadedToolNames.add(normalizedName);
|
|
907
|
-
references.push({
|
|
908
|
-
type: "tool_reference",
|
|
909
|
-
tool_name: isOAuthToken ? toClaudeCodeName(name) : name,
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
const convertedContent = convertContentBlocks(msg.content);
|
|
913
|
-
// Anthropic rejects tool references mixed with ordinary tool-result content.
|
|
930
|
+
function convertToolResult(msg) {
|
|
914
931
|
return {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
is_error: msg.isError,
|
|
920
|
-
},
|
|
921
|
-
siblingContent: references.length === 0
|
|
922
|
-
? []
|
|
923
|
-
: typeof convertedContent === "string"
|
|
924
|
-
? [{ type: "text", text: convertedContent }]
|
|
925
|
-
: convertedContent,
|
|
932
|
+
type: "tool_result",
|
|
933
|
+
tool_use_id: msg.toolCallId,
|
|
934
|
+
content: convertContentBlocks(msg.content),
|
|
935
|
+
is_error: msg.isError,
|
|
926
936
|
};
|
|
927
937
|
}
|
|
928
|
-
function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowEmptySignature = false,
|
|
938
|
+
function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowEmptySignature = false, managedProvider, nativeToolChanges = false) {
|
|
929
939
|
const params = [];
|
|
930
940
|
const assistantLevels = new Map();
|
|
931
|
-
|
|
941
|
+
// Later system messages are held back and emitted directly before the next assistant
|
|
942
|
+
// message (or at the end of the transcript). Anthropic requires `tool_result` blocks to
|
|
943
|
+
// immediately follow their `tool_use`, so a system message between them is rejected; this
|
|
944
|
+
// also mirrors where the managed-effort system messages are inserted. As a result an
|
|
945
|
+
// update placed before a user message in the transcript lands after it on the wire.
|
|
946
|
+
const pendingSystemMessages = [];
|
|
947
|
+
const flushPendingSystemMessages = () => {
|
|
948
|
+
params.push(...pendingSystemMessages);
|
|
949
|
+
pendingSystemMessages.length = 0;
|
|
950
|
+
};
|
|
932
951
|
for (let i = 0; i < transformedMessages.length; i++) {
|
|
933
952
|
const msg = transformedMessages[i];
|
|
934
|
-
if (msg.role === "
|
|
953
|
+
if (msg.role === "system") {
|
|
954
|
+
// Later system messages only reach this point when the model accepts them natively;
|
|
955
|
+
// otherwise the transcript was collapsed into the leading message before conversion.
|
|
956
|
+
const text = renderSystemMessageUpdate(msg);
|
|
957
|
+
const blocks = [];
|
|
958
|
+
if (text.length > 0)
|
|
959
|
+
blocks.push({ type: "text", text: sanitizeSurrogates(text) });
|
|
960
|
+
if (nativeToolChanges) {
|
|
961
|
+
for (const tool of msg.toolsRemoved ?? []) {
|
|
962
|
+
blocks.push({
|
|
963
|
+
type: "tool_removal",
|
|
964
|
+
tool: { type: "tool_reference", name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name },
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
for (const tool of msg.toolsAdded ?? []) {
|
|
968
|
+
blocks.push({
|
|
969
|
+
type: "tool_addition",
|
|
970
|
+
tool: { type: "tool_reference", name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name },
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (blocks.length > 0)
|
|
975
|
+
pendingSystemMessages.push({ role: "system", content: blocks });
|
|
976
|
+
}
|
|
977
|
+
else if (msg.role === "user") {
|
|
935
978
|
if (typeof msg.content === "string") {
|
|
936
979
|
if (msg.content.trim().length > 0) {
|
|
937
980
|
params.push({
|
|
@@ -974,6 +1017,7 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE
|
|
|
974
1017
|
}
|
|
975
1018
|
}
|
|
976
1019
|
else if (msg.role === "assistant") {
|
|
1020
|
+
flushPendingSystemMessages();
|
|
977
1021
|
const blocks = [];
|
|
978
1022
|
for (const block of msg.content) {
|
|
979
1023
|
if (block.type === "text") {
|
|
@@ -1046,31 +1090,32 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE
|
|
|
1046
1090
|
else if (msg.role === "toolResult") {
|
|
1047
1091
|
// Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint.
|
|
1048
1092
|
const toolResults = [];
|
|
1049
|
-
const siblingContent = [];
|
|
1050
1093
|
let j = i;
|
|
1051
1094
|
while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
|
|
1052
|
-
|
|
1053
|
-
toolResults.push(converted.toolResult);
|
|
1054
|
-
siblingContent.push(...converted.siblingContent);
|
|
1095
|
+
toolResults.push(convertToolResult(transformedMessages[j]));
|
|
1055
1096
|
j++;
|
|
1056
1097
|
}
|
|
1057
1098
|
// Skip the messages we've already processed.
|
|
1058
1099
|
i = j - 1;
|
|
1059
|
-
// Displaced reference-bearing results must follow every tool_result block.
|
|
1060
1100
|
params.push({
|
|
1061
1101
|
role: "user",
|
|
1062
|
-
content:
|
|
1102
|
+
content: toolResults,
|
|
1063
1103
|
});
|
|
1064
1104
|
}
|
|
1065
1105
|
}
|
|
1066
|
-
|
|
1106
|
+
flushPendingSystemMessages();
|
|
1107
|
+
// Add cache_control to the last user or system message to cache conversation history
|
|
1067
1108
|
if (cacheControl && params.length > 0) {
|
|
1068
1109
|
const lastMessage = params[params.length - 1];
|
|
1069
|
-
if (lastMessage.role === "user") {
|
|
1110
|
+
if (lastMessage.role === "user" || lastMessage.role === "system") {
|
|
1070
1111
|
if (Array.isArray(lastMessage.content)) {
|
|
1071
1112
|
const lastBlock = lastMessage.content[lastMessage.content.length - 1];
|
|
1072
1113
|
if (lastBlock &&
|
|
1073
|
-
(lastBlock.type === "text" ||
|
|
1114
|
+
(lastBlock.type === "text" ||
|
|
1115
|
+
lastBlock.type === "image" ||
|
|
1116
|
+
lastBlock.type === "tool_result" ||
|
|
1117
|
+
lastBlock.type === "tool_addition" ||
|
|
1118
|
+
lastBlock.type === "tool_removal")) {
|
|
1074
1119
|
lastBlock.cache_control = cacheControl;
|
|
1075
1120
|
}
|
|
1076
1121
|
}
|
|
@@ -1103,9 +1148,9 @@ function insertThinkingLevelMessages(converted, activeEffort) {
|
|
|
1103
1148
|
return messages;
|
|
1104
1149
|
}
|
|
1105
1150
|
function shouldUseFineGrainedToolStreamingBeta(model, context) {
|
|
1106
|
-
return
|
|
1151
|
+
return getCurrentTools(context.messages).length > 0 && !getAnthropicCompat(model).supportsEagerToolInputStreaming;
|
|
1107
1152
|
}
|
|
1108
|
-
function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl
|
|
1153
|
+
function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl) {
|
|
1109
1154
|
if (!tools)
|
|
1110
1155
|
return [];
|
|
1111
1156
|
return tools.map((tool, index) => {
|
|
@@ -1129,7 +1174,6 @@ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supp
|
|
|
1129
1174
|
...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),
|
|
1130
1175
|
...(strict === true ? { strict: true } : {}),
|
|
1131
1176
|
input_schema: inputSchema,
|
|
1132
|
-
...(deferLoading ? { defer_loading: true } : {}),
|
|
1133
1177
|
...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),
|
|
1134
1178
|
};
|
|
1135
1179
|
});
|