@myagentroam/node 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/capabilities.js +1 -0
- package/dist/connector.js +60 -5
- package/dist/database.js +3 -1
- package/dist/migrations/v017.js +46 -0
- package/dist/native-session-history.js +7 -15
- package/dist/runner/abstract-runner.js +4 -0
- package/dist/runner/claude/managed-run-controller.js +15 -3
- package/dist/runner/claude/tool-normalizer.js +100 -6
- package/dist/runner/codex/conversation-parser.js +41 -30
- package/dist/runner/codex/managed-run-controller.js +12 -1
- package/dist/runner/codex/tool-normalizer.js +5 -2
- package/dist/runner/mar-agent/compact-extensions.js +1 -0
- package/dist/runner/mar-agent/conversation-normalizer.js +43 -78
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +27 -0
- package/dist/runner/mar-agent/managed-run-controller.js +13 -1
- package/dist/runner/mar-agent/runtime-model-factory.js +5 -0
- package/dist/runner/mar-agent-runner.js +15 -0
- package/dist/runner/opencode/conversation-parser.js +49 -0
- package/dist/runner/opencode/managed-run-controller.js +12 -0
- package/dist/service/continuous-run-coordinator.js +1 -0
- package/dist/service/hosted-conversation-node-service.js +29 -2
- package/dist/service/mar-agent-model-transport-test-service.js +16 -1
- package/dist/service/model-usage-outbox.js +167 -0
- package/dist/service/model-usage-service.js +61 -0
- package/dist/service/node-control-message-service.js +7 -2
- package/dist/service/node-request-service.js +3 -1
- package/dist/service/node-run-event-bridge.js +6 -0
- package/dist/service/runner-usage-tracker.js +62 -0
- package/dist/service/session-command-service.js +4 -0
- package/dist/service/session-message-service.js +6 -0
- package/dist/service/session-query-service.js +16 -0
- package/dist/util/node-operation-parsers.js +0 -21
- package/dist/util/runner-native-session-parsers.js +9 -4
- package/package.json +4 -4
|
@@ -5,7 +5,7 @@ import { safeErrorCode } from '../../util/safe-error.js';
|
|
|
5
5
|
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
6
6
|
import { ResponseLatencyTracker } from '../../service/response-latency-tracker.js';
|
|
7
7
|
import { GenerationTimingTracker } from '../../service/generation-timing-tracker.js';
|
|
8
|
-
import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
|
|
8
|
+
import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, CodexUsageNormalizer, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
|
|
9
9
|
export function resolveCodexNotificationRun(runs, input) {
|
|
10
10
|
const entries = [...runs.entries()];
|
|
11
11
|
if (input.turnId !== undefined) {
|
|
@@ -28,6 +28,7 @@ export function resolveCodexNotificationRun(runs, input) {
|
|
|
28
28
|
export class CodexManagedRunController {
|
|
29
29
|
runner;
|
|
30
30
|
host;
|
|
31
|
+
usage = new Map();
|
|
31
32
|
constructor(runner, host) {
|
|
32
33
|
this.runner = runner;
|
|
33
34
|
this.host = host;
|
|
@@ -113,6 +114,7 @@ export class CodexManagedRunController {
|
|
|
113
114
|
catch (error) {
|
|
114
115
|
this.emitError(runId, error, 'CODEX_COMPACT_FAILED');
|
|
115
116
|
this.host.emit(runId, 'run.failed', runnerErrorProjection(error, 'CODEX_COMPACT_FAILED'), 'FAILED');
|
|
117
|
+
this.usage.delete(runId);
|
|
116
118
|
this.runner.execution.runs.delete(runId);
|
|
117
119
|
this.runner.releaseEnvironment(runId);
|
|
118
120
|
this.host.removeActive(runId);
|
|
@@ -138,6 +140,12 @@ export class CodexManagedRunController {
|
|
|
138
140
|
if (run === undefined)
|
|
139
141
|
return;
|
|
140
142
|
const [runId, activeRun] = run;
|
|
143
|
+
let usage = this.usage.get(runId);
|
|
144
|
+
if (!usage) {
|
|
145
|
+
usage = new CodexUsageNormalizer(runId, (value) => this.host.emit(runId, 'run.usage', value), this.host.session(activeRun.sessionId)?.model ?? undefined);
|
|
146
|
+
this.usage.set(runId, usage);
|
|
147
|
+
}
|
|
148
|
+
usage.observe(notification.method, params, activeRun.generationCompacting === true);
|
|
141
149
|
const receiptAt = activeRun.generationTiming?.timestamp();
|
|
142
150
|
if (notification.method === 'turn/started' && turnId !== undefined) {
|
|
143
151
|
const active = this.runner.execution.runs.get(runId);
|
|
@@ -332,6 +340,7 @@ export class CodexManagedRunController {
|
|
|
332
340
|
this.host.session(active.sessionId)?.titleSource !== 'AUTO'
|
|
333
341
|
? undefined
|
|
334
342
|
: this.host.syncTitle(active.sessionId, active.threadId, active.cwd);
|
|
343
|
+
this.usage.delete(runId);
|
|
335
344
|
this.runner.execution.runs.delete(runId);
|
|
336
345
|
if (titleRefresh === undefined)
|
|
337
346
|
this.runner.releaseEnvironment(runId);
|
|
@@ -624,6 +633,7 @@ export class CodexManagedRunController {
|
|
|
624
633
|
break;
|
|
625
634
|
}
|
|
626
635
|
catch (error) {
|
|
636
|
+
this.usage.delete(runId);
|
|
627
637
|
this.runner.execution.runs.delete(runId);
|
|
628
638
|
if (attempt === 0 && (await this.runner.recoverMissingRollout(runId, error)))
|
|
629
639
|
continue;
|
|
@@ -663,6 +673,7 @@ export class CodexManagedRunController {
|
|
|
663
673
|
}
|
|
664
674
|
this.emitError(runId, error, 'CODEX_RUN_FAILED');
|
|
665
675
|
this.host.emit(runId, 'run.failed', runnerErrorProjection(error, 'CODEX_RUN_FAILED'), 'FAILED');
|
|
676
|
+
this.usage.delete(runId);
|
|
666
677
|
this.runner.execution.runs.delete(runId);
|
|
667
678
|
this.runner.releaseEnvironment(runId);
|
|
668
679
|
this.host.removeActive(runId);
|
|
@@ -96,7 +96,10 @@ function codexSubagentProjection(namespace, toolName, input, output) {
|
|
|
96
96
|
: '';
|
|
97
97
|
return {
|
|
98
98
|
subagentOperation: operation,
|
|
99
|
-
|
|
99
|
+
...(toolName === 'wait_agent' ? { subagentWait: true } : {}),
|
|
100
|
+
subagentId: Array.isArray(inputData?.targets) && inputData.targets.length > 1
|
|
101
|
+
? null
|
|
102
|
+
: (codexSubagentId(input) ?? codexSubagentId(output) ?? null),
|
|
100
103
|
...(title.length === 0 ? {} : { title })
|
|
101
104
|
};
|
|
102
105
|
}
|
|
@@ -109,7 +112,7 @@ function codexSubagentId(value) {
|
|
|
109
112
|
if (candidate !== undefined)
|
|
110
113
|
return candidate;
|
|
111
114
|
}
|
|
112
|
-
if (Array.isArray(data.targets)) {
|
|
115
|
+
if (Array.isArray(data.targets) && data.targets.length === 1) {
|
|
113
116
|
for (const target of data.targets) {
|
|
114
117
|
const candidate = boundedId(target);
|
|
115
118
|
if (candidate !== undefined)
|
|
@@ -104,6 +104,7 @@ export async function generateMarAgentCompact(context, selected, signal) {
|
|
|
104
104
|
try {
|
|
105
105
|
const result = await generateModelText({
|
|
106
106
|
model: selected.model,
|
|
107
|
+
...(context.reportModelUsage === undefined ? {} : { onModelUsage: context.reportModelUsage }),
|
|
107
108
|
system,
|
|
108
109
|
reasoningEffort: selected.reasoningEffort,
|
|
109
110
|
messages: [
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { parseToolResultExtensions } from '@myagentroam/agent';
|
|
2
|
+
/** SDK usage is already normalized; never parse provider payloads in Node. */
|
|
3
|
+
export function normalizeMarAgentUsage(usage) {
|
|
4
|
+
const { executionId: _executionId, ...record } = usage;
|
|
5
|
+
return { ...record, schemaVersion: 1, coverage: 'request' };
|
|
6
|
+
}
|
|
1
7
|
import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../../util/node-operation-parsers.js';
|
|
2
8
|
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
3
9
|
export class MarAgentConversationNormalizer {
|
|
@@ -6,8 +12,6 @@ export class MarAgentConversationNormalizer {
|
|
|
6
12
|
#processOrigins = new Map();
|
|
7
13
|
#subagentTitles = new Map();
|
|
8
14
|
#subagentDetails = new Map();
|
|
9
|
-
#subagentStarts = new Map();
|
|
10
|
-
#subagentLifecycles = new Map();
|
|
11
15
|
#codeModeObservationIds = new Set();
|
|
12
16
|
#codeModeSourceCallIds = new Set();
|
|
13
17
|
messageItemId(nativeItemId) {
|
|
@@ -183,9 +187,6 @@ export class MarAgentConversationNormalizer {
|
|
|
183
187
|
: undefined;
|
|
184
188
|
const subagentTitle = agentId === undefined ? undefined : this.#subagentTitles.get(agentId);
|
|
185
189
|
const subagentDetails = agentId === undefined ? undefined : this.#subagentDetails.get(agentId);
|
|
186
|
-
const toolSubagentDetails = event.toolName === 'agent_message'
|
|
187
|
-
? withoutSubagentLatestMessage(subagentDetails)
|
|
188
|
-
: subagentDetails;
|
|
189
190
|
this.#tools.set(event.callId, {
|
|
190
191
|
toolName: event.toolName,
|
|
191
192
|
input: event.input,
|
|
@@ -197,7 +198,7 @@ export class MarAgentConversationNormalizer {
|
|
|
197
198
|
if (event.toolName === 'apply_patch' && 'input' in event)
|
|
198
199
|
return [fileChangeItem(ownItemId, 'IN_PROGRESS', [])];
|
|
199
200
|
return [
|
|
200
|
-
genericToolItem(ownItemId, event.toolName, 'IN_PROGRESS', event.input, null, null, event.truncated === true, undefined, subagentTitle,
|
|
201
|
+
genericToolItem(ownItemId, event.toolName, 'IN_PROGRESS', event.input, null, null, event.truncated === true, undefined, subagentTitle, subagentDetails)
|
|
201
202
|
];
|
|
202
203
|
}
|
|
203
204
|
#toolCompleted(event) {
|
|
@@ -220,9 +221,11 @@ export class MarAgentConversationNormalizer {
|
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
if (event.toolName === 'apply_patch' &&
|
|
223
|
-
(state?.structured === true ||
|
|
224
|
+
(state?.structured === true ||
|
|
225
|
+
Array.isArray(record(event.data)?.files) ||
|
|
226
|
+
parseToolResultExtensions(event.resultExtensions) !== undefined))
|
|
224
227
|
return [
|
|
225
|
-
fileChangeItem(state?.itemId ?? itemId('tool', event.callId), 'COMPLETED', patchChanges(event.data))
|
|
228
|
+
fileChangeItem(state?.itemId ?? itemId('tool', event.callId), 'COMPLETED', patchChanges(event.data, event.resultExtensions))
|
|
226
229
|
];
|
|
227
230
|
const generatedImages = imageArtifacts(event);
|
|
228
231
|
const controlTool = isMarAgentControlTool(event.toolName);
|
|
@@ -236,9 +239,7 @@ export class MarAgentConversationNormalizer {
|
|
|
236
239
|
? undefined
|
|
237
240
|
: mergeSubagentDetails(this.#subagentDetails.get(agentId), state?.subagentDetails ?? {});
|
|
238
241
|
const details = controlTool
|
|
239
|
-
? mergeSubagentDetails(
|
|
240
|
-
? withoutSubagentLatestMessage(cachedDetails)
|
|
241
|
-
: cachedDetails, subagentDetails(controlOutput))
|
|
242
|
+
? mergeSubagentDetails(cachedDetails, subagentDetails(controlOutput))
|
|
242
243
|
: {};
|
|
243
244
|
if (agentId !== undefined) {
|
|
244
245
|
const output = structuredRecord(controlOutput);
|
|
@@ -248,18 +249,11 @@ export class MarAgentConversationNormalizer {
|
|
|
248
249
|
this.#subagentTitles.set(agentId, title);
|
|
249
250
|
}
|
|
250
251
|
if (Object.keys(details).length > 0)
|
|
251
|
-
this.#subagentDetails.set(agentId, details);
|
|
252
|
-
if (event.toolName === 'agent_start' && state !== undefined)
|
|
253
|
-
this.#subagentStarts.set(agentId, state);
|
|
252
|
+
this.#subagentDetails.set(agentId, withoutSubagentLatestMessage(details) ?? {});
|
|
254
253
|
}
|
|
255
|
-
const
|
|
256
|
-
?
|
|
257
|
-
: undefined;
|
|
258
|
-
const item = genericToolItem(state?.itemId ?? itemId('tool', event.callId), event.toolName, lifecycle?.status ??
|
|
259
|
-
(event.toolName === 'agent_start' ? subagentControlStatus(controlOutput) : 'COMPLETED'), state?.input, lifecycle?.outputSummary ??
|
|
260
|
-
(generatedImages.length === 0
|
|
261
|
-
? event.summary
|
|
262
|
-
: generatedImages.map((image) => image.name).join(', ')), lifecycle?.errorCode ?? null, lifecycle?.truncated ?? event.truncated, event.data, agentId === undefined ? state?.subagentTitle : this.#subagentTitles.get(agentId), details);
|
|
254
|
+
const item = genericToolItem(state?.itemId ?? itemId('tool', event.callId), event.toolName, 'COMPLETED', state?.input, generatedImages.length === 0
|
|
255
|
+
? event.summary
|
|
256
|
+
: generatedImages.map((image) => image.name).join(', '), null, event.truncated, event.data, agentId === undefined ? state?.subagentTitle : this.#subagentTitles.get(agentId), details);
|
|
263
257
|
return [
|
|
264
258
|
{
|
|
265
259
|
...item,
|
|
@@ -283,7 +277,9 @@ export class MarAgentConversationNormalizer {
|
|
|
283
277
|
}
|
|
284
278
|
}
|
|
285
279
|
if (event.toolName === 'apply_patch' && state?.structured === true)
|
|
286
|
-
return [
|
|
280
|
+
return [
|
|
281
|
+
fileChangeItem(state?.itemId ?? itemId('tool', event.callId), 'FAILED', patchChanges(event.data, event.resultExtensions))
|
|
282
|
+
];
|
|
287
283
|
return [
|
|
288
284
|
genericToolItem(state?.itemId ?? itemId('tool', event.callId), event.toolName, 'FAILED', state?.input, event.message ?? event.code, event.code, event.truncated === true, undefined, state?.subagentTitle, state?.subagentDetails)
|
|
289
285
|
];
|
|
@@ -335,6 +331,9 @@ export class MarAgentConversationNormalizer {
|
|
|
335
331
|
toolName,
|
|
336
332
|
callId,
|
|
337
333
|
summary: observation.result.content,
|
|
334
|
+
...(parseToolResultExtensions(observation.resultExtensions)
|
|
335
|
+
? { resultExtensions: parseToolResultExtensions(observation.resultExtensions) }
|
|
336
|
+
: {}),
|
|
338
337
|
...(observation.result.truncated === undefined
|
|
339
338
|
? {}
|
|
340
339
|
: { truncated: observation.result.truncated }),
|
|
@@ -346,39 +345,10 @@ export class MarAgentConversationNormalizer {
|
|
|
346
345
|
const eventTitle = compactRunnerText(typeof eventDescription === 'string' ? eventDescription : '', 500);
|
|
347
346
|
if (eventTitle.length > 0)
|
|
348
347
|
this.#subagentTitles.set(event.agentId, eventTitle);
|
|
349
|
-
const
|
|
350
|
-
const details = mergeSubagentDetails(this.#subagentDetails.get(event.agentId), subagentDetails(event));
|
|
348
|
+
const details = mergeSubagentDetails(this.#subagentDetails.get(event.agentId), withoutSubagentLatestMessage(subagentDetails(event)) ?? {});
|
|
351
349
|
if (Object.keys(details).length > 0)
|
|
352
350
|
this.#subagentDetails.set(event.agentId, details);
|
|
353
|
-
|
|
354
|
-
const outputSummary = event.type === 'subagent.completed'
|
|
355
|
-
? latestText === undefined
|
|
356
|
-
? event.errorMessage === undefined
|
|
357
|
-
? null
|
|
358
|
-
: boundedToolSummary(event.errorMessage)
|
|
359
|
-
: boundedToolSummary(latestText)
|
|
360
|
-
: null;
|
|
361
|
-
const lifecycle = {
|
|
362
|
-
status: event.type === 'subagent.started'
|
|
363
|
-
? 'IN_PROGRESS'
|
|
364
|
-
: event.status === 'completed'
|
|
365
|
-
? 'COMPLETED'
|
|
366
|
-
: event.status === 'cancelled'
|
|
367
|
-
? 'CANCELLED'
|
|
368
|
-
: event.status === 'interrupted'
|
|
369
|
-
? 'INTERRUPTED'
|
|
370
|
-
: 'FAILED',
|
|
371
|
-
outputSummary,
|
|
372
|
-
errorCode: event.type === 'subagent.completed' && event.status === 'failed'
|
|
373
|
-
? (event.errorCode ?? 'MAR_AGENT_SUBAGENT_FAILED')
|
|
374
|
-
: null,
|
|
375
|
-
truncated: latestText !== undefined && outputSummary !== latestText
|
|
376
|
-
};
|
|
377
|
-
this.#subagentLifecycles.set(event.agentId, lifecycle);
|
|
378
|
-
const start = this.#subagentStarts.get(event.agentId);
|
|
379
|
-
if (start === undefined)
|
|
380
|
-
return [];
|
|
381
|
-
return [subagentStartItem(start, event.agentId, title, details, lifecycle)];
|
|
351
|
+
return [];
|
|
382
352
|
}
|
|
383
353
|
}
|
|
384
354
|
function codeModeToolResults(event) {
|
|
@@ -422,6 +392,7 @@ function codeModeCallResults(value) {
|
|
|
422
392
|
{
|
|
423
393
|
source: { callId: call.callId, toolName: call.toolName, input: call.input },
|
|
424
394
|
outcome: call.status,
|
|
395
|
+
resultExtensions: call.resultExtensions,
|
|
425
396
|
result: {
|
|
426
397
|
content: result.content,
|
|
427
398
|
...('data' in result ? { data: result.data } : {}),
|
|
@@ -582,15 +553,6 @@ function genericToolItem(id, toolName, status, input, outputSummary = null, erro
|
|
|
582
553
|
merge: true
|
|
583
554
|
};
|
|
584
555
|
}
|
|
585
|
-
function subagentStartItem(start, agentId, title, details, lifecycle) {
|
|
586
|
-
return genericToolItem(start.itemId, 'agent_start', lifecycle.status, start.input, lifecycle.outputSummary, lifecycle.errorCode, lifecycle.truncated, {
|
|
587
|
-
agentId,
|
|
588
|
-
description: title,
|
|
589
|
-
...details,
|
|
590
|
-
...(lifecycle.errorCode === null ? {} : { errorCode: lifecycle.errorCode }),
|
|
591
|
-
...(lifecycle.outputSummary === null ? {} : { errorMessage: lifecycle.outputSummary })
|
|
592
|
-
}, title, details);
|
|
593
|
-
}
|
|
594
556
|
function commandItem(command, status) {
|
|
595
557
|
return {
|
|
596
558
|
itemId: command.itemId,
|
|
@@ -623,7 +585,23 @@ function applyCommandResult(command, result) {
|
|
|
623
585
|
function fileChangeItem(id, status, changes) {
|
|
624
586
|
return { itemId: id, kind: 'file_change', status, payload: { changes }, merge: true };
|
|
625
587
|
}
|
|
626
|
-
function patchChanges(value) {
|
|
588
|
+
function patchChanges(value, extensions) {
|
|
589
|
+
const evidence = (parseToolResultExtensions(extensions) ?? []).flatMap((extension) => extension.entries);
|
|
590
|
+
if (evidence.length)
|
|
591
|
+
return evidence.map((entry) => ({
|
|
592
|
+
path: entry.path,
|
|
593
|
+
change: entry.change.toUpperCase(),
|
|
594
|
+
additions: entry.additions,
|
|
595
|
+
deletions: entry.deletions,
|
|
596
|
+
changeId: entry.changeId,
|
|
597
|
+
diffAvailable: entry.diff.status === 'available',
|
|
598
|
+
diff: entry.diff.status === 'available'
|
|
599
|
+
? { ...entry.diff, source: 'TOOL_CHANGE' }
|
|
600
|
+
: {
|
|
601
|
+
...entry.diff,
|
|
602
|
+
reason: entry.diff.reason === 'unsupported-content' ? 'binary' : entry.diff.reason
|
|
603
|
+
}
|
|
604
|
+
}));
|
|
627
605
|
const files = record(value)?.files;
|
|
628
606
|
if (!Array.isArray(files))
|
|
629
607
|
return [];
|
|
@@ -723,6 +701,7 @@ function marAgentSubagentProjection(toolName, input, output, knownTitle, knownDe
|
|
|
723
701
|
: ''), 500);
|
|
724
702
|
return {
|
|
725
703
|
subagentOperation: operation,
|
|
704
|
+
...(operation === 'STATUS' ? { subagentWait: inputData?.waitMs !== 0 } : {}),
|
|
726
705
|
subagentId: marAgentAgentId(input) ?? marAgentAgentId(controlOutput) ?? null,
|
|
727
706
|
...(title.length === 0 ? {} : { title }),
|
|
728
707
|
...subagentPayload(details),
|
|
@@ -762,20 +741,6 @@ function marAgentSubagentOperation(toolName) {
|
|
|
762
741
|
return 'CANCEL';
|
|
763
742
|
return undefined;
|
|
764
743
|
}
|
|
765
|
-
function subagentControlStatus(value) {
|
|
766
|
-
const status = structuredRecord(value)?.status;
|
|
767
|
-
if (status === 'queued' || status === 'running')
|
|
768
|
-
return 'IN_PROGRESS';
|
|
769
|
-
if (status === 'completed')
|
|
770
|
-
return 'COMPLETED';
|
|
771
|
-
if (status === 'cancelled')
|
|
772
|
-
return 'CANCELLED';
|
|
773
|
-
if (status === 'interrupted')
|
|
774
|
-
return 'INTERRUPTED';
|
|
775
|
-
if (status === 'failed')
|
|
776
|
-
return 'FAILED';
|
|
777
|
-
return 'COMPLETED';
|
|
778
|
-
}
|
|
779
744
|
function subagentDetails(value) {
|
|
780
745
|
const data = structuredRecord(value);
|
|
781
746
|
if (data === undefined)
|
|
@@ -57,6 +57,19 @@ export class MarAgentHostedConversationRuntime {
|
|
|
57
57
|
await mcp?.close().catch(() => undefined);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
async readToolResultResource(input) {
|
|
61
|
+
const catalog = await this.#openCatalog({ homeDirectory: input.homeDirectory });
|
|
62
|
+
try {
|
|
63
|
+
return await catalog.readToolResultResource(input.sessionId, {
|
|
64
|
+
resourceRef: input.resourceRef,
|
|
65
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
66
|
+
...(input.maxBytes === undefined ? {} : { maxBytes: input.maxBytes })
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await catalog.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
60
73
|
async history(input) {
|
|
61
74
|
const catalog = await this.#openCatalog({ homeDirectory: input.homeDirectory });
|
|
62
75
|
try {
|
|
@@ -147,6 +160,17 @@ export class MarAgentHostedConversationRuntime {
|
|
|
147
160
|
models: sdkModelConfigurations(models.filter((model) => input.models.some((candidate) => candidate.id === model.id))),
|
|
148
161
|
currentModelId: input.modelId,
|
|
149
162
|
prompt: input.userPrompt,
|
|
163
|
+
...(input.onModelUsage === undefined
|
|
164
|
+
? {}
|
|
165
|
+
: {
|
|
166
|
+
onModelUsage: (usage) => input.onModelUsage({
|
|
167
|
+
...usage,
|
|
168
|
+
sessionId: input.sessionId,
|
|
169
|
+
executionId: execution.executionId,
|
|
170
|
+
purpose: 'title',
|
|
171
|
+
agentRole: 'root'
|
|
172
|
+
})
|
|
173
|
+
}),
|
|
150
174
|
signal: titleController.signal
|
|
151
175
|
}).catch(() => undefined);
|
|
152
176
|
await this.#onEvent?.(input.conversationId, event);
|
|
@@ -302,6 +326,9 @@ export class MarAgentHostedConversationRuntime {
|
|
|
302
326
|
...(this.#webFetch === undefined ? {} : { webFetch: this.#webFetch })
|
|
303
327
|
}));
|
|
304
328
|
host = this.#createHost({
|
|
329
|
+
...('onModelUsage' in input && input.onModelUsage !== undefined
|
|
330
|
+
? { modelUsage: input.onModelUsage }
|
|
331
|
+
: {}),
|
|
305
332
|
extensions,
|
|
306
333
|
mode: 'node',
|
|
307
334
|
homeDirectory: input.homeDirectory,
|
|
@@ -12,7 +12,7 @@ import { boundedConversationItemId } from '../codex/conversation-parser.js';
|
|
|
12
12
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
13
13
|
import { canonicalJson } from '../../util/canonical-json.js';
|
|
14
14
|
import { MarAgentMcpClientCache } from './mcp-client-pool.js';
|
|
15
|
-
import { MarAgentConversationNormalizer } from './conversation-normalizer.js';
|
|
15
|
+
import { MarAgentConversationNormalizer, normalizeMarAgentUsage } from './conversation-normalizer.js';
|
|
16
16
|
import { parseRunnerImageAttachments } from '../../util/node-operation-parsers.js';
|
|
17
17
|
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
18
18
|
import { MarAgentRuntimeModelFactory, sdkModelConfigurations, closeImageCredentialProviders } from './runtime-model-factory.js';
|
|
@@ -435,6 +435,13 @@ export class MarAgentManagedRunController {
|
|
|
435
435
|
models: sdkModelConfigurations(executionModels),
|
|
436
436
|
currentModelId: input.model.id,
|
|
437
437
|
prompt: input.input,
|
|
438
|
+
onModelUsage: (usage) => this.host.emit(input.runId, 'run.usage', normalizeMarAgentUsage({
|
|
439
|
+
...usage,
|
|
440
|
+
sessionId: session.id,
|
|
441
|
+
executionId: handle.executionId,
|
|
442
|
+
purpose: 'title',
|
|
443
|
+
agentRole: 'root'
|
|
444
|
+
})),
|
|
438
445
|
signal: titleController.signal
|
|
439
446
|
}).catch((error) => {
|
|
440
447
|
if (!titleController.signal.aborted)
|
|
@@ -635,6 +642,11 @@ export class MarAgentManagedRunController {
|
|
|
635
642
|
releaseEnvironment = this.#retainEnvironment?.(input.secretEnvironment ?? {});
|
|
636
643
|
const mcp = mcpLease.pool;
|
|
637
644
|
const localHost = new LocalAgentHost({
|
|
645
|
+
modelUsage: (usage) => {
|
|
646
|
+
const runId = runtime?.activeRunId;
|
|
647
|
+
if (runId !== undefined)
|
|
648
|
+
this.host.emit(runId, 'run.usage', normalizeMarAgentUsage(usage));
|
|
649
|
+
},
|
|
638
650
|
mode: 'node',
|
|
639
651
|
homeDirectory: homedir(),
|
|
640
652
|
workspace: input.cwd,
|
|
@@ -76,6 +76,11 @@ export class MarAgentRuntimeModelFactory {
|
|
|
76
76
|
? {}
|
|
77
77
|
: { responsesTransport: configuration.responsesTransport }),
|
|
78
78
|
responsesToolNamespace: configuration.responsesToolNamespace,
|
|
79
|
+
...(configuration.responsesOptionalParameters === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: {
|
|
82
|
+
responsesOptionalParameters: configuration.responsesOptionalParameters
|
|
83
|
+
}),
|
|
79
84
|
responsesPreviousResponseId: configuration.responsesPreviousResponseId,
|
|
80
85
|
enabled: configuration.enabled
|
|
81
86
|
};
|
|
@@ -71,6 +71,21 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
71
71
|
let registeredIndex = 0;
|
|
72
72
|
return entry.images.map((image) => image.readable ? registeredImages[registeredIndex++] : undefined);
|
|
73
73
|
}
|
|
74
|
+
async readToolResultResource(session, input) {
|
|
75
|
+
if (!session.externalSessionId)
|
|
76
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
77
|
+
const catalog = await openMarAgentSessionCatalog({ homeDirectory: this.homeDirectory });
|
|
78
|
+
try {
|
|
79
|
+
return await catalog.readToolResultResource(session.externalSessionId, {
|
|
80
|
+
resourceRef: input.resourceRef,
|
|
81
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
82
|
+
...(input.maxBytes === undefined ? {} : { maxBytes: input.maxBytes })
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await catalog.close();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
74
89
|
profile(capabilities) {
|
|
75
90
|
if (!this.available(capabilities))
|
|
76
91
|
return undefined;
|
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
import { boundedConversationItemId, compactRunnerText } from '../codex/conversation-parser.js';
|
|
2
2
|
import { publicGoalHistoryText } from '../../service/goal-runtime.js';
|
|
3
3
|
import { normalizeOpenCodeTool } from './tool-normalizer.js';
|
|
4
|
+
import { RunnerUsageTracker, usageToken, usageTokenSum } from '../../service/runner-usage-tracker.js';
|
|
5
|
+
export class OpenCodeUsageNormalizer {
|
|
6
|
+
tracker;
|
|
7
|
+
messages = new Map();
|
|
8
|
+
constructor(identity, publish) {
|
|
9
|
+
this.tracker = new RunnerUsageTracker(identity, publish);
|
|
10
|
+
}
|
|
11
|
+
observe(value, startedAt) {
|
|
12
|
+
if (startedAt === undefined)
|
|
13
|
+
return;
|
|
14
|
+
const event = record(value), properties = record(event?.properties), info = record(properties?.info);
|
|
15
|
+
if (event?.type === 'message.updated' &&
|
|
16
|
+
info?.role === 'assistant' &&
|
|
17
|
+
typeof info.id === 'string' &&
|
|
18
|
+
typeof record(info.time)?.created === 'number' &&
|
|
19
|
+
Number(record(info.time)?.created) >= startedAt)
|
|
20
|
+
this.messages.set(info.id, {
|
|
21
|
+
...(typeof info.modelID === 'string' ? { model: info.modelID } : {}),
|
|
22
|
+
...(typeof info.providerID === 'string' ? { provider: info.providerID } : {})
|
|
23
|
+
});
|
|
24
|
+
const part = record(properties?.part);
|
|
25
|
+
if (event?.type !== 'message.part.updated' ||
|
|
26
|
+
part?.type !== 'step-finish' ||
|
|
27
|
+
typeof part.id !== 'string' ||
|
|
28
|
+
typeof part.messageID !== 'string')
|
|
29
|
+
return;
|
|
30
|
+
const model = this.messages.get(part.messageID);
|
|
31
|
+
if (!model)
|
|
32
|
+
return;
|
|
33
|
+
const tokens = record(part.tokens), cache = record(tokens?.cache);
|
|
34
|
+
const input = usageToken(tokens?.input), output = usageToken(tokens?.output), read = usageToken(cache?.read), write = usageToken(cache?.write), reasoning = usageToken(tokens?.reasoning);
|
|
35
|
+
const inputTokens = input === undefined ? undefined : usageTokenSum(input, read ?? '0', write ?? '0');
|
|
36
|
+
const outputTokens = output === undefined ? undefined : usageTokenSum(output, reasoning ?? '0');
|
|
37
|
+
this.tracker.snapshot(part.id, {
|
|
38
|
+
purpose: 'conversation',
|
|
39
|
+
agentRole: 'root',
|
|
40
|
+
...(model.model ? { actualModel: model.model } : {}),
|
|
41
|
+
...(model.provider ? { provider: model.provider } : {}),
|
|
42
|
+
...(inputTokens === undefined ? {} : { inputTokens }),
|
|
43
|
+
...(outputTokens === undefined ? {} : { outputTokens }),
|
|
44
|
+
...(read === undefined ? {} : { cacheReadTokens: read }),
|
|
45
|
+
...(write === undefined ? {} : { cacheWriteTokens: write }),
|
|
46
|
+
...(reasoning === undefined ? {} : { reasoningTokens: reasoning })
|
|
47
|
+
}, true, 'partial');
|
|
48
|
+
}
|
|
49
|
+
finish(state) {
|
|
50
|
+
this.tracker.finish(state);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
4
53
|
export function openCodeUserMessagePreview(messages) {
|
|
5
54
|
for (const message of messages) {
|
|
6
55
|
const entry = record(message);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OpenCodeUsageNormalizer } from './conversation-parser.js';
|
|
1
2
|
import { parseComposerTags } from '@myagentroam/protocol';
|
|
2
3
|
import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
|
|
3
4
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
@@ -11,6 +12,7 @@ export class OpenCodeManagedRunController {
|
|
|
11
12
|
runner;
|
|
12
13
|
host;
|
|
13
14
|
interactionTimeoutMs;
|
|
15
|
+
usage = new Map();
|
|
14
16
|
permissions = new Map();
|
|
15
17
|
questions = new Map();
|
|
16
18
|
startingCwds = new Map();
|
|
@@ -316,6 +318,12 @@ export class OpenCodeManagedRunController {
|
|
|
316
318
|
: undefined;
|
|
317
319
|
if (sessionID !== nativeSessionId)
|
|
318
320
|
return;
|
|
321
|
+
let usage = this.usage.get(runId);
|
|
322
|
+
if (!usage) {
|
|
323
|
+
usage = new OpenCodeUsageNormalizer(runId, (value) => this.host.emit(runId, 'run.usage', value));
|
|
324
|
+
this.usage.set(runId, usage);
|
|
325
|
+
}
|
|
326
|
+
usage.observe(value, execution?.responseStartedAt);
|
|
319
327
|
if (event?.type === 'session.idle') {
|
|
320
328
|
if (!this.promptAdmitted.has(runId)) {
|
|
321
329
|
this.pendingIdle.add(runId);
|
|
@@ -630,6 +638,10 @@ export class OpenCodeManagedRunController {
|
|
|
630
638
|
const client = execution?.client ?? this.startingClients.get(runId);
|
|
631
639
|
if (execution === undefined && cwd === undefined && client === undefined)
|
|
632
640
|
return;
|
|
641
|
+
this.usage
|
|
642
|
+
.get(runId)
|
|
643
|
+
?.finish(status === 'SUCCEEDED' ? 'succeeded' : status === 'FAILED' ? 'failed' : 'cancelled');
|
|
644
|
+
this.usage.delete(runId);
|
|
633
645
|
execution?.abort.abort();
|
|
634
646
|
this.runner.execution.delete(runId);
|
|
635
647
|
this.startingCwds.delete(runId);
|
|
@@ -328,6 +328,7 @@ export class ContinuousRunCoordinator {
|
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
330
|
this.options.control('run.start', {
|
|
331
|
+
...(pending.usageGrant === undefined ? {} : { usageGrant: pending.usageGrant }),
|
|
331
332
|
runId: executionId,
|
|
332
333
|
sessionId: pending.sessionId,
|
|
333
334
|
workspaceId: pending.workspaceId,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { modelUsageGrantSchema } from '@myagentroam/protocol';
|
|
1
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
3
|
import { constants } from 'node:fs';
|
|
3
4
|
import { lstat, mkdir, open, readFile, rename, rm, utimes } from 'node:fs/promises';
|
|
4
5
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
-
import { conversationTurnSchema, conversationTemplateDraftSchema, conversationTemplateSummarySchema, hostedConversationConfigUpdateInputSchema, hostedConversationCreateInputSchema, hostedConversationDeleteResultSchema, hostedConversationFileDeleteInputSchema, hostedConversationFileDeleteResultSchema, hostedConversationFileReadInputSchema, hostedConversationFileReadResultSchema, hostedConversationFilesSchema, hostedConversationHistoryInputSchema, hostedConversationHistoryPageSchema, hostedConversationIdInputSchema, hostedConversationImageReadInputSchema, hostedConversationListInputSchema, hostedConversationListPageSchema, hostedConversationMessageInputSchema, hostedConversationMetadataUpdateInputSchema, hostedConversationRollbackInputSchema, hostedConversationSnapshotSchema, hostedConversationSummarySchema, hostedConversationUnwatchInputSchema, hostedConversationUploadChunkInputSchema, hostedConversationUploadControlInputSchema, hostedConversationUploadCreateInputSchema, hostedConversationUploadPreflightInputSchema, hostedConversationUploadPreflightResultSchema, hostedConversationUploadSchema, hostedConversationWatchInputSchema, marAgentRuntimeModelConfigurationSchema, mcpInstallationConfigurationSchema, mcpRuntimeDescriptorSchema, resolveMarAgentDefaultReasoningEffort } from '@myagentroam/protocol';
|
|
6
|
+
import { toolResultResourceInputSchema, toolResultResourcePageSchema, conversationTurnSchema, conversationTemplateDraftSchema, conversationTemplateSummarySchema, hostedConversationConfigUpdateInputSchema, hostedConversationCreateInputSchema, hostedConversationDeleteResultSchema, hostedConversationFileDeleteInputSchema, hostedConversationFileDeleteResultSchema, hostedConversationFileReadInputSchema, hostedConversationFileReadResultSchema, hostedConversationFilesSchema, hostedConversationHistoryInputSchema, hostedConversationHistoryPageSchema, hostedConversationIdInputSchema, hostedConversationImageReadInputSchema, hostedConversationListInputSchema, hostedConversationListPageSchema, hostedConversationMessageInputSchema, hostedConversationMetadataUpdateInputSchema, hostedConversationRollbackInputSchema, hostedConversationSnapshotSchema, hostedConversationSummarySchema, hostedConversationUnwatchInputSchema, hostedConversationUploadChunkInputSchema, hostedConversationUploadControlInputSchema, hostedConversationUploadCreateInputSchema, hostedConversationUploadPreflightInputSchema, hostedConversationUploadPreflightResultSchema, hostedConversationUploadSchema, hostedConversationWatchInputSchema, marAgentRuntimeModelConfigurationSchema, mcpInstallationConfigurationSchema, mcpRuntimeDescriptorSchema, resolveMarAgentDefaultReasoningEffort } from '@myagentroam/protocol';
|
|
6
7
|
import { hostedConversationGeneratedImagesDisplayDirectory, hostedConversationImageRunId, marAgentGeneratedImagesDirectory } from '../runner/mar-agent/generated-images.js';
|
|
7
8
|
import { sessionTitleFromFirstMessage } from '../util/node-operation-parsers.js';
|
|
8
9
|
import { safeErrorCode } from '../util/safe-error.js';
|
|
@@ -70,6 +71,7 @@ export class HostedConversationNodeService {
|
|
|
70
71
|
'conversation.hosted.config.update': (raw) => this.updateConfiguration(raw),
|
|
71
72
|
'conversation.hosted.metadata.update': (raw) => this.updateMetadata(raw),
|
|
72
73
|
'conversation.hosted.image.read': (raw) => this.readImage(raw),
|
|
74
|
+
'conversation.hosted.tool-resource.read': (raw) => this.readToolResource(raw),
|
|
73
75
|
'conversation.hosted.files.list': (raw) => this.listFiles(raw),
|
|
74
76
|
'conversation.hosted.file.read': (raw) => this.readFile(raw),
|
|
75
77
|
'conversation.hosted.file.delete': (raw) => this.deleteFile(raw),
|
|
@@ -503,6 +505,9 @@ export class HostedConversationNodeService {
|
|
|
503
505
|
let runtimeEventsAccepted = false;
|
|
504
506
|
const handle = await this.options.runtime
|
|
505
507
|
.startRun({
|
|
508
|
+
...(payload.usageGrant === undefined || this.options.modelUsage === undefined
|
|
509
|
+
? {}
|
|
510
|
+
: { onModelUsage: (usage) => this.options.modelUsage(payload.usageGrant, usage) }),
|
|
506
511
|
conversationId: conversation.id,
|
|
507
512
|
sessionId: conversation.sessionId,
|
|
508
513
|
clientMessageId: input.clientMessageId,
|
|
@@ -874,6 +879,25 @@ export class HostedConversationNodeService {
|
|
|
874
879
|
files: this.options.store.listFiles(payload.userId, input.conversationId)
|
|
875
880
|
});
|
|
876
881
|
}
|
|
882
|
+
async readToolResource(raw) {
|
|
883
|
+
const payload = hostedRequest(raw);
|
|
884
|
+
const { conversationId } = hostedConversationIdInputSchema.parse(payload.data);
|
|
885
|
+
const conversation = this.options.store.requireConversation(payload.userId, conversationId);
|
|
886
|
+
const paths = this.options.store.pathsForConversation(payload.userId, conversation.id);
|
|
887
|
+
const data = payload.data;
|
|
888
|
+
const input = toolResultResourceInputSchema.parse({
|
|
889
|
+
resourceRef: data.resourceRef,
|
|
890
|
+
cursor: data.cursor,
|
|
891
|
+
maxBytes: data.maxBytes
|
|
892
|
+
});
|
|
893
|
+
if (!this.options.runtime.readToolResultResource)
|
|
894
|
+
throw new Error('TOOL_RESOURCE_UNSUPPORTED');
|
|
895
|
+
return toolResultResourcePageSchema.parse(await this.options.runtime.readToolResultResource({
|
|
896
|
+
homeDirectory: paths.homeDirectory,
|
|
897
|
+
sessionId: conversation.sessionId,
|
|
898
|
+
...input
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
877
901
|
async readImage(raw) {
|
|
878
902
|
const payload = hostedRequest(raw);
|
|
879
903
|
const input = hostedConversationImageReadInputSchema.parse(payload.data);
|
|
@@ -1638,7 +1662,7 @@ export class HostedConversationNodeService {
|
|
|
1638
1662
|
function hostedRequest(raw) {
|
|
1639
1663
|
if (!record(raw))
|
|
1640
1664
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
1641
|
-
const { __requestUserId, __template, __marAgentModel, __marAgentModels, __marAgentCompact, __marAgentRuntimeScopeId, __workspaceWatchSessionHash, __templateDeleted, ...data } = raw;
|
|
1665
|
+
const { __usageGrant, __requestUserId, __template, __marAgentModel, __marAgentModels, __marAgentCompact, __marAgentRuntimeScopeId, __workspaceWatchSessionHash, __templateDeleted, ...data } = raw;
|
|
1642
1666
|
if (typeof __requestUserId !== 'number' ||
|
|
1643
1667
|
!Number.isSafeInteger(__requestUserId) ||
|
|
1644
1668
|
__requestUserId <= 0)
|
|
@@ -1650,6 +1674,9 @@ function hostedRequest(raw) {
|
|
|
1650
1674
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
1651
1675
|
return {
|
|
1652
1676
|
data,
|
|
1677
|
+
...(__usageGrant === undefined
|
|
1678
|
+
? {}
|
|
1679
|
+
: { usageGrant: modelUsageGrantSchema.parse(__usageGrant) }),
|
|
1653
1680
|
userId: __requestUserId,
|
|
1654
1681
|
...(__template === undefined ? {} : { template: __template }),
|
|
1655
1682
|
...(__marAgentModel === undefined ? {} : { model: __marAgentModel }),
|
|
@@ -61,11 +61,26 @@ function modelTestErrorProjection(error) {
|
|
|
61
61
|
if (typeof candidate.code !== 'string' || typeof candidate.message !== 'string')
|
|
62
62
|
return runnerErrorProjection(error, 'MAR_AGENT_MODEL_CONNECTION_FAILED');
|
|
63
63
|
const prefix = `${candidate.code}: `;
|
|
64
|
-
|
|
64
|
+
const projection = runnerErrorProjection({
|
|
65
65
|
code: candidate.code,
|
|
66
66
|
message: candidate.message.startsWith(prefix)
|
|
67
67
|
? candidate.message.slice(prefix.length)
|
|
68
68
|
: candidate.message,
|
|
69
69
|
status: candidate.details?.['status']
|
|
70
70
|
}, 'MAR_AGENT_MODEL_CONNECTION_FAILED');
|
|
71
|
+
const details = candidate.details;
|
|
72
|
+
return {
|
|
73
|
+
...projection,
|
|
74
|
+
...(typeof details?.['body'] === 'string' && typeof details['bodyTruncated'] === 'boolean'
|
|
75
|
+
? {
|
|
76
|
+
response: {
|
|
77
|
+
body: details['body'],
|
|
78
|
+
bodyTruncated: details['bodyTruncated'],
|
|
79
|
+
contentType: details['contentType'] ?? null,
|
|
80
|
+
requestId: details['requestId'] ?? null,
|
|
81
|
+
retryAfter: details['retryAfter'] ?? null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
: {})
|
|
85
|
+
};
|
|
71
86
|
}
|