@myagentroam/agent 0.11.20 → 0.11.22

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.
Files changed (81) hide show
  1. package/dist/agent-core/error.d.ts +1 -1
  2. package/dist/agent-core/error.js +20 -14
  3. package/dist/agent-core/extensions/compaction.d.ts +6 -0
  4. package/dist/agent-core/extensions/compaction.js +16 -2
  5. package/dist/agent-core/extensions/model-request.d.ts +5 -9
  6. package/dist/agent-core/extensions/model-request.js +14 -3
  7. package/dist/agent-core/extensions/prompt-input.js +3 -1
  8. package/dist/agent-core/host/contracts.d.ts +8 -0
  9. package/dist/agent-core/host/local-agent-host.d.ts +2 -0
  10. package/dist/agent-core/host/local-agent-host.js +3 -0
  11. package/dist/agent-core/model/anthropic-messages.js +15 -26
  12. package/dist/agent-core/model/catalog-file.d.ts +2 -0
  13. package/dist/agent-core/model/configuration.d.ts +3 -1
  14. package/dist/agent-core/model/configuration.js +1 -0
  15. package/dist/agent-core/model/contracts.d.ts +5 -8
  16. package/dist/agent-core/model/image.d.ts +17 -0
  17. package/dist/agent-core/model/image.js +53 -0
  18. package/dist/agent-core/model/openai-responses.d.ts +7 -2
  19. package/dist/agent-core/model/openai-responses.js +81 -27
  20. package/dist/agent-core/runtime/compaction/retained-context.js +2 -9
  21. package/dist/agent-core/runtime/context/gc-runtime.d.ts +3 -1
  22. package/dist/agent-core/runtime/context/gc-runtime.js +17 -6
  23. package/dist/agent-core/runtime/context/gc-tokens.js +13 -0
  24. package/dist/agent-core/runtime/context/gc.d.ts +2 -1
  25. package/dist/agent-core/runtime/context/gc.js +75 -12
  26. package/dist/agent-core/runtime/context/projection.js +4 -0
  27. package/dist/agent-core/runtime/context/request-budget.d.ts +2 -4
  28. package/dist/agent-core/runtime/context/restore.d.ts +2 -0
  29. package/dist/agent-core/runtime/context/restore.js +19 -3
  30. package/dist/agent-core/runtime/contracts.d.ts +2 -3
  31. package/dist/agent-core/runtime/execution.d.ts +1 -1
  32. package/dist/agent-core/runtime/execution.js +10 -4
  33. package/dist/agent-core/runtime/resource-publication.d.ts +8 -0
  34. package/dist/agent-core/runtime/resource-publication.js +60 -0
  35. package/dist/agent-core/sdk/agent.js +16 -0
  36. package/dist/agent-core/sdk/model-runtime.js +2 -0
  37. package/dist/agent-core/sdk/resource-input.d.ts +31 -0
  38. package/dist/agent-core/sdk/resource-input.js +342 -0
  39. package/dist/agent-core/sdk/session.js +144 -39
  40. package/dist/agent-core/session/catalog.d.ts +4 -0
  41. package/dist/agent-core/session/catalog.js +4 -0
  42. package/dist/agent-core/session/context-gc-checkpoint.d.ts +6 -4
  43. package/dist/agent-core/session/context-gc-checkpoint.js +41 -16
  44. package/dist/agent-core/session/inline-media.d.ts +45 -0
  45. package/dist/agent-core/session/inline-media.js +163 -0
  46. package/dist/agent-core/session/store/contracts.d.ts +11 -0
  47. package/dist/agent-core/session/store/history.d.ts +1 -1
  48. package/dist/agent-core/session/store/history.js +26 -2
  49. package/dist/agent-core/session/store/images.d.ts +4 -6
  50. package/dist/agent-core/session/store/images.js +18 -111
  51. package/dist/agent-core/session/store/jsonl-store.d.ts +9 -0
  52. package/dist/agent-core/session/store/jsonl-store.js +100 -28
  53. package/dist/agent-core/session/store/resources.d.ts +26 -0
  54. package/dist/agent-core/session/store/resources.js +196 -0
  55. package/dist/agent-core/session/store/types.d.ts +7 -6
  56. package/dist/agent-core/session/transaction-recovery.js +1 -1
  57. package/dist/agent-core/tools/code-mode/cells.js +3 -4
  58. package/dist/agent-core/tools/code-mode/index.js +32 -8
  59. package/dist/agent-core/tools/code-mode/observations.d.ts +5 -16
  60. package/dist/agent-core/tools/code-mode/quickjs-engine.js +23 -26
  61. package/dist/agent-core/tools/contracts.d.ts +15 -5
  62. package/dist/agent-core/tools/execution-limits.d.ts +3 -1
  63. package/dist/agent-core/tools/execution-limits.js +3 -1
  64. package/dist/agent-core/tools/execution.d.ts +2 -1
  65. package/dist/agent-core/tools/execution.js +31 -4
  66. package/dist/agent-core/tools/input-diagnostics/source-preview.d.ts +10 -0
  67. package/dist/agent-core/tools/input-diagnostics/source-preview.js +64 -0
  68. package/dist/agent-core/tools/local/apply-patch-parser.js +2 -53
  69. package/dist/agent-core/tools/local/apply-patch.js +1 -1
  70. package/dist/agent-core/tools/local/image-processing.d.ts +24 -0
  71. package/dist/agent-core/tools/local/image-processing.js +135 -0
  72. package/dist/agent-core/tools/local/read.js +1 -1
  73. package/dist/agent-core/tools/local/view-image.d.ts +4 -0
  74. package/dist/agent-core/tools/local/view-image.js +154 -25
  75. package/dist/agent-core/tools/output-projection.d.ts +2 -4
  76. package/dist/agent-core/tools/output-projection.js +81 -10
  77. package/dist/agent-core/tools/runtime-context.d.ts +1 -0
  78. package/dist/agent-core/tools/runtime.js +20 -1
  79. package/dist/cli/main.js +2 -1
  80. package/dist/index.d.ts +1 -1
  81. package/package.json +2 -1
