@deepstrike/sdk 0.2.60 → 0.2.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/kernel.d.ts +4 -0
- package/dist/os/public.d.ts +1 -1
- package/dist/os/public.js +1 -1
- package/dist/providers/anthropic-adapter.d.ts +13 -1
- package/dist/providers/anthropic-adapter.js +106 -20
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +10 -3
- package/dist/providers/base.d.ts +2 -2
- package/dist/providers/base.js +7 -5
- package/dist/providers/catalog.js +5 -20
- package/dist/providers/factories.js +17 -7
- package/dist/providers/gemini-adapter.js +6 -14
- package/dist/providers/model-registry.d.ts +22 -1
- package/dist/providers/model-registry.js +72 -8
- package/dist/providers/ollama-adapter.js +2 -2
- package/dist/providers/openai-chat.js +4 -6
- package/dist/providers/openai-responses-adapter.js +12 -10
- package/dist/providers/openai-responses.d.ts +6 -1
- package/dist/providers/openai-responses.js +33 -0
- package/dist/providers/protocol-adapter.d.ts +6 -1
- package/dist/providers/protocol-adapter.js +6 -2
- package/dist/providers/provider-error.js +5 -1
- package/dist/providers/request-plan.d.ts +4 -1
- package/dist/providers/request-plan.js +25 -1
- package/dist/providers/usage-normalizer.js +58 -15
- package/dist/runtime/canonical-kernel-step.d.ts +2 -2
- package/dist/runtime/canonical-kernel-step.js +180 -232
- package/dist/runtime/kernel-doctor.d.ts +36 -0
- package/dist/runtime/kernel-doctor.js +60 -0
- package/dist/runtime/kernel-step.d.ts +11 -4
- package/dist/runtime/kernel-step.js +20 -0
- package/dist/runtime/provider-replay.d.ts +4 -0
- package/dist/runtime/provider-replay.js +15 -1
- package/dist/runtime/runner.d.ts +6 -5
- package/dist/runtime/runner.js +103 -43
- package/dist/runtime/session-log.d.ts +12 -0
- package/dist/types.d.ts +8 -5
- package/package.json +3 -3
|
@@ -31,6 +31,54 @@ export const registryEvidence = [
|
|
|
31
31
|
verifiedAt: "2026-08-12",
|
|
32
32
|
},
|
|
33
33
|
];
|
|
34
|
+
export const cacheCapabilityEvidence = [
|
|
35
|
+
{
|
|
36
|
+
endpointId: "anthropic.messages",
|
|
37
|
+
source: "https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching",
|
|
38
|
+
verifiedAt: "2026-08-26",
|
|
39
|
+
classification: "documentation",
|
|
40
|
+
usageFields: ["cache_read_input_tokens", "cache_creation_input_tokens"],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
endpointId: "deepseek.openai",
|
|
44
|
+
source: "https://api-docs.deepseek.com/guides/kv_cache",
|
|
45
|
+
verifiedAt: "2026-08-26",
|
|
46
|
+
classification: "documentation",
|
|
47
|
+
usageFields: ["prompt_cache_hit_tokens", "prompt_cache_miss_tokens"],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
export const tokenMeasurementEvidence = [
|
|
51
|
+
{
|
|
52
|
+
endpointId: "anthropic.messages",
|
|
53
|
+
source: "https://github.com/anthropics/anthropic-sdk-typescript/blob/main/api.md#count-tokens",
|
|
54
|
+
verifiedAt: "2026-08-26",
|
|
55
|
+
providerApiState: "supported",
|
|
56
|
+
adapterState: "available",
|
|
57
|
+
method: "provider_preflight",
|
|
58
|
+
coverage: ["system", "messages", "tools"],
|
|
59
|
+
sdk: "@anthropic-ai/sdk ^0.99.0",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
endpointId: "gemini.google",
|
|
63
|
+
source: "https://ai.google.dev/api/tokens#method-models.countTokens",
|
|
64
|
+
verifiedAt: "2026-08-26",
|
|
65
|
+
providerApiState: "supported",
|
|
66
|
+
adapterState: "available",
|
|
67
|
+
method: "provider_preflight",
|
|
68
|
+
coverage: ["contents", "system", "tools", "provider_options"],
|
|
69
|
+
sdk: "@google/generative-ai ^0.24.1",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
endpointId: "openai.responses",
|
|
73
|
+
source: "https://github.com/openai/openai-node/tree/master/src/resources/responses/input-tokens.ts",
|
|
74
|
+
verifiedAt: "2026-08-26",
|
|
75
|
+
providerApiState: "supported",
|
|
76
|
+
adapterState: "available",
|
|
77
|
+
method: "provider_preflight",
|
|
78
|
+
coverage: ["input", "instructions", "tools", "provider_options"],
|
|
79
|
+
sdk: "openai ^7.5.0",
|
|
80
|
+
},
|
|
81
|
+
];
|
|
34
82
|
const POLICY = {
|
|
35
83
|
"anthropic/claude-opus-4-1": { maxTurns: 50 },
|
|
36
84
|
"anthropic/claude-opus-4-7": { maxTurns: 50 },
|
|
@@ -114,11 +162,11 @@ const DEFAULT_ENDPOINT = {
|
|
|
114
162
|
anthropic: "anthropic.messages",
|
|
115
163
|
openai: "openai.chat",
|
|
116
164
|
minimax: "minimax.anthropic",
|
|
117
|
-
deepseek: "deepseek.
|
|
118
|
-
kimi: "kimi.
|
|
119
|
-
qwen: "qwen.
|
|
165
|
+
deepseek: "deepseek.openai",
|
|
166
|
+
kimi: "kimi.openai",
|
|
167
|
+
qwen: "qwen.dashscope",
|
|
120
168
|
gemini: "gemini.google",
|
|
121
|
-
glm: "glm.
|
|
169
|
+
glm: "glm.openai",
|
|
122
170
|
baai: "baai.self-hosted.embeddings",
|
|
123
171
|
ollama: "ollama.local",
|
|
124
172
|
};
|
|
@@ -137,6 +185,9 @@ const DEFAULT_MODEL = {
|
|
|
137
185
|
export function defaultModelForProvider(providerId) {
|
|
138
186
|
return DEFAULT_MODEL[providerId];
|
|
139
187
|
}
|
|
188
|
+
export function defaultEndpointForProvider(providerId) {
|
|
189
|
+
return DEFAULT_ENDPOINT[providerId];
|
|
190
|
+
}
|
|
140
191
|
function endpointFor(providerId, modelId) {
|
|
141
192
|
if (providerId === "openai") {
|
|
142
193
|
if (modelId.startsWith("text-embedding-"))
|
|
@@ -225,8 +276,10 @@ export const protocolRuntimeCapabilities = {
|
|
|
225
276
|
"ollama-chat": OLLAMA_PROTOCOL_CAPABILITIES,
|
|
226
277
|
};
|
|
227
278
|
export const endpointRuntimeCapabilities = {
|
|
228
|
-
"anthropic.messages": { nativeTokenCounting: true },
|
|
279
|
+
"anthropic.messages": { nativeTokenCounting: true, promptCaching: true },
|
|
280
|
+
"deepseek.openai": { promptCaching: true },
|
|
229
281
|
"gemini.google": { nativeTokenCounting: true },
|
|
282
|
+
"openai.responses": { nativeTokenCounting: true },
|
|
230
283
|
};
|
|
231
284
|
export function resolveEffectiveCapability(layers) {
|
|
232
285
|
const evidence = layers.filter(layer => layer.state !== "unknown").map(layer => layer.layer);
|
|
@@ -285,7 +338,14 @@ export function resolveEffectiveModelCapabilities(input) {
|
|
|
285
338
|
]),
|
|
286
339
|
parallelToolCalls: protocolBoolean(protocol.parallelToolCalls, overrides?.parallelToolCalls),
|
|
287
340
|
structuredOutput: protocolBoolean(protocol.structuredOutput, overrides?.structuredOutput),
|
|
288
|
-
promptCaching:
|
|
341
|
+
promptCaching: resolveEffectiveCapability([
|
|
342
|
+
{ layer: "protocol", state: booleanState(protocol.promptCaching), value: protocol.promptCaching },
|
|
343
|
+
{
|
|
344
|
+
layer: "endpoint",
|
|
345
|
+
state: booleanState(input.endpointCapabilities?.promptCaching),
|
|
346
|
+
value: input.endpointCapabilities?.promptCaching,
|
|
347
|
+
},
|
|
348
|
+
]),
|
|
289
349
|
nativeTokenCounting: resolveEffectiveCapability([
|
|
290
350
|
{ layer: "endpoint", state: booleanState(input.endpointCapabilities?.nativeTokenCounting) },
|
|
291
351
|
]),
|
|
@@ -307,8 +367,12 @@ export function generationProtocol(protocol) {
|
|
|
307
367
|
return undefined;
|
|
308
368
|
}
|
|
309
369
|
}
|
|
310
|
-
export function endpointCapabilitiesFor(endpointId, preserveEndpointIdentity) {
|
|
311
|
-
|
|
370
|
+
export function endpointCapabilitiesFor(endpointId, preserveEndpointIdentity, preserveCacheEvidence = preserveEndpointIdentity) {
|
|
371
|
+
const capabilities = preserveEndpointIdentity ? endpointRuntimeCapabilities[endpointId] : undefined;
|
|
372
|
+
if (!capabilities || preserveCacheEvidence)
|
|
373
|
+
return capabilities;
|
|
374
|
+
const { promptCaching: _promptCaching, ...withoutCacheEvidence } = capabilities;
|
|
375
|
+
return withoutCacheEvidence;
|
|
312
376
|
}
|
|
313
377
|
export function isKnownProviderId(value) {
|
|
314
378
|
return value in DEFAULT_ENDPOINT;
|
|
@@ -40,8 +40,8 @@ function validCount(raw, field) {
|
|
|
40
40
|
const value = raw[field];
|
|
41
41
|
if (value === undefined)
|
|
42
42
|
return undefined;
|
|
43
|
-
if (typeof value !== "number" || !Number.
|
|
44
|
-
throw new ProtocolResponseError("ollama-chat", `${field} must be a non-negative
|
|
43
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
44
|
+
throw new ProtocolResponseError("ollama-chat", `${field} must be a non-negative safe integer`);
|
|
45
45
|
}
|
|
46
46
|
return value;
|
|
47
47
|
}
|
|
@@ -2,6 +2,7 @@ import { assistantReplayKey } from "../runtime/provider-replay.js";
|
|
|
2
2
|
import { openAICachedPromptTokens, stablePromptCacheKey, ThinkingTagStreamExtractor, } from "./base.js";
|
|
3
3
|
import { normalizeCanonicalContext, projectToolOutputToText } from "./content-normalization.js";
|
|
4
4
|
import { normalizeToolCall } from "./base.js";
|
|
5
|
+
import { normalizeOpenAIUsage } from "./usage-normalizer.js";
|
|
5
6
|
import { DEGRADED_REASONING_PLACEHOLDER, assessReasoningReplay, validateOpenAIChatReplay, } from "./replay-validator.js";
|
|
6
7
|
import { ProtocolResponseError, } from "./protocol-adapter.js";
|
|
7
8
|
import { OPENAI_CHAT_PROTOCOL_CAPABILITIES } from "./protocol-capabilities.js";
|
|
@@ -349,6 +350,8 @@ export class OpenAIChatAdapter {
|
|
|
349
350
|
...(stopReason ? { stopReason } : {}),
|
|
350
351
|
...(state.finishReason ? { rawStopReason: state.finishReason } : {}),
|
|
351
352
|
...(providerUsage ? { providerUsage } : {}),
|
|
353
|
+
...(providerUsage?.cacheTelemetryStatus ? { cacheTelemetryStatus: providerUsage.cacheTelemetryStatus } : {}),
|
|
354
|
+
...(providerUsage?.cacheTelemetrySource ? { cacheTelemetrySource: providerUsage.cacheTelemetrySource } : {}),
|
|
352
355
|
});
|
|
353
356
|
}
|
|
354
357
|
const replay = streamReplay(state);
|
|
@@ -371,12 +374,7 @@ export class OpenAIChatAdapter {
|
|
|
371
374
|
const reasoningTokens = details && typeof details === "object"
|
|
372
375
|
? numberField(details, "reasoning_tokens")
|
|
373
376
|
: undefined;
|
|
374
|
-
return
|
|
375
|
-
inputTokens: inputTokens ?? 0,
|
|
376
|
-
outputTokens: outputTokens ?? 0,
|
|
377
|
-
...(cacheReadInputTokens > 0 ? { cacheReadInputTokens } : {}),
|
|
378
|
-
...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
|
|
379
|
-
};
|
|
377
|
+
return normalizeOpenAIUsage(usage);
|
|
380
378
|
}
|
|
381
379
|
normalizeStopReason(raw) {
|
|
382
380
|
if (raw === undefined)
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { normalizeCanonicalContext } from "./content-normalization.js";
|
|
2
2
|
import { normalizeToolCall, UnsupportedModalityError } from "./base.js";
|
|
3
|
+
import { normalizeOpenAIUsage } from "./usage-normalizer.js";
|
|
3
4
|
import { ProtocolResponseError, } from "./protocol-adapter.js";
|
|
4
5
|
import { OPENAI_RESPONSES_PROTOCOL_CAPABILITIES } from "./protocol-capabilities.js";
|
|
5
6
|
function numberField(raw, field) {
|
|
6
7
|
const value = raw[field];
|
|
7
8
|
if (value === undefined || value === null)
|
|
8
9
|
return undefined;
|
|
9
|
-
if (typeof value !== "number" || !Number.
|
|
10
|
-
throw new ProtocolResponseError("openai-responses", `usage.${field} must be a non-negative
|
|
10
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
11
|
+
throw new ProtocolResponseError("openai-responses", `usage.${field} must be a non-negative safe integer`);
|
|
11
12
|
}
|
|
12
13
|
return value;
|
|
13
14
|
}
|
|
@@ -267,6 +268,12 @@ export class OpenAIResponsesAdapter {
|
|
|
267
268
|
...(inputTokens ? { inputTokens } : {}),
|
|
268
269
|
...(outputTokens ? { outputTokens } : {}),
|
|
269
270
|
...(cacheReadInputTokens ? { cacheReadInputTokens } : {}),
|
|
271
|
+
...(providerUsage?.cacheTelemetryStatus
|
|
272
|
+
? { cacheTelemetryStatus: providerUsage.cacheTelemetryStatus }
|
|
273
|
+
: {}),
|
|
274
|
+
...(providerUsage?.cacheTelemetrySource
|
|
275
|
+
? { cacheTelemetrySource: providerUsage.cacheTelemetrySource }
|
|
276
|
+
: {}),
|
|
270
277
|
...(providerUsage && (inputTokens || outputTokens) ? { providerUsage } : {}),
|
|
271
278
|
...(stopReason ? { stopReason } : {}),
|
|
272
279
|
...(rawStopReason ? { rawStopReason } : {}),
|
|
@@ -294,16 +301,11 @@ export class OpenAIResponsesAdapter {
|
|
|
294
301
|
throw new ProtocolResponseError("openai-responses", "usage.output_tokens_details must be an object");
|
|
295
302
|
}
|
|
296
303
|
cacheReadTokens(usage);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
: undefined;
|
|
304
|
+
if (outputDetails)
|
|
305
|
+
numberField(outputDetails, "reasoning_tokens");
|
|
300
306
|
if (inputTokens === undefined && outputTokens === undefined)
|
|
301
307
|
return undefined;
|
|
302
|
-
return
|
|
303
|
-
inputTokens: inputTokens ?? 0,
|
|
304
|
-
outputTokens: outputTokens ?? 0,
|
|
305
|
-
...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
|
|
306
|
-
};
|
|
308
|
+
return normalizeOpenAIUsage(usage);
|
|
307
309
|
}
|
|
308
310
|
normalizeStopReason(raw) {
|
|
309
311
|
if (raw === undefined)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import type { LLMProvider, Message, ProviderRunState, RenderedContext, RuntimePolicy, StreamEvent, ToolSchema } from "../types.js";
|
|
2
|
+
import type { LLMProvider, Message, PromptMeasurement, ProviderRunState, RenderedContext, RuntimePolicy, StreamEvent, ToolSchema } from "../types.js";
|
|
3
3
|
import { CircuitBreaker } from "./base.js";
|
|
4
4
|
import { type CanonicalAdapterInput } from "./content-normalization.js";
|
|
5
5
|
import { OpenAIResponsesAdapter, type OpenAIResponsesRunState } from "./openai-responses-adapter.js";
|
|
@@ -14,6 +14,7 @@ export declare class OpenAIResponsesProvider implements LLMProvider {
|
|
|
14
14
|
protected baseDelay: number;
|
|
15
15
|
protected readonly responses: OpenAIResponsesAdapter;
|
|
16
16
|
private readonly resolvedRuntimePolicy;
|
|
17
|
+
private readonly directNativeTokenCounting;
|
|
17
18
|
private resolvedRuntime?;
|
|
18
19
|
constructor(apiKey: string, model?: string, retry?: {
|
|
19
20
|
maxRetries: number;
|
|
@@ -24,6 +25,10 @@ export declare class OpenAIResponsesProvider implements LLMProvider {
|
|
|
24
25
|
createRunState(): OpenAIResponsesRunState;
|
|
25
26
|
private adapterInput;
|
|
26
27
|
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
28
|
+
/** spc_024-05: native preflight via the official Responses input-token count endpoint. Counts
|
|
29
|
+
* the exact create request plan (stateful `previous_response_id` continuation included) —
|
|
30
|
+
* native measurement belongs to the verified official endpoint, not the wire protocol. */
|
|
31
|
+
countTokens(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): Promise<PromptMeasurement>;
|
|
27
32
|
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState, signal?: AbortSignal): AsyncIterable<StreamEvent>;
|
|
28
33
|
private builtinTools;
|
|
29
34
|
private requestExtensions;
|
|
@@ -6,6 +6,14 @@ import { endpointProfiles } from "./endpoints.js";
|
|
|
6
6
|
import { OpenAIResponsesAdapter, } from "./openai-responses-adapter.js";
|
|
7
7
|
import { circuitOpenError, classifyProviderError } from "./provider-error.js";
|
|
8
8
|
export { OpenAIResponsesAdapter } from "./openai-responses-adapter.js";
|
|
9
|
+
const OFFICIAL_OPENAI_RESPONSES_BASE_URL = "https://api.openai.com/v1";
|
|
10
|
+
/** Params the official input-token count endpoint accepts (SDK `InputTokenCountParams`). The
|
|
11
|
+
* create plan is projected onto this set — remaining keys (max_output_tokens, store, …) cannot
|
|
12
|
+
* change the input token count — rather than maintaining a second serialization. */
|
|
13
|
+
const INPUT_TOKEN_COUNT_PARAM_KEYS = [
|
|
14
|
+
"conversation", "input", "instructions", "model", "parallel_tool_calls",
|
|
15
|
+
"previous_response_id", "reasoning", "text", "tool_choice", "tools", "truncation",
|
|
16
|
+
];
|
|
9
17
|
export class OpenAIResponsesProvider {
|
|
10
18
|
model;
|
|
11
19
|
client;
|
|
@@ -14,6 +22,7 @@ export class OpenAIResponsesProvider {
|
|
|
14
22
|
baseDelay;
|
|
15
23
|
responses = new OpenAIResponsesAdapter();
|
|
16
24
|
resolvedRuntimePolicy;
|
|
25
|
+
directNativeTokenCounting;
|
|
17
26
|
resolvedRuntime;
|
|
18
27
|
constructor(apiKey, model = "gpt-4.1", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = "https://api.openai.com/v1", runtimePolicy = {}, authMode = "api_key") {
|
|
19
28
|
this.model = model;
|
|
@@ -26,6 +35,7 @@ export class OpenAIResponsesProvider {
|
|
|
26
35
|
this.maxRetries = retry.maxRetries;
|
|
27
36
|
this.baseDelay = retry.baseDelay;
|
|
28
37
|
this.resolvedRuntimePolicy = runtimePolicy;
|
|
38
|
+
this.directNativeTokenCounting = baseURL.replace(/\/+$/, "") === OFFICIAL_OPENAI_RESPONSES_BASE_URL;
|
|
29
39
|
}
|
|
30
40
|
runtimePolicy() {
|
|
31
41
|
return this.resolvedRuntimePolicy;
|
|
@@ -90,6 +100,29 @@ export class OpenAIResponsesProvider {
|
|
|
90
100
|
}
|
|
91
101
|
throw classifyProviderError("openai", lastError);
|
|
92
102
|
}
|
|
103
|
+
/** spc_024-05: native preflight via the official Responses input-token count endpoint. Counts
|
|
104
|
+
* the exact create request plan (stateful `previous_response_id` continuation included) —
|
|
105
|
+
* native measurement belongs to the verified official endpoint, not the wire protocol. */
|
|
106
|
+
async countTokens(context, tools, extensions, state) {
|
|
107
|
+
const enabled = this.resolvedRuntime
|
|
108
|
+
? this.resolvedRuntime.effectiveCapabilities.nativeTokenCounting.state === "supported"
|
|
109
|
+
: this.directNativeTokenCounting;
|
|
110
|
+
const inputTokens = this.client.responses.inputTokens;
|
|
111
|
+
if (!enabled || typeof inputTokens?.count !== "function") {
|
|
112
|
+
throw new Error("Native token counting is unavailable on this OpenAI-compatible endpoint");
|
|
113
|
+
}
|
|
114
|
+
const input = this.adapterInput(context, tools, extensions);
|
|
115
|
+
const plan = this.responses.buildRequest(input, this.asRunState(state));
|
|
116
|
+
const body = Object.fromEntries(INPUT_TOKEN_COUNT_PARAM_KEYS
|
|
117
|
+
.filter(key => key in plan.params)
|
|
118
|
+
.map(key => [key, plan.params[key]]));
|
|
119
|
+
const response = await inputTokens.count(body);
|
|
120
|
+
return {
|
|
121
|
+
inputTokens: response.input_tokens,
|
|
122
|
+
source: { kind: "native", provider: "openai" },
|
|
123
|
+
confidence: "exact",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
93
126
|
async *stream(context, tools, extensions, state, signal) {
|
|
94
127
|
try {
|
|
95
128
|
const runState = this.asRunState(state);
|
|
@@ -30,5 +30,10 @@ export interface ProtocolAdapter<TRequest, TCompleteResponse, TStreamChunk, TStr
|
|
|
30
30
|
}
|
|
31
31
|
export declare class ProtocolResponseError extends Error {
|
|
32
32
|
readonly protocol: GenerationProtocol;
|
|
33
|
-
|
|
33
|
+
readonly providerCode?: string;
|
|
34
|
+
readonly retryable?: boolean;
|
|
35
|
+
constructor(protocol: GenerationProtocol, message: string, options?: {
|
|
36
|
+
providerCode?: string;
|
|
37
|
+
retryable?: boolean;
|
|
38
|
+
});
|
|
34
39
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export { GEMINI_PROTOCOL_CAPABILITIES, OLLAMA_PROTOCOL_CAPABILITIES, } from "./protocol-capabilities.js";
|
|
2
2
|
export class ProtocolResponseError extends Error {
|
|
3
3
|
protocol;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
providerCode;
|
|
5
|
+
retryable;
|
|
6
|
+
constructor(protocol, message, options = {}) {
|
|
7
|
+
super(options.providerCode ? message : `${protocol} protocol response error: ${message}`);
|
|
6
8
|
this.name = "ProtocolResponseError";
|
|
7
9
|
this.protocol = protocol;
|
|
10
|
+
this.providerCode = options.providerCode;
|
|
11
|
+
this.retryable = options.retryable;
|
|
8
12
|
}
|
|
9
13
|
}
|
|
@@ -113,10 +113,14 @@ export function classifyProviderError(provider, error) {
|
|
|
113
113
|
const httpStatus = errorStatus(error);
|
|
114
114
|
const providerCode = errorCode(error);
|
|
115
115
|
const kind = classifyKind(error, httpStatus, providerCode);
|
|
116
|
+
const explicitProtocolRetryable = kind === "protocol"
|
|
117
|
+
&& typeof object(error)?.retryable === "boolean"
|
|
118
|
+
? object(error).retryable
|
|
119
|
+
: undefined;
|
|
116
120
|
return new ProviderError({
|
|
117
121
|
provider,
|
|
118
122
|
kind,
|
|
119
|
-
retryable: retryable(kind, httpStatus),
|
|
123
|
+
retryable: explicitProtocolRetryable ?? retryable(kind, httpStatus),
|
|
120
124
|
message: errorMessage(error),
|
|
121
125
|
...(httpStatus !== undefined ? { httpStatus } : {}),
|
|
122
126
|
...(providerCode !== undefined ? { providerCode } : {}),
|
|
@@ -13,6 +13,7 @@ export interface ProviderRequestPlan {
|
|
|
13
13
|
tools: ToolSchema[];
|
|
14
14
|
options: Record<string, unknown>;
|
|
15
15
|
fingerprint: string;
|
|
16
|
+
stablePrefixFingerprint: string;
|
|
16
17
|
}
|
|
17
18
|
export interface NormalizedProviderUsage extends ProviderUsage {
|
|
18
19
|
/** Input not accounted as a cache read or write. `inputTokens` remains the full footprint. */
|
|
@@ -28,6 +29,8 @@ export interface RecordedPromptMeasurement {
|
|
|
28
29
|
} | {
|
|
29
30
|
kind: "local_exact";
|
|
30
31
|
tokenizer: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "postflight";
|
|
31
34
|
} | {
|
|
32
35
|
kind: "heuristic";
|
|
33
36
|
};
|
|
@@ -56,7 +59,7 @@ export type CostObservation = {
|
|
|
56
59
|
source: "unpriced";
|
|
57
60
|
reason: "pricing_snapshot_not_effective" | "pricing_snapshot_expired" | "invalid_pricing_snapshot";
|
|
58
61
|
};
|
|
59
|
-
export declare function createProviderRequestPlan(input: Omit<ProviderRequestPlan, "fingerprint" | "options"> & {
|
|
62
|
+
export declare function createProviderRequestPlan(input: Omit<ProviderRequestPlan, "fingerprint" | "stablePrefixFingerprint" | "options"> & {
|
|
60
63
|
options?: Record<string, unknown>;
|
|
61
64
|
}): ProviderRequestPlan;
|
|
62
65
|
/** Build the plan from a resolved provider when the runner only has the public provider object. */
|
|
@@ -13,7 +13,19 @@ export function createProviderRequestPlan(input) {
|
|
|
13
13
|
tools: clone(input.tools),
|
|
14
14
|
options,
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
const stablePrefix = {
|
|
17
|
+
providerId: plan.providerId,
|
|
18
|
+
modelId: plan.modelId,
|
|
19
|
+
endpoint: plan.endpoint,
|
|
20
|
+
context: stablePrefixContext(plan.context),
|
|
21
|
+
tools: plan.tools,
|
|
22
|
+
options: plan.options,
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
...plan,
|
|
26
|
+
fingerprint: sha256(stableJson(plan)),
|
|
27
|
+
stablePrefixFingerprint: sha256(stableJson(stablePrefix)),
|
|
28
|
+
};
|
|
17
29
|
}
|
|
18
30
|
/** Build the plan from a resolved provider when the runner only has the public provider object. */
|
|
19
31
|
export function createProviderRequestPlanForProvider(provider, context, tools, options) {
|
|
@@ -59,6 +71,8 @@ export function measurementForPlan(plan, recorded) {
|
|
|
59
71
|
return clone(recorded);
|
|
60
72
|
if (source.kind === "local_exact" && typeof source.tokenizer === "string" && source.tokenizer.length > 0)
|
|
61
73
|
return clone(recorded);
|
|
74
|
+
if (source.kind === "postflight")
|
|
75
|
+
return clone(recorded);
|
|
62
76
|
if (source.kind === "heuristic")
|
|
63
77
|
return clone(recorded);
|
|
64
78
|
return undefined;
|
|
@@ -155,6 +169,16 @@ function stableJson(value) {
|
|
|
155
169
|
const object = value;
|
|
156
170
|
return `{${Object.keys(object).sort().map(key => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
|
|
157
171
|
}
|
|
172
|
+
function stablePrefixContext(context) {
|
|
173
|
+
const frozenPrefixLen = context.frozenPrefixLen ?? 0;
|
|
174
|
+
return {
|
|
175
|
+
systemText: context.systemText,
|
|
176
|
+
...(context.systemStable !== undefined ? { systemStable: context.systemStable } : {}),
|
|
177
|
+
...(context.systemKnowledge !== undefined ? { systemKnowledge: context.systemKnowledge } : {}),
|
|
178
|
+
frozenPrefixLen,
|
|
179
|
+
turns: clone(context.turns.slice(0, frozenPrefixLen)),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
158
182
|
function sha256(value) {
|
|
159
183
|
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
160
184
|
}
|
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import { openAICachedPromptTokens } from "./base.js";
|
|
2
|
-
|
|
2
|
+
import { ProtocolResponseError } from "./protocol-adapter.js";
|
|
3
|
+
function readNumber(obj, key, protocol) {
|
|
3
4
|
const value = obj?.[key];
|
|
4
|
-
|
|
5
|
+
if (value === undefined || value === null)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
8
|
+
throw new ProtocolResponseError(protocol, `usage.${key} must be a non-negative safe integer`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function hasOwn(obj, key) {
|
|
13
|
+
return obj !== undefined && Object.prototype.hasOwnProperty.call(obj, key);
|
|
14
|
+
}
|
|
15
|
+
function openAICacheTelemetry(usage) {
|
|
16
|
+
const promptDetails = usage?.prompt_tokens_details;
|
|
17
|
+
const inputDetails = usage?.input_tokens_details;
|
|
18
|
+
if (hasOwn(usage, "prompt_cache_hit_tokens") || hasOwn(usage, "prompt_cache_miss_tokens")) {
|
|
19
|
+
return { cacheTelemetryStatus: "measured", cacheTelemetrySource: "deepseek_prompt_cache" };
|
|
20
|
+
}
|
|
21
|
+
if (hasOwn(promptDetails, "cached_tokens") || hasOwn(inputDetails, "cached_tokens")) {
|
|
22
|
+
return { cacheTelemetryStatus: "measured", cacheTelemetrySource: "openai_prompt_details" };
|
|
23
|
+
}
|
|
24
|
+
return { cacheTelemetryStatus: "unavailable" };
|
|
5
25
|
}
|
|
6
26
|
/**
|
|
7
27
|
* Covers both OpenAI wire shapes actually in use: Chat Completions (`prompt_tokens`/
|
|
@@ -12,20 +32,34 @@ function readNumber(obj, key) {
|
|
|
12
32
|
*/
|
|
13
33
|
export function normalizeOpenAIUsage(usage) {
|
|
14
34
|
const u = usage && typeof usage === "object" ? usage : undefined;
|
|
15
|
-
const
|
|
16
|
-
const
|
|
35
|
+
const protocol = hasOwn(u, "input_tokens") ? "openai-responses" : "openai-chat";
|
|
36
|
+
const rawInput = readNumber(u, "prompt_tokens", protocol) ?? readNumber(u, "input_tokens", protocol);
|
|
37
|
+
const rawOutput = readNumber(u, "completion_tokens", protocol) ?? readNumber(u, "output_tokens", protocol);
|
|
17
38
|
if (rawInput === undefined && rawOutput === undefined)
|
|
18
39
|
return undefined;
|
|
19
40
|
const inputTokens = rawInput ?? 0;
|
|
20
41
|
const outputTokens = rawOutput ?? 0;
|
|
42
|
+
const promptDetails = u?.prompt_tokens_details;
|
|
43
|
+
const inputDetails = u?.input_tokens_details;
|
|
44
|
+
readNumber(promptDetails, "cached_tokens", protocol);
|
|
45
|
+
readNumber(inputDetails, "cached_tokens", protocol);
|
|
46
|
+
const cacheHit = readNumber(u, "prompt_cache_hit_tokens", protocol);
|
|
47
|
+
const cacheMiss = readNumber(u, "prompt_cache_miss_tokens", protocol);
|
|
21
48
|
const cacheReadInputTokens = openAICachedPromptTokens(usage);
|
|
22
49
|
const details = (u?.completion_tokens_details ?? u?.output_tokens_details);
|
|
23
|
-
const reasoningTokens = readNumber(details, "reasoning_tokens");
|
|
50
|
+
const reasoningTokens = readNumber(details, "reasoning_tokens", protocol);
|
|
51
|
+
if (cacheReadInputTokens > inputTokens) {
|
|
52
|
+
throw new ProtocolResponseError(protocol, "cache token subsets cannot exceed input tokens");
|
|
53
|
+
}
|
|
54
|
+
if (cacheHit !== undefined && cacheMiss !== undefined && cacheHit + cacheMiss !== inputTokens) {
|
|
55
|
+
throw new ProtocolResponseError(protocol, "DeepSeek cache hit and miss tokens must sum to prompt tokens");
|
|
56
|
+
}
|
|
24
57
|
const providerUsage = {
|
|
25
58
|
inputTokens,
|
|
26
59
|
outputTokens,
|
|
27
60
|
...(cacheReadInputTokens > 0 ? { cacheReadInputTokens } : {}),
|
|
28
61
|
...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
|
|
62
|
+
...openAICacheTelemetry(u),
|
|
29
63
|
};
|
|
30
64
|
return providerUsage;
|
|
31
65
|
}
|
|
@@ -42,10 +76,10 @@ export function normalizeOpenAIUsage(usage) {
|
|
|
42
76
|
*/
|
|
43
77
|
export function normalizeAnthropicUsage(usage) {
|
|
44
78
|
const u = usage && typeof usage === "object" ? usage : undefined;
|
|
45
|
-
const rawUncachedInput = readNumber(u, "input_tokens");
|
|
46
|
-
const rawCacheRead = readNumber(u, "cache_read_input_tokens");
|
|
47
|
-
const rawCacheCreation = readNumber(u, "cache_creation_input_tokens");
|
|
48
|
-
const rawOutput = readNumber(u, "output_tokens");
|
|
79
|
+
const rawUncachedInput = readNumber(u, "input_tokens", "anthropic-messages");
|
|
80
|
+
const rawCacheRead = readNumber(u, "cache_read_input_tokens", "anthropic-messages");
|
|
81
|
+
const rawCacheCreation = readNumber(u, "cache_creation_input_tokens", "anthropic-messages");
|
|
82
|
+
const rawOutput = readNumber(u, "output_tokens", "anthropic-messages");
|
|
49
83
|
if (rawUncachedInput === undefined && rawCacheRead === undefined && rawCacheCreation === undefined && rawOutput === undefined)
|
|
50
84
|
return undefined;
|
|
51
85
|
const uncachedInput = rawUncachedInput ?? 0;
|
|
@@ -57,6 +91,9 @@ export function normalizeAnthropicUsage(usage) {
|
|
|
57
91
|
outputTokens,
|
|
58
92
|
...(cacheReadInputTokens > 0 ? { cacheReadInputTokens } : {}),
|
|
59
93
|
...(cacheCreationInputTokens > 0 ? { cacheCreationInputTokens } : {}),
|
|
94
|
+
...(hasOwn(u, "cache_read_input_tokens") || hasOwn(u, "cache_creation_input_tokens")
|
|
95
|
+
? { cacheTelemetryStatus: "measured", cacheTelemetrySource: "anthropic_usage" }
|
|
96
|
+
: { cacheTelemetryStatus: "unavailable" }),
|
|
60
97
|
};
|
|
61
98
|
return providerUsage;
|
|
62
99
|
}
|
|
@@ -67,17 +104,23 @@ export function normalizeAnthropicUsage(usage) {
|
|
|
67
104
|
*/
|
|
68
105
|
export function normalizeGeminiUsage(usage) {
|
|
69
106
|
const u = usage && typeof usage === "object" ? usage : undefined;
|
|
70
|
-
const rawInput = readNumber(u, "promptTokenCount");
|
|
71
|
-
const rawOutput = readNumber(u, "candidatesTokenCount");
|
|
107
|
+
const rawInput = readNumber(u, "promptTokenCount", "gemini");
|
|
108
|
+
const rawOutput = readNumber(u, "candidatesTokenCount", "gemini");
|
|
72
109
|
if (rawInput === undefined && rawOutput === undefined)
|
|
73
110
|
return undefined;
|
|
74
111
|
const inputTokens = rawInput ?? 0;
|
|
75
112
|
const outputTokens = rawOutput ?? 0;
|
|
76
|
-
const cacheReadInputTokens = readNumber(u, "cachedContentTokenCount");
|
|
113
|
+
const cacheReadInputTokens = readNumber(u, "cachedContentTokenCount", "gemini");
|
|
114
|
+
if ((cacheReadInputTokens ?? 0) > inputTokens) {
|
|
115
|
+
throw new ProtocolResponseError("gemini", "cache token subsets cannot exceed input tokens");
|
|
116
|
+
}
|
|
77
117
|
const providerUsage = {
|
|
78
118
|
inputTokens,
|
|
79
119
|
outputTokens,
|
|
80
120
|
...(cacheReadInputTokens ? { cacheReadInputTokens } : {}),
|
|
121
|
+
...(hasOwn(u, "cachedContentTokenCount")
|
|
122
|
+
? { cacheTelemetryStatus: "measured", cacheTelemetrySource: "gemini_usage" }
|
|
123
|
+
: { cacheTelemetryStatus: "unavailable" }),
|
|
81
124
|
};
|
|
82
125
|
return providerUsage;
|
|
83
126
|
}
|
|
@@ -88,9 +131,9 @@ export function normalizeGeminiUsage(usage) {
|
|
|
88
131
|
*/
|
|
89
132
|
export function normalizeOllamaUsage(chunk) {
|
|
90
133
|
const u = chunk && typeof chunk === "object" ? chunk : undefined;
|
|
91
|
-
const rawInput = readNumber(u, "prompt_eval_count");
|
|
92
|
-
const rawOutput = readNumber(u, "eval_count");
|
|
134
|
+
const rawInput = readNumber(u, "prompt_eval_count", "ollama-chat");
|
|
135
|
+
const rawOutput = readNumber(u, "eval_count", "ollama-chat");
|
|
93
136
|
if (rawInput === undefined && rawOutput === undefined)
|
|
94
137
|
return undefined;
|
|
95
|
-
return { inputTokens: rawInput ?? 0, outputTokens: rawOutput ?? 0 };
|
|
138
|
+
return { inputTokens: rawInput ?? 0, outputTokens: rawOutput ?? 0, cacheTelemetryStatus: "unavailable" };
|
|
96
139
|
}
|
|
@@ -37,8 +37,8 @@ export interface CanonicalTransition {
|
|
|
37
37
|
replayed: boolean;
|
|
38
38
|
}
|
|
39
39
|
export declare function canonicalUnsupportedEffectResolution(effectId: string, effectKind: string): CanonicalKernelInput;
|
|
40
|
-
/**
|
|
41
|
-
export declare function
|
|
40
|
+
/** Adapt the additive core CurrentProjection JSON into the existing Node action surface. */
|
|
41
|
+
export declare function canonicalActionFromProjectionJson(raw: string): KernelRunnerAction | null;
|
|
42
42
|
export declare class CanonicalKernelRejectedError extends Error {
|
|
43
43
|
readonly fault: Record<string, unknown>;
|
|
44
44
|
constructor(faultJson: string);
|