@deepstrike/sdk 0.2.51 → 0.2.60
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 +28 -28
- package/dist/agent-ir.d.ts +103 -0
- package/dist/agent-ir.js +134 -0
- package/dist/agent.d.ts +67 -0
- package/dist/agent.js +36 -0
- package/dist/collaboration/harness.js +1 -1
- package/dist/collaboration/modes/creator-verifier.d.ts +2 -7
- package/dist/collaboration/modes/creator-verifier.js +4 -6
- package/dist/collaboration/pool.d.ts +8 -20
- package/dist/collaboration/pool.js +27 -97
- package/dist/compat/anthropic/mcp.d.ts +15 -0
- package/dist/compat/anthropic/mcp.js +10 -0
- package/dist/compat/openai/agent.d.ts +34 -0
- package/dist/compat/openai/agent.js +24 -0
- package/dist/governance.d.ts +1 -17
- package/dist/governance.js +1 -34
- package/dist/guardrail.d.ts +6 -0
- package/dist/guardrail.js +1 -0
- package/dist/handoff-target.d.ts +12 -0
- package/dist/handoff-target.js +1 -0
- package/dist/harness/manifest.js +0 -4
- package/dist/index.d.ts +19 -7
- package/dist/index.js +8 -5
- package/dist/kernel.d.ts +2 -20
- package/dist/knowledge/public.d.ts +29 -0
- package/dist/knowledge/public.js +1 -0
- package/dist/mcp-server.d.ts +28 -0
- package/dist/mcp-server.js +1 -0
- package/dist/memory/agent.d.ts +2 -2
- package/dist/memory/agent.js +2 -2
- package/dist/memory/durable.d.ts +16 -0
- package/dist/memory/durable.js +46 -0
- package/dist/memory/in-memory-store.d.ts +9 -7
- package/dist/memory/in-memory-store.js +8 -2
- package/dist/memory/protocols.d.ts +22 -4
- package/dist/memory/public.d.ts +4 -3
- package/dist/memory/public.js +3 -2
- package/dist/providers/anthropic-adapter.d.ts +47 -0
- package/dist/providers/anthropic-adapter.js +444 -0
- package/dist/providers/anthropic-compatible.d.ts +2 -3
- package/dist/providers/anthropic-compatible.js +8 -5
- package/dist/providers/anthropic.d.ts +19 -23
- package/dist/providers/anthropic.js +169 -395
- package/dist/providers/base.js +43 -3
- package/dist/providers/capability-router.d.ts +29 -0
- package/dist/providers/capability-router.js +43 -0
- package/dist/providers/catalog.d.ts +16 -4
- package/dist/providers/catalog.js +112 -21
- package/dist/providers/content-normalization.d.ts +57 -0
- package/dist/providers/content-normalization.js +238 -0
- package/dist/providers/content-policy.d.ts +16 -0
- package/dist/providers/content-policy.js +39 -0
- package/dist/providers/credentials.d.ts +83 -0
- package/dist/providers/credentials.js +190 -0
- package/dist/providers/endpoints.d.ts +137 -0
- package/dist/providers/endpoints.js +128 -0
- package/dist/providers/factories.js +9 -3
- package/dist/providers/gemini-adapter.d.ts +33 -0
- package/dist/providers/gemini-adapter.js +272 -0
- package/dist/providers/gemini.d.ts +16 -3
- package/dist/providers/gemini.js +97 -195
- package/dist/providers/model-catalog.d.ts +37 -0
- package/dist/providers/model-catalog.js +62 -0
- package/dist/providers/model-registry.d.ts +98 -0
- package/dist/providers/model-registry.js +315 -0
- package/dist/providers/ollama-adapter.d.ts +65 -0
- package/dist/providers/ollama-adapter.js +188 -0
- package/dist/providers/ollama.d.ts +9 -4
- package/dist/providers/ollama.js +96 -109
- package/dist/providers/openai-chat-dialects.d.ts +154 -0
- package/dist/providers/openai-chat-dialects.js +179 -0
- package/dist/providers/openai-chat.d.ts +46 -18
- package/dist/providers/openai-chat.js +418 -51
- package/dist/providers/openai-responses-adapter.d.ts +42 -0
- package/dist/providers/openai-responses-adapter.js +341 -0
- package/dist/providers/openai-responses.d.ts +14 -33
- package/dist/providers/openai-responses.js +131 -264
- package/dist/providers/openai.d.ts +29 -76
- package/dist/providers/openai.js +195 -292
- package/dist/providers/protocol-adapter.d.ts +34 -0
- package/dist/providers/protocol-adapter.js +9 -0
- package/dist/providers/protocol-capabilities.d.ts +34 -0
- package/dist/providers/protocol-capabilities.js +44 -0
- package/dist/providers/provider-error.d.ts +31 -0
- package/dist/providers/provider-error.js +149 -0
- package/dist/providers/public.d.ts +26 -3
- package/dist/providers/public.js +13 -1
- package/dist/providers/registry.d.ts +7 -6
- package/dist/providers/registry.js +47 -20
- package/dist/providers/request-plan.d.ts +86 -0
- package/dist/providers/request-plan.js +175 -0
- package/dist/providers/usage-normalizer.d.ts +48 -0
- package/dist/providers/usage-normalizer.js +96 -0
- package/dist/providers/vendor-profiles.d.ts +2 -15
- package/dist/providers/vendor-profiles.js +14 -60
- package/dist/runtime/canonical-kernel-step.d.ts +10 -2
- package/dist/runtime/canonical-kernel-step.js +106 -32
- package/dist/runtime/context-policy.d.ts +10 -12
- package/dist/runtime/context-policy.js +6 -8
- package/dist/runtime/durable-content.d.ts +50 -0
- package/dist/runtime/durable-content.js +159 -0
- package/dist/runtime/execution-plane.d.ts +2 -2
- package/dist/runtime/execution-plane.js +2 -2
- package/dist/runtime/kernel-event-log.js +0 -1
- package/dist/runtime/kernel-step.d.ts +0 -1
- package/dist/runtime/kernel-step.js +4 -2
- package/dist/runtime/mcp-proxy-plane.d.ts +25 -1
- package/dist/runtime/mcp-proxy-plane.js +44 -6
- package/dist/runtime/output-schema.d.ts +1 -2
- package/dist/runtime/provider-replay.d.ts +1 -1
- package/dist/runtime/provider-replay.js +12 -27
- package/dist/runtime/reactive-session.d.ts +1 -1
- package/dist/runtime/reactive-session.js +2 -3
- package/dist/runtime/run-group.d.ts +1 -1
- package/dist/runtime/runner.d.ts +26 -40
- package/dist/runtime/runner.js +145 -63
- package/dist/runtime/session-log.d.ts +8 -1
- package/dist/runtime/session-log.js +42 -2
- package/dist/runtime/session-repair.d.ts +1 -1
- package/dist/runtime/session-repair.js +1 -1
- package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
- package/dist/runtime/sub-agent-orchestrator.js +8 -11
- package/dist/runtime/workflow-control-flow.d.ts +0 -4
- package/dist/runtime/workflow-control-flow.js +0 -16
- package/dist/session.d.ts +11 -0
- package/dist/session.js +1 -0
- package/dist/skill.d.ts +17 -0
- package/dist/skill.js +16 -0
- package/dist/skills/loader.d.ts +3 -0
- package/dist/tools/errors.d.ts +1 -3
- package/dist/tools/errors.js +1 -3
- package/dist/tools/index.d.ts +3 -0
- package/dist/types/agent.d.ts +21 -9
- package/dist/types/agent.js +30 -4
- package/dist/types.d.ts +128 -13
- package/package.json +3 -3
- package/dist/providers/deepseek.d.ts +0 -46
- package/dist/providers/deepseek.js +0 -97
- package/dist/providers/glm.d.ts +0 -25
- package/dist/providers/glm.js +0 -48
- package/dist/providers/kimi.d.ts +0 -23
- package/dist/providers/kimi.js +0 -30
- package/dist/providers/minimax.d.ts +0 -49
- package/dist/providers/minimax.js +0 -98
- package/dist/providers/profiles.d.ts +0 -1992
- package/dist/providers/profiles.js +0 -796
- package/dist/providers/qwen.d.ts +0 -38
- package/dist/providers/qwen.js +0 -97
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
2
|
import { assistantReplayKey } from "../runtime/provider-replay.js";
|
|
3
3
|
import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
4
|
-
import { CircuitBreaker
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"claude-opus-4-0": { maxTurns: 50 },
|
|
10
|
-
"claude-sonnet-4-6": { maxTurns: 25 },
|
|
11
|
-
"claude-sonnet-4-0": { maxTurns: 25 },
|
|
12
|
-
"claude-haiku-4-5": { maxTurns: 15 },
|
|
13
|
-
"claude-3-5-haiku-latest": { maxTurns: 15 },
|
|
14
|
-
};
|
|
4
|
+
import { CircuitBreaker } from "./base.js";
|
|
5
|
+
import { AnthropicMessagesAdapter, } from "./anthropic-adapter.js";
|
|
6
|
+
import { normalizeCanonicalAdapterInput, } from "./content-normalization.js";
|
|
7
|
+
import { endpointProfiles } from "./endpoints.js";
|
|
8
|
+
import { circuitOpenError, classifyProviderError } from "./provider-error.js";
|
|
15
9
|
export class AnthropicProvider {
|
|
16
10
|
client;
|
|
17
11
|
circuit;
|
|
18
12
|
maxRetries;
|
|
19
13
|
baseDelay;
|
|
20
14
|
model;
|
|
15
|
+
adapter = new AnthropicMessagesAdapter();
|
|
21
16
|
nativeAssistantBlocks = new Map();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
resolvedRuntimePolicy;
|
|
18
|
+
directNativeTokenCounting;
|
|
19
|
+
resolvedRuntime;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
22
|
+
throw new TypeError("AnthropicProvider requires a configuration object");
|
|
23
|
+
}
|
|
24
|
+
if (typeof config.apiKey !== "string" || config.apiKey.length === 0) {
|
|
25
|
+
throw new TypeError("AnthropicProvider requires a non-empty apiKey");
|
|
26
|
+
}
|
|
27
|
+
const c = {
|
|
28
|
+
model: "claude-sonnet-4-6",
|
|
29
|
+
retry: { maxRetries: 3, baseDelay: 1000 },
|
|
30
|
+
...config,
|
|
31
|
+
};
|
|
28
32
|
this.model = c.model ?? "claude-sonnet-4-6";
|
|
29
33
|
this.client = withServerRuntimeGuard(() => new Anthropic({
|
|
30
34
|
...(c.authMode === "bearer"
|
|
@@ -35,11 +39,14 @@ export class AnthropicProvider {
|
|
|
35
39
|
this.circuit = new CircuitBreaker();
|
|
36
40
|
this.maxRetries = c.retry?.maxRetries ?? 3;
|
|
37
41
|
this.baseDelay = c.retry?.baseDelay ?? 1000;
|
|
42
|
+
this.resolvedRuntimePolicy = c.runtimePolicy ?? {};
|
|
43
|
+
this.directNativeTokenCounting = c.baseURL === undefined
|
|
44
|
+
|| c.baseURL === endpointProfiles["anthropic.messages"].baseURL;
|
|
38
45
|
}
|
|
39
46
|
runtimePolicy() {
|
|
40
|
-
return
|
|
47
|
+
return this.resolvedRuntimePolicy;
|
|
41
48
|
}
|
|
42
|
-
/** Identity advertised in the descriptor; overridden by Anthropic-compatible vendors
|
|
49
|
+
/** Identity advertised in the descriptor; overridden by Anthropic-compatible vendors. */
|
|
43
50
|
providerName() {
|
|
44
51
|
return "anthropic";
|
|
45
52
|
}
|
|
@@ -59,419 +66,186 @@ export class AnthropicProvider {
|
|
|
59
66
|
},
|
|
60
67
|
};
|
|
61
68
|
}
|
|
69
|
+
bindResolvedRuntime(resolved) {
|
|
70
|
+
if (resolved.identity.protocol !== "anthropic-messages"
|
|
71
|
+
|| resolved.identity.providerId !== this.providerName()
|
|
72
|
+
|| resolved.identity.modelId !== this.model) {
|
|
73
|
+
throw new Error("AnthropicProvider received a mismatched resolved runtime");
|
|
74
|
+
}
|
|
75
|
+
this.resolvedRuntime = resolved;
|
|
76
|
+
}
|
|
62
77
|
peekProviderReplay(message) {
|
|
63
78
|
const blocks = this.nativeAssistantBlocks.get(assistantReplayKey(message));
|
|
64
|
-
return blocks?.length ? { native_blocks: blocks } : undefined;
|
|
79
|
+
return blocks?.length ? { protocol: "anthropic-messages", native_blocks: blocks } : undefined;
|
|
65
80
|
}
|
|
66
81
|
seedProviderReplay(message, replay) {
|
|
67
|
-
if (replay.native_blocks?.length) {
|
|
82
|
+
if (replay.protocol === "anthropic-messages" && replay.native_blocks?.length) {
|
|
68
83
|
this.nativeAssistantBlocks.set(assistantReplayKey(message), replay.native_blocks);
|
|
69
|
-
return;
|
|
70
84
|
}
|
|
71
|
-
// Legacy log without persisted native blocks: reconstruct neutral
|
|
72
|
-
// text + tool_use blocks from the transcript so a tool-use turn can be
|
|
73
|
-
// replayed. Thinking blocks were never persisted, so they are not recovered.
|
|
74
|
-
const blocks = reconstructAnthropicBlocks(message);
|
|
75
|
-
if (blocks.length)
|
|
76
|
-
this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
|
|
77
85
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
adapterInput(context, tools, extensions) {
|
|
87
|
+
const providerId = this.providerName();
|
|
88
|
+
const endpoint = Object.values(endpointProfiles).find(profile => profile.providerId === providerId && profile.protocol === "anthropic-messages") ?? endpointProfiles["anthropic.messages"];
|
|
89
|
+
const resolved = this.resolvedRuntime ?? {
|
|
90
|
+
identity: {
|
|
91
|
+
providerId,
|
|
92
|
+
modelId: this.model,
|
|
93
|
+
endpointId: endpoint.id,
|
|
94
|
+
protocol: "anthropic-messages",
|
|
95
|
+
},
|
|
96
|
+
model: {
|
|
97
|
+
id: `${providerId}/${this.model}`,
|
|
98
|
+
providerId,
|
|
99
|
+
kind: "generation",
|
|
100
|
+
intrinsic: {},
|
|
101
|
+
},
|
|
102
|
+
endpoint,
|
|
103
|
+
adapter: this,
|
|
104
|
+
effectiveCapabilities: compatibilityCapabilities(),
|
|
105
|
+
};
|
|
106
|
+
return normalizeCanonicalAdapterInput({
|
|
107
|
+
context,
|
|
108
|
+
tools,
|
|
109
|
+
resolved,
|
|
110
|
+
extensions,
|
|
111
|
+
replayForMessage: message => this.peekProviderReplay(message),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
buildPlan(context, tools, extensions) {
|
|
115
|
+
const input = this.adapterInput(context, tools, extensions);
|
|
116
|
+
return { input, plan: this.adapter.buildRequest(input) };
|
|
96
117
|
}
|
|
97
118
|
async complete(context, tools, extensions) {
|
|
119
|
+
const provider = this.providerName();
|
|
98
120
|
if (this.circuit.isOpen())
|
|
99
|
-
throw
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
throw circuitOpenError(provider);
|
|
122
|
+
let input;
|
|
123
|
+
let plan;
|
|
124
|
+
try {
|
|
125
|
+
;
|
|
126
|
+
({ input, plan } = this.buildPlan(context, tools, extensions));
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
throw classifyProviderError(provider, error);
|
|
130
|
+
}
|
|
105
131
|
let lastErr;
|
|
106
|
-
for (let
|
|
132
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
107
133
|
try {
|
|
108
|
-
const
|
|
109
|
-
...requestExtensions,
|
|
110
|
-
model: this.model,
|
|
111
|
-
max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
|
|
112
|
-
...(system ? { system } : {}),
|
|
113
|
-
messages: msgs,
|
|
114
|
-
...(tools.length ? { tools: this.buildTools(tools, !Array.isArray(system), strategy) } : {}),
|
|
115
|
-
}, extensions);
|
|
134
|
+
const raw = await this.createMessage(plan.params, plan.transport);
|
|
116
135
|
this.circuit.recordSuccess();
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (block.type === "text")
|
|
121
|
-
content += block.text;
|
|
122
|
-
else if (block.type === "tool_use") {
|
|
123
|
-
const tc = normalizeToolCall(block.id, block.name, block.input);
|
|
124
|
-
if (tc)
|
|
125
|
-
toolCalls.push(tc);
|
|
126
|
-
}
|
|
136
|
+
const decoded = this.adapter.decodeComplete(raw, { input });
|
|
137
|
+
if (decoded.replay?.native_blocks) {
|
|
138
|
+
this.rememberNativeBlocks(decoded.message, decoded.replay.native_blocks);
|
|
127
139
|
}
|
|
128
|
-
|
|
129
|
-
this.rememberNativeBlocks(message, resp.content);
|
|
130
|
-
return message;
|
|
140
|
+
return decoded.message;
|
|
131
141
|
}
|
|
132
|
-
catch (
|
|
133
|
-
lastErr =
|
|
142
|
+
catch (error) {
|
|
143
|
+
lastErr = error;
|
|
134
144
|
this.circuit.recordFailure();
|
|
135
|
-
if (
|
|
136
|
-
await new Promise(
|
|
145
|
+
if (attempt < this.maxRetries - 1) {
|
|
146
|
+
await new Promise(resolve => setTimeout(resolve, this.baseDelay * 2 ** attempt));
|
|
147
|
+
}
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
|
-
throw lastErr;
|
|
150
|
+
throw classifyProviderError(provider, lastErr);
|
|
151
|
+
}
|
|
152
|
+
/** Native measurement belongs to the verified official endpoint, not the wire protocol. */
|
|
153
|
+
async countTokens(context, tools, extensions) {
|
|
154
|
+
const enabled = this.resolvedRuntime
|
|
155
|
+
? this.resolvedRuntime.effectiveCapabilities.nativeTokenCounting.state === "supported"
|
|
156
|
+
: this.providerName() === "anthropic" && this.directNativeTokenCounting;
|
|
157
|
+
if (!enabled) {
|
|
158
|
+
throw new Error(`Native token counting is unavailable on ${this.providerName()} Anthropic-compatible endpoint`);
|
|
159
|
+
}
|
|
160
|
+
const { plan } = this.buildPlan(context, tools, extensions);
|
|
161
|
+
const { model, system, messages, tools: requestTools } = plan.params;
|
|
162
|
+
const response = await this.client.messages.countTokens({
|
|
163
|
+
model,
|
|
164
|
+
...(system ? { system } : {}),
|
|
165
|
+
messages,
|
|
166
|
+
...(requestTools ? { tools: requestTools } : {}),
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
inputTokens: response.input_tokens,
|
|
170
|
+
source: { kind: "native", provider: "anthropic" },
|
|
171
|
+
confidence: "exact",
|
|
172
|
+
};
|
|
140
173
|
}
|
|
141
174
|
async *stream(context, tools, extensions, _state, signal) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// attributed pro-rata when the response arrives. Honest annotation: Anthropic returns one
|
|
150
|
-
// scalar (no per-slot breakdown), so this is an estimate, not authoritative.
|
|
151
|
-
const slotBp = countCacheControlSlots(system, builtTools, msgs);
|
|
152
|
-
const toolBlocks = {};
|
|
153
|
-
const nativeBlocks = {};
|
|
154
|
-
let finalText = "";
|
|
155
|
-
const finalToolCalls = [];
|
|
156
|
-
const stream = this.streamMessage({
|
|
157
|
-
...requestExtensions,
|
|
158
|
-
model: this.model,
|
|
159
|
-
max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
|
|
160
|
-
...(system ? { system } : {}),
|
|
161
|
-
messages: msgs,
|
|
162
|
-
...(builtTools ? { tools: builtTools } : {}),
|
|
163
|
-
}, extensions, signal);
|
|
164
|
-
let uncachedInput = 0;
|
|
165
|
-
let cacheReadTokens = 0;
|
|
166
|
-
let cacheCreationTokens = 0;
|
|
167
|
-
let outputTokens = 0;
|
|
168
|
-
for await (const evt of stream) {
|
|
169
|
-
if (evt.type === "message_start" || evt.type === "message_delta") {
|
|
170
|
-
const usage = evt.usage ?? evt.message?.usage;
|
|
171
|
-
if (usage) {
|
|
172
|
-
// input + cache counts are cumulative and pinned at message_start; a
|
|
173
|
-
// later message_delta may omit them (null), so Math.max keeps the
|
|
174
|
-
// running totals from being clobbered back to zero.
|
|
175
|
-
uncachedInput = Math.max(uncachedInput, usage.input_tokens ?? 0);
|
|
176
|
-
cacheReadTokens = Math.max(cacheReadTokens, usage.cache_read_input_tokens ?? 0);
|
|
177
|
-
cacheCreationTokens = Math.max(cacheCreationTokens, usage.cache_creation_input_tokens ?? 0);
|
|
178
|
-
outputTokens = Math.max(outputTokens, usage.output_tokens ?? 0);
|
|
179
|
-
// inputTokens is the FULL prompt size (uncached + cache read + cache
|
|
180
|
-
// write). The kernel reads it as the authoritative prompt size for
|
|
181
|
-
// context-pressure/compaction — excluding cached tokens would make a
|
|
182
|
-
// cache-heavy turn look tiny and suppress compaction until a 413.
|
|
183
|
-
const inputTokens = uncachedInput + cacheReadTokens + cacheCreationTokens;
|
|
184
|
-
const bySlot = estimateCacheReadBySlot(cacheReadTokens, slotBp);
|
|
185
|
-
// stop_reason is only present on message_delta (the closing frame). `max_tokens` drives
|
|
186
|
-
// the kernel's output-cap recovery; other reasons (end_turn/tool_use) are informational.
|
|
187
|
-
const stopReason = evt.delta?.stop_reason;
|
|
188
|
-
yield {
|
|
189
|
-
type: "usage",
|
|
190
|
-
totalTokens: inputTokens + outputTokens,
|
|
191
|
-
inputTokens,
|
|
192
|
-
outputTokens,
|
|
193
|
-
cacheReadInputTokens: cacheReadTokens,
|
|
194
|
-
cacheCreationInputTokens: cacheCreationTokens,
|
|
195
|
-
...(bySlot ? { cacheReadInputTokensBySlot: bySlot } : {}),
|
|
196
|
-
...(stopReason ? { stopReason } : {}),
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
else if (evt.type === "content_block_start") {
|
|
201
|
-
nativeBlocks[evt.index] = { ...evt.content_block };
|
|
202
|
-
if (evt.content_block.type === "tool_use") {
|
|
203
|
-
toolBlocks[evt.index] = { id: evt.content_block.id, name: evt.content_block.name, argsBuf: "" };
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
else if (evt.type === "content_block_delta") {
|
|
207
|
-
const d = evt.delta;
|
|
208
|
-
if (d.type === "text_delta") {
|
|
209
|
-
finalText += d.text;
|
|
210
|
-
nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], text: String(nativeBlocks[evt.index]?.text ?? "") + d.text };
|
|
211
|
-
yield { type: "text_delta", delta: d.text };
|
|
212
|
-
}
|
|
213
|
-
else if (d.type === "thinking_delta") {
|
|
214
|
-
nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], thinking: String(nativeBlocks[evt.index]?.thinking ?? "") + d.thinking };
|
|
215
|
-
yield { type: "thinking_delta", delta: d.thinking };
|
|
216
|
-
}
|
|
217
|
-
else if (d.type === "signature_delta") {
|
|
218
|
-
nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], signature: String(nativeBlocks[evt.index]?.signature ?? "") + d.signature };
|
|
219
|
-
}
|
|
220
|
-
else if (d.type === "input_json_delta" && toolBlocks[evt.index]) {
|
|
221
|
-
toolBlocks[evt.index].argsBuf += d.partial_json;
|
|
222
|
-
}
|
|
175
|
+
const provider = this.providerName();
|
|
176
|
+
try {
|
|
177
|
+
const { input, plan } = this.buildPlan(context, tools, extensions);
|
|
178
|
+
const state = this.adapter.createStreamState({ input });
|
|
179
|
+
for await (const chunk of this.streamMessage(plan.params, plan.transport, signal)) {
|
|
180
|
+
for (const event of this.adapter.pushStreamChunk(chunk, state).events)
|
|
181
|
+
yield event;
|
|
223
182
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
args = {};
|
|
233
|
-
}
|
|
234
|
-
nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], input: args };
|
|
235
|
-
finalToolCalls.push({ id: tb.id, name: tb.name, arguments: JSON.stringify(args) });
|
|
236
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
183
|
+
const final = this.adapter.finishStream(state);
|
|
184
|
+
for (const event of final.events)
|
|
185
|
+
yield event;
|
|
186
|
+
if (final.replay?.native_blocks) {
|
|
187
|
+
this.rememberNativeBlocks({ content: state.finalText, toolCalls: state.finalToolCalls }, final.replay.native_blocks);
|
|
237
188
|
}
|
|
238
189
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return omitExtensionKeys(extensions, ["model", "messages", "system", "tools", "max_tokens", "stream"]);
|
|
243
|
-
}
|
|
244
|
-
hasBetas(extensions) {
|
|
245
|
-
const betas = extensions?.betas;
|
|
246
|
-
return Array.isArray(betas) && betas.length > 0;
|
|
190
|
+
catch (error) {
|
|
191
|
+
throw classifyProviderError(provider, error);
|
|
192
|
+
}
|
|
247
193
|
}
|
|
248
|
-
createMessage(params,
|
|
249
|
-
return
|
|
194
|
+
createMessage(params, transport) {
|
|
195
|
+
return (transport === "beta"
|
|
250
196
|
? this.client.beta.messages.create(params)
|
|
251
|
-
: this.client.messages.create(params);
|
|
197
|
+
: this.client.messages.create(params));
|
|
252
198
|
}
|
|
253
|
-
streamMessage(params,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
: this.client.messages.stream(params, opts));
|
|
199
|
+
streamMessage(params, transport, signal) {
|
|
200
|
+
const options = signal ? { signal } : undefined;
|
|
201
|
+
return (transport === "beta"
|
|
202
|
+
? this.client.beta.messages.stream(params, options)
|
|
203
|
+
: this.client.messages.stream(params, options));
|
|
259
204
|
}
|
|
205
|
+
// White-box test seams. Request construction itself belongs to the adapter.
|
|
260
206
|
buildSystem(context, strategy) {
|
|
261
|
-
|
|
262
|
-
// (stable only), or 2 blocks (stable + knowledge). The first turn `systemKnowledge`
|
|
263
|
-
// appears, the block count rises 1→2, which is a one-time prompt-cache invalidation
|
|
264
|
-
// (the knowledge prefix didn't exist to cache before). It is byte-stable thereafter;
|
|
265
|
-
// dynamic per-turn knowledge belongs in the uncached tail, not this block. An empty
|
|
266
|
-
// knowledge string is intentionally never emitted (the API rejects empty text blocks).
|
|
267
|
-
if (!context.systemStable && !context.systemKnowledge) {
|
|
268
|
-
return context.systemText || undefined;
|
|
269
|
-
}
|
|
270
|
-
// System cache_control is emitted under "default" and "system-only". Other strategies
|
|
271
|
-
// keep the text-block structure for protocol parity but omit cache_control.
|
|
272
|
-
const emitOnSystemBlocks = strategy === "default" || strategy === "system-only";
|
|
273
|
-
const cc = { type: "ephemeral" };
|
|
274
|
-
const blocks = [];
|
|
275
|
-
if (context.systemStable) {
|
|
276
|
-
blocks.push({ type: "text", text: context.systemStable, ...(emitOnSystemBlocks ? { cache_control: cc } : {}) });
|
|
277
|
-
}
|
|
278
|
-
if (context.systemKnowledge) {
|
|
279
|
-
blocks.push({ type: "text", text: context.systemKnowledge, ...(emitOnSystemBlocks ? { cache_control: cc } : {}) });
|
|
280
|
-
}
|
|
281
|
-
return blocks.length ? blocks : undefined;
|
|
207
|
+
return this.buildPlan(context, [], { cacheBreakpointStrategy: strategy }).plan.params.system;
|
|
282
208
|
}
|
|
283
209
|
buildMessages(context, strategy) {
|
|
284
|
-
|
|
285
|
-
// Cache breakpoints anchor on the stable history; the volatile State turn is
|
|
286
|
-
// appended AFTER them as the uncached tail (so the history prefix re-reads
|
|
287
|
-
// across turns). On un-rebuilt bindings stateTurn is absent and the state is
|
|
288
|
-
// already inside `turns` — rendered as-is above. `frozenPrefixLen` (P1-E) pins
|
|
289
|
-
// the deep breakpoint at the compaction boundary; absent ⇒ rolling-pair fallback.
|
|
290
|
-
applyMessageCacheControl(msgs, context.frozenPrefixLen, strategy);
|
|
291
|
-
if (context.stateTurn) {
|
|
292
|
-
// Render through toAnthropicMessages so assistant tool_use blocks and
|
|
293
|
-
// tool-role tool_result parts are serialized correctly — toAnthropicContent
|
|
294
|
-
// only handles contentParts/content and would silently drop toolCalls.
|
|
295
|
-
const stateMsgs = toAnthropicMessages([context.stateTurn], message => this.nativeAssistantBlocks.get(assistantReplayKey(message)));
|
|
296
|
-
msgs.push(...stateMsgs);
|
|
297
|
-
}
|
|
298
|
-
if (msgs.length === 0) {
|
|
299
|
-
msgs.push({ role: "user", content: "Proceed." });
|
|
300
|
-
}
|
|
301
|
-
return msgs;
|
|
210
|
+
return this.buildPlan(context, [], { cacheBreakpointStrategy: strategy }).plan.params.messages;
|
|
302
211
|
}
|
|
303
212
|
rememberNativeBlocks(message, blocks) {
|
|
304
213
|
if (!blocks.length)
|
|
305
214
|
return;
|
|
306
|
-
if (!message.toolCalls?.length && !blocks.some(
|
|
215
|
+
if (!message.toolCalls?.length && !blocks.some(block => block.type === "thinking"))
|
|
307
216
|
return;
|
|
308
217
|
this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
|
|
309
218
|
}
|
|
310
219
|
}
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return { system: sysBp, tools: toolBp, messages: msgBp };
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* I1: split the response's `cache_read_input_tokens` evenly across the slots that carried a
|
|
345
|
-
* cache_control breakpoint on the request. Returns undefined when there's no cache read or no
|
|
346
|
-
* contributing slot — in those cases the consumer is better off seeing the field absent than
|
|
347
|
-
* seeing all zeros. The remainder (if the total doesn't divide evenly) lands on the first
|
|
348
|
-
* contributing slot to keep the sum exact.
|
|
349
|
-
*/
|
|
350
|
-
function estimateCacheReadBySlot(cacheRead, slotBp) {
|
|
351
|
-
if (cacheRead <= 0)
|
|
352
|
-
return undefined;
|
|
353
|
-
const count = (slotBp.system ? 1 : 0) + (slotBp.tools ? 1 : 0) + (slotBp.messages ? 1 : 0);
|
|
354
|
-
if (count === 0)
|
|
355
|
-
return undefined;
|
|
356
|
-
const share = Math.floor(cacheRead / count);
|
|
357
|
-
const remainder = cacheRead - share * count;
|
|
358
|
-
const out = {};
|
|
359
|
-
let firstDone = false;
|
|
360
|
-
const give = () => {
|
|
361
|
-
if (!firstDone) {
|
|
362
|
-
firstDone = true;
|
|
363
|
-
return share + remainder;
|
|
364
|
-
}
|
|
365
|
-
return share;
|
|
220
|
+
function compatibilityCapabilities() {
|
|
221
|
+
const unknown = { state: "unknown", evidence: [] };
|
|
222
|
+
const unsupported = { state: "unsupported", evidence: ["protocol"] };
|
|
223
|
+
return {
|
|
224
|
+
inputModalities: {
|
|
225
|
+
text: unknown,
|
|
226
|
+
image: unknown,
|
|
227
|
+
audio: unsupported,
|
|
228
|
+
video: unsupported,
|
|
229
|
+
file: unsupported,
|
|
230
|
+
},
|
|
231
|
+
outputModalities: {
|
|
232
|
+
text: unknown,
|
|
233
|
+
image: unsupported,
|
|
234
|
+
audio: unsupported,
|
|
235
|
+
embedding: unsupported,
|
|
236
|
+
},
|
|
237
|
+
tools: unknown,
|
|
238
|
+
reasoning: unknown,
|
|
239
|
+
parallelToolCalls: unknown,
|
|
240
|
+
structuredOutput: unsupported,
|
|
241
|
+
promptCaching: unknown,
|
|
242
|
+
nativeTokenCounting: unknown,
|
|
243
|
+
mediaForms: {
|
|
244
|
+
imageUrl: unknown,
|
|
245
|
+
imageBase64: unknown,
|
|
246
|
+
fileId: unsupported,
|
|
247
|
+
audioUrl: unsupported,
|
|
248
|
+
audioBase64: unsupported,
|
|
249
|
+
},
|
|
366
250
|
};
|
|
367
|
-
if (slotBp.system)
|
|
368
|
-
out.system = give();
|
|
369
|
-
if (slotBp.tools)
|
|
370
|
-
out.tools = give();
|
|
371
|
-
if (slotBp.messages)
|
|
372
|
-
out.messages = give();
|
|
373
|
-
return out;
|
|
374
|
-
}
|
|
375
|
-
/** Anthropic accepts at most this many cache_control breakpoints per request. */
|
|
376
|
-
const MAX_CACHE_BREAKPOINTS = 4;
|
|
377
|
-
/**
|
|
378
|
-
* Number of rolling cache breakpoints to spend on the message history. Anthropic
|
|
379
|
-
* allows 4 cache_control breakpoints total; the static system/tools prefix
|
|
380
|
-
* consumes up to 2 (systemStable + systemKnowledge), leaving 2 for the history.
|
|
381
|
-
*/
|
|
382
|
-
const MESSAGE_CACHE_BREAKPOINTS = 2;
|
|
383
|
-
/**
|
|
384
|
-
* Regression guard: fail loudly if the static (system + tools) breakpoints plus
|
|
385
|
-
* the rolling message budget could exceed Anthropic's hard limit, instead of
|
|
386
|
-
* letting the API reject the request with an opaque 400. Uses the worst-case
|
|
387
|
-
* message count (`MESSAGE_CACHE_BREAKPOINTS`), so it can only fire if a future
|
|
388
|
-
* change adds a system partition or raises the message budget.
|
|
389
|
-
*/
|
|
390
|
-
function assertCacheBudget(system, toolCount) {
|
|
391
|
-
const systemBreakpoints = Array.isArray(system) ? system.length : 0;
|
|
392
|
-
const toolBreakpoints = toolCount > 0 && !Array.isArray(system) ? 1 : 0;
|
|
393
|
-
const worstCase = systemBreakpoints + toolBreakpoints + MESSAGE_CACHE_BREAKPOINTS;
|
|
394
|
-
if (worstCase > MAX_CACHE_BREAKPOINTS) {
|
|
395
|
-
throw new Error(`Anthropic cache_control budget exceeded: ${systemBreakpoints} system + ${toolBreakpoints} tool + ${MESSAGE_CACHE_BREAKPOINTS} message > ${MAX_CACHE_BREAKPOINTS}`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Place the (≤2) message-history cache breakpoints. The final message always gets
|
|
400
|
-
* one — it writes the current full prefix for the next turn to read. The second is
|
|
401
|
-
* placed by one of two strategies:
|
|
402
|
-
*
|
|
403
|
-
* • **Deep anchor (P1-E)** — when `frozenPrefixLen` marks a distinct frozen prefix
|
|
404
|
-
* (the compaction boundary), pin the second breakpoint there. It is byte-stable
|
|
405
|
-
* across turns, so `[0..frozen]` is re-read cheaply every turn and is immune to
|
|
406
|
-
* the 20-block lookback miss that strikes heavy tool turns (>20 blocks/turn); the
|
|
407
|
-
* tail breakpoint then writes only the incremental `[frozen..tail]`.
|
|
408
|
-
* • **Rolling fallback** — otherwise (older binding / no compaction yet / whole
|
|
409
|
-
* render hot), roll the second breakpoint to the nearest preceding user turn, the
|
|
410
|
-
* previous turn's read anchor (Anthropic's 20-block lookback bridges light turns).
|
|
411
|
-
*
|
|
412
|
-
* Without any of this the cached prefix stops at the end of `system` and every turn
|
|
413
|
-
* re-bills the entire tool-result history at full price (~quadratic cumulative cost).
|
|
414
|
-
* cache_control attaches to the last content block of each target, promoting a bare
|
|
415
|
-
* string body to a text block.
|
|
416
|
-
*/
|
|
417
|
-
function applyMessageCacheControl(msgs, frozenPrefixLen, strategy) {
|
|
418
|
-
if (!msgs.length)
|
|
419
|
-
return;
|
|
420
|
-
// Message-level cache_control is emitted under "default" and "frozen-prefix" only.
|
|
421
|
-
// "tools-only", "system-only", and "none" skip the history entirely.
|
|
422
|
-
if (strategy === "tools-only" || strategy === "system-only" || strategy === "none")
|
|
423
|
-
return;
|
|
424
|
-
const targets = new Set([msgs.length - 1]);
|
|
425
|
-
if (typeof frozenPrefixLen === "number" && frozenPrefixLen >= 1 && frozenPrefixLen < msgs.length) {
|
|
426
|
-
// Deep anchor at the frozen-prefix boundary (last frozen turn). Fixed between compactions.
|
|
427
|
-
targets.add(frozenPrefixLen - 1);
|
|
428
|
-
}
|
|
429
|
-
else if (strategy === "default") {
|
|
430
|
-
// Rolling fallback is part of the default strategy only — `"frozen-prefix"` deliberately
|
|
431
|
-
// skips it so a verify can isolate the deep-anchor contribution from the rolling pair.
|
|
432
|
-
for (let i = msgs.length - 2; i >= 0 && targets.size < MESSAGE_CACHE_BREAKPOINTS; i--) {
|
|
433
|
-
if (msgs[i].role === "user")
|
|
434
|
-
targets.add(i);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
for (const idx of targets)
|
|
438
|
-
markLastBlockCacheable(msgs[idx]);
|
|
439
|
-
}
|
|
440
|
-
/** Attach an ephemeral cache breakpoint to a message's final content block. */
|
|
441
|
-
function markLastBlockCacheable(msg) {
|
|
442
|
-
const cache_control = { type: "ephemeral" };
|
|
443
|
-
if (typeof msg.content === "string") {
|
|
444
|
-
if (!msg.content)
|
|
445
|
-
return; // don't synthesize an empty (API-rejected) text block
|
|
446
|
-
msg.content = [{ type: "text", text: msg.content, cache_control }];
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
if (Array.isArray(msg.content) && msg.content.length) {
|
|
450
|
-
const last = msg.content[msg.content.length - 1];
|
|
451
|
-
last.cache_control = cache_control;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Reconstruct Anthropic assistant content blocks from a neutral transcript when
|
|
456
|
-
* no provider replay was persisted. Only meaningful for tool-use turns: a plain
|
|
457
|
-
* text turn needs no native blocks to replay.
|
|
458
|
-
*/
|
|
459
|
-
function reconstructAnthropicBlocks(message) {
|
|
460
|
-
const toolCalls = message.toolCalls ?? [];
|
|
461
|
-
if (!toolCalls.length)
|
|
462
|
-
return [];
|
|
463
|
-
const blocks = [];
|
|
464
|
-
if (message.content)
|
|
465
|
-
blocks.push({ type: "text", text: message.content });
|
|
466
|
-
for (const tc of toolCalls) {
|
|
467
|
-
let input = {};
|
|
468
|
-
try {
|
|
469
|
-
input = JSON.parse(tc.arguments || "{}");
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
input = {};
|
|
473
|
-
}
|
|
474
|
-
blocks.push({ type: "tool_use", id: tc.id, name: tc.name, input });
|
|
475
|
-
}
|
|
476
|
-
return blocks;
|
|
477
251
|
}
|