@myagentroam/agent 0.11.57 → 0.11.59

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.
@@ -25,6 +25,9 @@ export interface AgentSessionInputReceipt {
25
25
  }
26
26
  export interface AgentSessionContextUsage {
27
27
  inputTokens: number;
28
+ gcCount: number;
29
+ /** Measured input decreases since Compact; undefined when GC usage is not yet paired. */
30
+ gcReclaimedTokens?: number;
28
31
  }
29
32
  export interface AgentSessionDeleteResult {
30
33
  readonly deleted: boolean;
@@ -1,6 +1,7 @@
1
1
  import { AgentError } from '../error.js';
2
2
  import { publicHostedToolEvents } from '../runtime/event-projection.js';
3
3
  import { appliedInputRecord } from './deferred-input.js';
4
+ import { compactSummaryFromCheckpoint } from './record-projection.js';
4
5
  const MAX_INPUT_IMAGE_BYTES = 5 * 1024 * 1024;
5
6
  const INPUT_IMAGE_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/gif', 'image/webp']);
6
7
  export function createSessionCatalog(store, borrowed) {
@@ -182,20 +183,102 @@ export function createSessionCatalog(store, borrowed) {
182
183
  contextUsage: async (sessionId) => {
183
184
  assertOpen();
184
185
  const page = await store.readPage(sessionId, { limit: 1000 });
186
+ let inputTokens;
185
187
  for (const record of page.records.toReversed()) {
186
188
  if (!isAgentEvent(record.payload))
187
189
  continue;
188
- if (record.payload.type === 'context.compacted')
189
- return record.payload.retainedContext === true ? undefined : { inputTokens: 0 };
190
+ if (record.payload.type === 'context.compacted') {
191
+ if (record.payload.retainedContext === true)
192
+ return undefined;
193
+ inputTokens = 0;
194
+ break;
195
+ }
190
196
  if (record.payload.type === 'usage.updated' && record.payload.purpose !== 'compact') {
191
- const inputTokens = record.payload.contextInputTokens ?? record.payload.inputTokens;
192
- if (typeof inputTokens === 'number' &&
193
- Number.isSafeInteger(inputTokens) &&
194
- inputTokens >= 0)
195
- return { inputTokens };
197
+ const tokens = record.payload.contextInputTokens ?? record.payload.inputTokens;
198
+ if (typeof tokens === 'number' && Number.isSafeInteger(tokens) && tokens >= 0) {
199
+ inputTokens = tokens;
200
+ break;
201
+ }
196
202
  }
197
203
  }
198
- return undefined;
204
+ if (inputTokens === undefined)
205
+ return undefined;
206
+ const usage = { inputTokens };
207
+ let gcCount = 0;
208
+ let gcReclaimedTokens = 0;
209
+ let tokensKnown = true;
210
+ let nextInputTokens;
211
+ let nextModelId;
212
+ let nextExecutionId;
213
+ let requestInputTokens;
214
+ const statistics = () => ({
215
+ ...usage,
216
+ gcCount,
217
+ ...(tokensKnown ? { gcReclaimedTokens } : {})
218
+ });
219
+ let countPage = page;
220
+ for (;;) {
221
+ for (const record of countPage.records.toReversed()) {
222
+ if (compactSummaryFromCheckpoint(record) !== undefined)
223
+ return statistics();
224
+ if (record.type === 'execution.header' &&
225
+ record.executionId === nextExecutionId &&
226
+ isRecord(record.payload))
227
+ nextModelId =
228
+ typeof record.payload.modelId === 'string' ? record.payload.modelId : undefined;
229
+ if (isAgentEvent(record.payload) &&
230
+ record.payload.type === 'usage.updated' &&
231
+ record.payload.purpose !== 'compact') {
232
+ const tokens = record.payload.contextInputTokens ?? record.payload.inputTokens;
233
+ if (typeof tokens === 'number' && Number.isSafeInteger(tokens) && tokens >= 0) {
234
+ requestInputTokens ??= tokens;
235
+ nextInputTokens = tokens;
236
+ nextModelId = undefined;
237
+ nextExecutionId = record.executionId;
238
+ }
239
+ }
240
+ if (record.type === 'model.attempt' &&
241
+ isRecord(record.payload) &&
242
+ record.payload.state === 'STARTED' &&
243
+ record.payload.purpose === 'EXECUTION') {
244
+ // Reverse traversal preserves the last usage update within each request.
245
+ if (requestInputTokens !== undefined)
246
+ nextInputTokens = requestInputTokens;
247
+ requestInputTokens = undefined;
248
+ }
249
+ if (record.type === 'context.gc.completed' &&
250
+ isRecord(record.payload) &&
251
+ typeof record.payload.generation === 'number' &&
252
+ Number.isSafeInteger(record.payload.generation) &&
253
+ record.payload.generation > 0) {
254
+ gcCount ||= record.payload.generation;
255
+ const trigger = isRecord(record.payload.trigger) ? record.payload.trigger : undefined;
256
+ const before = trigger?.contextInputTokens;
257
+ if (typeof trigger?.usageSequence === 'number' &&
258
+ trigger.usageSequence > 0 &&
259
+ typeof before === 'number' &&
260
+ Number.isSafeInteger(before) &&
261
+ before >= 0 &&
262
+ nextInputTokens !== undefined &&
263
+ (nextModelId === undefined || nextModelId === trigger.modelId))
264
+ gcReclaimedTokens += Math.max(0, before - nextInputTokens);
265
+ else
266
+ tokensKnown = false;
267
+ nextInputTokens = undefined;
268
+ nextModelId = undefined;
269
+ nextExecutionId = undefined;
270
+ requestInputTokens = undefined;
271
+ if (record.payload.generation === 1)
272
+ return statistics();
273
+ }
274
+ }
275
+ if (countPage.previousBeforeSequence === undefined)
276
+ return statistics();
277
+ countPage = await store.readPage(sessionId, {
278
+ limit: 1000,
279
+ beforeSequence: countPage.previousBeforeSequence
280
+ });
281
+ }
199
282
  },
200
283
  fork: async (sessionId, options) => {
201
284
  assertOpen();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.11.57",
3
+ "version": "0.11.59",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",