@myagentroam/node 0.10.10 → 0.10.11

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.
@@ -12,6 +12,7 @@ const runtimeCredentialCapability = () => ({
12
12
  serviceTiers: ['auto', 'default', 'flex', 'priority', 'fast', 'ultrafast'],
13
13
  namespacedTools: true,
14
14
  responsesOptionalParameters: true,
15
+ responsesHttpContinuation: true,
15
16
  httpVersions: ['1.1', '2']
16
17
  });
17
18
  const workspaceDirectTransferCapability = () => ({
package/dist/connector.js CHANGED
@@ -698,6 +698,7 @@ export class NodeConnector {
698
698
  runtime: this.runtime,
699
699
  state: this.runState,
700
700
  codex: () => this.codexClient,
701
+ settleCodexUsage: (runId, turnId) => this.codexRunController.settleUsage(runId, turnId),
701
702
  claude: () => this.claudeClient,
702
703
  runners: this.runners,
703
704
  emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
@@ -975,7 +976,10 @@ export class NodeConnector {
975
976
  }
976
977
  },
977
978
  migrateQueue: (previousId, nextId) => this.continuousRunCoordinator.migrateSession(previousId, nextId),
978
- migrateRunnerState: (previousId, nextId) => this.runEventBridge.migrateRunnerSessionIdentity(previousId, nextId),
979
+ migrateRunnerState: (previousId, nextId) => {
980
+ this.modelUsage.promoteSession(previousId, nextId);
981
+ this.runEventBridge.migrateRunnerSessionIdentity(previousId, nextId);
982
+ },
979
983
  emitSession: (session) => this.emitSessionWorkbenchEvent({ session }),
980
984
  emitRun: (run) => this.emitWorkbenchEvent('run', { run }),
981
985
  emitQueue: (sessionId) => this.continuousRunCoordinator.publish(sessionId),
@@ -1,11 +1,96 @@
1
+ import { open, stat } from 'node:fs/promises';
2
+ import { StringDecoder } from 'node:string_decoder';
3
+ import { nodeLog } from '../../operational.js';
1
4
  import { RunnerUsageTracker, usageToken } from '../../service/runner-usage-tracker.js';
2
5
  export class CodexUsageNormalizer {
3
6
  requestedModel;
4
7
  tracker;
8
+ rollout;
9
+ pendingRead = Promise.resolve();
10
+ decoder = new StringDecoder('utf8');
11
+ partialLine = '';
12
+ oversizedLine = false;
5
13
  constructor(identity, publish, requestedModel) {
6
14
  this.requestedModel = requestedModel;
7
15
  this.tracker = new RunnerUsageTracker(identity, publish);
8
16
  }
17
+ async attachRollout(path, threadId) {
18
+ await this.pendingRead;
19
+ this.decoder = new StringDecoder('utf8');
20
+ this.partialLine = '';
21
+ this.oversizedLine = false;
22
+ try {
23
+ // Anchor before dispatch. Inherited history and earlier requests never become new usage.
24
+ this.rollout = { path, threadId, offset: (await stat(path)).size };
25
+ }
26
+ catch {
27
+ this.reportReadFailure();
28
+ }
29
+ }
30
+ get hasRollout() {
31
+ return this.rollout !== undefined;
32
+ }
33
+ flushRollout(turnId, compact) {
34
+ this.pendingRead = this.pendingRead
35
+ .then(async () => {
36
+ const rollout = this.rollout;
37
+ if (!rollout)
38
+ return;
39
+ const file = await open(rollout.path, 'r');
40
+ try {
41
+ const end = (await file.stat()).size;
42
+ if (end < rollout.offset || end - rollout.offset > 16 * 1024 * 1024)
43
+ throw new Error('USAGE_NATIVE_READ_LIMIT');
44
+ const buffer = Buffer.alloc(64 * 1024);
45
+ while (rollout.offset < end) {
46
+ const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, end - rollout.offset), rollout.offset);
47
+ if (!bytesRead)
48
+ break;
49
+ rollout.offset += bytesRead;
50
+ this.consumeRollout(this.decoder.write(buffer.subarray(0, bytesRead)), turnId, compact);
51
+ }
52
+ }
53
+ finally {
54
+ await file.close();
55
+ }
56
+ })
57
+ .catch(() => this.reportReadFailure());
58
+ return this.pendingRead;
59
+ }
60
+ reportReadFailure() {
61
+ this.rollout = undefined;
62
+ nodeLog('model.usage.unavailable', { runner: 'codex', code: 'USAGE_NATIVE_READ_FAILED' });
63
+ }
64
+ consumeRollout(text, turnId, compact) {
65
+ for (const part of text.split(/(?<=\n)/u)) {
66
+ if (!this.oversizedLine)
67
+ this.partialLine += part;
68
+ if (this.partialLine.length > 128 * 1024) {
69
+ this.partialLine = '';
70
+ this.oversizedLine = true;
71
+ }
72
+ if (!part.endsWith('\n'))
73
+ continue;
74
+ if (!this.oversizedLine) {
75
+ try {
76
+ const value = JSON.parse(this.partialLine);
77
+ if (isPlainRecord(value) && value.type === 'token_usage_record') {
78
+ const receipt = isPlainRecord(value.payload) ? value.payload : undefined;
79
+ if (receipt?.thread_id === this.rollout?.threadId && receipt?.turn_id === turnId)
80
+ this.observe('rawResponse/completed', {
81
+ responseId: receipt.response_id,
82
+ usage: receipt.usage
83
+ }, compact);
84
+ }
85
+ }
86
+ catch {
87
+ // Non-receipt and malformed native lines cannot turn into estimated usage.
88
+ }
89
+ }
90
+ this.partialLine = '';
91
+ this.oversizedLine = false;
92
+ }
93
+ }
9
94
  observe(method, params, compact) {
10
95
  // TokenCount.last can replay context/compaction snapshots without a request identity.
11
96
  // Only raw response completions are independently additive request facts.
@@ -15,10 +100,13 @@ export class CodexUsageNormalizer {
15
100
  const inputTokens = usageToken(usage.inputTokens ?? usage.input_tokens);
16
101
  const outputTokens = usageToken(usage.outputTokens ?? usage.output_tokens);
17
102
  const cacheReadTokens = usageToken(usage.cachedInputTokens ??
103
+ usage.cached_input_tokens ??
18
104
  (isPlainRecord(usage.input_tokens_details)
19
105
  ? usage.input_tokens_details.cached_tokens
20
106
  : undefined));
107
+ const cacheWriteTokens = usageToken(usage.cacheWriteInputTokens ?? usage.cache_write_input_tokens);
21
108
  const reasoningTokens = usageToken(usage.reasoningOutputTokens ??
109
+ usage.reasoning_output_tokens ??
22
110
  (isPlainRecord(usage.output_tokens_details)
23
111
  ? usage.output_tokens_details.reasoning_tokens
24
112
  : undefined));
@@ -31,6 +119,7 @@ export class CodexUsageNormalizer {
31
119
  ...(inputTokens === undefined ? {} : { inputTokens }),
32
120
  ...(outputTokens === undefined ? {} : { outputTokens }),
33
121
  ...(cacheReadTokens === undefined ? {} : { cacheReadTokens }),
122
+ ...(cacheWriteTokens === undefined ? {} : { cacheWriteTokens }),
34
123
  ...(reasoningTokens === undefined ? {} : { reasoningTokens })
35
124
  };
36
125
  this.tracker.snapshot(params.responseId, values, true, 'partial');
@@ -29,6 +29,7 @@ export class CodexManagedRunController {
29
29
  runner;
30
30
  host;
31
31
  usage = new Map();
32
+ finishing = new Set();
32
33
  constructor(runner, host) {
33
34
  this.runner = runner;
34
35
  this.host = host;
@@ -103,7 +104,8 @@ export class CodexManagedRunController {
103
104
  });
104
105
  this.host.emit(runId, 'run.started', { command: 'compact' }, 'STARTING');
105
106
  try {
106
- await this.runner.resumeThread(threadId, session.cwd, undefined, runId);
107
+ const resumed = await this.runner.resumeThread(threadId, session.cwd, undefined, runId);
108
+ await this.attachUsageRollout(runId, resumed);
107
109
  await this.runner.request('thread/compact/start', { threadId }, runId);
108
110
  return {
109
111
  command: 'compact',
@@ -140,12 +142,9 @@ export class CodexManagedRunController {
140
142
  if (run === undefined)
141
143
  return;
142
144
  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);
145
+ const usage = this.usageFor(runId);
146
+ const compactUsage = activeRun.refreshTitle === false || activeRun.generationCompacting === true;
147
+ usage.observe(notification.method, params, compactUsage);
149
148
  const receiptAt = activeRun.generationTiming?.timestamp();
150
149
  if (notification.method === 'turn/started' && turnId !== undefined) {
151
150
  const active = this.runner.execution.runs.get(runId);
@@ -161,6 +160,8 @@ export class CodexManagedRunController {
161
160
  return;
162
161
  }
163
162
  if (notification.method === 'thread/tokenUsage/updated') {
163
+ if (turnId !== undefined)
164
+ void usage.flushRollout(turnId, compactUsage);
164
165
  this.completeStandardUsage(runId, params, activeRun, receiptAt);
165
166
  return;
166
167
  }
@@ -325,6 +326,8 @@ export class CodexManagedRunController {
325
326
  return;
326
327
  }
327
328
  if (notification.method === 'turn/completed') {
329
+ if (this.finishing.has(runId))
330
+ return;
328
331
  this.endPendingGenerations(activeRun, receiptAt);
329
332
  const status = turn?.status;
330
333
  const terminal = status === 'completed' ? 'SUCCEEDED' : status === 'interrupted' ? 'INTERRUPTED' : 'FAILED';
@@ -333,21 +336,60 @@ export class CodexManagedRunController {
333
336
  const failure = terminal === 'FAILED'
334
337
  ? runnerErrorProjection(turn?.error ?? turn, 'CODEX_RUN_FAILED')
335
338
  : { status: status ?? 'unknown' };
336
- this.host.emit(runId, terminal === 'FAILED' ? 'run.failed' : 'run.completed', failure, terminal);
337
- const active = this.runner.execution.runs.get(runId);
338
- const titleRefresh = active === undefined ||
339
- active.refreshTitle === false ||
340
- this.host.session(active.sessionId)?.titleSource !== 'AUTO'
341
- ? undefined
342
- : this.host.syncTitle(active.sessionId, active.threadId, active.cwd);
343
- this.usage.delete(runId);
344
- this.runner.execution.runs.delete(runId);
345
- if (titleRefresh === undefined)
346
- this.runner.releaseEnvironment(runId);
339
+ const finish = () => {
340
+ if (!this.runner.execution.runs.has(runId)) {
341
+ this.finishing.delete(runId);
342
+ return;
343
+ }
344
+ this.host.emit(runId, terminal === 'FAILED' ? 'run.failed' : 'run.completed', failure, terminal);
345
+ const active = this.runner.execution.runs.get(runId);
346
+ const titleRefresh = active === undefined ||
347
+ active.refreshTitle === false ||
348
+ this.host.session(active.sessionId)?.titleSource !== 'AUTO'
349
+ ? undefined
350
+ : this.host.syncTitle(active.sessionId, active.threadId, active.cwd);
351
+ this.usage.delete(runId);
352
+ this.runner.execution.runs.delete(runId);
353
+ if (titleRefresh === undefined)
354
+ this.runner.releaseEnvironment(runId);
355
+ else
356
+ void titleRefresh.finally(() => this.runner.releaseEnvironment(runId));
357
+ this.host.removeActive(runId);
358
+ this.finishing.delete(runId);
359
+ };
360
+ if (usage.hasRollout && turnId !== undefined) {
361
+ this.finishing.add(runId);
362
+ void this.settleUsage(runId, turnId).then(finish);
363
+ }
347
364
  else
348
- void titleRefresh.finally(() => this.runner.releaseEnvironment(runId));
349
- this.host.removeActive(runId);
365
+ finish();
366
+ }
367
+ }
368
+ async settleUsage(runId, turnId) {
369
+ const usage = this.usage.get(runId);
370
+ const active = this.runner.execution.runs.get(runId);
371
+ if (usage) {
372
+ await usage.flushRollout(turnId, active?.refreshTitle === false || active?.generationCompacting === true);
373
+ if (this.usage.get(runId) === usage)
374
+ this.usage.delete(runId);
375
+ }
376
+ }
377
+ usageFor(runId) {
378
+ let usage = this.usage.get(runId);
379
+ if (!usage) {
380
+ const active = this.runner.execution.runs.get(runId);
381
+ usage = new CodexUsageNormalizer(runId, (value) => this.host.emit(runId, 'run.usage', {
382
+ ...value,
383
+ sessionId: this.runner.execution.runs.get(runId)?.threadId
384
+ }), active ? (this.host.session(active.sessionId)?.model ?? undefined) : undefined);
385
+ this.usage.set(runId, usage);
350
386
  }
387
+ return usage;
388
+ }
389
+ async attachUsageRollout(runId, result) {
390
+ const thread = isPlainRecord(result) && isPlainRecord(result.thread) ? result.thread : undefined;
391
+ if (typeof thread?.path === 'string' && typeof thread.id === 'string')
392
+ await this.usageFor(runId).attachRollout(thread.path, thread.id);
351
393
  }
352
394
  observeResponse(runId, itemId, incremental = false) {
353
395
  const latencyMs = this.runner.execution.runs
@@ -629,6 +671,7 @@ export class CodexManagedRunController {
629
671
  generationResponseIds: new Map(),
630
672
  completedGenerationResponseIds: new Set()
631
673
  });
674
+ await this.attachUsageRollout(runId, threadResult);
632
675
  turnResult = await this.runner.startTurn(threadId, input, cwd, turnConfiguration, materializedAttachments, runId);
633
676
  break;
634
677
  }
@@ -36,6 +36,11 @@ export class NodeModelUsageService {
36
36
  release(runId) {
37
37
  this.bindings.delete(runId);
38
38
  }
39
+ promoteSession(previousId, nextId) {
40
+ for (const [runId, binding] of this.bindings)
41
+ if (binding.sessionId === previousId)
42
+ this.bindings.set(runId, { ...binding, sessionId: nextId });
43
+ }
39
44
  clear() {
40
45
  this.bindings.clear();
41
46
  }
@@ -147,16 +147,22 @@ export class RunnerInteractionService {
147
147
  }
148
148
  async cancelCodexTurn(runId, threadId, turnId, cwd, terminalStatus) {
149
149
  const codex = this.options.codex();
150
+ let failed = false;
150
151
  try {
151
152
  await codex.interruptTurn(threadId, turnId, runId);
152
- const run = this.options.runtime.getRun(runId);
153
- if (run !== undefined && !isTerminalRunStatus(run.status))
154
- this.options.emitRun(runId, 'run.completed', { status: terminalStatus }, terminalStatus);
155
153
  }
156
154
  catch {
155
+ failed = true;
156
+ }
157
+ try {
158
+ await this.options.settleCodexUsage(runId, turnId);
157
159
  const run = this.options.runtime.getRun(runId);
158
- if (run !== undefined && !isTerminalRunStatus(run.status))
159
- this.options.emitRun(runId, 'run.failed', { code: 'CODEX_INTERRUPT_FAILED' }, 'FAILED');
160
+ if (run !== undefined && !isTerminalRunStatus(run.status)) {
161
+ if (failed)
162
+ this.options.emitRun(runId, 'run.failed', { code: 'CODEX_INTERRUPT_FAILED' }, 'FAILED');
163
+ else
164
+ this.options.emitRun(runId, 'run.completed', { status: terminalStatus }, terminalStatus);
165
+ }
160
166
  }
161
167
  finally {
162
168
  codex.execution.runs.delete(runId);
@@ -198,6 +198,10 @@ export class SessionLifecycleService {
198
198
  sessionId: bootstrapId,
199
199
  commandId: value.commandId,
200
200
  input: value.input,
201
+ ...(input.__requestUserId === undefined
202
+ ? {}
203
+ : { __requestUserId: input.__requestUserId }),
204
+ ...(input.__usageGrant === undefined ? {} : { __usageGrant: input.__usageGrant }),
201
205
  ...(input.secretEnvironment === undefined
202
206
  ? {}
203
207
  : { secretEnvironment: input.secretEnvironment })
@@ -211,6 +215,7 @@ export class SessionLifecycleService {
211
215
  clientMessageId: input.clientMessageId,
212
216
  content: input.content,
213
217
  ...(input.__requestUserId === undefined ? {} : { __requestUserId: input.__requestUserId }),
218
+ ...(input.__usageGrant === undefined ? {} : { __usageGrant: input.__usageGrant }),
214
219
  ...(input.secretEnvironment === undefined
215
220
  ? {}
216
221
  : { secretEnvironment: input.secretEnvironment }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.10.10",
3
+ "version": "0.10.11",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -37,9 +37,9 @@
37
37
  "playwright": "1.62.1",
38
38
  "ws": "^8.21.3",
39
39
  "zod": "4.4.3",
40
- "@myagentroam/agent": "0.10.10",
40
+ "@myagentroam/agent": "0.10.11",
41
41
  "@myagentroam/code-navigation": "0.10.1",
42
- "@myagentroam/protocol": "0.10.10",
42
+ "@myagentroam/protocol": "0.10.11",
43
43
  "@myagentroam/marctl": "0.10.2"
44
44
  },
45
45
  "devDependencies": {