@myagentroam/agent 0.10.7 → 0.10.9
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/dist/agent-core/extensions/compaction.d.ts +1 -0
- package/dist/agent-core/extensions/model-request.js +1 -1
- package/dist/agent-core/extensions/session-messages.d.ts +1 -1
- package/dist/agent-core/host/contracts.d.ts +2 -0
- package/dist/agent-core/host/local-agent-host.d.ts +2 -0
- package/dist/agent-core/host/local-agent-host.js +2 -0
- package/dist/agent-core/model/anthropic-messages.js +15 -23
- package/dist/agent-core/model/catalog-file.d.ts +14 -0
- package/dist/agent-core/model/configuration.d.ts +7 -0
- package/dist/agent-core/model/configuration.js +10 -0
- package/dist/agent-core/model/contracts.d.ts +8 -0
- package/dist/agent-core/model/openai-responses.d.ts +1 -1
- package/dist/agent-core/model/openai-responses.js +62 -29
- package/dist/agent-core/model/runtime-credential.d.ts +2 -0
- package/dist/agent-core/model/runtime-credential.js +15 -0
- package/dist/agent-core/model/sse.d.ts +1 -1
- package/dist/agent-core/model/sse.js +22 -4
- package/dist/agent-core/model/tool-input.d.ts +3 -0
- package/dist/agent-core/model/tool-input.js +18 -0
- package/dist/agent-core/model/usage-observer.d.ts +31 -0
- package/dist/agent-core/model/usage-observer.js +82 -0
- package/dist/agent-core/runtime/events.d.ts +2 -0
- package/dist/agent-core/sdk/agent.js +9 -1
- package/dist/agent-core/sdk/extension-models.d.ts +1 -1
- package/dist/agent-core/sdk/extension-models.js +9 -1
- package/dist/agent-core/sdk/session.js +31 -3
- package/dist/agent-core/session/catalog.d.ts +1 -0
- package/dist/agent-core/session/catalog.js +1 -0
- package/dist/agent-core/session/jsonl-store.d.ts +9 -0
- package/dist/agent-core/session/jsonl-store.js +24 -0
- package/dist/agent-core/session/tool-result-resources.d.ts +26 -0
- package/dist/agent-core/session/tool-result-resources.js +285 -0
- package/dist/agent-core/session/transaction-recovery.js +1 -1
- package/dist/agent-core/tools/code-mode/dispatch.js +5 -0
- package/dist/agent-core/tools/code-mode/index.js +8 -2
- package/dist/agent-core/tools/code-mode/observations.d.ts +1 -0
- package/dist/agent-core/tools/contracts.d.ts +2 -0
- package/dist/agent-core/tools/execution.js +11 -2
- package/dist/agent-core/tools/file-change-diff.d.ts +10 -0
- package/dist/agent-core/tools/file-change-diff.js +139 -0
- package/dist/agent-core/tools/local/apply-patch.js +19 -8
- package/dist/agent-core/tools/result-extensions.d.ts +46 -0
- package/dist/agent-core/tools/result-extensions.js +134 -0
- package/dist/agent-core/tools/runtime-context.d.ts +1 -0
- package/dist/agent-core/tools/runtime.js +6 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/shared/auto-title.d.ts +2 -0
- package/dist/shared/auto-title.js +6 -0
- package/dist/shared/model-text.d.ts +2 -0
- package/dist/shared/model-text.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { MarAgentModelConfiguration } from './configuration.js';
|
|
2
|
+
import type { ModelEvent, ModelRequest } from './contracts.js';
|
|
3
|
+
/** Safe request facts, independent of conversation events and provider payloads. */
|
|
4
|
+
export interface ModelRequestUsage {
|
|
5
|
+
readonly recordId: string;
|
|
6
|
+
readonly requestId: string;
|
|
7
|
+
readonly revision: number;
|
|
8
|
+
readonly modelConfigurationId: string;
|
|
9
|
+
readonly modelName: string;
|
|
10
|
+
readonly provider: string;
|
|
11
|
+
readonly requestedModel: string;
|
|
12
|
+
readonly actualModel?: string;
|
|
13
|
+
readonly responseId?: string;
|
|
14
|
+
readonly startedAt: number;
|
|
15
|
+
readonly endedAt?: number;
|
|
16
|
+
readonly state: 'running' | 'succeeded' | 'failed' | 'cancelled' | 'interrupted';
|
|
17
|
+
readonly inputTokens?: string;
|
|
18
|
+
readonly outputTokens?: string;
|
|
19
|
+
readonly cacheReadTokens?: string;
|
|
20
|
+
readonly cacheWriteTokens?: string;
|
|
21
|
+
readonly reasoningTokens?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ModelUsageObservation extends ModelRequestUsage {
|
|
24
|
+
readonly sessionId: string;
|
|
25
|
+
readonly executionId?: string;
|
|
26
|
+
readonly purpose: 'conversation' | 'compact' | 'title' | 'other';
|
|
27
|
+
readonly agentRole: 'root' | 'subagent';
|
|
28
|
+
}
|
|
29
|
+
export type ModelUsageObserver = (usage: ModelUsageObservation) => Promise<void> | void;
|
|
30
|
+
/** Observe before retry prelude buffering so failed attempts retain reported usage. */
|
|
31
|
+
export declare function observeModelRequestUsage(start: () => AsyncIterable<ModelEvent>, request: ModelRequest, configuration: MarAgentModelConfiguration, signal: AbortSignal, now?: () => number): AsyncIterable<ModelEvent>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
function count(value) {
|
|
3
|
+
return value !== undefined && Number.isSafeInteger(value) && value >= 0
|
|
4
|
+
? String(value)
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
/** Observe before retry prelude buffering so failed attempts retain reported usage. */
|
|
8
|
+
export async function* observeModelRequestUsage(start, request, configuration, signal, now = Date.now) {
|
|
9
|
+
if (!request.onModelUsage) {
|
|
10
|
+
yield* start();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const base = {
|
|
14
|
+
recordId: randomUUID(),
|
|
15
|
+
requestId: request.usageRequestId ?? randomUUID(),
|
|
16
|
+
modelConfigurationId: configuration.id,
|
|
17
|
+
modelName: configuration.name,
|
|
18
|
+
provider: configuration.protocol,
|
|
19
|
+
requestedModel: configuration.modelId,
|
|
20
|
+
startedAt: now()
|
|
21
|
+
};
|
|
22
|
+
let usage = {};
|
|
23
|
+
let responseId;
|
|
24
|
+
let actualModel;
|
|
25
|
+
let state = 'interrupted';
|
|
26
|
+
const publish = async (value) => {
|
|
27
|
+
try {
|
|
28
|
+
await request.onModelUsage(value);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Metering must not break the model stream or expose callback error details.
|
|
32
|
+
try {
|
|
33
|
+
request.onModelUsageFailure?.();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Host diagnostics are also best effort.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
await publish({ ...base, revision: 1, state: 'running' });
|
|
41
|
+
try {
|
|
42
|
+
for await (const event of start()) {
|
|
43
|
+
if (event.type === 'response.started') {
|
|
44
|
+
responseId = event.responseId;
|
|
45
|
+
actualModel = event.modelId;
|
|
46
|
+
}
|
|
47
|
+
else if (event.type === 'usage') {
|
|
48
|
+
for (const [key, value] of Object.entries(event.usage)) {
|
|
49
|
+
if (value !== undefined)
|
|
50
|
+
usage = { ...usage, [key]: value };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (event.type === 'completed') {
|
|
54
|
+
state = event.reason === 'cancelled' ? 'cancelled' : 'succeeded';
|
|
55
|
+
}
|
|
56
|
+
yield event;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
state = signal.aborted ? 'cancelled' : 'failed';
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await publish({
|
|
65
|
+
...base,
|
|
66
|
+
revision: 2,
|
|
67
|
+
state: signal.aborted && state !== 'succeeded' ? 'cancelled' : state,
|
|
68
|
+
endedAt: now(),
|
|
69
|
+
...(responseId === undefined ? {} : { responseId }),
|
|
70
|
+
...(actualModel === undefined ? {} : { actualModel }),
|
|
71
|
+
...optionalCount('inputTokens', usage.totalInputTokens),
|
|
72
|
+
...optionalCount('outputTokens', usage.outputTokens),
|
|
73
|
+
...optionalCount('cacheReadTokens', usage.cacheReadTokens),
|
|
74
|
+
...optionalCount('cacheWriteTokens', usage.cacheWriteTokens),
|
|
75
|
+
...optionalCount('reasoningTokens', usage.reasoningTokens)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function optionalCount(key, value) {
|
|
80
|
+
const normalized = count(value);
|
|
81
|
+
return normalized === undefined ? {} : { [key]: normalized };
|
|
82
|
+
}
|
|
@@ -75,6 +75,7 @@ export type MarAgentEvent = MarAgentEventBase & ({
|
|
|
75
75
|
truncated?: boolean;
|
|
76
76
|
} | {
|
|
77
77
|
type: 'tool.completed';
|
|
78
|
+
resultExtensions?: readonly import('../tools/result-extensions.js').ToolResultExtension[];
|
|
78
79
|
toolName: string;
|
|
79
80
|
callId: string;
|
|
80
81
|
parentCallId?: string;
|
|
@@ -84,6 +85,7 @@ export type MarAgentEvent = MarAgentEventBase & ({
|
|
|
84
85
|
data?: CodeCellEventData | import('../tools/contracts.js').ExecResult | Omit<import('../tools/contracts.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/contracts.js').ApplyPatchResult | import('../tools/contracts.js').ReadEventData | import('../tools/contracts.js').ViewImageResult | import('../subagent/contracts.js').SubagentTaskSnapshot;
|
|
85
86
|
} | {
|
|
86
87
|
type: 'tool.failed';
|
|
88
|
+
resultExtensions?: readonly import('../tools/result-extensions.js').ToolResultExtension[];
|
|
87
89
|
data?: CodeCellEventData | import('../tools/contracts.js').ApplyPatchPartialResult;
|
|
88
90
|
toolName: string;
|
|
89
91
|
callId: string;
|
|
@@ -84,7 +84,15 @@ export async function createMarAgentWithAdapter(options) {
|
|
|
84
84
|
if (!snapshots)
|
|
85
85
|
throw new MarAgentError('MAR_AGENT_MODEL_SNAPSHOT_INVALID', 'Request snapshot is unavailable.');
|
|
86
86
|
return snapshots.resolve(snapshot);
|
|
87
|
-
}
|
|
87
|
+
}, options.host.modelUsage === undefined
|
|
88
|
+
? undefined
|
|
89
|
+
: (usage, call) => options.host.modelUsage({
|
|
90
|
+
...usage,
|
|
91
|
+
sessionId: call.sessionId,
|
|
92
|
+
...(call.executionId === undefined ? {} : { executionId: call.executionId }),
|
|
93
|
+
purpose: 'other',
|
|
94
|
+
agentRole: sessionRuntimes.get(call.sessionId)?.sourceType === 'subagent' ? 'subagent' : 'root'
|
|
95
|
+
})),
|
|
88
96
|
sessionBackend: (id) => sessionRuntimes.get(id)?.sessionBackend,
|
|
89
97
|
agentsBackend: (id) => sessionRuntimes.get(id)?.agentsBackend,
|
|
90
98
|
scheduleTool: (id) => (definition, run) => {
|
|
@@ -10,4 +10,4 @@ export declare function createExtensionModels(state: () => {
|
|
|
10
10
|
modelId: string;
|
|
11
11
|
request: ModelRequest;
|
|
12
12
|
view: CoreModelRequest;
|
|
13
|
-
}): CoreModelsBackend;
|
|
13
|
+
}, observeUsage?: (usage: import('../model/usage-observer.js').ModelRequestUsage, call: CoreOperationContext) => Promise<void> | void): CoreModelsBackend;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { MarAgentError } from '../error.js';
|
|
2
3
|
import { ExtensionRequestBridge } from '../extensions/model-request.js';
|
|
3
4
|
import { transformCoreModelStream } from '../extensions/model-stream.js';
|
|
4
5
|
import { modelTextInput } from '../model/text-blocks.js';
|
|
5
|
-
export function createExtensionModels(state, createAdapter, resolveSnapshot) {
|
|
6
|
+
export function createExtensionModels(state, createAdapter, resolveSnapshot, observeUsage) {
|
|
6
7
|
return {
|
|
7
8
|
list: () => {
|
|
8
9
|
const { models, defaultModelId } = state();
|
|
@@ -67,6 +68,13 @@ export function createExtensionModels(state, createAdapter, resolveSnapshot) {
|
|
|
67
68
|
throw new MarAgentError('MAR_AGENT_MODEL_REQUEST_INVALID', 'Independent generation does not execute tools.');
|
|
68
69
|
const request = {
|
|
69
70
|
...lowered,
|
|
71
|
+
usageRequestId: randomUUID(),
|
|
72
|
+
...(observeUsage === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: {
|
|
75
|
+
onModelUsage: (usage) => observeUsage(usage, call),
|
|
76
|
+
onModelUsageFailure: () => diagnoseCleanup?.('MAR_AGENT_MODEL_USAGE_FAILED')
|
|
77
|
+
}),
|
|
70
78
|
...(input.retainToolCatalog &&
|
|
71
79
|
snapshot.request.systemBlocks === undefined &&
|
|
72
80
|
snapshot.request.messages.every((message) => message.textBlocks === undefined)
|
|
@@ -667,7 +667,7 @@ export function createSessionOpener(services) {
|
|
|
667
667
|
code: 'MAR_AGENT_MODEL_STREAM_CLEANUP_FAILED',
|
|
668
668
|
sessionId
|
|
669
669
|
}));
|
|
670
|
-
const transformModelRequest = async (candidate, bindings = captureTools(), modelId = selectedModel.id) => {
|
|
670
|
+
const transformModelRequest = async (candidate, bindings = captureTools(), modelId = selectedModel.id, purpose = mode === 'compact' ? 'compact' : 'conversation') => {
|
|
671
671
|
let lowered = candidate;
|
|
672
672
|
if (coreExtensions.hooks.hasInterceptors('model.request')) {
|
|
673
673
|
const bridge = new ExtensionRequestBridge(modelId, candidate);
|
|
@@ -681,7 +681,25 @@ export function createSessionOpener(services) {
|
|
|
681
681
|
if (!binding || !isDeepStrictEqual(contract, capturedContract))
|
|
682
682
|
throw new MarAgentError('MAR_AGENT_EXTENSION_MODEL_REQUEST_INVALID', 'Request tools do not match captured sources.');
|
|
683
683
|
}
|
|
684
|
-
return
|
|
684
|
+
return {
|
|
685
|
+
...lowered,
|
|
686
|
+
usageRequestId: randomUUID(),
|
|
687
|
+
...(host.modelUsage === undefined
|
|
688
|
+
? {}
|
|
689
|
+
: {
|
|
690
|
+
onModelUsage: (usage) => host.modelUsage({
|
|
691
|
+
...usage,
|
|
692
|
+
sessionId,
|
|
693
|
+
executionId,
|
|
694
|
+
purpose,
|
|
695
|
+
agentRole: sessionSource.type === 'subagent' ? 'subagent' : 'root'
|
|
696
|
+
}),
|
|
697
|
+
onModelUsageFailure: () => coreExtensions.diagnose({
|
|
698
|
+
code: 'MAR_AGENT_MODEL_USAGE_FAILED',
|
|
699
|
+
sessionId
|
|
700
|
+
})
|
|
701
|
+
})
|
|
702
|
+
};
|
|
685
703
|
};
|
|
686
704
|
const toolEligible = (tool) => {
|
|
687
705
|
toolCallers(tool);
|
|
@@ -825,6 +843,7 @@ export function createSessionOpener(services) {
|
|
|
825
843
|
: subagents.captureParentFrame(executionId);
|
|
826
844
|
const toolContext = {
|
|
827
845
|
host: host,
|
|
846
|
+
writeToolResultResource: (text) => store.writeToolResultResource(sessionId, text),
|
|
828
847
|
todo,
|
|
829
848
|
...(subagents === undefined || mode === 'compact'
|
|
830
849
|
? {}
|
|
@@ -1038,6 +1057,15 @@ export function createSessionOpener(services) {
|
|
|
1038
1057
|
...(focus === undefined ? {} : { focus }),
|
|
1039
1058
|
summaryTokenBudget,
|
|
1040
1059
|
getPrompt: () => buildCompactionMessages('')[1].content,
|
|
1060
|
+
reportModelUsage: host.modelUsage === undefined
|
|
1061
|
+
? undefined
|
|
1062
|
+
: (usage) => host.modelUsage({
|
|
1063
|
+
...usage,
|
|
1064
|
+
sessionId,
|
|
1065
|
+
executionId,
|
|
1066
|
+
purpose: 'compact',
|
|
1067
|
+
agentRole: sessionSource.type === 'subagent' ? 'subagent' : 'root'
|
|
1068
|
+
}),
|
|
1041
1069
|
reportDiagnostic: async (value) => {
|
|
1042
1070
|
if (!acceptsDiagnostics || ++diagnosticReports > 8)
|
|
1043
1071
|
return;
|
|
@@ -1179,7 +1207,7 @@ export function createSessionOpener(services) {
|
|
|
1179
1207
|
promptCacheKey: sessionId,
|
|
1180
1208
|
reasoningEffort: requestReasoningEffort ?? reasoningEffort,
|
|
1181
1209
|
onAttemptDiagnostic: recordModelAttempt('COMPACTION')
|
|
1182
|
-
});
|
|
1210
|
+
}, undefined, selectedModel.id, 'compact');
|
|
1183
1211
|
for await (const event of streamModel((signal) => compaction.start({ start: (request) => selected.start(request, signal) }, request), compaction.signal)) {
|
|
1184
1212
|
if (event.type === 'text.completed' &&
|
|
1185
1213
|
(event.phase ?? 'final_answer') === 'final_answer')
|
|
@@ -71,6 +71,7 @@ export interface MarAgentSessionCatalog {
|
|
|
71
71
|
readonly imageIndex: number;
|
|
72
72
|
}): Promise<MarAgentSessionImage | undefined>;
|
|
73
73
|
contextUsage(sessionId: string): Promise<MarAgentContextUsage | undefined>;
|
|
74
|
+
readToolResultResource(sessionId: string, input: import('./tool-result-resources.js').ToolResultResourceInput): Promise<import('./tool-result-resources.js').ToolResultResourcePage>;
|
|
74
75
|
fork(sessionId: string, input: {
|
|
75
76
|
readonly boundaryTurnId: string | null;
|
|
76
77
|
readonly title: string;
|
|
@@ -79,6 +79,7 @@ export async function openMarAgentSessionCatalog(input) {
|
|
|
79
79
|
dataBase64: image.dataBase64
|
|
80
80
|
};
|
|
81
81
|
},
|
|
82
|
+
readToolResultResource: (sessionId, input) => store.readToolResultResource(sessionId, input),
|
|
82
83
|
contextUsage: async (sessionId) => {
|
|
83
84
|
const page = await store.readPage(sessionId, { limit: 1000 });
|
|
84
85
|
for (const record of page.records.toReversed()) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ToolResultResourceInput } from './tool-result-resources.js';
|
|
1
2
|
import { type CoreCheckpoint, type CoreJsonValue } from '../extensions/data.js';
|
|
2
3
|
import type { ModelInputMetadata, ModelMessage, ModelUsage } from '../model/contracts.js';
|
|
3
4
|
import type { EnvironmentContextState } from '../runtime/context/contracts.js';
|
|
@@ -104,6 +105,14 @@ export declare class JsonlSessionStore {
|
|
|
104
105
|
writeContextualWorldState(sessionId: string, state: unknown): Promise<void>;
|
|
105
106
|
/** Mark each image pending before publication; turn.user or lease recovery resolves ownership. */
|
|
106
107
|
persistInputImages(sessionId: string, images: readonly SessionInputImage[]): Promise<SessionInputImageMetadata[]>;
|
|
108
|
+
writeToolResultResource(sessionId: string, text: string): Promise<{
|
|
109
|
+
status: "available";
|
|
110
|
+
resourceRef: string;
|
|
111
|
+
format: "unified-diff";
|
|
112
|
+
byteSize: number;
|
|
113
|
+
sha256: string;
|
|
114
|
+
}>;
|
|
115
|
+
readToolResultResource(sessionId: string, input: ToolResultResourceInput): Promise<import("./tool-result-resources.js").ToolResultResourcePage>;
|
|
107
116
|
readInputImages(sessionId: string, value: unknown): Promise<RestoredSessionInputImage[]>;
|
|
108
117
|
readInputImage(sessionId: string, input: {
|
|
109
118
|
readonly sequence: number;
|
|
@@ -4,6 +4,7 @@ import { copyFile, lstat, mkdir, open, readFile, readdir, rename, rm, stat, writ
|
|
|
4
4
|
import { basename, join } from 'node:path';
|
|
5
5
|
import { isDeepStrictEqual } from 'node:util';
|
|
6
6
|
import { MarAgentError } from '../error.js';
|
|
7
|
+
import { completeToolResultResources, cleanupUnreferencedToolResources, bindToolResultResources, copyToolResultResources, readToolResultResource, toolResultResourceFiles, writeToolResultResource } from './tool-result-resources.js';
|
|
7
8
|
import { acquireFileLock } from '../storage/file-lock.js';
|
|
8
9
|
import { cloneCoreJson } from '../extensions/data.js';
|
|
9
10
|
import { applyCodeModeStateMutations, parseCodeModeStateMutations, restoreCodeModeState } from '../tools/code-mode/state.js';
|
|
@@ -484,6 +485,22 @@ export class JsonlSessionStore {
|
|
|
484
485
|
throw error;
|
|
485
486
|
}
|
|
486
487
|
}
|
|
488
|
+
async writeToolResultResource(sessionId, text) {
|
|
489
|
+
this.#assertOpen();
|
|
490
|
+
if (!this.#shared.index.sessions.some((item) => item.id === sessionId))
|
|
491
|
+
throw new MarAgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
492
|
+
return writeToolResultResource(this.sessionDirectory(sessionId), text);
|
|
493
|
+
}
|
|
494
|
+
async readToolResultResource(sessionId, input) {
|
|
495
|
+
this.#assertOpen();
|
|
496
|
+
await this.#refreshIndex();
|
|
497
|
+
if (!this.#shared.index.sessions.some((item) => item.id === sessionId))
|
|
498
|
+
throw new MarAgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
|
|
499
|
+
return readToolResultResource(this.sessionDirectory(sessionId), sessionId, input, async (sequence) => {
|
|
500
|
+
await this.#refreshIndex();
|
|
501
|
+
return (await this.readPage(sessionId, { beforeSequence: sequence + 1, limit: 1 })).records.find((record) => record.sequence === sequence);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
487
504
|
async readInputImages(sessionId, value) {
|
|
488
505
|
this.#assertOpen();
|
|
489
506
|
const references = inputImageReferences(value);
|
|
@@ -606,6 +623,7 @@ export class JsonlSessionStore {
|
|
|
606
623
|
this.#shared.segments.set(target.id, staged.segments.length);
|
|
607
624
|
for (const record of copied)
|
|
608
625
|
this.#applyRecordToResumeIndex(target, record);
|
|
626
|
+
await copyToolResultResources(this.sessionDirectory(sourceSessionId), this.sessionDirectory(target.id), copied);
|
|
609
627
|
await copyGeneratedImages(this.sessionDirectory(sourceSessionId), this.sessionDirectory(target.id), generatedImageFileNames(retained));
|
|
610
628
|
await copyInputImages(this.sessionDirectory(sourceSessionId), this.sessionDirectory(target.id), inputImageReferencesFromRecords(retained));
|
|
611
629
|
target.lastActivityAt = target.createdAt;
|
|
@@ -719,6 +737,7 @@ export class JsonlSessionStore {
|
|
|
719
737
|
contextualWorldStateStaged = await renameIfExists(contextualWorldStatePath, contextualWorldStateBackup);
|
|
720
738
|
generatedTrash = await stageUnreferencedImages(this.sessionDirectory(sessionId), 'generated_images', new Set(generatedImageFileNames(retained)), transactionId);
|
|
721
739
|
inputTrash = await stageUnreferencedImages(this.sessionDirectory(sessionId), 'input_images', new Set(inputImageReferencesFromRecords(retained).map((image) => image.fileName)), transactionId);
|
|
740
|
+
inputTrash.push(...(await stageUnreferencedImages(this.sessionDirectory(sessionId), 'tool_results', toolResultResourceFiles(retained), transactionId)));
|
|
722
741
|
for (const item of backups)
|
|
723
742
|
await rename(item.path, item.backup);
|
|
724
743
|
for (const item of staged.files) {
|
|
@@ -880,6 +899,7 @@ export class JsonlSessionStore {
|
|
|
880
899
|
contextualWorldStateStaged = await renameIfExists(contextualWorldStatePath, contextualWorldStateBackup);
|
|
881
900
|
generatedTrash = await stageUnreferencedImages(this.sessionDirectory(sessionId), 'generated_images', new Set(), transactionId);
|
|
882
901
|
inputTrash = await stageUnreferencedImages(this.sessionDirectory(sessionId), 'input_images', new Set(), transactionId);
|
|
902
|
+
inputTrash.push(...(await stageUnreferencedImages(this.sessionDirectory(sessionId), 'tool_results', new Set(), transactionId)));
|
|
883
903
|
for (const item of backups)
|
|
884
904
|
await rename(item.path, item.backup);
|
|
885
905
|
for (const item of staged.files) {
|
|
@@ -998,6 +1018,8 @@ export class JsonlSessionStore {
|
|
|
998
1018
|
type: input.type,
|
|
999
1019
|
payload: input.payload
|
|
1000
1020
|
};
|
|
1021
|
+
JSON.stringify(record); // Reject malformed payloads before binding resources.
|
|
1022
|
+
await bindToolResultResources(this.sessionDirectory(sessionId), record, (sequence) => this.#readRecordAtSequence(meta, sequence), true);
|
|
1001
1023
|
const serialized = `${JSON.stringify(record)}\n`;
|
|
1002
1024
|
let segment = await this.#currentSegment(sessionId);
|
|
1003
1025
|
let path = this.#segmentPath(sessionId, segment);
|
|
@@ -1028,6 +1050,7 @@ export class JsonlSessionStore {
|
|
|
1028
1050
|
await handle.close();
|
|
1029
1051
|
}
|
|
1030
1052
|
meta.nextSequence++;
|
|
1053
|
+
await completeToolResultResources(this.sessionDirectory(sessionId), record);
|
|
1031
1054
|
if (record.type === 'turn.user' &&
|
|
1032
1055
|
isObject(record.payload) &&
|
|
1033
1056
|
Array.isArray(record.payload.images))
|
|
@@ -1798,6 +1821,7 @@ export class JsonlSessionStore {
|
|
|
1798
1821
|
await repairSegmentTail(latestPath);
|
|
1799
1822
|
const records = await readRecords(latestPath);
|
|
1800
1823
|
await recoverPendingInputImages(this.sessionDirectory(sessionId), () => this.#readRecords(sessionId));
|
|
1824
|
+
await cleanupUnreferencedToolResources(this.sessionDirectory(sessionId), () => this.#readRecords(sessionId));
|
|
1801
1825
|
const last = records.at(-1);
|
|
1802
1826
|
const match = /events-(\d{6})\.jsonl$/.exec(latestPath);
|
|
1803
1827
|
const segment = Number(match?.[1] ?? 1);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AvailableToolResultResource } from '../tools/result-extensions.js';
|
|
2
|
+
import type { SessionRecord } from './jsonl-store.js';
|
|
3
|
+
export interface ToolResultResourceInput {
|
|
4
|
+
resourceRef: string;
|
|
5
|
+
cursor?: string;
|
|
6
|
+
maxBytes?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ToolResultResourcePage {
|
|
9
|
+
type: 'file_changes';
|
|
10
|
+
version: 1;
|
|
11
|
+
format: 'unified-diff';
|
|
12
|
+
resourceRef: string;
|
|
13
|
+
byteSize: number;
|
|
14
|
+
sha256: string;
|
|
15
|
+
content: string;
|
|
16
|
+
nextCursor?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function writeToolResultResource(directory: string, text: string): Promise<AvailableToolResultResource>;
|
|
19
|
+
export declare function recordToolResources(record: Pick<SessionRecord, 'type' | 'payload'>): AvailableToolResultResource[];
|
|
20
|
+
/** A locator is only a hint: reads must verify its exact committed record identity. */
|
|
21
|
+
export declare function bindToolResultResources(directory: string, record: SessionRecord, readRecord?: (sequence: number) => Promise<SessionRecord | undefined>, degrade?: boolean): Promise<void>;
|
|
22
|
+
export declare function toolResultResourceFiles(records: readonly SessionRecord[]): Set<string>;
|
|
23
|
+
export declare function copyToolResultResources(source: string, target: string, records: readonly SessionRecord[]): Promise<void>;
|
|
24
|
+
export declare function completeToolResultResources(directory: string, record: SessionRecord): Promise<void>;
|
|
25
|
+
export declare function cleanupUnreferencedToolResources(directory: string, readRecords: () => Promise<readonly SessionRecord[]>): Promise<void>;
|
|
26
|
+
export declare function readToolResultResource(directory: string, sessionId: string, input: ToolResultResourceInput, readRecord: (sequence: number) => Promise<SessionRecord | undefined>): Promise<ToolResultResourcePage>;
|