@myagentroam/node 0.9.174 → 0.9.175
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/codex-app-server.js +1 -0
- package/dist/runner/claude/managed-run-controller.js +89 -15
- package/dist/runner/codex/managed-run-controller.js +149 -3
- package/dist/runner/mar-agent/managed-run-controller.js +1 -1
- package/dist/runner/opencode/managed-run-controller.js +128 -14
- package/dist/service/generation-timing-tracker.js +81 -0
- package/package.json +5 -5
package/dist/codex-app-server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
|
8
8
|
import { safeErrorCode } from '../../util/safe-error.js';
|
|
9
9
|
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
10
10
|
import { ResponseLatencyTracker } from '../../service/response-latency-tracker.js';
|
|
11
|
+
import { GenerationTimingTracker } from '../../service/generation-timing-tracker.js';
|
|
11
12
|
export class ClaudeManagedRunController {
|
|
12
13
|
runner;
|
|
13
14
|
host;
|
|
@@ -164,12 +165,27 @@ export class ClaudeManagedRunController {
|
|
|
164
165
|
try {
|
|
165
166
|
const gate = new ClaudeChannelGate(sessionId);
|
|
166
167
|
const responseLatency = new ResponseLatencyTracker();
|
|
168
|
+
const generationTiming = new GenerationTimingTracker();
|
|
169
|
+
const finalOutputTokens = new Map();
|
|
170
|
+
const pendingOutputTokens = new Map();
|
|
171
|
+
const completedMessages = new Set();
|
|
167
172
|
let responseMessageId;
|
|
168
173
|
const observeResponse = (id, incremental = false) => {
|
|
169
174
|
const latencyMs = responseLatency.response(id, incremental);
|
|
170
175
|
if (latencyMs !== undefined)
|
|
171
176
|
this.host.emit(runId, 'run.performance', { latencyMs });
|
|
172
177
|
};
|
|
178
|
+
const settleGeneration = (id, observedAt) => {
|
|
179
|
+
if (id === undefined)
|
|
180
|
+
return;
|
|
181
|
+
generationTiming.end(id, observedAt);
|
|
182
|
+
const outputTokens = finalOutputTokens.get(id);
|
|
183
|
+
if (outputTokens === undefined)
|
|
184
|
+
return;
|
|
185
|
+
const result = generationTiming.settle(id, outputTokens, observedAt);
|
|
186
|
+
if (result !== undefined)
|
|
187
|
+
this.host.emit(runId, 'run.performance', { tokensPerSecond: result.tokensPerSecond });
|
|
188
|
+
};
|
|
173
189
|
handle = this.runner.startChannel({
|
|
174
190
|
prompt: payload.input,
|
|
175
191
|
cwd,
|
|
@@ -200,9 +216,14 @@ export class ClaudeManagedRunController {
|
|
|
200
216
|
}))
|
|
201
217
|
}),
|
|
202
218
|
onPermission: (toolName, toolInput, options) => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
219
|
+
if (options.agentID === undefined) {
|
|
220
|
+
const receiptAt = generationTiming.timestamp();
|
|
221
|
+
if (responseMessageId !== undefined)
|
|
222
|
+
generationTiming.pause(responseMessageId, receiptAt);
|
|
223
|
+
const latencyMs = responseLatency.toolStarted(options.toolUseID);
|
|
224
|
+
if (latencyMs !== undefined)
|
|
225
|
+
this.host.emit(runId, 'run.performance', { latencyMs });
|
|
226
|
+
}
|
|
206
227
|
return toolName === 'AskUserQuestion'
|
|
207
228
|
? (toolKinds.set(options.toolUseID, 'user_input_request'),
|
|
208
229
|
this.host.requestUserInput(runId, toolInput, options.toolUseID))
|
|
@@ -212,6 +233,7 @@ export class ClaudeManagedRunController {
|
|
|
212
233
|
onMessage: (message) => {
|
|
213
234
|
if (terminal)
|
|
214
235
|
return;
|
|
236
|
+
const receiptAt = generationTiming.timestamp();
|
|
215
237
|
const record = message;
|
|
216
238
|
const nativeSessionId = typeof record.session_id === 'string' ? record.session_id : undefined;
|
|
217
239
|
const current = this.runner.execution.runs.get(runId);
|
|
@@ -236,7 +258,8 @@ export class ClaudeManagedRunController {
|
|
|
236
258
|
});
|
|
237
259
|
}
|
|
238
260
|
}
|
|
239
|
-
|
|
261
|
+
const mainTask = record.parent_tool_use_id === undefined || record.parent_tool_use_id === null;
|
|
262
|
+
if (record.type === 'stream_event' && mainTask) {
|
|
240
263
|
// Observe semantic timing without forwarding raw thinking or input fragments.
|
|
241
264
|
const event = record.event;
|
|
242
265
|
const block = event?.content_block;
|
|
@@ -245,14 +268,49 @@ export class ClaudeManagedRunController {
|
|
|
245
268
|
const message = event.message;
|
|
246
269
|
responseMessageId = typeof message?.id === 'string' ? message.id : undefined;
|
|
247
270
|
}
|
|
271
|
+
if (event?.type === 'message_delta') {
|
|
272
|
+
const usage = event.usage;
|
|
273
|
+
if (responseMessageId !== undefined) {
|
|
274
|
+
if (typeof usage?.output_tokens === 'number')
|
|
275
|
+
pendingOutputTokens.set(responseMessageId, usage.output_tokens);
|
|
276
|
+
if (typeof delta?.stop_reason === 'string' && delta.stop_reason.length > 0) {
|
|
277
|
+
const tokens = pendingOutputTokens.get(responseMessageId);
|
|
278
|
+
if (tokens !== undefined)
|
|
279
|
+
finalOutputTokens.set(responseMessageId, tokens);
|
|
280
|
+
completedMessages.add(responseMessageId);
|
|
281
|
+
settleGeneration(responseMessageId, receiptAt);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (event?.type === 'message_stop') {
|
|
286
|
+
if (responseMessageId !== undefined) {
|
|
287
|
+
completedMessages.add(responseMessageId);
|
|
288
|
+
if (!finalOutputTokens.has(responseMessageId)) {
|
|
289
|
+
const tokens = pendingOutputTokens.get(responseMessageId);
|
|
290
|
+
if (tokens !== undefined)
|
|
291
|
+
finalOutputTokens.set(responseMessageId, tokens);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
settleGeneration(responseMessageId, receiptAt);
|
|
295
|
+
}
|
|
248
296
|
if (event?.type === 'content_block_start' &&
|
|
249
|
-
block?.type === 'tool_use'
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
297
|
+
(block?.type === 'tool_use' ||
|
|
298
|
+
block?.type === 'thinking' ||
|
|
299
|
+
block?.type === 'redacted_thinking' ||
|
|
300
|
+
block?.type === 'encrypted_thinking' ||
|
|
301
|
+
block?.type === 'text')) {
|
|
302
|
+
if (responseMessageId !== undefined)
|
|
303
|
+
generationTiming.start(responseMessageId, receiptAt);
|
|
304
|
+
if (block?.type !== 'text' ||
|
|
305
|
+
(typeof block.text === 'string' && block.text.length > 0)) {
|
|
306
|
+
const latencyMs = block?.type === 'tool_use' && typeof block.id === 'string'
|
|
307
|
+
? responseLatency.toolStarted(block.id)
|
|
308
|
+
: responseLatency.response(responseMessageId);
|
|
309
|
+
if (latencyMs !== undefined)
|
|
310
|
+
this.host.emit(runId, 'run.performance', { latencyMs });
|
|
311
|
+
if (block?.type === 'tool_use')
|
|
312
|
+
responseLatency.response(responseMessageId);
|
|
313
|
+
}
|
|
256
314
|
}
|
|
257
315
|
if ((event?.type === 'content_block_start' && block?.type === 'thinking') ||
|
|
258
316
|
(event?.type === 'content_block_delta' &&
|
|
@@ -264,12 +322,27 @@ export class ClaudeManagedRunController {
|
|
|
264
322
|
delta.thinking.length > 0) ||
|
|
265
323
|
(delta?.type === 'input_json_delta' &&
|
|
266
324
|
typeof delta.partial_json === 'string' &&
|
|
267
|
-
delta.partial_json.length > 0))))
|
|
325
|
+
delta.partial_json.length > 0)))) {
|
|
326
|
+
if (responseMessageId !== undefined)
|
|
327
|
+
generationTiming.start(responseMessageId, receiptAt);
|
|
268
328
|
observeResponse(responseMessageId, event?.type === 'content_block_delta');
|
|
329
|
+
}
|
|
269
330
|
}
|
|
270
331
|
if (record.type === 'assistant') {
|
|
332
|
+
const snapshotMessage = record.message;
|
|
333
|
+
const snapshotId = typeof snapshotMessage?.id === 'string' ? snapshotMessage.id : undefined;
|
|
334
|
+
const snapshotUsage = snapshotMessage?.usage;
|
|
335
|
+
if (mainTask &&
|
|
336
|
+
snapshotId !== undefined &&
|
|
337
|
+
typeof snapshotUsage?.output_tokens === 'number' &&
|
|
338
|
+
(completedMessages.has(snapshotId) ||
|
|
339
|
+
(typeof snapshotMessage?.stop_reason === 'string' &&
|
|
340
|
+
snapshotMessage.stop_reason.length > 0))) {
|
|
341
|
+
finalOutputTokens.set(snapshotId, snapshotUsage.output_tokens);
|
|
342
|
+
settleGeneration(snapshotId, receiptAt);
|
|
343
|
+
}
|
|
271
344
|
const text = extractClaudeText(record);
|
|
272
|
-
if (text.length > 0 || extractClaudeThinkingSummaries(record).length > 0)
|
|
345
|
+
if (mainTask && (text.length > 0 || extractClaudeThinkingSummaries(record).length > 0))
|
|
273
346
|
observeResponse(typeof record.message?.id === 'string'
|
|
274
347
|
? record.message.id
|
|
275
348
|
: undefined);
|
|
@@ -292,7 +365,7 @@ export class ClaudeManagedRunController {
|
|
|
292
365
|
});
|
|
293
366
|
}
|
|
294
367
|
for (const tool of extractClaudeToolUses(record)) {
|
|
295
|
-
const latencyMs = responseLatency.toolStarted(tool.id);
|
|
368
|
+
const latencyMs = mainTask ? responseLatency.toolStarted(tool.id) : undefined;
|
|
296
369
|
if (latencyMs !== undefined)
|
|
297
370
|
this.host.emit(runId, 'run.performance', { latencyMs });
|
|
298
371
|
if (tool.name === 'AskUserQuestion') {
|
|
@@ -358,7 +431,8 @@ export class ClaudeManagedRunController {
|
|
|
358
431
|
}
|
|
359
432
|
if (record.type === 'user') {
|
|
360
433
|
for (const result of extractClaudeToolResults(record)) {
|
|
361
|
-
|
|
434
|
+
if (mainTask)
|
|
435
|
+
responseLatency.toolCompleted(result.toolUseId);
|
|
362
436
|
const kind = toolKinds.get(result.toolUseId) ?? 'tool_call';
|
|
363
437
|
if (kind === 'user_input_request')
|
|
364
438
|
continue;
|
|
@@ -4,6 +4,7 @@ import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
|
4
4
|
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
|
+
import { GenerationTimingTracker } from '../../service/generation-timing-tracker.js';
|
|
7
8
|
import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
|
|
8
9
|
export function resolveCodexNotificationRun(runs, input) {
|
|
9
10
|
const entries = [...runs.entries()];
|
|
@@ -137,6 +138,7 @@ export class CodexManagedRunController {
|
|
|
137
138
|
if (run === undefined)
|
|
138
139
|
return;
|
|
139
140
|
const [runId, activeRun] = run;
|
|
141
|
+
const receiptAt = activeRun.generationTiming?.timestamp();
|
|
140
142
|
if (notification.method === 'turn/started' && turnId !== undefined) {
|
|
141
143
|
const active = this.runner.execution.runs.get(runId);
|
|
142
144
|
if (active !== undefined)
|
|
@@ -144,6 +146,16 @@ export class CodexManagedRunController {
|
|
|
144
146
|
this.host.emit(runId, 'run.running', { threadId, turnId }, 'RUNNING');
|
|
145
147
|
return;
|
|
146
148
|
}
|
|
149
|
+
if (notification.method === 'rawResponseItem/completed')
|
|
150
|
+
return;
|
|
151
|
+
if (notification.method === 'rawResponse/completed') {
|
|
152
|
+
this.completeRawResponse(runId, params, activeRun, receiptAt);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (notification.method === 'thread/tokenUsage/updated') {
|
|
156
|
+
this.completeStandardUsage(runId, params, activeRun, receiptAt);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
147
159
|
if (notification.method === 'thread/goal/updated' &&
|
|
148
160
|
activeRun.goalMcp === true &&
|
|
149
161
|
turnId !== undefined &&
|
|
@@ -168,6 +180,7 @@ export class CodexManagedRunController {
|
|
|
168
180
|
if (notification.requestId !== undefined &&
|
|
169
181
|
(notification.method === 'item/commandExecution/requestApproval' ||
|
|
170
182
|
notification.method === 'item/fileChange/requestApproval')) {
|
|
183
|
+
this.pauseGeneration(runId, receiptAt);
|
|
171
184
|
this.requestApproval(runId, {
|
|
172
185
|
...notification,
|
|
173
186
|
requestId: notification.requestId
|
|
@@ -176,6 +189,7 @@ export class CodexManagedRunController {
|
|
|
176
189
|
}
|
|
177
190
|
if (notification.requestId !== undefined &&
|
|
178
191
|
notification.method === 'item/tool/requestUserInput') {
|
|
192
|
+
this.pauseGeneration(runId, receiptAt);
|
|
179
193
|
this.requestUserInput(runId, notification.requestId, params);
|
|
180
194
|
return;
|
|
181
195
|
}
|
|
@@ -200,15 +214,19 @@ export class CodexManagedRunController {
|
|
|
200
214
|
text: params.delta,
|
|
201
215
|
...(itemId === undefined ? {} : { itemId })
|
|
202
216
|
});
|
|
217
|
+
if (params.delta.length > 0)
|
|
218
|
+
this.beginGeneration(runId, receiptAt);
|
|
203
219
|
if (params.delta.length > 0)
|
|
204
220
|
this.observeResponse(runId, itemId, true);
|
|
205
221
|
return;
|
|
206
222
|
}
|
|
207
223
|
if (notification.method === 'item/reasoning/textDelta' && typeof params.delta === 'string') {
|
|
208
|
-
if (params.delta.length > 0)
|
|
224
|
+
if (params.delta.length > 0) {
|
|
225
|
+
this.beginGeneration(runId, receiptAt);
|
|
209
226
|
this.observeResponse(runId, typeof params.itemId === 'string'
|
|
210
227
|
? boundedConversationItemId('codex', params.itemId)
|
|
211
228
|
: undefined, true);
|
|
229
|
+
}
|
|
212
230
|
return;
|
|
213
231
|
}
|
|
214
232
|
if (notification.method === 'item/reasoning/summaryTextDelta' &&
|
|
@@ -225,8 +243,10 @@ export class CodexManagedRunController {
|
|
|
225
243
|
merge: true,
|
|
226
244
|
append: 'sections'
|
|
227
245
|
});
|
|
228
|
-
if (params.delta.length > 0)
|
|
246
|
+
if (params.delta.length > 0) {
|
|
247
|
+
this.beginGeneration(runId, receiptAt);
|
|
229
248
|
this.observeResponse(runId, boundedConversationItemId('codex', params.itemId), true);
|
|
249
|
+
}
|
|
230
250
|
return;
|
|
231
251
|
}
|
|
232
252
|
if (notification.method === 'item/commandExecution/outputDelta' &&
|
|
@@ -260,9 +280,25 @@ export class CodexManagedRunController {
|
|
|
260
280
|
const item = codexLiveConversationItem(params.item, notification.method === 'item/completed', attachments);
|
|
261
281
|
if (item !== undefined) {
|
|
262
282
|
this.host.emitItem(runId, item);
|
|
283
|
+
if (item.kind === 'context_compaction') {
|
|
284
|
+
activeRun.generationCompacting = notification.method === 'item/started';
|
|
285
|
+
if (activeRun.generationCompacting) {
|
|
286
|
+
for (const key of activeRun.generationKeys ?? [])
|
|
287
|
+
activeRun.generationTiming?.discard(key);
|
|
288
|
+
activeRun.generationKeys = [];
|
|
289
|
+
activeRun.currentGenerationKey = undefined;
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
263
293
|
const tracker = activeRun.responseLatency;
|
|
264
294
|
const tool = ['tool_call', 'command_execution', 'file_change'].includes(item.kind);
|
|
265
295
|
if (tool) {
|
|
296
|
+
if (notification.method === 'item/started') {
|
|
297
|
+
if (codexToolGeneratesArguments(params.item))
|
|
298
|
+
this.beginGeneration(runId, receiptAt);
|
|
299
|
+
else
|
|
300
|
+
this.pauseGeneration(runId, receiptAt);
|
|
301
|
+
}
|
|
266
302
|
if (notification.method === 'item/completed')
|
|
267
303
|
tracker?.toolCompleted(item.itemId);
|
|
268
304
|
else {
|
|
@@ -273,12 +309,15 @@ export class CodexManagedRunController {
|
|
|
273
309
|
}
|
|
274
310
|
else if (['assistant_message', 'reasoning_summary', 'plan'].includes(item.kind)) {
|
|
275
311
|
// A started reasoning item is itself a semantic response, even before its summary.
|
|
312
|
+
if (notification.method === 'item/started')
|
|
313
|
+
this.beginGeneration(runId, receiptAt);
|
|
276
314
|
this.observeResponse(runId, item.itemId);
|
|
277
315
|
}
|
|
278
316
|
}
|
|
279
317
|
return;
|
|
280
318
|
}
|
|
281
319
|
if (notification.method === 'turn/completed') {
|
|
320
|
+
this.endPendingGenerations(activeRun, receiptAt);
|
|
282
321
|
const status = turn?.status;
|
|
283
322
|
const terminal = status === 'completed' ? 'SUCCEEDED' : status === 'interrupted' ? 'INTERRUPTED' : 'FAILED';
|
|
284
323
|
if (terminal === 'FAILED')
|
|
@@ -308,6 +347,87 @@ export class CodexManagedRunController {
|
|
|
308
347
|
if (latencyMs !== undefined)
|
|
309
348
|
this.host.emit(runId, 'run.performance', { latencyMs });
|
|
310
349
|
}
|
|
350
|
+
beginGeneration(runId, observedAt) {
|
|
351
|
+
const active = this.runner.execution.runs.get(runId);
|
|
352
|
+
if (active?.generationTiming === undefined || active.generationCompacting === true)
|
|
353
|
+
return;
|
|
354
|
+
let key = active.currentGenerationKey;
|
|
355
|
+
if (key === undefined) {
|
|
356
|
+
const sequence = (active.generationRequestSequence ?? 0) + 1;
|
|
357
|
+
key = `${active.turnId ?? active.threadId}:generation:${sequence}`;
|
|
358
|
+
active.generationKeys ??= [];
|
|
359
|
+
active.generationKeys.push(key);
|
|
360
|
+
active.generationRequestSequence = sequence;
|
|
361
|
+
active.currentGenerationKey = key;
|
|
362
|
+
}
|
|
363
|
+
active.generationTiming.start(key, observedAt);
|
|
364
|
+
}
|
|
365
|
+
pauseGeneration(runId, observedAt) {
|
|
366
|
+
const active = this.runner.execution.runs.get(runId);
|
|
367
|
+
const key = active?.currentGenerationKey;
|
|
368
|
+
if (active?.generationTiming === undefined || key === undefined)
|
|
369
|
+
return;
|
|
370
|
+
active.generationTiming.pause(key, observedAt);
|
|
371
|
+
}
|
|
372
|
+
endPendingGenerations(active, observedAt) {
|
|
373
|
+
if (active.generationTiming === undefined)
|
|
374
|
+
return;
|
|
375
|
+
for (const key of active.generationKeys ?? [])
|
|
376
|
+
active.generationTiming.end(key, observedAt);
|
|
377
|
+
}
|
|
378
|
+
completeRawResponse(runId, params, active, observedAt) {
|
|
379
|
+
const responseId = typeof params.responseId === 'string' ? params.responseId : undefined;
|
|
380
|
+
if (responseId === undefined || active.completedGenerationResponseIds?.has(responseId))
|
|
381
|
+
return;
|
|
382
|
+
active.completedGenerationResponseIds ??= new Set();
|
|
383
|
+
active.completedGenerationResponseIds.add(responseId);
|
|
384
|
+
if (active.generationCompacting === true)
|
|
385
|
+
return;
|
|
386
|
+
const key = active.generationResponseIds?.get(responseId) ??
|
|
387
|
+
active.currentGenerationKey ??
|
|
388
|
+
active.generationKeys?.find((candidate) => active.generationTiming?.has(candidate));
|
|
389
|
+
if (key === undefined || active.generationTiming === undefined)
|
|
390
|
+
return;
|
|
391
|
+
active.generationResponseIds ??= new Map();
|
|
392
|
+
active.generationResponseIds.set(responseId, key);
|
|
393
|
+
active.generationTiming.end(key, observedAt);
|
|
394
|
+
if (active.currentGenerationKey === key)
|
|
395
|
+
active.currentGenerationKey = undefined;
|
|
396
|
+
const outputTokens = codexOutputTokens(params.usage);
|
|
397
|
+
if (outputTokens === undefined)
|
|
398
|
+
return;
|
|
399
|
+
this.emitGenerationTps(runId, active, key, outputTokens, observedAt);
|
|
400
|
+
}
|
|
401
|
+
completeStandardUsage(runId, params, active, observedAt) {
|
|
402
|
+
if (active.generationTiming === undefined)
|
|
403
|
+
return;
|
|
404
|
+
const usage = isPlainRecord(params.tokenUsage) ? params.tokenUsage : undefined;
|
|
405
|
+
const outputTokens = codexOutputTokens(usage?.last);
|
|
406
|
+
if (outputTokens === undefined)
|
|
407
|
+
return;
|
|
408
|
+
// TokenCount also repeats the previous request's snapshot (for example after compaction).
|
|
409
|
+
// Include total counters to distinguish equal last counts from distinct model requests.
|
|
410
|
+
const snapshot = JSON.stringify([
|
|
411
|
+
codexUsageCounters(usage?.last),
|
|
412
|
+
codexUsageCounters(usage?.total)
|
|
413
|
+
]);
|
|
414
|
+
if (active.generationUsageSnapshot === snapshot)
|
|
415
|
+
return;
|
|
416
|
+
active.generationUsageSnapshot = snapshot;
|
|
417
|
+
if (active.generationCompacting === true)
|
|
418
|
+
return;
|
|
419
|
+
const key = active.generationKeys?.find((candidate) => active.generationTiming?.has(candidate));
|
|
420
|
+
if (key !== undefined)
|
|
421
|
+
this.emitGenerationTps(runId, active, key, outputTokens, observedAt);
|
|
422
|
+
}
|
|
423
|
+
emitGenerationTps(runId, active, key, outputTokens, observedAt) {
|
|
424
|
+
const result = active.generationTiming?.settle(key, outputTokens, observedAt);
|
|
425
|
+
if (result !== undefined)
|
|
426
|
+
this.host.emit(runId, 'run.performance', { tokensPerSecond: result.tokensPerSecond });
|
|
427
|
+
active.generationKeys = active.generationKeys?.filter((candidate) => candidate !== key);
|
|
428
|
+
if (active.currentGenerationKey === key)
|
|
429
|
+
active.currentGenerationKey = undefined;
|
|
430
|
+
}
|
|
311
431
|
requestApproval(runId, notification) {
|
|
312
432
|
const approvalId = `codex:${runId}:${notification.requestId}`;
|
|
313
433
|
const expiresAt = Date.now() + 10 * 60_000;
|
|
@@ -494,7 +614,11 @@ export class CodexManagedRunController {
|
|
|
494
614
|
cwd,
|
|
495
615
|
goalMcp: hasGoalMcp(payload.mcpServers),
|
|
496
616
|
scheduleMcp: hasScheduleMcp(payload.mcpServers),
|
|
497
|
-
responseLatency: new ResponseLatencyTracker()
|
|
617
|
+
responseLatency: new ResponseLatencyTracker(),
|
|
618
|
+
generationTiming: new GenerationTimingTracker(),
|
|
619
|
+
generationKeys: [],
|
|
620
|
+
generationResponseIds: new Map(),
|
|
621
|
+
completedGenerationResponseIds: new Set()
|
|
498
622
|
});
|
|
499
623
|
turnResult = await this.runner.startTurn(threadId, input, cwd, turnConfiguration, materializedAttachments, runId);
|
|
500
624
|
break;
|
|
@@ -560,3 +684,25 @@ function hasGoalMcp(value) {
|
|
|
560
684
|
function hasScheduleMcp(value) {
|
|
561
685
|
return isPlainRecord(value) && isPlainRecord(value.mar_schedule);
|
|
562
686
|
}
|
|
687
|
+
function codexOutputTokens(value) {
|
|
688
|
+
if (!isPlainRecord(value) ||
|
|
689
|
+
typeof value.outputTokens !== 'number' ||
|
|
690
|
+
!Number.isSafeInteger(value.outputTokens) ||
|
|
691
|
+
value.outputTokens < 0)
|
|
692
|
+
return undefined;
|
|
693
|
+
return value.outputTokens;
|
|
694
|
+
}
|
|
695
|
+
function codexToolGeneratesArguments(value) {
|
|
696
|
+
return ['functionCall', 'function_call', 'customToolCall', 'custom_tool_call'].includes(typeof value.type === 'string' ? value.type : '');
|
|
697
|
+
}
|
|
698
|
+
function codexUsageCounters(value) {
|
|
699
|
+
if (!isPlainRecord(value))
|
|
700
|
+
return undefined;
|
|
701
|
+
return [
|
|
702
|
+
value.inputTokens,
|
|
703
|
+
value.cachedInputTokens,
|
|
704
|
+
value.outputTokens,
|
|
705
|
+
value.reasoningOutputTokens,
|
|
706
|
+
value.totalTokens
|
|
707
|
+
];
|
|
708
|
+
}
|
|
@@ -832,7 +832,7 @@ export class MarAgentManagedRunController {
|
|
|
832
832
|
this.host.refreshContext?.(runId);
|
|
833
833
|
if (event.type === 'model.performance')
|
|
834
834
|
this.host.emit(runId, 'run.performance', {
|
|
835
|
-
latencyMs: event.latencyMs,
|
|
835
|
+
...(event.latencyMs === undefined ? {} : { latencyMs: event.latencyMs }),
|
|
836
836
|
...(event.tokensPerSecond === undefined ? {} : { tokensPerSecond: event.tokensPerSecond })
|
|
837
837
|
});
|
|
838
838
|
else if (event.type === 'message.delta')
|
|
@@ -5,6 +5,7 @@ import { boundedConversationItemId, compactRunnerText } from '../codex/conversat
|
|
|
5
5
|
import { openCodePatchToolPayload, openCodeQuestions } from './conversation-parser.js';
|
|
6
6
|
import { normalizeOpenCodeTool } from './tool-normalizer.js';
|
|
7
7
|
import { ResponseLatencyTracker } from '../../service/response-latency-tracker.js';
|
|
8
|
+
import { GenerationTimingTracker } from '../../service/generation-timing-tracker.js';
|
|
8
9
|
export class OpenCodeManagedRunController {
|
|
9
10
|
runner;
|
|
10
11
|
host;
|
|
@@ -272,6 +273,7 @@ export class OpenCodeManagedRunController {
|
|
|
272
273
|
execution.responsePartKinds = new Map();
|
|
273
274
|
execution.responseStartedAt = Date.now();
|
|
274
275
|
execution.responseMessages = new Set();
|
|
276
|
+
execution.generationTiming = new GenerationTimingTracker();
|
|
275
277
|
}
|
|
276
278
|
await client.prompt({
|
|
277
279
|
directory: input.cwd,
|
|
@@ -295,6 +297,8 @@ export class OpenCodeManagedRunController {
|
|
|
295
297
|
const event = record(value);
|
|
296
298
|
const properties = record(event?.properties);
|
|
297
299
|
const nested = record(properties?.part) ?? record(properties?.info);
|
|
300
|
+
const execution = this.runner.execution.get(runId);
|
|
301
|
+
const receiptAt = execution?.generationTiming?.timestamp();
|
|
298
302
|
const sessionID = typeof properties?.sessionID === 'string'
|
|
299
303
|
? properties.sessionID
|
|
300
304
|
: typeof nested?.sessionID === 'string'
|
|
@@ -325,8 +329,10 @@ export class OpenCodeManagedRunController {
|
|
|
325
329
|
if (info.role === 'assistant' &&
|
|
326
330
|
execution?.responseStartedAt !== undefined &&
|
|
327
331
|
typeof created === 'number' &&
|
|
328
|
-
created >= execution.responseStartedAt)
|
|
332
|
+
created >= execution.responseStartedAt) {
|
|
329
333
|
execution.responseMessages?.add(info.id);
|
|
334
|
+
execution.currentResponseMessageId = info.id;
|
|
335
|
+
}
|
|
330
336
|
}
|
|
331
337
|
if (info?.role === 'assistant' &&
|
|
332
338
|
typeof info.id === 'string' &&
|
|
@@ -358,8 +364,12 @@ export class OpenCodeManagedRunController {
|
|
|
358
364
|
typeof properties.delta === 'string' &&
|
|
359
365
|
properties.delta.length > 0 &&
|
|
360
366
|
typeof properties.messageID === 'string' &&
|
|
361
|
-
this.currentResponseMessage(runId, properties.messageID))
|
|
367
|
+
this.currentResponseMessage(runId, properties.messageID)) {
|
|
368
|
+
const key = this.generationKey(runId, properties.messageID, properties.partID);
|
|
369
|
+
if (key !== undefined)
|
|
370
|
+
execution?.generationTiming?.start(key, receiptAt);
|
|
362
371
|
this.observeResponse(runId, boundedConversationItemId('opencode', properties.partID), true);
|
|
372
|
+
}
|
|
363
373
|
return;
|
|
364
374
|
}
|
|
365
375
|
if (event?.type === 'message.part.updated') {
|
|
@@ -386,36 +396,91 @@ export class OpenCodeManagedRunController {
|
|
|
386
396
|
return;
|
|
387
397
|
if (typeof part.id === 'string' && typeof part.type === 'string')
|
|
388
398
|
this.runner.execution.get(runId)?.responsePartKinds?.set(part.id, part.type);
|
|
399
|
+
const tracker = this.currentResponseMessage(runId, part.messageID)
|
|
400
|
+
? execution?.generationTiming
|
|
401
|
+
: undefined;
|
|
402
|
+
if (part.type === 'step-start') {
|
|
403
|
+
if (tracker !== undefined && typeof part.id === 'string')
|
|
404
|
+
this.generationKey(runId, part.messageID, part.id, receiptAt, true);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (part.type === 'step-finish') {
|
|
408
|
+
const key = typeof part.id === 'string'
|
|
409
|
+
? (execution?.generationPartKeys?.get(part.id) ??
|
|
410
|
+
execution?.generationRequestKeys?.get(part.messageID))
|
|
411
|
+
: undefined;
|
|
412
|
+
if (tracker !== undefined && key !== undefined && typeof part.id === 'string') {
|
|
413
|
+
execution?.generationPartKeys?.set(part.id, key);
|
|
414
|
+
tracker.end(key, receiptAt);
|
|
415
|
+
if (execution?.generationRequestKeys?.get(part.messageID) === key)
|
|
416
|
+
execution.generationRequestKeys.delete(part.messageID);
|
|
417
|
+
const outputTokens = openCodeOutputTokens(part.tokens);
|
|
418
|
+
if (outputTokens !== undefined) {
|
|
419
|
+
const result = tracker.settle(key, outputTokens, receiptAt);
|
|
420
|
+
if (result !== undefined)
|
|
421
|
+
this.host.emit(runId, 'run.performance', { tokensPerSecond: result.tokensPerSecond });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
389
426
|
const item = openCodePart(part, this.runner.execution.get(runId)?.agent === 'plan');
|
|
390
427
|
if (item !== undefined)
|
|
391
428
|
this.host.emitItem(runId, item);
|
|
392
429
|
if (typeof part.id === 'string') {
|
|
393
430
|
const itemId = boundedConversationItemId('opencode', part.id);
|
|
394
|
-
const
|
|
395
|
-
?
|
|
431
|
+
const responseLatency = this.currentResponseMessage(runId, part.messageID)
|
|
432
|
+
? execution?.responseLatency
|
|
433
|
+
: undefined;
|
|
434
|
+
const generationTiming = this.currentResponseMessage(runId, part.messageID)
|
|
435
|
+
? execution?.generationTiming
|
|
396
436
|
: undefined;
|
|
437
|
+
const generationKey = generationTiming === undefined
|
|
438
|
+
? undefined
|
|
439
|
+
: this.generationKey(runId, part.messageID, part.id);
|
|
397
440
|
if (part.type === 'tool') {
|
|
398
441
|
const state = record(part.state);
|
|
399
|
-
if (state?.status === '
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const latencyMs =
|
|
442
|
+
if (state?.status === 'pending') {
|
|
443
|
+
if (generationKey !== undefined)
|
|
444
|
+
generationTiming?.start(generationKey, receiptAt);
|
|
445
|
+
const latencyMs = responseLatency?.toolStarted(itemId);
|
|
446
|
+
if (latencyMs !== undefined)
|
|
447
|
+
this.host.emit(runId, 'run.performance', { latencyMs });
|
|
448
|
+
}
|
|
449
|
+
else if (state?.status === 'running') {
|
|
450
|
+
if (generationKey !== undefined)
|
|
451
|
+
generationTiming?.pause(generationKey, receiptAt);
|
|
452
|
+
const latencyMs = responseLatency?.toolStarted(itemId);
|
|
403
453
|
if (latencyMs !== undefined)
|
|
404
454
|
this.host.emit(runId, 'run.performance', { latencyMs });
|
|
405
455
|
}
|
|
456
|
+
else if (state?.status === 'completed' || state?.status === 'error') {
|
|
457
|
+
if (generationKey !== undefined)
|
|
458
|
+
generationTiming?.pause(generationKey, receiptAt);
|
|
459
|
+
responseLatency?.toolCompleted(itemId);
|
|
460
|
+
}
|
|
406
461
|
}
|
|
407
|
-
else if (
|
|
462
|
+
else if (generationTiming !== undefined &&
|
|
463
|
+
generationKey !== undefined &&
|
|
408
464
|
(part.type === 'text' || part.type === 'reasoning') &&
|
|
409
|
-
typeof part.text === 'string'
|
|
410
|
-
part.
|
|
411
|
-
|
|
465
|
+
typeof part.text === 'string') {
|
|
466
|
+
if (isOpenCodeGenerationStart(part.time))
|
|
467
|
+
generationTiming.start(generationKey, receiptAt);
|
|
468
|
+
if (isOpenCodeGenerationEnd(part.time))
|
|
469
|
+
generationTiming.pause(generationKey, receiptAt);
|
|
470
|
+
if (part.text.length > 0)
|
|
471
|
+
this.observeResponse(runId, itemId);
|
|
472
|
+
}
|
|
412
473
|
}
|
|
413
474
|
return;
|
|
414
475
|
}
|
|
415
|
-
if (event?.type === 'permission.asked')
|
|
476
|
+
if (event?.type === 'permission.asked') {
|
|
477
|
+
this.pauseCurrentGeneration(runId, receiptAt);
|
|
416
478
|
this.requestPermission(runId, properties);
|
|
417
|
-
|
|
479
|
+
}
|
|
480
|
+
if (event?.type === 'question.asked') {
|
|
481
|
+
this.pauseCurrentGeneration(runId, receiptAt);
|
|
418
482
|
this.requestQuestion(runId, properties);
|
|
483
|
+
}
|
|
419
484
|
}
|
|
420
485
|
currentResponseMessage(runId, messageId) {
|
|
421
486
|
const parent = this.messageParents.get(runId)?.get(messageId);
|
|
@@ -430,6 +495,37 @@ export class OpenCodeManagedRunController {
|
|
|
430
495
|
if (latencyMs !== undefined)
|
|
431
496
|
this.host.emit(runId, 'run.performance', { latencyMs });
|
|
432
497
|
}
|
|
498
|
+
pauseCurrentGeneration(runId, observedAt) {
|
|
499
|
+
const execution = this.runner.execution.get(runId);
|
|
500
|
+
const key = execution?.currentResponseMessageId === undefined
|
|
501
|
+
? undefined
|
|
502
|
+
: execution.generationRequestKeys?.get(execution.currentResponseMessageId);
|
|
503
|
+
if (key !== undefined)
|
|
504
|
+
execution?.generationTiming?.pause(key, observedAt);
|
|
505
|
+
}
|
|
506
|
+
generationKey(runId, messageId, partId, observedAt, newRequest = false) {
|
|
507
|
+
const execution = this.runner.execution.get(runId);
|
|
508
|
+
if (execution?.generationTiming === undefined)
|
|
509
|
+
return undefined;
|
|
510
|
+
execution.generationPartKeys ??= new Map();
|
|
511
|
+
const existing = execution.generationPartKeys.get(partId);
|
|
512
|
+
if (existing !== undefined)
|
|
513
|
+
return existing;
|
|
514
|
+
execution.generationRequestKeys ??= new Map();
|
|
515
|
+
let key = execution.generationRequestKeys.get(messageId);
|
|
516
|
+
if (newRequest && key !== undefined) {
|
|
517
|
+
execution.generationTiming.end(key, observedAt);
|
|
518
|
+
key = undefined;
|
|
519
|
+
}
|
|
520
|
+
if (key === undefined) {
|
|
521
|
+
const sequence = (execution.generationRequestSequence ?? 0) + 1;
|
|
522
|
+
execution.generationRequestSequence = sequence;
|
|
523
|
+
key = `${messageId}:generation:${sequence}`;
|
|
524
|
+
execution.generationRequestKeys.set(messageId, key);
|
|
525
|
+
}
|
|
526
|
+
execution.generationPartKeys.set(partId, key);
|
|
527
|
+
return key;
|
|
528
|
+
}
|
|
433
529
|
requestPermission(runId, properties) {
|
|
434
530
|
const execution = this.runner.execution.get(runId);
|
|
435
531
|
const requestId = typeof properties?.id === 'string' ? properties.id : undefined;
|
|
@@ -655,3 +751,21 @@ function record(value) {
|
|
|
655
751
|
? value
|
|
656
752
|
: undefined;
|
|
657
753
|
}
|
|
754
|
+
function isOpenCodeGenerationStart(value) {
|
|
755
|
+
return typeof record(value)?.start === 'number';
|
|
756
|
+
}
|
|
757
|
+
function isOpenCodeGenerationEnd(value) {
|
|
758
|
+
return typeof record(value)?.end === 'number';
|
|
759
|
+
}
|
|
760
|
+
function openCodeOutputTokens(value) {
|
|
761
|
+
const tokens = record(value);
|
|
762
|
+
const output = tokens?.output;
|
|
763
|
+
const reasoning = tokens?.reasoning;
|
|
764
|
+
if (!Number.isSafeInteger(output) ||
|
|
765
|
+
!Number.isSafeInteger(reasoning) ||
|
|
766
|
+
output < 0 ||
|
|
767
|
+
reasoning < 0 ||
|
|
768
|
+
output + reasoning > Number.MAX_SAFE_INTEGER)
|
|
769
|
+
return undefined;
|
|
770
|
+
return output + reasoning;
|
|
771
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export class GenerationTimingTracker {
|
|
2
|
+
now;
|
|
3
|
+
requests = new Map();
|
|
4
|
+
constructor(now = () => performance.now()) {
|
|
5
|
+
this.now = now;
|
|
6
|
+
}
|
|
7
|
+
timestamp() {
|
|
8
|
+
return this.now();
|
|
9
|
+
}
|
|
10
|
+
start(key, observedAt) {
|
|
11
|
+
const at = observedAt ?? this.now();
|
|
12
|
+
const request = this.requests.get(key) ?? {
|
|
13
|
+
started: false,
|
|
14
|
+
durationMs: 0,
|
|
15
|
+
ended: false,
|
|
16
|
+
settled: false,
|
|
17
|
+
invalid: false
|
|
18
|
+
};
|
|
19
|
+
this.observe(request, at);
|
|
20
|
+
if (request.ended || request.settled || request.invalid || request.runningAt !== undefined)
|
|
21
|
+
return false;
|
|
22
|
+
request.started = true;
|
|
23
|
+
request.runningAt = at;
|
|
24
|
+
this.requests.set(key, request);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
pause(key, observedAt) {
|
|
28
|
+
const request = this.requests.get(key);
|
|
29
|
+
if (request === undefined || request.settled)
|
|
30
|
+
return;
|
|
31
|
+
const at = observedAt ?? this.now();
|
|
32
|
+
this.observe(request, at);
|
|
33
|
+
if (request.runningAt === undefined)
|
|
34
|
+
return;
|
|
35
|
+
const duration = at - request.runningAt;
|
|
36
|
+
if (!(duration >= 0))
|
|
37
|
+
request.invalid = true;
|
|
38
|
+
else
|
|
39
|
+
request.durationMs += duration;
|
|
40
|
+
request.runningAt = undefined;
|
|
41
|
+
}
|
|
42
|
+
end(key, observedAt) {
|
|
43
|
+
const request = this.requests.get(key);
|
|
44
|
+
if (request === undefined || request.settled || request.ended)
|
|
45
|
+
return;
|
|
46
|
+
this.pause(key, observedAt);
|
|
47
|
+
request.ended = true;
|
|
48
|
+
}
|
|
49
|
+
settle(key, outputTokens, observedAt) {
|
|
50
|
+
const request = this.requests.get(key);
|
|
51
|
+
if (request === undefined ||
|
|
52
|
+
request.settled ||
|
|
53
|
+
!request.started ||
|
|
54
|
+
request.invalid ||
|
|
55
|
+
!Number.isSafeInteger(outputTokens) ||
|
|
56
|
+
outputTokens < 0)
|
|
57
|
+
return undefined;
|
|
58
|
+
this.end(key, observedAt);
|
|
59
|
+
request.settled = true;
|
|
60
|
+
if (request.invalid || request.durationMs < 1)
|
|
61
|
+
return undefined;
|
|
62
|
+
return {
|
|
63
|
+
outputTokens,
|
|
64
|
+
durationMs: request.durationMs,
|
|
65
|
+
tokensPerSecond: Math.round((outputTokens * 100_000) / request.durationMs) / 100
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
discard(key) {
|
|
69
|
+
this.requests.delete(key);
|
|
70
|
+
}
|
|
71
|
+
has(key) {
|
|
72
|
+
return this.requests.has(key);
|
|
73
|
+
}
|
|
74
|
+
observe(request, observedAt) {
|
|
75
|
+
if (!Number.isFinite(observedAt) ||
|
|
76
|
+
(request.lastAt !== undefined && observedAt < request.lastAt))
|
|
77
|
+
request.invalid = true;
|
|
78
|
+
if (Number.isFinite(observedAt))
|
|
79
|
+
request.lastAt = observedAt;
|
|
80
|
+
}
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.175",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"playwright": "1.62.1",
|
|
37
37
|
"ws": "^8.21.3",
|
|
38
38
|
"zod": "4.4.3",
|
|
39
|
-
"@myagentroam/agent": "0.9.
|
|
40
|
-
"@myagentroam/
|
|
41
|
-
"@myagentroam/
|
|
42
|
-
"@myagentroam/
|
|
39
|
+
"@myagentroam/agent": "0.9.175",
|
|
40
|
+
"@myagentroam/marctl": "0.9.123",
|
|
41
|
+
"@myagentroam/protocol": "0.9.175",
|
|
42
|
+
"@myagentroam/code-navigation": "0.9.129"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/ws": "^8.18.1"
|