@deepstrike/wasm 0.2.33 → 0.2.35

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 CHANGED
@@ -1,3 +1,9 @@
1
+ <p align="center">
2
+ <a href="https://github.com/kongusen/deepstrike">
3
+ <img src="https://raw.githubusercontent.com/kongusen/deepstrike/main/docs/public/banner.png" alt="DeepStrike" width="420" />
4
+ </a>
5
+ </p>
6
+
1
7
  # DeepStrike WASM SDK
2
8
 
3
9
  Runtime framework built on a Rust kernel compiled to WebAssembly. Runs in browsers, Cloudflare Workers, Deno Deploy, and Vercel Edge — anywhere that supports `fetch` and WASM.
@@ -250,6 +250,8 @@ export class AnthropicProvider {
250
250
  const inputTokens = uncachedInput + cacheReadTokens + cacheCreationTokens;
251
251
  if (inputTokens > 0 || outputTokens > 0) {
252
252
  const bySlot = estimateCacheReadBySlot(cacheReadTokens, slotBp);
253
+ // stop_reason rides on message_delta; "max_tokens" drives output-cap recovery.
254
+ const stopReason = evt.delta?.stop_reason;
253
255
  yield {
254
256
  type: "usage",
255
257
  totalTokens: inputTokens + outputTokens,
@@ -258,6 +260,7 @@ export class AnthropicProvider {
258
260
  cacheReadInputTokens: cacheReadTokens,
259
261
  cacheCreationInputTokens: cacheCreationTokens,
260
262
  ...(bySlot ? { cacheReadInputTokensBySlot: bySlot } : {}),
263
+ ...(stopReason ? { stopReason } : {}),
261
264
  };
262
265
  }
263
266
  }
@@ -37,6 +37,9 @@ export class OpenAIProvider {
37
37
  const reader = resp.body.getReader();
38
38
  const decoder = new TextDecoder();
39
39
  let buf = "";
40
+ // Phase 4: OpenAI signals an output-cap truncation via finish_reason="length"; the kernel
41
+ // treats it as a truncation (== Anthropic "max_tokens") and drives output-cap recovery.
42
+ let finishReason;
40
43
  while (true) {
41
44
  const { done, value } = await reader.read();
42
45
  if (done)
@@ -48,10 +51,17 @@ export class OpenAIProvider {
48
51
  if (!line.startsWith("data: "))
49
52
  continue;
50
53
  const data = line.slice(6).trim();
51
- if (data === "[DONE]")
54
+ if (data === "[DONE]") {
55
+ // Surface an output-cap truncation (finish_reason="length") to the runner/kernel. Emitted
56
+ // as a usage frame (the channel the runner reads stopReason from) before the early return.
57
+ if (finishReason)
58
+ yield { type: "usage", totalTokens: 0, stopReason: finishReason };
52
59
  return;
60
+ }
53
61
  try {
54
62
  const chunk = JSON.parse(data);
63
+ if (chunk.choices?.[0]?.finish_reason)
64
+ finishReason = chunk.choices[0].finish_reason ?? undefined;
55
65
  const delta = chunk.choices?.[0]?.delta;
56
66
  if (!delta)
57
67
  continue;
@@ -5,7 +5,7 @@ import { peekProviderReplay, seedProviderReplayFromEvents } from "./provider-rep
5
5
  import { sanitizeReplayText } from "./replay-sanitize.js";
6
6
  import { formatToolError } from "../tools/errors.js";
7
7
  import { buildLlmCompletedEvent, buildRunTerminalEvent, buildWorkflowNodeCompletedEvent, buildWorkflowNodesSubmittedEvent, recoverCompletedWorkflowNodes, recoverSubmittedWorkflowNodes, repairEventsForRecovery, } from "./session-repair.js";
8
- import { forceCompact, kernelAction, kernelApply, kernelMaybeAction, messageToKernelMessage, skillMetadataToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
8
+ import { kernelAction, kernelApply, kernelMaybeAction, messageToKernelMessage, skillMetadataToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
9
9
  import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass, milestoneCheckResultToKernel, spawnObservationToManifest, subAgentResultToKernel, submitWorkflowNodesToKernel, submitWorkflowToKernel, workflowBudgetNote, workflowNodeToManifest, workflowNodeToSpec, workflowSpecToKernel, } from "./types/agent.js";
10
10
  import { defaultSubAgentOrchestrator } from "./sub-agent-orchestrator.js";
11
11
  import { extractJsonValue, schemaInstruction, schemaRetryInstruction, validateAgainstSchema, } from "./output-schema.js";
@@ -408,7 +408,6 @@ export class RuntimeRunner {
408
408
  let action = resumeMidRun
409
409
  ? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
410
410
  : kernelAction(runtime, this.pendingObservations, startPayload);
411
- let hasAttemptedReactiveCompact = false;
412
411
  // P0-C: the skill loaded and in effect going into the current turn → per-turn `activeSkill` metric.
413
412
  let activeSkill;
414
413
  // I0b: kernel-throw safety net — see Node runner for full rationale.
@@ -466,7 +465,7 @@ export class RuntimeRunner {
466
465
  let turnCacheReadTokens = 0;
467
466
  let turnCacheCreationTokens = 0;
468
467
  let turnCacheReadBySlot;
469
- let shouldRetry = false;
468
+ let turnStopReason;
470
469
  const abortSignal = this.abortController?.signal;
471
470
  try {
472
471
  for await (const evt of this.opts.provider.stream(context, tools, Object.keys(ext).length ? ext : undefined, providerState, abortSignal)) {
@@ -483,6 +482,9 @@ export class RuntimeRunner {
483
482
  turnCacheCreationTokens = usageEvt.cacheCreationInputTokens ?? 0;
484
483
  // I1: per-slot attribution forwarded to TurnMetrics; undefined on non-Anthropic providers.
485
484
  turnCacheReadBySlot = usageEvt.cacheReadInputTokensBySlot;
485
+ // Phase 4: stop_reason drives the kernel's max-output-tokens recovery.
486
+ if (usageEvt.stopReason)
487
+ turnStopReason = usageEvt.stopReason;
486
488
  continue;
487
489
  }
488
490
  yield evt;
@@ -495,23 +497,31 @@ export class RuntimeRunner {
495
497
  }
496
498
  }
497
499
  catch (err) {
498
- // #2-B-ii: an aborted in-flight request surfaces as an AbortError — treat as an interrupt.
499
500
  if (abortSignal?.aborted) {
501
+ // #2-B-ii: an aborted in-flight request surfaces as an AbortError — treat it as an
502
+ // interrupt (the post-stream `aborted` check below converts it to a clean
503
+ // timeout/UserAbort), not a crash or a provider error.
500
504
  this.interrupted = true;
501
505
  }
502
- const errMsg = formatToolError(err).toLowerCase();
503
- if ((errMsg.includes("413") || errMsg.includes("too long") || errMsg.includes("context length exceeded") || errMsg.includes("context_length_exceeded")) &&
504
- !hasAttemptedReactiveCompact) {
505
- hasAttemptedReactiveCompact = true;
506
- if (forceCompact(runtime, this.pendingObservations)) {
507
- nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
508
- shouldRetry = true;
506
+ else {
507
+ // Reactive recovery is now a kernel decision. Forward the raw provider error and
508
+ // dispatch whatever the kernel returns: `call_provider` to retry with a freshly
509
+ // compacted context, or `done` to terminate with an honest `ContextOverflow`. The
510
+ // classify + compact + retry + give-up policy lives in the kernel (one place), not
511
+ // duplicated across the four SDK runners.
512
+ action = kernelAction(runtime, this.pendingObservations, {
513
+ kind: "provider_error",
514
+ message: formatToolError(err),
515
+ });
516
+ // Withholding (query.ts parity): surface the raw provider error only when the kernel
517
+ // could NOT recover (it returned a terminal). On a recovered retry (`call_provider`)
518
+ // the error stays hidden. `continue` re-enters the loop: a recovered turn persists its
519
+ // compaction archive via the loop-top appendObservations, and a terminal `done` exits
520
+ // through `isTerminal()`.
521
+ if (action.kind === "done") {
522
+ yield { type: "error", message: formatToolError(err) };
509
523
  }
510
- }
511
- if (!shouldRetry) {
512
- yield { type: "error", message: formatToolError(err) };
513
- action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
514
- break;
524
+ continue;
515
525
  }
516
526
  }
517
527
  // #2-B-ii: stream aborted (preempt/interrupt) via the break path — end the turn now.
@@ -519,14 +529,6 @@ export class RuntimeRunner {
519
529
  action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
520
530
  break;
521
531
  }
522
- if (shouldRetry) {
523
- action = {
524
- kind: "call_provider",
525
- context: runtime.render(),
526
- tools,
527
- };
528
- continue;
529
- }
530
532
  const assistantMessage = {
531
533
  role: "assistant",
532
534
  content: finalText,
@@ -539,6 +541,7 @@ export class RuntimeRunner {
539
541
  ...(turnInputTokens > 0 ? { observed_input_tokens: turnInputTokens } : {}),
540
542
  ...(turnOutputTokens > 0 ? { observed_output_tokens: turnOutputTokens } : {}),
541
543
  now_ms: Date.now(),
544
+ ...(turnStopReason ? { stop_reason: turnStopReason } : {}),
542
545
  };
543
546
  let nextAction = kernelMaybeAction(runtime, this.pendingObservations, providerEvent);
544
547
  const hasSuspended = this.pendingObservations.some(o => o.kind === "suspended");
package/dist/types.d.ts CHANGED
@@ -73,7 +73,8 @@ export interface UsageEvent extends StreamEvent {
73
73
  system?: number;
74
74
  tools?: number;
75
75
  messages?: number;
76
- };
76
+ }; /** Provider stop reason — `max_tokens` (Anthropic) / `length` (OpenAI) flag an output-cap truncation driving the kernel's max-output-tokens recovery. */
77
+ stopReason?: string;
77
78
  }
78
79
  export interface ThinkingDelta extends StreamEvent {
79
80
  type: "thinking_delta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.2.33",
3
+ "version": "0.2.35",
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.2.33"
18
+ "@deepstrike/wasm-kernel": "0.2.35"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/jest": "^30.0.0",