@@ -0,0 +1,163 @@
1
+ import { MarAgentError } from '../error.js';
2
+ import { appliedInputRecord } from './deferred-input.js';
3
+ const MiB = 1_048_576;
4
+ export const INLINE_MEDIA_POLICY = Object.freeze({
5
+ historicalInlineMediaTrigger: 7 * MiB,
6
+ historicalInlineMediaTarget: 5 * MiB,
7
+ requestInlineMediaHardLimit: 12 * MiB
8
+ });
9
+ export function parseInlineMediaGc(value) {
10
+ if (!value || typeof value !== 'object')
11
+ return undefined;
12
+ const item = value;
13
+ const state = parseInlineMediaState(item.state);
14
+ return state?.evictedThrough &&
15
+ state.consideredThrough &&
16
+ Number.isSafeInteger(item.beforeBytes) &&
17
+ item.beforeBytes > 0 &&
18
+ Number.isSafeInteger(item.afterBytes) &&
19
+ item.afterBytes >= 0 &&
20
+ item.afterBytes <= INLINE_MEDIA_POLICY.historicalInlineMediaTarget &&
21
+ item.afterBytes < item.beforeBytes &&
22
+ (item.reason === 'SOFT_LIMIT' || item.reason === 'HARD_LIMIT' || item.reason === 'TEXT_GC') &&
23
+ (item.reason !== 'SOFT_LIMIT' ||
24
+ item.beforeBytes > INLINE_MEDIA_POLICY.historicalInlineMediaTrigger) &&
25
+ (item.reason !== 'TEXT_GC' ||
26
+ item.beforeBytes > INLINE_MEDIA_POLICY.historicalInlineMediaTarget)
27
+ ? { reason: item.reason, state, beforeBytes: item.beforeBytes, afterBytes: item.afterBytes }
28
+ : undefined;
29
+ }
30
+ export function inlineMediaSource(message) {
31
+ return message.callId !== undefined
32
+ ? `tool:${message.callId}`
33
+ : message.metadata?.recordId !== undefined
34
+ ? `input:${message.metadata.recordId}`
35
+ : message;
36
+ }
37
+ /** GC may advance only a durable, already-considered source prefix. */
38
+ export function inlineMediaGcAnchored(records, media) {
39
+ const history = inlineMediaHistory(records);
40
+ const positions = new Map();
41
+ const key = (cursor) => JSON.stringify([cursor?.sourceId, cursor?.imageIndex]);
42
+ for (const cursor of history.cursors) {
43
+ const identity = key({ sourceId: cursor.sourceId, imageIndex: cursor.imageIndex });
44
+ if (!positions.has(identity))
45
+ positions.set(identity, { index: positions.size, rollout: cursor.rollout });
46
+ }
47
+ const evicted = positions.get(key(media.state.evictedThrough));
48
+ const considered = positions.get(key(media.state.consideredThrough));
49
+ const previous = positions.get(key(history.state?.evictedThrough));
50
+ return (evicted !== undefined &&
51
+ considered !== undefined &&
52
+ key(media.state.consideredThrough) === key(history.state?.consideredThrough) &&
53
+ evicted.index <= considered.index &&
54
+ evicted.index > (previous?.index ?? -1) &&
55
+ (media.reason === 'HARD_LIMIT' || history.rollouts - evicted.rollout >= 2));
56
+ }
57
+ export function inlineMediaBytes(image) {
58
+ const bytes = image.dataBase64 !== undefined
59
+ ? image.dataBase64.length
60
+ : image.byteSize !== undefined
61
+ ? 4 * Math.ceil(image.byteSize / 3)
62
+ : undefined;
63
+ if (bytes === undefined)
64
+ throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Resource size is unavailable.');
65
+ return bytes + Buffer.byteLength(`data:${image.mimeType};base64,`);
66
+ }
67
+ export function inlineMediaLimit() {
68
+ throw new MarAgentError('MAR_AGENT_INLINE_MEDIA_LIMIT', 'Inline images exceed the 12 MiB request limit. Reduce this image batch or use view_image with resize or region before retrying.');
69
+ }
70
+ /** Final encoding guard; URL-only images do not consume the inline budget. */
71
+ export function assertInlineMediaBudget(request) {
72
+ let total = 0;
73
+ for (const message of request.messages)
74
+ for (const image of message.images ?? [])
75
+ if (image.dataBase64 !== undefined)
76
+ total += inlineMediaBytes(image);
77
+ for (const image of request.images ?? [])
78
+ if (image.dataBase64 !== undefined)
79
+ total += inlineMediaBytes(image);
80
+ if (total > INLINE_MEDIA_POLICY.requestInlineMediaHardLimit)
81
+ inlineMediaLimit();
82
+ }
83
+ export function parseInlineMediaState(value) {
84
+ if (!value || typeof value !== 'object' || Array.isArray(value))
85
+ return undefined;
86
+ const state = value;
87
+ const cursor = (value) => {
88
+ if (!value || typeof value !== 'object')
89
+ return false;
90
+ const item = value;
91
+ return (typeof item.sourceId === 'string' &&
92
+ item.sourceId.length > 0 &&
93
+ item.sourceId.length <= 256 &&
94
+ Number.isSafeInteger(item.imageIndex) &&
95
+ item.imageIndex >= 0);
96
+ };
97
+ if (state.version !== 1 ||
98
+ (state.consideredThrough !== undefined && !cursor(state.consideredThrough)) ||
99
+ (state.evictedThrough !== undefined && !cursor(state.evictedThrough)))
100
+ return undefined;
101
+ return structuredClone(state);
102
+ }
103
+ /** Recover ordering once at the lifecycle boundary, including GC-omitted sources. */
104
+ export function inlineMediaHistory(records) {
105
+ const cursors = [];
106
+ let rollouts = 0;
107
+ let state;
108
+ const add = (sourceId, images) => {
109
+ if (!sourceId || !Array.isArray(images))
110
+ return;
111
+ for (let imageIndex = 0; imageIndex < images.length; imageIndex++)
112
+ cursors.push({ sourceId, imageIndex, rollout: rollouts });
113
+ };
114
+ for (const record of records) {
115
+ const payload = record.payload;
116
+ if (!payload || typeof payload !== 'object')
117
+ continue;
118
+ if (record.type === 'model.attempt' &&
119
+ payload.purpose === 'EXECUTION' &&
120
+ payload.state === 'SUCCEEDED')
121
+ rollouts++;
122
+ if (record.type === 'compact.completed') {
123
+ cursors.splice(0);
124
+ state = parseInlineMediaState(payload.inlineMediaState);
125
+ for (const item of (payload.retainedModelContext ?? [])) {
126
+ const applied = appliedInputRecord({ ...record, type: 'model.context', payload: item });
127
+ if (applied) {
128
+ add(`input:${applied.recordId}`, applied.payload.images);
129
+ continue;
130
+ }
131
+ const key = inlineMediaSource(item);
132
+ add(typeof key === 'string' ? key : undefined, item.images);
133
+ }
134
+ }
135
+ else if (record.type === 'resource.window') {
136
+ state = parseInlineMediaState(payload);
137
+ }
138
+ else if (record.type === 'context.gc.completed' && payload.inlineMedia !== undefined) {
139
+ const media = parseInlineMediaGc(payload.inlineMedia);
140
+ if (!media)
141
+ throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Inline media GC state is invalid.');
142
+ state = media.state;
143
+ }
144
+ else if ((record.type === 'turn.user' || record.type === 'extension.input') &&
145
+ payload.deferred !== true) {
146
+ add(`input:${record.recordId}`, payload.images);
147
+ }
148
+ else if (record.type === 'model.context') {
149
+ const applied = appliedInputRecord(record);
150
+ if (applied) {
151
+ add(`input:${applied.recordId}`, applied.payload.images);
152
+ continue;
153
+ }
154
+ const key = typeof payload.inputRef === 'string'
155
+ ? `input:${payload.inputRef}`
156
+ : typeof payload.callId === 'string'
157
+ ? `tool:${payload.callId}`
158
+ : undefined;
159
+ add(key, payload.images);
160
+ }
161
+ }
162
+ return { cursors, rollouts, ...(state ? { state } : {}) };
163
+ }
@@ -10,6 +10,7 @@ import type { SessionListInput, SessionSource, SessionTitleUpdateInput } from '.
10
10
  import type { CompactHistoryMessage, RestoredSessionInputImage, SessionExecutionState, SessionInputImage, SessionInputImageData, SessionInputImageMetadata, SessionRecord } from './types.js';
