@myagentroam/agent 0.9.169 → 0.9.171

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.
@@ -0,0 +1,25 @@
1
+ import type { ModelMessage, ModelUsage } from '../model/contracts.js';
2
+ export interface CoreCompactMessage {
3
+ readonly role: 'user' | 'assistant' | 'tool' | 'tool_call';
4
+ readonly content: string;
5
+ readonly callId?: string;
6
+ readonly name?: string;
7
+ readonly arguments?: unknown;
8
+ }
9
+ export interface CoreCompactContext {
10
+ readonly operationId: string;
11
+ readonly reason: 'manual' | 'automatic';
12
+ readonly messages: readonly CoreCompactMessage[];
13
+ readonly focus?: string;
14
+ readonly summaryTokenBudget: number;
15
+ /** Return the Core compaction contract selected for this operation's main model snapshot. */
16
+ readonly getPrompt: () => string;
17
+ }
18
+ export interface CoreCompactResult {
19
+ readonly summary: string;
20
+ readonly usage?: Readonly<ModelUsage>;
21
+ }
22
+ /** Copy only neutral evidence; a hook never receives provider payloads or history ownership. */
23
+ export declare function compactExtensionMessages(messages: readonly ModelMessage[]): readonly CoreCompactMessage[];
24
+ /** Invalid plugin results decline generation rather than altering the checkpoint contract. */
25
+ export declare function compactExtensionResult(value: unknown, budget: number): CoreCompactResult | undefined;
@@ -0,0 +1,55 @@
1
+ import { countModelTokens } from '../runtime/token-budget.js';
2
+ /** Copy only neutral evidence; a hook never receives provider payloads or history ownership. */
3
+ export function compactExtensionMessages(messages) {
4
+ return freeze(messages
5
+ .filter((message) => message.role !== 'provider' && message.contextKind === undefined)
6
+ .map((message) => ({
7
+ role: message.role,
8
+ content: message.content,
9
+ ...(message.callId === undefined ? {} : { callId: message.callId }),
10
+ ...(message.name === undefined ? {} : { name: message.name }),
11
+ ...(message.arguments === undefined
12
+ ? {}
13
+ : { arguments: structuredClone(message.arguments) })
14
+ })));
15
+ }
16
+ const usageKeys = new Set([
17
+ 'inputTokens',
18
+ 'contextInputTokens',
19
+ 'outputTokens',
20
+ 'cacheReadTokens',
21
+ 'cacheWriteTokens',
22
+ 'rolloutBudgetUnits',
23
+ 'hostedSearches',
24
+ 'hostedFetches'
25
+ ]);
26
+ /** Invalid plugin results decline generation rather than altering the checkpoint contract. */
27
+ export function compactExtensionResult(value, budget) {
28
+ if (value === null ||
29
+ typeof value !== 'object' ||
30
+ !('summary' in value) ||
31
+ typeof value.summary !== 'string')
32
+ return undefined;
33
+ const summary = value.summary.trim();
34
+ if (!summary || countModelTokens(summary) > budget)
35
+ return undefined;
36
+ if (!('usage' in value) || value.usage === undefined)
37
+ return { summary };
38
+ if (value.usage === null || typeof value.usage !== 'object' || Array.isArray(value.usage))
39
+ return undefined;
40
+ const usage = {};
41
+ for (const [key, amount] of Object.entries(value.usage)) {
42
+ if (!usageKeys.has(key) || typeof amount !== 'number' || !Number.isFinite(amount) || amount < 0)
43
+ return undefined;
44
+ Object.assign(usage, { [key]: amount });
45
+ }
46
+ return { summary, usage };
47
+ }
48
+ function freeze(value) {
49
+ if (value !== null && typeof value === 'object') {
50
+ for (const child of Object.values(value))
51
+ freeze(child);
52
+ Object.freeze(value);
53
+ }
54
+ return value;
55
+ }
@@ -1,4 +1,5 @@
1
1
  import type { CoreModelRequest } from './model-request.js';
2
+ import type { CoreCompactContext, CoreCompactResult } from './compaction.js';
2
3
  import type { CorePromptInput, CorePromptResult } from './prompt-input.js';
3
4
  import type { CoreModelEvent } from './model-stream.js';
4
5
  import type { CorePromptComposition, CorePromptSectionInput } from './prompt-composition.js';
@@ -115,6 +116,10 @@ export interface CoreDomainOperations extends CoreFileOperations {
115
116
  input: CoreCompactInput;
116
117
  output: CoreSessionActionResult;
117
118
  };
