@deepstrike/wasm 0.2.62 → 0.2.64
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/LICENSE +106 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -1
- package/dist/providers/request-plan.d.ts +34 -1
- package/dist/providers/request-plan.js +49 -0
- package/dist/runtime/execution-evidence.d.ts +118 -0
- package/dist/runtime/execution-evidence.js +45 -0
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/kernel-step.d.ts +5 -0
- package/dist/runtime/kernel-step.js +11 -2
- package/dist/runtime/runner.d.ts +22 -0
- package/dist/runtime/runner.js +141 -7
- package/dist/runtime/session-log.d.ts +93 -3
- package/dist/runtime/session-log.js +131 -0
- package/dist/runtime/session-repair.d.ts +4 -1
- package/dist/runtime/session-repair.js +8 -0
- package/dist/types.d.ts +17 -0
- package/package.json +4 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
DeepStrike License (Dual License)
|
|
2
|
+
Version 1.0 — 2026-09-01
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025-2026 DeepStrike Contributors
|
|
5
|
+
Project: https://github.com/kongusen/deepstrike
|
|
6
|
+
|
|
7
|
+
======================================================================
|
|
8
|
+
中文摘要(便于理解,不具有法律效力;以下英文条款为准)
|
|
9
|
+
======================================================================
|
|
10
|
+
|
|
11
|
+
本软件采用双授权模式:
|
|
12
|
+
|
|
13
|
+
1. 个人,以及上一财年总营收低于 1,000,000 美元的组织(含其关联方),
|
|
14
|
+
可按 MIT 条款免费使用本软件(见第 2 节),包括使用、复制、修改、
|
|
15
|
+
合并、发布、分发、再授权及销售的权利。
|
|
16
|
+
2. 上一财年总营收达到或超过 1,000,000 美元的组织(含其关联方),
|
|
17
|
+
在使用本软件之前(包括内部使用、基于本软件开发的项目、以及对外
|
|
18
|
+
分发此类产品或服务)须向作者获取商业授权(见第 3 节)。
|
|
19
|
+
获取方式:发送邮件至 wanghaishan0210@gmail.com,或通过
|
|
20
|
+
https://github.com/kongusen/deepstrike 提交 Issue / Discussion
|
|
21
|
+
联系作者。出于评估目的的试用不超过 30 天除外。
|
|
22
|
+
3. 当组织的年营收增长至达到或超过该阈值时,自该时点起第 2 节的免费
|
|
23
|
+
授权终止,须获取商业授权后方可继续使用。
|
|
24
|
+
4. v0.2.62 及之前已经以 MIT 协议发布的版本继续适用 MIT 协议,不受
|
|
25
|
+
本协议影响。本协议仅适用于随其一同分发的版本。
|
|
26
|
+
|
|
27
|
+
======================================================================
|
|
28
|
+
TERMS AND CONDITIONS
|
|
29
|
+
======================================================================
|
|
30
|
+
|
|
31
|
+
1. DEFINITIONS
|
|
32
|
+
|
|
33
|
+
"Software" means the DeepStrike software and associated documentation
|
|
34
|
+
files distributed by the Licensor, including all copies and substantial
|
|
35
|
+
portions thereof.
|
|
36
|
+
|
|
37
|
+
"Licensor" means the copyright holders of the Software (the DeepStrike
|
|
38
|
+
Contributors), represented by the project maintainer at
|
|
39
|
+
https://github.com/kongusen/deepstrike.
|
|
40
|
+
|
|
41
|
+
"You" (or "Licensee") means the individual or legal entity exercising
|
|
42
|
+
permissions granted by this License, including, where that entity is an
|
|
43
|
+
organization, all entities that control, are controlled by, or are under
|
|
44
|
+
common control with that entity ("Affiliates"). "Control" means ownership
|
|
45
|
+
of more than fifty percent (50%) of the equity or voting interests, or
|
|
46
|
+
the power to direct management and policies.
|
|
47
|
+
|
|
48
|
+
"Annual Revenue" means the total gross revenue of You and Your Affiliates
|
|
49
|
+
for the most recently completed fiscal year, expressed in US dollars or
|
|
50
|
+
converted into US dollars at prevailing exchange rates.
|
|
51
|
+
|
|
52
|
+
2. FREE LICENSE FOR INDIVIDUALS AND SMALL ORGANIZATIONS
|
|
53
|
+
|
|
54
|
+
If You are an individual, or a legal entity whose Annual Revenue is less
|
|
55
|
+
than one million US dollars (USD 1,000,000), the Licensor hereby grants
|
|
56
|
+
You, free of charge, permission to deal in the Software without
|
|
57
|
+
restriction, including without limitation the rights to use, copy, modify,
|
|
58
|
+
merge, publish, distribute, sublicense, and/or sell copies of the
|
|
59
|
+
Software, and to permit persons to whom the Software is furnished to do
|
|
60
|
+
so, subject to the following conditions:
|
|
61
|
+
|
|
62
|
+
(a) The above copyright notice and this License shall be included in all
|
|
63
|
+
copies or substantial portions of the Software.
|
|
64
|
+
|
|
65
|
+
(b) If Your Annual Revenue reaches or exceeds USD 1,000,000, the grant in
|
|
66
|
+
this Section 2 terminates for You, and Section 3 applies from that
|
|
67
|
+
point on.
|
|
68
|
+
|
|
69
|
+
3. COMMERCIAL LICENSE REQUIRED (ANNUAL REVENUE >= USD 1,000,000)
|
|
70
|
+
|
|
71
|
+
If Your Annual Revenue equals or exceeds one million US dollars
|
|
72
|
+
(USD 1,000,000), You must obtain a commercial license from the Licensor
|
|
73
|
+
before using the Software. This applies to every form of use, including
|
|
74
|
+
internal use, use within products or services developed based on the
|
|
75
|
+
Software, and distribution or provision of such products or services to
|
|
76
|
+
third parties.
|
|
77
|
+
|
|
78
|
+
Exception: You may use the Software without a commercial license solely
|
|
79
|
+
for evaluation purposes, for a period of up to thirty (30) days.
|
|
80
|
+
|
|
81
|
+
To obtain a commercial license, contact the Licensor at
|
|
82
|
+
wanghaishan0210@gmail.com, or via the project repository at
|
|
83
|
+
https://github.com/kongusen/deepstrike (open an issue or discussion).
|
|
84
|
+
Commercial terms, including fees, are set by individual agreement with
|
|
85
|
+
the Licensor.
|
|
86
|
+
|
|
87
|
+
Any use of the Software requiring a commercial license under this
|
|
88
|
+
Section 3 without one is unlicensed use and constitutes copyright
|
|
89
|
+
infringement.
|
|
90
|
+
|
|
91
|
+
4. PRIOR VERSIONS
|
|
92
|
+
|
|
93
|
+
Versions of the Software released before this License took effect (up to
|
|
94
|
+
and including version 0.2.62) were released under the MIT License and
|
|
95
|
+
remain available under the terms of the MIT License. This License applies
|
|
96
|
+
only to versions of the Software distributed together with it.
|
|
97
|
+
|
|
98
|
+
5. NO WARRANTY
|
|
99
|
+
|
|
100
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
101
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
102
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
103
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
104
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
105
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
106
|
+
DEALINGS IN THE SOFTWARE.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export { RuntimeRunner, collectText, runAgent, runFanout, InMemorySessionLog, LocalExecutionPlane, DEFAULT_NATIVE_SIGNAL_POLICY, DEFAULT_NATIVE_GOVERNANCE_POLICY, DEFAULT_SANDBOX_POLICY, assertNativeProfile, osProfile, validateDeclarativePolicy, ReplayProvider, extractRecordedMessages, judge, buildEvalMessages, parseVerdict, verdictOutputSchema, } from "./runtime/index.js";
|
|
1
|
+
export { RuntimeRunner, collectText, runAgent, runFanout, InMemorySessionLog, assertCanonicalProviderAttemptRecord, SESSION_EVENT_KINDS, LocalExecutionPlane, DEFAULT_NATIVE_SIGNAL_POLICY, DEFAULT_NATIVE_GOVERNANCE_POLICY, DEFAULT_SANDBOX_POLICY, assertNativeProfile, osProfile, validateDeclarativePolicy, ReplayProvider, extractRecordedMessages, judge, buildEvalMessages, parseVerdict, verdictOutputSchema, } from "./runtime/index.js";
|
|
2
2
|
export { DriverKernelJournal, InMemoryJournalDriver, InMemoryKernelJournal, JournalCasConflictError, JournalIntegrityError, JournalIoError, } from "./runtime/kernel-journal.js";
|
|
3
3
|
export type { CheckpointCandidate, InstalledCheckpoint, JournalAppendReceipt, JournalEntry, JournalHead, JournalPruneReceipt, JournalRecordInput, JournalStorageDriver, KernelJournal, } from "./runtime/kernel-journal.js";
|
|
4
|
+
export { CANONICAL_CONTENT_PARTS_PREFIX, encodeCanonicalContentParts, decodeCanonicalContentParts, } from "./runtime/kernel-step.js";
|
|
4
5
|
export type { ReplayProviderOpts, Criterion, Verdict, VerdictDetail, JudgeArgs, } from "./runtime/index.js";
|
|
5
|
-
export type { NativeOsProfile, OsProfileId, MemoryPolicy, MemoryWriteRateLimit, ResourceQuota, RuntimeOptions, PromptBudget, SchedulerPolicy, SignalPolicy, SessionEvent, SessionLog, RunContext, ExecutionPlane, KernelEventCategory, KernelPrimitive, } from "./runtime/index.js";
|
|
6
|
+
export type { NativeOsProfile, OsProfileId, MemoryPolicy, MemoryWriteRateLimit, ResourceQuota, RuntimeOptions, PromptBudget, SchedulerPolicy, SignalPolicy, SessionEvent, SessionEventKind, SessionLog, RunContext, ExecutionPlane, KernelEventCategory, KernelPrimitive, } from "./runtime/index.js";
|
|
6
7
|
export { primitiveForCategory, primitiveForKind } from "./runtime/index.js";
|
|
7
8
|
export { FilteredExecutionPlane } from "./runtime/filtered-plane.js";
|
|
8
9
|
export { SubAgentOrchestrator, defaultSubAgentOrchestrator, spawnStandalone } from "./runtime/sub-agent-orchestrator.js";
|
|
@@ -40,12 +41,14 @@ export { ScheduledPrompt } from "./signals/index.js";
|
|
|
40
41
|
export type { RuntimeSignal, SignalSource } from "./signals/index.js";
|
|
41
42
|
export { PermissionManager, PermissionMode } from "./safety/index.js";
|
|
42
43
|
export { createProviderRequestPlan, createProviderRequestPlanForProvider, estimateProviderPromptTokens, measurementForPlan, normalizeProviderUsage, priceProviderUsage, recordPromptMeasurement } from "./providers/request-plan.js";
|
|
43
|
-
export type { CostObservation, NormalizedProviderUsage, PricingSnapshot, ProviderRequestEndpoint, ProviderRequestPlan, ProviderUsage, RecordedPromptMeasurement } from "./providers/request-plan.js";
|
|
44
|
+
export type { CostObservation, NormalizedProviderUsage, PricingSnapshot, ProviderRequestEndpoint, ProviderRequestPlan, ProviderUsage, RecordedPromptMeasurement, ResolvedProviderRoute } from "./providers/request-plan.js";
|
|
45
|
+
export type { ProviderAttempt, ProviderAttemptRecord, ProviderAttemptStatus, ModelInvocation, InvocationOutcome, ModelUsageSettlement, UsageAccountingPolicy, } from "./runtime/execution-evidence.js";
|
|
46
|
+
export { FULL_FOOTPRINT_USAGE_ACCOUNTING_POLICY, providerAttemptToRecord, tryNormalizeProviderUsage } from "./runtime/execution-evidence.js";
|
|
44
47
|
export { decodeCanonicalStopReason, normalizeProviderStopReason } from "./providers/stop-reason.js";
|
|
45
48
|
export type { CanonicalStopReason } from "./providers/stop-reason.js";
|
|
46
49
|
export { ContentPolicyError, contentDispositionFor, requireContentDisposition } from "./providers/content-policy.js";
|
|
47
50
|
export type { ContentDisposition, ContentPlacement, InputModality } from "./providers/content-policy.js";
|
|
48
51
|
export type { PermissionDecision } from "./safety/index.js";
|
|
49
|
-
export type { Message, ToolCall, ToolResult, ToolSchema, RenderedContext, ProviderRunState, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, ToolAuditFailedEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, EntropySample, EntropySampleEvent, EntropyAlertEvent, EntropyWatchOptions, LLMProvider, CacheBreakpointStrategy, } from "./types.js";
|
|
52
|
+
export type { Message, ToolCall, ToolResult, ToolSchema, RenderedContext, ProviderRunState, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, ToolAuditFailedEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, EntropySample, EntropySampleEvent, EntropyAlertEvent, EntropyWatchOptions, LLMProvider, CacheBreakpointStrategy, ProviderWireEvidence, ProviderTransportTelemetry, } from "./types.js";
|
|
50
53
|
export { DurableContentError, decodeDurableContent, decodeDurableToolResult, encodeDurableContent, encodeDurableToolResult, toolOutputBlocksToDurable, durableBlocksToToolOutput } from "./runtime/durable-content.js";
|
|
51
54
|
export type { DurableContent, DurableContentBlock, DurableSource, DurableToolResult } from "./runtime/durable-content.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { RuntimeRunner, collectText, runAgent, runFanout, InMemorySessionLog, LocalExecutionPlane, DEFAULT_NATIVE_SIGNAL_POLICY, DEFAULT_NATIVE_GOVERNANCE_POLICY, DEFAULT_SANDBOX_POLICY, assertNativeProfile, osProfile, validateDeclarativePolicy, ReplayProvider, extractRecordedMessages, judge, buildEvalMessages, parseVerdict, verdictOutputSchema, } from "./runtime/index.js";
|
|
1
|
+
export { RuntimeRunner, collectText, runAgent, runFanout, InMemorySessionLog, assertCanonicalProviderAttemptRecord, SESSION_EVENT_KINDS, LocalExecutionPlane, DEFAULT_NATIVE_SIGNAL_POLICY, DEFAULT_NATIVE_GOVERNANCE_POLICY, DEFAULT_SANDBOX_POLICY, assertNativeProfile, osProfile, validateDeclarativePolicy, ReplayProvider, extractRecordedMessages, judge, buildEvalMessages, parseVerdict, verdictOutputSchema, } from "./runtime/index.js";
|
|
2
2
|
// ── Durable transaction capability (Canonical Kernel ABI §9.1) ──────────────
|
|
3
3
|
export { DriverKernelJournal, InMemoryJournalDriver, InMemoryKernelJournal, JournalCasConflictError, JournalIntegrityError, JournalIoError, } from "./runtime/kernel-journal.js";
|
|
4
|
+
// ── content-parts-v1 registered encoding (F14/B5; byte-pinned by sdk-conformance) ──
|
|
5
|
+
export { CANONICAL_CONTENT_PARTS_PREFIX, encodeCanonicalContentParts, decodeCanonicalContentParts, } from "./runtime/kernel-step.js";
|
|
4
6
|
export { primitiveForCategory, primitiveForKind } from "./runtime/index.js";
|
|
5
7
|
export { FilteredExecutionPlane } from "./runtime/filtered-plane.js";
|
|
6
8
|
export { SubAgentOrchestrator, defaultSubAgentOrchestrator, spawnStandalone } from "./runtime/sub-agent-orchestrator.js";
|
|
@@ -23,6 +25,7 @@ export { AttemptLoop, RuntimeAttemptBody, VerdictFnJudge, LlmEvalJudge, HybridJu
|
|
|
23
25
|
export { ScheduledPrompt } from "./signals/index.js";
|
|
24
26
|
export { PermissionManager, PermissionMode } from "./safety/index.js";
|
|
25
27
|
export { createProviderRequestPlan, createProviderRequestPlanForProvider, estimateProviderPromptTokens, measurementForPlan, normalizeProviderUsage, priceProviderUsage, recordPromptMeasurement } from "./providers/request-plan.js";
|
|
28
|
+
export { FULL_FOOTPRINT_USAGE_ACCOUNTING_POLICY, providerAttemptToRecord, tryNormalizeProviderUsage } from "./runtime/execution-evidence.js";
|
|
26
29
|
export { decodeCanonicalStopReason, normalizeProviderStopReason } from "./providers/stop-reason.js";
|
|
27
30
|
export { ContentPolicyError, contentDispositionFor, requireContentDisposition } from "./providers/content-policy.js";
|
|
28
31
|
export { DurableContentError, decodeDurableContent, decodeDurableToolResult, encodeDurableContent, encodeDurableToolResult, toolOutputBlocksToDurable, durableBlocksToToolOutput } from "./runtime/durable-content.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RenderedContext, ToolSchema } from "../types.js";
|
|
1
|
+
import type { ProviderProtocol, RenderedContext, ToolSchema } from "../types.js";
|
|
2
2
|
export interface ProviderRequestEndpoint {
|
|
3
3
|
id: string;
|
|
4
4
|
protocol: string;
|
|
@@ -36,6 +36,16 @@ export interface ProviderUsage {
|
|
|
36
36
|
cacheCreationInputTokens?: number;
|
|
37
37
|
reasoningTokens?: number;
|
|
38
38
|
}
|
|
39
|
+
/** P4 §1.3 mirror of the node ResolvedProviderRoute (content-addressed route evidence). */
|
|
40
|
+
export interface ResolvedProviderRoute {
|
|
41
|
+
routeId: string;
|
|
42
|
+
provider: string;
|
|
43
|
+
protocol: ProviderProtocol;
|
|
44
|
+
model: string;
|
|
45
|
+
endpoint: ProviderRequestEndpoint;
|
|
46
|
+
adapterVersion: string;
|
|
47
|
+
capabilitiesRef: string;
|
|
48
|
+
}
|
|
39
49
|
export interface NormalizedProviderUsage extends ProviderUsage {
|
|
40
50
|
uncachedInputTokens: number;
|
|
41
51
|
}
|
|
@@ -85,3 +95,26 @@ export declare function recordPromptMeasurement(plan: Pick<ProviderRequestPlan,
|
|
|
85
95
|
export declare function measurementForPlan(plan: Pick<ProviderRequestPlan, "fingerprint">, record: RecordedPromptMeasurement | undefined): RecordedPromptMeasurement | undefined;
|
|
86
96
|
export declare function normalizeProviderUsage(usage: ProviderUsage): NormalizedProviderUsage;
|
|
87
97
|
export declare function priceProviderUsage(usage: NormalizedProviderUsage, snapshot: PricingSnapshot, observedAt?: string | Date): CostObservation;
|
|
98
|
+
/**
|
|
99
|
+
* P4-S1: assemble the run's route once at runner construction (P4 §0.2 — the provider is
|
|
100
|
+
* fixed for the run today; a future failover resolver re-evaluates per attempt with the same
|
|
101
|
+
* shape). Never throws: evidence assembly degrades to "unknown" fields rather than breaking a
|
|
102
|
+
* run. The wasm bundle cannot read its own package manifest at runtime (browser targets), so
|
|
103
|
+
* adapterVersion is honestly "unknown" — the field is a report, not a gate.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveProviderRoute(provider: {
|
|
106
|
+
descriptor?(): {
|
|
107
|
+
provider: string;
|
|
108
|
+
protocol: string;
|
|
109
|
+
model: string;
|
|
110
|
+
};
|
|
111
|
+
requestPlanIdentity?(): {
|
|
112
|
+
providerId?: string;
|
|
113
|
+
modelId?: string;
|
|
114
|
+
endpoint?: {
|
|
115
|
+
id?: string;
|
|
116
|
+
protocol?: string;
|
|
117
|
+
baseURL?: string;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
}): ResolvedProviderRoute;
|
|
@@ -106,6 +106,55 @@ function sanitizeEndpoint(endpoint) {
|
|
|
106
106
|
return { ...clone(endpoint), baseURL: "" };
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
const KNOWN_GENERATION_PROTOCOLS = new Set([
|
|
110
|
+
"anthropic-messages",
|
|
111
|
+
"openai-chat",
|
|
112
|
+
"openai-responses",
|
|
113
|
+
"gemini",
|
|
114
|
+
"ollama-chat",
|
|
115
|
+
]);
|
|
116
|
+
/**
|
|
117
|
+
* P4-S1: assemble the run's route once at runner construction (P4 §0.2 — the provider is
|
|
118
|
+
* fixed for the run today; a future failover resolver re-evaluates per attempt with the same
|
|
119
|
+
* shape). Never throws: evidence assembly degrades to "unknown" fields rather than breaking a
|
|
120
|
+
* run. The wasm bundle cannot read its own package manifest at runtime (browser targets), so
|
|
121
|
+
* adapterVersion is honestly "unknown" — the field is a report, not a gate.
|
|
122
|
+
*/
|
|
123
|
+
export function resolveProviderRoute(provider) {
|
|
124
|
+
try {
|
|
125
|
+
const descriptor = provider.descriptor?.() ?? { provider: "unknown", protocol: "unknown", model: "unknown" };
|
|
126
|
+
const identity = provider.requestPlanIdentity?.();
|
|
127
|
+
const protocolRaw = identity?.endpoint?.protocol ?? descriptor.protocol;
|
|
128
|
+
// Evidence records what the descriptor said, even when a foreign provider speaks a protocol
|
|
129
|
+
// outside the in-tree vocabulary — the field is a report, not a gate.
|
|
130
|
+
const protocol = protocolRaw;
|
|
131
|
+
const route = {
|
|
132
|
+
provider: identity?.providerId ?? descriptor.provider,
|
|
133
|
+
protocol,
|
|
134
|
+
model: identity?.modelId ?? descriptor.model,
|
|
135
|
+
endpoint: sanitizeEndpoint({
|
|
136
|
+
id: identity?.endpoint?.id ?? `${descriptor.provider}.${descriptor.protocol}`,
|
|
137
|
+
protocol: protocolRaw,
|
|
138
|
+
baseURL: identity?.endpoint?.baseURL ?? "",
|
|
139
|
+
}),
|
|
140
|
+
adapterVersion: "unknown",
|
|
141
|
+
capabilitiesRef: KNOWN_GENERATION_PROTOCOLS.has(protocolRaw) ? protocolRaw : "unknown",
|
|
142
|
+
};
|
|
143
|
+
// §1.3: content addressing covers every field EXCEPT capabilitiesRef (a reference, not content).
|
|
144
|
+
const { capabilitiesRef: _ref, ...addressed } = route;
|
|
145
|
+
return { ...route, routeId: sha256Hex(stableJson(addressed)) };
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
const fallback = {
|
|
149
|
+
provider: "unknown",
|
|
150
|
+
protocol: "unknown",
|
|
151
|
+
model: "unknown",
|
|
152
|
+
endpoint: { id: "unknown", protocol: "unknown", baseURL: "" },
|
|
153
|
+
adapterVersion: "unknown",
|
|
154
|
+
};
|
|
155
|
+
return { ...fallback, capabilitiesRef: "unknown", routeId: sha256Hex(stableJson(fallback)) };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
109
158
|
function sanitizeMaterialValue(value) {
|
|
110
159
|
if (value === undefined || typeof value === "function")
|
|
111
160
|
return undefined;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P4 (0.2.64 Execution Evidence Plane): wasm mirror of the host-side object model connecting
|
|
3
|
+
* kernel-minted effects to real provider execution — ModelInvocation / ProviderAttempt /
|
|
4
|
+
* ResolvedProviderRoute / UsageAccountingPolicy. Everything here is L2 host evidence (B7):
|
|
5
|
+
* it lands in SessionLog for cross-verification against the journal (C6/C8) and is NEVER
|
|
6
|
+
* fed back as kernel input (B4/DEC-2 discipline: wall-clock and wire facts stay host-side).
|
|
7
|
+
*/
|
|
8
|
+
import type { ProviderWireEvidence } from "../types.js";
|
|
9
|
+
import type { NormalizedProviderUsage, ResolvedProviderRoute } from "../providers/request-plan.js";
|
|
10
|
+
/** Canonical stop-reason vocabulary already carried on the wire usage frame (types.ts). */
|
|
11
|
+
export type CanonicalStopReason = "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "content_filtered";
|
|
12
|
+
/**
|
|
13
|
+
* P4 §1.1: one logical model call = the fact-connected chain of CallProvider effects the
|
|
14
|
+
* kernel walked to obtain one turn of model output. Derived identity, zero minting:
|
|
15
|
+
* `invocationId` IS the first effect's effect_id. Authority = journal; the SessionLog
|
|
16
|
+
* projection (`llm_completed.invocation_id`) is evidence only.
|
|
17
|
+
*/
|
|
18
|
+
export interface ModelInvocation {
|
|
19
|
+
invocationId: string;
|
|
20
|
+
turn: number;
|
|
21
|
+
/** Every effect_id on the chain, in order. Length 1 = first attempt succeeded. */
|
|
22
|
+
effectChain: string[];
|
|
23
|
+
outcome?: InvocationOutcome;
|
|
24
|
+
}
|
|
25
|
+
/** P4 §1.4: the invocation's terminal projection. */
|
|
26
|
+
export interface InvocationOutcome {
|
|
27
|
+
invocationId: string;
|
|
28
|
+
/** The effect the kernel adopted as the outcome (the successful one). */
|
|
29
|
+
selectedEffectId: string;
|
|
30
|
+
stopReason?: CanonicalStopReason;
|
|
31
|
+
/** Absent on a failed chain (nothing settled). */
|
|
32
|
+
settlement?: ModelUsageSettlement;
|
|
33
|
+
}
|
|
34
|
+
export type ProviderAttemptStatus = "success" | "transport_exhausted" | "aborted" | "rejected";
|
|
35
|
+
/**
|
|
36
|
+
* P4 §1.2: one effect's execution against one route, one physical attempt. The kernel-minted
|
|
37
|
+
* `effectId` is the primary key (H7 — no parallel id minting). Transport-ladder rungs are
|
|
38
|
+
* summarized as a count plus the final error class; rung-level evidence belongs to adapter
|
|
39
|
+
* debug logs, not SessionLog.
|
|
40
|
+
*/
|
|
41
|
+
export interface ProviderAttempt {
|
|
42
|
+
effectId: string;
|
|
43
|
+
/** Failover sequence within one effect execution (1-based). Always 1 today (P4 §0.2). */
|
|
44
|
+
attemptSeq: number;
|
|
45
|
+
route: ResolvedProviderRoute;
|
|
46
|
+
/** → ProviderRequestPlan.fingerprint (G2). */
|
|
47
|
+
requestFingerprint: string;
|
|
48
|
+
status: ProviderAttemptStatus;
|
|
49
|
+
transportRungs: number;
|
|
50
|
+
/** classifyProviderError's class, never the raw vendor text (B1 spirit). */
|
|
51
|
+
lastErrorClass?: string;
|
|
52
|
+
/** Host wall-clock, pure evidence, never kernel input (DEC-2 discipline). */
|
|
53
|
+
startedAtMs: number;
|
|
54
|
+
finishedAtMs: number;
|
|
55
|
+
/** Full measurement fields (P4 §2); only the settlement crosses the kernel boundary (B4). */
|
|
56
|
+
usage?: NormalizedProviderUsage;
|
|
57
|
+
wireEvidence?: ProviderWireEvidence;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The SessionLog wire payload of a ProviderAttempt (P4 §3): the §1.2 fields flattened into
|
|
61
|
+
* the event, snake_case per SessionLog convention. Nested objects keep their native shape
|
|
62
|
+
* (same convention as `prompt_measured.measurement`).
|
|
63
|
+
*/
|
|
64
|
+
export interface ProviderAttemptRecord {
|
|
65
|
+
effect_id: string;
|
|
66
|
+
attempt_seq: number;
|
|
67
|
+
route: ResolvedProviderRoute;
|
|
68
|
+
request_fingerprint: string;
|
|
69
|
+
status: ProviderAttemptStatus;
|
|
70
|
+
transport_rungs: number;
|
|
71
|
+
last_error_class?: string;
|
|
72
|
+
started_at_ms: number;
|
|
73
|
+
finished_at_ms: number;
|
|
74
|
+
usage?: NormalizedProviderUsage;
|
|
75
|
+
wire_evidence?: ProviderWireEvidence;
|
|
76
|
+
/** P4 §2.1: the accounting policy this attempt's settlement was/will be derived with —
|
|
77
|
+
* pinned on the attempt so settlement is deterministically recomputable from
|
|
78
|
+
* (measurement, policy_id). */
|
|
79
|
+
accounting_policy_id?: string;
|
|
80
|
+
}
|
|
81
|
+
export declare function providerAttemptToRecord(attempt: ProviderAttempt, accountingPolicyId?: string): ProviderAttemptRecord;
|
|
82
|
+
/**
|
|
83
|
+
* P4 §2: the only two numbers that cross the kernel boundary (B4). Field names match the
|
|
84
|
+
* existing ResolveEffect wire shape — this is what the runner already feeds the kernel as
|
|
85
|
+
* `observed_input_tokens` / `observed_output_tokens`.
|
|
86
|
+
*/
|
|
87
|
+
export interface ModelUsageSettlement {
|
|
88
|
+
observed_input_tokens: number;
|
|
89
|
+
observed_output_tokens: number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* P4 §2.1: the named, pinnable, replayable policy turning a measurement into a settlement.
|
|
93
|
+
* `settle` must be a pure function of the measurement — given (usage, policyId) any auditor
|
|
94
|
+
* recomputes the identical settlement.
|
|
95
|
+
*/
|
|
96
|
+
export interface UsageAccountingPolicy {
|
|
97
|
+
policyId: string;
|
|
98
|
+
settle(usage: NormalizedProviderUsage): ModelUsageSettlement;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The default policy = today's implicit runner behavior exactly (full input footprint + full
|
|
102
|
+
* output footprint — the two numbers the runner has always fed `observed_*`). P4 changes no
|
|
103
|
+
* default numbers; it only makes the conversion a named object. The date stamp is the
|
|
104
|
+
* policy's identity: any future semantics change MUST ship under a new policyId.
|
|
105
|
+
*/
|
|
106
|
+
export declare const FULL_FOOTPRINT_USAGE_ACCOUNTING_POLICY: UsageAccountingPolicy;
|
|
107
|
+
/**
|
|
108
|
+
* Defensive measurement assembly for the attempt evidence path: an invalid frame (cache
|
|
109
|
+
* subsets exceeding input, etc.) degrades to NO measurement instead of breaking the run —
|
|
110
|
+
* evidence is never worth a run failure, and the settlement falls back to the raw counts.
|
|
111
|
+
*/
|
|
112
|
+
export declare function tryNormalizeProviderUsage(usage: {
|
|
113
|
+
inputTokens: number;
|
|
114
|
+
outputTokens: number;
|
|
115
|
+
cacheReadInputTokens?: number;
|
|
116
|
+
cacheCreationInputTokens?: number;
|
|
117
|
+
reasoningTokens?: number;
|
|
118
|
+
}): NormalizedProviderUsage | undefined;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { normalizeProviderUsage } from "../providers/request-plan.js";
|
|
2
|
+
export function providerAttemptToRecord(attempt, accountingPolicyId) {
|
|
3
|
+
return {
|
|
4
|
+
effect_id: attempt.effectId,
|
|
5
|
+
attempt_seq: attempt.attemptSeq,
|
|
6
|
+
route: attempt.route,
|
|
7
|
+
request_fingerprint: attempt.requestFingerprint,
|
|
8
|
+
status: attempt.status,
|
|
9
|
+
transport_rungs: attempt.transportRungs,
|
|
10
|
+
...(attempt.lastErrorClass !== undefined ? { last_error_class: attempt.lastErrorClass } : {}),
|
|
11
|
+
started_at_ms: attempt.startedAtMs,
|
|
12
|
+
finished_at_ms: attempt.finishedAtMs,
|
|
13
|
+
...(attempt.usage !== undefined ? { usage: attempt.usage } : {}),
|
|
14
|
+
...(attempt.wireEvidence !== undefined ? { wire_evidence: attempt.wireEvidence } : {}),
|
|
15
|
+
...(accountingPolicyId !== undefined ? { accounting_policy_id: accountingPolicyId } : {}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The default policy = today's implicit runner behavior exactly (full input footprint + full
|
|
20
|
+
* output footprint — the two numbers the runner has always fed `observed_*`). P4 changes no
|
|
21
|
+
* default numbers; it only makes the conversion a named object. The date stamp is the
|
|
22
|
+
* policy's identity: any future semantics change MUST ship under a new policyId.
|
|
23
|
+
*/
|
|
24
|
+
export const FULL_FOOTPRINT_USAGE_ACCOUNTING_POLICY = {
|
|
25
|
+
policyId: "deepstrike.full-footprint@2026-09-15",
|
|
26
|
+
settle(usage) {
|
|
27
|
+
return {
|
|
28
|
+
observed_input_tokens: usage.inputTokens,
|
|
29
|
+
observed_output_tokens: usage.outputTokens,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Defensive measurement assembly for the attempt evidence path: an invalid frame (cache
|
|
35
|
+
* subsets exceeding input, etc.) degrades to NO measurement instead of breaking the run —
|
|
36
|
+
* evidence is never worth a run failure, and the settlement falls back to the raw counts.
|
|
37
|
+
*/
|
|
38
|
+
export function tryNormalizeProviderUsage(usage) {
|
|
39
|
+
try {
|
|
40
|
+
return normalizeProviderUsage(usage);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export type { SessionEvent, SessionLog } from "./session-log.js";
|
|
2
|
-
export { InMemorySessionLog } from "./session-log.js";
|
|
1
|
+
export type { SessionEvent, SessionEventKind, SessionLog } from "./session-log.js";
|
|
2
|
+
export { InMemorySessionLog, assertCanonicalProviderAttemptRecord } from "./session-log.js";
|
|
3
|
+
export { SESSION_EVENT_KINDS } from "./session-log.js";
|
|
3
4
|
export { DriverKernelJournal, InMemoryJournalDriver, InMemoryKernelJournal, JournalCasConflictError, JournalIntegrityError, JournalIoError, } from "./kernel-journal.js";
|
|
4
5
|
export type { CheckpointCandidate, InstalledCheckpoint, JournalAppendReceipt, JournalEntry, JournalHead, JournalPruneReceipt, JournalRecordInput, JournalStorageDriver, KernelJournal, } from "./kernel-journal.js";
|
|
5
6
|
export * from "./context-policy.js";
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { InMemorySessionLog } from "./session-log.js";
|
|
1
|
+
export { InMemorySessionLog, assertCanonicalProviderAttemptRecord } from "./session-log.js";
|
|
2
|
+
// Registered session-event vocabulary (F9/S3; manifest-pinned by sdk-conformance, P7-S4)
|
|
3
|
+
export { SESSION_EVENT_KINDS } from "./session-log.js";
|
|
2
4
|
// Durable transaction capability (Canonical Kernel ABI §9.1)
|
|
3
5
|
export { DriverKernelJournal, InMemoryJournalDriver, InMemoryKernelJournal, JournalCasConflictError, JournalIntegrityError, JournalIoError, } from "./kernel-journal.js";
|
|
4
6
|
export * from "./context-policy.js";
|
|
@@ -22,6 +22,11 @@ export declare const CANONICAL_CONTENT_PARTS_PREFIX = "[[deepstrike-content-part
|
|
|
22
22
|
/**
|
|
23
23
|
* Browser/worker-safe transport for typed content. `btoa` and `atob` operate on
|
|
24
24
|
* binary strings, so explicitly bridge UTF-8 bytes rather than assuming Latin-1.
|
|
25
|
+
*
|
|
26
|
+
* Byte contract (content-parts-v1, F14/B5): compact JSON with literal UTF-8, insertion key
|
|
27
|
+
* order, **base64url alphabet, no padding** — identical bytes to the Node and Python SDKs.
|
|
28
|
+
* Decode stays lenient (both alphabets, optional padding) so payloads written before the
|
|
29
|
+
* url-alphabet alignment remain readable.
|
|
25
30
|
*/
|
|
26
31
|
export declare function encodeCanonicalContentParts(parts: unknown[]): string;
|
|
27
32
|
export declare function decodeCanonicalContentParts(content: string): Array<Record<string, unknown>> | undefined;
|
|
@@ -2,19 +2,28 @@ export const CANONICAL_CONTENT_PARTS_PREFIX = "[[deepstrike-content-parts]]";
|
|
|
2
2
|
/**
|
|
3
3
|
* Browser/worker-safe transport for typed content. `btoa` and `atob` operate on
|
|
4
4
|
* binary strings, so explicitly bridge UTF-8 bytes rather than assuming Latin-1.
|
|
5
|
+
*
|
|
6
|
+
* Byte contract (content-parts-v1, F14/B5): compact JSON with literal UTF-8, insertion key
|
|
7
|
+
* order, **base64url alphabet, no padding** — identical bytes to the Node and Python SDKs.
|
|
8
|
+
* Decode stays lenient (both alphabets, optional padding) so payloads written before the
|
|
9
|
+
* url-alphabet alignment remain readable.
|
|
5
10
|
*/
|
|
6
11
|
export function encodeCanonicalContentParts(parts) {
|
|
7
12
|
const bytes = new TextEncoder().encode(JSON.stringify(parts));
|
|
8
13
|
let binary = "";
|
|
9
14
|
for (const byte of bytes)
|
|
10
15
|
binary += String.fromCharCode(byte);
|
|
11
|
-
|
|
16
|
+
const payload = btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
17
|
+
return `${CANONICAL_CONTENT_PARTS_PREFIX}${payload}`;
|
|
12
18
|
}
|
|
13
19
|
export function decodeCanonicalContentParts(content) {
|
|
14
20
|
if (!content.startsWith(CANONICAL_CONTENT_PARTS_PREFIX))
|
|
15
21
|
return undefined;
|
|
16
22
|
try {
|
|
17
|
-
const
|
|
23
|
+
const raw = content.slice(CANONICAL_CONTENT_PARTS_PREFIX.length);
|
|
24
|
+
const standard = raw.replaceAll("-", "+").replaceAll("_", "/");
|
|
25
|
+
const padded = standard + "=".repeat((4 - (standard.length % 4)) % 4);
|
|
26
|
+
const binary = atob(padded);
|
|
18
27
|
const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
|
|
19
28
|
const decoded = JSON.parse(new TextDecoder().decode(bytes));
|
|
20
29
|
return Array.isArray(decoded)
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { type ReducerRegistry } from "./reducers.js";
|
|
|
13
13
|
import { type NativeOsProfile, type OsProfileId, type SignalPolicy } from "./os-profile.js";
|
|
14
14
|
import { PayloadStore } from "./payload-store.js";
|
|
15
15
|
import { type ContextPolicyOverrides } from "./context-policy.js";
|
|
16
|
+
import { type UsageAccountingPolicy } from "./execution-evidence.js";
|
|
16
17
|
export interface MemoryWriteRateLimit {
|
|
17
18
|
maxWrites: number;
|
|
18
19
|
windowMs: number;
|
|
@@ -140,6 +141,10 @@ export interface RuntimeOptions {
|
|
|
140
141
|
governancePolicy?: GovernancePolicy;
|
|
141
142
|
signalPolicy?: SignalPolicy;
|
|
142
143
|
promptBudget?: PromptBudget;
|
|
144
|
+
/** P4 §2.1: the named policy turning a provider measurement into the two observed_* numbers
|
|
145
|
+
* that cross the kernel boundary. Undefined ⇒ FULL_FOOTPRINT (the numbers the runner has
|
|
146
|
+
* always fed); only makes the conversion named, pinnable on `provider_attempt`, replayable. */
|
|
147
|
+
usageAccountingPolicy?: UsageAccountingPolicy;
|
|
143
148
|
/** Stable replayable context behavior; ratios are normalized to integer ppm. */
|
|
144
149
|
contextPolicy?: ContextPolicyOverrides;
|
|
145
150
|
schedulerPolicy?: SchedulerPolicy;
|
|
@@ -281,7 +286,24 @@ export declare class RuntimeRunner {
|
|
|
281
286
|
private activePageOutArchive;
|
|
282
287
|
/** Provider continuation emitted after a canonical nested workflow completes. */
|
|
283
288
|
private workflowContinuation;
|
|
289
|
+
/** P4 (0.2.64 Evidence Plane): the resolved route every provider attempt is pinned to —
|
|
290
|
+
* resolved once so evidence identity is byte-stable across the run (C6.3 pins in-run
|
|
291
|
+
* route stability). */
|
|
292
|
+
private readonly providerRoute;
|
|
293
|
+
private readonly usageAccountingPolicy;
|
|
294
|
+
/** P4 §1.1: the active invocation's derived identity = its chain's FIRST effect_id. Tracked
|
|
295
|
+
* across the kernel-recovered retry loop; a fresh call_provider with no pending retry opens
|
|
296
|
+
* a new invocation. */
|
|
297
|
+
private providerRetryPending;
|
|
298
|
+
private activeProviderInvocationId;
|
|
284
299
|
constructor(opts: RuntimeOptions);
|
|
300
|
+
/**
|
|
301
|
+
* P4-S1 (G1): land one provider_attempt evidence record per effect execution. Pure host
|
|
302
|
+
* evidence (B7) — appended AFTER the transport fact exists (success/failure/abort), never
|
|
303
|
+
* consulted for kernel input. The accounting policy id pins only when a measurement exists,
|
|
304
|
+
* so (usage, policy_id) deterministically recomputes the settlement that crossed the wire.
|
|
305
|
+
*/
|
|
306
|
+
private appendProviderAttempt;
|
|
285
307
|
private resolveKernelJournal;
|
|
286
308
|
private createCanonicalRuntime;
|
|
287
309
|
private commitKernelApply;
|
package/dist/runtime/runner.js
CHANGED
|
@@ -19,7 +19,8 @@ import { assertNativeProfile } from "./os-profile.js";
|
|
|
19
19
|
import { classifyProviderError, providerErrorEventFields } from "../providers/provider-error.js";
|
|
20
20
|
import { PayloadStore } from "./payload-store.js";
|
|
21
21
|
import { contextPolicy, normalizeContextPolicy, } from "./context-policy.js";
|
|
22
|
-
import { createProviderRequestPlanForProvider, estimateProviderPromptTokens, measurementForPlan, recordPromptMeasurement, } from "../providers/request-plan.js";
|
|
22
|
+
import { createProviderRequestPlanForProvider, estimateProviderPromptTokens, measurementForPlan, recordPromptMeasurement, resolveProviderRoute, } from "../providers/request-plan.js";
|
|
23
|
+
import { FULL_FOOTPRINT_USAGE_ACCOUNTING_POLICY, providerAttemptToRecord, tryNormalizeProviderUsage, } from "./execution-evidence.js";
|
|
23
24
|
export function schedulerPolicyToKernel(policy) {
|
|
24
25
|
const allowed = new Set([
|
|
25
26
|
"criticalPathWeight", "fanoutWeight", "ageWeight", "tokenCostWeight",
|
|
@@ -121,12 +122,36 @@ export class RuntimeRunner {
|
|
|
121
122
|
activePageOutArchive;
|
|
122
123
|
/** Provider continuation emitted after a canonical nested workflow completes. */
|
|
123
124
|
workflowContinuation = null;
|
|
125
|
+
/** P4 (0.2.64 Evidence Plane): the resolved route every provider attempt is pinned to —
|
|
126
|
+
* resolved once so evidence identity is byte-stable across the run (C6.3 pins in-run
|
|
127
|
+
* route stability). */
|
|
128
|
+
providerRoute;
|
|
129
|
+
usageAccountingPolicy;
|
|
130
|
+
/** P4 §1.1: the active invocation's derived identity = its chain's FIRST effect_id. Tracked
|
|
131
|
+
* across the kernel-recovered retry loop; a fresh call_provider with no pending retry opens
|
|
132
|
+
* a new invocation. */
|
|
133
|
+
providerRetryPending = false;
|
|
134
|
+
activeProviderInvocationId;
|
|
124
135
|
constructor(opts) {
|
|
125
136
|
this.opts = opts;
|
|
126
137
|
const schemaAttempts = opts.workflowSchemaValidationAttempts ?? 2;
|
|
127
138
|
if (!Number.isInteger(schemaAttempts) || schemaAttempts < 1 || schemaAttempts > 16) {
|
|
128
139
|
throw new RangeError("workflowSchemaValidationAttempts must be an integer between 1 and 16");
|
|
129
140
|
}
|
|
141
|
+
this.providerRoute = resolveProviderRoute(opts.provider);
|
|
142
|
+
this.usageAccountingPolicy = opts.usageAccountingPolicy ?? FULL_FOOTPRINT_USAGE_ACCOUNTING_POLICY;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* P4-S1 (G1): land one provider_attempt evidence record per effect execution. Pure host
|
|
146
|
+
* evidence (B7) — appended AFTER the transport fact exists (success/failure/abort), never
|
|
147
|
+
* consulted for kernel input. The accounting policy id pins only when a measurement exists,
|
|
148
|
+
* so (usage, policy_id) deterministically recomputes the settlement that crossed the wire.
|
|
149
|
+
*/
|
|
150
|
+
async appendProviderAttempt(sessionId, attempt) {
|
|
151
|
+
await this.opts.sessionLog.append(sessionId, {
|
|
152
|
+
kind: "provider_attempt",
|
|
153
|
+
...providerAttemptToRecord(attempt, attempt.usage ? this.usageAccountingPolicy.policyId : undefined),
|
|
154
|
+
});
|
|
130
155
|
}
|
|
131
156
|
resolveKernelJournal() {
|
|
132
157
|
const embedded = this.opts.sessionLog;
|
|
@@ -269,6 +294,7 @@ export class RuntimeRunner {
|
|
|
269
294
|
agent_id: this.opts.agentId,
|
|
270
295
|
system_prompt: this.opts.systemPrompt,
|
|
271
296
|
...(attachments ? { attachments } : {}),
|
|
297
|
+
route: this.providerRoute,
|
|
272
298
|
});
|
|
273
299
|
}
|
|
274
300
|
yield* this.execute(req.sessionId, runId, req.goal, req.criteria ?? [], req.extensions, prior.length > 0 ? prior : undefined, midRun, attachments);
|
|
@@ -355,7 +381,7 @@ export class RuntimeRunner {
|
|
|
355
381
|
await this.commitKernelApply(this.activeKernel, this.pendingObservations, { kind: "skill_deactivated", name });
|
|
356
382
|
this.knowledgePushedSkills.delete(name);
|
|
357
383
|
}
|
|
358
|
-
async resolveKernelSuspend(requests, runtime, sessionId) {
|
|
384
|
+
async resolveKernelSuspend(requests, runtime, sessionId, effectId) {
|
|
359
385
|
const approved = [];
|
|
360
386
|
const denied = [];
|
|
361
387
|
const events = [];
|
|
@@ -421,6 +447,8 @@ export class RuntimeRunner {
|
|
|
421
447
|
await this.opts.sessionLog.append(sessionId, {
|
|
422
448
|
kind: "tool_completed",
|
|
423
449
|
turn: runtime.turn(),
|
|
450
|
+
// P4 §1.1: governance-denied completions carry the requesting effect's identity.
|
|
451
|
+
...(effectId !== undefined ? { effect_id: effectId } : {}),
|
|
424
452
|
results: [{
|
|
425
453
|
call_id: requestAction.callId,
|
|
426
454
|
output: `permission denied: ${denyReason}`,
|
|
@@ -638,6 +666,18 @@ export class RuntimeRunner {
|
|
|
638
666
|
break;
|
|
639
667
|
if (action.kind === "call_provider") {
|
|
640
668
|
const providerEffectId = action.effectId;
|
|
669
|
+
// P4 §1.1: invocation identity is derived, never minted — the chain's FIRST effect_id.
|
|
670
|
+
// A provider_error commit armed `providerRetryPending`, so this effect CONTINUES the
|
|
671
|
+
// pending invocation; otherwise it opens a new one. The chain itself lives in the
|
|
672
|
+
// journal (each hop a resolution input → new effect); this is the SessionLog evidence
|
|
673
|
+
// projection of it.
|
|
674
|
+
if (!this.providerRetryPending || this.activeProviderInvocationId === undefined) {
|
|
675
|
+
this.activeProviderInvocationId = providerEffectId;
|
|
676
|
+
}
|
|
677
|
+
this.providerRetryPending = false;
|
|
678
|
+
const invocationId = this.activeProviderInvocationId;
|
|
679
|
+
// Host wall-clock, pure evidence (B7/DEC-2): never crosses into kernel input.
|
|
680
|
+
const attemptStartedAtMs = Date.now();
|
|
641
681
|
const finalToolCalls = [];
|
|
642
682
|
let finalText = "";
|
|
643
683
|
// I5: governance schema-level pre-filter — see Node runner for full rationale.
|
|
@@ -684,7 +724,7 @@ export class RuntimeRunner {
|
|
|
684
724
|
});
|
|
685
725
|
}
|
|
686
726
|
recordedMeasurements.set(providerPlan.fingerprint, promptMeasurement);
|
|
687
|
-
await this.opts.sessionLog.append(sessionId, { kind: "prompt_measured", turn: runtime.turn(), measurement: promptMeasurement });
|
|
727
|
+
await this.opts.sessionLog.append(sessionId, { kind: "prompt_measured", turn: runtime.turn(), measurement: promptMeasurement, effect_id: providerEffectId });
|
|
688
728
|
}
|
|
689
729
|
const reservedPromptTokens = (this.opts.promptBudget?.promptOverheadTokens ?? 0)
|
|
690
730
|
+ (this.opts.promptBudget?.outputReserveTokens ?? 0)
|
|
@@ -693,11 +733,29 @@ export class RuntimeRunner {
|
|
|
693
733
|
&& promptMeasurement.source.kind !== "heuristic"
|
|
694
734
|
&& promptMeasurement.inputTokens + reservedPromptTokens > this.opts.maxTokens;
|
|
695
735
|
if (context.budgetOverflow || measuredOverflow) {
|
|
736
|
+
// P4 §1.2: blocked BEFORE any transport — zero rungs, status rejected. The
|
|
737
|
+
// fingerprint still binds the would-be request to its prompt_measured record (G2).
|
|
738
|
+
await this.appendProviderAttempt(sessionId, {
|
|
739
|
+
effectId: providerEffectId,
|
|
740
|
+
attemptSeq: 1,
|
|
741
|
+
route: this.providerRoute,
|
|
742
|
+
requestFingerprint: providerPlan.fingerprint,
|
|
743
|
+
status: "rejected",
|
|
744
|
+
transportRungs: 0,
|
|
745
|
+
lastErrorClass: "context_overflow",
|
|
746
|
+
startedAtMs: attemptStartedAtMs,
|
|
747
|
+
finishedAtMs: Date.now(),
|
|
748
|
+
wireEvidence: {
|
|
749
|
+
protocol: this.providerRoute.protocol,
|
|
750
|
+
request_fingerprint: providerPlan.fingerprint,
|
|
751
|
+
},
|
|
752
|
+
});
|
|
696
753
|
action = await this.commitKernelAction(runtime, this.pendingObservations, {
|
|
697
754
|
kind: "provider_error", effect_id: providerEffectId,
|
|
698
755
|
message: "provider-visible prompt exceeds the configured context budget",
|
|
699
756
|
error_kind: "context_overflow", retryable: false,
|
|
700
757
|
});
|
|
758
|
+
this.providerRetryPending = action.kind === "call_provider";
|
|
701
759
|
continue;
|
|
702
760
|
}
|
|
703
761
|
const abortSignal = this.abortController?.signal;
|
|
@@ -741,6 +799,23 @@ export class RuntimeRunner {
|
|
|
741
799
|
else {
|
|
742
800
|
const descriptor = this.opts.provider.descriptor?.();
|
|
743
801
|
const providerError = classifyProviderError(descriptor?.provider ?? "unknown", err);
|
|
802
|
+
// P4 §1.2: the transport ladder is exhausted — one attempt record, error CLASS only
|
|
803
|
+
// (B1: never the raw vendor text). The single-shot stream path reports 1 rung.
|
|
804
|
+
await this.appendProviderAttempt(sessionId, {
|
|
805
|
+
effectId: providerEffectId,
|
|
806
|
+
attemptSeq: 1,
|
|
807
|
+
route: this.providerRoute,
|
|
808
|
+
requestFingerprint: providerPlan.fingerprint,
|
|
809
|
+
status: "transport_exhausted",
|
|
810
|
+
transportRungs: 1,
|
|
811
|
+
lastErrorClass: providerError.kind,
|
|
812
|
+
startedAtMs: attemptStartedAtMs,
|
|
813
|
+
finishedAtMs: Date.now(),
|
|
814
|
+
wireEvidence: {
|
|
815
|
+
protocol: this.providerRoute.protocol,
|
|
816
|
+
request_fingerprint: providerPlan.fingerprint,
|
|
817
|
+
},
|
|
818
|
+
});
|
|
744
819
|
// Reactive recovery is now a kernel decision. Forward the raw provider error and
|
|
745
820
|
// dispatch whatever the kernel returns: `call_provider` to retry with a freshly
|
|
746
821
|
// compacted context, or `done` to terminate with an honest `ContextOverflow`. The
|
|
@@ -752,6 +827,9 @@ export class RuntimeRunner {
|
|
|
752
827
|
message: providerError.message,
|
|
753
828
|
...providerErrorEventFields(providerError),
|
|
754
829
|
});
|
|
830
|
+
// P4 §1.1: a kernel-recovered retry CONTINUES this invocation (the journal holds the
|
|
831
|
+
// causation hop); a terminal closes it.
|
|
832
|
+
this.providerRetryPending = action.kind === "call_provider";
|
|
755
833
|
// Withholding (query.ts parity): surface the raw provider error only when the kernel
|
|
756
834
|
// could NOT recover (it returned a terminal). On a recovered retry (`call_provider`)
|
|
757
835
|
// the error stays hidden. `continue` re-enters the loop: a recovered turn persists its
|
|
@@ -765,6 +843,21 @@ export class RuntimeRunner {
|
|
|
765
843
|
}
|
|
766
844
|
// #2-B-ii: stream aborted (preempt/interrupt) via the break path — end the turn now.
|
|
767
845
|
if (abortSignal?.aborted) {
|
|
846
|
+
// P4 §1.2: host cancellation mid-stream — the attempt is evidence too.
|
|
847
|
+
await this.appendProviderAttempt(sessionId, {
|
|
848
|
+
effectId: providerEffectId,
|
|
849
|
+
attemptSeq: 1,
|
|
850
|
+
route: this.providerRoute,
|
|
851
|
+
requestFingerprint: providerPlan.fingerprint,
|
|
852
|
+
status: "aborted",
|
|
853
|
+
transportRungs: 1,
|
|
854
|
+
startedAtMs: attemptStartedAtMs,
|
|
855
|
+
finishedAtMs: Date.now(),
|
|
856
|
+
wireEvidence: {
|
|
857
|
+
protocol: this.providerRoute.protocol,
|
|
858
|
+
request_fingerprint: providerPlan.fingerprint,
|
|
859
|
+
},
|
|
860
|
+
});
|
|
768
861
|
action = await this.commitKernelAction(runtime, this.pendingObservations, {
|
|
769
862
|
kind: "cancel_operation",
|
|
770
863
|
reason: this.cancellationReason ?? "user",
|
|
@@ -778,22 +871,60 @@ export class RuntimeRunner {
|
|
|
778
871
|
toolCalls: finalToolCalls,
|
|
779
872
|
tokenCount: turnOutputTokens || turnTokens || undefined,
|
|
780
873
|
};
|
|
874
|
+
// P4 §2: assemble the measurement from the exact numbers that cross the boundary today
|
|
875
|
+
// (input/output turn counters + the cache split). An invalid frame degrades to no
|
|
876
|
+
// measurement (evidence never breaks a run); the settlement then falls back to the raw
|
|
877
|
+
// counts below.
|
|
878
|
+
const attemptUsage = (turnInputTokens > 0 || turnOutputTokens > 0)
|
|
879
|
+
? tryNormalizeProviderUsage({
|
|
880
|
+
inputTokens: turnInputTokens,
|
|
881
|
+
outputTokens: turnOutputTokens,
|
|
882
|
+
...(turnCacheReadTokens > 0 ? { cacheReadInputTokens: turnCacheReadTokens } : {}),
|
|
883
|
+
...(turnCacheCreationTokens > 0 ? { cacheCreationInputTokens: turnCacheCreationTokens } : {}),
|
|
884
|
+
})
|
|
885
|
+
: undefined;
|
|
886
|
+
const settlement = attemptUsage ? this.usageAccountingPolicy.settle(attemptUsage) : undefined;
|
|
781
887
|
const providerEvent = {
|
|
782
888
|
kind: "provider_result",
|
|
783
889
|
effect_id: providerEffectId,
|
|
784
890
|
message: messageToKernelMessage(assistantMessage),
|
|
785
|
-
|
|
786
|
-
|
|
891
|
+
// P4-S2: observed_* now comes from the pinned policy's settlement of the measurement.
|
|
892
|
+
// Under the default full-footprint policy these are provably the numbers the runner
|
|
893
|
+
// has always fed (inputTokens/outputTokens verbatim under the same >0 gates).
|
|
894
|
+
...(turnInputTokens > 0 ? { observed_input_tokens: settlement?.observed_input_tokens ?? turnInputTokens } : {}),
|
|
895
|
+
...(turnOutputTokens > 0 ? { observed_output_tokens: settlement?.observed_output_tokens ?? turnOutputTokens } : {}),
|
|
787
896
|
...(turnStopReason ? { stop_reason: turnStopReason } : {}),
|
|
788
897
|
};
|
|
789
|
-
|
|
898
|
+
// P4-S1: land the attempt evidence BEFORE the kernel resolution commits — the record
|
|
899
|
+
// describes the transport fact, which exists regardless of what the kernel decides next.
|
|
790
900
|
const providerReplay = peekProviderReplay(this.opts.provider, finalText, finalToolCalls);
|
|
901
|
+
const wireEvidence = {
|
|
902
|
+
protocol: this.providerRoute.protocol,
|
|
903
|
+
request_fingerprint: providerPlan.fingerprint,
|
|
904
|
+
...(providerReplay !== undefined ? { replay_state: providerReplay } : {}),
|
|
905
|
+
};
|
|
906
|
+
await this.appendProviderAttempt(sessionId, {
|
|
907
|
+
effectId: providerEffectId,
|
|
908
|
+
attemptSeq: 1,
|
|
909
|
+
route: this.providerRoute,
|
|
910
|
+
requestFingerprint: providerPlan.fingerprint,
|
|
911
|
+
status: "success",
|
|
912
|
+
transportRungs: 1,
|
|
913
|
+
startedAtMs: attemptStartedAtMs,
|
|
914
|
+
finishedAtMs: Date.now(),
|
|
915
|
+
...(attemptUsage !== undefined ? { usage: attemptUsage } : {}),
|
|
916
|
+
wireEvidence,
|
|
917
|
+
});
|
|
918
|
+
action = await this.commitKernelAction(runtime, this.pendingObservations, providerEvent);
|
|
791
919
|
await this.opts.sessionLog.append(sessionId, buildLlmCompletedEvent({
|
|
792
920
|
turn: runtime.turn(),
|
|
793
921
|
content: finalText,
|
|
794
922
|
tokenCount: turnOutputTokens || turnTokens || undefined,
|
|
795
923
|
toolCalls: finalToolCalls,
|
|
796
924
|
providerReplay,
|
|
925
|
+
effectId: providerEffectId,
|
|
926
|
+
invocationId,
|
|
927
|
+
wireEvidence,
|
|
797
928
|
}));
|
|
798
929
|
// P0-C: per-turn tool-gating telemetry. `activeSkill` reflects the skill in effect GOING INTO
|
|
799
930
|
// this turn; a `skill` call here only takes effect next turn — emit first, then advance.
|
|
@@ -823,7 +954,7 @@ export class RuntimeRunner {
|
|
|
823
954
|
}
|
|
824
955
|
}
|
|
825
956
|
else if (action.kind === "request_approval") {
|
|
826
|
-
const resolved = await this.resolveKernelSuspend(action.requests, runtime, sessionId);
|
|
957
|
+
const resolved = await this.resolveKernelSuspend(action.requests, runtime, sessionId, action.effectId);
|
|
827
958
|
for (const evt of resolved.events)
|
|
828
959
|
yield evt;
|
|
829
960
|
action = await this.commitKernelAction(runtime, this.pendingObservations, {
|
|
@@ -1076,6 +1207,8 @@ export class RuntimeRunner {
|
|
|
1076
1207
|
await this.opts.sessionLog.append(sessionId, {
|
|
1077
1208
|
kind: "tool_completed",
|
|
1078
1209
|
turn: runtime.turn(),
|
|
1210
|
+
// P4 §1.1: the completion carries the execute_tools effect's identity.
|
|
1211
|
+
effect_id: toolEffectId,
|
|
1079
1212
|
results: toolResults.map(r => ({
|
|
1080
1213
|
call_id: r.callId,
|
|
1081
1214
|
output: r.output,
|
|
@@ -1530,6 +1663,7 @@ export class RuntimeRunner {
|
|
|
1530
1663
|
goal: `workflow:${spec.nodes.length} nodes`,
|
|
1531
1664
|
criteria: [],
|
|
1532
1665
|
...(this.opts.agentId ? { agent_id: this.opts.agentId } : {}),
|
|
1666
|
+
route: this.providerRoute,
|
|
1533
1667
|
});
|
|
1534
1668
|
await this.initializeWorkflowKernel(sessionId, runId);
|
|
1535
1669
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { ProviderReplay, ToolCall, ToolErrorKind } from "../types.js";
|
|
1
|
+
import type { ProviderReplay, ProviderWireEvidence, ToolCall, ToolErrorKind } from "../types.js";
|
|
2
2
|
import type { MemoryRecall, MemoryScope } from "../memory/index.js";
|
|
3
3
|
import type { KernelPrimitive } from "./kernel-event-log.js";
|
|
4
4
|
import type { KernelJournal } from "./kernel-journal.js";
|
|
5
|
-
import type { RecordedPromptMeasurement } from "../providers/request-plan.js";
|
|
5
|
+
import type { NormalizedProviderUsage, RecordedPromptMeasurement, ResolvedProviderRoute } from "../providers/request-plan.js";
|
|
6
6
|
export type RollbackReason = {
|
|
7
7
|
kind: "fatal_tool_error";
|
|
8
8
|
tool_name: string;
|
|
@@ -22,6 +22,25 @@ export type RollbackReason = {
|
|
|
22
22
|
kind: "malformed_replay";
|
|
23
23
|
reason: string;
|
|
24
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* P4 §3 mirror: the SessionLog wire payload of a ProviderAttempt (P4 §1.2 fields, flattened,
|
|
27
|
+
* snake_case). Nested objects keep their native shape. The kernel-minted effect_id joins this
|
|
28
|
+
* host evidence to the journal effect chain 1:1 (C6); step_seq NEVER enters SessionLog (P2 §4).
|
|
29
|
+
*/
|
|
30
|
+
export interface ProviderAttemptRecord {
|
|
31
|
+
effect_id: string;
|
|
32
|
+
attempt_seq: number;
|
|
33
|
+
route: ResolvedProviderRoute;
|
|
34
|
+
request_fingerprint: string;
|
|
35
|
+
status: "success" | "transport_exhausted" | "aborted" | "rejected";
|
|
36
|
+
transport_rungs: number;
|
|
37
|
+
last_error_class?: string;
|
|
38
|
+
started_at_ms: number;
|
|
39
|
+
finished_at_ms: number;
|
|
40
|
+
usage?: NormalizedProviderUsage;
|
|
41
|
+
wire_evidence?: ProviderWireEvidence;
|
|
42
|
+
accounting_policy_id?: string;
|
|
43
|
+
}
|
|
25
44
|
export type SessionEvent = {
|
|
26
45
|
kind: "run_started";
|
|
27
46
|
run_id: string;
|
|
@@ -30,6 +49,7 @@ export type SessionEvent = {
|
|
|
30
49
|
agent_id?: string;
|
|
31
50
|
system_prompt?: string;
|
|
32
51
|
attachments?: import("../types.js").ContentPart[];
|
|
52
|
+
route?: ResolvedProviderRoute;
|
|
33
53
|
} | {
|
|
34
54
|
kind: "llm_completed";
|
|
35
55
|
turn: number;
|
|
@@ -37,11 +57,17 @@ export type SessionEvent = {
|
|
|
37
57
|
token_count?: number;
|
|
38
58
|
tool_calls: ToolCall[];
|
|
39
59
|
provider_replay?: ProviderReplay;
|
|
60
|
+
effect_id?: string;
|
|
61
|
+
invocation_id?: string;
|
|
62
|
+
wire_evidence?: ProviderWireEvidence;
|
|
40
63
|
} | {
|
|
41
64
|
kind: "prompt_measured";
|
|
42
65
|
turn: number;
|
|
43
66
|
measurement: RecordedPromptMeasurement;
|
|
44
|
-
|
|
67
|
+
effect_id?: string;
|
|
68
|
+
} | ({
|
|
69
|
+
kind: "provider_attempt";
|
|
70
|
+
} & ProviderAttemptRecord) | {
|
|
45
71
|
kind: "tool_requested";
|
|
46
72
|
turn: number;
|
|
47
73
|
calls: ToolCall[];
|
|
@@ -59,6 +85,7 @@ export type SessionEvent = {
|
|
|
59
85
|
blocks: Record<string, unknown>[];
|
|
60
86
|
};
|
|
61
87
|
}>;
|
|
88
|
+
effect_id?: string;
|
|
62
89
|
} | {
|
|
63
90
|
kind: "tool_argument_repaired";
|
|
64
91
|
turn: number;
|
|
@@ -98,6 +125,18 @@ export type SessionEvent = {
|
|
|
98
125
|
tier_hint?: string;
|
|
99
126
|
message_count?: number;
|
|
100
127
|
archive_ref?: string;
|
|
128
|
+
} | {
|
|
129
|
+
kind: "semantic_archive_pending";
|
|
130
|
+
effect_id: string;
|
|
131
|
+
action?: string;
|
|
132
|
+
} | {
|
|
133
|
+
kind: "semantic_archive_completed";
|
|
134
|
+
effect_id: string;
|
|
135
|
+
record_id: string;
|
|
136
|
+
} | {
|
|
137
|
+
kind: "semantic_archive_failed";
|
|
138
|
+
effect_id: string;
|
|
139
|
+
error: string;
|
|
101
140
|
} | {
|
|
102
141
|
kind: "page_in";
|
|
103
142
|
turn: number;
|
|
@@ -228,6 +267,17 @@ export type SessionEvent = {
|
|
|
228
267
|
turn: number;
|
|
229
268
|
record_id: string;
|
|
230
269
|
error: string;
|
|
270
|
+
} | {
|
|
271
|
+
kind: "memory_write_failed";
|
|
272
|
+
turn: number;
|
|
273
|
+
record_id: string;
|
|
274
|
+
error: string;
|
|
275
|
+
} | {
|
|
276
|
+
kind: "memory_query_failed";
|
|
277
|
+
turn: number;
|
|
278
|
+
scope: MemoryScope;
|
|
279
|
+
query: string;
|
|
280
|
+
error: string;
|
|
231
281
|
} | {
|
|
232
282
|
kind: "memory_retrieval_result";
|
|
233
283
|
hits: MemoryRecall[];
|
|
@@ -278,7 +328,39 @@ export type SessionEvent = {
|
|
|
278
328
|
kind: "summary_upgraded";
|
|
279
329
|
compressed_seq: number;
|
|
280
330
|
summary: string;
|
|
331
|
+
} | {
|
|
332
|
+
kind: "group_member_joined";
|
|
333
|
+
session_id: string;
|
|
334
|
+
role?: string;
|
|
335
|
+
member_kind?: "peer" | "vehicle";
|
|
336
|
+
} | {
|
|
337
|
+
kind: "group_budget_charged";
|
|
338
|
+
tokens: number;
|
|
339
|
+
subagents: number;
|
|
340
|
+
rounds?: number;
|
|
341
|
+
} | {
|
|
342
|
+
kind: "round_started";
|
|
343
|
+
/** 1-based round number within the loop. */
|
|
344
|
+
round: number;
|
|
345
|
+
goal: string;
|
|
346
|
+
} | {
|
|
347
|
+
kind: "round_paced";
|
|
348
|
+
round: number;
|
|
349
|
+
action: "continue" | "sleep" | "stop";
|
|
350
|
+
delay_ms?: number;
|
|
351
|
+
/** Absolute wake time for sleep — lets a stateless host re-arm from the log alone. */
|
|
352
|
+
wake_at_ms?: number;
|
|
353
|
+
reason: string;
|
|
354
|
+
coerced_from?: string;
|
|
281
355
|
};
|
|
356
|
+
export type SessionEventKind = SessionEvent["kind"];
|
|
357
|
+
/**
|
|
358
|
+
* The registered session-event vocabulary (F9 / S3, P7-S4). This list must equal the Node SDK's
|
|
359
|
+
* `SESSION_EVENT_KINDS` — the cross-SDK manifest fixture
|
|
360
|
+
* `tests/fixtures/sdk-conformance/canonical/session-event-vocabulary.json` pins both, and any
|
|
361
|
+
* same-commit desync turns conformance red. Declared in `SessionEvent` union order.
|
|
362
|
+
*/
|
|
363
|
+
export declare const SESSION_EVENT_KINDS: readonly ["run_started", "llm_completed", "prompt_measured", "provider_attempt", "tool_requested", "tool_completed", "tool_argument_repaired", "tool_denied", "permission_requested", "permission_resolved", "compressed", "page_out", "semantic_archive_pending", "semantic_archive_completed", "semantic_archive_failed", "page_in", "rollbacked", "capability_changed", "context_renewed", "suspended", "resumed", "tool_gated", "signal_delivery_disposed", "budget_exceeded", "budget_usage_reported", "operation_cancelled", "milestone_advanced", "milestone_blocked", "checkpoint_taken", "entropy_sample", "entropy_alert", "agent_process_changed", "memory_written", "memory_queried", "memory_validation_failed", "memory_write_failed", "memory_query_failed", "memory_retrieval_result", "workflow_node_completed", "workflow_nodes_submitted", "workflow_batch_spawned", "workflow_completed", "kernel_observation", "run_terminal", "summary_upgraded", "group_member_joined", "group_budget_charged", "round_started", "round_paced"];
|
|
282
364
|
/**
|
|
283
365
|
* The business-projection log (spec §9.2): run started/terminal, stream events, observations,
|
|
284
366
|
* provider/tool presentation, and audit metadata. Canonical durable records live exclusively in
|
|
@@ -313,3 +395,11 @@ export declare class InMemorySessionLog implements SessionLog {
|
|
|
313
395
|
}>>;
|
|
314
396
|
latestSeq(sessionId: string): Promise<number>;
|
|
315
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* P4 §3 teeth, wasm mirror of the node/python persisted-record validator. The wasm SessionLog is
|
|
400
|
+
* in-memory only (no durable read path where validation would otherwise live), so the teeth are
|
|
401
|
+
* exported for hosts that bridge these events to durable storage — and for the conformance
|
|
402
|
+
* runner, which pins the rejection contract (`invalid_provider_attempt`) across all three
|
|
403
|
+
* JSON-projection SDKs. Same checks and message wording as node `assertCanonicalProviderAttempt`.
|
|
404
|
+
*/
|
|
405
|
+
export declare function assertCanonicalProviderAttemptRecord(event: Record<string, unknown>): void;
|
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
import { primitiveForKind } from "./kernel-event-log.js";
|
|
2
2
|
import { InMemoryKernelJournal } from "./kernel-journal.js";
|
|
3
|
+
/**
|
|
4
|
+
* The registered session-event vocabulary (F9 / S3, P7-S4). This list must equal the Node SDK's
|
|
5
|
+
* `SESSION_EVENT_KINDS` — the cross-SDK manifest fixture
|
|
6
|
+
* `tests/fixtures/sdk-conformance/canonical/session-event-vocabulary.json` pins both, and any
|
|
7
|
+
* same-commit desync turns conformance red. Declared in `SessionEvent` union order.
|
|
8
|
+
*/
|
|
9
|
+
export const SESSION_EVENT_KINDS = [
|
|
10
|
+
"run_started",
|
|
11
|
+
"llm_completed",
|
|
12
|
+
"prompt_measured",
|
|
13
|
+
"provider_attempt",
|
|
14
|
+
"tool_requested",
|
|
15
|
+
"tool_completed",
|
|
16
|
+
"tool_argument_repaired",
|
|
17
|
+
"tool_denied",
|
|
18
|
+
"permission_requested",
|
|
19
|
+
"permission_resolved",
|
|
20
|
+
"compressed",
|
|
21
|
+
"page_out",
|
|
22
|
+
"semantic_archive_pending",
|
|
23
|
+
"semantic_archive_completed",
|
|
24
|
+
"semantic_archive_failed",
|
|
25
|
+
"page_in",
|
|
26
|
+
"rollbacked",
|
|
27
|
+
"capability_changed",
|
|
28
|
+
"context_renewed",
|
|
29
|
+
"suspended",
|
|
30
|
+
"resumed",
|
|
31
|
+
"tool_gated",
|
|
32
|
+
"signal_delivery_disposed",
|
|
33
|
+
"budget_exceeded",
|
|
34
|
+
"budget_usage_reported",
|
|
35
|
+
"operation_cancelled",
|
|
36
|
+
"milestone_advanced",
|
|
37
|
+
"milestone_blocked",
|
|
38
|
+
"checkpoint_taken",
|
|
39
|
+
"entropy_sample",
|
|
40
|
+
"entropy_alert",
|
|
41
|
+
"agent_process_changed",
|
|
42
|
+
"memory_written",
|
|
43
|
+
"memory_queried",
|
|
44
|
+
"memory_validation_failed",
|
|
45
|
+
"memory_write_failed",
|
|
46
|
+
"memory_query_failed",
|
|
47
|
+
"memory_retrieval_result",
|
|
48
|
+
"workflow_node_completed",
|
|
49
|
+
"workflow_nodes_submitted",
|
|
50
|
+
"workflow_batch_spawned",
|
|
51
|
+
"workflow_completed",
|
|
52
|
+
"kernel_observation",
|
|
53
|
+
"run_terminal",
|
|
54
|
+
"summary_upgraded",
|
|
55
|
+
"group_member_joined",
|
|
56
|
+
"group_budget_charged",
|
|
57
|
+
"round_started",
|
|
58
|
+
"round_paced",
|
|
59
|
+
];
|
|
60
|
+
const _vocabularyCoversUnion = true;
|
|
61
|
+
void _vocabularyCoversUnion;
|
|
3
62
|
/**
|
|
4
63
|
* **Single-isolate dev/test implementation** of both capabilities (spec §9.4: one class may
|
|
5
64
|
* implement several capabilities; the *interfaces* stay separate). Its `KernelJournal` half is
|
|
@@ -40,3 +99,75 @@ export class InMemorySessionLog {
|
|
|
40
99
|
return (this.seqCounters.get(sessionId) ?? 0) - 1;
|
|
41
100
|
}
|
|
42
101
|
}
|
|
102
|
+
const PROVIDER_ATTEMPT_KEYS = new Set([
|
|
103
|
+
"kind", "effect_id", "attempt_seq", "route", "request_fingerprint", "status", "transport_rungs",
|
|
104
|
+
"last_error_class", "started_at_ms", "finished_at_ms", "usage", "wire_evidence", "accounting_policy_id",
|
|
105
|
+
]);
|
|
106
|
+
const PROVIDER_ATTEMPT_STATUSES = new Set(["success", "transport_exhausted", "aborted", "rejected"]);
|
|
107
|
+
const WIRE_EVIDENCE_KEYS = new Set(["protocol", "request_fingerprint", "response_id", "raw_usage", "replay_state"]);
|
|
108
|
+
function assertCanonicalRoute(value) {
|
|
109
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
110
|
+
throw new Error("route must be an object");
|
|
111
|
+
const route = value;
|
|
112
|
+
for (const field of ["routeId", "provider", "protocol", "model", "adapterVersion", "capabilitiesRef"]) {
|
|
113
|
+
if (typeof route[field] !== "string" || route[field].length === 0) {
|
|
114
|
+
throw new Error(`route ${field} must be a non-empty string`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!route.endpoint || typeof route.endpoint !== "object" || Array.isArray(route.endpoint)) {
|
|
118
|
+
throw new Error("route endpoint must be an object");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function assertCanonicalWireEvidence(value) {
|
|
122
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
123
|
+
throw new Error("wire evidence must be an object");
|
|
124
|
+
const evidence = value;
|
|
125
|
+
for (const key of Object.keys(evidence))
|
|
126
|
+
if (!WIRE_EVIDENCE_KEYS.has(key))
|
|
127
|
+
throw new Error(`wire evidence has unknown field ${key}`);
|
|
128
|
+
if (typeof evidence.protocol !== "string" || evidence.protocol.length === 0)
|
|
129
|
+
throw new Error("wire evidence protocol is required");
|
|
130
|
+
// G2: the fingerprint is mandatory non-empty — it is what binds this evidence to a request plan.
|
|
131
|
+
if (typeof evidence.request_fingerprint !== "string" || evidence.request_fingerprint.length === 0) {
|
|
132
|
+
throw new Error("wire evidence request_fingerprint is required");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* P4 §3 teeth, wasm mirror of the node/python persisted-record validator. The wasm SessionLog is
|
|
137
|
+
* in-memory only (no durable read path where validation would otherwise live), so the teeth are
|
|
138
|
+
* exported for hosts that bridge these events to durable storage — and for the conformance
|
|
139
|
+
* runner, which pins the rejection contract (`invalid_provider_attempt`) across all three
|
|
140
|
+
* JSON-projection SDKs. Same checks and message wording as node `assertCanonicalProviderAttempt`.
|
|
141
|
+
*/
|
|
142
|
+
export function assertCanonicalProviderAttemptRecord(event) {
|
|
143
|
+
for (const key of Object.keys(event))
|
|
144
|
+
if (!PROVIDER_ATTEMPT_KEYS.has(key))
|
|
145
|
+
throw new Error(`provider_attempt has unknown field ${key}`);
|
|
146
|
+
if (typeof event.effect_id !== "string" || event.effect_id.length === 0)
|
|
147
|
+
throw new Error("provider_attempt effect_id is required");
|
|
148
|
+
if (!Number.isInteger(event.attempt_seq) || event.attempt_seq < 1)
|
|
149
|
+
throw new Error("provider_attempt attempt_seq must be a positive integer");
|
|
150
|
+
assertCanonicalRoute(event.route);
|
|
151
|
+
if (typeof event.request_fingerprint !== "string" || event.request_fingerprint.length === 0) {
|
|
152
|
+
throw new Error("provider_attempt request_fingerprint is required");
|
|
153
|
+
}
|
|
154
|
+
if (typeof event.status !== "string" || !PROVIDER_ATTEMPT_STATUSES.has(event.status)) {
|
|
155
|
+
throw new Error("provider_attempt status must be success|transport_exhausted|aborted|rejected");
|
|
156
|
+
}
|
|
157
|
+
if (!Number.isInteger(event.transport_rungs) || event.transport_rungs < 0) {
|
|
158
|
+
throw new Error("provider_attempt transport_rungs must be a non-negative integer");
|
|
159
|
+
}
|
|
160
|
+
if (event.last_error_class !== undefined && typeof event.last_error_class !== "string") {
|
|
161
|
+
throw new Error("provider_attempt last_error_class must be a string");
|
|
162
|
+
}
|
|
163
|
+
for (const field of ["started_at_ms", "finished_at_ms"]) {
|
|
164
|
+
if (typeof event[field] !== "number" || !Number.isFinite(event[field])) {
|
|
165
|
+
throw new Error(`provider_attempt ${field} must be a finite number`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (event.wire_evidence !== undefined)
|
|
169
|
+
assertCanonicalWireEvidence(event.wire_evidence);
|
|
170
|
+
if (event.accounting_policy_id !== undefined && typeof event.accounting_policy_id !== "string") {
|
|
171
|
+
throw new Error("provider_attempt accounting_policy_id must be a string");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message, ProviderReplay, ToolCall } from "../types.js";
|
|
1
|
+
import type { Message, ProviderReplay, ProviderWireEvidence, ToolCall } from "../types.js";
|
|
2
2
|
import type { SessionEvent } from "./session-log.js";
|
|
3
3
|
import type { WorkflowNodeStatus } from "./types/agent.js";
|
|
4
4
|
export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay-sanitize.js";
|
|
@@ -27,6 +27,9 @@ export declare function buildLlmCompletedEvent(input: {
|
|
|
27
27
|
tokenCount?: number;
|
|
28
28
|
toolCalls: ToolCall[];
|
|
29
29
|
providerReplay?: ProviderReplay;
|
|
30
|
+
effectId?: string;
|
|
31
|
+
invocationId?: string;
|
|
32
|
+
wireEvidence?: ProviderWireEvidence;
|
|
30
33
|
}): Extract<SessionEvent, {
|
|
31
34
|
kind: "llm_completed";
|
|
32
35
|
}>;
|
|
@@ -21,6 +21,11 @@ export function normalizeLlmCompleted(event, maxBytes) {
|
|
|
21
21
|
tool_calls: toolCalls,
|
|
22
22
|
token_count: event.token_count ?? estimateTokenCount(content),
|
|
23
23
|
...(providerReplay ? { provider_replay: providerReplay } : {}),
|
|
24
|
+
// P4 §3: the evidence-plane fields ride along verbatim — recovery never reads them
|
|
25
|
+
// (SessionLog is evidence, not authority), so normalize must neither synthesize nor drop them.
|
|
26
|
+
...(event.effect_id !== undefined ? { effect_id: event.effect_id } : {}),
|
|
27
|
+
...(event.invocation_id !== undefined ? { invocation_id: event.invocation_id } : {}),
|
|
28
|
+
...(event.wire_evidence !== undefined ? { wire_evidence: event.wire_evidence } : {}),
|
|
24
29
|
};
|
|
25
30
|
}
|
|
26
31
|
export function repairEventsForRecovery(events, maxBytes) {
|
|
@@ -38,6 +43,9 @@ export function buildLlmCompletedEvent(input) {
|
|
|
38
43
|
tool_calls: input.toolCalls ?? [],
|
|
39
44
|
token_count: input.tokenCount,
|
|
40
45
|
provider_replay: input.providerReplay,
|
|
46
|
+
...(input.effectId !== undefined ? { effect_id: input.effectId } : {}),
|
|
47
|
+
...(input.invocationId !== undefined ? { invocation_id: input.invocationId } : {}),
|
|
48
|
+
...(input.wireEvidence !== undefined ? { wire_evidence: input.wireEvidence } : {}),
|
|
41
49
|
});
|
|
42
50
|
}
|
|
43
51
|
export function buildRunTerminalEvent(input) {
|
package/dist/types.d.ts
CHANGED
|
@@ -273,6 +273,21 @@ export interface ProviderReplay {
|
|
|
273
273
|
native_message?: unknown;
|
|
274
274
|
tool_calls?: unknown[];
|
|
275
275
|
}
|
|
276
|
+
/** P3 §3.3 (D1) mirror of the node ProviderWireEvidence — SessionLog wire evidence bundle. */
|
|
277
|
+
export interface ProviderWireEvidence {
|
|
278
|
+
protocol: ProviderProtocol;
|
|
279
|
+
/** → ProviderRequestPlan.fingerprint. Mandatory non-empty (G2). */
|
|
280
|
+
request_fingerprint: string;
|
|
281
|
+
response_id?: string;
|
|
282
|
+
/** BoundedJson semantics: producers truncate to ≤4KB. */
|
|
283
|
+
raw_usage?: unknown;
|
|
284
|
+
replay_state?: ProviderReplay;
|
|
285
|
+
}
|
|
286
|
+
/** P4 §1.2 mirror: host-observed transport facts for one provider execution. */
|
|
287
|
+
export interface ProviderTransportTelemetry {
|
|
288
|
+
rungs: number;
|
|
289
|
+
responseId?: string;
|
|
290
|
+
}
|
|
276
291
|
export interface LLMProvider {
|
|
277
292
|
createRunState?(): ProviderRunState;
|
|
278
293
|
runtimePolicy?(): {
|
|
@@ -291,6 +306,8 @@ export interface LLMProvider {
|
|
|
291
306
|
};
|
|
292
307
|
peekProviderReplay?(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
|
|
293
308
|
seedProviderReplay?(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
|
|
309
|
+
/** P4-S1 mirror: transport facts of the most recent execution (host evidence only, B7). */
|
|
310
|
+
peekTransportTelemetry?(): ProviderTransportTelemetry | undefined;
|
|
294
311
|
countTokens?(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<PromptMeasurement>;
|
|
295
312
|
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
296
313
|
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState,
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/wasm",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.64",
|
|
4
4
|
"description": "DeepStrike WASM SDK — browser, Cloudflare Workers, Deno Deploy",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "dist/index.js",
|
|
7
8
|
"types": "dist/index.d.ts",
|
|
@@ -13,10 +14,10 @@
|
|
|
13
14
|
"build:wasm": "wasm-pack build ../crates/deepstrike-wasm --target bundler --out-dir ../../wasm/pkg",
|
|
14
15
|
"build": "tsc",
|
|
15
16
|
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
|
16
|
-
"test:canonical-binding": "wasm-pack build ../crates/deepstrike-wasm --target nodejs --dev --out-dir ../../wasm/pkg-node && node tests/canonical-binding.node.cjs"
|
|
17
|
+
"test:canonical-binding": "wasm-pack build ../crates/deepstrike-wasm --target nodejs --dev --out-dir ../../wasm/pkg-node && node tests/canonical-binding.node.cjs && node tests/golden-fixtures-sweep.node.cjs"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@deepstrike/wasm-kernel": "0.2.
|
|
20
|
+
"@deepstrike/wasm-kernel": "0.2.64"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@types/jest": "^30.0.0",
|