@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.
- package/README.md +15 -14
- package/dist/agent-ir.d.ts +81 -0
- package/dist/agent-ir.js +115 -0
- package/dist/agent.d.ts +140 -0
- package/dist/agent.js +34 -0
- package/dist/governance.d.ts +0 -25
- package/dist/governance.js +1 -53
- package/dist/index.d.ts +23 -8
- package/dist/index.js +11 -4
- package/dist/memory/durable.d.ts +13 -0
- package/dist/memory/durable.js +42 -0
- package/dist/memory/in-memory-store.d.ts +8 -6
- package/dist/memory/in-memory-store.js +8 -2
- package/dist/memory/index.d.ts +18 -2
- package/dist/providers/anthropic.d.ts +9 -0
- package/dist/providers/anthropic.js +12 -33
- package/dist/providers/content-policy.d.ts +16 -0
- package/dist/providers/content-policy.js +39 -0
- package/dist/providers/openai.d.ts +36 -17
- package/dist/providers/openai.js +86 -46
- package/dist/providers/provider-error.d.ts +22 -0
- package/dist/providers/provider-error.js +90 -0
- package/dist/providers/request-plan.d.ts +87 -0
- package/dist/providers/request-plan.js +153 -0
- package/dist/providers/stop-reason.d.ts +5 -0
- package/dist/providers/stop-reason.js +24 -0
- package/dist/runtime/canonical-kernel-step.d.ts +11 -5
- package/dist/runtime/canonical-kernel-step.js +99 -78
- package/dist/runtime/context-policy.d.ts +10 -12
- package/dist/runtime/context-policy.js +6 -8
- package/dist/runtime/durable-content.d.ts +44 -0
- package/dist/runtime/durable-content.js +119 -0
- package/dist/runtime/execution-plane.d.ts +2 -2
- package/dist/runtime/execution-plane.js +2 -2
- package/dist/runtime/kernel-event-log.js +0 -1
- package/dist/runtime/kernel-step.d.ts +1 -2
- package/dist/runtime/kernel-step.js +1 -1
- package/dist/runtime/provider-replay.d.ts +1 -1
- package/dist/runtime/provider-replay.js +12 -20
- package/dist/runtime/runner.d.ts +22 -18
- package/dist/runtime/runner.js +115 -55
- package/dist/runtime/session-log.d.ts +8 -1
- package/dist/runtime/session-repair.d.ts +1 -1
- package/dist/runtime/session-repair.js +1 -1
- package/dist/runtime/sha256.d.ts +2 -0
- package/dist/runtime/sha256.js +38 -0
- package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
- package/dist/runtime/sub-agent-orchestrator.js +3 -3
- package/dist/runtime/types/agent.d.ts +17 -7
- package/dist/runtime/types/agent.js +30 -4
- package/dist/runtime/workflow-control-flow.d.ts +0 -4
- package/dist/runtime/workflow-control-flow.js +0 -16
- package/dist/types.d.ts +58 -5
- 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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
693
|
+
if (value === undefined || value === null || value === "")
|
|
693
694
|
return undefined;
|
|
694
|
-
|
|
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
|
|
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
|
|
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, "
|
|
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
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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);
|