@deepstrike/wasm 0.1.14 → 0.1.15

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.
@@ -1,9 +1,13 @@
1
- import type { RenderedContext, ToolSchema, StreamEvent, LLMProvider, Message } from "../types.js";
1
+ import type { RenderedContext, ToolSchema, StreamEvent, LLMProvider, Message, ProviderReplay } from "../types.js";
2
2
  export declare class AnthropicProvider implements LLMProvider {
3
3
  private readonly apiKey;
4
4
  private readonly model;
5
5
  private readonly maxTokens;
6
+ private nativeAssistantBlocks;
6
7
  constructor(apiKey: string, model?: string, maxTokens?: number);
8
+ peekProviderReplay(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
9
+ seedProviderReplay(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
7
10
  complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
8
11
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
12
+ private rememberNativeBlocks;
9
13
  }
@@ -1,4 +1,4 @@
1
- import { collectStreamMessage, toAnthropicMessages } from "./base.js";
1
+ import { assistantReplayKey, collectStreamMessage, toAnthropicMessages } from "./base.js";
2
2
  function buildAnthropicTools(tools) {
3
3
  return tools.map(t => ({ name: t.name, description: t.description, input_schema: JSON.parse(t.parameters) }));
4
4
  }
@@ -6,17 +6,27 @@ export class AnthropicProvider {
6
6
  apiKey;
7
7
  model;
8
8
  maxTokens;
9
+ nativeAssistantBlocks = new Map();
9
10
  constructor(apiKey, model = "claude-sonnet-4-6", maxTokens = 8096) {
10
11
  this.apiKey = apiKey;
11
12
  this.model = model;
12
13
  this.maxTokens = maxTokens;
13
14
  }
15
+ peekProviderReplay(message) {
16
+ const blocks = this.nativeAssistantBlocks.get(assistantReplayKey(message));
17
+ return blocks?.length ? { native_blocks: blocks } : undefined;
18
+ }
19
+ seedProviderReplay(message, replay) {
20
+ if (replay.native_blocks?.length) {
21
+ this.nativeAssistantBlocks.set(assistantReplayKey(message), replay.native_blocks);
22
+ }
23
+ }
14
24
  async complete(context, tools, extensions) {
15
25
  return collectStreamMessage(this.stream(context, tools, extensions));
16
26
  }
17
27
  async *stream(context, tools, extensions) {
18
28
  const system = context.systemText || undefined;
19
- const msgs = toAnthropicMessages(context);
29
+ const msgs = toAnthropicMessages(context, message => this.nativeAssistantBlocks.get(assistantReplayKey(message)));
20
30
  const body = {
21
31
  model: this.model,
22
32
  max_tokens: this.maxTokens,
@@ -40,6 +50,9 @@ export class AnthropicProvider {
40
50
  if (!resp.ok)
41
51
  throw new Error(`Anthropic ${resp.status}: ${await resp.text()}`);
42
52
  const toolBlocks = {};
53
+ const nativeBlocks = {};
54
+ let finalText = "";
55
+ const finalToolCalls = [];
43
56
  const reader = resp.body.getReader();
44
57
  const decoder = new TextDecoder();
45
58
  let buf = "";
@@ -68,35 +81,59 @@ export class AnthropicProvider {
68
81
  }
69
82
  }
70
83
  else if (evt.type === "content_block_start") {
84
+ const idx = evt.index;
85
+ nativeBlocks[idx] = { ...evt.content_block };
71
86
  const cb = evt.content_block;
72
87
  if (cb.type === "tool_use")
73
- toolBlocks[evt.index] = { id: cb.id, name: cb.name, argsBuf: "" };
88
+ toolBlocks[idx] = { id: cb.id, name: cb.name, argsBuf: "" };
74
89
  }
75
90
  else if (evt.type === "content_block_delta") {
76
91
  const d = evt.delta;
77
92
  const idx = evt.index;
78
- if (d.type === "text_delta")
93
+ if (d.type === "text_delta") {
94
+ finalText += String(d.text);
95
+ nativeBlocks[idx] = { ...nativeBlocks[idx], text: String(nativeBlocks[idx]?.text ?? "") + d.text };
79
96
  yield { type: "text_delta", delta: d.text };
80
- else if (d.type === "thinking_delta")
97
+ }
98
+ else if (d.type === "thinking_delta") {
99
+ nativeBlocks[idx] = { ...nativeBlocks[idx], thinking: String(nativeBlocks[idx]?.thinking ?? "") + d.thinking };
81
100
  yield { type: "thinking_delta", delta: d.thinking };
82
- else if (d.type === "input_json_delta" && toolBlocks[idx])
101
+ }
102
+ else if (d.type === "signature_delta") {
103
+ nativeBlocks[idx] = { ...nativeBlocks[idx], signature: String(nativeBlocks[idx]?.signature ?? "") + d.signature };
104
+ }
105
+ else if (d.type === "input_json_delta" && toolBlocks[idx]) {
83
106
  toolBlocks[idx].argsBuf += d.partial_json;
84
- }
85
- else if (evt.type === "content_block_stop" && toolBlocks[evt.index]) {
86
- const tb = toolBlocks[evt.index];
87
- delete toolBlocks[evt.index];
88
- let args = {};
89
- try {
90
- args = JSON.parse(tb.argsBuf || "{}");
91
107
  }
92
- catch {
93
- args = {};
108
+ }
109
+ else if (evt.type === "content_block_stop") {
110
+ const idx = evt.index;
111
+ if (toolBlocks[idx]) {
112
+ const tb = toolBlocks[idx];
113
+ delete toolBlocks[idx];
114
+ let args = {};
115
+ try {
116
+ args = JSON.parse(tb.argsBuf || "{}");
117
+ }
118
+ catch {
119
+ args = {};
120
+ }
121
+ nativeBlocks[idx] = { ...nativeBlocks[idx], input: args };
122
+ finalToolCalls.push({ id: tb.id, name: tb.name, arguments: JSON.stringify(args) });
123
+ yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
94
124
  }
95
- yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
96
125
  }
97
126
  }
98
127
  catch { /* skip malformed */ }
99
128
  }
100
129
  }
130
+ this.rememberNativeBlocks({ content: finalText, toolCalls: finalToolCalls }, Object.keys(nativeBlocks).map(Number).sort((a, b) => a - b).map(index => nativeBlocks[index]));
131
+ }
132
+ rememberNativeBlocks(message, blocks) {
133
+ if (!blocks.length)
134
+ return;
135
+ if (!message.toolCalls?.length && !blocks.some(b => b.type === "thinking"))
136
+ return;
137
+ this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
101
138
  }
102
139
  }
@@ -1,11 +1,8 @@
1
1
  import type { Message, RenderedContext } from "../types.js";
2
+ import { assistantReplayKey } from "../runtime/provider-replay.js";
2
3
  /** Build OpenAI-compatible chat messages from a RenderedContext. */
3
4
  export declare function toOpenAIMessages(context: RenderedContext): Array<Record<string, unknown>>;
4
- /** Anthropic messages array (system is a separate top-level param). */
5
- export declare function toAnthropicMessages(context: RenderedContext): Array<{
6
- role: string;
7
- content: string;
8
- }>;
5
+ export declare function toAnthropicMessages(context: RenderedContext, nativeReplay?: (message: Message) => Array<Record<string, unknown>> | undefined): Array<Record<string, unknown>>;
9
6
  /** Collect a non-streaming assistant Message from stream events. */
10
7
  export declare function collectStreamMessage(stream: AsyncIterable<{
11
8
  type: string;
@@ -14,3 +11,4 @@ export declare function collectStreamMessage(stream: AsyncIterable<{
14
11
  name?: string;
15
12
  arguments?: Record<string, unknown>;
16
13
  }>): Promise<Message>;
14
+ export { assistantReplayKey };
@@ -1,3 +1,12 @@
1
+ import { assistantReplayKey } from "../runtime/provider-replay.js";
2
+ function parseToolArguments(args) {
3
+ try {
4
+ return JSON.parse(args || "{}");
5
+ }
6
+ catch {
7
+ return {};
8
+ }
9
+ }
1
10
  /** Build OpenAI-compatible chat messages from a RenderedContext. */
2
11
  export function toOpenAIMessages(context) {
3
12
  const messages = [];
@@ -21,11 +30,34 @@ export function toOpenAIMessages(context) {
21
30
  }
22
31
  return messages;
23
32
  }
24
- /** Anthropic messages array (system is a separate top-level param). */
25
- export function toAnthropicMessages(context) {
26
- return context.turns
27
- .filter(m => m.role !== "system")
28
- .map(m => ({ role: m.role, content: m.content }));
33
+ export function toAnthropicMessages(context, nativeReplay) {
34
+ const result = [];
35
+ for (const msg of context.turns) {
36
+ if (msg.role === "tool") {
37
+ result.push({ role: "user", content: [{ type: "tool_result", tool_use_id: msg.content ? undefined : "", content: msg.content, is_error: false }] });
38
+ continue;
39
+ }
40
+ if (msg.role === "assistant" && msg.toolCalls?.length) {
41
+ const replay = nativeReplay?.(msg);
42
+ if (replay) {
43
+ result.push({ role: "assistant", content: replay });
44
+ continue;
45
+ }
46
+ const blocks = [];
47
+ if (msg.content)
48
+ blocks.push({ type: "text", text: msg.content });
49
+ blocks.push(...msg.toolCalls.map(tc => ({
50
+ type: "tool_use",
51
+ id: tc.id,
52
+ name: tc.name,
53
+ input: parseToolArguments(tc.arguments),
54
+ })));
55
+ result.push({ role: "assistant", content: blocks });
56
+ continue;
57
+ }
58
+ result.push({ role: msg.role, content: msg.content });
59
+ }
60
+ return result;
29
61
  }
30
62
  /** Collect a non-streaming assistant Message from stream events. */
31
63
  export async function collectStreamMessage(stream) {
@@ -40,3 +72,4 @@ export async function collectStreamMessage(stream) {
40
72
  }
41
73
  return { role: "assistant", content, ...(toolCalls.length ? { toolCalls } : {}) };
42
74
  }
75
+ export { assistantReplayKey };
@@ -0,0 +1,7 @@
1
+ import type { LLMProvider, Message, ProviderReplay, ToolCall } from "../types.js";
2
+ import type { SessionEvent } from "./session-log.js";
3
+ export declare function assistantReplayKey(message: Pick<Message, "content" | "toolCalls">): string;
4
+ export declare function seedProviderReplayFromEvents(provider: LLMProvider, events: Array<{
5
+ event: SessionEvent;
6
+ }>): void;
7
+ export declare function peekProviderReplay(provider: LLMProvider, content: string, toolCalls: ToolCall[]): ProviderReplay | undefined;
@@ -0,0 +1,18 @@
1
+ export function assistantReplayKey(message) {
2
+ return JSON.stringify({
3
+ content: message.content,
4
+ toolCalls: message.toolCalls ?? [],
5
+ });
6
+ }
7
+ export function seedProviderReplayFromEvents(provider, events) {
8
+ if (!provider.seedProviderReplay)
9
+ return;
10
+ for (const { event } of events) {
11
+ if (event.kind !== "llm_completed" || !event.provider_replay)
12
+ continue;
13
+ provider.seedProviderReplay({ content: event.content, toolCalls: event.tool_calls ?? [] }, event.provider_replay);
14
+ }
15
+ }
16
+ export function peekProviderReplay(provider, content, toolCalls) {
17
+ return provider.peekProviderReplay?.({ content, toolCalls });
18
+ }
@@ -1,4 +1,5 @@
1
1
  import { getKernel } from "./kernel.js";
2
+ import { peekProviderReplay, seedProviderReplayFromEvents } from "./provider-replay.js";
2
3
  export class RuntimeRunner {
3
4
  opts;
4
5
  interrupted = false;
@@ -129,6 +130,7 @@ export class RuntimeRunner {
129
130
  if (this.opts.knowledgeSource)
130
131
  sm.setKnowledgeEnabled(true);
131
132
  if (priorEvents && priorEvents.length > 0) {
133
+ seedProviderReplayFromEvents(this.opts.provider, priorEvents);
132
134
  sm.preloadHistory(replayMessages(priorEvents));
133
135
  }
134
136
  const sessionStart = Date.now();
@@ -203,12 +205,14 @@ export class RuntimeRunner {
203
205
  toolCalls: finalToolCalls,
204
206
  tokenCount: turnTokens || undefined,
205
207
  });
208
+ const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
206
209
  await this.opts.sessionLog.append(sessionId, {
207
210
  kind: "llm_completed",
208
211
  turn: sm.turn,
209
212
  content: finalText,
210
213
  token_count: turnTokens || undefined,
211
214
  tool_calls: finalToolCalls,
215
+ ...(providerReplay ? { provider_replay: providerReplay } : {}),
212
216
  });
213
217
  }
214
218
  else if (action.kind === "execute_tools") {
@@ -1,4 +1,4 @@
1
- import type { ToolCall } from "../types.js";
1
+ import type { ProviderReplay, ToolCall } from "../types.js";
2
2
  export type SessionEvent = {
3
3
  kind: "run_started";
4
4
  run_id: string;
@@ -12,6 +12,7 @@ export type SessionEvent = {
12
12
  content: string;
13
13
  token_count?: number;
14
14
  tool_calls: ToolCall[];
15
+ provider_replay?: ProviderReplay;
15
16
  } | {
16
17
  kind: "tool_requested";
17
18
  turn: number;
package/dist/types.d.ts CHANGED
@@ -75,12 +75,18 @@ export interface PermissionRequestEvent extends StreamEvent {
75
75
  * The framework creates and threads this object; providers may read/write it.
76
76
  */
77
77
  export type ProviderRunState = Record<string, unknown>;
78
+ export interface ProviderReplay {
79
+ native_blocks?: Array<Record<string, unknown>>;
80
+ reasoning_content?: string;
81
+ }
78
82
  export interface LLMProvider {
79
83
  createRunState?(): ProviderRunState;
80
84
  runtimePolicy?(): {
81
85
  maxTurns?: number;
82
86
  timeoutMs?: number;
83
87
  };
88
+ peekProviderReplay?(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
89
+ seedProviderReplay?(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
84
90
  complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
85
91
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
86
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "DeepStrike WASM SDK — browser, Cloudflare Workers, Deno Deploy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "test": "node --experimental-vm-modules node_modules/.bin/jest"
16
16
  },
17
17
  "dependencies": {
18
- "@deepstrike/wasm-kernel": "0.1.14"
18
+ "@deepstrike/wasm-kernel": "0.1.15"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/jest": "^30.0.0",