@deepstrike/sdk 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 Node.js SDK
2
8
 
3
9
  Runtime framework built on a Rust kernel. The kernel owns loop control, context compression, governance, signal routing, and memory paging — the SDK owns all I/O (LLM calls, tool execution, disk, long-term memory).
@@ -182,6 +182,9 @@ export class AnthropicProvider {
182
182
  // cache-heavy turn look tiny and suppress compaction until a 413.
183
183
  const inputTokens = uncachedInput + cacheReadTokens + cacheCreationTokens;
184
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;
185
188
  yield {
186
189
  type: "usage",
187
190
  totalTokens: inputTokens + outputTokens,
@@ -190,6 +193,7 @@ export class AnthropicProvider {
190
193
  cacheReadInputTokens: cacheReadTokens,
191
194
  cacheCreationInputTokens: cacheCreationTokens,
192
195
  ...(bySlot ? { cacheReadInputTokensBySlot: bySlot } : {}),
196
+ ...(stopReason ? { stopReason } : {}),
193
197
  };
194
198
  }
195
199
  }
@@ -250,6 +250,11 @@ export class OpenAIChatProvider {
250
250
  let inputTokens = 0;
251
251
  let outputTokens = 0;
252
252
  let cacheReadTokens = 0;
253
+ // Phase 4: OpenAI signals an output-cap truncation via finish_reason="length", which arrives on
254
+ // a `choices` frame separate from the trailing `usage` frame — so capture it and attach it to the
255
+ // usage event the runner reads. The kernel treats "length" as a truncation (== Anthropic
256
+ // "max_tokens"); other reasons ("stop"/"tool_calls") pass through harmlessly.
257
+ let finishReason;
253
258
  for await (const chunk of stream) {
254
259
  if (chunk.usage) {
255
260
  totalTokens = chunk.usage.total_tokens;
@@ -261,6 +266,8 @@ export class OpenAIChatProvider {
261
266
  const choice = chunk.choices[0];
262
267
  if (!choice)
263
268
  continue;
269
+ if (choice.finish_reason)
270
+ finishReason = choice.finish_reason;
264
271
  const delta = choice.delta;
265
272
  if (!delta)
266
273
  continue;
@@ -317,7 +324,7 @@ export class OpenAIChatProvider {
317
324
  rememberStream();
318
325
  yield* emitPendingToolCalls();
319
326
  if (totalTokens > 0)
320
- yield { type: "usage", totalTokens, inputTokens, outputTokens, ...(cacheReadTokens > 0 ? { cacheReadInputTokens: cacheReadTokens } : {}) };
327
+ yield { type: "usage", totalTokens, inputTokens, outputTokens, ...(cacheReadTokens > 0 ? { cacheReadInputTokens: cacheReadTokens } : {}), ...(finishReason ? { stopReason: finishReason } : {}) };
321
328
  }
322
329
  /**
323
330
  * Default `prompt_cache_key` derived from the cacheable prefix (system prompt +
@@ -5,7 +5,7 @@ import { peekProviderReplay, seedProviderReplayFromEvents } from "./provider-rep
5
5
  import { sanitizeReplayText } from "./replay-sanitize.js";
6
6
  import { buildLlmCompletedEvent, buildRunTerminalEvent, buildWorkflowNodeCompletedEvent, buildWorkflowNodesSubmittedEvent, recoverCompletedWorkflowNodes, recoverSubmittedWorkflowNodes, repairEventsForRecovery, } from "./session-repair.js";
7
7
  import { KernelPrimitivesDashboard } from "./kernel-primitives-dashboard.js";
8
- import { capabilityMarker, capabilitySkill, capabilityTool, capabilityCommandMount, capabilityCommandUnmount, kernelAction, kernelApply, kernelMaybeAction, forceCompact, messageToKernelMessage, skillMetadataToKernel, taskUpdateToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
8
+ import { capabilityMarker, capabilitySkill, capabilityTool, capabilityCommandMount, capabilityCommandUnmount, kernelAction, kernelApply, kernelMaybeAction, messageToKernelMessage, skillMetadataToKernel, taskUpdateToKernel, 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";
@@ -1077,7 +1077,6 @@ export class RuntimeRunner {
1077
1077
  let action = resumeMidRun
1078
1078
  ? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
1079
1079
  : kernelAction(runtime, this.pendingObservations, startPayload);
1080
- let hasAttemptedReactiveCompact = false;
1081
1080
  // P0-C: the skill loaded and in effect going into the current turn (updated when the model's
1082
1081
  // `skill` tool call resolves). Drives the per-turn `activeSkill` metric → dwell measurement.
1083
1082
  let activeSkill;
@@ -1158,7 +1157,7 @@ export class RuntimeRunner {
1158
1157
  let turnCacheReadTokens = 0;
1159
1158
  let turnCacheCreationTokens = 0;
1160
1159
  let turnCacheReadBySlot;
1161
- let shouldRetry = false;
1160
+ let turnStopReason;
1162
1161
  const abortSignal = this.abortController?.signal;
1163
1162
  try {
1164
1163
  for await (const evt of this.opts.provider.stream(context, tools, Object.keys(ext).length ? ext : undefined, providerState, abortSignal)) {
@@ -1178,6 +1177,10 @@ export class RuntimeRunner {
1178
1177
  // I1: per-slot attribution forwarded into TurnMetrics. Undefined when the provider
1179
1178
  // doesn't honor cache_control (OpenAI-family auto-cache).
1180
1179
  turnCacheReadBySlot = usageEvt.cacheReadInputTokensBySlot;
1180
+ // Phase 4: stop_reason drives the kernel's max-output-tokens recovery. The closing
1181
+ // usage frame carries it; keep the last non-empty value seen this turn.
1182
+ if (usageEvt.stopReason)
1183
+ turnStopReason = usageEvt.stopReason;
1181
1184
  continue;
1182
1185
  }
1183
1186
  yield evt;
@@ -1190,24 +1193,32 @@ export class RuntimeRunner {
1190
1193
  }
1191
1194
  }
1192
1195
  catch (err) {
1193
- // #2-B-ii: an aborted in-flight request surfaces as an AbortError — treat it as an interrupt
1194
- // (the loop-top `interrupted` check converts it to a clean `timeout`/UserAbort), not a crash.
1195
1196
  if (abortSignal?.aborted) {
1197
+ // #2-B-ii: an aborted in-flight request surfaces as an AbortError — treat it as an
1198
+ // interrupt (the post-stream `aborted` check below converts it to a clean
1199
+ // timeout/UserAbort), not a crash or a provider error.
1196
1200
  this.interrupted = true;
1197
1201
  }
1198
- const errMsg = formatToolError(err).toLowerCase();
1199
- if ((errMsg.includes("413") || errMsg.includes("too long") || errMsg.includes("context length exceeded") || errMsg.includes("context_length_exceeded")) &&
1200
- !hasAttemptedReactiveCompact) {
1201
- hasAttemptedReactiveCompact = true;
1202
- if (forceCompact(runtime, this.pendingObservations)) {
1203
- nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart);
1204
- shouldRetry = true;
1202
+ else {
1203
+ // Reactive recovery is now a kernel decision. Forward the raw provider error and
1204
+ // dispatch whatever the kernel returns: `call_provider` to retry with a freshly
1205
+ // compacted context, or `done` to terminate with an honest `ContextOverflow`. The
1206
+ // classify + compact + retry + give-up policy lives in the kernel (one place), not
1207
+ // duplicated across the four SDK runners. `continue` re-enters the loop: a recovered
1208
+ // turn persists its compaction archive via the loop-top appendObservations, and a
1209
+ // terminal `done` exits through `isTerminal()` into the run_terminal emit below.
1210
+ action = kernelAction(runtime, this.pendingObservations, {
1211
+ kind: "provider_error",
1212
+ message: formatToolError(err),
1213
+ });
1214
+ // Withholding (query.ts parity): surface the raw provider error only when the kernel
1215
+ // could NOT recover (it returned a terminal). On a recovered retry (`call_provider`)
1216
+ // the error stays hidden, so embedders that terminate on `error` events don't see a
1217
+ // phantom failure mid-recovery.
1218
+ if (action.kind === "done") {
1219
+ yield { type: "error", message: formatToolError(err) };
1205
1220
  }
1206
- }
1207
- if (!shouldRetry) {
1208
- yield { type: "error", message: formatToolError(err) };
1209
- action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
1210
- break;
1221
+ continue;
1211
1222
  }
1212
1223
  }
1213
1224
  // #2-B-ii: stream aborted (preempt/interrupt) via the break path (provider yielded no error) —
@@ -1217,14 +1228,6 @@ export class RuntimeRunner {
1217
1228
  action = kernelAction(runtime, this.pendingObservations, { kind: "timeout" });
1218
1229
  break;
1219
1230
  }
1220
- if (shouldRetry) {
1221
- action = {
1222
- kind: "call_provider",
1223
- context: runtime.render(),
1224
- tools,
1225
- };
1226
- continue;
1227
- }
1228
1231
  const assistantMessage = {
1229
1232
  role: "assistant",
1230
1233
  content: finalText,
@@ -1237,6 +1240,7 @@ export class RuntimeRunner {
1237
1240
  ...(turnInputTokens > 0 ? { observed_input_tokens: turnInputTokens } : {}),
1238
1241
  ...(turnOutputTokens > 0 ? { observed_output_tokens: turnOutputTokens } : {}),
1239
1242
  now_ms: Date.now(),
1243
+ ...(turnStopReason ? { stop_reason: turnStopReason } : {}),
1240
1244
  };
1241
1245
  let nextAction = kernelMaybeAction(runtime, this.pendingObservations, providerEvent);
1242
1246
  if (!nextAction && this.pendingObservations.some(o => o.kind === "suspended")) {
package/dist/types.d.ts CHANGED
@@ -93,6 +93,10 @@ export interface UsageEvent extends StreamEvent {
93
93
  tools?: number;
94
94
  messages?: number;
95
95
  };
96
+ /** Provider stop reason for the response — `max_tokens` (Anthropic) / `length` (OpenAI) flag an
97
+ * output-cap truncation, which drives the kernel's max-output-tokens recovery. Absent when the
98
+ * provider doesn't report one. */
99
+ stopReason?: string;
96
100
  }
97
101
  export type ToolChunk = string | {
98
102
  type: "text";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/sdk",
3
- "version": "0.2.33",
3
+ "version": "0.2.35",
4
4
  "description": "DeepStrike Node.js SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@anthropic-ai/sdk": "^0.99.0",
75
- "@deepstrike/core": "0.2.33",
75
+ "@deepstrike/core": "0.2.35",
76
76
  "@google/generative-ai": "^0.24.1",
77
77
  "openai": "^5.23.2"
78
78
  },