@deepstrike/wasm 0.2.51 → 0.2.60

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.
Files changed (54) hide show
  1. package/README.md +15 -14
  2. package/dist/agent-ir.d.ts +81 -0
  3. package/dist/agent-ir.js +115 -0
  4. package/dist/agent.d.ts +140 -0
  5. package/dist/agent.js +34 -0
  6. package/dist/governance.d.ts +0 -25
  7. package/dist/governance.js +1 -53
  8. package/dist/index.d.ts +23 -8
  9. package/dist/index.js +11 -4
  10. package/dist/memory/durable.d.ts +13 -0
  11. package/dist/memory/durable.js +42 -0
  12. package/dist/memory/in-memory-store.d.ts +8 -6
  13. package/dist/memory/in-memory-store.js +8 -2
  14. package/dist/memory/index.d.ts +18 -2
  15. package/dist/providers/anthropic.d.ts +9 -0
  16. package/dist/providers/anthropic.js +12 -33
  17. package/dist/providers/content-policy.d.ts +16 -0
  18. package/dist/providers/content-policy.js +39 -0
  19. package/dist/providers/openai.d.ts +36 -17
  20. package/dist/providers/openai.js +86 -46
  21. package/dist/providers/provider-error.d.ts +22 -0
  22. package/dist/providers/provider-error.js +90 -0
  23. package/dist/providers/request-plan.d.ts +87 -0
  24. package/dist/providers/request-plan.js +153 -0
  25. package/dist/providers/stop-reason.d.ts +5 -0
  26. package/dist/providers/stop-reason.js +24 -0
  27. package/dist/runtime/canonical-kernel-step.d.ts +11 -5
  28. package/dist/runtime/canonical-kernel-step.js +99 -78
  29. package/dist/runtime/context-policy.d.ts +10 -12
  30. package/dist/runtime/context-policy.js +6 -8
  31. package/dist/runtime/durable-content.d.ts +44 -0
  32. package/dist/runtime/durable-content.js +119 -0
  33. package/dist/runtime/execution-plane.d.ts +2 -2
  34. package/dist/runtime/execution-plane.js +2 -2
  35. package/dist/runtime/kernel-event-log.js +0 -1
  36. package/dist/runtime/kernel-step.d.ts +1 -2
  37. package/dist/runtime/kernel-step.js +1 -1
  38. package/dist/runtime/provider-replay.d.ts +1 -1
  39. package/dist/runtime/provider-replay.js +12 -20
  40. package/dist/runtime/runner.d.ts +22 -18
  41. package/dist/runtime/runner.js +115 -55
  42. package/dist/runtime/session-log.d.ts +8 -1
  43. package/dist/runtime/session-repair.d.ts +1 -1
  44. package/dist/runtime/session-repair.js +1 -1
  45. package/dist/runtime/sha256.d.ts +2 -0
  46. package/dist/runtime/sha256.js +38 -0
  47. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  48. package/dist/runtime/sub-agent-orchestrator.js +3 -3
  49. package/dist/runtime/types/agent.d.ts +17 -7
  50. package/dist/runtime/types/agent.js +30 -4
  51. package/dist/runtime/workflow-control-flow.d.ts +0 -4
  52. package/dist/runtime/workflow-control-flow.js +0 -16
  53. package/dist/types.d.ts +58 -5
  54. package/package.json +2 -2
