tinker-agent 2.0.0 → 2.2.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 +44 -1
- package/README.md +27 -2
- package/package.json +2 -1
- package/src/agent/context-meter.ts +2 -4
- package/src/agent/runtime-session.ts +9 -2
- package/src/agent/session-ledger.ts +12 -5
- package/src/agent/tool-result-content.ts +76 -0
- package/src/agent/types.ts +14 -2
- package/src/cli/config.ts +4 -0
- package/src/cli/model-profiles.ts +41 -2
- package/src/cli/public-config-contract.ts +30 -7
- package/src/cli/runner-dependencies.ts +5 -0
- package/src/cli/tui-memory.ts +1 -0
- package/src/cli/tui-runner.tsx +4 -0
- package/src/context/compiled-context-hash.ts +2 -1
- package/src/context/compiled-context-validator.ts +13 -4
- package/src/context/context-protocol-validator.ts +33 -2
- package/src/context/context-revision-compiler.ts +2 -1
- package/src/context/context-revision.ts +8 -2
- package/src/context/context-swap-renderer.ts +46 -12
- package/src/context/prefix-retirement-planner.ts +13 -9
- package/src/context/protocol-frame.ts +74 -7
- package/src/context/swap-planner.ts +19 -14
- package/src/events/observation-text-log.ts +1 -1
- package/src/events/stdout-event-printer.ts +6 -0
- package/src/image/image-asset-store.ts +32 -3
- package/src/memory/contracts.ts +63 -3
- package/src/memory/memory-coordinator.ts +319 -49
- package/src/memory/memory-extractor.ts +48 -48
- package/src/memory/memory-get-tool.ts +86 -0
- package/src/memory/memory-search-tool.ts +122 -33
- package/src/memory/memory-store.ts +227 -20
- package/src/model/fake-model-client.ts +129 -76
- package/src/model/model-client.ts +62 -11
- package/src/model/openai-chat-mapping.ts +2 -1
- package/src/model/openai-chat-model-client.ts +22 -10
- package/src/model/openai-model-utils.ts +61 -30
- package/src/model/openai-responses-mapping.ts +25 -1
- package/src/model/openai-responses-model-client.ts +27 -11
- package/src/model/token-estimator.ts +10 -0
- package/src/observation/observation-builder.ts +100 -25
- package/src/session/session-history-reader.ts +128 -5
- package/src/session/session-schema.ts +59 -9
- package/src/session/session-store.ts +343 -196
- package/src/tools/registry.ts +18 -0
- package/src/tools/types.ts +46 -0
- package/src/tools/view-image.ts +89 -0
- package/src/tools/wait.ts +85 -0
- package/src/tui/components/memory-browser.tsx +3 -0
- package/src/tui/components/prompt-input.tsx +48 -25
- package/src/tui/event-store.ts +61 -2
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
import {
|
|
51
51
|
ContextSwapRenderer,
|
|
52
52
|
SWAP_OBSERVATION_FORMAT,
|
|
53
|
+
SWAP_TOOL_IMAGE_FORMAT,
|
|
53
54
|
} from "../context/context-swap-renderer";
|
|
54
55
|
import {
|
|
55
56
|
SUPPORTED_TOOL_OBSERVATION_FORMATS,
|
|
@@ -78,10 +79,16 @@ import type {
|
|
|
78
79
|
StoredContextOverrideV8,
|
|
79
80
|
SwapOverride,
|
|
80
81
|
} from "../context/context-revision";
|
|
81
|
-
import type { IterationIdentity, ToolCall } from "../agent/types";
|
|
82
|
+
import type { IterationIdentity, ToolCall, ToolResultContent } from "../agent/types";
|
|
83
|
+
import {
|
|
84
|
+
canonicalToolResultContentHash,
|
|
85
|
+
toolResultDisplayText,
|
|
86
|
+
validateToolResultContent,
|
|
87
|
+
} from "../agent/tool-result-content";
|
|
82
88
|
import {
|
|
83
89
|
parseImageAssetId,
|
|
84
90
|
parseImageAttachmentId,
|
|
91
|
+
validateImageAssetRef,
|
|
85
92
|
validateOriginalImageName,
|
|
86
93
|
validateUserMessage,
|
|
87
94
|
type ImageAssetRef,
|
|
@@ -108,7 +115,7 @@ import {
|
|
|
108
115
|
} from "./session-history-reader";
|
|
109
116
|
import { SessionLease } from "./session-lock";
|
|
110
117
|
import {
|
|
111
|
-
|
|
118
|
+
SESSION_SCHEMA_V10_FINGERPRINT,
|
|
112
119
|
SESSION_SCHEMA_VERSION,
|
|
113
120
|
upgradeActiveTurnRetirementContract,
|
|
114
121
|
upgradeRecallIndexContract,
|
|
@@ -144,20 +151,11 @@ import {
|
|
|
144
151
|
IMAGE_INPUT_POLICY_VERSION,
|
|
145
152
|
} from "../image/image-input-policy";
|
|
146
153
|
|
|
147
|
-
export type
|
|
154
|
+
export type SessionMediaCompatibility = {
|
|
148
155
|
readonly policyVersion: string;
|
|
149
156
|
readonly policySha256: string;
|
|
150
157
|
readonly inputModalities: readonly ("text" | "image")[];
|
|
151
|
-
readonly
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
type LegacyInputTokenEstimatorCompatibility = {
|
|
155
|
-
readonly kind: "moonshot-estimate-token-count-v1";
|
|
156
|
-
readonly coverageVersion: "full-request-v1";
|
|
157
|
-
readonly model: string;
|
|
158
|
-
readonly endpoint: string;
|
|
159
|
-
readonly timeoutMs: number;
|
|
160
|
-
readonly maxRetries: 0;
|
|
158
|
+
readonly toolResultModalities: readonly ("text" | "image")[];
|
|
161
159
|
};
|
|
162
160
|
|
|
163
161
|
export type CompletedTurnMessageSnapshot =
|
|
@@ -189,11 +187,11 @@ export type SessionCompatibilityContract = {
|
|
|
189
187
|
includeReasoningContent: boolean;
|
|
190
188
|
contextProfile: ModelContextProfile;
|
|
191
189
|
messageProtocol: ModelMessageProtocol;
|
|
192
|
-
|
|
190
|
+
media: SessionMediaCompatibility;
|
|
193
191
|
};
|
|
194
192
|
|
|
195
|
-
export type
|
|
196
|
-
schemaVersion:
|
|
193
|
+
export type StoredSessionMetaV10 = {
|
|
194
|
+
schemaVersion: 10;
|
|
197
195
|
schemaFingerprint: string;
|
|
198
196
|
initializationState: "creating" | "ready";
|
|
199
197
|
sessionId: SessionId;
|
|
@@ -220,7 +218,7 @@ export type StoredSessionMetaV9 = {
|
|
|
220
218
|
| null;
|
|
221
219
|
};
|
|
222
220
|
|
|
223
|
-
export type SessionCloseReason = NonNullable<
|
|
221
|
+
export type SessionCloseReason = NonNullable<StoredSessionMetaV10["lastCloseReason"]>;
|
|
224
222
|
|
|
225
223
|
export type SessionRecoveryResult = {
|
|
226
224
|
recoveredTurnId?: TurnId;
|
|
@@ -517,7 +515,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
517
515
|
)
|
|
518
516
|
.run(
|
|
519
517
|
SESSION_SCHEMA_VERSION,
|
|
520
|
-
|
|
518
|
+
SESSION_SCHEMA_V10_FINGERPRINT,
|
|
521
519
|
input.sessionId,
|
|
522
520
|
workspaceRoot,
|
|
523
521
|
input.modelName,
|
|
@@ -1700,7 +1698,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
1700
1698
|
messageId: message.messageId,
|
|
1701
1699
|
frameId: message.frameId,
|
|
1702
1700
|
ordinal: message.ordinal,
|
|
1703
|
-
content: message.
|
|
1701
|
+
content: message.displayText,
|
|
1704
1702
|
contentSha256: message.contentSha256,
|
|
1705
1703
|
},
|
|
1706
1704
|
name: activation.name,
|
|
@@ -2084,13 +2082,14 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2084
2082
|
for (const input of completionInputs) {
|
|
2085
2083
|
const createdAt = this.clock();
|
|
2086
2084
|
const content = observationForCompletion(input);
|
|
2085
|
+
const displayText = toolResultDisplayText(content);
|
|
2087
2086
|
const messageId = idFactory.createMessageId();
|
|
2088
2087
|
const message = immutableRecord<CanonicalMessageRecord>({
|
|
2089
2088
|
messageId,
|
|
2090
2089
|
sessionId: this.sessionId,
|
|
2091
2090
|
frameId: frame.frameId,
|
|
2092
2091
|
ordinal: view.messages.length + messages.length + 1,
|
|
2093
|
-
contentSha256:
|
|
2092
|
+
contentSha256: canonicalToolResultContentHash(content),
|
|
2094
2093
|
createdAt,
|
|
2095
2094
|
role: "tool",
|
|
2096
2095
|
turnId,
|
|
@@ -2099,6 +2098,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2099
2098
|
providerToolCallId: input.call.providerToolCallId,
|
|
2100
2099
|
name: input.call.name,
|
|
2101
2100
|
content,
|
|
2101
|
+
displayText,
|
|
2102
2102
|
origin: "runtime",
|
|
2103
2103
|
});
|
|
2104
2104
|
const completion: ToolCompletion = immutableRecord({
|
|
@@ -2111,7 +2111,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2111
2111
|
toolCallId: input.call.toolCallId,
|
|
2112
2112
|
toolMessageId: messageId,
|
|
2113
2113
|
completion,
|
|
2114
|
-
observationSha256:
|
|
2114
|
+
observationSha256: canonicalToolResultContentHash(content),
|
|
2115
2115
|
createdAt,
|
|
2116
2116
|
});
|
|
2117
2117
|
messages.push(message);
|
|
@@ -2297,6 +2297,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2297
2297
|
loadProtocolView(): ProtocolContextView {
|
|
2298
2298
|
this.requireOpen();
|
|
2299
2299
|
const imageAttachments = loadMessageImageAttachments(this.database);
|
|
2300
|
+
const toolContentBlocks = loadToolMessageContentBlocks(this.database);
|
|
2300
2301
|
const frames = this.database
|
|
2301
2302
|
.query("SELECT * FROM protocol_frames ORDER BY first_ordinal")
|
|
2302
2303
|
.all()
|
|
@@ -2307,13 +2308,21 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2307
2308
|
.map((row) => {
|
|
2308
2309
|
const record = recordFromSql(row, "message");
|
|
2309
2310
|
const messageId = stringFromSql(record.message_id, "message_id");
|
|
2310
|
-
const message = decodeMessage(
|
|
2311
|
+
const message = decodeMessage(
|
|
2312
|
+
row,
|
|
2313
|
+
imageAttachments.get(messageId),
|
|
2314
|
+
toolContentBlocks.get(messageId),
|
|
2315
|
+
);
|
|
2311
2316
|
imageAttachments.delete(messageId);
|
|
2317
|
+
toolContentBlocks.delete(messageId);
|
|
2312
2318
|
return message;
|
|
2313
2319
|
});
|
|
2314
2320
|
if (imageAttachments.size > 0) {
|
|
2315
2321
|
throw new Error("Image attachment rows reference unknown messages.");
|
|
2316
2322
|
}
|
|
2323
|
+
if (toolContentBlocks.size > 0) {
|
|
2324
|
+
throw new Error("Tool content block rows reference unknown messages.");
|
|
2325
|
+
}
|
|
2317
2326
|
const toolResults = this.database
|
|
2318
2327
|
.query(
|
|
2319
2328
|
`SELECT tr.* FROM tool_results tr
|
|
@@ -2335,21 +2344,23 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2335
2344
|
this.requireOpen();
|
|
2336
2345
|
const distinct = new Map<string, ImageAssetRef>();
|
|
2337
2346
|
for (const message of this.loadProtocolView().messages) {
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2347
|
+
const assets =
|
|
2348
|
+
message.role === "user"
|
|
2349
|
+
? (message.attachments ?? []).map(imageAssetRefFromAttachment)
|
|
2350
|
+
: message.role === "tool"
|
|
2351
|
+
? message.content.flatMap((block) =>
|
|
2352
|
+
block.type === "image" ? [block.asset] : [],
|
|
2353
|
+
)
|
|
2354
|
+
: [];
|
|
2355
|
+
for (const asset of assets) {
|
|
2356
|
+
const previous = distinct.get(asset.assetId);
|
|
2344
2357
|
if (
|
|
2345
2358
|
previous !== undefined &&
|
|
2346
2359
|
stableJsonStringify(previous) !== stableJsonStringify(asset)
|
|
2347
2360
|
) {
|
|
2348
|
-
throw new Error(
|
|
2349
|
-
`Conflicting metadata for image asset ${attachment.assetId}.`,
|
|
2350
|
-
);
|
|
2361
|
+
throw new Error(`Conflicting metadata for image asset ${asset.assetId}.`);
|
|
2351
2362
|
}
|
|
2352
|
-
distinct.set(
|
|
2363
|
+
distinct.set(asset.assetId, asset);
|
|
2353
2364
|
}
|
|
2354
2365
|
}
|
|
2355
2366
|
if (distinct.size === 0) {
|
|
@@ -2367,7 +2378,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2367
2378
|
try {
|
|
2368
2379
|
if (
|
|
2369
2380
|
meta.sessionId !== this.sessionId ||
|
|
2370
|
-
meta.schemaFingerprint !==
|
|
2381
|
+
meta.schemaFingerprint !== SESSION_SCHEMA_V10_FINGERPRINT
|
|
2371
2382
|
) {
|
|
2372
2383
|
throw new Error("Session metadata identity or schema fingerprint changed.");
|
|
2373
2384
|
}
|
|
@@ -2413,7 +2424,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2413
2424
|
}
|
|
2414
2425
|
}
|
|
2415
2426
|
|
|
2416
|
-
readMeta():
|
|
2427
|
+
readMeta(): StoredSessionMetaV10 {
|
|
2417
2428
|
this.requireOpen();
|
|
2418
2429
|
const rows = this.database.query("SELECT * FROM session_meta").all();
|
|
2419
2430
|
if (rows.length !== 1) {
|
|
@@ -2549,7 +2560,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
2549
2560
|
const meta = this.readMeta();
|
|
2550
2561
|
if (
|
|
2551
2562
|
meta.sessionId !== this.sessionId ||
|
|
2552
|
-
meta.schemaFingerprint !==
|
|
2563
|
+
meta.schemaFingerprint !== SESSION_SCHEMA_V10_FINGERPRINT ||
|
|
2553
2564
|
meta.initializationState !== "ready" ||
|
|
2554
2565
|
meta.activeRevisionId === null
|
|
2555
2566
|
) {
|
|
@@ -3047,7 +3058,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
3047
3058
|
}
|
|
3048
3059
|
|
|
3049
3060
|
private loadValidatedContextSnapshot(
|
|
3050
|
-
meta:
|
|
3061
|
+
meta: StoredSessionMetaV10,
|
|
3051
3062
|
canonical: ProtocolContextView,
|
|
3052
3063
|
): StoredContextSnapshotV8 {
|
|
3053
3064
|
const activeRevisionId = requireActiveRevisionId(meta);
|
|
@@ -3333,7 +3344,8 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
3333
3344
|
if (
|
|
3334
3345
|
(revision?.kind !== "swap_only" && revision?.kind !== "skills_update") ||
|
|
3335
3346
|
(revision.kind === "swap_only" &&
|
|
3336
|
-
override.rendererFormat !== SWAP_OBSERVATION_FORMAT
|
|
3347
|
+
override.rendererFormat !== SWAP_OBSERVATION_FORMAT &&
|
|
3348
|
+
override.rendererFormat !== SWAP_TOOL_IMAGE_FORMAT) ||
|
|
3337
3349
|
(revision.kind === "skills_update" &&
|
|
3338
3350
|
override.rendererFormat !== SKILL_ACTIVATION_RECEIPT_FORMAT) ||
|
|
3339
3351
|
revisionNumberById.get(revision.revisionId) === undefined ||
|
|
@@ -3350,7 +3362,8 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
3350
3362
|
throw new Error("Stored context override canonical identity is invalid.");
|
|
3351
3363
|
}
|
|
3352
3364
|
const rendered =
|
|
3353
|
-
override.rendererFormat === SWAP_OBSERVATION_FORMAT
|
|
3365
|
+
override.rendererFormat === SWAP_OBSERVATION_FORMAT ||
|
|
3366
|
+
override.rendererFormat === SWAP_TOOL_IMAGE_FORMAT
|
|
3354
3367
|
? this.swapRenderer.render({ message, result })
|
|
3355
3368
|
: this.renderStoredSkillReceipt(message, result, revision.revisionId);
|
|
3356
3369
|
if (
|
|
@@ -3469,7 +3482,7 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
3469
3482
|
messageId: message.messageId,
|
|
3470
3483
|
frameId: message.frameId,
|
|
3471
3484
|
ordinal: message.ordinal,
|
|
3472
|
-
content: message.
|
|
3485
|
+
content: message.displayText,
|
|
3473
3486
|
contentSha256: message.contentSha256,
|
|
3474
3487
|
},
|
|
3475
3488
|
name: activation.name,
|
|
@@ -3495,7 +3508,10 @@ export class SessionStore implements SessionLedgerCommitter {
|
|
|
3495
3508
|
: decodeMeasuredContextState(row, this.sessionId);
|
|
3496
3509
|
}
|
|
3497
3510
|
|
|
3498
|
-
private validateCounters(
|
|
3511
|
+
private validateCounters(
|
|
3512
|
+
meta: StoredSessionMetaV10,
|
|
3513
|
+
view: ProtocolContextView,
|
|
3514
|
+
): void {
|
|
3499
3515
|
const turns = this.database
|
|
3500
3516
|
.query("SELECT * FROM turns ORDER BY turn_number")
|
|
3501
3517
|
.all() as Array<Record<string, unknown>>;
|
|
@@ -3711,7 +3727,8 @@ export function createSessionCompatibilityContract(input: {
|
|
|
3711
3727
|
includeReasoningContent: boolean;
|
|
3712
3728
|
contextProfile: ModelContextProfile;
|
|
3713
3729
|
messageProtocol: ModelMessageProtocol;
|
|
3714
|
-
inputModalities
|
|
3730
|
+
inputModalities: readonly ("text" | "image")[];
|
|
3731
|
+
toolResultModalities: readonly ("text" | "image")[];
|
|
3715
3732
|
}): SessionCompatibilityContract {
|
|
3716
3733
|
if (input.modelName.trim() === "") {
|
|
3717
3734
|
throw new Error("Session compatibility model name must not be empty.");
|
|
@@ -3728,14 +3745,18 @@ export function createSessionCompatibilityContract(input: {
|
|
|
3728
3745
|
) {
|
|
3729
3746
|
throw new Error("Session compatibility message protocol is invalid.");
|
|
3730
3747
|
}
|
|
3731
|
-
const inputModalities = normalizeInputModalities(input.inputModalities
|
|
3748
|
+
const inputModalities = normalizeInputModalities(input.inputModalities);
|
|
3749
|
+
const toolResultModalities = normalizeInputModalities(input.toolResultModalities);
|
|
3750
|
+
if (toolResultModalities.includes("image") && !inputModalities.includes("image")) {
|
|
3751
|
+
throw new Error("Session compatibility image tool results require image input.");
|
|
3752
|
+
}
|
|
3732
3753
|
return Object.freeze({
|
|
3733
3754
|
modelName: input.modelName,
|
|
3734
3755
|
...(input.profileName === undefined ? {} : { profileName: input.profileName }),
|
|
3735
3756
|
includeReasoningContent: input.includeReasoningContent,
|
|
3736
3757
|
contextProfile: Object.freeze(createModelContextProfile(input.contextProfile)),
|
|
3737
3758
|
messageProtocol: immutableCanonicalClone(input.messageProtocol),
|
|
3738
|
-
|
|
3759
|
+
media: Object.freeze({
|
|
3739
3760
|
policyVersion: IMAGE_INPUT_POLICY_VERSION,
|
|
3740
3761
|
policySha256: sha256(
|
|
3741
3762
|
stableJsonStringify({
|
|
@@ -3744,6 +3765,7 @@ export function createSessionCompatibilityContract(input: {
|
|
|
3744
3765
|
}),
|
|
3745
3766
|
),
|
|
3746
3767
|
inputModalities,
|
|
3768
|
+
toolResultModalities,
|
|
3747
3769
|
}),
|
|
3748
3770
|
});
|
|
3749
3771
|
}
|
|
@@ -3759,7 +3781,8 @@ function normalizeSessionCompatibilityContract(
|
|
|
3759
3781
|
includeReasoningContent: contract.includeReasoningContent,
|
|
3760
3782
|
contextProfile: contract.contextProfile,
|
|
3761
3783
|
messageProtocol: contract.messageProtocol,
|
|
3762
|
-
inputModalities: contract.
|
|
3784
|
+
inputModalities: contract.media.inputModalities,
|
|
3785
|
+
toolResultModalities: contract.media.toolResultModalities,
|
|
3763
3786
|
});
|
|
3764
3787
|
}
|
|
3765
3788
|
|
|
@@ -3779,40 +3802,6 @@ function normalizeInputModalities(
|
|
|
3779
3802
|
);
|
|
3780
3803
|
}
|
|
3781
3804
|
|
|
3782
|
-
function validateTokenEstimatorCompatibility(
|
|
3783
|
-
estimator: LegacyInputTokenEstimatorCompatibility,
|
|
3784
|
-
): void {
|
|
3785
|
-
if (
|
|
3786
|
-
estimator.kind !== "moonshot-estimate-token-count-v1" ||
|
|
3787
|
-
estimator.coverageVersion !== "full-request-v1" ||
|
|
3788
|
-
estimator.model.trim() === "" ||
|
|
3789
|
-
!isNormalizedEstimatorEndpoint(estimator.endpoint) ||
|
|
3790
|
-
!Number.isSafeInteger(estimator.timeoutMs) ||
|
|
3791
|
-
estimator.timeoutMs < 1_000 ||
|
|
3792
|
-
estimator.timeoutMs > 60_000 ||
|
|
3793
|
-
estimator.maxRetries !== 0
|
|
3794
|
-
) {
|
|
3795
|
-
throw new Error("Session compatibility token estimator identity is invalid.");
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
|
|
3799
|
-
function isNormalizedEstimatorEndpoint(value: string): boolean {
|
|
3800
|
-
try {
|
|
3801
|
-
const url = new URL(value);
|
|
3802
|
-
return (
|
|
3803
|
-
(url.protocol === "https:" || url.protocol === "http:") &&
|
|
3804
|
-
url.username === "" &&
|
|
3805
|
-
url.password === "" &&
|
|
3806
|
-
url.search === "" &&
|
|
3807
|
-
url.hash === "" &&
|
|
3808
|
-
url.pathname.endsWith("/tokenizers/estimate-token-count") &&
|
|
3809
|
-
url.toString() === value
|
|
3810
|
-
);
|
|
3811
|
-
} catch {
|
|
3812
|
-
return false;
|
|
3813
|
-
}
|
|
3814
|
-
}
|
|
3815
|
-
|
|
3816
3805
|
export function sessionDatabasePath(
|
|
3817
3806
|
workspaceRoot: string,
|
|
3818
3807
|
sessionId: SessionId,
|
|
@@ -3866,7 +3855,7 @@ function insertMessage(database: Database, message: CanonicalMessageRecord): voi
|
|
|
3866
3855
|
message.role,
|
|
3867
3856
|
turnId,
|
|
3868
3857
|
iterationId,
|
|
3869
|
-
message.content,
|
|
3858
|
+
message.role === "tool" ? message.displayText : message.content,
|
|
3870
3859
|
message.contentSha256,
|
|
3871
3860
|
assistant?.reasoningContent ?? null,
|
|
3872
3861
|
reasoningPresent,
|
|
@@ -3884,6 +3873,9 @@ function insertMessage(database: Database, message: CanonicalMessageRecord): voi
|
|
|
3884
3873
|
if (message.role === "user" && message.attachments !== undefined) {
|
|
3885
3874
|
insertMessageImageAttachments(database, message);
|
|
3886
3875
|
}
|
|
3876
|
+
if (message.role === "tool") {
|
|
3877
|
+
insertToolMessageContentBlocks(database, message);
|
|
3878
|
+
}
|
|
3887
3879
|
}
|
|
3888
3880
|
|
|
3889
3881
|
function insertMessageImageAttachments(
|
|
@@ -3901,45 +3893,11 @@ function insertMessageImageAttachments(
|
|
|
3901
3893
|
}
|
|
3902
3894
|
for (let position = 0; position < message.attachments!.length; position += 1) {
|
|
3903
3895
|
const attachment = requireItem(message.attachments!, position, "image attachment");
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
.get(attachment.assetId) as {
|
|
3910
|
-
mime_type: unknown;
|
|
3911
|
-
byte_length: unknown;
|
|
3912
|
-
width: unknown;
|
|
3913
|
-
height: unknown;
|
|
3914
|
-
created_at: unknown;
|
|
3915
|
-
} | null;
|
|
3916
|
-
if (existing === null) {
|
|
3917
|
-
database
|
|
3918
|
-
.query(
|
|
3919
|
-
`INSERT INTO image_assets (
|
|
3920
|
-
asset_id, mime_type, byte_length, width, height, created_at
|
|
3921
|
-
) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
3922
|
-
)
|
|
3923
|
-
.run(
|
|
3924
|
-
attachment.assetId,
|
|
3925
|
-
attachment.mimeType,
|
|
3926
|
-
attachment.byteLength,
|
|
3927
|
-
attachment.width,
|
|
3928
|
-
attachment.height,
|
|
3929
|
-
message.createdAt,
|
|
3930
|
-
);
|
|
3931
|
-
} else {
|
|
3932
|
-
timestampFromSql(existing.created_at, "image asset created_at");
|
|
3933
|
-
if (
|
|
3934
|
-
existing.mime_type !== attachment.mimeType ||
|
|
3935
|
-
numberFromSql(existing.byte_length, "image asset byte_length") !==
|
|
3936
|
-
attachment.byteLength ||
|
|
3937
|
-
numberFromSql(existing.width, "image asset width") !== attachment.width ||
|
|
3938
|
-
numberFromSql(existing.height, "image asset height") !== attachment.height
|
|
3939
|
-
) {
|
|
3940
|
-
throw new Error(`Image asset metadata conflicts for ${attachment.assetId}.`);
|
|
3941
|
-
}
|
|
3942
|
-
}
|
|
3896
|
+
ensureImageAsset(
|
|
3897
|
+
database,
|
|
3898
|
+
imageAssetRefFromAttachment(attachment),
|
|
3899
|
+
message.createdAt,
|
|
3900
|
+
);
|
|
3943
3901
|
database
|
|
3944
3902
|
.query(
|
|
3945
3903
|
`INSERT INTO message_image_attachments (
|
|
@@ -3960,6 +3918,84 @@ function insertMessageImageAttachments(
|
|
|
3960
3918
|
}
|
|
3961
3919
|
}
|
|
3962
3920
|
|
|
3921
|
+
function insertToolMessageContentBlocks(
|
|
3922
|
+
database: Database,
|
|
3923
|
+
message: Extract<CanonicalMessageRecord, { role: "tool" }>,
|
|
3924
|
+
): void {
|
|
3925
|
+
validateToolResultContent(message.content);
|
|
3926
|
+
if (
|
|
3927
|
+
canonicalToolResultContentHash(message.content) !== message.contentSha256 ||
|
|
3928
|
+
toolResultDisplayText(message.content) !== message.displayText
|
|
3929
|
+
) {
|
|
3930
|
+
throw new Error("Tool content blocks do not match canonical message metadata.");
|
|
3931
|
+
}
|
|
3932
|
+
for (let position = 0; position < message.content.length; position += 1) {
|
|
3933
|
+
const block = requireItem(message.content, position, "tool content block");
|
|
3934
|
+
if (block.type === "image") {
|
|
3935
|
+
ensureImageAsset(database, block.asset, message.createdAt);
|
|
3936
|
+
}
|
|
3937
|
+
database
|
|
3938
|
+
.query(
|
|
3939
|
+
`INSERT INTO tool_message_content_blocks (
|
|
3940
|
+
message_id, position, kind, text_content, asset_id
|
|
3941
|
+
) VALUES (?, ?, ?, ?, ?)`,
|
|
3942
|
+
)
|
|
3943
|
+
.run(
|
|
3944
|
+
message.messageId,
|
|
3945
|
+
position,
|
|
3946
|
+
block.type,
|
|
3947
|
+
block.type === "text" ? block.text : null,
|
|
3948
|
+
block.type === "image" ? block.asset.assetId : null,
|
|
3949
|
+
);
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3953
|
+
function ensureImageAsset(
|
|
3954
|
+
database: Database,
|
|
3955
|
+
asset: ImageAssetRef,
|
|
3956
|
+
createdAt: string,
|
|
3957
|
+
): void {
|
|
3958
|
+
const existing = database
|
|
3959
|
+
.query(
|
|
3960
|
+
`SELECT mime_type, byte_length, width, height, created_at
|
|
3961
|
+
FROM image_assets WHERE asset_id = ?`,
|
|
3962
|
+
)
|
|
3963
|
+
.get(asset.assetId) as {
|
|
3964
|
+
mime_type: unknown;
|
|
3965
|
+
byte_length: unknown;
|
|
3966
|
+
width: unknown;
|
|
3967
|
+
height: unknown;
|
|
3968
|
+
created_at: unknown;
|
|
3969
|
+
} | null;
|
|
3970
|
+
if (existing === null) {
|
|
3971
|
+
database
|
|
3972
|
+
.query(
|
|
3973
|
+
`INSERT INTO image_assets (
|
|
3974
|
+
asset_id, mime_type, byte_length, width, height, created_at
|
|
3975
|
+
) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
3976
|
+
)
|
|
3977
|
+
.run(
|
|
3978
|
+
asset.assetId,
|
|
3979
|
+
asset.mimeType,
|
|
3980
|
+
asset.byteLength,
|
|
3981
|
+
asset.width,
|
|
3982
|
+
asset.height,
|
|
3983
|
+
createdAt,
|
|
3984
|
+
);
|
|
3985
|
+
return;
|
|
3986
|
+
}
|
|
3987
|
+
timestampFromSql(existing.created_at, "image asset created_at");
|
|
3988
|
+
if (
|
|
3989
|
+
existing.mime_type !== asset.mimeType ||
|
|
3990
|
+
numberFromSql(existing.byte_length, "image asset byte_length") !==
|
|
3991
|
+
asset.byteLength ||
|
|
3992
|
+
numberFromSql(existing.width, "image asset width") !== asset.width ||
|
|
3993
|
+
numberFromSql(existing.height, "image asset height") !== asset.height
|
|
3994
|
+
) {
|
|
3995
|
+
throw new Error(`Image asset metadata conflicts for ${asset.assetId}.`);
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3963
3999
|
function insertToolResult(database: Database, result: ToolResultRecord): void {
|
|
3964
4000
|
const returned = result.completion.kind === "returned" ? result.completion : null;
|
|
3965
4001
|
const synthetic = result.completion.kind === "synthetic" ? result.completion : null;
|
|
@@ -4113,9 +4149,74 @@ function loadMessageImageAttachments(
|
|
|
4113
4149
|
);
|
|
4114
4150
|
}
|
|
4115
4151
|
|
|
4152
|
+
function loadToolMessageContentBlocks(
|
|
4153
|
+
database: Database,
|
|
4154
|
+
): Map<string, readonly ToolResultContent[]> {
|
|
4155
|
+
const rows = database
|
|
4156
|
+
.query(
|
|
4157
|
+
`SELECT tcb.*, ia.mime_type, ia.byte_length, ia.width, ia.height,
|
|
4158
|
+
ia.created_at AS asset_created_at
|
|
4159
|
+
FROM tool_message_content_blocks tcb
|
|
4160
|
+
LEFT JOIN image_assets ia ON ia.asset_id = tcb.asset_id
|
|
4161
|
+
JOIN messages m ON m.message_id = tcb.message_id
|
|
4162
|
+
ORDER BY m.ordinal, tcb.position`,
|
|
4163
|
+
)
|
|
4164
|
+
.all();
|
|
4165
|
+
const mutable = new Map<string, ToolResultContent[]>();
|
|
4166
|
+
for (const rowValue of rows) {
|
|
4167
|
+
const row = recordFromSql(rowValue, "tool message content block");
|
|
4168
|
+
const messageId = stringFromSql(row.message_id, "tool block message_id");
|
|
4169
|
+
const blocks = mutable.get(messageId) ?? [];
|
|
4170
|
+
const position = numberFromSql(row.position, "tool block position");
|
|
4171
|
+
if (position !== blocks.length) {
|
|
4172
|
+
throw new Error(
|
|
4173
|
+
`Tool content block positions for message ${messageId} are not continuous.`,
|
|
4174
|
+
);
|
|
4175
|
+
}
|
|
4176
|
+
const kind = enumFromSql(row.kind, ["text", "image"] as const, "tool block kind");
|
|
4177
|
+
const block: ToolResultContent =
|
|
4178
|
+
kind === "text"
|
|
4179
|
+
? immutableRecord({
|
|
4180
|
+
type: "text",
|
|
4181
|
+
text: stringFromSql(row.text_content, "tool block text_content"),
|
|
4182
|
+
})
|
|
4183
|
+
: immutableRecord({
|
|
4184
|
+
type: "image",
|
|
4185
|
+
asset: immutableRecord({
|
|
4186
|
+
assetId: parseImageAssetId(
|
|
4187
|
+
stringFromSql(row.asset_id, "tool block asset_id"),
|
|
4188
|
+
),
|
|
4189
|
+
mimeType: enumFromSql(
|
|
4190
|
+
row.mime_type,
|
|
4191
|
+
["image/png", "image/jpeg", "image/webp"] as const,
|
|
4192
|
+
"tool block image mime_type",
|
|
4193
|
+
),
|
|
4194
|
+
byteLength: numberFromSql(
|
|
4195
|
+
row.byte_length,
|
|
4196
|
+
"tool block image byte_length",
|
|
4197
|
+
),
|
|
4198
|
+
width: numberFromSql(row.width, "tool block image width"),
|
|
4199
|
+
height: numberFromSql(row.height, "tool block image height"),
|
|
4200
|
+
}),
|
|
4201
|
+
});
|
|
4202
|
+
if (kind === "image") {
|
|
4203
|
+
timestampFromSql(row.asset_created_at, "tool block image asset created_at");
|
|
4204
|
+
}
|
|
4205
|
+
blocks.push(block);
|
|
4206
|
+
mutable.set(messageId, blocks);
|
|
4207
|
+
}
|
|
4208
|
+
return new Map(
|
|
4209
|
+
[...mutable].map(([messageId, blocks]) => {
|
|
4210
|
+
validateToolResultContent(blocks);
|
|
4211
|
+
return [messageId, Object.freeze(blocks)] as const;
|
|
4212
|
+
}),
|
|
4213
|
+
);
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4116
4216
|
function decodeMessage(
|
|
4117
4217
|
rowValue: unknown,
|
|
4118
4218
|
attachments?: readonly UserImageAttachment[],
|
|
4219
|
+
toolContentBlocks?: readonly ToolResultContent[],
|
|
4119
4220
|
): CanonicalMessageRecord {
|
|
4120
4221
|
const row = recordFromSql(rowValue, "message");
|
|
4121
4222
|
const base = {
|
|
@@ -4133,8 +4234,8 @@ function decodeMessage(
|
|
|
4133
4234
|
);
|
|
4134
4235
|
switch (role) {
|
|
4135
4236
|
case "system":
|
|
4136
|
-
if (attachments !== undefined) {
|
|
4137
|
-
throw new Error("System messages cannot have
|
|
4237
|
+
if (attachments !== undefined || toolContentBlocks !== undefined) {
|
|
4238
|
+
throw new Error("System messages cannot have media relation rows.");
|
|
4138
4239
|
}
|
|
4139
4240
|
return immutableRecord({
|
|
4140
4241
|
...base,
|
|
@@ -4143,6 +4244,9 @@ function decodeMessage(
|
|
|
4143
4244
|
origin: "runtime",
|
|
4144
4245
|
});
|
|
4145
4246
|
case "user": {
|
|
4247
|
+
if (toolContentBlocks !== undefined) {
|
|
4248
|
+
throw new Error("User messages cannot have tool content blocks.");
|
|
4249
|
+
}
|
|
4146
4250
|
const message = {
|
|
4147
4251
|
...base,
|
|
4148
4252
|
role,
|
|
@@ -4161,8 +4265,8 @@ function decodeMessage(
|
|
|
4161
4265
|
return immutableRecord(message);
|
|
4162
4266
|
}
|
|
4163
4267
|
case "assistant": {
|
|
4164
|
-
if (attachments !== undefined) {
|
|
4165
|
-
throw new Error("Assistant messages cannot have
|
|
4268
|
+
if (attachments !== undefined || toolContentBlocks !== undefined) {
|
|
4269
|
+
throw new Error("Assistant messages cannot have media relation rows.");
|
|
4166
4270
|
}
|
|
4167
4271
|
const content = nullableTextFromSql(row.content, "content");
|
|
4168
4272
|
const reasoningPresent = numberFromSql(
|
|
@@ -4198,10 +4302,18 @@ function decodeMessage(
|
|
|
4198
4302
|
origin: "model",
|
|
4199
4303
|
});
|
|
4200
4304
|
}
|
|
4201
|
-
case "tool":
|
|
4305
|
+
case "tool": {
|
|
4202
4306
|
if (attachments !== undefined) {
|
|
4203
4307
|
throw new Error("Tool messages cannot have image attachments.");
|
|
4204
4308
|
}
|
|
4309
|
+
if (toolContentBlocks === undefined) {
|
|
4310
|
+
throw new Error("Tool messages must have persisted content blocks.");
|
|
4311
|
+
}
|
|
4312
|
+
validateToolResultContent(toolContentBlocks);
|
|
4313
|
+
const displayText = stringFromSql(row.content, "content");
|
|
4314
|
+
if (toolResultDisplayText(toolContentBlocks) !== displayText) {
|
|
4315
|
+
throw new Error("Tool message display projection does not match its blocks.");
|
|
4316
|
+
}
|
|
4205
4317
|
return immutableRecord({
|
|
4206
4318
|
...base,
|
|
4207
4319
|
role,
|
|
@@ -4213,9 +4325,11 @@ function decodeMessage(
|
|
|
4213
4325
|
"provider_tool_call_id",
|
|
4214
4326
|
),
|
|
4215
4327
|
name: stringFromSql(row.name, "name"),
|
|
4216
|
-
content:
|
|
4328
|
+
content: toolContentBlocks,
|
|
4329
|
+
displayText,
|
|
4217
4330
|
origin: enumFromSql(row.origin, ["tool", "runtime"] as const, "tool origin"),
|
|
4218
4331
|
});
|
|
4332
|
+
}
|
|
4219
4333
|
}
|
|
4220
4334
|
}
|
|
4221
4335
|
|
|
@@ -4597,7 +4711,11 @@ function decodeStoredSwapOverride(rowValue: unknown): StoredContextOverrideV8 {
|
|
|
4597
4711
|
ordinal: numberFromSql(row.ordinal, "ordinal"),
|
|
4598
4712
|
rendererFormat: enumFromSql(
|
|
4599
4713
|
row.renderer_format,
|
|
4600
|
-
[
|
|
4714
|
+
[
|
|
4715
|
+
SWAP_OBSERVATION_FORMAT,
|
|
4716
|
+
SWAP_TOOL_IMAGE_FORMAT,
|
|
4717
|
+
SKILL_ACTIVATION_RECEIPT_FORMAT,
|
|
4718
|
+
] as const,
|
|
4601
4719
|
"context override renderer format",
|
|
4602
4720
|
),
|
|
4603
4721
|
source: stringFromSql(row.source, "source") as StoredContextOverrideV8["source"],
|
|
@@ -4937,6 +5055,7 @@ export function decodeStoredToolRawResult(value: unknown): ToolRawResult {
|
|
|
4937
5055
|
raw.kind,
|
|
4938
5056
|
[
|
|
4939
5057
|
"read",
|
|
5058
|
+
"view_image",
|
|
4940
5059
|
"write",
|
|
4941
5060
|
"edit",
|
|
4942
5061
|
"delete",
|
|
@@ -4952,6 +5071,8 @@ export function decodeStoredToolRawResult(value: unknown): ToolRawResult {
|
|
|
4952
5071
|
"web_fetch",
|
|
4953
5072
|
"recall",
|
|
4954
5073
|
"memory_search",
|
|
5074
|
+
"memory_get",
|
|
5075
|
+
"wait",
|
|
4955
5076
|
"skill",
|
|
4956
5077
|
"mcp",
|
|
4957
5078
|
"generic",
|
|
@@ -4964,9 +5085,65 @@ export function decodeStoredToolRawResult(value: unknown): ToolRawResult {
|
|
|
4964
5085
|
if (kind === "skill") {
|
|
4965
5086
|
return decodeStoredSkillRawResult(raw);
|
|
4966
5087
|
}
|
|
5088
|
+
if (kind === "view_image") {
|
|
5089
|
+
return decodeStoredViewImageRawResult(raw);
|
|
5090
|
+
}
|
|
4967
5091
|
return immutableCanonicalClone(raw) as ToolRawResult;
|
|
4968
5092
|
}
|
|
4969
5093
|
|
|
5094
|
+
function decodeStoredViewImageRawResult(
|
|
5095
|
+
raw: Record<string, unknown>,
|
|
5096
|
+
): Extract<ToolRawResult, { kind: "view_image" }> {
|
|
5097
|
+
assertObjectKeys(
|
|
5098
|
+
raw,
|
|
5099
|
+
["kind", "ok", "filePath", "originalName", "asset", "error"],
|
|
5100
|
+
["kind", "ok", "filePath"],
|
|
5101
|
+
"ViewImage raw result",
|
|
5102
|
+
);
|
|
5103
|
+
const filePath = stringFromSql(raw.filePath, "ViewImage filePath");
|
|
5104
|
+
if (raw.ok === false) {
|
|
5105
|
+
if (raw.originalName !== undefined || raw.asset !== undefined) {
|
|
5106
|
+
throw new Error("Failed ViewImage raw result cannot contain image metadata.");
|
|
5107
|
+
}
|
|
5108
|
+
const error = stringFromSql(raw.error, "ViewImage error");
|
|
5109
|
+
if (error.trim() === "") {
|
|
5110
|
+
throw new Error("Failed ViewImage raw result error must not be empty.");
|
|
5111
|
+
}
|
|
5112
|
+
return immutableRecord({ kind: "view_image", ok: false, filePath, error });
|
|
5113
|
+
}
|
|
5114
|
+
if (raw.ok !== true || raw.error !== undefined || filePath.trim() === "") {
|
|
5115
|
+
throw new Error("Successful ViewImage raw result is invalid.");
|
|
5116
|
+
}
|
|
5117
|
+
const originalName = stringFromSql(raw.originalName, "ViewImage originalName");
|
|
5118
|
+
validateOriginalImageName(originalName);
|
|
5119
|
+
const storedAsset = recordFromSql(raw.asset, "ViewImage asset");
|
|
5120
|
+
assertObjectKeys(
|
|
5121
|
+
storedAsset,
|
|
5122
|
+
["assetId", "mimeType", "byteLength", "width", "height"],
|
|
5123
|
+
["assetId", "mimeType", "byteLength", "width", "height"],
|
|
5124
|
+
"ViewImage asset",
|
|
5125
|
+
);
|
|
5126
|
+
const asset: ImageAssetRef = immutableRecord({
|
|
5127
|
+
assetId: parseImageAssetId(stringFromSql(storedAsset.assetId, "assetId")),
|
|
5128
|
+
mimeType: enumFromSql(
|
|
5129
|
+
storedAsset.mimeType,
|
|
5130
|
+
["image/png", "image/jpeg", "image/webp"] as const,
|
|
5131
|
+
"ViewImage asset mimeType",
|
|
5132
|
+
),
|
|
5133
|
+
byteLength: numberFromJson(storedAsset.byteLength, "ViewImage asset byteLength"),
|
|
5134
|
+
width: numberFromJson(storedAsset.width, "ViewImage asset width"),
|
|
5135
|
+
height: numberFromJson(storedAsset.height, "ViewImage asset height"),
|
|
5136
|
+
});
|
|
5137
|
+
validateImageAssetRef(asset);
|
|
5138
|
+
return immutableRecord({
|
|
5139
|
+
kind: "view_image",
|
|
5140
|
+
ok: true,
|
|
5141
|
+
filePath,
|
|
5142
|
+
originalName,
|
|
5143
|
+
asset,
|
|
5144
|
+
});
|
|
5145
|
+
}
|
|
5146
|
+
|
|
4970
5147
|
function decodeStoredSkillRawResult(
|
|
4971
5148
|
raw: Record<string, unknown>,
|
|
4972
5149
|
): Extract<ToolRawResult, { kind: "skill" }> {
|
|
@@ -5325,7 +5502,7 @@ function assertCommitPrefixRetirementRevisionInput(
|
|
|
5325
5502
|
}
|
|
5326
5503
|
}
|
|
5327
5504
|
|
|
5328
|
-
function requireActiveRevisionId(meta:
|
|
5505
|
+
function requireActiveRevisionId(meta: StoredSessionMetaV10): ContextRevisionId {
|
|
5329
5506
|
if (meta.initializationState !== "ready" || meta.activeRevisionId === null) {
|
|
5330
5507
|
throw new Error("Session has no active context revision.");
|
|
5331
5508
|
}
|
|
@@ -5339,16 +5516,19 @@ function previousRevision(
|
|
|
5339
5516
|
return revisions[revision.revisionNumber - 2];
|
|
5340
5517
|
}
|
|
5341
5518
|
|
|
5342
|
-
function decodeMeta(
|
|
5519
|
+
function decodeMeta(
|
|
5520
|
+
value: unknown,
|
|
5521
|
+
expectedSessionId: SessionId,
|
|
5522
|
+
): StoredSessionMetaV10 {
|
|
5343
5523
|
const row = recordFromSql(value, "session metadata");
|
|
5344
5524
|
const sessionId = stringFromSql(row.session_id, "session_id") as SessionId;
|
|
5345
5525
|
if (sessionId !== expectedSessionId) {
|
|
5346
5526
|
throw new Error(`Metadata session ID ${sessionId} does not match directory.`);
|
|
5347
5527
|
}
|
|
5348
5528
|
const schemaVersion = numberFromSql(row.schema_version, "schema_version");
|
|
5349
|
-
if (schemaVersion !==
|
|
5529
|
+
if (schemaVersion !== 10) {
|
|
5350
5530
|
throw new Error(
|
|
5351
|
-
`Session metadata schema version must be
|
|
5531
|
+
`Session metadata schema version must be 10; received ${schemaVersion}.`,
|
|
5352
5532
|
);
|
|
5353
5533
|
}
|
|
5354
5534
|
const projectInstructionFile = nullableStringFromSql(
|
|
@@ -5477,7 +5657,7 @@ function compatibilityContractDifferences(
|
|
|
5477
5657
|
"includeReasoningContent",
|
|
5478
5658
|
"contextProfile",
|
|
5479
5659
|
"messageProtocol",
|
|
5480
|
-
"
|
|
5660
|
+
"media",
|
|
5481
5661
|
];
|
|
5482
5662
|
return fields.filter((key) => {
|
|
5483
5663
|
const storedValue = record[key];
|
|
@@ -5504,14 +5684,14 @@ function decodeSessionCompatibilityContract(
|
|
|
5504
5684
|
"includeReasoningContent",
|
|
5505
5685
|
"contextProfile",
|
|
5506
5686
|
"messageProtocol",
|
|
5507
|
-
"
|
|
5687
|
+
"media",
|
|
5508
5688
|
],
|
|
5509
5689
|
[
|
|
5510
5690
|
"modelName",
|
|
5511
5691
|
"includeReasoningContent",
|
|
5512
5692
|
"contextProfile",
|
|
5513
5693
|
"messageProtocol",
|
|
5514
|
-
"
|
|
5694
|
+
"media",
|
|
5515
5695
|
],
|
|
5516
5696
|
"session compatibility contract",
|
|
5517
5697
|
);
|
|
@@ -5535,24 +5715,21 @@ function decodeSessionCompatibilityContract(
|
|
|
5535
5715
|
["adapter", "serializationVersion"],
|
|
5536
5716
|
"session compatibility message protocol",
|
|
5537
5717
|
);
|
|
5538
|
-
const
|
|
5539
|
-
record.imageInput,
|
|
5540
|
-
"session compatibility image input",
|
|
5541
|
-
);
|
|
5718
|
+
const media = recordFromSql(record.media, "session compatibility media");
|
|
5542
5719
|
assertObjectKeys(
|
|
5543
|
-
|
|
5544
|
-
["policyVersion", "policySha256", "inputModalities", "
|
|
5545
|
-
["policyVersion", "policySha256", "inputModalities"],
|
|
5546
|
-
"session compatibility
|
|
5720
|
+
media,
|
|
5721
|
+
["policyVersion", "policySha256", "inputModalities", "toolResultModalities"],
|
|
5722
|
+
["policyVersion", "policySha256", "inputModalities", "toolResultModalities"],
|
|
5723
|
+
"session compatibility media",
|
|
5547
5724
|
);
|
|
5548
|
-
const inputModalities =
|
|
5549
|
-
|
|
5550
|
-
|
|
5725
|
+
const inputModalities = decodeCompatibilityModalities(media.inputModalities, "input");
|
|
5726
|
+
const toolResultModalities = decodeCompatibilityModalities(
|
|
5727
|
+
media.toolResultModalities,
|
|
5728
|
+
"tool result",
|
|
5729
|
+
);
|
|
5730
|
+
if (toolResultModalities.includes("image") && !inputModalities.includes("image")) {
|
|
5731
|
+
throw new Error("Session compatibility image tool results require image input.");
|
|
5551
5732
|
}
|
|
5552
|
-
const tokenEstimator =
|
|
5553
|
-
imageInput.tokenEstimator === undefined
|
|
5554
|
-
? undefined
|
|
5555
|
-
: decodeTokenEstimatorCompatibility(imageInput.tokenEstimator);
|
|
5556
5733
|
if (typeof record.includeReasoningContent !== "boolean") {
|
|
5557
5734
|
throw new Error("Session compatibility reasoning replay flag must be boolean.");
|
|
5558
5735
|
}
|
|
@@ -5582,17 +5759,12 @@ function decodeSessionCompatibilityContract(
|
|
|
5582
5759
|
"compatibility serializationVersion",
|
|
5583
5760
|
),
|
|
5584
5761
|
});
|
|
5585
|
-
const modalities = normalizeInputModalities(
|
|
5586
|
-
inputModalities.map((value) =>
|
|
5587
|
-
enumFromSql(value, ["text", "image"] as const, "compatibility input modality"),
|
|
5588
|
-
),
|
|
5589
|
-
);
|
|
5590
5762
|
const policyVersion = stringFromSql(
|
|
5591
|
-
|
|
5763
|
+
media.policyVersion,
|
|
5592
5764
|
"compatibility image policyVersion",
|
|
5593
5765
|
);
|
|
5594
5766
|
const policySha256 = stringFromSql(
|
|
5595
|
-
|
|
5767
|
+
media.policySha256,
|
|
5596
5768
|
"compatibility image policySha256",
|
|
5597
5769
|
);
|
|
5598
5770
|
if (!/^[0-9a-f]{64}$/.test(policySha256)) {
|
|
@@ -5604,49 +5776,31 @@ function decodeSessionCompatibilityContract(
|
|
|
5604
5776
|
includeReasoningContent: record.includeReasoningContent,
|
|
5605
5777
|
contextProfile: Object.freeze(context),
|
|
5606
5778
|
messageProtocol: protocol,
|
|
5607
|
-
|
|
5779
|
+
media: Object.freeze({
|
|
5608
5780
|
policyVersion,
|
|
5609
5781
|
policySha256,
|
|
5610
|
-
inputModalities
|
|
5611
|
-
|
|
5782
|
+
inputModalities,
|
|
5783
|
+
toolResultModalities,
|
|
5612
5784
|
}),
|
|
5613
5785
|
});
|
|
5614
5786
|
}
|
|
5615
5787
|
|
|
5616
|
-
function
|
|
5788
|
+
function decodeCompatibilityModalities(
|
|
5617
5789
|
value: unknown,
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
"compatibility token estimator kind",
|
|
5631
|
-
),
|
|
5632
|
-
coverageVersion: enumFromSql(
|
|
5633
|
-
record.coverageVersion,
|
|
5634
|
-
["full-request-v1"] as const,
|
|
5635
|
-
"compatibility token estimator coverage",
|
|
5636
|
-
),
|
|
5637
|
-
model: stringFromSql(record.model, "compatibility token estimator model"),
|
|
5638
|
-
endpoint: stringFromSql(record.endpoint, "compatibility token estimator endpoint"),
|
|
5639
|
-
timeoutMs: numberFromJson(
|
|
5640
|
-
record.timeoutMs,
|
|
5641
|
-
"compatibility token estimator timeoutMs",
|
|
5642
|
-
),
|
|
5643
|
-
maxRetries: zeroFromJson(
|
|
5644
|
-
record.maxRetries,
|
|
5645
|
-
"compatibility token estimator maxRetries",
|
|
5790
|
+
label: string,
|
|
5791
|
+
): readonly ("text" | "image")[] {
|
|
5792
|
+
if (!Array.isArray(value)) {
|
|
5793
|
+
throw new Error(`Session compatibility ${label} modalities must be an array.`);
|
|
5794
|
+
}
|
|
5795
|
+
return normalizeInputModalities(
|
|
5796
|
+
value.map((modality) =>
|
|
5797
|
+
enumFromSql(
|
|
5798
|
+
modality,
|
|
5799
|
+
["text", "image"] as const,
|
|
5800
|
+
`compatibility ${label} modality`,
|
|
5801
|
+
),
|
|
5646
5802
|
),
|
|
5647
|
-
|
|
5648
|
-
validateTokenEstimatorCompatibility(estimator);
|
|
5649
|
-
return Object.freeze(estimator);
|
|
5803
|
+
);
|
|
5650
5804
|
}
|
|
5651
5805
|
|
|
5652
5806
|
function openWritableDatabase(databasePath: string): Database {
|
|
@@ -6244,13 +6398,6 @@ function numberFromJson(value: unknown, name: string): number {
|
|
|
6244
6398
|
return value as number;
|
|
6245
6399
|
}
|
|
6246
6400
|
|
|
6247
|
-
function zeroFromJson(value: unknown, name: string): 0 {
|
|
6248
|
-
if (value !== 0) {
|
|
6249
|
-
throw new Error(`${name} must be 0.`);
|
|
6250
|
-
}
|
|
6251
|
-
return 0;
|
|
6252
|
-
}
|
|
6253
|
-
|
|
6254
6401
|
function enumFromSql<const T extends readonly string[]>(
|
|
6255
6402
|
value: unknown,
|
|
6256
6403
|
values: T,
|