11
11
  export type { CompactHistoryMessage, CompactUserInput, RestoredSessionInputImage, SessionExecutionState, SessionInputImage, SessionInputImageData, SessionInputImageMetadata, SessionRecord } from './types.js';
12
12
  export type { CodeModeStateEntry } from '../../tools/code-mode/state.js';
13
+ export type { SessionResource } from './resources.js';
13
14
  export type { TodoSnapshot, TodoItem } from '../../tools/contracts.js';
14
15
  export type { CodeToolCallRecord, CodeObservation } from '../../tools/code-mode/observations.js';
15
16
  export type { ModelMessage, ModelUsage, ModelInputMetadata, ModelTextBlock, ModelContextualBlock } from '../../model/contracts.js';
@@ -38,6 +39,7 @@ export interface SessionStoreCompactInput {
38
39
  summary: string;
39
40
  replacementHistory?: readonly CompactHistoryMessage[];
40
41
  retainedModelContext?: readonly ModelMessage[];
42
+ inlineMediaState?: import('../inline-media.js').InlineMediaState;
41
43
  pendingInputs?: readonly SessionRecord[];
42
44
  snapshotRecordId?: string;
43
45
  snapshotMessageCount?: number;
@@ -116,6 +118,15 @@ export interface SessionStore {
116
118
  }): Promise<SessionRecord>;
