tinker-agent 2.5.0 → 2.7.0
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/CHANGELOG.md +42 -1
- package/README.md +30 -7
- package/package.json +1 -1
- package/src/agent/loop.ts +22 -0
- package/src/agent/runtime-session.ts +158 -0
- package/src/cli/main.ts +2 -0
- package/src/cli/public-config-contract.ts +1 -1
- package/src/cli/tui-runner.tsx +16 -1
- package/src/events/stdout-event-printer.ts +14 -0
- package/src/events/types.ts +9 -0
- package/src/memory/contracts.ts +46 -0
- package/src/memory/memory-coordinator.ts +445 -4
- package/src/memory/memory-create-tool.ts +117 -0
- package/src/memory/memory-delete-tool.ts +88 -0
- package/src/memory/memory-store.ts +239 -0
- package/src/memory/memory-update-tool.ts +142 -0
- package/src/observation/observation-builder.ts +70 -0
- package/src/session/session-clone-helpers.ts +249 -0
- package/src/session/session-compatibility-codec.ts +401 -0
- package/src/session/session-store-contracts.ts +304 -0
- package/src/session/session-store-filesystem.ts +245 -0
- package/src/session/session-store-record-codecs.ts +1064 -0
- package/src/session/session-store-value-codecs.ts +156 -0
- package/src/session/session-store.ts +234 -3023
- package/src/session/session-tool-result-codec.ts +594 -0
- package/src/tools/ask-user.ts +100 -0
- package/src/tools/grep.ts +1 -3
- package/src/tools/registry.ts +30 -0
- package/src/tools/types.ts +71 -0
- package/src/tui/app.tsx +35 -5
- package/src/tui/components/ask-user.tsx +61 -0
- package/src/tui/components/footer.tsx +14 -1
- package/src/tui/components/prompt-input.tsx +4 -0
- package/src/tui/components/resume-session-picker.tsx +118 -37
- package/src/tui/event-store.ts +57 -0
- package/src/tui/tui-session-controller.ts +8 -0
|
@@ -0,0 +1,1064 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import type {
|
|
3
|
+
ContextRevisionId,
|
|
4
|
+
ContextSurfaceId,
|
|
5
|
+
IterationId,
|
|
6
|
+
MessageId,
|
|
7
|
+
ProtocolFrameId,
|
|
8
|
+
SessionId,
|
|
9
|
+
ToolCallId,
|
|
10
|
+
TurnId,
|
|
11
|
+
} from "../ids/runtime-id";
|
|
12
|
+
import type { ToolDefinition } from "../tools/types";
|
|
13
|
+
import {
|
|
14
|
+
SUPPORTED_TOOL_OBSERVATION_FORMATS,
|
|
15
|
+
immutableCanonicalClone,
|
|
16
|
+
immutableRecord,
|
|
17
|
+
type CanonicalMessageRecord,
|
|
18
|
+
type ProtocolContextView,
|
|
19
|
+
type ProtocolFrame,
|
|
20
|
+
type ToolCompletion,
|
|
21
|
+
type ToolResultRecord,
|
|
22
|
+
} from "../context/protocol-frame";
|
|
23
|
+
import {
|
|
24
|
+
validateStoredContextSurface,
|
|
25
|
+
type StoredContextSurfaceV8,
|
|
26
|
+
} from "../context/context-surface";
|
|
27
|
+
import type {
|
|
28
|
+
StoredContextRevisionV8,
|
|
29
|
+
StoredContextOverrideV8,
|
|
30
|
+
SwapOverride,
|
|
31
|
+
} from "../context/context-revision";
|
|
32
|
+
import {
|
|
33
|
+
SWAP_OBSERVATION_FORMAT,
|
|
34
|
+
SWAP_TOOL_IMAGE_FORMAT,
|
|
35
|
+
} from "../context/context-swap-renderer";
|
|
36
|
+
import { SUPPORTED_RECALL_RETIREMENT_CONTRACT_VERSIONS } from "../context/recall-retirement-contract";
|
|
37
|
+
import type { ToolCall, ToolResultContent } from "../agent/types";
|
|
38
|
+
import {
|
|
39
|
+
toolResultDisplayText,
|
|
40
|
+
validateToolResultContent,
|
|
41
|
+
} from "../agent/tool-result-content";
|
|
42
|
+
import {
|
|
43
|
+
parseImageAssetId,
|
|
44
|
+
parseImageAttachmentId,
|
|
45
|
+
validateOriginalImageName,
|
|
46
|
+
validateUserMessage,
|
|
47
|
+
type ImageAssetRef,
|
|
48
|
+
type UserImageAttachment,
|
|
49
|
+
} from "../image/image-types";
|
|
50
|
+
import type { ProjectInstructionManifest } from "../instructions/project-instructions";
|
|
51
|
+
import type {
|
|
52
|
+
ActiveSkillManifestEntry,
|
|
53
|
+
SkillCatalogManifestEntry,
|
|
54
|
+
} from "../skills/skill-catalog";
|
|
55
|
+
import {
|
|
56
|
+
SKILL_ACTIVATION_RECEIPT_FORMAT,
|
|
57
|
+
SKILL_POLICY_VERSION,
|
|
58
|
+
} from "../skills/skill-context";
|
|
59
|
+
import type { StoredSkillActivation } from "./session-store-contracts";
|
|
60
|
+
import { decodeStoredToolRawResult } from "./session-tool-result-codec";
|
|
61
|
+
import {
|
|
62
|
+
assertObjectKeys,
|
|
63
|
+
enumFromSql,
|
|
64
|
+
nullableNumberFromSql,
|
|
65
|
+
nullableStringFromSql,
|
|
66
|
+
nullableTextFromSql,
|
|
67
|
+
numberFromJson,
|
|
68
|
+
numberFromSql,
|
|
69
|
+
parseJson,
|
|
70
|
+
recordFromSql,
|
|
71
|
+
sha256FromSql,
|
|
72
|
+
stringFromSql,
|
|
73
|
+
timestampFromSql,
|
|
74
|
+
timestampValue,
|
|
75
|
+
} from "./session-store-value-codecs";
|
|
76
|
+
|
|
77
|
+
export function decodeFrame(rowValue: unknown): ProtocolFrame {
|
|
78
|
+
const row = recordFromSql(rowValue, "protocol frame");
|
|
79
|
+
const state = enumFromSql(row.state, ["open", "closed"] as const, "frame state");
|
|
80
|
+
const kind = enumFromSql(
|
|
81
|
+
row.kind,
|
|
82
|
+
["system", "user", "assistant_text", "tool_exchange"] as const,
|
|
83
|
+
"frame kind",
|
|
84
|
+
);
|
|
85
|
+
const lastOrdinal = nullableNumberFromSql(row.last_ordinal, "last_ordinal");
|
|
86
|
+
const closedAt = nullableStringFromSql(row.closed_at, "closed_at");
|
|
87
|
+
return immutableRecord({
|
|
88
|
+
frameId: stringFromSql(row.frame_id, "frame_id") as ProtocolFrameId,
|
|
89
|
+
sessionId: stringFromSql(row.session_id, "session_id") as SessionId,
|
|
90
|
+
...(row.turn_id === null
|
|
91
|
+
? {}
|
|
92
|
+
: { turnId: stringFromSql(row.turn_id, "turn_id") as TurnId }),
|
|
93
|
+
...(row.iteration_id === null
|
|
94
|
+
? {}
|
|
95
|
+
: {
|
|
96
|
+
iterationId: stringFromSql(row.iteration_id, "iteration_id") as IterationId,
|
|
97
|
+
}),
|
|
98
|
+
kind,
|
|
99
|
+
state,
|
|
100
|
+
firstOrdinal: numberFromSql(row.first_ordinal, "first_ordinal"),
|
|
101
|
+
...(lastOrdinal === null ? {} : { lastOrdinal }),
|
|
102
|
+
createdAt: timestampFromSql(row.created_at, "created_at"),
|
|
103
|
+
...(closedAt === null ? {} : { closedAt: timestampValue(closedAt, "closed_at") }),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function loadMessageImageAttachments(
|
|
108
|
+
database: Database,
|
|
109
|
+
): Map<string, readonly UserImageAttachment[]> {
|
|
110
|
+
const rows = database
|
|
111
|
+
.query(
|
|
112
|
+
`SELECT mia.*, ia.mime_type, ia.byte_length, ia.width, ia.height,
|
|
113
|
+
ia.created_at AS asset_created_at
|
|
114
|
+
FROM message_image_attachments mia
|
|
115
|
+
JOIN image_assets ia ON ia.asset_id = mia.asset_id
|
|
116
|
+
JOIN messages m ON m.message_id = mia.message_id
|
|
117
|
+
ORDER BY m.ordinal, mia.position`,
|
|
118
|
+
)
|
|
119
|
+
.all();
|
|
120
|
+
const mutable = new Map<string, UserImageAttachment[]>();
|
|
121
|
+
for (const rowValue of rows) {
|
|
122
|
+
const row = recordFromSql(rowValue, "message image attachment");
|
|
123
|
+
const messageId = stringFromSql(row.message_id, "attachment message_id");
|
|
124
|
+
const attachments = mutable.get(messageId) ?? [];
|
|
125
|
+
const position = numberFromSql(row.position, "attachment position");
|
|
126
|
+
if (position !== attachments.length) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Image attachment positions for message ${messageId} are not continuous.`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
timestampFromSql(row.asset_created_at, "image asset created_at");
|
|
132
|
+
const attachment = immutableRecord<UserImageAttachment>({
|
|
133
|
+
attachmentId: parseImageAttachmentId(
|
|
134
|
+
stringFromSql(row.attachment_id, "attachment_id"),
|
|
135
|
+
),
|
|
136
|
+
assetId: parseImageAssetId(stringFromSql(row.asset_id, "asset_id")),
|
|
137
|
+
mimeType: enumFromSql(
|
|
138
|
+
row.mime_type,
|
|
139
|
+
["image/png", "image/jpeg", "image/webp"] as const,
|
|
140
|
+
"image mime_type",
|
|
141
|
+
),
|
|
142
|
+
byteLength: numberFromSql(row.byte_length, "image byte_length"),
|
|
143
|
+
width: numberFromSql(row.width, "image width"),
|
|
144
|
+
height: numberFromSql(row.height, "image height"),
|
|
145
|
+
label: stringFromSql(row.label, "attachment label"),
|
|
146
|
+
range: immutableRecord({
|
|
147
|
+
start: numberFromSql(row.range_start, "attachment range_start"),
|
|
148
|
+
end: numberFromSql(row.range_end, "attachment range_end"),
|
|
149
|
+
}),
|
|
150
|
+
originalName: stringFromSql(row.original_name, "attachment original_name"),
|
|
151
|
+
});
|
|
152
|
+
validateOriginalImageName(attachment.originalName);
|
|
153
|
+
attachments.push(attachment);
|
|
154
|
+
mutable.set(messageId, attachments);
|
|
155
|
+
}
|
|
156
|
+
return new Map(
|
|
157
|
+
[...mutable].map(([messageId, attachments]) => [
|
|
158
|
+
messageId,
|
|
159
|
+
Object.freeze(attachments),
|
|
160
|
+
]),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function loadToolMessageContentBlocks(
|
|
165
|
+
database: Database,
|
|
166
|
+
): Map<string, readonly ToolResultContent[]> {
|
|
167
|
+
const rows = database
|
|
168
|
+
.query(
|
|
169
|
+
`SELECT tcb.*, ia.mime_type, ia.byte_length, ia.width, ia.height,
|
|
170
|
+
ia.created_at AS asset_created_at
|
|
171
|
+
FROM tool_message_content_blocks tcb
|
|
172
|
+
LEFT JOIN image_assets ia ON ia.asset_id = tcb.asset_id
|
|
173
|
+
JOIN messages m ON m.message_id = tcb.message_id
|
|
174
|
+
ORDER BY m.ordinal, tcb.position`,
|
|
175
|
+
)
|
|
176
|
+
.all();
|
|
177
|
+
const mutable = new Map<string, ToolResultContent[]>();
|
|
178
|
+
for (const rowValue of rows) {
|
|
179
|
+
const row = recordFromSql(rowValue, "tool message content block");
|
|
180
|
+
const messageId = stringFromSql(row.message_id, "tool block message_id");
|
|
181
|
+
const blocks = mutable.get(messageId) ?? [];
|
|
182
|
+
const position = numberFromSql(row.position, "tool block position");
|
|
183
|
+
if (position !== blocks.length) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Tool content block positions for message ${messageId} are not continuous.`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const kind = enumFromSql(row.kind, ["text", "image"] as const, "tool block kind");
|
|
189
|
+
const block: ToolResultContent =
|
|
190
|
+
kind === "text"
|
|
191
|
+
? immutableRecord({
|
|
192
|
+
type: "text",
|
|
193
|
+
text: stringFromSql(row.text_content, "tool block text_content"),
|
|
194
|
+
})
|
|
195
|
+
: immutableRecord({
|
|
196
|
+
type: "image",
|
|
197
|
+
asset: immutableRecord({
|
|
198
|
+
assetId: parseImageAssetId(
|
|
199
|
+
stringFromSql(row.asset_id, "tool block asset_id"),
|
|
200
|
+
),
|
|
201
|
+
mimeType: enumFromSql(
|
|
202
|
+
row.mime_type,
|
|
203
|
+
["image/png", "image/jpeg", "image/webp"] as const,
|
|
204
|
+
"tool block image mime_type",
|
|
205
|
+
),
|
|
206
|
+
byteLength: numberFromSql(
|
|
207
|
+
row.byte_length,
|
|
208
|
+
"tool block image byte_length",
|
|
209
|
+
),
|
|
210
|
+
width: numberFromSql(row.width, "tool block image width"),
|
|
211
|
+
height: numberFromSql(row.height, "tool block image height"),
|
|
212
|
+
}),
|
|
213
|
+
});
|
|
214
|
+
if (kind === "image") {
|
|
215
|
+
timestampFromSql(row.asset_created_at, "tool block image asset created_at");
|
|
216
|
+
}
|
|
217
|
+
blocks.push(block);
|
|
218
|
+
mutable.set(messageId, blocks);
|
|
219
|
+
}
|
|
220
|
+
return new Map(
|
|
221
|
+
[...mutable].map(([messageId, blocks]) => {
|
|
222
|
+
validateToolResultContent(blocks);
|
|
223
|
+
return [messageId, Object.freeze(blocks)] as const;
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function decodeMessage(
|
|
229
|
+
rowValue: unknown,
|
|
230
|
+
attachments?: readonly UserImageAttachment[],
|
|
231
|
+
toolContentBlocks?: readonly ToolResultContent[],
|
|
232
|
+
): CanonicalMessageRecord {
|
|
233
|
+
const row = recordFromSql(rowValue, "message");
|
|
234
|
+
const base = {
|
|
235
|
+
messageId: stringFromSql(row.message_id, "message_id") as MessageId,
|
|
236
|
+
sessionId: stringFromSql(row.session_id, "session_id") as SessionId,
|
|
237
|
+
frameId: stringFromSql(row.frame_id, "frame_id") as ProtocolFrameId,
|
|
238
|
+
ordinal: numberFromSql(row.ordinal, "ordinal"),
|
|
239
|
+
contentSha256: stringFromSql(row.content_sha256, "content_sha256"),
|
|
240
|
+
createdAt: timestampFromSql(row.created_at, "created_at"),
|
|
241
|
+
};
|
|
242
|
+
const role = enumFromSql(
|
|
243
|
+
row.role,
|
|
244
|
+
["system", "user", "assistant", "tool"] as const,
|
|
245
|
+
"message role",
|
|
246
|
+
);
|
|
247
|
+
switch (role) {
|
|
248
|
+
case "system":
|
|
249
|
+
if (attachments !== undefined || toolContentBlocks !== undefined) {
|
|
250
|
+
throw new Error("System messages cannot have media relation rows.");
|
|
251
|
+
}
|
|
252
|
+
return immutableRecord({
|
|
253
|
+
...base,
|
|
254
|
+
role,
|
|
255
|
+
content: stringFromSql(row.content, "content"),
|
|
256
|
+
origin: "runtime",
|
|
257
|
+
});
|
|
258
|
+
case "user": {
|
|
259
|
+
if (toolContentBlocks !== undefined) {
|
|
260
|
+
throw new Error("User messages cannot have tool content blocks.");
|
|
261
|
+
}
|
|
262
|
+
const message = {
|
|
263
|
+
...base,
|
|
264
|
+
role,
|
|
265
|
+
turnId: stringFromSql(row.turn_id, "turn_id") as TurnId,
|
|
266
|
+
content: stringFromSql(row.content, "content"),
|
|
267
|
+
...(attachments === undefined ? {} : { attachments }),
|
|
268
|
+
origin: "user",
|
|
269
|
+
} as const;
|
|
270
|
+
validateUserMessage({
|
|
271
|
+
role: "user",
|
|
272
|
+
content: message.content,
|
|
273
|
+
...(message.attachments === undefined
|
|
274
|
+
? {}
|
|
275
|
+
: { attachments: message.attachments }),
|
|
276
|
+
});
|
|
277
|
+
return immutableRecord(message);
|
|
278
|
+
}
|
|
279
|
+
case "assistant": {
|
|
280
|
+
if (attachments !== undefined || toolContentBlocks !== undefined) {
|
|
281
|
+
throw new Error("Assistant messages cannot have media relation rows.");
|
|
282
|
+
}
|
|
283
|
+
const content = nullableTextFromSql(row.content, "content");
|
|
284
|
+
const reasoningPresent = numberFromSql(
|
|
285
|
+
row.reasoning_content_present,
|
|
286
|
+
"reasoning_content_present",
|
|
287
|
+
);
|
|
288
|
+
if (reasoningPresent !== 0 && reasoningPresent !== 1) {
|
|
289
|
+
throw new Error("reasoning_content_present must be 0 or 1.");
|
|
290
|
+
}
|
|
291
|
+
const toolCalls =
|
|
292
|
+
row.tool_calls_json === null
|
|
293
|
+
? undefined
|
|
294
|
+
: decodeStoredToolCalls(
|
|
295
|
+
stringFromSql(row.tool_calls_json, "tool_calls_json"),
|
|
296
|
+
);
|
|
297
|
+
return immutableRecord({
|
|
298
|
+
...base,
|
|
299
|
+
role,
|
|
300
|
+
turnId: stringFromSql(row.turn_id, "turn_id") as TurnId,
|
|
301
|
+
iterationId: stringFromSql(row.iteration_id, "iteration_id") as IterationId,
|
|
302
|
+
content,
|
|
303
|
+
...(reasoningPresent === 0
|
|
304
|
+
? {}
|
|
305
|
+
: {
|
|
306
|
+
reasoningContent: nullableTextFromSql(
|
|
307
|
+
row.reasoning_content,
|
|
308
|
+
"reasoning_content",
|
|
309
|
+
),
|
|
310
|
+
}),
|
|
311
|
+
...(toolCalls === undefined ? {} : { toolCalls }),
|
|
312
|
+
provider: stringFromSql(row.provider, "provider"),
|
|
313
|
+
model: stringFromSql(row.model, "model"),
|
|
314
|
+
origin: "model",
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
case "tool": {
|
|
318
|
+
if (attachments !== undefined) {
|
|
319
|
+
throw new Error("Tool messages cannot have image attachments.");
|
|
320
|
+
}
|
|
321
|
+
if (toolContentBlocks === undefined) {
|
|
322
|
+
throw new Error("Tool messages must have persisted content blocks.");
|
|
323
|
+
}
|
|
324
|
+
validateToolResultContent(toolContentBlocks);
|
|
325
|
+
const displayText = stringFromSql(row.content, "content");
|
|
326
|
+
if (toolResultDisplayText(toolContentBlocks) !== displayText) {
|
|
327
|
+
throw new Error("Tool message display projection does not match its blocks.");
|
|
328
|
+
}
|
|
329
|
+
return immutableRecord({
|
|
330
|
+
...base,
|
|
331
|
+
role,
|
|
332
|
+
turnId: stringFromSql(row.turn_id, "turn_id") as TurnId,
|
|
333
|
+
iterationId: stringFromSql(row.iteration_id, "iteration_id") as IterationId,
|
|
334
|
+
toolCallId: stringFromSql(row.tool_call_id, "tool_call_id") as ToolCallId,
|
|
335
|
+
providerToolCallId: stringFromSql(
|
|
336
|
+
row.provider_tool_call_id,
|
|
337
|
+
"provider_tool_call_id",
|
|
338
|
+
),
|
|
339
|
+
name: stringFromSql(row.name, "name"),
|
|
340
|
+
content: toolContentBlocks,
|
|
341
|
+
displayText,
|
|
342
|
+
origin: enumFromSql(row.origin, ["tool", "runtime"] as const, "tool origin"),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function imageAssetRefFromAttachment(
|
|
349
|
+
attachment: UserImageAttachment,
|
|
350
|
+
): ImageAssetRef {
|
|
351
|
+
return Object.freeze({
|
|
352
|
+
assetId: attachment.assetId,
|
|
353
|
+
mimeType: attachment.mimeType,
|
|
354
|
+
byteLength: attachment.byteLength,
|
|
355
|
+
width: attachment.width,
|
|
356
|
+
height: attachment.height,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function decodeToolResult(rowValue: unknown): ToolResultRecord {
|
|
361
|
+
const row = recordFromSql(rowValue, "tool result");
|
|
362
|
+
const kind = enumFromSql(
|
|
363
|
+
row.completion_kind,
|
|
364
|
+
["returned", "synthetic"] as const,
|
|
365
|
+
"completion kind",
|
|
366
|
+
);
|
|
367
|
+
let completion: ToolCompletion;
|
|
368
|
+
if (kind === "returned") {
|
|
369
|
+
completion = immutableRecord({
|
|
370
|
+
kind,
|
|
371
|
+
raw: decodeStoredToolRawResult(
|
|
372
|
+
parseJson(stringFromSql(row.raw_json, "raw_json"), "raw_json"),
|
|
373
|
+
),
|
|
374
|
+
rawSha256: stringFromSql(row.raw_sha256, "raw_sha256"),
|
|
375
|
+
observationFormat: enumFromSql(
|
|
376
|
+
row.observation_format,
|
|
377
|
+
SUPPORTED_TOOL_OBSERVATION_FORMATS,
|
|
378
|
+
"observation format",
|
|
379
|
+
),
|
|
380
|
+
});
|
|
381
|
+
} else {
|
|
382
|
+
const reason = enumFromSql(
|
|
383
|
+
row.synthetic_reason,
|
|
384
|
+
[
|
|
385
|
+
"cancelled_active",
|
|
386
|
+
"skipped_after_cancel",
|
|
387
|
+
"failed_active",
|
|
388
|
+
"skipped_after_failure",
|
|
389
|
+
"interrupted_active",
|
|
390
|
+
"skipped_after_interruption",
|
|
391
|
+
] as const,
|
|
392
|
+
"synthetic reason",
|
|
393
|
+
);
|
|
394
|
+
completion = immutableRecord({
|
|
395
|
+
kind,
|
|
396
|
+
reason,
|
|
397
|
+
...(row.synthetic_detail === null
|
|
398
|
+
? {}
|
|
399
|
+
: {
|
|
400
|
+
detail: stringFromSql(row.synthetic_detail, "synthetic_detail"),
|
|
401
|
+
}),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return immutableRecord({
|
|
405
|
+
sessionId: stringFromSql(row.session_id, "session_id") as SessionId,
|
|
406
|
+
frameId: stringFromSql(row.frame_id, "frame_id") as ProtocolFrameId,
|
|
407
|
+
toolCallId: stringFromSql(row.tool_call_id, "tool_call_id") as ToolCallId,
|
|
408
|
+
toolMessageId: stringFromSql(row.tool_message_id, "tool_message_id") as MessageId,
|
|
409
|
+
completion,
|
|
410
|
+
observationSha256: stringFromSql(row.observation_sha256, "observation_sha256"),
|
|
411
|
+
createdAt: timestampFromSql(row.created_at, "created_at"),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function decodeContextSurface(rowValue: unknown): StoredContextSurfaceV8 {
|
|
416
|
+
const row = recordFromSql(rowValue, "context surface");
|
|
417
|
+
const projectInstruction =
|
|
418
|
+
row.project_instruction_json === null
|
|
419
|
+
? undefined
|
|
420
|
+
: decodeProjectInstructionManifest(
|
|
421
|
+
parseJson(
|
|
422
|
+
stringFromSql(row.project_instruction_json, "project_instruction_json"),
|
|
423
|
+
"project_instruction_json",
|
|
424
|
+
),
|
|
425
|
+
);
|
|
426
|
+
const toolDefinitions = decodeToolDefinitions(
|
|
427
|
+
parseJson(
|
|
428
|
+
stringFromSql(row.tool_definitions_json, "tool_definitions_json"),
|
|
429
|
+
"tool_definitions_json",
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
const skillCatalog = decodeSkillCatalogManifest(
|
|
433
|
+
parseJson(
|
|
434
|
+
stringFromSql(row.skill_catalog_json, "skill_catalog_json"),
|
|
435
|
+
"skill_catalog_json",
|
|
436
|
+
),
|
|
437
|
+
);
|
|
438
|
+
const activeSkills = decodeActiveSkillsManifest(
|
|
439
|
+
parseJson(
|
|
440
|
+
stringFromSql(row.active_skills_json, "active_skills_json"),
|
|
441
|
+
"active_skills_json",
|
|
442
|
+
),
|
|
443
|
+
);
|
|
444
|
+
const surface = Object.freeze({
|
|
445
|
+
surfaceId: stringFromSql(row.surface_id, "surface_id") as ContextSurfaceId,
|
|
446
|
+
sessionId: stringFromSql(row.session_id, "session_id") as SessionId,
|
|
447
|
+
systemPrompt: stringFromSql(row.system_prompt, "system_prompt"),
|
|
448
|
+
systemPromptSha256: sha256FromSql(row.system_prompt_sha256, "system_prompt_sha256"),
|
|
449
|
+
recallContractVersion: enumFromSql(
|
|
450
|
+
row.recall_contract_version,
|
|
451
|
+
SUPPORTED_RECALL_RETIREMENT_CONTRACT_VERSIONS,
|
|
452
|
+
"recall_contract_version",
|
|
453
|
+
),
|
|
454
|
+
...(projectInstruction === undefined ? {} : { projectInstruction }),
|
|
455
|
+
skillCatalog,
|
|
456
|
+
skillCatalogSha256: sha256FromSql(row.skill_catalog_sha256, "skill_catalog_sha256"),
|
|
457
|
+
activeSkills,
|
|
458
|
+
activeSkillsSha256: sha256FromSql(row.active_skills_sha256, "active_skills_sha256"),
|
|
459
|
+
toolDefinitions,
|
|
460
|
+
toolDefinitionsSha256: sha256FromSql(
|
|
461
|
+
row.tool_definitions_sha256,
|
|
462
|
+
"tool_definitions_sha256",
|
|
463
|
+
),
|
|
464
|
+
toolSchemaSha256: sha256FromSql(row.tool_schema_sha256, "tool_schema_sha256"),
|
|
465
|
+
requestConfigSha256: sha256FromSql(
|
|
466
|
+
row.request_config_sha256,
|
|
467
|
+
"request_config_sha256",
|
|
468
|
+
),
|
|
469
|
+
requestMaxOutputTokens: numberFromSql(
|
|
470
|
+
row.request_max_output_tokens,
|
|
471
|
+
"request_max_output_tokens",
|
|
472
|
+
),
|
|
473
|
+
surfaceSha256: sha256FromSql(row.surface_sha256, "surface_sha256"),
|
|
474
|
+
createdAt: timestampFromSql(row.created_at, "created_at"),
|
|
475
|
+
});
|
|
476
|
+
validateStoredContextSurface(surface);
|
|
477
|
+
return surface;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function decodeContextRevision(rowValue: unknown): StoredContextRevisionV8 {
|
|
481
|
+
const row = recordFromSql(rowValue, "context revision");
|
|
482
|
+
const kind = enumFromSql(
|
|
483
|
+
row.kind,
|
|
484
|
+
[
|
|
485
|
+
"initial_full",
|
|
486
|
+
"swap_only",
|
|
487
|
+
"surface_refresh",
|
|
488
|
+
"prefix_retirement",
|
|
489
|
+
"skills_update",
|
|
490
|
+
] as const,
|
|
491
|
+
"context revision kind",
|
|
492
|
+
);
|
|
493
|
+
const common = {
|
|
494
|
+
revisionId: stringFromSql(row.revision_id, "revision_id") as ContextRevisionId,
|
|
495
|
+
sessionId: stringFromSql(row.session_id, "session_id") as SessionId,
|
|
496
|
+
surfaceId: stringFromSql(row.surface_id, "surface_id") as ContextSurfaceId,
|
|
497
|
+
surfaceSha256: sha256FromSql(row.surface_sha256, "surface_sha256"),
|
|
498
|
+
keepFromOrdinal: numberFromSql(row.keep_from_ordinal, "keep_from_ordinal"),
|
|
499
|
+
sourceThroughOrdinal: numberFromSql(
|
|
500
|
+
row.source_through_ordinal,
|
|
501
|
+
"source_through_ordinal",
|
|
502
|
+
),
|
|
503
|
+
addedOverrideCount: numberFromSql(row.added_override_count, "added_override_count"),
|
|
504
|
+
activeOverrideCount: numberFromSql(
|
|
505
|
+
row.active_override_count,
|
|
506
|
+
"active_override_count",
|
|
507
|
+
),
|
|
508
|
+
activeOverrideManifestSha256: sha256FromSql(
|
|
509
|
+
row.active_override_manifest_sha256,
|
|
510
|
+
"active_override_manifest_sha256",
|
|
511
|
+
),
|
|
512
|
+
canonicalSequenceSha256: sha256FromSql(
|
|
513
|
+
row.canonical_sequence_sha256,
|
|
514
|
+
"canonical_sequence_sha256",
|
|
515
|
+
),
|
|
516
|
+
renderedMessageSha256: sha256FromSql(
|
|
517
|
+
row.rendered_message_sha256,
|
|
518
|
+
"rendered_message_sha256",
|
|
519
|
+
),
|
|
520
|
+
createdAt: timestampFromSql(row.created_at, "created_at"),
|
|
521
|
+
};
|
|
522
|
+
if (common.keepFromOrdinal < 1) {
|
|
523
|
+
throw new Error("Context revision keep_from_ordinal must be positive.");
|
|
524
|
+
}
|
|
525
|
+
const revisionNumber = numberFromSql(row.revision_number, "revision_number");
|
|
526
|
+
const parentRevisionId = nullableStringFromSql(
|
|
527
|
+
row.parent_revision_id,
|
|
528
|
+
"parent_revision_id",
|
|
529
|
+
) as ContextRevisionId | null;
|
|
530
|
+
if (kind === "initial_full") {
|
|
531
|
+
if (
|
|
532
|
+
revisionNumber !== 1 ||
|
|
533
|
+
parentRevisionId !== null ||
|
|
534
|
+
common.sourceThroughOrdinal !== 1 ||
|
|
535
|
+
common.addedOverrideCount !== 0 ||
|
|
536
|
+
common.activeOverrideCount !== 0 ||
|
|
537
|
+
common.keepFromOrdinal !== 1 ||
|
|
538
|
+
row.policy_version !== null ||
|
|
539
|
+
row.renderer_format !== null ||
|
|
540
|
+
row.plan_sha256 !== null ||
|
|
541
|
+
row.change_manifest_sha256 !== null ||
|
|
542
|
+
row.activation_manifest_sha256 !== null ||
|
|
543
|
+
!hasNoRetirementFields(row)
|
|
544
|
+
) {
|
|
545
|
+
throw new Error("Initial context revision row is invalid.");
|
|
546
|
+
}
|
|
547
|
+
return Object.freeze({
|
|
548
|
+
...common,
|
|
549
|
+
revisionNumber: 1,
|
|
550
|
+
parentRevisionId: null,
|
|
551
|
+
kind,
|
|
552
|
+
keepFromOrdinal: 1,
|
|
553
|
+
sourceThroughOrdinal: 1,
|
|
554
|
+
addedOverrideCount: 0,
|
|
555
|
+
activeOverrideCount: 0,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (
|
|
559
|
+
kind === "swap_only" &&
|
|
560
|
+
(revisionNumber < 2 ||
|
|
561
|
+
parentRevisionId === null ||
|
|
562
|
+
common.addedOverrideCount < 1 ||
|
|
563
|
+
common.activeOverrideCount < common.addedOverrideCount)
|
|
564
|
+
) {
|
|
565
|
+
throw new Error("Swap context revision row is invalid.");
|
|
566
|
+
}
|
|
567
|
+
if (kind === "swap_only") {
|
|
568
|
+
if (
|
|
569
|
+
row.change_manifest_sha256 !== null ||
|
|
570
|
+
row.activation_manifest_sha256 !== null ||
|
|
571
|
+
!hasNoRetirementFields(row)
|
|
572
|
+
) {
|
|
573
|
+
throw new Error("Swap context revision has a change manifest.");
|
|
574
|
+
}
|
|
575
|
+
return Object.freeze({
|
|
576
|
+
...common,
|
|
577
|
+
revisionNumber,
|
|
578
|
+
parentRevisionId: parentRevisionId!,
|
|
579
|
+
kind,
|
|
580
|
+
policyVersion: enumFromSql(
|
|
581
|
+
row.policy_version,
|
|
582
|
+
["swap-only-v1"] as const,
|
|
583
|
+
"context revision policy",
|
|
584
|
+
),
|
|
585
|
+
rendererFormat: enumFromSql(
|
|
586
|
+
row.renderer_format,
|
|
587
|
+
[SWAP_OBSERVATION_FORMAT] as const,
|
|
588
|
+
"context revision renderer format",
|
|
589
|
+
),
|
|
590
|
+
planSha256: sha256FromSql(row.plan_sha256, "plan_sha256"),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
if (kind === "skills_update") {
|
|
594
|
+
if (
|
|
595
|
+
revisionNumber < 2 ||
|
|
596
|
+
parentRevisionId === null ||
|
|
597
|
+
common.addedOverrideCount < 1 ||
|
|
598
|
+
common.activeOverrideCount < common.addedOverrideCount ||
|
|
599
|
+
row.plan_sha256 !== null ||
|
|
600
|
+
!hasNoRetirementFields(row)
|
|
601
|
+
) {
|
|
602
|
+
throw new Error("Agent Skills context revision row is invalid.");
|
|
603
|
+
}
|
|
604
|
+
return Object.freeze({
|
|
605
|
+
...common,
|
|
606
|
+
revisionNumber,
|
|
607
|
+
parentRevisionId,
|
|
608
|
+
kind,
|
|
609
|
+
policyVersion: enumFromSql(
|
|
610
|
+
row.policy_version,
|
|
611
|
+
[SKILL_POLICY_VERSION] as const,
|
|
612
|
+
"Agent Skills context revision policy",
|
|
613
|
+
),
|
|
614
|
+
rendererFormat: enumFromSql(
|
|
615
|
+
row.renderer_format,
|
|
616
|
+
[SKILL_ACTIVATION_RECEIPT_FORMAT] as const,
|
|
617
|
+
"Agent Skills context revision renderer format",
|
|
618
|
+
),
|
|
619
|
+
changeManifestSha256: sha256FromSql(
|
|
620
|
+
row.change_manifest_sha256,
|
|
621
|
+
"change_manifest_sha256",
|
|
622
|
+
),
|
|
623
|
+
activationManifestSha256: sha256FromSql(
|
|
624
|
+
row.activation_manifest_sha256,
|
|
625
|
+
"activation_manifest_sha256",
|
|
626
|
+
),
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
if (kind === "prefix_retirement") {
|
|
630
|
+
const retiredThroughOrdinal = numberFromSql(
|
|
631
|
+
row.retired_through_ordinal,
|
|
632
|
+
"retired_through_ordinal",
|
|
633
|
+
);
|
|
634
|
+
const retiredTurnCount = numberFromSql(
|
|
635
|
+
row.retired_turn_count,
|
|
636
|
+
"retired_turn_count",
|
|
637
|
+
);
|
|
638
|
+
const retiredFrameCount = numberFromSql(
|
|
639
|
+
row.retired_frame_count,
|
|
640
|
+
"retired_frame_count",
|
|
641
|
+
);
|
|
642
|
+
const retiredMessageCount = numberFromSql(
|
|
643
|
+
row.retired_message_count,
|
|
644
|
+
"retired_message_count",
|
|
645
|
+
);
|
|
646
|
+
if (
|
|
647
|
+
revisionNumber < 2 ||
|
|
648
|
+
parentRevisionId === null ||
|
|
649
|
+
common.keepFromOrdinal <= 1 ||
|
|
650
|
+
common.addedOverrideCount !== 0 ||
|
|
651
|
+
retiredThroughOrdinal !== common.keepFromOrdinal - 1 ||
|
|
652
|
+
retiredTurnCount < 1 ||
|
|
653
|
+
retiredFrameCount < 1 ||
|
|
654
|
+
retiredMessageCount < 1 ||
|
|
655
|
+
row.renderer_format !== null ||
|
|
656
|
+
row.change_manifest_sha256 !== null ||
|
|
657
|
+
row.activation_manifest_sha256 !== null
|
|
658
|
+
) {
|
|
659
|
+
throw new Error("Prefix retirement revision row is invalid.");
|
|
660
|
+
}
|
|
661
|
+
return Object.freeze({
|
|
662
|
+
...common,
|
|
663
|
+
revisionNumber,
|
|
664
|
+
parentRevisionId,
|
|
665
|
+
kind,
|
|
666
|
+
addedOverrideCount: 0,
|
|
667
|
+
policyVersion: enumFromSql(
|
|
668
|
+
row.policy_version,
|
|
669
|
+
["recall-first-retirement-v1"] as const,
|
|
670
|
+
"context revision policy",
|
|
671
|
+
),
|
|
672
|
+
planSha256: sha256FromSql(row.plan_sha256, "plan_sha256"),
|
|
673
|
+
retiredThroughOrdinal,
|
|
674
|
+
retiredTurnCount,
|
|
675
|
+
retiredFrameCount,
|
|
676
|
+
retiredMessageCount,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
if (
|
|
680
|
+
revisionNumber < 2 ||
|
|
681
|
+
parentRevisionId === null ||
|
|
682
|
+
common.addedOverrideCount !== 0 ||
|
|
683
|
+
row.policy_version !== null ||
|
|
684
|
+
row.renderer_format !== null ||
|
|
685
|
+
row.plan_sha256 !== null ||
|
|
686
|
+
row.activation_manifest_sha256 !== null ||
|
|
687
|
+
!hasNoRetirementFields(row)
|
|
688
|
+
) {
|
|
689
|
+
throw new Error("Surface context revision row is invalid.");
|
|
690
|
+
}
|
|
691
|
+
return Object.freeze({
|
|
692
|
+
...common,
|
|
693
|
+
revisionNumber,
|
|
694
|
+
parentRevisionId,
|
|
695
|
+
kind,
|
|
696
|
+
addedOverrideCount: 0,
|
|
697
|
+
changeManifestSha256: sha256FromSql(
|
|
698
|
+
row.change_manifest_sha256,
|
|
699
|
+
"change_manifest_sha256",
|
|
700
|
+
),
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function hasNoRetirementFields(row: Record<string, unknown>): boolean {
|
|
705
|
+
return (
|
|
706
|
+
row.retired_through_ordinal === null &&
|
|
707
|
+
row.retired_turn_count === null &&
|
|
708
|
+
row.retired_frame_count === null &&
|
|
709
|
+
row.retired_message_count === null
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export function decodeStoredSwapOverride(rowValue: unknown): StoredContextOverrideV8 {
|
|
714
|
+
const row = recordFromSql(rowValue, "context override");
|
|
715
|
+
if (row.representation !== "swapped") {
|
|
716
|
+
throw new Error("Context override representation must be swapped.");
|
|
717
|
+
}
|
|
718
|
+
return Object.freeze({
|
|
719
|
+
introducedRevisionId: stringFromSql(
|
|
720
|
+
row.introduced_revision_id,
|
|
721
|
+
"introduced_revision_id",
|
|
722
|
+
) as ContextRevisionId,
|
|
723
|
+
frameId: stringFromSql(row.frame_id, "frame_id") as ProtocolFrameId,
|
|
724
|
+
messageId: stringFromSql(row.message_id, "message_id") as MessageId,
|
|
725
|
+
ordinal: numberFromSql(row.ordinal, "ordinal"),
|
|
726
|
+
rendererFormat: enumFromSql(
|
|
727
|
+
row.renderer_format,
|
|
728
|
+
[
|
|
729
|
+
SWAP_OBSERVATION_FORMAT,
|
|
730
|
+
SWAP_TOOL_IMAGE_FORMAT,
|
|
731
|
+
SKILL_ACTIVATION_RECEIPT_FORMAT,
|
|
732
|
+
] as const,
|
|
733
|
+
"context override renderer format",
|
|
734
|
+
),
|
|
735
|
+
source: stringFromSql(row.source, "source") as StoredContextOverrideV8["source"],
|
|
736
|
+
originalContentSha256: sha256FromSql(
|
|
737
|
+
row.original_content_sha256,
|
|
738
|
+
"original_content_sha256",
|
|
739
|
+
),
|
|
740
|
+
renderedContent: stringFromSql(row.rendered_content, "rendered_content"),
|
|
741
|
+
renderedContentSha256: sha256FromSql(
|
|
742
|
+
row.rendered_content_sha256,
|
|
743
|
+
"rendered_content_sha256",
|
|
744
|
+
),
|
|
745
|
+
originalBytes: numberFromSql(row.original_bytes, "original_bytes"),
|
|
746
|
+
renderedBytes: numberFromSql(row.rendered_bytes, "rendered_bytes"),
|
|
747
|
+
byteSavings: numberFromSql(row.byte_savings, "byte_savings"),
|
|
748
|
+
createdAt: timestampFromSql(row.created_at, "created_at"),
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export function stripStoredOverride(override: StoredContextOverrideV8): SwapOverride {
|
|
753
|
+
return Object.freeze({
|
|
754
|
+
frameId: override.frameId,
|
|
755
|
+
messageId: override.messageId,
|
|
756
|
+
ordinal: override.ordinal,
|
|
757
|
+
source: override.source,
|
|
758
|
+
originalContentSha256: override.originalContentSha256,
|
|
759
|
+
renderedContent: override.renderedContent,
|
|
760
|
+
renderedContentSha256: override.renderedContentSha256,
|
|
761
|
+
originalBytes: override.originalBytes,
|
|
762
|
+
renderedBytes: override.renderedBytes,
|
|
763
|
+
byteSavings: override.byteSavings,
|
|
764
|
+
...(override.rendererFormat === SWAP_OBSERVATION_FORMAT
|
|
765
|
+
? {}
|
|
766
|
+
: { rendererFormat: override.rendererFormat }),
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export function decodeSkillActivation(rowValue: unknown): StoredSkillActivation {
|
|
771
|
+
const row = recordFromSql(rowValue, "skill activation");
|
|
772
|
+
const state = enumFromSql(
|
|
773
|
+
row.state,
|
|
774
|
+
["pending", "dispatched", "promoted", "rejected"] as const,
|
|
775
|
+
"skill activation state",
|
|
776
|
+
);
|
|
777
|
+
const dispatchedIterationId = nullableStringFromSql(
|
|
778
|
+
row.dispatched_iteration_id,
|
|
779
|
+
"dispatched_iteration_id",
|
|
780
|
+
) as IterationId | null;
|
|
781
|
+
const settledRevisionId = nullableStringFromSql(
|
|
782
|
+
row.settled_revision_id,
|
|
783
|
+
"settled_revision_id",
|
|
784
|
+
) as ContextRevisionId | null;
|
|
785
|
+
const rejectionReason = nullableStringFromSql(
|
|
786
|
+
row.rejection_reason,
|
|
787
|
+
"rejection_reason",
|
|
788
|
+
);
|
|
789
|
+
if (
|
|
790
|
+
(state === "pending" &&
|
|
791
|
+
(dispatchedIterationId !== null ||
|
|
792
|
+
settledRevisionId !== null ||
|
|
793
|
+
rejectionReason !== null)) ||
|
|
794
|
+
(state === "dispatched" &&
|
|
795
|
+
(dispatchedIterationId === null ||
|
|
796
|
+
settledRevisionId !== null ||
|
|
797
|
+
rejectionReason !== null)) ||
|
|
798
|
+
(state === "promoted" &&
|
|
799
|
+
(dispatchedIterationId === null ||
|
|
800
|
+
settledRevisionId === null ||
|
|
801
|
+
rejectionReason !== null)) ||
|
|
802
|
+
(state === "rejected" &&
|
|
803
|
+
(settledRevisionId === null ||
|
|
804
|
+
rejectionReason === null ||
|
|
805
|
+
rejectionReason.trim() === ""))
|
|
806
|
+
) {
|
|
807
|
+
throw new Error("Skill activation lifecycle fields are invalid.");
|
|
808
|
+
}
|
|
809
|
+
return Object.freeze({
|
|
810
|
+
activationMessageId: stringFromSql(
|
|
811
|
+
row.activation_message_id,
|
|
812
|
+
"activation_message_id",
|
|
813
|
+
) as MessageId,
|
|
814
|
+
toolCallId: stringFromSql(row.tool_call_id, "tool_call_id") as ToolCallId,
|
|
815
|
+
sessionId: stringFromSql(row.session_id, "session_id") as SessionId,
|
|
816
|
+
name: stringFromSql(row.name, "skill activation name"),
|
|
817
|
+
scope: enumFromSql(
|
|
818
|
+
row.scope,
|
|
819
|
+
["project", "user"] as const,
|
|
820
|
+
"skill activation scope",
|
|
821
|
+
),
|
|
822
|
+
skillFileSha256: sha256FromSql(row.skill_file_sha256, "skill_file_sha256"),
|
|
823
|
+
state,
|
|
824
|
+
...(dispatchedIterationId === null ? {} : { dispatchedIterationId }),
|
|
825
|
+
...(settledRevisionId === null ? {} : { settledRevisionId }),
|
|
826
|
+
...(rejectionReason === null ? {} : { rejectionReason }),
|
|
827
|
+
createdAt: timestampFromSql(row.created_at, "created_at"),
|
|
828
|
+
updatedAt: timestampFromSql(row.updated_at, "updated_at"),
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
export function protocolPrefixView(
|
|
833
|
+
canonical: ProtocolContextView,
|
|
834
|
+
throughOrdinal: number,
|
|
835
|
+
): ProtocolContextView {
|
|
836
|
+
const messages = canonical.messages.filter(
|
|
837
|
+
(message) => message.ordinal <= throughOrdinal,
|
|
838
|
+
);
|
|
839
|
+
const messageIds = new Set(messages.map((message) => message.messageId));
|
|
840
|
+
return Object.freeze({
|
|
841
|
+
sessionId: canonical.sessionId,
|
|
842
|
+
faulted: false,
|
|
843
|
+
frames: Object.freeze(
|
|
844
|
+
canonical.frames.filter(
|
|
845
|
+
(frame) =>
|
|
846
|
+
frame.state === "closed" &&
|
|
847
|
+
frame.lastOrdinal !== undefined &&
|
|
848
|
+
frame.lastOrdinal <= throughOrdinal,
|
|
849
|
+
),
|
|
850
|
+
),
|
|
851
|
+
messages: Object.freeze(messages),
|
|
852
|
+
toolResults: Object.freeze(
|
|
853
|
+
canonical.toolResults.filter((result) => messageIds.has(result.toolMessageId)),
|
|
854
|
+
),
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export function decodeStoredToolCalls(json: string): readonly ToolCall[] {
|
|
859
|
+
const value = parseJson(json, "tool_calls_json");
|
|
860
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
861
|
+
throw new Error("tool_calls_json must contain a non-empty array.");
|
|
862
|
+
}
|
|
863
|
+
const calls = value.map((entry, index): ToolCall => {
|
|
864
|
+
const call = recordFromSql(entry, `tool call ${index}`);
|
|
865
|
+
assertObjectKeys(
|
|
866
|
+
call,
|
|
867
|
+
[
|
|
868
|
+
"args",
|
|
869
|
+
"argsParseError",
|
|
870
|
+
"iterationId",
|
|
871
|
+
"iterationNumber",
|
|
872
|
+
"name",
|
|
873
|
+
"providerToolCallId",
|
|
874
|
+
"rawArgs",
|
|
875
|
+
"sessionId",
|
|
876
|
+
"toolCallId",
|
|
877
|
+
"toolCallNumber",
|
|
878
|
+
"turnId",
|
|
879
|
+
"turnNumber",
|
|
880
|
+
],
|
|
881
|
+
[
|
|
882
|
+
"args",
|
|
883
|
+
"iterationId",
|
|
884
|
+
"iterationNumber",
|
|
885
|
+
"name",
|
|
886
|
+
"providerToolCallId",
|
|
887
|
+
"sessionId",
|
|
888
|
+
"toolCallId",
|
|
889
|
+
"toolCallNumber",
|
|
890
|
+
"turnId",
|
|
891
|
+
"turnNumber",
|
|
892
|
+
],
|
|
893
|
+
`tool call ${index}`,
|
|
894
|
+
);
|
|
895
|
+
const toolCallNumber = numberFromJson(call.toolCallNumber, "toolCallNumber");
|
|
896
|
+
return {
|
|
897
|
+
sessionId: stringFromSql(call.sessionId, "sessionId") as SessionId,
|
|
898
|
+
turnId: stringFromSql(call.turnId, "turnId") as TurnId,
|
|
899
|
+
turnNumber: numberFromJson(call.turnNumber, "turnNumber"),
|
|
900
|
+
iterationId: stringFromSql(call.iterationId, "iterationId") as IterationId,
|
|
901
|
+
iterationNumber: numberFromJson(call.iterationNumber, "iterationNumber"),
|
|
902
|
+
toolCallId: stringFromSql(call.toolCallId, "toolCallId") as ToolCallId,
|
|
903
|
+
toolCallNumber,
|
|
904
|
+
providerToolCallId: stringFromSql(call.providerToolCallId, "providerToolCallId"),
|
|
905
|
+
name: stringFromSql(call.name, "name"),
|
|
906
|
+
args: immutableCanonicalClone(call.args),
|
|
907
|
+
...(call.rawArgs === undefined
|
|
908
|
+
? {}
|
|
909
|
+
: { rawArgs: stringFromSql(call.rawArgs, "rawArgs") }),
|
|
910
|
+
...(call.argsParseError === undefined
|
|
911
|
+
? {}
|
|
912
|
+
: {
|
|
913
|
+
argsParseError: stringFromSql(call.argsParseError, "argsParseError"),
|
|
914
|
+
}),
|
|
915
|
+
};
|
|
916
|
+
});
|
|
917
|
+
return Object.freeze(calls);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function decodeProjectInstructionManifest(value: unknown): ProjectInstructionManifest {
|
|
921
|
+
const record = recordFromSql(value, "project instruction manifest");
|
|
922
|
+
assertObjectKeys(
|
|
923
|
+
record,
|
|
924
|
+
["path", "byteLength", "sha256"],
|
|
925
|
+
["path", "byteLength", "sha256"],
|
|
926
|
+
"project instruction manifest",
|
|
927
|
+
);
|
|
928
|
+
return Object.freeze({
|
|
929
|
+
path: enumFromSql(
|
|
930
|
+
record.path,
|
|
931
|
+
["AGENTS.md", "CLAUDE.md"] as const,
|
|
932
|
+
"project instruction path",
|
|
933
|
+
),
|
|
934
|
+
byteLength: numberFromJson(record.byteLength, "project instruction byteLength"),
|
|
935
|
+
sha256: sha256FromSql(record.sha256, "project instruction sha256"),
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function decodeSkillCatalogManifest(
|
|
940
|
+
value: unknown,
|
|
941
|
+
): readonly SkillCatalogManifestEntry[] {
|
|
942
|
+
if (!Array.isArray(value)) {
|
|
943
|
+
throw new Error("skill_catalog_json must contain an array.");
|
|
944
|
+
}
|
|
945
|
+
return Object.freeze(
|
|
946
|
+
value.map((entry, index) => {
|
|
947
|
+
const record = recordFromSql(entry, `skill catalog entry ${index}`);
|
|
948
|
+
assertObjectKeys(
|
|
949
|
+
record,
|
|
950
|
+
[
|
|
951
|
+
"name",
|
|
952
|
+
"scope",
|
|
953
|
+
"directorySha256",
|
|
954
|
+
"descriptionSha256",
|
|
955
|
+
"skillFileSha256",
|
|
956
|
+
"byteLength",
|
|
957
|
+
],
|
|
958
|
+
[
|
|
959
|
+
"name",
|
|
960
|
+
"scope",
|
|
961
|
+
"directorySha256",
|
|
962
|
+
"descriptionSha256",
|
|
963
|
+
"skillFileSha256",
|
|
964
|
+
"byteLength",
|
|
965
|
+
],
|
|
966
|
+
`skill catalog entry ${index}`,
|
|
967
|
+
);
|
|
968
|
+
return Object.freeze({
|
|
969
|
+
name: stringFromSql(record.name, "skill name"),
|
|
970
|
+
scope: enumFromSql(record.scope, ["project", "user"] as const, "skill scope"),
|
|
971
|
+
directorySha256: sha256FromSql(record.directorySha256, "skill directorySha256"),
|
|
972
|
+
descriptionSha256: sha256FromSql(
|
|
973
|
+
record.descriptionSha256,
|
|
974
|
+
"skill descriptionSha256",
|
|
975
|
+
),
|
|
976
|
+
skillFileSha256: sha256FromSql(record.skillFileSha256, "skill skillFileSha256"),
|
|
977
|
+
byteLength: numberFromJson(record.byteLength, "skill byteLength"),
|
|
978
|
+
});
|
|
979
|
+
}),
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function decodeActiveSkillsManifest(
|
|
984
|
+
value: unknown,
|
|
985
|
+
): readonly ActiveSkillManifestEntry[] {
|
|
986
|
+
if (!Array.isArray(value)) {
|
|
987
|
+
throw new Error("active_skills_json must contain an array.");
|
|
988
|
+
}
|
|
989
|
+
return Object.freeze(
|
|
990
|
+
value.map((entry, index) => {
|
|
991
|
+
const record = recordFromSql(entry, `active skill entry ${index}`);
|
|
992
|
+
assertObjectKeys(
|
|
993
|
+
record,
|
|
994
|
+
[
|
|
995
|
+
"name",
|
|
996
|
+
"scope",
|
|
997
|
+
"directorySha256",
|
|
998
|
+
"descriptionSha256",
|
|
999
|
+
"skillFileSha256",
|
|
1000
|
+
"byteLength",
|
|
1001
|
+
"activationMessageId",
|
|
1002
|
+
],
|
|
1003
|
+
[
|
|
1004
|
+
"name",
|
|
1005
|
+
"scope",
|
|
1006
|
+
"directorySha256",
|
|
1007
|
+
"descriptionSha256",
|
|
1008
|
+
"skillFileSha256",
|
|
1009
|
+
"byteLength",
|
|
1010
|
+
"activationMessageId",
|
|
1011
|
+
],
|
|
1012
|
+
`active skill entry ${index}`,
|
|
1013
|
+
);
|
|
1014
|
+
const [catalogEntry] = decodeSkillCatalogManifest([
|
|
1015
|
+
{
|
|
1016
|
+
name: record.name,
|
|
1017
|
+
scope: record.scope,
|
|
1018
|
+
directorySha256: record.directorySha256,
|
|
1019
|
+
descriptionSha256: record.descriptionSha256,
|
|
1020
|
+
skillFileSha256: record.skillFileSha256,
|
|
1021
|
+
byteLength: record.byteLength,
|
|
1022
|
+
},
|
|
1023
|
+
]);
|
|
1024
|
+
if (catalogEntry === undefined) {
|
|
1025
|
+
throw new Error(`Active skill entry ${index} is missing.`);
|
|
1026
|
+
}
|
|
1027
|
+
return Object.freeze({
|
|
1028
|
+
...catalogEntry,
|
|
1029
|
+
activationMessageId: stringFromSql(
|
|
1030
|
+
record.activationMessageId,
|
|
1031
|
+
"skill activationMessageId",
|
|
1032
|
+
) as MessageId,
|
|
1033
|
+
});
|
|
1034
|
+
}),
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function decodeToolDefinitions(value: unknown): readonly ToolDefinition[] {
|
|
1039
|
+
if (!Array.isArray(value)) {
|
|
1040
|
+
throw new Error("tool_definitions_json must contain an array.");
|
|
1041
|
+
}
|
|
1042
|
+
const definitions = value.map((entry, index): ToolDefinition => {
|
|
1043
|
+
const record = recordFromSql(entry, `tool definition ${index}`);
|
|
1044
|
+
assertObjectKeys(
|
|
1045
|
+
record,
|
|
1046
|
+
["name", "description", "parameters"],
|
|
1047
|
+
["name", "description", "parameters"],
|
|
1048
|
+
`tool definition ${index}`,
|
|
1049
|
+
);
|
|
1050
|
+
const parameters = recordFromSql(
|
|
1051
|
+
record.parameters,
|
|
1052
|
+
`tool definition ${index} parameters`,
|
|
1053
|
+
);
|
|
1054
|
+
return Object.freeze({
|
|
1055
|
+
name: stringFromSql(record.name, `tool definition ${index} name`),
|
|
1056
|
+
description: stringFromSql(
|
|
1057
|
+
record.description,
|
|
1058
|
+
`tool definition ${index} description`,
|
|
1059
|
+
),
|
|
1060
|
+
parameters: immutableCanonicalClone(parameters),
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
1063
|
+
return Object.freeze(definitions);
|
|
1064
|
+
}
|