@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.
- package/dist/agent-core/error.d.ts +1 -1
- package/dist/agent-core/error.js +20 -14
- package/dist/agent-core/extensions/compaction.d.ts +6 -0
- package/dist/agent-core/extensions/compaction.js +16 -2
- package/dist/agent-core/extensions/model-request.d.ts +5 -9
- package/dist/agent-core/extensions/model-request.js +14 -3
- package/dist/agent-core/extensions/prompt-input.js +3 -1
- package/dist/agent-core/host/contracts.d.ts +8 -0
- package/dist/agent-core/host/local-agent-host.d.ts +2 -0
- package/dist/agent-core/host/local-agent-host.js +3 -0
- package/dist/agent-core/model/anthropic-messages.js +15 -26
- package/dist/agent-core/model/catalog-file.d.ts +2 -0
- package/dist/agent-core/model/configuration.d.ts +3 -1
- package/dist/agent-core/model/configuration.js +1 -0
- package/dist/agent-core/model/contracts.d.ts +5 -8
- package/dist/agent-core/model/image.d.ts +17 -0
- package/dist/agent-core/model/image.js +53 -0
- package/dist/agent-core/model/openai-responses.d.ts +7 -2
- package/dist/agent-core/model/openai-responses.js +81 -27
- package/dist/agent-core/runtime/compaction/retained-context.js +2 -9
- package/dist/agent-core/runtime/context/gc-runtime.d.ts +3 -1
- package/dist/agent-core/runtime/context/gc-runtime.js +17 -6
- package/dist/agent-core/runtime/context/gc-tokens.js +13 -0
- package/dist/agent-core/runtime/context/gc.d.ts +2 -1
- package/dist/agent-core/runtime/context/gc.js +75 -12
- package/dist/agent-core/runtime/context/projection.js +4 -0
- package/dist/agent-core/runtime/context/request-budget.d.ts +2 -4
- package/dist/agent-core/runtime/context/restore.d.ts +2 -0
- package/dist/agent-core/runtime/context/restore.js +19 -3
- package/dist/agent-core/runtime/contracts.d.ts +2 -3
- package/dist/agent-core/runtime/execution.d.ts +1 -1
- package/dist/agent-core/runtime/execution.js +10 -4
- package/dist/agent-core/runtime/resource-publication.d.ts +8 -0
- package/dist/agent-core/runtime/resource-publication.js +60 -0
- package/dist/agent-core/sdk/agent.js +16 -0
- package/dist/agent-core/sdk/model-runtime.js +2 -0
- package/dist/agent-core/sdk/resource-input.d.ts +31 -0
- package/dist/agent-core/sdk/resource-input.js +342 -0
- package/dist/agent-core/sdk/session.js +144 -39
- package/dist/agent-core/session/catalog.d.ts +4 -0
- package/dist/agent-core/session/catalog.js +4 -0
- package/dist/agent-core/session/context-gc-checkpoint.d.ts +6 -4
- package/dist/agent-core/session/context-gc-checkpoint.js +41 -16
- package/dist/agent-core/session/inline-media.d.ts +45 -0
- package/dist/agent-core/session/inline-media.js +163 -0
- package/dist/agent-core/session/store/contracts.d.ts +11 -0
- package/dist/agent-core/session/store/history.d.ts +1 -1
- package/dist/agent-core/session/store/history.js +26 -2
- package/dist/agent-core/session/store/images.d.ts +4 -6
- package/dist/agent-core/session/store/images.js +18 -111
- package/dist/agent-core/session/store/jsonl-store.d.ts +9 -0
- package/dist/agent-core/session/store/jsonl-store.js +100 -28
- package/dist/agent-core/session/store/resources.d.ts +26 -0
- package/dist/agent-core/session/store/resources.js +196 -0
- package/dist/agent-core/session/store/types.d.ts +7 -6
- package/dist/agent-core/session/transaction-recovery.js +1 -1
- package/dist/agent-core/tools/code-mode/cells.js +3 -4
- package/dist/agent-core/tools/code-mode/index.js +32 -8
- package/dist/agent-core/tools/code-mode/observations.d.ts +5 -16
- package/dist/agent-core/tools/code-mode/quickjs-engine.js +23 -26
- package/dist/agent-core/tools/contracts.d.ts +15 -5
- package/dist/agent-core/tools/execution-limits.d.ts +3 -1
- package/dist/agent-core/tools/execution-limits.js +3 -1
- package/dist/agent-core/tools/execution.d.ts +2 -1
- package/dist/agent-core/tools/execution.js +31 -4
- package/dist/agent-core/tools/input-diagnostics/source-preview.d.ts +10 -0
- package/dist/agent-core/tools/input-diagnostics/source-preview.js +64 -0
- package/dist/agent-core/tools/local/apply-patch-parser.js +2 -53
- package/dist/agent-core/tools/local/apply-patch.js +1 -1
- package/dist/agent-core/tools/local/image-processing.d.ts +24 -0
- package/dist/agent-core/tools/local/image-processing.js +135 -0
- package/dist/agent-core/tools/local/read.js +1 -1
- package/dist/agent-core/tools/local/view-image.d.ts +4 -0
- package/dist/agent-core/tools/local/view-image.js +154 -25
- package/dist/agent-core/tools/output-projection.d.ts +2 -4
- package/dist/agent-core/tools/output-projection.js +81 -10
- package/dist/agent-core/tools/runtime-context.d.ts +1 -0
- package/dist/agent-core/tools/runtime.js +20 -1
- package/dist/cli/main.js +2 -1
- package/dist/index.d.ts +1 -1
- package/package.json +2 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ModelImage } from '../../model/image.js';
|
|
1
2
|
/** Execution metadata is independent of explicit model observations. */
|
|
2
3
|
export interface CodeToolCallRecord {
|
|
3
4
|
callId: string;
|
|
@@ -41,10 +42,7 @@ export type CodeObservation = {
|
|
|
41
42
|
};
|
|
42
43
|
export declare function observationContent(value: CodeObservation): string;
|
|
43
44
|
export declare function observationImages(value: CodeObservation): readonly number[];
|
|
44
|
-
export declare function selectObservationImages(values: readonly CodeObservation[], images: readonly {
|
|
45
|
-
mimeType: string;
|
|
46
|
-
dataBase64: string;
|
|
47
|
-
}[], offset?: number): {
|
|
45
|
+
export declare function selectObservationImages(values: readonly CodeObservation[], images: readonly ModelImage[], offset?: number): {
|
|
48
46
|
observations: ({
|
|
49
47
|
result: {
|
|
50
48
|
imageIndices: number[];
|
|
@@ -68,10 +66,7 @@ export declare function selectObservationImages(values: readonly CodeObservation
|
|
|
68
66
|
value: string;
|
|
69
67
|
truncated?: boolean;
|
|
70
68
|
})[];
|
|
71
|
-
images:
|
|
72
|
-
mimeType: string;
|
|
73
|
-
dataBase64: string;
|
|
74
|
-
}[];
|
|
69
|
+
images: ModelImage[];
|
|
75
70
|
};
|
|
76
71
|
/** Only the visible projection crosses into model text; program data and inputs stay private. */
|
|
77
72
|
export declare function visibleObservation(value: CodeObservation): Record<string, unknown>;
|
|
@@ -90,19 +85,13 @@ export declare function observationModelParts(message: {
|
|
|
90
85
|
readonly text: string;
|
|
91
86
|
}[];
|
|
92
87
|
observations?: readonly CodeObservation[];
|
|
93
|
-
images?: readonly
|
|
94
|
-
mimeType: string;
|
|
95
|
-
dataBase64: string;
|
|
96
|
-
}[];
|
|
88
|
+
images?: readonly import('../../model/image.js').ModelImage[];
|
|
97
89
|
}): Array<{
|
|
98
90
|
type: 'text';
|
|
99
91
|
text: string;
|
|
100
92
|
} | {
|
|
101
93
|
type: 'image';
|
|
102
|
-
image:
|
|
103
|
-
mimeType: string;
|
|
104
|
-
dataBase64: string;
|
|
105
|
-
};
|
|
94
|
+
image: import('../../model/image.js').ModelImage;
|
|
106
95
|
}> | undefined;
|
|
107
96
|
/** Uses the same visible text as both adapters, without pixels or private observation data. */
|
|
108
97
|
export declare function observationModelText(message: Parameters<typeof observationModelParts>[0]): string | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BoundedTextOutput } from './text-output.js';
|
|
2
|
+
import { sourcePreview } from '../input-diagnostics/source-preview.js';
|
|
2
3
|
// This trusted module runs only in the Host-managed worker, never in the model's JS context.
|
|
3
4
|
/** @returns {Promise<import('./engine.js').CodeModeEngine>} */
|
|
4
5
|
export async function createCodeModeEngine(options) {
|
|
@@ -80,19 +81,25 @@ export async function createCodeModeEngine(options) {
|
|
|
80
81
|
throw executionTimeout();
|
|
81
82
|
let message = String(error?.message ?? error).slice(0, 4096);
|
|
82
83
|
const syntaxError = locateSource && error?.name === 'SyntaxError';
|
|
83
|
-
if (syntaxError)
|
|
84
|
-
|
|
84
|
+
if (syntaxError) {
|
|
85
|
+
const reason = message === 'unexpected end of string' ? 'Unterminated string literal.' : message;
|
|
86
|
+
const location = compilerSourceLocation(error, input.code);
|
|
87
|
+
message = `${reason.slice(0, 256)}\n${location ? `Line ${location.line}${location.column === undefined ? '' : `, column ${location.column}`}. ` : ''}No tools were executed.`;
|
|
88
|
+
if (location?.column !== undefined) {
|
|
89
|
+
const source = input.code.split(/\r\n|[\r\n\u2028\u2029]/u)[location.line - 1];
|
|
90
|
+
const preview = sourcePreview(source, location.column);
|
|
91
|
+
const gutter = String(location.line);
|
|
92
|
+
message += `\n\n${gutter} | ${preview.text}\n${' '.repeat(gutter.length)} | ${' '.repeat(preview.caret)}^`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
85
95
|
if (error?.name === 'ReferenceError' &&
|
|
86
96
|
["'text' is not defined", "'console' is not defined"].includes(message))
|
|
87
97
|
message =
|
|
88
98
|
`${message} Code Mode has no console or standalone text/image helpers; use observe.text(value) or observe.image(value).`.slice(0, 4096);
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
? 'MAR_AGENT_CODE_MEMORY_LIMIT'
|
|
94
|
-
: syntaxError
|
|
95
|
-
? 'MAR_AGENT_CODE_SYNTAX_ERROR'
|
|
99
|
+
const code = syntaxError
|
|
100
|
+
? 'MAR_AGENT_CODE_SYNTAX_ERROR'
|
|
101
|
+
: message.includes('out of memory')
|
|
102
|
+
? 'MAR_AGENT_CODE_MEMORY_LIMIT'
|
|
96
103
|
: typeof error?.code === 'string'
|
|
97
104
|
? error.code
|
|
98
105
|
: 'MAR_AGENT_CODE_FAILED';
|
|
@@ -711,12 +718,12 @@ function compilerSourceLocation(error, source) {
|
|
|
711
718
|
error.fileName !== 'code-mode.js' ||
|
|
712
719
|
!Number.isSafeInteger(error.lineNumber) ||
|
|
713
720
|
error.lineNumber <= 1)
|
|
714
|
-
return
|
|
721
|
+
return undefined;
|
|
715
722
|
// QuickJS counts LF lines and Unicode code-point columns; the wrapper adds one LF line.
|
|
716
723
|
const lines = source.split('\n');
|
|
717
724
|
const line = error.lineNumber - 1;
|
|
718
725
|
if (line > lines.length)
|
|
719
|
-
return
|
|
726
|
+
return originalSourceLocation(source);
|
|
720
727
|
const column = Number(typeof error.stack === 'string'
|
|
721
728
|
? error.stack.match(/^\s*at code-mode\.js:\d+:(\d+)(?:\n|$)/u)?.[1]
|
|
722
729
|
: undefined);
|
|
@@ -725,9 +732,9 @@ function compilerSourceLocation(error, source) {
|
|
|
725
732
|
column > Array.from(lines[line - 1]).length + 1) {
|
|
726
733
|
// Without a valid column, intra-LF-line separators make the original source line ambiguous.
|
|
727
734
|
if (/[\r\u2028\u2029]/u.test(lines[line - 1].replace(/\r$/u, '')))
|
|
728
|
-
return
|
|
735
|
+
return undefined;
|
|
729
736
|
const preceding = line > 1 ? lines.slice(0, line - 1).join('\n') + '\n' : '';
|
|
730
|
-
return
|
|
737
|
+
return { line: preceding.split(/\r\n|[\r\n\u2028\u2029]/u).length };
|
|
731
738
|
}
|
|
732
739
|
const prefix = [
|
|
733
740
|
...lines.slice(0, line - 1),
|
|
@@ -735,23 +742,13 @@ function compilerSourceLocation(error, source) {
|
|
|
735
742
|
.slice(0, column - 1)
|
|
736
743
|
.join('')
|
|
737
744
|
].join('\n');
|
|
738
|
-
return
|
|
745
|
+
return originalSourceLocation(prefix);
|
|
739
746
|
}
|
|
740
|
-
function
|
|
747
|
+
function originalSourceLocation(prefix) {
|
|
741
748
|
const segments = prefix.split(/\r\n|[\r\n\u2028\u2029]/u);
|
|
742
749
|
const line = segments.length;
|
|
743
750
|
const column = Array.from(segments.at(-1)).length + 1;
|
|
744
|
-
|
|
745
|
-
// Source may contain credentials, commands or private code. Show only a punctuation token.
|
|
746
|
-
const token = endOfInput
|
|
747
|
-
? '<end of input>'
|
|
748
|
-
: character === undefined || /[\r\n\u2028\u2029]/u.test(character)
|
|
749
|
-
? '<end of line>'
|
|
750
|
-
: /^[{}[\]():;,.=+*/?!<>|&%\-]$/u.test(character)
|
|
751
|
-
? character
|
|
752
|
-
: '<character omitted>';
|
|
753
|
-
const preview = '<source omitted>';
|
|
754
|
-
return ` (code-mode.js:${line}${endOfInput ? ', end of input' : ''})\nSyntax location: line ${line}, column ${column}\n${line} | ${preview}${token}\n${' '.repeat(String(line).length)} | ${' '.repeat(preview.length)}^`;
|
|
751
|
+
return { line, column };
|
|
755
752
|
}
|
|
756
753
|
function isParameterlessTool(tool) {
|
|
757
754
|
if (tool.parameterless === true)
|
|
@@ -98,10 +98,7 @@ export interface ToolExecutionResult {
|
|
|
98
98
|
/** Runtime-only definitions used to encode a Tool Search output. */
|
|
99
99
|
toolSearchTools?: readonly ClientToolDefinition[];
|
|
100
100
|
truncated?: boolean;
|
|
101
|
-
images?: readonly
|
|
102
|
-
mimeType: string;
|
|
103
|
-
dataBase64: string;
|
|
104
|
-
}[];
|
|
101
|
+
images?: readonly import('../model/image.js').ModelImage[];
|
|
105
102
|
artifacts?: readonly MarAgentToolArtifact[];
|
|
106
103
|
/** Host-facing evidence, excluded from model and programmatic guest results. */
|
|
107
104
|
resultExtensions?: readonly import('./result-extensions.js').ToolResultExtension[];
|
|
@@ -164,10 +161,23 @@ export interface ReadEventData {
|
|
|
164
161
|
}>;
|
|
165
162
|
truncated: boolean;
|
|
166
163
|
}
|
|
164
|
+
export interface ViewImageRegion {
|
|
165
|
+
x: number;
|
|
166
|
+
y: number;
|
|
167
|
+
width: number;
|
|
168
|
+
height: number;
|
|
169
|
+
}
|
|
167
170
|
export interface ViewImageResult {
|
|
168
|
-
|
|
171
|
+
/** The input source; transformations may produce a different output resource. */
|
|
172
|
+
path?: string;
|
|
173
|
+
resourceId?: string;
|
|
169
174
|
mimeType: 'image/png' | 'image/jpeg' | 'image/webp';
|
|
170
175
|
bytes: number;
|
|
176
|
+
width: number;
|
|
177
|
+
height: number;
|
|
178
|
+
sourceWidth?: number;
|
|
179
|
+
sourceHeight?: number;
|
|
180
|
+
region?: ViewImageRegion;
|
|
171
181
|
sha256: string;
|
|
172
182
|
}
|
|
173
183
|
export interface TodoItem {
|
|
@@ -5,7 +5,9 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
|
|
|
5
5
|
readMaxBatchItems: 16;
|
|
6
6
|
readBatchConcurrency: 4;
|
|
7
7
|
readMaxBatchBytes: 4194304;
|
|
8
|
-
viewImageMaxBytes:
|
|
8
|
+
viewImageMaxBytes: 3145728;
|
|
9
|
+
viewImageMaxSourceBytes: 20971520;
|
|
10
|
+
viewImageMaxPixels: 16777216;
|
|
9
11
|
execMaxTimeoutMs: 3600000;
|
|
10
12
|
execDefaultYieldMs: 10000;
|
|
11
13
|
execMaxYieldMs: 30000;
|
|
@@ -5,7 +5,9 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
|
|
|
5
5
|
readMaxBatchItems: 16,
|
|
6
6
|
readBatchConcurrency: 4,
|
|
7
7
|
readMaxBatchBytes: 4_194_304,
|
|
8
|
-
viewImageMaxBytes:
|
|
8
|
+
viewImageMaxBytes: 3_145_728,
|
|
9
|
+
viewImageMaxSourceBytes: 20_971_520,
|
|
10
|
+
viewImageMaxPixels: 16_777_216,
|
|
9
11
|
execMaxTimeoutMs: 3_600_000,
|
|
10
12
|
execDefaultYieldMs: 10_000,
|
|
11
13
|
execMaxYieldMs: 30_000,
|
|
@@ -11,6 +11,7 @@ export type ExecutedToolCall = {
|
|
|
11
11
|
call: ClientToolCall;
|
|
12
12
|
content: string;
|
|
13
13
|
images?: ToolExecutionResult['images'];
|
|
14
|
+
sourceImages?: ToolExecutionResult['images'];
|
|
14
15
|
observations?: ToolExecutionResult['observations'];
|
|
15
16
|
calls?: ToolExecutionResult['calls'];
|
|
16
17
|
} & ({
|
|
@@ -31,7 +32,7 @@ export interface RoundToolScope {
|
|
|
31
32
|
readonly assertExecutionLive: () => void;
|
|
32
33
|
readonly emit: (event: EventPayload, assertLive?: () => void) => Promise<void>;
|
|
33
34
|
readonly searchMcpTools: NonNullable<ToolRuntimeContext['searchMcpTools']>;
|
|
34
|
-
readonly recentImages: () => ToolRuntimeImage[]
|
|
35
|
+
readonly recentImages: () => Promise<ToolRuntimeImage[]>;
|
|
35
36
|
readonly captureCodeContext: () => Omit<ToolRuntimeContext, 'signal' | 'codeMode' | 'expectedMcpTool' | 'searchMcpTools'>;
|
|
36
37
|
readonly codeModeContext: (call: ToolCall & {
|
|
37
38
|
readonly callId: string;
|
|
@@ -4,6 +4,7 @@ import { allowsToolCaller } from './callers.js';
|
|
|
4
4
|
import { CodeCellExecutionError } from './code-mode/cells.js';
|
|
5
5
|
import { createCodeToolDispatch } from './code-mode/dispatch.js';
|
|
6
6
|
import { boundedModelToolOutput, structuredToolArtifacts, structuredToolEventData, structuredToolFailureData } from './output-projection.js';
|
|
7
|
+
import { publishModelResources } from '../runtime/resource-publication.js';
|
|
7
8
|
import { executeRegisteredTool } from './runtime.js';
|
|
8
9
|
import { failedToolResultExtensions } from './result-extensions.js';
|
|
9
10
|
import { permissionStopFromError } from './permissions.js';
|
|
@@ -13,6 +14,11 @@ export async function createRoundToolRuntimeContext(scope, call, signal) {
|
|
|
13
14
|
scope.assertLive();
|
|
14
15
|
signal.throwIfAborted();
|
|
15
16
|
const codeMode = await scope.codeModeContext(call);
|
|
17
|
+
const recentImages = scope.extensionToolNames.has(call.name) ||
|
|
18
|
+
call.name === 'code_exec' ||
|
|
19
|
+
call.name === 'code_exec_wait'
|
|
20
|
+
? await scope.recentImages()
|
|
21
|
+
: undefined;
|
|
16
22
|
scope.assertLive();
|
|
17
23
|
signal.throwIfAborted();
|
|
18
24
|
const assertSessionLive = scope.assertLive;
|
|
@@ -52,7 +58,10 @@ export async function createRoundToolRuntimeContext(scope, call, signal) {
|
|
|
52
58
|
}
|
|
53
59
|
}),
|
|
54
60
|
invoke: createCodeToolDispatch({
|
|
55
|
-
context:
|
|
61
|
+
context: {
|
|
62
|
+
...scope.captureCodeContext(),
|
|
63
|
+
...(recentImages === undefined ? {} : { recentImages })
|
|
64
|
+
},
|
|
56
65
|
tools: scope.tools,
|
|
57
66
|
expectations: scope.expectations,
|
|
58
67
|
parentCallId: call.callId,
|
|
@@ -61,7 +70,9 @@ export async function createRoundToolRuntimeContext(scope, call, signal) {
|
|
|
61
70
|
emit: (event) => emitCodeEvent(event, assertDispatchLive)
|
|
62
71
|
})
|
|
63
72
|
},
|
|
64
|
-
...(scope.extensionToolNames.has(call.name)
|
|
73
|
+
...(scope.extensionToolNames.has(call.name) && recentImages !== undefined
|
|
74
|
+
? { recentImages }
|
|
75
|
+
: {})
|
|
65
76
|
};
|
|
66
77
|
}
|
|
67
78
|
/** Complete every call in model order even if individual execution fails. */
|
|
@@ -84,9 +95,19 @@ export async function executeRoundTools(calls, scope) {
|
|
|
84
95
|
signal.throwIfAborted();
|
|
85
96
|
return executeRegisteredTool(call.name, call.arguments, call.callId, context);
|
|
86
97
|
};
|
|
87
|
-
const
|
|
98
|
+
const rawOutput = call.name === 'code_exec' || call.name === 'code_exec_wait'
|
|
88
99
|
? await runTool()
|
|
89
100
|
: await scope.schedule(scope.tools.find((tool) => tool.name === call.name), runTool);
|
|
101
|
+
const publishedImages = await publishModelResources(scope.context.host, rawOutput.images, {
|
|
102
|
+
sessionId: scope.context.sessionId,
|
|
103
|
+
executionId: scope.context.executionId
|
|
104
|
+
}, scope.context.imageStore);
|
|
105
|
+
const output = publishedImages === rawOutput.images
|
|
106
|
+
? rawOutput
|
|
107
|
+
: {
|
|
108
|
+
...rawOutput,
|
|
109
|
+
...(publishedImages === undefined ? {} : { images: publishedImages })
|
|
110
|
+
};
|
|
90
111
|
const modelOutput = boundedModelToolOutput(call.name, output.content, output.data);
|
|
91
112
|
const eventSummary = toolEventSummary(output.content);
|
|
92
113
|
const structuredData = structuredToolEventData(call.name, output.data);
|
|
@@ -106,6 +127,7 @@ export async function executeRoundTools(calls, scope) {
|
|
|
106
127
|
call,
|
|
107
128
|
output,
|
|
108
129
|
content: modelOutput.content,
|
|
130
|
+
...(rawOutput.images?.length ? { sourceImages: rawOutput.images } : {}),
|
|
109
131
|
...(output.calls
|
|
110
132
|
? { calls: output.calls.map(({ resultExtensions: _evidence, ...call }) => call) }
|
|
111
133
|
: {}),
|
|
@@ -116,7 +138,11 @@ export async function executeRoundTools(calls, scope) {
|
|
|
116
138
|
}
|
|
117
139
|
catch (error) {
|
|
118
140
|
const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
|
|
119
|
-
const
|
|
141
|
+
const partialResult = error instanceof CodeCellExecutionError ? error.result : undefined;
|
|
142
|
+
const publishedPartialImages = await publishModelResources(scope.context.host, partialResult?.images, { sessionId: scope.context.sessionId, executionId: scope.context.executionId }, scope.context.imageStore);
|
|
143
|
+
const partial = partialResult !== undefined && publishedPartialImages !== partialResult.images
|
|
144
|
+
? { ...partialResult, images: publishedPartialImages }
|
|
145
|
+
: partialResult;
|
|
120
146
|
const failureData = structuredToolFailureData(call.name, error);
|
|
121
147
|
const eventData = failureData ??
|
|
122
148
|
(partial?.data === undefined
|
|
@@ -151,6 +177,7 @@ export async function executeRoundTools(calls, scope) {
|
|
|
151
177
|
}), failureData).content
|
|
152
178
|
: boundedModelToolOutput(call.name, partial.content, partial.data).content,
|
|
153
179
|
...(partial?.images?.length ? { images: partial.images } : {}),
|
|
180
|
+
...(partialResult?.images?.length ? { sourceImages: partialResult.images } : {}),
|
|
154
181
|
failed: true,
|
|
155
182
|
...(partial?.calls
|
|
156
183
|
? { calls: partial.calls.map(({ resultExtensions: _evidence, ...call }) => call) }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview only the submitted line around a one-based Unicode character column.
|
|
3
|
+
* Escape controls and measure rendered graphemes, not source columns, for the caret.
|
|
4
|
+
* @param {string} source
|
|
5
|
+
* @param {number} column
|
|
6
|
+
*/
|
|
7
|
+
export function sourcePreview(source: string, column: number): {
|
|
8
|
+
text: string;
|
|
9
|
+
caret: number;
|
|
10
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview only the submitted line around a one-based Unicode character column.
|
|
3
|
+
* Escape controls and measure rendered graphemes, not source columns, for the caret.
|
|
4
|
+
* @param {string} source
|
|
5
|
+
* @param {number} column
|
|
6
|
+
*/
|
|
7
|
+
export function sourcePreview(source, column) {
|
|
8
|
+
const characters = Array.from(source);
|
|
9
|
+
const index = column - 1;
|
|
10
|
+
let before = '';
|
|
11
|
+
let start = index;
|
|
12
|
+
let width = 0;
|
|
13
|
+
while (start > 0) {
|
|
14
|
+
const token = displayCharacter(characters[start - 1]);
|
|
15
|
+
const candidate = token + before;
|
|
16
|
+
const size = displayWidth(candidate);
|
|
17
|
+
if (size > 60 || Array.from(candidate).length > 60)
|
|
18
|
+
break;
|
|
19
|
+
before = candidate;
|
|
20
|
+
width = size;
|
|
21
|
+
start--;
|
|
22
|
+
}
|
|
23
|
+
const prefix = start > 0 ? '… ' : '';
|
|
24
|
+
const caret = width + prefix.length;
|
|
25
|
+
let text = before;
|
|
26
|
+
let end = index;
|
|
27
|
+
while (end < characters.length) {
|
|
28
|
+
const token = displayCharacter(characters[end]);
|
|
29
|
+
const candidate = text + token;
|
|
30
|
+
if (displayWidth(candidate) > 156 || Array.from(candidate).length > 156)
|
|
31
|
+
break;
|
|
32
|
+
text = candidate;
|
|
33
|
+
end++;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
text: `${prefix}${text}${end < characters.length ? ' …' : ''}`,
|
|
37
|
+
caret
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
41
|
+
function displayWidth(text) {
|
|
42
|
+
let width = 0;
|
|
43
|
+
for (const { segment } of graphemes.segment(text)) {
|
|
44
|
+
const base = segment.match(/[^\p{Mark}\p{Cf}]/u)?.[0];
|
|
45
|
+
if (base === undefined)
|
|
46
|
+
continue;
|
|
47
|
+
// Match conventional monospace cells: combining marks join their base and emoji occupy two.
|
|
48
|
+
width +=
|
|
49
|
+
/[\p{Emoji_Presentation}\ufe0f\u20e3]/u.test(segment) ||
|
|
50
|
+
/[\u1100-\u115f\u2329\u232a\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe10-\ufe19\ufe30-\ufe6f\uff01-\uff60\uffe0-\uffe6\u{20000}-\u{3fffd}]/u.test(base)
|
|
51
|
+
? 2
|
|
52
|
+
: 1;
|
|
53
|
+
}
|
|
54
|
+
return width;
|
|
55
|
+
}
|
|
56
|
+
function displayCharacter(character) {
|
|
57
|
+
if (character === '\t')
|
|
58
|
+
return ' ';
|
|
59
|
+
if (/[\u0000-\u001f\u007f-\u009f\u2028\u2029\p{Bidi_Control}]/u.test(character)) {
|
|
60
|
+
const escaped = JSON.stringify(character).slice(1, -1);
|
|
61
|
+
return escaped === character ? `\\u{${character.codePointAt(0).toString(16)}}` : escaped;
|
|
62
|
+
}
|
|
63
|
+
return character;
|
|
64
|
+
}
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import { isAbsolute, resolve } from 'node:path';
|
|
2
2
|
import { MarAgentError } from '../../error.js';
|
|
3
3
|
import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
|
|
4
|
+
import { sourcePreview } from '../input-diagnostics/source-preview.js';
|
|
4
5
|
const beginMarker = '*** Begin Patch';
|
|
5
6
|
const endMarker = '*** End Patch';
|
|
6
7
|
const addHeader = '*** Add File: ';
|
|
7
8
|
const updateHeader = '*** Update File: ';
|
|
8
9
|
const deleteHeader = '*** Delete File: ';
|
|
9
10
|
const moveHeader = '*** Move to: ';
|
|
10
|
-
const syntaxTokens = [
|
|
11
|
-
`${beginMarker} ***`,
|
|
12
|
-
`${endMarker} ***`,
|
|
13
|
-
addHeader,
|
|
14
|
-
updateHeader,
|
|
15
|
-
deleteHeader,
|
|
16
|
-
moveHeader,
|
|
17
|
-
'@@ ',
|
|
18
|
-
'+',
|
|
19
|
-
'-',
|
|
20
|
-
' '
|
|
21
|
-
];
|
|
22
11
|
export function parsePatch(patch) {
|
|
23
12
|
if (typeof patch !== 'string')
|
|
24
13
|
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Patch must be a string.');
|
|
@@ -169,7 +158,7 @@ class FreeformPatchParser {
|
|
|
169
158
|
const line = index + 1;
|
|
170
159
|
const column = characterCount(source.slice(0, characterOffset)) + 1;
|
|
171
160
|
const offset = this.#lineOffsets[index] + characterOffset;
|
|
172
|
-
const preview =
|
|
161
|
+
const preview = sourcePreview(source, column);
|
|
173
162
|
const gutter = String(line);
|
|
174
163
|
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', `Patch syntax error at line ${line}, column ${column}: ${message}\n${gutter} | ${preview.text}\n${' '.repeat(gutter.length)} | ${' '.repeat(preview.caret)}^`, { details: { line, column, offset } });
|
|
175
164
|
}
|
|
@@ -186,43 +175,3 @@ function characterCount(value) {
|
|
|
186
175
|
count++;
|
|
187
176
|
return count;
|
|
188
177
|
}
|
|
189
|
-
function syntaxPreview(source, characterOffset) {
|
|
190
|
-
// Only grammar tokens and one offending character are public; paths and file bodies are not.
|
|
191
|
-
const trimmed = source.trimStart();
|
|
192
|
-
const leading = source.length - trimmed.length;
|
|
193
|
-
const tokenLength = Math.max(...syntaxTokens.map((token) => commonPrefixLength(trimmed, token)));
|
|
194
|
-
const token = trimmed.slice(0, tokenLength);
|
|
195
|
-
if (characterOffset < leading) {
|
|
196
|
-
const before = characterOffset > 40 ? '… ' : displaySyntax(source.slice(0, characterOffset));
|
|
197
|
-
return {
|
|
198
|
-
text: `${before}${displayCharacter(source[characterOffset])}<content omitted>`,
|
|
199
|
-
caret: before.length
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
const prefix = leading > 40 ? '… ' : displaySyntax(source.slice(0, leading));
|
|
203
|
-
const syntaxEnd = leading + tokenLength;
|
|
204
|
-
if (characterOffset < syntaxEnd) {
|
|
205
|
-
return {
|
|
206
|
-
text: `${prefix}${token}${source.length > syntaxEnd ? '<content omitted>' : ''}`,
|
|
207
|
-
caret: prefix.length + characterOffset - leading
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
const gap = characterOffset > syntaxEnd ? '<content omitted>' : '';
|
|
211
|
-
const character = source.slice(characterOffset)[Symbol.iterator]().next().value;
|
|
212
|
-
const before = `${prefix}${token}${gap}`;
|
|
213
|
-
return {
|
|
214
|
-
text: `${before}${character === undefined ? '<end of line>' : displayCharacter(character)}${character !== undefined && source.length > characterOffset + character.length ? '<content omitted>' : ''}`,
|
|
215
|
-
caret: before.length
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
function displayCharacter(character) {
|
|
219
|
-
const point = character.codePointAt(0);
|
|
220
|
-
if (point >= 0x7f)
|
|
221
|
-
return `\\u{${point.toString(16)}}`;
|
|
222
|
-
return JSON.stringify(character).slice(1, -1);
|
|
223
|
-
}
|
|
224
|
-
function displaySyntax(value) {
|
|
225
|
-
return [...value]
|
|
226
|
-
.map((character) => (character === '\t' ? ' ' : displayCharacter(character)))
|
|
227
|
-
.join('');
|
|
228
|
-
}
|
|
@@ -11,7 +11,7 @@ export const applyPatchToolDefinition = {
|
|
|
11
11
|
allowedCallers: ['direct', 'programmatic'],
|
|
12
12
|
codeModeExposure: 'both',
|
|
13
13
|
parallelSafety: 'serial',
|
|
14
|
-
description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch, each marker on its own line. Surrounding marker whitespace and an optional trailing *** are accepted; incomplete markers or extra text are rejected. Add File content lines, including blank lines, must start with +. Syntax errors include line/column and a bounded
|
|
14
|
+
description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch, each marker on its own line. Surrounding marker whitespace and an optional trailing *** are accepted; incomplete markers or extra text are rejected. Add File content lines, including blank lines, must start with +. Syntax errors include line/column and a bounded excerpt of the submitted patch with a caret at the error. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>. Context-only hunks, including trailing hunks, and unchanged file sections are validated in order but are not written or reported as edits. The patch must include a change or move. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. In Code Mode, the Promise resolves on success and rejects with code/message on failure; the returned envelope has data.files and data.totals, not an ok or success flag. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
|
|
15
15
|
inputMode: 'freeform',
|
|
16
16
|
outputSchema: {
|
|
17
17
|
type: 'object',
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ViewImageRegion, ViewImageResult } from '../contracts.js';
|
|
2
|
+
export interface ImageResizeBounds {
|
|
3
|
+
maxWidth?: number;
|
|
4
|
+
maxHeight?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ImageMetadata {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
pages: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ProcessedImage {
|
|
12
|
+
bytes: Buffer;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
mimeType: 'image/png';
|
|
16
|
+
sourceWidth?: number;
|
|
17
|
+
sourceHeight?: number;
|
|
18
|
+
region?: ViewImageRegion;
|
|
19
|
+
}
|
|
20
|
+
export declare function readImageMetadata(data: Buffer, mimeType: ViewImageResult['mimeType'], signal?: AbortSignal): Promise<ImageMetadata>;
|
|
21
|
+
export declare function processImage(data: Buffer, metadata: ImageMetadata, options: {
|
|
22
|
+
region?: ViewImageRegion;
|
|
23
|
+
resize?: ImageResizeBounds;
|
|
24
|
+
}, signal?: AbortSignal): Promise<ProcessedImage>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { MarAgentError } from '../../error.js';
|
|
2
|
+
import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
|
|
3
|
+
export async function readImageMetadata(data, mimeType, signal) {
|
|
4
|
+
throwIfAborted(signal);
|
|
5
|
+
try {
|
|
6
|
+
const sharp = await loadSharp();
|
|
7
|
+
const metadata = await sharp(data, {
|
|
8
|
+
failOn: 'warning',
|
|
9
|
+
limitInputPixels: TOOL_EXECUTION_LIMITS.viewImageMaxPixels,
|
|
10
|
+
pages: 1
|
|
11
|
+
}).metadata();
|
|
12
|
+
throwIfAborted(signal);
|
|
13
|
+
if (metadata.format !== expectedSharpFormat(mimeType) ||
|
|
14
|
+
!isPositiveSafeInteger(metadata.width) ||
|
|
15
|
+
!isPositiveSafeInteger(metadata.height))
|
|
16
|
+
return incompleteImage();
|
|
17
|
+
const width = metadata.autoOrient?.width ?? metadata.width;
|
|
18
|
+
const height = metadata.autoOrient?.height ?? metadata.height;
|
|
19
|
+
assertPixelLimit(width, height);
|
|
20
|
+
return { width, height, pages: metadata.pages ?? 1 };
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throwIfAborted(signal);
|
|
24
|
+
if (error instanceof MarAgentError)
|
|
25
|
+
throw error;
|
|
26
|
+
if (isPixelLimitError(error))
|
|
27
|
+
return imagePixelLimit();
|
|
28
|
+
return incompleteImage();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function processImage(data, metadata, options, signal) {
|
|
32
|
+
throwIfAborted(signal);
|
|
33
|
+
if (metadata.pages > 1)
|
|
34
|
+
throw new MarAgentError('MAR_AGENT_IMAGE_FORMAT_UNSUPPORTED', 'Animated images cannot be cropped or resized.');
|
|
35
|
+
const region = options.region;
|
|
36
|
+
const regionWidth = region?.width ?? metadata.width;
|
|
37
|
+
const regionHeight = region?.height ?? metadata.height;
|
|
38
|
+
const resize = options.resize;
|
|
39
|
+
try {
|
|
40
|
+
const sharp = await loadSharp();
|
|
41
|
+
const image = sharp(data, {
|
|
42
|
+
failOn: 'warning',
|
|
43
|
+
limitInputPixels: TOOL_EXECUTION_LIMITS.viewImageMaxPixels,
|
|
44
|
+
pages: 1,
|
|
45
|
+
autoOrient: true
|
|
46
|
+
});
|
|
47
|
+
if (region)
|
|
48
|
+
image.extract({
|
|
49
|
+
left: region.x,
|
|
50
|
+
top: region.y,
|
|
51
|
+
width: region.width,
|
|
52
|
+
height: region.height
|
|
53
|
+
});
|
|
54
|
+
if (resize) {
|
|
55
|
+
image.resize({
|
|
56
|
+
...(resize.maxWidth === undefined ? {} : { width: Math.min(resize.maxWidth, regionWidth) }),
|
|
57
|
+
...(resize.maxHeight === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: { height: Math.min(resize.maxHeight, regionHeight) }),
|
|
60
|
+
fit: 'inside',
|
|
61
|
+
withoutEnlargement: true
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const output = await image.png().toBuffer({ resolveWithObject: true });
|
|
65
|
+
throwIfAborted(signal);
|
|
66
|
+
if (output.data.byteLength > TOOL_EXECUTION_LIMITS.viewImageMaxBytes)
|
|
67
|
+
throw imageOutputLimit();
|
|
68
|
+
const changedRegion = region !== undefined &&
|
|
69
|
+
(region.x !== 0 ||
|
|
70
|
+
region.y !== 0 ||
|
|
71
|
+
region.width !== metadata.width ||
|
|
72
|
+
region.height !== metadata.height);
|
|
73
|
+
const changedSize = output.info.width !== regionWidth || output.info.height !== regionHeight;
|
|
74
|
+
return {
|
|
75
|
+
bytes: output.data,
|
|
76
|
+
width: output.info.width,
|
|
77
|
+
height: output.info.height,
|
|
78
|
+
mimeType: 'image/png',
|
|
79
|
+
...(changedRegion || changedSize
|
|
80
|
+
? { sourceWidth: metadata.width, sourceHeight: metadata.height }
|
|
81
|
+
: {}),
|
|
82
|
+
...(changedRegion ? { region } : {})
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throwIfAborted(signal);
|
|
87
|
+
if (error instanceof MarAgentError)
|
|
88
|
+
throw error;
|
|
89
|
+
if (isPixelLimitError(error))
|
|
90
|
+
return imagePixelLimit();
|
|
91
|
+
return incompleteImage();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function expectedSharpFormat(mimeType) {
|
|
95
|
+
switch (mimeType) {
|
|
96
|
+
case 'image/png':
|
|
97
|
+
return 'png';
|
|
98
|
+
case 'image/jpeg':
|
|
99
|
+
return 'jpeg';
|
|
100
|
+
case 'image/webp':
|
|
101
|
+
return 'webp';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function assertPixelLimit(width, height) {
|
|
105
|
+
if (!isPositiveSafeInteger(width) ||
|
|
106
|
+
!isPositiveSafeInteger(height) ||
|
|
107
|
+
width > Math.floor(TOOL_EXECUTION_LIMITS.viewImageMaxPixels / height))
|
|
108
|
+
imagePixelLimit();
|
|
109
|
+
}
|
|
110
|
+
function isPositiveSafeInteger(value) {
|
|
111
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
112
|
+
}
|
|
113
|
+
function isPixelLimitError(error) {
|
|
114
|
+
return error instanceof Error && /pixel limit/iu.test(error.message);
|
|
115
|
+
}
|
|
116
|
+
function throwIfAborted(signal) {
|
|
117
|
+
signal?.throwIfAborted();
|
|
118
|
+
}
|
|
119
|
+
function imageOutputLimit() {
|
|
120
|
+
return new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', `Image output exceeds ${TOOL_EXECUTION_LIMITS.viewImageMaxBytes} bytes. Use a smaller resize or region.`);
|
|
121
|
+
}
|
|
122
|
+
function imagePixelLimit() {
|
|
123
|
+
throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', `Image exceeds the source pixel-area limit (equivalent to ${Math.sqrt(TOOL_EXECUTION_LIMITS.viewImageMaxPixels)}×${Math.sqrt(TOOL_EXECUTION_LIMITS.viewImageMaxPixels)} pixels).`);
|
|
124
|
+
}
|
|
125
|
+
function incompleteImage() {
|
|
126
|
+
throw new MarAgentError('MAR_AGENT_IMAGE_INCOMPLETE', 'Image file is incomplete, malformed, or changed while reading. Wait for the writer to finish and read it again.');
|
|
127
|
+
}
|
|
128
|
+
async function loadSharp() {
|
|
129
|
+
try {
|
|
130
|
+
return (await import('sharp')).default;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw new MarAgentError('MAR_AGENT_IMAGE_PROCESSING_UNAVAILABLE', 'Native image processing is unavailable because the sharp dependency could not be loaded.');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -289,7 +289,7 @@ function readKey(input) {
|
|
|
289
289
|
}
|
|
290
290
|
function readError(path, error) {
|
|
291
291
|
if (error instanceof MarAgentError)
|
|
292
|
-
return { path, error: { code: error.code, message: error.
|
|
292
|
+
return { path, error: { code: error.code, message: error.publicMessage } };
|
|
293
293
|
const code = typeof error === 'object' && error !== null && 'code' in error && typeof error.code === 'string'
|
|
294
294
|
? error.code
|
|
295
295
|
: 'MAR_AGENT_TOOL_FAILED';
|