@deepstrike/wasm 0.2.22 → 0.2.26

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.
@@ -65,6 +65,19 @@ export function milestoneCheckResultToKernel(result) {
65
65
  ...(result.reason ? { reason: result.reason } : {}),
66
66
  };
67
67
  }
68
+ /** Tool-call `arguments` reach us as a raw model-authored string (e.g. the OpenAIChat-family
69
+ * non-streaming path passes it through verbatim via `normalizeToolCalls`). A malformed JSON
70
+ * string must degrade to empty args here, never throw — otherwise one bad tool-call on a
71
+ * sub-agent's final turn bricks the parent's result serialization. Mirrors the `catch→{}`
72
+ * guard every provider/runtime parse site already uses. */
73
+ function safeParseToolArgs(raw) {
74
+ try {
75
+ return JSON.parse(raw || "{}");
76
+ }
77
+ catch {
78
+ return {};
79
+ }
80
+ }
68
81
  export function subAgentResultToKernel(result) {
69
82
  const finalMessage = result.result.finalMessage;
70
83
  return {
@@ -78,7 +91,7 @@ export function subAgentResultToKernel(result) {
78
91
  tool_calls: (finalMessage.toolCalls ?? []).map(tc => ({
79
92
  id: tc.id,
80
93
  name: tc.name,
81
- arguments: JSON.parse(tc.arguments || "{}"),
94
+ arguments: safeParseToolArgs(tc.arguments),
82
95
  })),
83
96
  ...(finalMessage.tokenCount !== undefined ? { token_count: finalMessage.tokenCount } : {}),
84
97
  }
@@ -0,0 +1,42 @@
1
+ import type { ToolExecContext, RegisteredTool } from "./index.js";
2
+ /** Stable JSON shape returned to the model by `safeTool`. Same shape as the Node SDK so a tool
3
+ * authored once works across both runtimes. The model can branch on `code` instead of pattern-
4
+ * matching free-form strings; `hint` is the self-correcting affordance (e.g. "call document_outline
5
+ * first"). */
6
+ export interface ToolEnvelopeOk<T = unknown> {
7
+ success: true;
8
+ data?: T;
9
+ }
10
+ export interface ToolEnvelopeFail {
11
+ success: false;
12
+ code: string;
13
+ error: string;
14
+ hint?: string;
15
+ }
16
+ export type ToolEnvelope<T = unknown> = ToolEnvelopeOk<T> | ToolEnvelopeFail;
17
+ /** Error class understood by `safeTool` and the runtime's error-aware serialization. Throwing
18
+ * it produces `{success:false, code, error, hint?}`; a plain `Error` with string `.code`/`.hint`
19
+ * is honored too, so existing code keeps working without migration. */
20
+ export declare class ToolError extends Error {
21
+ code: string;
22
+ hint?: string;
23
+ constructor(message: string, opts?: {
24
+ code?: string;
25
+ hint?: string;
26
+ cause?: unknown;
27
+ });
28
+ }
29
+ export declare function ok<T>(data?: T): ToolEnvelopeOk<T>;
30
+ export declare function fail(code: string, error: string, hint?: string): ToolEnvelopeFail;
31
+ /** Error-aware serialization for tool-execution error paths. Replaces `String(err)` at the
32
+ * sites that hand the model (or the host's stream) a failure message.
33
+ *
34
+ * - `Error` with no extra fields → `err.message` (clean, no `"Error: "` prefix).
35
+ * - `Error` carrying `code` / `hint` / `cause` → JSON `{message, name?, code?, hint?, cause?}`.
36
+ * - Plain objects → `JSON.stringify(...)` (replaces the old `"[object Object]"`).
37
+ * - Primitives / null / undefined → `String(...)` (unchanged). */
38
+ export declare function formatToolError(err: unknown): string;
39
+ /** `tool()` equivalent that wraps the body in a try/catch and returns a stable
40
+ * `{success, code, error, hint?}` JSON envelope to the model. Opt-in; the classic `tool()`
41
+ * factory is unchanged. See the Node SDK for the full rationale. */
42
+ export declare function safeTool<T = unknown>(name: string, description: string, parameters: Record<string, unknown>, fn: (args: Record<string, unknown>, ctx?: ToolExecContext) => Promise<ToolEnvelope<T> | T> | ToolEnvelope<T> | T): RegisteredTool;
@@ -0,0 +1,97 @@
1
+ import { tool } from "./index.js";
2
+ /** Error class understood by `safeTool` and the runtime's error-aware serialization. Throwing
3
+ * it produces `{success:false, code, error, hint?}`; a plain `Error` with string `.code`/`.hint`
4
+ * is honored too, so existing code keeps working without migration. */
5
+ export class ToolError extends Error {
6
+ code;
7
+ hint;
8
+ constructor(message, opts = {}) {
9
+ super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
10
+ this.name = "ToolError";
11
+ this.code = opts.code ?? "internal";
12
+ if (opts.hint !== undefined)
13
+ this.hint = opts.hint;
14
+ }
15
+ }
16
+ export function ok(data) {
17
+ return data === undefined ? { success: true } : { success: true, data };
18
+ }
19
+ export function fail(code, error, hint) {
20
+ return hint === undefined ? { success: false, code, error } : { success: false, code, error, hint };
21
+ }
22
+ function isEnvelope(v) {
23
+ return typeof v === "object" && v !== null && "success" in v &&
24
+ typeof v.success === "boolean";
25
+ }
26
+ /** Error-aware serialization for tool-execution error paths. Replaces `String(err)` at the
27
+ * sites that hand the model (or the host's stream) a failure message.
28
+ *
29
+ * - `Error` with no extra fields → `err.message` (clean, no `"Error: "` prefix).
30
+ * - `Error` carrying `code` / `hint` / `cause` → JSON `{message, name?, code?, hint?, cause?}`.
31
+ * - Plain objects → `JSON.stringify(...)` (replaces the old `"[object Object]"`).
32
+ * - Primitives / null / undefined → `String(...)` (unchanged). */
33
+ export function formatToolError(err) {
34
+ if (err == null)
35
+ return String(err);
36
+ if (typeof err === "string")
37
+ return err;
38
+ if (err instanceof Error) {
39
+ const anyErr = err;
40
+ const code = anyErr.code;
41
+ const hint = anyErr.hint;
42
+ const cause = anyErr.cause;
43
+ if (code === undefined && hint === undefined && cause === undefined) {
44
+ return err.message || err.name || "Error";
45
+ }
46
+ const payload = { message: err.message };
47
+ if (err.name && err.name !== "Error")
48
+ payload.name = err.name;
49
+ if (code !== undefined)
50
+ payload.code = code;
51
+ if (hint !== undefined)
52
+ payload.hint = hint;
53
+ if (cause !== undefined)
54
+ payload.cause = cause instanceof Error ? cause.message : cause;
55
+ try {
56
+ return JSON.stringify(payload);
57
+ }
58
+ catch {
59
+ return err.message || err.name || "Error";
60
+ }
61
+ }
62
+ if (typeof err === "object") {
63
+ try {
64
+ return JSON.stringify(err);
65
+ }
66
+ catch {
67
+ return Object.prototype.toString.call(err);
68
+ }
69
+ }
70
+ return String(err);
71
+ }
72
+ /** `tool()` equivalent that wraps the body in a try/catch and returns a stable
73
+ * `{success, code, error, hint?}` JSON envelope to the model. Opt-in; the classic `tool()`
74
+ * factory is unchanged. See the Node SDK for the full rationale. */
75
+ export function safeTool(name, description, parameters, fn) {
76
+ const wrapped = async (args, ctx) => {
77
+ try {
78
+ const result = await fn(args, ctx);
79
+ if (isEnvelope(result))
80
+ return JSON.stringify(result);
81
+ return JSON.stringify(ok(result));
82
+ }
83
+ catch (err) {
84
+ if (err instanceof ToolError) {
85
+ return JSON.stringify(fail(err.code, err.message || err.name, err.hint));
86
+ }
87
+ if (err instanceof Error) {
88
+ const anyErr = err;
89
+ const code = typeof anyErr.code === "string" ? anyErr.code : "internal";
90
+ const hint = typeof anyErr.hint === "string" ? anyErr.hint : undefined;
91
+ return JSON.stringify(fail(code, err.message || err.name || "Error", hint));
92
+ }
93
+ return JSON.stringify(fail("internal", formatToolError(err)));
94
+ }
95
+ };
96
+ return tool(name, description, parameters, wrapped);
97
+ }
@@ -2,9 +2,16 @@ import type { ToolSchema, ToolResult } from "../types.js";
2
2
  /** M3/G4: the runtime context a tool may read when executing (carries the working directory). A
3
3
  * narrow, dependency-free shape; the execution plane's `RunContext` is structurally assignable to it.
4
4
  * (WASM has no filesystem, so worktree isolation is N/A here — this keeps the tool ABI in parity
5
- * with the Node/Python ports so a tool authored once works across all of them.) */
5
+ * with the Node/Python ports so a tool authored once works across all of them.)
6
+ *
7
+ * `audit` is the best-effort post-commit side-effect channel: wrap an audit-log write or metrics
8
+ * emit in `await ctx.audit(label, () => store.write(...))`. If the side-effect throws, the failure
9
+ * surfaces as a `tool_audit_failed` stream event and the tool still completes successfully —
10
+ * avoiding the foot-gun where a transient audit-store outage flips an already-committed write
11
+ * into `isError: true` and triggers a duplicate retry. */
6
12
  export interface ToolExecContext {
7
13
  cwd?: string;
14
+ audit?: (label: string, fn: () => Promise<void> | void) => Promise<void>;
8
15
  }
9
16
  export interface RegisteredTool {
10
17
  schema: ToolSchema;
@@ -1,3 +1,4 @@
1
+ import { formatToolError } from "./errors.js";
1
2
  export function tool(name, description, parameters, fn) {
2
3
  return {
3
4
  schema: { name, description, parameters: JSON.stringify(parameters) },
@@ -14,7 +15,7 @@ export async function executeTools(calls, registry) {
14
15
  return { callId: c.id, output: await t.execute(args), isError: false };
15
16
  }
16
17
  catch (err) {
17
- return { callId: c.id, output: String(err), isError: true };
18
+ return { callId: c.id, output: formatToolError(err), isError: true };
18
19
  }
19
20
  }));
20
21
  }