117
119
  commitContextGc(sessionId: string, draft: ContextGcCheckpointDraft, planningRecords?: readonly SessionRecord[]): Promise<SessionRecord>;
118
120
  persistInputImages(sessionId: string, images: readonly SessionInputImage[]): Promise<SessionInputImageMetadata[]>;
121
+ persistResource(sessionId: string, image: import('../../model/image.js').ModelImage): Promise<import('./resources.js').SessionResource>;
122
+ setResourceUrl(sessionId: string, resourceId: string, published: {
123
+ url: string;
124
+ urlExpiresAt: number;
125
+ }): Promise<import('./resources.js').SessionResource>;
126
+ readResource(sessionId: string, resourceId: string): Promise<{
127
+ mimeType: string;
128
+ dataBase64: string;
129
+ } | undefined>;
119
130
  readInputImages(sessionId: string, value: unknown): Promise<RestoredSessionInputImage[]>;
120
131
  readInputImage(sessionId: string, input: {
121
132
  readonly sequence: number;
@@ -8,7 +8,7 @@ interface StagedSessionSegments {
8
8
  readonly segments: NonNullable<SessionMeta['segments']>;
9
9
  }
10
10
  export declare function stageSessionSegments(sessionDirectory: string, records: readonly SessionRecord[], operation?: 'rollback' | 'fork'): Promise<StagedSessionSegments>;
11
- export declare function stageUnreferencedImages(sessionDirectory: string, directoryName: 'input_images' | 'generated_images' | 'tool_results', retained: ReadonlySet<string>, transactionId: string): Promise<Array<{
11
+ export declare function stageUnreferencedImages(sessionDirectory: string, directoryName: 'generated_images' | 'tool_results' | 'resources', retained: ReadonlySet<string>, transactionId: string): Promise<Array<{
12
12
  from: string;
13
13
  to: string;
14
14
  trash: string;
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { open, readdir, rename, rm } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { MarAgentError } from '../../error.js';
5
+ import { parseInlineMediaState } from '../inline-media.js';
5
6
  import { MAX_SEGMENT_BYTES } from './records.js';
6
7
  export async function renameIfExists(from, to) {
7
8
  try {
@@ -117,11 +118,32 @@ export function forkedPayload(type, value, sourceSessionId, targetSessionId, tit
117
118
  if (value === null || typeof value !== 'object' || Array.isArray(value))
118
119
  return value;
119
120
  const payload = { ...value };
121
+ const remapWindow = (value) => {
122
+ const state = parseInlineMediaState(value);
123
+ if (!state)
124
+ return value;
125
+ for (const cursor of [state.consideredThrough, state.evictedThrough]) {
126
+ if (!cursor?.sourceId.startsWith('input:'))
127
+ continue;
128
+ const target = identities.get(cursor.sourceId.slice(6));
129
+ if (!target)
130
+ throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Fork media reference is missing.');
131
+ cursor.sourceId = `input:${target.recordId}`;
132
+ }
133
+ return state;
134
+ };
135
+ if (type === 'resource.window')
136
+ return remapWindow(payload);
120
137
  if (payload.sessionId === sourceSessionId)
121
138
  payload.sessionId = targetSessionId;
122
139
  const application = (input) => {
123
- if (typeof input.inputRef !== 'string')
124
- return input;
140
+ if (typeof input.inputRef !== 'string') {
141
+ const recordId = isObject(input.metadata) ? input.metadata.recordId : undefined;
142
+ const identity = typeof recordId === 'string' ? identities.get(recordId) : undefined;
143
+ return identity && isObject(input.metadata)
144
+ ? { ...input, metadata: { ...input.metadata, recordId: identity.recordId } }
145
+ : input;
146
+ }
125
147
  const identity = identities.get(input.inputRef);
126
148
  if (!identity)
127
149
  throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Fork input reference is missing.');
@@ -136,6 +158,8 @@ export function forkedPayload(type, value, sourceSessionId, targetSessionId, tit
136
158
  if (type === 'model.context')
137
159
  return application(payload);
138
160
  if (type === 'compact.completed') {
161
+ if (payload.inlineMediaState)
162
+ payload.inlineMediaState = remapWindow(payload.inlineMediaState);
139
163
  if (Array.isArray(payload.retainedModelContext))
140
164
  payload.retainedModelContext = payload.retainedModelContext.map((item) => isObject(item) ? application(item) : item);
141
165
  if (Array.isArray(payload.pendingInputs))
@@ -1,4 +1,4 @@
1
- import type { SessionInputImage, SessionInputImageMetadata, SessionRecord } from './types.js';
1
+ import type { SessionRecord } from './types.js';
2
2
  export declare const MAX_INPUT_IMAGES = 5;
3
3
  export declare function generatedImageFileNames(records: readonly SessionRecord[]): readonly string[];
4
4
  export declare function copyGeneratedImages(sourceSessionDirectory: string, targetSessionDirectory: string, fileNames: readonly string[]): Promise<void>;
@@ -8,14 +8,12 @@ interface InputImageReference {
8
8
  readonly mimeType: string;
9
9
  readonly byteSize: number;
10
10
  readonly sha256: string;
11
+ readonly resourceId?: string;
12
+ readonly url?: string;
13
+ readonly urlExpiresAt?: number;
11
14
  }
12
- export declare function inputImageReferencesFromRecords(records: readonly SessionRecord[]): readonly InputImageReference[];
13
15
  export declare function inputImageReferences(value: unknown): InputImageReference[];
14
16
  export declare function inputImageReference(value: unknown): InputImageReference | undefined;
15
17
  export declare function validInputImageFileName(fileName: string): boolean;
16
18
  export declare function readInputImageBytes(directory: string, reference: InputImageReference): Promise<Buffer>;
17
- export declare function copyInputImages(sourceSessionDirectory: string, targetSessionDirectory: string, references: readonly InputImageReference[]): Promise<void>;
18
- /** A pending marker is removable only after the committed JSONL references are checked. */
19
- export declare function recoverPendingInputImages(sessionDirectory: string, readHistory: () => Promise<SessionRecord[]>): Promise<void>;
20
- export declare function writeInputImages(directory: string, images: readonly SessionInputImage[]): Promise<SessionInputImageMetadata[]>;
21
19
  export {};
@@ -1,8 +1,9 @@
1
- import { createHash, randomUUID } from 'node:crypto';
1
+ import { createHash } from 'node:crypto';
2
2
  import { constants } from 'node:fs';
3
- import { copyFile, lstat, mkdir, open, readdir, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { copyFile, lstat, mkdir, open } from 'node:fs/promises';
4
4
  import { basename, join } from 'node:path';
5
5
  import { MarAgentError } from '../../error.js';
6
+ import { isModelImageUrl } from '../../model/image.js';
6
7
  export const MAX_INPUT_IMAGES = 5;
7
8
  const MAX_INPUT_IMAGE_BYTES = 5 * 1024 * 1024;
8
9
  const MAX_INPUT_IMAGES_BYTES = 10 * 1024 * 1024;
@@ -36,23 +37,6 @@ export async function copyGeneratedImages(sourceSessionDirectory, targetSessionD
36
37
  for (const fileName of fileNames)
37
38
  await copyFile(join(source, fileName), join(target, fileName));
38
39
  }
39
- export function inputImageReferencesFromRecords(records) {
40
- const references = new Map();
41
- for (const record of records) {
42
- if (record.type !== 'turn.user' || !isObject(record.payload))
43
- continue;
44
- for (const reference of inputImageReferences(record.payload.images)) {
45
- const existing = references.get(reference.fileName);
46
- if (existing !== undefined &&
47
- (existing.mimeType !== reference.mimeType ||
48
- existing.byteSize !== reference.byteSize ||
49
- existing.sha256 !== reference.sha256))
50
- throw corruptInputImageReference();
51
- references.set(reference.fileName, reference);
52
- }
53
- }
54
- return [...references.values()];
55
- }
56
40
  export function inputImageReferences(value) {
57
41
  if (!Array.isArray(value))
58
42
  return [];
@@ -82,14 +66,27 @@ export function inputImageReference(value) {
82
66
  typeof value.fileName !== 'string' ||
83
67
  !validInputImageFileName(value.fileName) ||
84
68
  typeof value.sha256 !== 'string' ||
85
- !/^[a-f0-9]{64}$/u.test(value.sha256))
69
+ !/^[a-f0-9]{64}$/u.test(value.sha256) ||
70
+ (value.resourceId !== undefined &&
71
+ (typeof value.resourceId !== 'string' ||
72
+ !/^[a-f0-9]{64}$/u.test(value.resourceId) ||
73
+ value.fileName !== `${value.resourceId}.bin`)) ||
74
+ (value.url !== undefined && !isModelImageUrl(value.url)) ||
75
+ (value.urlExpiresAt !== undefined &&
76
+ (typeof value.urlExpiresAt !== 'number' ||
77
+ !Number.isSafeInteger(value.urlExpiresAt) ||
78
+ value.urlExpiresAt <= 0)) ||
79
+ (value.urlExpiresAt !== undefined && value.url === undefined))
86
80
  throw corruptInputImageReference();
87
81
  return {
88
82
  name: value.name,
89
83
  fileName: value.fileName,
90
84
  mimeType: value.mimeType,
91
85
  byteSize: value.byteSize,
92
- sha256: value.sha256
86
+ sha256: value.sha256,
87
+ ...(typeof value.resourceId === 'string' ? { resourceId: value.resourceId } : {}),
88
+ ...(value.url === undefined ? {} : { url: value.url }),
89
+ ...(typeof value.urlExpiresAt === 'number' ? { urlExpiresAt: value.urlExpiresAt } : {})
93
90
  };
94
91
  }
95
92
  export function validInputImageFileName(fileName) {
@@ -148,101 +145,11 @@ async function readExactBytes(handle, byteSize) {
148
145
  throw corruptInputImageReference();
149
146
  return data;
150
147
  }
151
- export async function copyInputImages(sourceSessionDirectory, targetSessionDirectory, references) {
152
- if (references.length === 0)
153
- return;
154
- const source = join(sourceSessionDirectory, 'input_images');
155
- const target = join(targetSessionDirectory, 'input_images');
156
- await mkdir(target, { recursive: true, mode: 0o700 });
157
- for (const reference of references) {
158
- const data = await readInputImageBytes(source, reference);
159
- await writeFile(join(target, reference.fileName), data, { mode: 0o600, flag: 'wx' });
160
- }
161
- }
162
148
  function corruptInputImageReference(cause) {
163
149
  return new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Session input image is corrupt.', {
164
150
  ...(cause === undefined ? {} : { cause })
165
151
  });
166
152
  }
167
- /** A pending marker is removable only after the committed JSONL references are checked. */
168
- export async function recoverPendingInputImages(sessionDirectory, readHistory) {
169
- const directory = join(sessionDirectory, 'input_images');
170
- let names;
171
- try {
172
- names = await readdir(directory);
173
- }
174
- catch (error) {
175
- if (error.code === 'ENOENT')
176
- return;
177
- throw error;
178
- }
179
- const pending = names.filter((name) => /^\.[0-9a-f-]{36}\.bin\.pending$/iu.test(name));
180
- const temporary = names.filter((name) => /^\.[0-9a-f-]{36}\.bin\.[0-9a-f-]{36}\.tmp$/iu.test(name));
181
- if (pending.length === 0 && temporary.length === 0)
182
- return;
183
- const referenced = pending.length === 0
184
- ? new Set()
185
- : new Set(inputImageReferencesFromRecords(await readHistory()).map((image) => image.fileName));
186
- for (const name of pending) {
187
- const fileName = name.slice(1, -'.pending'.length);
188
- if (!referenced.has(fileName))
189
- await rm(join(directory, fileName), { force: true });
190
- await rm(join(directory, name), { force: true });
191
- }
192
- for (const name of temporary)
193
- await rm(join(directory, name), { force: true });
194
- }
195
- export async function writeInputImages(directory, images) {
196
- await mkdir(directory, { recursive: true, mode: 0o700 });
197
- const written = [];
198
- const pending = [];
199
- let temporary;
200
- let totalBytes = 0;
201
- try {
202
- const metadata = [];
203
- for (const image of images) {
204
- if (!INPUT_IMAGE_MIME_TYPES.has(image.mimeType))
205
- throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'Image input MIME type is unsupported.');
206
- const data = Buffer.from(image.dataBase64, 'base64');
207
- totalBytes += data.byteLength;
208
- if (data.byteLength === 0 ||
209
- data.byteLength > MAX_INPUT_IMAGE_BYTES ||
210
- totalBytes > MAX_INPUT_IMAGES_BYTES)
211
- throw new MarAgentError('MAR_AGENT_HOST_LIMIT_EXCEEDED', 'Image input exceeds the supported size limit.');
212
- const fileName = `${randomUUID()}.bin`;
213
- temporary = join(directory, `.${fileName}.${randomUUID()}.tmp`);
214
- const handle = await open(temporary, 'wx', 0o600);
215
- try {
216
- await handle.writeFile(data);
217
- await handle.sync();
218
- }
219
- finally {
220
- await handle.close();
221
- }
222
- const marker = join(directory, `.${fileName}.pending`);
223
- await writeFile(marker, '', { mode: 0o600, flag: 'wx', flush: true });
224
- pending.push(marker);
225
- await rename(temporary, join(directory, fileName));
226
- temporary = undefined;
227
- written.push(fileName);
228
- metadata.push({
229
- name: image.name,
230
- mimeType: image.mimeType,
231
- byteSize: data.byteLength,
232
- fileName,
233
- sha256: createHash('sha256').update(data).digest('hex')
234
- });
235
- }
236
- return metadata;
237
- }
238
- catch (error) {
239
- if (temporary !== undefined)
240
- await rm(temporary, { force: true }).catch(() => undefined);
241
- await Promise.all(pending.map((marker) => rm(marker, { force: true })));
242
- await Promise.all(written.map((fileName) => rm(join(directory, fileName), { force: true }).catch(() => undefined)));
243
- throw error;
244
- }
245
- }
246
153
  function isObject(value) {
247
154
  return value !== null && typeof value === 'object' && !Array.isArray(value);
248
155
  }
@@ -19,6 +19,15 @@ export declare class JsonlSessionStore implements SessionStore {
19
19
  writeContextualWorldState(sessionId: string, state: unknown): Promise<void>;
20
20
  /** Mark each image pending before publication; turn.user or lease recovery resolves ownership. */
21
21
  persistInputImages(sessionId: string, images: readonly SessionInputImage[]): Promise<SessionInputImageMetadata[]>;
22
+ persistResource(sessionId: string, image: import('../../model/image.js').ModelImage): Promise<import("./resources.js").SessionResource>;
23
+ setResourceUrl(sessionId: string, resourceId: string, published: {
24
+ url: string;
25
+ urlExpiresAt: number;
26
+ }): Promise<import("./resources.js").SessionResource>;
27
+ readResource(sessionId: string, resourceId: string): Promise<{
28
+ mimeType: string;
29
+ dataBase64: string;
30
+ } | undefined>;
22
31
  writeToolResultResource(sessionId: string, text: string): Promise<{
23
32
  status: "available";
24
33
  resourceRef: string;