@deepstrike/sdk 0.2.61 → 0.2.63
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 +10 -4
- package/dist/index.js +7 -1
- package/dist/kernel.d.ts +4 -0
- package/dist/providers/anthropic.d.ts +5 -1
- package/dist/providers/anthropic.js +15 -0
- package/dist/providers/gemini.d.ts +4 -1
- package/dist/providers/gemini.js +15 -1
- package/dist/providers/ollama.d.ts +4 -1
- package/dist/providers/ollama.js +7 -0
- package/dist/providers/openai-responses.d.ts +4 -1
- package/dist/providers/openai-responses.js +16 -0
- package/dist/providers/openai.d.ts +4 -1
- package/dist/providers/openai.js +13 -0
- package/dist/providers/request-plan.d.ts +40 -0
- package/dist/providers/request-plan.js +61 -0
- package/dist/runtime/canonical-kernel-step.d.ts +2 -2
- package/dist/runtime/canonical-kernel-step.js +180 -232
- package/dist/runtime/execution-evidence.d.ts +116 -0
- package/dist/runtime/execution-evidence.js +51 -0
- package/dist/runtime/kernel-doctor.d.ts +36 -0
- package/dist/runtime/kernel-doctor.js +60 -0
- package/dist/runtime/kernel-step.d.ts +13 -4
- package/dist/runtime/kernel-step.js +22 -2
- package/dist/runtime/runner.d.ts +25 -0
- package/dist/runtime/runner.js +235 -49
- package/dist/runtime/session-log.d.ts +32 -3
- package/dist/runtime/session-log.js +144 -0
- package/dist/runtime/session-repair.d.ts +8 -1
- package/dist/runtime/session-repair.js +10 -0
- package/dist/types.d.ts +32 -0
- package/package.json +3 -2
|
@@ -41,239 +41,186 @@ export function canonicalUnsupportedEffectResolution(effectId, effectKind) {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
/** The only ABI-v3 planned-step → Node host-action projection. */
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const finalMessage = asObject(result.final_message);
|
|
55
|
-
const pace = asObject(result.pace_decision);
|
|
56
|
-
if (Object.keys(finalMessage).length > 0) {
|
|
57
|
-
return {
|
|
58
|
-
kind: "done",
|
|
59
|
-
effectId: "",
|
|
60
|
-
result: {
|
|
61
|
-
termination,
|
|
62
|
-
turnsUsed,
|
|
63
|
-
totalTokensUsed: totalUsageTokens(terminal),
|
|
64
|
-
finalMessage: {
|
|
65
|
-
role: String(finalMessage.role ?? "assistant"),
|
|
66
|
-
content: String(finalMessage.content ?? ""),
|
|
67
|
-
toolCalls: (Array.isArray(finalMessage.tool_calls) ? finalMessage.tool_calls : [])
|
|
68
|
-
.map(value => {
|
|
69
|
-
const call = asObject(value);
|
|
70
|
-
return {
|
|
71
|
-
id: String(call.call_id ?? ""),
|
|
72
|
-
name: String(call.name ?? ""),
|
|
73
|
-
arguments: JSON.stringify(call.arguments ?? {}),
|
|
74
|
-
};
|
|
75
|
-
}),
|
|
76
|
-
},
|
|
77
|
-
...(Object.keys(pace).length > 0
|
|
78
|
-
? {
|
|
79
|
-
paceDecision: {
|
|
80
|
-
action: String(pace.action ?? "stop"),
|
|
81
|
-
...(pace.delay_ms !== undefined ? { delayMs: Number(pace.delay_ms) } : {}),
|
|
82
|
-
reason: String(pace.reason ?? ""),
|
|
83
|
-
...(pace.coerced_from ? { coercedFrom: String(pace.coerced_from) } : {}),
|
|
84
|
-
},
|
|
85
|
-
}
|
|
86
|
-
: {}),
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
else if (terminal.kind === "workflow") {
|
|
92
|
-
const outcome = asObject(terminal.outcome);
|
|
93
|
-
termination = String(outcome.status ?? "completed");
|
|
94
|
-
}
|
|
95
|
-
else if (terminal.kind === "cancelled") {
|
|
96
|
-
termination = String(terminal.reason ?? "cancelled");
|
|
97
|
-
}
|
|
98
|
-
else if (terminal.kind === "failed") {
|
|
99
|
-
const failure = asObject(terminal.failure);
|
|
100
|
-
termination = failure.code === "provider_recovery_exhausted"
|
|
101
|
-
? "context_overflow"
|
|
102
|
-
: "error";
|
|
103
|
-
}
|
|
44
|
+
function canonicalDoneFromTerminal(terminal) {
|
|
45
|
+
const usage = asObject(terminal.usage);
|
|
46
|
+
let termination = String(terminal.kind ?? "failed");
|
|
47
|
+
let turnsUsed = Number(usage.turns ?? 0);
|
|
48
|
+
if (terminal.kind === "agent") {
|
|
49
|
+
const result = asObject(terminal.result);
|
|
50
|
+
termination = String(result.termination ?? "completed");
|
|
51
|
+
turnsUsed = Number(result.turns_used ?? turnsUsed);
|
|
52
|
+
const finalMessage = asObject(result.final_message);
|
|
53
|
+
const pace = asObject(result.pace_decision);
|
|
104
54
|
return {
|
|
105
|
-
kind: "done",
|
|
106
|
-
effectId: "",
|
|
55
|
+
kind: "done", effectId: "",
|
|
107
56
|
result: {
|
|
108
|
-
termination,
|
|
109
|
-
|
|
110
|
-
|
|
57
|
+
termination, turnsUsed, totalTokensUsed: totalUsageTokens(terminal),
|
|
58
|
+
...(Object.keys(finalMessage).length > 0 ? { finalMessage: {
|
|
59
|
+
role: String(finalMessage.role ?? "assistant"),
|
|
60
|
+
content: String(finalMessage.content ?? ""),
|
|
61
|
+
toolCalls: (Array.isArray(finalMessage.tool_calls) ? finalMessage.tool_calls : []).map(value => {
|
|
62
|
+
const call = asObject(value);
|
|
63
|
+
return { id: String(call.call_id ?? ""), name: String(call.name ?? ""), arguments: JSON.stringify(call.arguments ?? {}) };
|
|
64
|
+
}),
|
|
65
|
+
} } : {}),
|
|
66
|
+
...(Object.keys(pace).length > 0 ? { paceDecision: {
|
|
67
|
+
action: String(pace.action ?? "stop"),
|
|
68
|
+
...(pace.delay_ms !== undefined ? { delayMs: Number(pace.delay_ms) } : {}),
|
|
69
|
+
reason: String(pace.reason ?? ""),
|
|
70
|
+
...(pace.coerced_from ? { coercedFrom: String(pace.coerced_from) } : {}),
|
|
71
|
+
} } : {}),
|
|
111
72
|
},
|
|
112
73
|
};
|
|
113
74
|
}
|
|
114
|
-
|
|
115
|
-
|
|
75
|
+
if (terminal.kind === "workflow")
|
|
76
|
+
termination = String(asObject(terminal.outcome).status ?? "completed");
|
|
77
|
+
if (terminal.kind === "cancelled")
|
|
78
|
+
termination = String(terminal.reason ?? "cancelled");
|
|
79
|
+
if (terminal.kind === "failed")
|
|
80
|
+
termination = asObject(terminal.failure).code === "provider_recovery_exhausted" ? "context_overflow" : "error";
|
|
81
|
+
return { kind: "done", effectId: "", result: { termination, turnsUsed, totalTokensUsed: totalUsageTokens(terminal) } };
|
|
82
|
+
}
|
|
83
|
+
/** Adapt the additive core CurrentProjection JSON into the existing Node action surface. */
|
|
84
|
+
export function canonicalActionFromProjectionJson(raw) {
|
|
85
|
+
const projection = asObject(JSON.parse(raw));
|
|
86
|
+
const state = String(projection.state ?? "idle");
|
|
87
|
+
if (state === "idle")
|
|
116
88
|
return null;
|
|
117
|
-
if (
|
|
118
|
-
|
|
89
|
+
if (state === "terminal") {
|
|
90
|
+
return canonicalDoneFromTerminal(asObject(projection.action));
|
|
119
91
|
}
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
};
|
|
138
|
-
}),
|
|
139
|
-
};
|
|
140
|
-
case "execute_tools":
|
|
141
|
-
return {
|
|
142
|
-
kind: "execute_tool",
|
|
143
|
-
effectId,
|
|
144
|
-
calls: (Array.isArray(effect.calls) ? effect.calls : []).map(raw => {
|
|
145
|
-
const call = asObject(raw);
|
|
146
|
-
return {
|
|
147
|
-
id: String(call.call_id ?? ""),
|
|
148
|
-
name: String(call.name ?? ""),
|
|
149
|
-
arguments: JSON.stringify(call.arguments ?? {}),
|
|
150
|
-
};
|
|
151
|
-
}),
|
|
152
|
-
};
|
|
153
|
-
case "request_approval":
|
|
154
|
-
return {
|
|
155
|
-
kind: "request_approval",
|
|
156
|
-
effectId,
|
|
157
|
-
requests: (Array.isArray(effect.requests) ? effect.requests : []).map(raw => {
|
|
158
|
-
const request = asObject(raw);
|
|
159
|
-
return {
|
|
160
|
-
callId: String(request.call_id ?? ""),
|
|
161
|
-
tool: String(request.tool_name ?? ""),
|
|
162
|
-
arguments: JSON.stringify(request.arguments ?? {}),
|
|
163
|
-
reason: String(request.reason ?? ""),
|
|
164
|
-
};
|
|
165
|
-
}),
|
|
166
|
-
};
|
|
167
|
-
case "spawn_tasks":
|
|
168
|
-
return {
|
|
169
|
-
kind: "spawn_workflow",
|
|
170
|
-
effectId,
|
|
171
|
-
nodes: (Array.isArray(effect.tasks) ? effect.tasks : []).map(raw => {
|
|
172
|
-
const task = asObject(raw);
|
|
173
|
-
const spec = asObject(task.spec);
|
|
174
|
-
return {
|
|
175
|
-
agent_id: String(task.task_id ?? ""),
|
|
176
|
-
task_id: String(task.task_id ?? ""),
|
|
177
|
-
attempt_id: String(task.attempt_id ?? ""),
|
|
178
|
-
launch_token: String(task.launch_token ?? ""),
|
|
179
|
-
node_id: String(task.node_id ?? ""),
|
|
180
|
-
goal: String(spec.goal ?? ""),
|
|
181
|
-
role: String(spec.role ?? "custom"),
|
|
182
|
-
isolation: String(spec.isolation ?? "shared"),
|
|
183
|
-
context_inheritance: String(spec.context_inheritance ?? "none"),
|
|
184
|
-
...(spec.metadata && typeof spec.metadata === "object"
|
|
185
|
-
? asObject(spec.metadata)
|
|
186
|
-
: {}),
|
|
187
|
-
};
|
|
188
|
-
}),
|
|
189
|
-
...(effect.budget ? { budget: asObject(effect.budget) } : {}),
|
|
190
|
-
};
|
|
191
|
-
case "preempt_tasks": {
|
|
192
|
-
const attempts = (Array.isArray(effect.attempts) ? effect.attempts : []).map(raw => {
|
|
193
|
-
const attempt = asObject(raw);
|
|
92
|
+
const action = asObject(projection.action);
|
|
93
|
+
const payload = asObject(action.payload);
|
|
94
|
+
const effectId = String(action.effect_id ?? "");
|
|
95
|
+
if (action.kind === "query_memory") {
|
|
96
|
+
return {
|
|
97
|
+
kind: "query_memory",
|
|
98
|
+
effectId,
|
|
99
|
+
query: asObject(payload.query),
|
|
100
|
+
requestedK: Number(payload.requested_k ?? 0),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (action.kind === "execute_tools") {
|
|
104
|
+
return {
|
|
105
|
+
kind: "execute_tool",
|
|
106
|
+
effectId,
|
|
107
|
+
calls: (Array.isArray(payload.calls) ? payload.calls : []).map(raw => {
|
|
108
|
+
const call = asObject(raw);
|
|
194
109
|
return {
|
|
195
|
-
|
|
196
|
-
|
|
110
|
+
id: String(call.call_id ?? ""),
|
|
111
|
+
name: String(call.name ?? ""),
|
|
112
|
+
arguments: JSON.stringify(call.arguments ?? {}),
|
|
197
113
|
};
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (action.kind === "call_provider") {
|
|
118
|
+
const context = asObject(payload.context);
|
|
119
|
+
return {
|
|
120
|
+
kind: "call_provider",
|
|
121
|
+
effectId,
|
|
122
|
+
context: renderedContextToSdk(context),
|
|
123
|
+
tools: (Array.isArray(payload.tools) ? payload.tools : []).map(raw => {
|
|
124
|
+
const tool = asObject(raw);
|
|
125
|
+
return {
|
|
126
|
+
name: String(tool.name ?? ""),
|
|
127
|
+
description: String(tool.description ?? ""),
|
|
128
|
+
parameters: JSON.stringify(tool.parameters ?? {}),
|
|
129
|
+
};
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (action.kind === "request_approval") {
|
|
134
|
+
return {
|
|
135
|
+
kind: "request_approval",
|
|
136
|
+
effectId,
|
|
137
|
+
requests: (Array.isArray(payload.requests) ? payload.requests : []).map(raw => {
|
|
138
|
+
const request = asObject(raw);
|
|
139
|
+
return {
|
|
140
|
+
callId: String(request.call_id ?? ""),
|
|
141
|
+
tool: String(request.tool_name ?? ""),
|
|
142
|
+
arguments: JSON.stringify(request.arguments ?? {}),
|
|
143
|
+
reason: String(request.reason ?? ""),
|
|
144
|
+
};
|
|
145
|
+
}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (action.kind === "load_payload") {
|
|
149
|
+
return {
|
|
150
|
+
kind: "load_payload",
|
|
151
|
+
effectId,
|
|
152
|
+
handleId: String(payload.handle_id ?? ""),
|
|
153
|
+
payloadRef: String(payload.payload_ref ?? ""),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (action.kind === "evaluate_milestone") {
|
|
157
|
+
const request = asObject(payload.request);
|
|
158
|
+
return {
|
|
159
|
+
kind: "evaluate_milestone",
|
|
160
|
+
effectId,
|
|
161
|
+
phaseId: String(request.phase_id ?? ""),
|
|
162
|
+
criteria: [],
|
|
163
|
+
requiredEvidence: [],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (action.kind === "spawn_tasks") {
|
|
167
|
+
return {
|
|
168
|
+
kind: "spawn_workflow",
|
|
169
|
+
effectId,
|
|
170
|
+
nodes: (Array.isArray(payload.tasks) ? payload.tasks : []).map(raw => {
|
|
171
|
+
const task = asObject(raw);
|
|
172
|
+
const spec = asObject(task.spec);
|
|
173
|
+
return {
|
|
174
|
+
agent_id: String(task.task_id ?? ""), task_id: String(task.task_id ?? ""),
|
|
175
|
+
attempt_id: String(task.attempt_id ?? ""), launch_token: String(task.launch_token ?? ""),
|
|
176
|
+
node_id: String(task.node_id ?? ""), goal: String(spec.goal ?? ""),
|
|
177
|
+
role: String(spec.role ?? "custom"), isolation: String(spec.isolation ?? "shared"),
|
|
178
|
+
context_inheritance: String(spec.context_inheritance ?? "none"),
|
|
179
|
+
...(spec.metadata && typeof spec.metadata === "object" ? asObject(spec.metadata) : {}),
|
|
180
|
+
};
|
|
181
|
+
}),
|
|
182
|
+
...(payload.budget ? { budget: asObject(payload.budget) } : {}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (action.kind === "preempt_tasks") {
|
|
186
|
+
const attempts = (Array.isArray(payload.attempts) ? payload.attempts : []).map(raw => {
|
|
187
|
+
const attempt = asObject(raw);
|
|
188
|
+
return { task_id: String(attempt.task_id ?? ""), attempt_id: String(attempt.attempt_id ?? "") };
|
|
189
|
+
});
|
|
190
|
+
return { kind: "preempt_sub_agents", effectId, attempts, agentIds: attempts.map(a => a.task_id), reason: String(payload.reason ?? "") };
|
|
191
|
+
}
|
|
192
|
+
if (action.kind === "persist_memory") {
|
|
193
|
+
return { kind: "persist_memory", effectId, memory: asObject(payload.memory) };
|
|
194
|
+
}
|
|
195
|
+
if (action.kind === "archive_page_out") {
|
|
196
|
+
const archivePayload = asObject(payload.payload);
|
|
197
|
+
let archived = [];
|
|
198
|
+
try {
|
|
199
|
+
const decoded = JSON.parse(String(archivePayload.content ?? ""));
|
|
200
|
+
if (Array.isArray(decoded))
|
|
201
|
+
archived = decoded.map(value => kernelMessageToSdk(asObject(value)));
|
|
252
202
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
kind: "load_payload",
|
|
256
|
-
effectId,
|
|
257
|
-
handleId: String(effect.handle_id ?? ""),
|
|
258
|
-
payloadRef: String(effect.payload_ref ?? ""),
|
|
259
|
-
};
|
|
260
|
-
case "evaluate_milestone": {
|
|
261
|
-
const request = asObject(effect.request);
|
|
262
|
-
return {
|
|
263
|
-
kind: "evaluate_milestone",
|
|
264
|
-
effectId,
|
|
265
|
-
phaseId: String(request.phase_id ?? ""),
|
|
266
|
-
criteria: [],
|
|
267
|
-
requiredEvidence: [],
|
|
268
|
-
};
|
|
203
|
+
catch {
|
|
204
|
+
// The opaque archive payload remains authoritative even when presentation decoding fails.
|
|
269
205
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
206
|
+
return {
|
|
207
|
+
kind: "archive_page_out",
|
|
208
|
+
effectId,
|
|
209
|
+
handleId: String(payload.handle_id ?? ""),
|
|
210
|
+
payload: {
|
|
211
|
+
content: String(archivePayload.content ?? ""),
|
|
212
|
+
digest: String(archivePayload.digest ?? ""),
|
|
213
|
+
original_size: String(archivePayload.original_size ?? "0"),
|
|
214
|
+
...(archivePayload.preview ? { preview: String(archivePayload.preview) } : {}),
|
|
215
|
+
},
|
|
216
|
+
archived,
|
|
217
|
+
};
|
|
276
218
|
}
|
|
219
|
+
return {
|
|
220
|
+
kind: "unsupported_effect",
|
|
221
|
+
effectId,
|
|
222
|
+
effectKind: String(action.kind ?? ""),
|
|
223
|
+
};
|
|
277
224
|
}
|
|
278
225
|
export class CanonicalKernelRejectedError extends Error {
|
|
279
226
|
fault;
|
|
@@ -1178,6 +1125,16 @@ export class CanonicalRunnerRuntime {
|
|
|
1178
1125
|
throw new Error("canonical observation is missing kind");
|
|
1179
1126
|
this.hostObservations.push({ ...raw, kind });
|
|
1180
1127
|
}
|
|
1128
|
+
// §7.11 · a committed step that publishes effects is a fact worth recording in the
|
|
1129
|
+
// host event log: the journal stores only a digest of the step, so this manifest is
|
|
1130
|
+
// what makes the published effect ids + kinds recoverable post-hoc without replaying.
|
|
1131
|
+
const published = JSON.parse(this.host.kernel.publishedEffectsManifestJson(JSON.stringify(transition.plannedStep)));
|
|
1132
|
+
if (published.length > 0) {
|
|
1133
|
+
this.hostObservations.push({
|
|
1134
|
+
kind: "step_published_effects",
|
|
1135
|
+
effects: published,
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1181
1138
|
if (transition.checkpointAdvice) {
|
|
1182
1139
|
this.hostObservations.push({
|
|
1183
1140
|
kind: "checkpoint_advised",
|
|
@@ -1191,7 +1148,7 @@ export class CanonicalRunnerRuntime {
|
|
|
1191
1148
|
});
|
|
1192
1149
|
}
|
|
1193
1150
|
}
|
|
1194
|
-
this.lastAction =
|
|
1151
|
+
this.lastAction = canonicalActionFromProjectionJson(this.host.kernel.projectPlannedStepJson(JSON.stringify(transition.plannedStep)));
|
|
1195
1152
|
}
|
|
1196
1153
|
if (this.lastAction?.kind !== "unsupported_effect")
|
|
1197
1154
|
return this.lastAction;
|
|
@@ -1199,16 +1156,7 @@ export class CanonicalRunnerRuntime {
|
|
|
1199
1156
|
}
|
|
1200
1157
|
}
|
|
1201
1158
|
currentAction() {
|
|
1202
|
-
|
|
1203
|
-
if (terminal) {
|
|
1204
|
-
return canonicalActionFromPlannedStep({
|
|
1205
|
-
disposition: { kind: "terminal", terminal: JSON.parse(terminal) },
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1208
|
-
const effects = this.pendingEffects();
|
|
1209
|
-
return canonicalActionFromPlannedStep({
|
|
1210
|
-
disposition: { kind: "effects", effects },
|
|
1211
|
-
});
|
|
1159
|
+
return canonicalActionFromProjectionJson(this.host.kernel.currentProjectionJson());
|
|
1212
1160
|
}
|
|
1213
1161
|
pendingEffects() {
|
|
1214
1162
|
return JSON.parse(this.host.kernel.pendingEffectsJson());
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P4 (0.2.64 Execution Evidence Plane): the host-side object model connecting a kernel-minted
|
|
3
|
+
* effect to the real provider execution it drove — ModelInvocation / ProviderAttempt /
|
|
4
|
+
* ResolvedProviderRoute / UsageAccountingPolicy. Everything here is L2 host evidence
|
|
5
|
+
* (B7): it lands in SessionLog for cross-verification against the journal (C6/C8) and is
|
|
6
|
+
* NEVER fed back as kernel input (B4/DEC-2 discipline: wall-clock and wire facts stay host-side).
|
|
7
|
+
*
|
|
8
|
+
* Kernel ABI is untouched — the kernel projection of an invocation outcome remains the
|
|
9
|
+
* existing ProviderCompleted/HostEffectFailure shapes (D2).
|
|
10
|
+
*/
|
|
11
|
+
import type { ProviderUsage, ProviderWireEvidence, UsageEvent } from "../types.js";
|
|
12
|
+
import { type NormalizedProviderUsage, type ResolvedProviderRoute } from "../providers/request-plan.js";
|
|
13
|
+
/** Canonical stop-reason vocabulary already carried on the wire usage frame (types.ts). */
|
|
14
|
+
export type CanonicalStopReason = NonNullable<UsageEvent["stopReason"]>;
|
|
15
|
+
/**
|
|
16
|
+
* P4 §1.1: one logical model call = the fact-connected chain of CallProvider effects the
|
|
17
|
+
* kernel walked to obtain one turn of model output. Derived identity, zero minting:
|
|
18
|
+
* `invocationId` IS the first effect's effect_id. Authority = journal; the SessionLog
|
|
19
|
+
* projection (`llm_completed.invocation_id`) is evidence only.
|
|
20
|
+
*/
|
|
21
|
+
export interface ModelInvocation {
|
|
22
|
+
invocationId: string;
|
|
23
|
+
turn: number;
|
|
24
|
+
/** Every effect_id on the chain, in order. Length 1 = first attempt succeeded. */
|
|
25
|
+
effectChain: string[];
|
|
26
|
+
outcome?: InvocationOutcome;
|
|
27
|
+
}
|
|
28
|
+
/** P4 §1.4: the invocation's terminal projection. */
|
|
29
|
+
export interface InvocationOutcome {
|
|
30
|
+
invocationId: string;
|
|
31
|
+
/** The effect the kernel adopted as the outcome (the successful one). */
|
|
32
|
+
selectedEffectId: string;
|
|
33
|
+
stopReason?: CanonicalStopReason;
|
|
34
|
+
/** Absent on a failed chain (nothing settled). */
|
|
35
|
+
settlement?: UsageSettlement;
|
|
36
|
+
}
|
|
37
|
+
export type ProviderAttemptStatus = "success" | "transport_exhausted" | "aborted" | "rejected";
|
|
38
|
+
/**
|
|
39
|
+
* P4 §1.2: one effect's execution against one route, one physical attempt. The kernel-minted
|
|
40
|
+
* `effectId` is the primary key (H7 — no parallel id minting). Transport-ladder rungs are
|
|
41
|
+
* summarized as a count plus the final error class; rung-level evidence belongs to adapter
|
|
42
|
+
* debug logs, not SessionLog.
|
|
43
|
+
*/
|
|
44
|
+
export interface ProviderAttempt {
|
|
45
|
+
effectId: string;
|
|
46
|
+
/** Failover sequence within one effect execution (1-based). Always 1 today (P4 §0.2). */
|
|
47
|
+
attemptSeq: number;
|
|
48
|
+
route: ResolvedProviderRoute;
|
|
49
|
+
/** → ProviderRequestPlan.fingerprint (G2). */
|
|
50
|
+
requestFingerprint: string;
|
|
51
|
+
status: ProviderAttemptStatus;
|
|
52
|
+
transportRungs: number;
|
|
53
|
+
/** classifyProviderError's class, never the raw vendor text (B1 spirit). */
|
|
54
|
+
lastErrorClass?: string;
|
|
55
|
+
/** Host wall-clock, pure evidence, never kernel input (DEC-2 discipline). */
|
|
56
|
+
startedAtMs: number;
|
|
57
|
+
finishedAtMs: number;
|
|
58
|
+
/** Full measurement fields (P4 §2); only the settlement crosses the kernel boundary (B4). */
|
|
59
|
+
usage?: NormalizedProviderUsage;
|
|
60
|
+
wireEvidence?: ProviderWireEvidence;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The SessionLog wire payload of a ProviderAttempt (P4 §3): the §1.2 fields flattened into
|
|
64
|
+
* the event, snake_case per SessionLog convention. Nested objects keep their native shape
|
|
65
|
+
* (same convention as `prompt_measured.measurement`).
|
|
66
|
+
*/
|
|
67
|
+
export interface ProviderAttemptRecord {
|
|
68
|
+
effect_id: string;
|
|
69
|
+
attempt_seq: number;
|
|
70
|
+
route: ResolvedProviderRoute;
|
|
71
|
+
request_fingerprint: string;
|
|
72
|
+
status: ProviderAttemptStatus;
|
|
73
|
+
transport_rungs: number;
|
|
74
|
+
last_error_class?: string;
|
|
75
|
+
started_at_ms: number;
|
|
76
|
+
finished_at_ms: number;
|
|
77
|
+
usage?: NormalizedProviderUsage;
|
|
78
|
+
wire_evidence?: ProviderWireEvidence;
|
|
79
|
+
/** P4 §2.1: the accounting policy this attempt's settlement was/will be derived with —
|
|
80
|
+
* pinned on the attempt so settlement is deterministically recomputable from
|
|
81
|
+
* (measurement, policy_id). */
|
|
82
|
+
accounting_policy_id?: string;
|
|
83
|
+
}
|
|
84
|
+
export declare function providerAttemptToRecord(attempt: ProviderAttempt, accountingPolicyId?: string): ProviderAttemptRecord;
|
|
85
|
+
/**
|
|
86
|
+
* P4 §2: the only two numbers that cross the kernel boundary (B4). Field names match the
|
|
87
|
+
* existing ResolveEffect wire shape — this is what the runner already feeds the kernel as
|
|
88
|
+
* `observed_input_tokens` / `observed_output_tokens`.
|
|
89
|
+
*/
|
|
90
|
+
export interface UsageSettlement {
|
|
91
|
+
observed_input_tokens: number;
|
|
92
|
+
observed_output_tokens: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* P4 §2.1: the named, pinnable, replayable policy turning a measurement into a settlement.
|
|
96
|
+
* `settle` must be a pure function of the measurement — given (usage, policyId) any auditor
|
|
97
|
+
* recomputes the identical settlement.
|
|
98
|
+
*/
|
|
99
|
+
export interface UsageAccountingPolicy {
|
|
100
|
+
policyId: string;
|
|
101
|
+
settle(usage: NormalizedProviderUsage): UsageSettlement;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The default policy = today's implicit runner behavior exactly (full input footprint + full
|
|
105
|
+
* output footprint — the two numbers the runner has always fed `observed_*`). P4 changes no
|
|
106
|
+
* default numbers; it only makes the conversion a named object. The date stamp is the
|
|
107
|
+
* policy's identity: any future semantics change MUST ship under a new policyId.
|
|
108
|
+
*/
|
|
109
|
+
export declare const FULL_FOOTPRINT_USAGE_ACCOUNTING_POLICY: UsageAccountingPolicy;
|
|
110
|
+
/**
|
|
111
|
+
* Defensive measurement assembly for the attempt evidence path: an invalid frame (cache
|
|
112
|
+
* subsets exceeding input, etc.) degrades to NO measurement instead of breaking the run —
|
|
113
|
+
* evidence is never worth a run failure, and the settlement falls back to the raw counts.
|
|
114
|
+
* Telemetry fields normalizeProviderUsage drops are re-attached so `usage` stays full-field.
|
|
115
|
+
*/
|
|
116
|
+
export declare function tryNormalizeProviderUsage(usage: ProviderUsage): NormalizedProviderUsage | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
* Telemetry fields normalizeProviderUsage drops are re-attached so `usage` stays full-field.
|
|
38
|
+
*/
|
|
39
|
+
export function tryNormalizeProviderUsage(usage) {
|
|
40
|
+
try {
|
|
41
|
+
const normalized = normalizeProviderUsage(usage);
|
|
42
|
+
return {
|
|
43
|
+
...normalized,
|
|
44
|
+
...(usage.cacheTelemetryStatus !== undefined ? { cacheTelemetryStatus: usage.cacheTelemetryStatus } : {}),
|
|
45
|
+
...(usage.cacheTelemetrySource !== undefined ? { cacheTelemetrySource: usage.cacheTelemetrySource } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CanonicalKernelInstance } from "../kernel.js";
|
|
2
|
+
import type { KernelJournal } from "./kernel-journal.js";
|
|
3
|
+
/**
|
|
4
|
+
* What a journal inspection knows: whether the operation restores, and — when it does not — which
|
|
5
|
+
* record is the first this binary cannot replay. The effect manifest (which effects each step
|
|
6
|
+
* published) lives in the host event log via the `step_published_effects` observation, not here:
|
|
7
|
+
* a journal stores only each record's input and digests, never the step it produced (§8.1).
|
|
8
|
+
*/
|
|
9
|
+
export interface KernelJournalDiagnosis {
|
|
10
|
+
operationId: string;
|
|
11
|
+
recordCount: number;
|
|
12
|
+
/** True when `restore()` replayed the whole journal and verified every step digest. */
|
|
13
|
+
restorable: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The `step_seq` of the first record whose replay diverged from the durable digest, when known.
|
|
16
|
+
* The restore failure message names this step; a fault before any record replays leaves it null.
|
|
17
|
+
*/
|
|
18
|
+
divergenceStep: number | null;
|
|
19
|
+
divergenceReason: string | null;
|
|
20
|
+
/** The operation's terminal, only when the journal restored cleanly. */
|
|
21
|
+
terminal: Record<string, unknown> | undefined;
|
|
22
|
+
/** Pending effects at the head, only when the journal restored cleanly. */
|
|
23
|
+
pendingEffects: Array<{
|
|
24
|
+
effect_id: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Inspect a durable journal without committing anything: replay it through the kernel and report
|
|
30
|
+
* whether it still restores. This turns a "silent brick" — an operation whose journal was appended
|
|
31
|
+
* but whose step can no longer be re-derived by this binary — into an actionable diagnosis: which
|
|
32
|
+
* record diverges, and what the head still holds if it does not.
|
|
33
|
+
*
|
|
34
|
+
* Nothing is mutated; the same journal can be diagnosed repeatedly and then restored normally.
|
|
35
|
+
*/
|
|
36
|
+
export declare function diagnoseKernelJournal(kernel: CanonicalKernelInstance, journal: KernelJournal, operationId: string): Promise<KernelJournalDiagnosis>;
|