119
+ 'compact.generate': {
120
+ input: CoreCompactContext;
121
+ output: CoreCompactResult | undefined;
122
+ };
118
123
  }
119
124
  export interface CoreDomainFacts {
120
125
  'session.ending': {
@@ -117,9 +117,9 @@ export class AnthropicMessagesAdapter {
117
117
  responseId = text(message.id) ?? responseId;
118
118
  yield { type: 'response.started', responseId };
119
119
  const usage = record(message.usage);
120
- const inputTokens = number(usage.input_tokens);
121
- const cacheReadTokens = number(usage.cache_read_input_tokens);
122
- const cacheWriteTokens = number(usage.cache_creation_input_tokens);
120
+ const inputTokens = inputTokenCount(usage.input_tokens);
121
+ const cacheReadTokens = inputTokenCount(usage.cache_read_input_tokens);
122
+ const cacheWriteTokens = inputTokenCount(usage.cache_creation_input_tokens);
123
123
  latestInputUsage = inputUsage(inputTokens, cacheReadTokens, cacheWriteTokens);
124
124
  yield {
125
125
  type: 'usage',
@@ -334,12 +334,13 @@ export class AnthropicMessagesAdapter {
334
334
  }
335
335
  pendingTextBlocks.clear();
336
336
  const usage = record(event.usage);
337
- const inputTokens = number(usage.input_tokens);
338
- const cacheReadTokens = number(usage.cache_read_input_tokens);
339
- const cacheWriteTokens = number(usage.cache_creation_input_tokens);
340
- const deltaInputUsage = inputUsage(inputTokens, cacheReadTokens, cacheWriteTokens);
341
- if ((deltaInputUsage.contextInputTokens ?? 0) > 0)
342
- latestInputUsage = deltaInputUsage;
337
+ const inputTokens = inputTokenCount(usage.input_tokens);
338
+ const cacheReadTokens = inputTokenCount(usage.cache_read_input_tokens);
339
+ const cacheWriteTokens = inputTokenCount(usage.cache_creation_input_tokens);
340
+ // Some compatible gateways emit a complete all-zero input placeholder at the end.
341
+ const zeroPlaceholder = inputTokens === 0 && cacheReadTokens === 0 && cacheWriteTokens === 0;
342
+ if (!zeroPlaceholder)
343
+ latestInputUsage = inputUsage(inputTokens ?? latestInputUsage?.inputTokens, cacheReadTokens ?? latestInputUsage?.cacheReadTokens, cacheWriteTokens ?? latestInputUsage?.cacheWriteTokens);
343
344
  yield {
344
345
  type: 'usage',
345
346
  usage: {
@@ -439,6 +440,9 @@ function retryAfter(error) {
439
440
  const value = error.details?.retryAfter;
440
441
  return typeof value === 'string' ? value : null;
441
442
  }
443
+ function inputTokenCount(value) {
444
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
445
+ }
442
446
  function inputUsage(inputTokens, cacheReadTokens, cacheWriteTokens) {
443
447
  return {
444
448
  inputTokens,
@@ -17,6 +17,8 @@ export declare class CompactionOperation {
17
17
  readonly signal: AbortSignal;
18
18
  constructor(parent: AbortSignal, record: (progress: CompactionProgress) => void | Promise<void>);
19
19
  start(model: Pick<ModelTurnSession, 'start'>, request: ModelRequest): AsyncIterable<ModelEvent>;
20
+ /** External generation shares this operation's deadline, including the default fallback. */
21
+ generate<T>(run: (signal: AbortSignal) => Promise<T>): Promise<T>;
20
22
  dispose(): Promise<void>;
21
23
  private report;
22
24
  }
@@ -61,6 +61,26 @@ export class CompactionOperation {
61
61
  throw error;
62
62
  }
63
63
  }
64
+ /** External generation shares this operation's deadline, including the default fallback. */
65
+ async generate(run) {
66
+ this.signal.throwIfAborted();
67
+ let abort;
68
+ try {
69
+ return await Promise.race([
70
+ Promise.resolve().then(() => run(this.signal)),
71
+ new Promise((_resolve, reject) => {
72
+ abort = () => reject(this.signal.reason);
73
+ this.signal.addEventListener('abort', abort, { once: true });
74
+ if (this.signal.aborted)
75
+ abort();
76
+ })
77
+ ]);
78
+ }
79
+ finally {
80
+ if (abort)
81
+ this.signal.removeEventListener('abort', abort);
82
+ }
83
+ }
64
84
  async dispose() {
65
85
  if (this.#closed)
66
86
  return;
@@ -11,6 +11,7 @@ import { discoveredMcpToolNames, rebindMcpToolSearchOutputs } from '../tools/mcp
11
11
  import { currentExecutionImages } from '../tools/output-projection.js';
12
12
  import { createHash, randomUUID } from 'node:crypto';
13
13
  import { ExecutionToolSources } from './extension-tools.js';
14
+ import { compactExtensionMessages, compactExtensionResult } from '../extensions/compaction.js';
14
15
  import { ExtensionRequestBridge, ExtensionRequestSnapshots } from '../extensions/model-request.js';
15
16
  import { normalizeSessionMessagesInput, sessionMessagesPage } from '../extensions/session-messages.js';
16
17
  import { transformCoreModelStream } from '../extensions/model-stream.js';
@@ -1013,7 +1014,37 @@ export function createSessionOpener(services) {
1013
1014
  : buildExecutionSystemPrompt();
1014
1015
  let useManualCompactionFallback;
1015
1016
  const manualAppendBoundary = pendingAppends.length;
1016
- if (mode === 'compact') {
1017
+ const generateCompactExtension = async (history, reason, focus, compaction) => {
1018
+ if (!coreExtensions.hooks.hasInterceptors('compact.generate'))
1019
+ return undefined;
1020
+ const summaryTokenBudget = Math.min(10_000, selectedModel.maxOutputTokens, Math.max(1, Math.floor(selectedModel.contextWindowTokens / 4)));
1021
+ const highDensityCompaction = selectedModel.highDensityCompaction;
1022
+ try {
1023
+ const result = await compaction.generate((signal) => operation((call) => coreExtensions.hooks.invoke('compact.generate', Object.freeze({
1024
+ operationId: call.operationId,
1025
+ reason,
1026
+ messages: compactExtensionMessages(history),
1027
+ ...(focus === undefined ? {} : { focus }),
1028
+ summaryTokenBudget,
1029
+ getPrompt: () => buildCompactionMessages('', highDensityCompaction)[1].content
1030
+ }), Object.freeze({ ...call, signal }), async () => undefined)));
1031
+ compaction.signal.throwIfAborted();
1032
+ return compactExtensionResult(result, summaryTokenBudget);
1033
+ }
1034
+ catch {
1035
+ compaction.signal.throwIfAborted();
1036
+ return undefined;
1037
+ }
1038
+ };
1039
+ let manualExtension;
1040
+ if (mode === 'compact' && coreExtensions.hooks.hasInterceptors('compact.generate')) {
1041
+ manualCompactionStarted = true;
1042
+ manualCompaction = createCompactionOperation(manualCompactionId, compactSignal);
1043
+ await emit({ type: 'context.compacting', compactionId: manualCompactionId });
1044
+ rolloutBudget?.assertAvailable();
1045
+ manualExtension = await generateCompactExtension(messages, 'manual', compactFocus, manualCompaction);
1046
+ }
1047
+ if (mode === 'compact' && manualExtension === undefined) {
1017
1048
  const compactSystemPrompt = initialSystemPrompt;
1018
1049
  const compactHistory = [...messages];
1019
1050
  const projectedHistory = normalizeMessagesForModel(insertContextualWorldState(compactHistory, contextualWorldState.entries), { supportsImages: false });
@@ -1079,8 +1110,6 @@ export function createSessionOpener(services) {
1079
1110
  });
1080
1111
  const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, buildCompactionMessages('', selectedModel.highDensityCompaction), executionTools);
1081
1112
  const compactInputTokens = Math.max(0, selectedModel.contextWindowTokens - compactOverheadTokens);
1082
- if (compactInputTokens === 0)
1083
- throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Compaction prompt exceeds the selected model context window.');
1084
1113
  let usingNativeMessages = shouldUseNativeCompaction({
1085
1114
  estimatedRequestTokens: estimatedRequestTokens(compactSystemPrompt, nativeCompactMessages, executionTools),
1086
1115
  contextWindowTokens: selectedModel.contextWindowTokens,
@@ -1101,7 +1130,16 @@ export function createSessionOpener(services) {
1101
1130
  const compactionRetryState = { attempts: 0 };
1102
1131
  const compaction = createCompactionOperation(compactionId, signal);
1103
1132
  try {
1104
- while (true) {
1133
+ const extensionResult = await generateCompactExtension(compactHistory, 'automatic', input.focus, compaction);
1134
+ if (extensionResult) {
1135
+ compactSummary = extensionResult.summary;
1136
+ latestUsage = extensionResult.usage;
1137
+ if (latestUsage)
1138
+ await emit({ type: 'usage.updated', ...publicModelUsage(latestUsage) });
1139
+ }
1140
+ while (extensionResult === undefined) {
1141
+ if (compactInputTokens === 0)
1142
+ throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Compaction prompt exceeds the selected model context window.');
1105
1143
  compactSummary = '';
1106
1144
  latestUsage = undefined;
1107
1145
  try {
@@ -1172,6 +1210,7 @@ export function createSessionOpener(services) {
1172
1210
  }, options);
1173
1211
  messages.splice(0, messages.length, environmentContextUpdate(executionEnvironment), ...compactedHistoryMessages(replacementHistory), ...retainedModelContext);
1174
1212
  pendingAppends.length = 0;
1213
+ selected.resetContinuation?.();
1175
1214
  return record;
1176
1215
  });
1177
1216
  contextGc.reset();
@@ -1385,7 +1424,15 @@ export function createSessionOpener(services) {
1385
1424
  }
1386
1425
  }
1387
1426
  });
1388
- const completed = await executionLoop.run();
1427
+ if (manualExtension) {
1428
+ rolloutBudget?.recordUsage(manualExtension.usage);
1429
+ rolloutBudget?.assertAvailable();
1430
+ if (manualExtension.usage)
1431
+ await emit({ type: 'usage.updated', ...publicModelUsage(manualExtension.usage) });
1432
+ }
1433
+ const completed = manualExtension
1434
+ ? { finalAnswer: manualExtension.summary, usage: manualExtension.usage }
1435
+ : await executionLoop.run();
1389
1436
  finalAnswer = completed.finalAnswer;
1390
1437
  const compactSummary = mode === 'compact' ? finalAnswer.trim() : '';
1391
1438
  if (manualCompaction) {
@@ -1410,6 +1457,7 @@ export function createSessionOpener(services) {
1410
1457
  }, options);
1411
1458
  messages.splice(0, messages.length, ...compactedHistoryMessages(replacementHistory), ...retainedModelContext);
1412
1459
  pendingAppends.length = 0;
1460
+ selected.resetContinuation?.();
1413
1461
  return record;
1414
1462
  });
1415
1463
  if (mode === 'compact' && manualCompactionId !== undefined)
package/dist/index.d.ts CHANGED
@@ -8,10 +8,14 @@ export { testMarAgentModelTransport } from './agent-core/model/transport-test.js
8
8
  export type { AccessMode, ExecutionMode, ExecutionResult, MarAgentImageMetadata, RolloutBudgetOptions, RunInput, RunInputImage } from './agent-core/runtime/contracts.js';
9
9
  export type { MarAgentEvent } from './agent-core/runtime/events.js';
10
10
  export { createMarAgent } from './agent-core/sdk/agent.js';
11
+ export { generateModelText, type GenerateModelTextInput } from './shared/model-text.js';
11
12
  export type { CoreAgentExtensionContext, CoreSessionExtensionContext, CoreRuntimeExtension, CoreExtensionSnapshot, CoreExtensionSource, CoreExtensionDiagnostic, CoreDomainOperations, CoreDomainFacts, CoreHookRegistrar, CoreCapabilitySnapshot, CoreCommandSpec, CorePromptSection, CoreServiceDeclaration, CoreServiceDependency } from './agent-core/extensions/contracts.js';
12
13
  export type { CoreDisposer, CoreResourceRegistration, CoreResourceScope } from './agent-core/extensions/resources.js';
13
14
  export type { CoreHookOptions, CoreInterceptor } from './agent-core/extensions/hooks.js';
14
15
  export type { CoreOperationContext } from './agent-core/extensions/operation.js';
16
+ export type { CoreCompactContext, CoreCompactMessage, CoreCompactResult } from './agent-core/extensions/compaction.js';
17
+ export { buildCompactionInput as buildCompactEvidence } from './agent-core/runtime/compaction/history.js';
18
+ export { countModelTokens } from './agent-core/runtime/token-budget.js';
15
19
  export type { CoreServiceKey, CoreServiceRegistry } from './agent-core/extensions/services.js';
16
20
  export type { CoreToolSpec, CoreToolRef, CoreToolInvocation, CoreToolCheck, CoreToolAttempt, CoreToolCompletion, CoreToolFailure } from './agent-core/extensions/tools.js';
17
21
  export type { CoreModelMessage, CoreModelRequest, CoreRequestSnapshot } from './agent-core/extensions/model-request.js';
package/dist/index.js CHANGED
@@ -4,6 +4,9 @@ export { LocalAgentHost, selectExecutionShell } from './agent-core/host/local-ag
4
4
  export { SecretValue, marAgentReasoningEffortSchema, parseModelCatalog, resolveModelDefaultReasoningEffort } from './agent-core/model/configuration.js';
5
5
  export { testMarAgentModelTransport } from './agent-core/model/transport-test.js';
6
6
  export { createMarAgent } from './agent-core/sdk/agent.js';
7
+ export { generateModelText } from './shared/model-text.js';
8
+ export { buildCompactionInput as buildCompactEvidence } from './agent-core/runtime/compaction/history.js';
9
+ export { countModelTokens } from './agent-core/runtime/token-budget.js';
7
10
  export { generateSessionTitle } from './shared/auto-title.js';
8
11
  export { openMarAgentSessionCatalog } from './agent-core/session/catalog.js';
9
12
  export { MCP_DEFINITION_LIMITS, assertMcpToolCatalog, assertMcpToolDefinition } from './agent-core/tools/mcp/catalog.js';
@@ -0,0 +1,16 @@
1
+ import { type MarAgentModelConfiguration, type MarAgentReasoningEffort } from '../agent-core/model/configuration.js';
2
+ import type { ModelUsage } from '../agent-core/model/contracts.js';
3
+ import type { CoreCompactMessage } from '../agent-core/extensions/compaction.js';
4
+ export interface GenerateModelTextInput {
5
+ readonly model: MarAgentModelConfiguration;
6
+ readonly system: string;
7
+ readonly messages: readonly CoreCompactMessage[];
8
+ readonly reasoningEffort?: MarAgentReasoningEffort;
9
+ readonly outputLimit?: number;
10
+ readonly signal: AbortSignal;
11
+ }
12
+ /** One tool-free request using the SDK transports. Credential providers remain caller-owned. */
13
+ export declare function generateModelText(input: GenerateModelTextInput): Promise<{
14
+ text: string;
15
+ usage?: ModelUsage;
16
+ } | undefined>;
@@ -0,0 +1,43 @@
1
+ import { MarAgentError } from '../agent-core/error.js';
2
+ import { AnthropicMessagesAdapter } from '../agent-core/model/anthropic-messages.js';
3
+ import { OpenAiResponsesAdapter } from '../agent-core/model/openai-responses.js';
4
+ import { resolveModelDefaultReasoningEffort } from '../agent-core/model/configuration.js';
5
+ /** One tool-free request using the SDK transports. Credential providers remain caller-owned. */
6
+ export async function generateModelText(input) {
7
+ input.signal.throwIfAborted();
8
+ if (!input.model.enabled)
9
+ return undefined;
10
+ const adapter = input.model.protocol === 'OPENAI_RESPONSES'
11
+ ? new OpenAiResponsesAdapter(input.model)
12
+ : new AnthropicMessagesAdapter(input.model);
13
+ let text = '';
14
+ let completed = false;
15
+ let usage;
16
+ try {
17
+ for await (const event of adapter.start({
18
+ retryState: { attempts: 0, maxAttempts: 1 },
19
+ system: input.system,
20
+ messages: input.messages,
21
+ tools: [],
22
+ allowTools: false,
23
+ toolChoice: 'none',
24
+ reasoningEffort: input.reasoningEffort ?? resolveModelDefaultReasoningEffort(input.model),
25
+ ...(input.outputLimit === undefined ? {} : { outputLimit: input.outputLimit })
26
+ }, input.signal)) {
27
+ input.signal.throwIfAborted();
28
+ if (event.type === 'text.completed' && (event.phase ?? 'final_answer') === 'final_answer')
29
+ text = event.text.trim();
30
+ else if (event.type === 'usage')
31
+ usage = event.usage;
32
+ else if (event.type === 'completed')
33
+ completed = event.reason === 'completed';
34
+ else if (event.type === 'client_tool.call')
35
+ throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Text generation returned a tool call while tools were disabled.');
36
+ }
37
+ input.signal.throwIfAborted();
38
+ return completed && text ? { text, ...(usage === undefined ? {} : { usage }) } : undefined;
39
+ }
40
+ finally {
41
+ await adapter.close?.();
42
+ }
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.169",
3
+ "version": "0.9.171",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",