package/dist/types.d.ts CHANGED
@@ -69,6 +69,11 @@ export interface UsageEvent extends StreamEvent {
69
69
  outputTokens?: number;
70
70
  cacheReadInputTokens?: number;
71
71
  cacheCreationInputTokens?: number;
72
+ cacheReadInputTokensBySlot?: {
73
+ system?: number;
74
+ tools?: number;
75
+ messages?: number;
76
+ };
72
77
  }
73
78
  export interface ThinkingDelta extends StreamEvent {
74
79
  type: "thinking_delta";
@@ -131,6 +136,16 @@ export interface ToolDeniedEvent extends StreamEvent {
131
136
  toolName: string;
132
137
  reason: string;
133
138
  }
139
+ /** A tool's `ctx.audit(label, fn)` best-effort side-effect threw. The tool itself completed
140
+ * successfully (no isError flip, no retry); this event lets the host log / monitor that an
141
+ * audit-store / metrics-emit / non-essential persistence step failed. */
142
+ export interface ToolAuditFailedEvent extends StreamEvent {
143
+ type: "tool_audit_failed";
144
+ callId: string;
145
+ name: string;
146
+ label: string;
147
+ error: string;
148
+ }
134
149
  export interface ToolArgumentRepairedEvent extends StreamEvent {
135
150
  type: "tool_argument_repaired";
136
151
  callId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.2.22",
3
+ "version": "0.2.26",
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.22"
18
+ "@deepstrike/wasm-kernel": "0.2.26"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/jest": "^30.0.0",