@deepstrike/wasm 0.2.22 → 0.2.25

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.
@@ -37,7 +37,16 @@ export interface GovernancePolicy {
37
37
  windowMs: number;
38
38
  }[];
39
39
  constraints?: GovernanceConstraint[];
40
+ /** I5: when true (default), the runner pre-filters denied tools out of the schema. */
41
+ surfaceDeniedInSystem?: boolean;
40
42
  }
43
+ /** I5: bucket tools into allowed/denied per the policy. Pure. Mirrors Node. */
44
+ export declare function governanceFilterSchema<T extends {
45
+ name: string;
46
+ }>(tools: T[], policy: GovernancePolicy | undefined): {
47
+ allowed: T[];
48
+ denied: string[];
49
+ };
41
50
  export type GovernanceConstraint = {
42
51
  kind: "required";
43
52
  tool: string;
@@ -51,6 +51,31 @@ export class Governance {
51
51
  return this._inner.evaluate(toolName, argsJson);
52
52
  }
53
53
  }
54
+ /** I5: bucket tools into allowed/denied per the policy. Pure. Mirrors Node. */
55
+ export function governanceFilterSchema(tools, policy) {
56
+ if (!policy)
57
+ return { allowed: tools, denied: [] };
58
+ const vetoes = new Set(policy.vetoes ?? []);
59
+ const allowed = [];
60
+ const denied = [];
61
+ const matches = (pat, name) => pat === name || (pat.endsWith("*") && name.startsWith(pat.slice(0, -1)));
62
+ for (const tool of tools) {
63
+ if (vetoes.has(tool.name)) {
64
+ denied.push(tool.name);
65
+ continue;
66
+ }
67
+ let action = policy.defaultAction ?? "allow";
68
+ for (const r of policy.rules ?? []) {
69
+ if (matches(r.pattern, tool.name))
70
+ action = r.action;
71
+ }
72
+ if (action === "deny")
73
+ denied.push(tool.name);
74
+ else
75
+ allowed.push(tool);
76
+ }
77
+ return { allowed, denied };
78
+ }
54
79
  export function governancePolicyToKernelEvent(policy) {
55
80
  return {
56
81
  kind: "load_governance_policy",
@@ -3,6 +3,10 @@ export interface Criterion {
3
3
  text: string;
4
4
  required: boolean;
5
5
  weight?: number;
6
+ /** I3.3 (A4): optional stable id from the host's contract layer; threaded to verdictFn. */
7
+ id?: string;
8
+ /** I3.3 (A4): host hint — host has a deterministic check for this criterion. */
9
+ machineCheckable?: boolean;
6
10
  }
7
11
  export interface CriterionResult {
8
12
  criterion: string;
@@ -62,13 +66,22 @@ export declare class SinglePassHarness {
62
66
  constructor(runner: RuntimeRunner);
63
67
  run(request: HarnessRequest): Promise<HarnessOutcome>;
64
68
  }
69
+ /** I3.2 (A2/A3): host-supplied judgment — see Node `VerdictFn` for the full contract. Mirrors. */
70
+ export type VerdictFn = (ctx: {
71
+ goal: string;
72
+ criteria: Criterion[];
73
+ attempt: number;
74
+ result: string;
75
+ }) => Verdict | undefined | Promise<Verdict | undefined>;
65
76
  export interface HarnessLoopOptions {
66
77
  maxAttempts?: number;
78
+ verdictFn?: VerdictFn;
67
79
  }
68
80
  export declare class HarnessLoop {
69
81
  private runner;
70
82
  private evalProvider;
71
83
  private maxAttempts;
84
+ private verdictFn?;
72
85
  constructor(runner: RuntimeRunner, evalProvider: import("../types.js").LLMProvider, options?: HarnessLoopOptions);
73
86
  runStreaming(request: HarnessRequest): AsyncIterable<HarnessEvent>;
74
87
  }
@@ -23,10 +23,12 @@ export class HarnessLoop {
23
23
  runner;
24
24
  evalProvider;
25
25
  maxAttempts;
26
+ verdictFn;
26
27
  constructor(runner, evalProvider, options = {}) {
27
28
  this.runner = runner;
28
29
  this.evalProvider = evalProvider;
29
30
  this.maxAttempts = options.maxAttempts ?? 3;
31
+ this.verdictFn = options.verdictFn;
30
32
  }
31
33
  async *runStreaming(request) {
32
34
  const kernel = await import("@deepstrike/wasm-kernel");
@@ -61,24 +63,31 @@ export class HarnessLoop {
61
63
  }
62
64
  }
63
65
  yield { type: "supervising" };
64
- // #6 (0.5.0): eval/verdict compute is the kernel's stateless free functions (was EvalPipeline).
65
- const evalMsgs = kernel.buildEvalMessages(request.goal, criteria, lastResult, attempt, true);
66
- let evalText = "";
67
- const evalContext = {
68
- systemText: "",
69
- turns: evalMsgs,
70
- };
71
- for await (const evt of this.evalProvider.stream(evalContext, [], undefined)) {
72
- if (evt.type === "text_delta")
73
- evalText += evt.delta;
66
+ // I3.2 (A2/A3): host-supplied verdictFn short-circuits the LLM eval. Undefined defer.
67
+ let verdict;
68
+ if (this.verdictFn) {
69
+ verdict = await this.verdictFn({ goal: request.goal, criteria, attempt, result: lastResult });
70
+ }
71
+ if (!verdict) {
72
+ // #6 (0.5.0): eval/verdict compute is the kernel's stateless free functions (was EvalPipeline).
73
+ const evalMsgs = kernel.buildEvalMessages(request.goal, criteria, lastResult, attempt, true);
74
+ let evalText = "";
75
+ const evalContext = {
76
+ systemText: "",
77
+ turns: evalMsgs,
78
+ };
79
+ for await (const evt of this.evalProvider.stream(evalContext, [], undefined)) {
80
+ if (evt.type === "text_delta")
81
+ evalText += evt.delta;
82
+ }
83
+ const parsed = kernel.parseVerdict(evalText);
84
+ verdict = {
85
+ passed: parsed.passed,
86
+ overallScore: parsed.overallScore,
87
+ feedback: parsed.feedback,
88
+ details: (parsed.details ?? []),
89
+ };
74
90
  }
75
- const parsed = kernel.parseVerdict(evalText);
76
- const verdict = {
77
- passed: parsed.passed,
78
- overallScore: parsed.overallScore,
79
- feedback: parsed.feedback,
80
- details: (parsed.details ?? []),
81
- };
82
91
  if (verdict.passed) {
83
92
  yield { type: "done", verdict, iterations: lastIterations, totalTokens: lastTotalTokens, status: lastStatus };
84
93
  return;
package/dist/index.d.ts CHANGED
@@ -12,15 +12,17 @@ export type { GovernanceVerdict } from "./governance.js";
12
12
  export { AnthropicProvider } from "./providers/anthropic.js";
13
13
  export { OpenAIProvider, QwenProvider, DeepSeekProvider, MiniMaxProvider, KimiProvider } from "./providers/openai.js";
14
14
  export { tool, executeTools } from "./tools/index.js";
15
- export type { RegisteredTool } from "./tools/index.js";
15
+ export type { RegisteredTool, ToolExecContext } from "./tools/index.js";
16
+ export { safeTool, ok, fail, ToolError, formatToolError } from "./tools/errors.js";
17
+ export type { ToolEnvelope, ToolEnvelopeOk, ToolEnvelopeFail } from "./tools/errors.js";
16
18
  export { WorkingMemory } from "./memory/index.js";
17
19
  export { InMemoryDreamStore } from "./memory/in-memory-store.js";
18
20
  export type { DreamStore, DreamResult, SessionStore, SessionData, SessionMessage, MemoryEntry, CurationResult, CurationStats, } from "./memory/index.js";
19
21
  export type { KnowledgeSource } from "./knowledge/index.js";
20
22
  export { SinglePassHarness, HarnessLoop } from "./harness/index.js";
21
- export type { HarnessRequest, HarnessOutcome, HarnessLoopOptions } from "./harness/index.js";
23
+ export type { HarnessRequest, HarnessOutcome, HarnessLoopOptions, CriterionResult, HarnessEvent, VerdictFn } from "./harness/index.js";
22
24
  export { ScheduledPrompt } from "./signals/index.js";
23
25
  export type { RuntimeSignal, SignalSource } from "./signals/index.js";
24
26
  export { PermissionManager, PermissionMode } from "./safety/index.js";
25
27
  export type { PermissionDecision } from "./safety/index.js";
26
- export type { Message, ToolCall, ToolResult, ToolSchema, RenderedContext, ProviderRunState, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, LLMProvider, CacheBreakpointStrategy, } from "./types.js";
28
+ export type { Message, ToolCall, ToolResult, ToolSchema, RenderedContext, ProviderRunState, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, ToolAuditFailedEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, LLMProvider, CacheBreakpointStrategy, } from "./types.js";
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export { Governance } from "./governance.js";
7
7
  export { AnthropicProvider } from "./providers/anthropic.js";
8
8
  export { OpenAIProvider, QwenProvider, DeepSeekProvider, MiniMaxProvider, KimiProvider } from "./providers/openai.js";
9
9
  export { tool, executeTools } from "./tools/index.js";
10
+ export { safeTool, ok, fail, ToolError, formatToolError } from "./tools/errors.js";
10
11
  export { WorkingMemory } from "./memory/index.js";
11
12
  export { InMemoryDreamStore } from "./memory/in-memory-store.js";
12
13
  export { SinglePassHarness, HarnessLoop } from "./harness/index.js";
@@ -23,6 +23,46 @@ function resolveCacheBreakpointStrategy(extensions) {
23
23
  }
24
24
  return "default";
25
25
  }
26
+ /** I1: which slots of the outgoing request carry a `cache_control` breakpoint — used to pro-rata
27
+ * attribute the response's `cache_read_input_tokens` (a single scalar). Mirrors Node. */
28
+ function countCacheControlSlots(system, builtTools, msgs) {
29
+ const sysBp = Array.isArray(system) && system.some(b => b?.cache_control != null);
30
+ const toolBp = !!builtTools && builtTools.some(t => t?.cache_control != null);
31
+ let msgBp = false;
32
+ for (const m of msgs) {
33
+ if (Array.isArray(m.content)) {
34
+ if (m.content.some(b => b?.cache_control != null)) {
35
+ msgBp = true;
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ return { system: sysBp, tools: toolBp, messages: msgBp };
41
+ }
42
+ /** I1: split `cache_read_input_tokens` evenly across contributing slots. Remainder lands on the
43
+ * first contributing slot to keep the sum exact. Mirrors Node. */
44
+ function estimateCacheReadBySlot(cacheRead, slotBp) {
45
+ if (cacheRead <= 0)
46
+ return undefined;
47
+ const count = (slotBp.system ? 1 : 0) + (slotBp.tools ? 1 : 0) + (slotBp.messages ? 1 : 0);
48
+ if (count === 0)
49
+ return undefined;
50
+ const share = Math.floor(cacheRead / count);
51
+ const remainder = cacheRead - share * count;
52
+ const out = {};
53
+ let firstDone = false;
54
+ const give = () => { if (!firstDone) {
55
+ firstDone = true;
56
+ return share + remainder;
57
+ } return share; };
58
+ if (slotBp.system)
59
+ out.system = give();
60
+ if (slotBp.tools)
61
+ out.tools = give();
62
+ if (slotBp.messages)
63
+ out.messages = give();
64
+ return out;
65
+ }
26
66
  /**
27
67
  * Roll cache breakpoints across the conversation tail so the message-history
28
68
  * prefix is written once and re-read on later turns (without this the cached
@@ -143,13 +183,16 @@ export class AnthropicProvider {
143
183
  msgs.push(...stateMsgs);
144
184
  }
145
185
  assertCacheBudget(system, tools.length);
186
+ const builtTools = tools.length ? buildAnthropicTools(tools, !Array.isArray(system), strategy) : undefined;
187
+ // I1: capture which slots will carry cache_control for pro-rata attribution at usage time.
188
+ const slotBp = countCacheControlSlots(system, builtTools, msgs);
146
189
  const body = {
147
190
  model: this.model,
148
191
  max_tokens: this.maxTokens,
149
192
  messages: msgs,
150
193
  stream: true,
151
194
  ...(system ? { system } : {}),
152
- ...(tools.length ? { tools: buildAnthropicTools(tools, !Array.isArray(system), strategy) } : {}),
195
+ ...(builtTools ? { tools: builtTools } : {}),
153
196
  };
154
197
  if (extensions?.enable_thinking) {
155
198
  body.thinking = { type: "enabled", budget_tokens: 8000 };
@@ -206,6 +249,7 @@ export class AnthropicProvider {
206
249
  // the kernel reads it as the authoritative context size.
207
250
  const inputTokens = uncachedInput + cacheReadTokens + cacheCreationTokens;
208
251
  if (inputTokens > 0 || outputTokens > 0) {
252
+ const bySlot = estimateCacheReadBySlot(cacheReadTokens, slotBp);
209
253
  yield {
210
254
  type: "usage",
211
255
  totalTokens: inputTokens + outputTokens,
@@ -213,6 +257,7 @@ export class AnthropicProvider {
213
257
  outputTokens,
214
258
  cacheReadInputTokens: cacheReadTokens,
215
259
  cacheCreationInputTokens: cacheCreationTokens,
260
+ ...(bySlot ? { cacheReadInputTokensBySlot: bySlot } : {}),
216
261
  };
217
262
  }
218
263
  }
@@ -1,4 +1,5 @@
1
1
  import { LargeResultSpool } from "./large-result-spool.js";
2
+ import { formatToolError } from "../tools/errors.js";
2
3
  function stripFrontmatter(content) {
3
4
  const s = content.trimStart();
4
5
  if (!s.startsWith("---"))
@@ -71,19 +72,38 @@ export class LocalExecutionPlane {
71
72
  yield { type: "tool_result", callId: call.id, name: call.name, content: `unknown tool: ${call.name}`, isError: true, isFatal: false, errorKind: "recoverable" };
72
73
  continue;
73
74
  }
75
+ // Per-call `audit` helper: failures collected here are surfaced as `tool_audit_failed`
76
+ // events rather than flipping the main tool result to `isError: true`.
77
+ const auditFailures = [];
78
+ const callCtx = {
79
+ ...(ctx.cwd !== undefined ? { cwd: ctx.cwd } : {}),
80
+ audit: async (label, fn) => {
81
+ try {
82
+ await fn();
83
+ }
84
+ catch (err) {
85
+ auditFailures.push({ label, error: formatToolError(err) });
86
+ }
87
+ },
88
+ };
74
89
  try {
75
90
  const args = JSON.parse(call.arguments || "{}");
76
- // M3/G4: pass the run context for tool-ABI parity with Node/Python (`RunContext` is
77
- // structurally assignable to the tool's `ToolExecContext`).
78
- const output = await registered.execute(args, ctx);
91
+ // M3/G4: pass the run context (incl. `cwd`, `audit`) for tool-ABI parity with Node/Python.
92
+ const output = await registered.execute(args, callCtx);
93
+ for (const f of auditFailures) {
94
+ yield { type: "tool_audit_failed", callId: call.id, name: call.name, label: f.label, error: f.error };
95
+ }
79
96
  yield { type: "tool_result", callId: call.id, name: call.name, content: String(output), isError: false };
80
97
  }
81
98
  catch (err) {
99
+ for (const f of auditFailures) {
100
+ yield { type: "tool_audit_failed", callId: call.id, name: call.name, label: f.label, error: f.error };
101
+ }
82
102
  yield {
83
103
  type: "tool_result",
84
104
  callId: call.id,
85
105
  name: call.name,
86
- content: String(err),
106
+ content: formatToolError(err),
87
107
  isError: true,
88
108
  isFatal: Boolean(err?.isFatal),
89
109
  errorKind: err?.errorKind,
@@ -134,7 +154,7 @@ export async function resolvePermissionRequest(request, ctx) {
134
154
  return {
135
155
  approved: false,
136
156
  responder: "permission_handler",
137
- reason: `permission handler failed: ${String(err)}`,
157
+ reason: `permission handler failed: ${formatToolError(err)}`,
138
158
  };
139
159
  }
140
160
  }
@@ -51,6 +51,12 @@ export interface TurnMetrics {
51
51
  activeSkill?: string;
52
52
  inputTokens: number;
53
53
  cacheReadTokens: number;
54
+ /** I1: pro-rata per-slot attribution of `cacheReadTokens` (Anthropic only). Mirrors Node. */
55
+ cacheReadTokensBySlot?: {
56
+ system?: number;
57
+ tools?: number;
58
+ messages?: number;
59
+ };
54
60
  cacheCreationTokens: number;
55
61
  }
56
62
  export interface RuntimeOptions {
@@ -67,6 +73,12 @@ export interface RuntimeOptions {
67
73
  maxTurns?: number;
68
74
  timeoutMs?: number;
69
75
  agentId?: string;
76
+ /** I4: optional run-start memory pre-fetch hook (mirrors Node SDK). Called once per run before
77
+ * the first LLM turn; each returned query string becomes a dreamStore search; hits page into
78
+ * the knowledge partition before turn 1. Requires dreamStore + agentId. */
79
+ preQueryMemory?: (ctx: {
80
+ goal: string;
81
+ }) => Promise<string[] | undefined> | string[] | undefined;
70
82
  systemPrompt?: string;
71
83
  initialMemory?: string[];
72
84
  /** Skill name → markdown body (WASM has no filesystem). */