@myagentroam/agent 0.10.30 → 0.10.31

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.
@@ -10,6 +10,7 @@ export interface ModelInputMetadata {
10
10
  recordId?: string;
11
11
  extensionId?: string;
12
12
  truncated?: true;
13
+ smartCompaction?: true;
13
14
  }
14
15
  /** SDK-owned provenance. Text labels never grant instruction authority. */
15
16
  export interface ModelContextualBlock {
@@ -1771,6 +1771,8 @@ function strictFunctionSchema(value, optional = false) {
1771
1771
  return value;
1772
1772
  const schema = value;
1773
1773
  const supportedSchema = Object.fromEntries(Object.entries(schema).filter(([keyword]) => keyword !== 'propertyNames' && keyword !== 'uniqueItems'));
1774
+ if (optional && Array.isArray(schema.enum) && !schema.enum.includes(null))
1775
+ supportedSchema.enum = [...schema.enum, null];
1774
1776
  if (schema.type === 'object') {
1775
1777
  const properties = record(schema.properties);
1776
1778
  const required = new Set(Array.isArray(schema.required) ? schema.required : []);
@@ -1795,11 +1797,13 @@ function strictFunctionSchema(value, optional = false) {
1795
1797
  return supportedSchema;
1796
1798
  if (typeof schema.type === 'string')
1797
1799
  return { ...supportedSchema, type: [schema.type, 'null'] };
1798
- if (Array.isArray(schema.enum))
1800
+ if (Array.isArray(schema.type))
1799
1801
  return {
1800
1802
  ...supportedSchema,
1801
- enum: schema.enum.includes(null) ? schema.enum : [...schema.enum, null]
1803
+ type: schema.type.includes('null') ? schema.type : [...schema.type, 'null']
1802
1804
  };
1805
+ if (Array.isArray(schema.enum))
1806
+ return supportedSchema;
1803
1807
  return { anyOf: [supportedSchema, { type: 'null' }] };
1804
1808
  }
1805
1809
  function responseCitations(response) {
@@ -0,0 +1,14 @@
1
+ import type { ModelMessage } from '../../model/contracts.js';
2
+ import { type SessionRecord } from '../../session/jsonl-store.js';
3
+ export declare const SMART_COMPACTION_MARKER = "<mar_runtime>\n<smart_compaction scope=\"this_message\">eligible</smart_compaction>\n</mar_runtime>";
4
+ export interface SmartCompactionInput {
5
+ readonly recordId: string;
6
+ readonly eligible: boolean;
7
+ requested: boolean;
8
+ error?: unknown;
9
+ }
10
+ /** A measured watermark, not the stricter native-request budget calibration. */
11
+ export declare function latestSmartContextTokens(records: readonly SessionRecord[], modelId: string, contextWindowTokens: number): number | undefined;
12
+ /** Only provider views receive the marker; durable message content stays unchanged. */
13
+ export declare function projectSmartCompactionMessage(message: ModelMessage): ModelMessage;
14
+ export declare function requestSmartCompaction(input: SmartCompactionInput | undefined, start: () => void): void;
@@ -0,0 +1,59 @@
1
+ import { MarAgentError } from '../../error.js';
2
+ import { compactSummaryFromCheckpoint } from '../../session/jsonl-store.js';
3
+ export const SMART_COMPACTION_MARKER = '<mar_runtime>\n<smart_compaction scope="this_message">eligible</smart_compaction>\n</mar_runtime>';
4
+ /** A measured watermark, not the stricter native-request budget calibration. */
5
+ export function latestSmartContextTokens(records, modelId, contextWindowTokens) {
6
+ let tokens;
7
+ let matchingModel = false;
8
+ for (const record of records) {
9
+ const payload = record.payload;
10
+ if (compactSummaryFromCheckpoint(record) !== undefined) {
11
+ tokens = undefined;
12
+ // A sliced checkpoint does not prove the input window used by later usage.
13
+ matchingModel = false;
14
+ }
15
+ if (record.type === 'context.gc.completed')
16
+ tokens = undefined;
17
+ if (record.type === 'execution.header' && payload.mode !== 'compact') {
18
+ matchingModel =
19
+ payload.modelId === modelId && payload.contextWindowTokens === contextWindowTokens;
20
+ if (!matchingModel)
21
+ tokens = undefined;
22
+ }
23
+ if (matchingModel && payload.type === 'usage.updated' && payload.purpose !== 'compact') {
24
+ const value = payload.contextInputTokens ?? payload.inputTokens;
25
+ if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0)
26
+ tokens = value;
27
+ }
28
+ }
29
+ return tokens;
30
+ }
31
+ /** Only provider views receive the marker; durable message content stays unchanged. */
32
+ export function projectSmartCompactionMessage(message) {
33
+ if (message.role !== 'user' ||
34
+ message.contextKind !== undefined ||
35
+ !message.metadata?.smartCompaction)
36
+ return message;
37
+ const suffix = '\n\n' + SMART_COMPACTION_MARKER;
38
+ return {
39
+ ...message,
40
+ content: message.content + suffix,
41
+ ...(message.textBlocks ? { textBlocks: [...message.textBlocks, { text: suffix }] } : {})
42
+ };
43
+ }
44
+ export function requestSmartCompaction(input, start) {
45
+ if (!input?.eligible)
46
+ throw new MarAgentError('MAR_AGENT_SMART_COMPACT_DENIED', 'The current user input is not eligible for Smart Compaction.');
47
+ if ('error' in input)
48
+ throw input.error;
49
+ if (input.requested)
50
+ return;
51
+ input.requested = true;
52
+ try {
53
+ start();
54
+ }
55
+ catch (error) {
56
+ input.error = error;
57
+ throw error;
58
+ }
59
+ }
@@ -4,6 +4,7 @@ export declare const AGENT_EXECUTION_POLICY: Readonly<{
4
4
  modelToolOutputTokens: 10000;
5
5
  automaticCompactionPercent: 90;
6
6
  backgroundCompactionPercent: 85;
7
+ smartCompactionPercent: 60;
7
8
  subagentVerificationCharacters: 500;
8
9
  estimatedTokensPerImage: 1000;
9
10
  }>;
@@ -4,6 +4,7 @@ export const AGENT_EXECUTION_POLICY = Object.freeze({
4
4
  modelToolOutputTokens: 10_000,
5
5
  automaticCompactionPercent: 90,
6
6
  backgroundCompactionPercent: 85,
7
+ smartCompactionPercent: 60,
7
8
  subagentVerificationCharacters: 500,
8
9
  estimatedTokensPerImage: 1_000
9
10
  });
@@ -41,6 +41,7 @@ import { ContextGcRuntime } from '../runtime/context/gc-runtime.js';
41
41
  import { AGENT_EXECUTION_POLICY } from '../runtime/execution-policy.js';
42
42
  import { loadAgentInstructions, renderAgentInstructions } from '../runtime/context/instructions.js';
43
43
  import { normalizeMessagesForModel } from '../runtime/context/normalize.js';
44
+ import { latestSmartContextTokens, projectSmartCompactionMessage, requestSmartCompaction } from '../runtime/compaction/smart.js';
44
45
  import { compactSummaryFromCheckpoint, inputMetadataFromRecord } from '../session/jsonl-store.js';
45
46
  import { SubagentSessionController } from '../subagent/session-controller.js';
46
47
  import { agentStartToolDefinitionWithModels } from '../tools/agent/agent-start.js';
@@ -417,6 +418,7 @@ export function createSessionOpener(services) {
417
418
  pendingAppends.length = 0;
418
419
  immediateTarget = messages;
419
420
  let recordedEnvironment = restored.environmentState;
421
+ let smartInput;
420
422
  let compactContext;
421
423
  let startCompact;
422
424
  const writeInputRecord = async (content, type, metadata, call, committed) => {
@@ -425,6 +427,16 @@ export function createSessionOpener(services) {
425
427
  throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'Selected model does not support image input.');
426
428
  await withRecordProjection(async () => {
427
429
  call.signal.throwIfAborted();
430
+ const smartPercent = smartContextTokens === undefined
431
+ ? undefined
432
+ : (smartContextTokens * 100) / selectedModel.contextWindowTokens;
433
+ const smartCompaction = mode !== 'compact' &&
434
+ sessionSource.type === 'user' &&
435
+ type === 'turn.user' &&
436
+ metadata.inputSource === undefined &&
437
+ smartPercent !== undefined &&
438
+ smartPercent >= AGENT_EXECUTION_POLICY.smartCompactionPercent &&
439
+ smartPercent < AGENT_EXECUTION_POLICY.automaticCompactionPercent;
428
440
  const images = content.images?.length
429
441
  ? await store.persistInputImages(sessionId, content.images)
430
442
  : [];
@@ -433,6 +445,7 @@ export function createSessionOpener(services) {
433
445
  payload: {
434
446
  ...metadata,
435
447
  prompt: content.text,
448
+ ...(smartCompaction ? { smartCompaction: true } : {}),
436
449
  ...(content.modelText === undefined ? {} : { modelText: content.modelText }),
437
450
  ...(content.context === undefined ? {} : { context: content.context }),
438
451
  ...(content.modelContext === undefined
@@ -444,6 +457,11 @@ export function createSessionOpener(services) {
444
457
  turnId
445
458
  }, { signal: call.signal });
446
459
  const inputMetadata = inputMetadataFromRecord(record);
460
+ smartInput = {
461
+ recordId: record.recordId,
462
+ eligible: smartCompaction,
463
+ requested: false
464
+ };
447
465
  compactionUserMessages.push({
448
466
  content: content.modelText ?? content.text,
449
467
  metadata: inputMetadata
@@ -549,6 +567,7 @@ export function createSessionOpener(services) {
549
567
  const selected = createModelTurnSession(selectedAdapter);
550
568
  selectedTurn = selected;
551
569
  let lastServerUsage = latestModelUsage(contextRecords, selectedModel.id);
570
+ let smartContextTokens = latestSmartContextTokens(contextRecords, selectedModel.id, selectedModel.contextWindowTokens);
552
571
  const compactUserTokenLimit = Math.min(20_000, Math.floor(selectedModel.contextWindowTokens / 8));
553
572
  const reasoningEffort = input.reasoningEffort ?? selectedModel.defaultReasoningEffort;
554
573
  const useEffortUpdates = usesResponsesConfigurationUpdates(selectedModel);
@@ -887,8 +906,24 @@ export function createSessionOpener(services) {
887
906
  const parentFrame = subagents === undefined || mode === 'compact'
888
907
  ? undefined
889
908
  : subagents.captureParentFrame(executionId);
909
+ const roundSmartInput = smartInput;
890
910
  const toolContext = {
891
911
  host: host,
912
+ requestSmartCompact: () => {
913
+ controller.signal.throwIfAborted();
914
+ requestSmartCompaction(roundSmartInput === smartInput ? roundSmartInput : undefined, () => {
915
+ // The Session owns this background operation, not the short-lived tool callback.
916
+ services.extensions.operations.enter({
917
+ sessionId,
918
+ sessionEpoch: coreExtensions.epoch,
919
+ executionId,
920
+ turnId,
921
+ actor: 'sdk',
922
+ entry: 'native-api',
923
+ signal: controller.signal
924
+ }, () => startCompact({ async: true }));
925
+ });
926
+ },
892
927
  permissionPolicy,
893
928
  writeToolResultResource: (text) => store.writeToolResultResource(sessionId, text),
894
929
  todo,
@@ -1185,7 +1220,7 @@ export function createSessionOpener(services) {
1185
1220
  (entry.metadata?.recordId
1186
1221
  ? message.metadata?.recordId === entry.metadata.recordId
1187
1222
  : message.content === entry.content)));
1188
- const snapshotTools = structuredClone(executionTools);
1223
+ const snapshotTools = structuredClone(projectTools(executionTools));
1189
1224
  const generation = contextualWorldState.generation;
1190
1225
  const snapshotUsage = lastServerUsage;
1191
1226
  const snapshotEffort = requestReasoningEffort ?? reasoningEffort;
@@ -1201,7 +1236,7 @@ export function createSessionOpener(services) {
1201
1236
  const compactSystemPrompt = compactComposition.system;
1202
1237
  const projectedHistory = normalizeMessagesForModel(compactHistory, {
1203
1238
  supportsImages: false
1204
- });
1239
+ }).map(projectSmartCompactionMessage);
1205
1240
  const nativeCompactMessages = buildNativeCompactionMessages(projectedHistory, {
1206
1241
  ...(input.focus === undefined ? {} : { focus: input.focus })
1207
1242
  });
@@ -1358,6 +1393,7 @@ export function createSessionOpener(services) {
1358
1393
  await resetCompactWorldState(compactRecord.recordId, { resources: compactResources });
1359
1394
  lastServerContextTokens = undefined;
1360
1395
  lastServerUsage = undefined;
1396
+ smartContextTokens = undefined;
1361
1397
  requestReasoningEffort = reasoningEffort;
1362
1398
  effectiveReasoningEffort = reasoningEffort;
1363
1399
  await emit({
@@ -1371,7 +1407,7 @@ export function createSessionOpener(services) {
1371
1407
  };
1372
1408
  startCompact = (input = {}) => {
1373
1409
  const id = manualCompactionId ?? randomUUID();
1374
- return compactCoordinator.start(id, input.focus, async (prepare, signal) => {
1410
+ const flight = compactCoordinator.start(id, input.focus, async (prepare, signal) => {
1375
1411
  const linked = AbortSignal.any([signal, controller.signal, compactSignal]);
1376
1412
  try {
1377
1413
  if (mode === 'compact' && inputs.currentCompact())
@@ -1393,6 +1429,9 @@ export function createSessionOpener(services) {
1393
1429
  throw error;
1394
1430
  }
1395
1431
  });
1432
+ if (smartInput)
1433
+ smartInput.requested = true;
1434
+ return flight;
1396
1435
  };
1397
1436
  compactContext = async (input = {}) => {
1398
1437
  const flight = startCompact(input);
@@ -1576,10 +1615,12 @@ export function createSessionOpener(services) {
1576
1615
  },
1577
1616
  observeContextTokens: (tokens) => {
1578
1617
  lastServerContextTokens = tokens;
1618
+ smartContextTokens = tokens;
1579
1619
  },
1580
1620
  observeUsage: (usage) => {
1581
1621
  if (usage !== undefined) {
1582
1622
  lastServerContextTokens = usage.contextInputTokens;
1623
+ smartContextTokens = usage.contextInputTokens;
1583
1624
  lastServerUsage = { ...usage, historyMessages: [...messages] };
1584
1625
  }
1585
1626
  },
@@ -1622,6 +1663,7 @@ export function createSessionOpener(services) {
1622
1663
  }
1623
1664
  });
1624
1665
  if (committed) {
1666
+ smartContextTokens = undefined;
1625
1667
  selected.resetContinuation?.();
1626
1668
  const refreshed = await restoreMessages(store, sessionId, committed.records, {
1627
1669
  includeImages: true,
@@ -2070,7 +2112,7 @@ function projectSessionRequestMessages(messages, entries, supportsImages, codeMo
2070
2112
  supportsImages
2071
2113
  }).map((message) => projectCodeModeSearchResult(autoPermission && message.toolSearchTools
2072
2114
  ? { ...message, toolSearchTools: message.toolSearchTools.map(projectPermissionTool) }
2073
- : message, codeMode));
2115
+ : projectSmartCompactionMessage(message), codeMode));
2074
2116
  }
2075
2117
  function projectAppendedModelMessage(input) {
2076
2118
  if (input.visibility === 'transcript')
@@ -2527,7 +2527,8 @@ export function parseModelInputMetadata(value) {
2527
2527
  ...(typeof metadata.extensionId === 'string' && metadata.extensionId.length <= 128
2528
2528
  ? { extensionId: metadata.extensionId }
2529
2529
  : {}),
2530
- ...(metadata.truncated === true ? { truncated: true } : {})
2530
+ ...(metadata.truncated === true ? { truncated: true } : {}),
2531
+ ...(metadata.smartCompaction === true ? { smartCompaction: true } : {})
2531
2532
  };
2532
2533
  }
2533
2534
  export function inputMetadataFromRecord(record) {
@@ -2540,6 +2541,7 @@ export function inputMetadataFromRecord(record) {
2540
2541
  ? 'parent_agent'
2541
2542
  : 'user',
2542
2543
  recordId: record.recordId,
2544
+ ...(payload.smartCompaction === true ? { smartCompaction: true } : {}),
2543
2545
  ...(typeof source?.extensionId === 'string' && source.extensionId.length <= 128
2544
2546
  ? { extensionId: source.extensionId }
2545
2547
  : {})
@@ -0,0 +1,9 @@
1
+ import type { ClientToolDefinition } from '../../model/contracts.js';
2
+ import type { ToolRuntimeContext } from '../runtime-context.js';
3
+ export declare const smartCompactToolDefinition: ClientToolDefinition;
4
+ export declare function executeSmartCompact(arguments_: unknown, context: ToolRuntimeContext): Promise<{
5
+ content: string;
6
+ data: {
7
+ status: string;
8
+ };
9
+ }>;
@@ -0,0 +1,20 @@
1
+ import { MarAgentError } from '../../error.js';
2
+ export const smartCompactToolDefinition = {
3
+ name: 'smart_compact',
4
+ allowedCallers: ['direct', 'programmatic'],
5
+ parallelSafety: 'safe',
6
+ description: 'Request background context compaction only when the current real user message ends with <mar_runtime><smart_compaction scope="this_message">eligible</smart_compaction></mar_runtime> (on separate lines). The scope applies only to the containing user message; a historical marker does not authorize the current input. Call only when prior work is substantially complete, the request begins a sustained new work phase, and prior detailed context has low continuation value while key state can be retained in a summary. Do not call merely because the conversation is long, for a one-off question, or while prior evidence is still needed. Prefer calling alongside the first batch of necessary read/search/exec tools rather than creating a separate rollout. Acceptance does not wait for generation or installation.',
7
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false }
8
+ };
9
+ export function executeSmartCompact(arguments_, context) {
10
+ if (!arguments_ ||
11
+ typeof arguments_ !== 'object' ||
12
+ Array.isArray(arguments_) ||
13
+ Object.keys(arguments_).length)
14
+ throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Smart Compaction input must be an empty object.');
15
+ if (!context.requestSmartCompact)
16
+ throw new MarAgentError('MAR_AGENT_SMART_COMPACT_DENIED', 'The current user input is not eligible for Smart Compaction.');
17
+ context.requestSmartCompact();
18
+ const data = { status: 'accepted' };
19
+ return Promise.resolve({ content: JSON.stringify(data), data });
20
+ }
@@ -12,6 +12,7 @@ import { executeTodoRead, todoReadToolDefinition } from './agent/todo-read.js';
12
12
  import { executeTodoWrite, todoWriteToolDefinition } from './agent/todo-write.js';
13
13
  import { executeToolSearch, toolSearchToolDefinition } from './mcp/tool-search.js';
14
14
  import { viewImageToolDefinition } from './local/view-image.js';
15
+ import { smartCompactToolDefinition, executeSmartCompact } from './agent/smart-compact.js';
15
16
  export const BUILTIN_TOOL_DEFINITIONS = [
16
17
  skillToolDefinition,
17
18
  toolSearchToolDefinition,
@@ -28,10 +29,12 @@ export const BUILTIN_TOOL_DEFINITIONS = [
28
29
  agentStartToolDefinition,
29
30
  agentWaitToolDefinition,
30
31
  agentMessageToolDefinition,
31
- agentCancelToolDefinition
32
+ agentCancelToolDefinition,
33
+ smartCompactToolDefinition
32
34
  ];
33
35
  export const AGENT_TOOL_HANDLERS = new Map([
34
36
  ['skill', executeSkill],
37
+ ['smart_compact', executeSmartCompact],
35
38
  ['tool_search', executeToolSearch],
36
39
  ['question', executeQuestion],
37
40
  ['todo_read', (_arguments, context) => executeTodoRead(context)],
@@ -40,6 +40,7 @@ export interface ToolRuntimeImage {
40
40
  export interface ToolRuntimeContext {
41
41
  host: MarAgentHost;
42
42
  todo: TodoStore;
43
+ requestSmartCompact?: () => void;
43
44
  subagents?: SubagentController;
44
45
  mode: ExecutionMode;
45
46
  sessionId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.10.30",
3
+ "version": "0.10.31",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",