@deepstrike/wasm 0.2.66 → 0.2.68

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.
@@ -9,7 +9,6 @@ export declare class WorkingMemory {
9
9
  export interface SessionMessage {
10
10
  role: "system" | "user" | "assistant" | "tool";
11
11
  content: string;
12
- tokenCount?: number;
13
12
  toolCalls?: Array<{
14
13
  id: string;
15
14
  name: string;
@@ -31,16 +31,16 @@ function openaiAudioFormat(mediaType) {
31
31
  function openAIPartsContent(parts) {
32
32
  return parts.map(p => {
33
33
  if (p.type === "image") {
34
- const url = p.data ? `data:${p.mediaType ?? "image/png"};base64,${p.data}` : (p.url ?? "");
34
+ const url = p.source?.kind === "base64" ? `data:${p.mediaType ?? "image/png"};base64,${p.source.data}` : p.source?.kind === "url" ? p.source.url : "";
35
35
  return { type: "image_url", image_url: { url, ...(p.detail ? { detail: p.detail } : {}) } };
36
36
  }
37
37
  if (p.type === "audio") {
38
38
  // OpenAI audio has no URL form — a part without base64 data cannot be sent.
39
- if (!p.data)
40
- throw new UnsupportedModalityError("audio (no data)", "openai");
39
+ if (p.source?.kind !== "base64")
40
+ throw new UnsupportedModalityError("audio (no durable base64 source)", "openai");
41
41
  return {
42
42
  type: "input_audio",
43
- input_audio: { data: p.data, format: openaiAudioFormat(p.mediaType) },
43
+ input_audio: { data: p.source.data, format: openaiAudioFormat(p.mediaType) },
44
44
  };
45
45
  }
46
46
  return { type: "text", text: p.text ?? p.output ?? "" };
@@ -50,9 +50,9 @@ function openAIPartsContent(parts) {
50
50
  function anthropicPartsContent(parts) {
51
51
  return parts.map(p => {
52
52
  if (p.type === "image") {
53
- const source = p.data
54
- ? { type: "base64", media_type: p.mediaType ?? "image/png", data: p.data }
55
- : { type: "url", url: p.url ?? "" };
53
+ const source = p.source?.kind === "base64"
54
+ ? { type: "base64", media_type: p.mediaType ?? "image/png", data: p.source.data }
55
+ : { type: "url", url: p.source?.kind === "url" ? p.source.url : "" };
56
56
  return { type: "image", source };
57
57
  }
58
58
  if (p.type === "audio") {
@@ -139,7 +139,6 @@ export function toAnthropicMessages(context, nativeReplay) {
139
139
  export async function collectStreamMessage(stream) {
140
140
  let content = "";
141
141
  const toolCalls = [];
142
- let outputTokens;
143
142
  for await (const evt of stream) {
144
143
  if (evt.type === "text_delta" && evt.delta)
145
144
  content += evt.delta;
@@ -147,9 +146,8 @@ export async function collectStreamMessage(stream) {
147
146
  toolCalls.push({ id: evt.id, name: evt.name, arguments: JSON.stringify(evt.arguments ?? {}) });
148
147
  }
149
148
  else if (evt.type === "usage") {
150
- outputTokens = evt.outputTokens ?? evt.totalTokens;
151
149
  }
152
150
  }
153
- return { role: "assistant", content, ...(outputTokens ? { tokenCount: outputTokens } : {}), ...(toolCalls.length ? { toolCalls } : {}) };
151
+ return { role: "assistant", content, ...(toolCalls.length ? { toolCalls } : {}) };
154
152
  }
155
153
  export { assistantReplayKey };
@@ -85,9 +85,6 @@ export function messageToKernelMessage(message) {
85
85
  arguments: tryParseJson(tc.arguments) ?? {},
86
86
  })),
87
87
  };
88
- if (message.tokenCount !== undefined) {
89
- out.token_count = message.tokenCount;
90
- }
91
88
  // Multimodal: serialize typed content parts to the kernel `Content::Parts` shape when present
92
89
  // (image/audio must survive the reconstruction→preload path, not just live ingress).
93
90
  if (message.contentParts && message.contentParts.length > 0) {
@@ -98,10 +95,10 @@ export function messageToKernelMessage(message) {
98
95
  return { type: "tool_result", call_id: part.callId, output: part.output, is_error: part.isError };
99
96
  }
100
97
  if (part.type === "image") {
101
- return { type: "image", url: part.url, data: part.data, media_type: part.mediaType, detail: part.detail };
98
+ return { type: "image", source: part.source, media_type: part.mediaType, detail: part.detail };
102
99
  }
103
100
  if (part.type === "audio") {
104
- return { type: "audio", data: part.data, media_type: part.mediaType };
101
+ return { type: "audio", source: part.source, media_type: part.mediaType };
105
102
  }
106
103
  return { type: "text", text: message.content };
107
104
  });
@@ -112,12 +109,12 @@ export function messageToKernelMessage(message) {
112
109
  return out;
113
110
  }
114
111
  export function toolResultToKernel(result) {
112
+ // Usage evidence enters through the host event contract; content alone cannot establish usage.
115
113
  const out = {
116
114
  call_id: result.callId,
117
115
  output: result.output,
118
116
  is_error: result.isError,
119
117
  is_fatal: result.isFatal ?? false,
120
- token_count: result.tokenCount ?? null,
121
118
  };
122
119
  if (result.errorKind !== undefined) {
123
120
  out.error_kind = result.errorKind;
@@ -173,9 +170,6 @@ export function kernelMessageToSdk(raw) {
173
170
  arguments: JSON.stringify(tc.arguments ?? {}),
174
171
  })),
175
172
  };
176
- if (typeof raw.token_count === "number") {
177
- message.tokenCount = raw.token_count;
178
- }
179
173
  if (typeof content === "string") {
180
174
  const parts = decodeCanonicalContentParts(content);
181
175
  if (parts) {
@@ -196,8 +190,7 @@ export function kernelMessageToSdk(raw) {
196
190
  case "image":
197
191
  contentParts.push({
198
192
  type: "image",
199
- ...(part.url ? { url: String(part.url) } : {}),
200
- ...(part.data ? { data: String(part.data) } : {}),
193
+ ...(part.source ? { source: part.source } : {}),
201
194
  ...(part.media_type ? { mediaType: String(part.media_type) } : {}),
202
195
  ...(part.detail === "auto" || part.detail === "low" || part.detail === "high"
203
196
  ? { detail: part.detail }
@@ -207,7 +200,7 @@ export function kernelMessageToSdk(raw) {
207
200
  case "audio":
208
201
  contentParts.push({
209
202
  type: "audio",
210
- data: String(part.data ?? ""),
203
+ source: part.source,
211
204
  ...(part.media_type ? { mediaType: String(part.media_type) } : {}),
212
205
  });
213
206
  break;
@@ -14,9 +14,8 @@ import type { SessionEvent } from "./session-log.js";
14
14
  * Walks `llm_completed` events (which is what the runner appends for every LLM call) and produces
15
15
  * one Message per event. Pass the result directly to `new ReplayProvider(messages)`.
16
16
  *
17
- * Accepts both wire shapes the SDK uses interchangeably:
18
- * - in-memory: `{ toolCalls, tokenCount, providerReplay }` (camelCase)
19
- * - serialised session-log: `{ tool_calls, token_count, provider_replay }` (snake_case)
17
+ * Accepts both camelCase and snake_case tool-call wire shapes. Token usage remains session
18
+ * evidence; the returned public Message mirror carries no token projection.
20
19
  *
21
20
  * @param events Session events, in original order. Accepts both `{ event, seq }` (the shape
22
21
  * `SessionLog.read()` returns) and a bare `SessionEvent[]`.
@@ -12,9 +12,8 @@
12
12
  * Walks `llm_completed` events (which is what the runner appends for every LLM call) and produces
13
13
  * one Message per event. Pass the result directly to `new ReplayProvider(messages)`.
14
14
  *
15
- * Accepts both wire shapes the SDK uses interchangeably:
16
- * - in-memory: `{ toolCalls, tokenCount, providerReplay }` (camelCase)
17
- * - serialised session-log: `{ tool_calls, token_count, provider_replay }` (snake_case)
15
+ * Accepts both camelCase and snake_case tool-call wire shapes. Token usage remains session
16
+ * evidence; the returned public Message mirror carries no token projection.
18
17
  *
19
18
  * @param events Session events, in original order. Accepts both `{ event, seq }` (the shape
20
19
  * `SessionLog.read()` returns) and a bare `SessionEvent[]`.
@@ -27,14 +26,12 @@ export function extractRecordedMessages(events) {
27
26
  continue;
28
27
  const e = event;
29
28
  const tcRaw = (e.toolCalls ?? e.tool_calls);
30
- const tokenCount = (e.tokenCount ?? e.token_count);
31
29
  out.push({
32
30
  role: "assistant",
33
31
  content: typeof e.content === "string" ? e.content : "",
34
32
  ...(Array.isArray(tcRaw) && tcRaw.length > 0
35
33
  ? { toolCalls: normalizeToolCalls(tcRaw) }
36
34
  : {}),
37
- ...(tokenCount !== undefined ? { tokenCount } : {}),
38
35
  });
39
36
  }
40
37
  return out;
@@ -16,8 +16,7 @@
16
16
  * - `inputTokens` is ESTIMATED from the rendered context this call carries (NOT a recorded value
17
17
  * from the original run). That's the point of replay-for-benchmarking: prompt may differ across
18
18
  * variants, response is pinned, so a cost Δ purely reflects the prompt change.
19
- * - `outputTokens` is taken from `message.tokenCount` when present; otherwise estimated from
20
- * `message.content.length / 4`.
19
+ * - `outputTokens` is estimated from `message.content.length / 4`.
21
20
  * - `cacheReadInputTokens` / `cacheCreationInputTokens` are emitted as 0 — replay has no real
22
21
  * cache state. Mechanisms whose Δ depends on cache behavior must validate with a live A/B too.
23
22
  *
@@ -16,8 +16,7 @@
16
16
  * - `inputTokens` is ESTIMATED from the rendered context this call carries (NOT a recorded value
17
17
  * from the original run). That's the point of replay-for-benchmarking: prompt may differ across
18
18
  * variants, response is pinned, so a cost Δ purely reflects the prompt change.
19
- * - `outputTokens` is taken from `message.tokenCount` when present; otherwise estimated from
20
- * `message.content.length / 4`.
19
+ * - `outputTokens` is estimated from `message.content.length / 4`.
21
20
  * - `cacheReadInputTokens` / `cacheCreationInputTokens` are emitted as 0 — replay has no real
22
21
  * cache state. Mechanisms whose Δ depends on cache behavior must validate with a live A/B too.
23
22
  *
@@ -68,13 +67,12 @@ export class ReplayProvider {
68
67
  role: "assistant",
69
68
  content: msg.content,
70
69
  ...(msg.toolCalls ? { toolCalls: msg.toolCalls } : {}),
71
- ...(msg.tokenCount !== undefined ? { tokenCount: msg.tokenCount } : {}),
72
70
  };
73
71
  }
74
72
  async *stream(context, tools, _extensions, _state, _signal) {
75
73
  const msg = this.pull();
76
74
  const inputTokens = this.estimateInputTokens(context, tools);
77
- const outputTokens = msg.tokenCount !== undefined ? msg.tokenCount : this.tokenizer(msg.content || "");
75
+ const outputTokens = this.tokenizer(msg.content || "");
78
76
  const usage = {
79
77
  type: "usage",
80
78
  totalTokens: inputTokens + outputTokens,
@@ -869,7 +869,6 @@ export class RuntimeRunner {
869
869
  role: "assistant",
870
870
  content: finalText,
871
871
  toolCalls: finalToolCalls,
872
- tokenCount: turnOutputTokens || turnTokens || undefined,
873
872
  };
874
873
  // P4 §2: assemble the measurement from the exact numbers that cross the boundary today
875
874
  // (input/output turn counters + the cache split). An invalid frame degrades to no
@@ -919,7 +918,6 @@ export class RuntimeRunner {
919
918
  await this.opts.sessionLog.append(sessionId, buildLlmCompletedEvent({
920
919
  turn: runtime.turn(),
921
920
  content: finalText,
922
- tokenCount: turnOutputTokens || turnTokens || undefined,
923
921
  toolCalls: finalToolCalls,
924
922
  providerReplay,
925
923
  effectId: providerEffectId,
@@ -1213,7 +1211,6 @@ export class RuntimeRunner {
1213
1211
  call_id: r.callId,
1214
1212
  output: r.output,
1215
1213
  is_error: r.isError,
1216
- token_count: r.tokenCount,
1217
1214
  content: { blocks: toolOutputBlocksToDurable(r.contentParts?.length ? r.contentParts : [{ type: "text", text: r.output }]) },
1218
1215
  })),
1219
1216
  });
@@ -1390,7 +1387,6 @@ export class RuntimeRunner {
1390
1387
  const newMsgs = runtime.drainNewMessages().map(m => ({
1391
1388
  role: m.role,
1392
1389
  content: m.content,
1393
- tokenCount: m.tokenCount,
1394
1390
  toolCalls: m.toolCalls?.length ? m.toolCalls : undefined,
1395
1391
  }));
1396
1392
  if (newMsgs.length > 0) {
@@ -2027,7 +2023,6 @@ export async function replayMessages(events, maxBytes, archiveStore) {
2027
2023
  content: userText,
2028
2024
  ...(contentParts ? { contentParts } : {}),
2029
2025
  toolCalls: [],
2030
- tokenCount: Math.max(1, Math.ceil(userText.length / 4)),
2031
2026
  });
2032
2027
  }
2033
2028
  else if (e.kind === "compressed") {
@@ -2040,7 +2035,6 @@ export async function replayMessages(events, maxBytes, archiveStore) {
2040
2035
  role: "system",
2041
2036
  content: systemText,
2042
2037
  toolCalls: [],
2043
- tokenCount: Math.max(1, Math.ceil(systemText.length / 4)),
2044
2038
  });
2045
2039
  }
2046
2040
  }
@@ -2057,7 +2051,6 @@ export async function replayMessages(events, maxBytes, archiveStore) {
2057
2051
  const systemText = `[Compressed context: turn ${e.turn}]\n${e.summary}`;
2058
2052
  messages.push({
2059
2053
  role: "system", content: systemText, toolCalls: [],
2060
- tokenCount: Math.max(1, Math.ceil(systemText.length / 4)),
2061
2054
  });
2062
2055
  }
2063
2056
  }
@@ -2067,7 +2060,6 @@ export async function replayMessages(events, maxBytes, archiveStore) {
2067
2060
  role: "assistant",
2068
2061
  content: sanitizeReplayText(e.content, maxBytes),
2069
2062
  toolCalls: e.tool_calls ?? [],
2070
- tokenCount: e.token_count,
2071
2063
  });
2072
2064
  }
2073
2065
  else if (e.kind === "tool_completed") {
@@ -2082,7 +2074,6 @@ export async function replayMessages(events, maxBytes, archiveStore) {
2082
2074
  content: "",
2083
2075
  toolCalls: [],
2084
2076
  contentParts: [{ type: "tool_result", callId: durable.call_id, output: sanitizeReplayText(r.output, maxBytes), isError: durable.is_error, ...(durable.blocks.length ? { contentParts: durableBlocksToToolOutput(durable.blocks) } : {}) }],
2085
- tokenCount: r.token_count,
2086
2077
  });
2087
2078
  }
2088
2079
  }
@@ -2183,14 +2174,13 @@ function attachmentsToKernelMessage(parts) {
2183
2174
  if (p.type === "image") {
2184
2175
  return {
2185
2176
  type: "image",
2186
- ...(p.url ? { url: p.url } : {}),
2187
- ...(p.data ? { data: p.data } : {}),
2177
+ ...(p.source ? { source: p.source } : {}),
2188
2178
  ...(p.mediaType ? { media_type: p.mediaType } : {}),
2189
2179
  ...(p.detail ? { detail: p.detail } : {}),
2190
2180
  };
2191
2181
  }
2192
2182
  if (p.type === "audio")
2193
- return { type: "audio", data: p.data, media_type: p.mediaType };
2183
+ return { type: "audio", source: p.source, media_type: p.mediaType };
2194
2184
  if (p.type === "text")
2195
2185
  return { type: "text", text: p.text };
2196
2186
  return { type: "text", text: "" };
@@ -4,7 +4,7 @@ import type { WorkflowNodeStatus } from "./types/agent.js";
4
4
  export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay-sanitize.js";
5
5
  /**
6
6
  * Normalize a persisted llm_completed event for recovery. Content is sanitized
7
- * and token_count backfilled, but the stored `provider_replay` envelope is
7
+ * and any existing token_count remains raw evidence, but the stored `provider_replay` envelope is
8
8
  * passed through verbatim — this layer is provider-neutral and never
9
9
  * synthesizes protocol-specific replay shapes. Canonical replay seeding is the
10
10
  * responsibility of the target provider's `seedProviderReplay`.
@@ -1,11 +1,8 @@
1
1
  import { sanitizeReplayText } from "./replay-sanitize.js";
2
2
  export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay-sanitize.js";
3
- function estimateTokenCount(text) {
4
- return Math.max(1, Math.ceil(text.length / 4));
5
- }
6
3
  /**
7
4
  * Normalize a persisted llm_completed event for recovery. Content is sanitized
8
- * and token_count backfilled, but the stored `provider_replay` envelope is
5
+ * and any existing token_count remains raw evidence, but the stored `provider_replay` envelope is
9
6
  * passed through verbatim — this layer is provider-neutral and never
10
7
  * synthesizes protocol-specific replay shapes. Canonical replay seeding is the
11
8
  * responsibility of the target provider's `seedProviderReplay`.
@@ -19,7 +16,7 @@ export function normalizeLlmCompleted(event, maxBytes) {
19
16
  turn: event.turn,
20
17
  content,
21
18
  tool_calls: toolCalls,
22
- token_count: event.token_count ?? estimateTokenCount(content),
19
+ ...(event.token_count !== undefined ? { token_count: event.token_count } : {}),
23
20
  ...(providerReplay ? { provider_replay: providerReplay } : {}),
24
21
  // P4 §3: the evidence-plane fields ride along verbatim — recovery never reads them
25
22
  // (SessionLog is evidence, not authority), so normalize must neither synthesize nor drop them.
@@ -218,7 +218,6 @@ export interface KernelWorkflowNodeOutcome {
218
218
  name: string;
219
219
  arguments?: Record<string, unknown>;
220
220
  }>;
221
- token_count?: number;
222
221
  };
223
222
  }
224
223
  export interface WorkflowNodeOutcome {
@@ -92,7 +92,6 @@ export function subAgentResultToKernel(result) {
92
92
  name: tc.name,
93
93
  arguments: safeParseToolArgs(tc.arguments),
94
94
  })),
95
- ...(finalMessage.tokenCount !== undefined ? { token_count: finalMessage.tokenCount } : {}),
96
95
  }
97
96
  : null,
98
97
  turns_used: result.result.turnsUsed,
@@ -146,7 +145,6 @@ export function workflowNodeOutcomeFromKernel(raw) {
146
145
  name: call.name,
147
146
  arguments: JSON.stringify(call.arguments ?? {}),
148
147
  })),
149
- ...(output.token_count != null ? { tokenCount: output.token_count } : {}),
150
148
  },
151
149
  }
152
150
  : {}),
package/dist/types.d.ts CHANGED
@@ -2,10 +2,7 @@ import type { WorkflowNodeSpec } from "./runtime/types/agent.js";
2
2
  export interface ContentPart {
3
3
  type: "text" | "image" | "audio" | "tool_result";
4
4
  text?: string;
5
- /** Remote image URL (mutually exclusive with `data`). */
6
- url?: string;
7
- /** Raw base64-encoded bytes (image/audio). */
8
- data?: string;
5
+ source?: MediaSource;
9
6
  /** MIME type, e.g. `"image/png"`. */
10
7
  mediaType?: string;
11
8
  /** OpenAI vision detail level. */
@@ -45,7 +42,6 @@ export type ToolOutputBlock = {
45
42
  export interface Message {
46
43
  role: "system" | "user" | "assistant" | "tool";
47
44
  content: string;
48
- tokenCount?: number;
49
45
  toolCalls?: ToolCall[];
50
46
  /** Multimodal parts (text + image/audio). When present, providers render these
51
47
  * instead of the plain `content` string. */
@@ -63,7 +59,6 @@ export interface ToolResult {
63
59
  isError: boolean;
64
60
  isFatal?: boolean;
65
61
  errorKind?: ToolErrorKind;
66
- tokenCount?: number;
67
62
  contentParts?: ToolOutputBlock[];
68
63
  }
69
64
  export interface ToolSchema {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.2.66",
3
+ "version": "0.2.68",
4
4
  "description": "DeepStrike WASM SDK — browser, Cloudflare Workers, Deno Deploy",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "test:canonical-binding": "wasm-pack build ../crates/deepstrike-wasm --target nodejs --dev --out-dir ../../wasm/pkg-node && node tests/canonical-binding.node.cjs && node tests/golden-fixtures-sweep.node.cjs"
18
18
  },
19
19
  "dependencies": {
20
- "@deepstrike/wasm-kernel": "0.2.66"
20
+ "@deepstrike/wasm-kernel": "0.2.68"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/jest": "^30.0.0",