@@ -0,0 +1,87 @@
1
+ import type { RenderedContext, ToolSchema } from "../types.js";
2
+ export interface ProviderRequestEndpoint {
3
+ id: string;
4
+ protocol: string;
5
+ baseURL: string;
6
+ }
7
+ export interface ProviderRequestPlan {
8
+ providerId: string;
9
+ modelId: string;
10
+ endpoint: ProviderRequestEndpoint;
11
+ context: RenderedContext;
12
+ tools: ToolSchema[];
13
+ options: Record<string, unknown>;
14
+ fingerprint: string;
15
+ }
16
+ /** Stable, provider-independent fallback used when no native token meter is available. */
17
+ export declare function estimateProviderPromptTokens(context: RenderedContext, tools: ToolSchema[]): number;
18
+ export interface RecordedPromptMeasurement {
19
+ requestFingerprint: string;
20
+ inputTokens: number;
21
+ source: {
22
+ kind: "native";
23
+ provider: string;
24
+ } | {
25
+ kind: "local_exact";
26
+ tokenizer: string;
27
+ } | {
28
+ kind: "heuristic";
29
+ };
30
+ confidence: "exact" | "high_confidence" | "low_confidence";
31
+ }
32
+ export interface ProviderUsage {
33
+ inputTokens: number;
34
+ outputTokens: number;
35
+ cacheReadInputTokens?: number;
36
+ cacheCreationInputTokens?: number;
37
+ reasoningTokens?: number;
38
+ }
39
+ export interface NormalizedProviderUsage extends ProviderUsage {
40
+ uncachedInputTokens: number;
41
+ }
42
+ export interface PricingSnapshot {
43
+ version: string;
44
+ currency: string;
45
+ region: string;
46
+ effectiveFrom: string;
47
+ expiresAt?: string;
48
+ ratesPerMillion: {
49
+ input: number;
50
+ output: number;
51
+ cacheRead?: number;
52
+ cacheCreation?: number;
53
+ reasoning?: number;
54
+ };
55
+ }
56
+ export type CostObservation = {
57
+ source: "snapshot";
58
+ currency: string;
59
+ amount: number;
60
+ pricingVersion: string;
61
+ } | {
62
+ source: "unpriced";
63
+ reason: "pricing_snapshot_not_effective" | "pricing_snapshot_expired" | "invalid_pricing_snapshot";
64
+ };
65
+ export declare function createProviderRequestPlan(input: Omit<ProviderRequestPlan, "fingerprint" | "options"> & {
66
+ options?: Record<string, unknown>;
67
+ }): ProviderRequestPlan;
68
+ export declare function createProviderRequestPlanForProvider(provider: {
69
+ descriptor?(): {
70
+ provider: string;
71
+ protocol: string;
72
+ model: string;
73
+ };
74
+ requestPlanIdentity?(): {
75
+ providerId?: string;
76
+ modelId?: string;
77
+ endpoint?: {
78
+ id?: string;
79
+ protocol?: string;
80
+ baseURL?: string;
81
+ };
82
+ };
83
+ }, context: RenderedContext, tools: ToolSchema[], options?: Record<string, unknown>): ProviderRequestPlan;
84
+ export declare function recordPromptMeasurement(plan: Pick<ProviderRequestPlan, "fingerprint">, measurement: Omit<RecordedPromptMeasurement, "requestFingerprint">): RecordedPromptMeasurement;
85
+ export declare function measurementForPlan(plan: Pick<ProviderRequestPlan, "fingerprint">, record: RecordedPromptMeasurement | undefined): RecordedPromptMeasurement | undefined;
86
+ export declare function normalizeProviderUsage(usage: ProviderUsage): NormalizedProviderUsage;
87
+ export declare function priceProviderUsage(usage: NormalizedProviderUsage, snapshot: PricingSnapshot, observedAt?: string | Date): CostObservation;
@@ -0,0 +1,153 @@
1
+ import { sha256Hex } from "../runtime/sha256.js";
2
+ /** Stable, provider-independent fallback used when no native token meter is available. */
3
+ export function estimateProviderPromptTokens(context, tools) {
4
+ const bytes = new TextEncoder().encode(stableJson({ context, tools })).byteLength;
5
+ return Math.max(1, Math.ceil(bytes / 4));
6
+ }
7
+ const EXCLUDED = new Set(["apiKey", "api_key", "bearerToken", "bearer_token", "authorization", "credential", "credentials", "retry", "maxRetries", "baseDelay", "timeout", "signal"]);
8
+ export function createProviderRequestPlan(input) {
9
+ const value = {
10
+ providerId: input.providerId, modelId: input.modelId, endpoint: sanitizeEndpoint(input.endpoint),
11
+ context: clone(input.context), tools: clone(input.tools), options: materialOptions(input.options ?? {}),
12
+ };
13
+ return { ...value, fingerprint: sha256Hex(stableJson(value)) };
14
+ }
15
+ export function createProviderRequestPlanForProvider(provider, context, tools, options) {
16
+ const descriptor = provider.descriptor?.() ?? { provider: "unknown", protocol: "unknown", model: "unknown" };
17
+ const identity = provider.requestPlanIdentity?.();
18
+ return createProviderRequestPlan({
19
+ providerId: identity?.providerId ?? descriptor.provider,
20
+ modelId: identity?.modelId ?? descriptor.model,
21
+ endpoint: {
22
+ id: identity?.endpoint?.id ?? `${descriptor.provider}.${descriptor.protocol}`,
23
+ protocol: identity?.endpoint?.protocol ?? descriptor.protocol,
24
+ baseURL: identity?.endpoint?.baseURL ?? "",
25
+ },
26
+ context, tools, options,
27
+ });
28
+ }
29
+ export function recordPromptMeasurement(plan, measurement) {
30
+ if (!Number.isSafeInteger(measurement.inputTokens) || measurement.inputTokens < 0)
31
+ throw new RangeError("inputTokens must be a non-negative safe integer");
32
+ return { requestFingerprint: plan.fingerprint, inputTokens: measurement.inputTokens, source: clone(measurement.source), confidence: measurement.confidence };
33
+ }
34
+ export function measurementForPlan(plan, record) {
35
+ if (!record || record.requestFingerprint !== plan.fingerprint)
36
+ return undefined;
37
+ if (!Number.isSafeInteger(record.inputTokens) || record.inputTokens < 0)
38
+ return undefined;
39
+ if (record.confidence !== "exact" && record.confidence !== "high_confidence" && record.confidence !== "low_confidence")
40
+ return undefined;
41
+ if (!record.source || typeof record.source !== "object")
42
+ return undefined;
43
+ if (record.source.kind === "native" && typeof record.source.provider === "string" && record.source.provider.length > 0)
44
+ return clone(record);
45
+ if (record.source.kind === "local_exact" && typeof record.source.tokenizer === "string" && record.source.tokenizer.length > 0)
46
+ return clone(record);
47
+ if (record.source.kind === "heuristic")
48
+ return clone(record);
49
+ return undefined;
50
+ }
51
+ export function normalizeProviderUsage(usage) {
52
+ const inputTokens = nonNegativeInteger(usage.inputTokens, "inputTokens");
53
+ const outputTokens = nonNegativeInteger(usage.outputTokens, "outputTokens");
54
+ const cacheReadInputTokens = optionalNonNegativeInteger(usage.cacheReadInputTokens, "cacheReadInputTokens");
55
+ const cacheCreationInputTokens = optionalNonNegativeInteger(usage.cacheCreationInputTokens, "cacheCreationInputTokens");
56
+ const reasoningTokens = optionalNonNegativeInteger(usage.reasoningTokens, "reasoningTokens");
57
+ const cached = (cacheReadInputTokens ?? 0) + (cacheCreationInputTokens ?? 0);
58
+ if (cached > inputTokens)
59
+ throw new RangeError("cache token subsets cannot exceed inputTokens");
60
+ if (reasoningTokens !== undefined && reasoningTokens > outputTokens)
61
+ throw new RangeError("reasoningTokens must be a subset of outputTokens");
62
+ return {
63
+ inputTokens,
64
+ uncachedInputTokens: inputTokens - cached,
65
+ outputTokens,
66
+ ...(cacheReadInputTokens !== undefined ? { cacheReadInputTokens } : {}),
67
+ ...(cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens } : {}),
68
+ ...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
69
+ };
70
+ }
71
+ export function priceProviderUsage(usage, snapshot, observedAt = new Date()) {
72
+ const at = typeof observedAt === "string" ? new Date(observedAt) : observedAt;
73
+ const from = new Date(snapshot.effectiveFrom);
74
+ const expires = snapshot.expiresAt ? new Date(snapshot.expiresAt) : undefined;
75
+ const rates = snapshot.ratesPerMillion;
76
+ const requiredRates = [rates.input, rates.output];
77
+ if (!snapshot.version || !snapshot.currency || Number.isNaN(at.valueOf()) || Number.isNaN(from.valueOf())
78
+ || requiredRates.some(rate => typeof rate !== "number" || !Number.isFinite(rate) || rate < 0)
79
+ || Object.values(rates).some(rate => typeof rate !== "number" || !Number.isFinite(rate) || rate < 0)) {
80
+ return { source: "unpriced", reason: "invalid_pricing_snapshot" };
81
+ }
82
+ if (at < from)
83
+ return { source: "unpriced", reason: "pricing_snapshot_not_effective" };
84
+ if (expires && (Number.isNaN(expires.valueOf()) || at >= expires))
85
+ return { source: "unpriced", reason: "pricing_snapshot_expired" };
86
+ const amount = (usage.uncachedInputTokens * rates.input
87
+ + usage.outputTokens * rates.output
88
+ + (usage.cacheReadInputTokens ?? 0) * (rates.cacheRead ?? rates.input)
89
+ + (usage.cacheCreationInputTokens ?? 0) * (rates.cacheCreation ?? rates.input)
90
+ + (usage.reasoningTokens ?? 0) * (rates.reasoning ?? 0)) / 1_000_000;
91
+ return { source: "snapshot", currency: snapshot.currency, amount, pricingVersion: snapshot.version };
92
+ }
93
+ function materialOptions(options) {
94
+ return sanitizeMaterialValue(options);
95
+ }
96
+ function sanitizeEndpoint(endpoint) {
97
+ try {
98
+ const url = new URL(endpoint.baseURL);
99
+ url.username = "";
100
+ url.password = "";
101
+ url.search = "";
102
+ url.hash = "";
103
+ return { ...clone(endpoint), baseURL: url.toString().replace(/\/$/, "") };
104
+ }
105
+ catch {
106
+ return { ...clone(endpoint), baseURL: "" };
107
+ }
108
+ }
109
+ function sanitizeMaterialValue(value) {
110
+ if (value === undefined || typeof value === "function")
111
+ return undefined;
112
+ if (Array.isArray(value))
113
+ return value.map(sanitizeMaterialValue).filter(item => item !== undefined);
114
+ if (value !== null && typeof value === "object") {
115
+ return Object.fromEntries(Object.keys(value).sort().flatMap(key => {
116
+ if (EXCLUDED.has(key) || isTransportOnlyKey(key))
117
+ return [];
118
+ const sanitized = sanitizeMaterialValue(value[key]);
119
+ return sanitized === undefined ? [] : [[key, sanitized]];
120
+ }));
121
+ }
122
+ return value;
123
+ }
124
+ function isTransportOnlyKey(key) {
125
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
126
+ return normalized.includes("authorization") || normalized.includes("credential")
127
+ || normalized.includes("accesstoken") || normalized.includes("refreshtoken")
128
+ || normalized.includes("apikey") || normalized === "bearer" || normalized === "token"
129
+ || normalized === "secret" || normalized === "xapikey";
130
+ }
131
+ function stableJson(value) {
132
+ if (value === null || typeof value !== "object")
133
+ return JSON.stringify(value);
134
+ if (Array.isArray(value))
135
+ return `[${value.map(stableJson).join(",")}]`;
136
+ const object = value;
137
+ return `{${Object.keys(object).sort().map(key => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
138
+ }
139
+ function clone(value) {
140
+ if (value === undefined || value === null || typeof value !== "object")
141
+ return value;
142
+ if (Array.isArray(value))
143
+ return value.map(clone);
144
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, clone(item)]));
145
+ }
146
+ function nonNegativeInteger(value, name) {
147
+ if (!Number.isSafeInteger(value) || value < 0)
148
+ throw new RangeError(`${name} must be a non-negative safe integer`);
149
+ return value;
150
+ }
151
+ function optionalNonNegativeInteger(value, name) {
152
+ return value === undefined ? undefined : nonNegativeInteger(value, name);
153
+ }
@@ -0,0 +1,5 @@
1
+ export type CanonicalStopReason = "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | "content_filter" | "other";
2
+ /** Validates the closed, provider-normalized stop-reason contract. */
3
+ export declare function decodeCanonicalStopReason(value: unknown): CanonicalStopReason;
4
+ /** Normalize an open-ended provider value into the closed runtime contract. */
5
+ export declare function normalizeProviderStopReason(value: unknown): CanonicalStopReason;
@@ -0,0 +1,24 @@
1
+ const KNOWN = new Set([
2
+ "end_turn",
3
+ "tool_use",
4
+ "max_tokens",
5
+ "stop_sequence",
6
+ "content_filter",
7
+ "other",
8
+ ]);
9
+ /** Validates the closed, provider-normalized stop-reason contract. */
10
+ export function decodeCanonicalStopReason(value) {
11
+ if (typeof value !== "string" || !KNOWN.has(value)) {
12
+ throw new RangeError(`unknown canonical stop reason: ${String(value)}`);
13
+ }
14
+ return value;
15
+ }
16
+ /** Normalize an open-ended provider value into the closed runtime contract. */
17
+ export function normalizeProviderStopReason(value) {
18
+ if (typeof value !== "string" || value.length === 0) {
19
+ throw new RangeError(`invalid provider stop reason: ${String(value)}`);
20
+ }
21
+ return KNOWN.has(value)
22
+ ? value
23
+ : "other";
24
+ }
@@ -30,6 +30,10 @@ export interface CanonicalTransition {
30
30
  recordDigest: string;
31
31
  plannedStep: CanonicalPlannedStep;
32
32
  checkpointAdvice?: Record<string, unknown>;
33
+ /** Set when the advised §12.3 checkpoint failed AFTER this step was durably committed.
34
+ * The step itself is published and authoritative; the checkpoint is deferred
35
+ * housekeeping retried at the next advice or forced by the checkpoint_required gate. */
36
+ checkpointFailure?: string;
33
37
  replayed: boolean;
34
38
  }
35
39
  export declare function canonicalUnsupportedEffectResolution(effectId: string, effectKind: string): CanonicalKernelInput;
@@ -44,8 +48,13 @@ export declare class CanonicalKernelRejectedError extends Error {
44
48
  * is therefore a rebuild boundary, never an abort boundary.
45
49
  */
46
50
  export declare class CanonicalKernelRebuildRequiredError extends Error {
51
+ /** True when the runtime was already rebuilt from the journal with the durable record
52
+ * applied — the caller can resync and continue. False when the rebuild itself failed
53
+ * and the operation cannot proceed without journal repair. */
54
+ readonly rebuilt: boolean;
47
55
  constructor(message: string, options?: {
48
56
  cause?: unknown;
57
+ rebuilt?: boolean;
49
58
  });
50
59
  }
51
60
  /**
@@ -85,13 +94,9 @@ export interface CanonicalRunnerRuntimeOptions {
85
94
  }>;
86
95
  }
87
96
  export declare function sha256(value: string): string;
88
- /** Small synchronous SHA-256 for Worker/browser hosts (no node:crypto). */
89
- declare function syncSha256(value: string): string;
90
- export { syncSha256 as sha256Hex };
91
97
  /**
92
98
  * Canonical operation runtime used by the WASM host.
93
- * Every durable transition below is one of the canonical ABI's five input classes; no legacy
94
- * envelope or synthesized host transaction reaches core or storage.
99
+ * Every durable transition below is one of the canonical contract's five input classes.
95
100
  */
96
101
  export declare class CanonicalRunnerRuntime {
97
102
  private readonly options;
@@ -144,3 +149,4 @@ export declare function canonicalKernelAction(runtime: CanonicalRunnerRuntime, p
144
149
  export declare function canonicalStartAgent(runtime: CanonicalRunnerRuntime, pending: KernelObservationLike[], task: Record<string, unknown>, runSpec?: Record<string, unknown>): Promise<KernelRunnerAction>;
145
150
  export declare function canonicalStartWorkflow(runtime: CanonicalRunnerRuntime, pending: KernelObservationLike[], spec: Record<string, unknown>): Promise<KernelRunnerAction | null>;
146
151
  type KernelObservationLike = KernelObservation;
152
+ export {};
@@ -1,4 +1,5 @@
1
- import { kernelAbiVersion } from "@deepstrike/wasm-kernel";
1
+ import { normalizeProviderStopReason } from "../providers/stop-reason.js";
2
+ import { sha256Hex } from "./sha256.js";
2
3
  import { JournalCasConflictError, MAX_CHAIN_POSITION as JOURNAL_MAX_CHAIN_POSITION, } from "./kernel-journal.js";
3
4
  import { encodeCanonicalContentParts, kernelMessageToSdk, renderedContextToSdk, } from "./kernel-step.js";
4
5
  export const MAX_CHAIN_POSITION = JOURNAL_MAX_CHAIN_POSITION;
@@ -281,9 +282,14 @@ export class CanonicalKernelRejectedError extends Error {
281
282
  * is therefore a rebuild boundary, never an abort boundary.
282
283
  */
283
284
  export class CanonicalKernelRebuildRequiredError extends Error {
285
+ /** True when the runtime was already rebuilt from the journal with the durable record
286
+ * applied — the caller can resync and continue. False when the rebuild itself failed
287
+ * and the operation cannot proceed without journal repair. */
288
+ rebuilt;
284
289
  constructor(message, options) {
285
- super(message, options);
290
+ super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);
286
291
  this.name = "CanonicalKernelRebuildRequiredError";
292
+ this.rebuilt = options?.rebuilt ?? false;
287
293
  }
288
294
  }
289
295
  function chainPosition(value, label) {
@@ -338,7 +344,6 @@ export class CanonicalKernelHost {
338
344
  }
339
345
  async transition(input, options = {}) {
340
346
  const inputJson = JSON.stringify({
341
- abi_version: kernelAbiVersion(),
342
347
  operation_id: this.operationId,
343
348
  input_id: options.inputId ?? `wasm-input-${crypto.randomUUID()}`,
344
349
  observed_at_ms: options.observedAtMs ?? String(Date.now()),
@@ -434,6 +439,7 @@ export class CanonicalKernelHost {
434
439
  }
435
440
  const stepSeq = chainPosition(preparation.stepSeq, "prepared stepSeq");
436
441
  let appended = false;
442
+ let transition;
437
443
  try {
438
444
  const receipt = await this.journal.compareAndAppend(this.operationId, preparation.expectedHead, {
439
445
  step_seq: stepSeq,
@@ -447,7 +453,7 @@ export class CanonicalKernelHost {
447
453
  throw new Error("canonical commit receipt disagrees with the durably appended record");
448
454
  }
449
455
  const checkpointAdvice = parseAdvice(committed.checkpointAdviceJson);
450
- const transition = {
456
+ transition = {
451
457
  inputJson,
452
458
  stepSeq,
453
459
  recordDigest: committed.recordDigest,
@@ -455,9 +461,6 @@ export class CanonicalKernelHost {
455
461
  ...(checkpointAdvice ? { checkpointAdvice } : {}),
456
462
  replayed: false,
457
463
  };
458
- if (checkpointAdvice)
459
- await this.checkpoint();
460
- return transition;
461
464
  }
462
465
  catch (error) {
463
466
  if (appended) {
@@ -467,7 +470,7 @@ export class CanonicalKernelHost {
467
470
  catch (restoreError) {
468
471
  throw new CanonicalKernelRebuildRequiredError("canonical record is durable, commit failed, and journal rebuild also failed", { cause: new AggregateError([error, restoreError]) });
469
472
  }
470
- throw new CanonicalKernelRebuildRequiredError("canonical record is durable but commit could not be published; runtime rebuilt from journal", { cause: error });
473
+ throw new CanonicalKernelRebuildRequiredError("canonical record is durable but commit could not be published; runtime rebuilt from journal", { cause: error, rebuilt: true });
471
474
  }
472
475
  try {
473
476
  this.kernel.abort(preparation.prepareToken);
@@ -481,6 +484,19 @@ export class CanonicalKernelHost {
481
484
  }
482
485
  throw error;
483
486
  }
487
+ // Past this point the step is durable AND committed. A failing advised checkpoint
488
+ // must not be misdiagnosed as a lost commit: it is deferred housekeeping — the next
489
+ // advice retries it, and the checkpoint_required prepare gate is the hard backstop.
490
+ if (transition.checkpointAdvice) {
491
+ try {
492
+ await this.checkpoint();
493
+ }
494
+ catch (checkpointError) {
495
+ transition.checkpointFailure =
496
+ checkpointError instanceof Error ? checkpointError.message : String(checkpointError);
497
+ }
498
+ }
499
+ return transition;
484
500
  }
485
501
  }
486
502
  function canonicalProviderMessage(raw) {
@@ -575,7 +591,7 @@ function logicalRunSpec(raw, goal) {
575
591
  ...(raw.metadata && typeof raw.metadata === "object" ? { metadata: raw.metadata } : {}),
576
592
  };
577
593
  }
578
- function canonicalWorkflowSpec(raw) {
594
+ function canonicalWorkflowSpec(raw, allowHostSchedulingFactors = false) {
579
595
  const nodes = Array.isArray(raw.nodes) ? raw.nodes.map(asObject) : [];
580
596
  const nodeIds = nodes.map((_node, index) => `wf-node${index}`);
581
597
  return {
@@ -598,6 +614,9 @@ function canonicalWorkflowSpec(raw) {
598
614
  unsupported.push("max_turns");
599
615
  if (node.max_wall_ms !== undefined || node.maxWallMs !== undefined)
600
616
  unsupported.push("max_wall_ms");
617
+ const schedulingFactors = node.scheduling_factors ?? node.schedulingFactors;
618
+ if (schedulingFactors !== undefined && !allowHostSchedulingFactors)
619
+ unsupported.push("scheduling_factors");
601
620
  const inheritance = node.context_inheritance ?? node.contextInheritance;
602
621
  if (unsupported.length > 0) {
603
622
  throw new CanonicalKernelRejectedError(JSON.stringify({
@@ -618,16 +637,20 @@ function canonicalWorkflowSpec(raw) {
618
637
  : [];
619
638
  const modelHint = node.model_hint ?? node.modelHint;
620
639
  const outputSchema = node.output_schema ?? node.outputSchema;
640
+ const canonicalSchedulingFactors = schedulingFactors === undefined
641
+ ? undefined
642
+ : canonicalSchedulingFactorsForHost(schedulingFactors);
621
643
  const runSpec = logicalRunSpec({
622
644
  goal,
623
645
  ...(node.role ? { role: node.role } : {}),
624
646
  ...(node.isolation ? { isolation: node.isolation } : {}),
625
647
  ...(inheritance ? { context_inheritance: inheritance } : {}),
626
- ...((modelHint !== undefined || outputSchema !== undefined)
648
+ ...((modelHint !== undefined || outputSchema !== undefined || canonicalSchedulingFactors !== undefined)
627
649
  ? {
628
650
  metadata: {
629
651
  ...(modelHint !== undefined ? { model_hint: modelHint } : {}),
630
652
  ...(outputSchema !== undefined ? { output_schema: outputSchema } : {}),
653
+ ...(canonicalSchedulingFactors !== undefined ? { scheduling_factors: canonicalSchedulingFactors } : {}),
631
654
  },
632
655
  }
633
656
  : {}),
@@ -645,62 +668,35 @@ function canonicalWorkflowSpec(raw) {
645
668
  }),
646
669
  };
647
670
  }
648
- export function sha256(value) {
649
- return syncSha256(value);
650
- }
651
- /** Small synchronous SHA-256 for Worker/browser hosts (no node:crypto). */
652
- function syncSha256(value) {
653
- const encoder = new TextEncoder();
654
- const input = encoder.encode(value);
655
- const bitLength = input.length * 8;
656
- const length = ((input.length + 9 + 63) >> 6) << 6;
657
- const data = new Uint8Array(length);
658
- data.set(input);
659
- data[input.length] = 0x80;
660
- new DataView(data.buffer).setUint32(length - 4, bitLength >>> 0, false);
661
- const h = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
662
- const k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
663
- const ror = (x, n) => (x >>> n) | (x << (32 - n));
664
- for (let offset = 0; offset < data.length; offset += 64) {
665
- const w = new Uint32Array(64);
666
- const view = new DataView(data.buffer, offset, 64);
667
- for (let i = 0; i < 16; i++)
668
- w[i] = view.getUint32(i * 4, false);
669
- for (let i = 16; i < 64; i++) {
670
- w[i] = (w[i - 16] + (ror(w[i - 15], 7) ^ ror(w[i - 15], 18) ^ (w[i - 15] >>> 3)) + w[i - 7] + (ror(w[i - 2], 17) ^ ror(w[i - 2], 19) ^ (w[i - 2] >>> 10))) >>> 0;
671
+ function canonicalSchedulingFactorsForHost(value) {
672
+ const raw = asObject(value);
673
+ const allowed = new Set(["deadline_urgency", "process_priority", "resource_pressure", "budget_pressure"]);
674
+ const unknown = Object.keys(raw).filter(key => !allowed.has(key));
675
+ if (unknown.length > 0)
676
+ throw new TypeError(`unknown scheduling factor(s): ${unknown.join(", ")}`);
677
+ const factors = {};
678
+ for (const key of allowed) {
679
+ const factor = raw[key];
680
+ if (factor === undefined)
681
+ continue;
682
+ if (!Number.isSafeInteger(factor) || factor < 0) {
683
+ throw new RangeError(`scheduling_factors.${key} must be a non-negative safe integer`);
671
684
  }
672
- let [a, b, c, d, e, f, g, hh] = h;
673
- for (let i = 0; i < 64; i++) {
674
- const t1 = (hh + (ror(e, 6) ^ ror(e, 11) ^ ror(e, 25)) + ((e & f) ^ ((~e) & g)) + k[i] + w[i]) >>> 0;
675
- const t2 = ((ror(a, 2) ^ ror(a, 13) ^ ror(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
676
- hh = g;
677
- g = f;
678
- f = e;
679
- e = (d + t1) >>> 0;
680
- d = c;
681
- c = b;
682
- b = a;
683
- a = (t1 + t2) >>> 0;
684
- }
685
- for (let i = 0; i < 8; i++)
686
- h[i] = (h[i] + [a, b, c, d, e, f, g, hh][i]) >>> 0;
685
+ factors[key] = factor;
687
686
  }
688
- return `sha256:${Array.from(h, word => word.toString(16).padStart(8, "0")).join("")}`;
687
+ return factors;
688
+ }
689
+ export function sha256(value) {
690
+ return sha256Hex(value);
689
691
  }
690
- export { syncSha256 as sha256Hex };
691
692
  function providerStopReason(value) {
692
- if (typeof value !== "string" || value.length === 0)
693
+ if (value === undefined || value === null || value === "")
693
694
  return undefined;
694
- const normalized = value.toLowerCase();
695
- if (["end_turn", "tool_use", "max_tokens", "stop_sequence", "content_filter"].includes(normalized)) {
696
- return normalized;
697
- }
698
- return "other";
695
+ return normalizeProviderStopReason(value);
699
696
  }
700
697
  /**
701
698
  * Canonical operation runtime used by the WASM host.
702
- * Every durable transition below is one of the canonical ABI's five input classes; no legacy
703
- * envelope or synthesized host transaction reaches core or storage.
699
+ * Every durable transition below is one of the canonical contract's five input classes.
704
700
  */
705
701
  export class CanonicalRunnerRuntime {
706
702
  options;
@@ -826,7 +822,7 @@ export class CanonicalRunnerRuntime {
826
822
  kind: "start_operation",
827
823
  entry: {
828
824
  kind: "workflow",
829
- spec: canonicalWorkflowSpec(specValue),
825
+ spec: canonicalWorkflowSpec(specValue, true),
830
826
  },
831
827
  initial_context: this.initialContext,
832
828
  });
@@ -880,7 +876,8 @@ export class CanonicalRunnerRuntime {
880
876
  }
881
877
  case "provider_error": {
882
878
  const message = String(event.message ?? "");
883
- const contextOverflow = /context|token.*limit|too long/i.test(message);
879
+ const errorKind = typeof event.error_kind === "string" ? event.error_kind : "unknown";
880
+ const contextOverflow = errorKind === "context_overflow";
884
881
  input = contextOverflow
885
882
  ? {
886
883
  kind: "resolve_effect",
@@ -890,7 +887,11 @@ export class CanonicalRunnerRuntime {
890
887
  result: { kind: "provider", outcome: { kind: "context_overflow" } },
891
888
  },
892
889
  }
893
- : this.failedEffect(event, "transport_exhausted", message, true);
890
+ : this.failedEffect(event, ["transport", "rate_limit", "model_unavailable"].includes(errorKind)
891
+ ? "transport_exhausted"
892
+ : ["auth", "invalid_request", "modality", "protocol"].includes(errorKind)
893
+ ? "protocol_error"
894
+ : "unknown", message, typeof event.retryable === "boolean" ? event.retryable : false);
894
895
  break;
895
896
  }
896
897
  case "tool_results": {
@@ -1140,22 +1141,45 @@ export class CanonicalRunnerRuntime {
1140
1141
  async commit(input) {
1141
1142
  let nextInput = input;
1142
1143
  for (;;) {
1143
- const transition = await this.host.transition(nextInput);
1144
- if (!transition.replayed) {
1145
- for (const raw of transition.plannedStep.observations ?? []) {
1146
- const kind = String(raw.kind ?? "");
1147
- if (!kind)
1148
- throw new Error("canonical observation is missing kind");
1149
- this.hostObservations.push({ ...raw, kind });
1150
- }
1151
- if (transition.checkpointAdvice) {
1152
- this.hostObservations.push({
1153
- kind: "checkpoint_advised",
1154
- ...transition.checkpointAdvice,
1155
- });
1144
+ let transition;
1145
+ try {
1146
+ transition = await this.host.transition(nextInput);
1147
+ }
1148
+ catch (error) {
1149
+ if (!(error instanceof CanonicalKernelRebuildRequiredError) || !error.rebuilt)
1150
+ throw error;
1151
+ // The input's record is durable and the kernel was rebuilt from the journal with it
1152
+ // applied; only that step's observations are lost (the crash-window cost). Resync and
1153
+ // publish the rebuilt kernel's pending work instead of failing a healthy run.
1154
+ const lifecycle = this.host.kernel.lifecycle();
1155
+ this.configured = lifecycle !== "created";
1156
+ this.started = !["created", "configured"].includes(lifecycle);
1157
+ this.hostObservations.push({ kind: "kernel_rebuilt", reason: error.message });
1158
+ this.lastAction = this.currentAction();
1159
+ }
1160
+ if (transition) {
1161
+ if (!transition.replayed) {
1162
+ for (const raw of transition.plannedStep.observations ?? []) {
1163
+ const kind = String(raw.kind ?? "");
1164
+ if (!kind)
1165
+ throw new Error("canonical observation is missing kind");
1166
+ this.hostObservations.push({ ...raw, kind });
1167
+ }
1168
+ if (transition.checkpointAdvice) {
1169
+ this.hostObservations.push({
1170
+ kind: "checkpoint_advised",
1171
+ ...transition.checkpointAdvice,
1172
+ });
1173
+ }
1174
+ if (transition.checkpointFailure) {
1175
+ this.hostObservations.push({
1176
+ kind: "checkpoint_deferred",
1177
+ reason: transition.checkpointFailure,
1178
+ });
1179
+ }
1156
1180
  }
1181
+ this.lastAction = canonicalActionFromPlannedStep(transition.plannedStep);
1157
1182
  }
1158
- this.lastAction = canonicalActionFromPlannedStep(transition.plannedStep);
1159
1183
  if (this.lastAction?.kind !== "unsupported_effect")
1160
1184
  return this.lastAction;
1161
1185
  nextInput = canonicalUnsupportedEffectResolution(this.lastAction.effectId, this.lastAction.effectKind);
@@ -1426,9 +1450,6 @@ export class CanonicalRunnerRuntime {
1426
1450
  delete asObject(execution.entropy_watch).threshold;
1427
1451
  delete asObject(execution.entropy_watch).hysteresis;
1428
1452
  }
1429
- if (config.tool_dispatch_gate !== undefined) {
1430
- this.featurePolicy().tool_dispatch_gate = config.tool_dispatch_gate;
1431
- }
1432
1453
  if (config.knowledge_budget_ratio !== undefined) {
1433
1454
  const context = asObject(this.config.context_policy);
1434
1455
  context.knowledge_budget_ppm = Math.round(Number(config.knowledge_budget_ratio) * 1_000_000);