@myagentroam/agent 0.9.144 → 0.9.146

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.
Files changed (33) hide show
  1. package/dist/agent-core/model/catalog-file.d.ts +0 -2
  2. package/dist/agent-core/model/catalog-file.js +8 -0
  3. package/dist/agent-core/model/configuration.d.ts +1 -5
  4. package/dist/agent-core/model/configuration.js +2 -8
  5. package/dist/agent-core/model/contracts.d.ts +0 -1
  6. package/dist/agent-core/model/openai-responses.d.ts +1 -2
  7. package/dist/agent-core/model/openai-responses.js +32 -116
  8. package/dist/agent-core/model/runtime-credential.d.ts +1 -1
  9. package/dist/agent-core/model/runtime-credential.js +24 -2
  10. package/dist/agent-core/runtime/context/gc-tokens.js +1 -1
  11. package/dist/agent-core/sdk/agent.js +2 -0
  12. package/dist/agent-core/sdk/model-runtime.js +0 -4
  13. package/dist/agent-core/sdk/session.js +1 -27
  14. package/dist/agent-core/sdk/types.d.ts +6 -1
  15. package/dist/agent-core/session/jsonl-store.d.ts +3 -5
  16. package/dist/agent-core/session/jsonl-store.js +38 -12
  17. package/dist/agent-core/session/types.d.ts +6 -0
  18. package/dist/agent-core/tools/code-mode/cells.js +1 -1
  19. package/dist/agent-core/tools/code-mode/index.js +1 -1
  20. package/dist/agent-core/tools/code-mode/quickjs-engine.js +8 -3
  21. package/dist/agent-core/tools/execution-limits.d.ts +2 -2
  22. package/dist/agent-core/tools/execution-limits.js +2 -2
  23. package/dist/agent-core/tools/local/exec.js +1 -1
  24. package/dist/cli/main.js +35 -12
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +1 -0
  27. package/dist/shared/auto-title.d.ts +11 -0
  28. package/dist/shared/auto-title.js +67 -0
  29. package/package.json +1 -1
  30. package/dist/agent-core/prompts/title.d.ts +0 -1
  31. package/dist/agent-core/prompts/title.js +0 -9
  32. package/dist/agent-core/sdk/session-title.d.ts +0 -3
  33. package/dist/agent-core/sdk/session-title.js +0 -42
@@ -32,7 +32,6 @@ export declare function serializeModel(model: CliModelConfiguration): {
32
32
  fastMode?: boolean | undefined;
33
33
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
34
34
  hostedWebSearch: boolean;
35
- responsesEncoding?: "STANDARD" | "LITE" | undefined;
36
35
  responsesToolNamespace?: string | undefined;
37
36
  enabled: boolean;
38
37
  } | {
@@ -60,7 +59,6 @@ export declare function serializeModel(model: CliModelConfiguration): {
60
59
  fastMode?: boolean | undefined;
61
60
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
62
61
  hostedWebSearch: boolean;
63
- responsesEncoding?: "STANDARD" | "LITE" | undefined;
64
62
  responsesToolNamespace?: string | undefined;
65
63
  enabled: boolean;
66
64
  };
@@ -52,6 +52,14 @@ function parseCliModelCatalog(input, options = {}) {
52
52
  return parseModelCatalog(input, options);
53
53
  const externalCredentialModels = new Set();
54
54
  const normalizedModels = input.models.map((model, index) => {
55
+ if (isRecord(model) && Object.hasOwn(model, 'responsesEncoding')) {
56
+ if (model.protocol !== 'OPENAI_RESPONSES' ||
57
+ (model.responsesEncoding !== 'STANDARD' && model.responsesEncoding !== 'LITE'))
58
+ throw invalidCatalog();
59
+ const { responsesEncoding: _encoding, ...configuration } = model;
60
+ void _encoding;
61
+ model = configuration;
62
+ }
55
63
  if (!isRecord(model) || !Object.hasOwn(model, 'credential'))
56
64
  return model;
57
65
  const credential = model.credential;
@@ -89,10 +89,6 @@ declare const rawModelSchema: z.ZodObject<{
89
89
  }, z.core.$strict>, z.ZodObject<{
90
90
  transport: z.ZodLiteral<"WEBSOCKET">;
91
91
  }, z.core.$strict>], "transport">>;
92
- responsesEncoding: z.ZodOptional<z.ZodEnum<{
93
- STANDARD: "STANDARD";
94
- LITE: "LITE";
95
- }>>;
96
92
  responsesToolNamespace: z.ZodOptional<z.ZodString>;
97
93
  responsesPreviousResponseId: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
98
94
  enabled: z.ZodBoolean;
@@ -108,7 +104,7 @@ export interface MarAgentModelConfiguration extends Omit<z.infer<typeof rawModel
108
104
  responsesPreviousResponseId?: boolean;
109
105
  }
110
106
  export declare function supportsResponsesConfigurationUpdates(model: Pick<MarAgentModelConfiguration, 'responsesConfigurationUpdates' | 'codexCredential'>): boolean;
111
- export declare function usesResponsesConfigurationUpdates(model: Pick<MarAgentModelConfiguration, 'protocol' | 'responsesEncoding' | 'responsesConfigurationUpdates' | 'codexCredential'>): boolean;
107
+ export declare function usesResponsesConfigurationUpdates(model: Pick<MarAgentModelConfiguration, 'protocol' | 'responsesConfigurationUpdates' | 'codexCredential'>): boolean;
112
108
  export interface ModelCatalog {
113
109
  version: 1;
114
110
  defaultModelId?: string;
@@ -54,7 +54,6 @@ const rawModelSchema = z
54
54
  serviceTier: z.enum(['auto', 'default', 'flex', 'priority', 'fast', 'ultrafast']).optional(),
55
55
  hostedWebSearch: z.boolean(),
56
56
  responsesTransport: rawResponsesTransportSchema.optional(),
57
- responsesEncoding: z.enum(['STANDARD', 'LITE']).optional(),
58
57
  responsesToolNamespace: z
59
58
  .string()
60
59
  .regex(/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/u)
@@ -93,10 +92,7 @@ const rawModelSchema = z
93
92
  });
94
93
  if (value.protocol !== 'OPENAI_RESPONSES' && value.responsesTransport !== undefined)
95
94
  context.addIssue({ code: 'custom', message: 'Responses transport requires Responses.' });
96
- if (value.protocol !== 'OPENAI_RESPONSES' && value.responsesEncoding !== undefined)
97
- context.addIssue({ code: 'custom', message: 'Responses encoding requires Responses.' });
98
- if (value.responsesToolNamespace !== undefined &&
99
- (value.protocol !== 'OPENAI_RESPONSES' || value.responsesEncoding === 'LITE'))
95
+ if (value.responsesToolNamespace !== undefined && value.protocol !== 'OPENAI_RESPONSES')
100
96
  context.addIssue({
101
97
  code: 'custom',
102
98
  path: ['responsesToolNamespace'],
@@ -111,9 +107,7 @@ export function supportsResponsesConfigurationUpdates(model) {
111
107
  return model.responsesConfigurationUpdates ?? model.codexCredential === true;
112
108
  }
113
109
  export function usesResponsesConfigurationUpdates(model) {
114
- return (model.protocol === 'OPENAI_RESPONSES' &&
115
- model.responsesEncoding !== 'LITE' &&
116
- supportsResponsesConfigurationUpdates(model));
110
+ return model.protocol === 'OPENAI_RESPONSES' && supportsResponsesConfigurationUpdates(model);
117
111
  }
118
112
  export function resolveModelDefaultReasoningEffort(model) {
119
113
  const configured = marAgentReasoningEffortSchema.safeParse(model.defaultReasoningEffort);
@@ -30,7 +30,6 @@ export interface ModelAttemptDiagnostic {
30
30
  attempt: number;
31
31
  maxAttempts: number;
32
32
  continuation: boolean;
33
- prewarm?: boolean;
34
33
  credentialReason: ModelCredentialAcquireReason;
35
34
  semanticEventObserved?: boolean;
36
35
  firstSemanticEventType?: ModelEvent['type'];
@@ -27,7 +27,6 @@ interface PreparedResponsesRequest {
27
27
  }
28
28
  export declare class OpenAiResponsesAdapter implements ModelAdapter {
29
29
  #private;
30
- readonly prefixIdentity: `${string}-${string}-${string}-${string}-${string}`;
31
30
  constructor(configuration: MarAgentModelConfiguration);
32
31
  get configuration(): MarAgentModelConfiguration;
33
32
  prepareReconfigure(configuration: MarAgentModelConfiguration): (() => void) | undefined;
@@ -67,5 +66,5 @@ declare class OpenAiResponsesTurnSession implements ModelTurnSession {
67
66
  private connectWebSocket;
68
67
  }
69
68
  export declare function isOpenAiResponsesRecoverableStreamError(error: unknown): error is MarAgentError;
70
- export declare function openAiInput(messages: ModelRequest['messages'], images: ModelRequest['images'] | undefined, modelId: string, requireEncryptedReasoning: boolean, nativeToolSearch?: boolean, configurationUpdates?: boolean): Record<string, unknown>[];
69
+ export declare function openAiInput(messages: ModelRequest['messages'], images: ModelRequest['images'] | undefined, modelId: string, requireEncryptedReasoning: boolean, configurationUpdates?: boolean): Record<string, unknown>[];
71
70
  export {};
@@ -11,10 +11,7 @@ const WEBSOCKET_IDLE_TIMEOUT_MS = 300_000;
11
11
  export const OPENAI_RESPONSES_EARLY_EOF_REASON = 'OPENAI_RESPONSES_EARLY_EOF';
12
12
  export const OPENAI_RESPONSES_WEBSOCKET_CONNECTION_LIMIT_REASON = 'OPENAI_RESPONSES_WEBSOCKET_CONNECTION_LIMIT';
13
13
  const X_CODEX_TURN_STATE = 'x-codex-turn-state';
14
- const RESPONSES_LITE_HEADER = 'x-openai-internal-codex-responses-lite';
15
- const RESPONSES_LITE_METADATA = 'ws_request_header_x_openai_internal_codex_responses_lite';
16
14
  export class OpenAiResponsesAdapter {
17
- prefixIdentity = randomUUID();
18
15
  #cachedState = {};
19
16
  #cacheLeased = false;
20
17
  #closed = false;
@@ -242,12 +239,12 @@ export class OpenAiResponsesAdapter {
242
239
  const fetchResponse = async (requestBody) => {
243
240
  const serializedBody = JSON.stringify(requestBody);
244
241
  return await fetchModelResponse(responsesEndpoint(this.configuration.baseUrl), async () => {
245
- const credential = await acquireResponsesCredential(this.configuration, credentialReason);
242
+ const credential = await acquireResponsesCredential(this.configuration, credentialReason, signal);
246
243
  latestCredentialRevision = credential.revision;
247
244
  return {
248
245
  request: {
249
246
  method: 'POST',
250
- headers: responsesHeaders(credential, request.promptCacheKey, turn.currentTurnState(), this.configuration.responsesEncoding === 'LITE'),
247
+ headers: responsesHeaders(credential, request.promptCacheKey, turn.currentTurnState()),
251
248
  body: serializedBody,
252
249
  redirect: 'manual'
253
250
  },
@@ -629,49 +626,8 @@ class OpenAiResponsesTurnSession {
629
626
  let completed = false;
630
627
  let sawUnrepresentedOutput = false;
631
628
  try {
632
- let prepared = this.prepareRequest(request);
629
+ const prepared = this.prepareRequest(request);
633
630
  this.#fullBody = prepared.fullBody;
634
- if (this.adapter.configuration.responsesEncoding === 'LITE' &&
635
- this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET' &&
636
- responsesContinuationEnabled(this.adapter.configuration) &&
637
- this.state.lastRequest === undefined &&
638
- request.allowTools !== false &&
639
- request.toolChoice !== 'none' &&
640
- (request.tools.length > 0 || this.adapter.configuration.hostedWebSearch)) {
641
- const prefix = prepared.fullBody.input.slice(0, request.system ? 2 : 1);
642
- const warmBody = { ...prepared.fullBody, input: prefix, generate: false };
643
- this.#fullBody = warmBody;
644
- let warmId;
645
- let warmCompleted = false;
646
- for await (const event of this.adapter.startTurn({
647
- ...request,
648
- onAttemptDiagnostic: async (diagnostic) => request.onAttemptDiagnostic?.({ ...diagnostic, prewarm: true })
649
- }, signal, this, { fullBody: warmBody, body: warmBody, continuation: false })) {
650
- if (event.type === 'response.started' && event.responseId !== 'unknown')
651
- warmId = event.responseId;
652
- if (event.type === 'completed')
653
- warmCompleted = event.reason === 'completed';
654
- if (event.type === 'client_tool.call' ||
655
- event.type === 'output.delta' ||
656
- event.type === 'text.delta' ||
657
- event.type === 'text.completed' ||
658
- event.type === 'response.item' ||
659
- event.type === 'reasoning.summary') {
660
- this.invalidateWebSocket();
661
- throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Responses prewarm returned model output.');
662
- }
663
- }
664
- warmId = this.#completedResponseId ?? warmId;
665
- this.#completedResponseId = undefined;
666
- if (!warmCompleted || !warmId) {
667
- this.invalidateWebSocket();
668
- throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Responses prewarm did not complete.');
669
- }
670
- this.state.lastRequest = { ...prepared.fullBody, input: prefix };
671
- this.state.lastResponse = { responseId: warmId, outputItems: [] };
672
- prepared = this.prepareRequest(request);
673
- this.#fullBody = prepared.fullBody;
674
- }
675
631
  this.#completedResponseId = undefined;
676
632
  for await (const event of this.adapter.startTurn(request, signal, this, prepared)) {
677
633
  if (event.type === 'response.started' && event.responseId !== 'unknown')
@@ -732,7 +688,7 @@ class OpenAiResponsesTurnSession {
732
688
  invalidateResponsesWebSocket(this.state, false);
733
689
  clearResponsesContinuation(this.state);
734
690
  }
735
- const fullBody = responsesRequestBody(this.adapter.configuration, request, request.messages, this.adapter.prefixIdentity);
691
+ const fullBody = responsesRequestBody(this.adapter.configuration, request, request.messages);
736
692
  if (responsesContinuationEnabled(this.adapter.configuration)) {
737
693
  const incremental = incrementalResponsesInput(this.state, fullBody);
738
694
  if (incremental !== undefined) {
@@ -806,11 +762,11 @@ class OpenAiResponsesTurnSession {
806
762
  throw localContinuationReset();
807
763
  }
808
764
  if (this.state.socket === undefined) {
809
- const handshakeCredential = await acquireResponsesCredential(input.configuration, input.reason);
765
+ const handshakeCredential = await acquireResponsesCredential(input.configuration, input.reason, input.signal);
810
766
  input.credentialAcquired(handshakeCredential.revision);
811
767
  await this.connectWebSocket(endpoint, handshakeCredential, input.promptCacheKey, input.signal);
812
768
  }
813
- const requestCredential = await acquireResponsesCredential(input.configuration, input.reason);
769
+ const requestCredential = await acquireResponsesCredential(input.configuration, input.reason, input.signal);
814
770
  input.credentialAcquired(requestCredential.revision);
815
771
  if (requestCredential.revision !== this.state.credentialRevision ||
816
772
  responsesCredentialFingerprint(requestCredential) !== this.state.credentialFingerprint) {
@@ -853,7 +809,7 @@ class OpenAiResponsesTurnSession {
853
809
  const proxyType = credential.socks5Url === undefined ? 'DIRECT' : 'SOCKS5';
854
810
  this.#connectionDiagnostic = { connectionId, connectionReused: false, proxyType };
855
811
  const socket = new WebSocket(parsedEndpoint, {
856
- headers: Object.fromEntries(webSocketHeaders(credential, promptCacheKey, this.adapter.configuration.responsesEncoding === 'LITE')),
812
+ headers: Object.fromEntries(webSocketHeaders(credential, promptCacheKey)),
857
813
  perMessageDeflate: false,
858
814
  maxPayload: 16 * 1024 * 1024,
859
815
  handshakeTimeout: 30_000,
@@ -884,10 +840,10 @@ function localContinuationReset() {
884
840
  function isLocalContinuationReset(error) {
885
841
  return error instanceof MarAgentError && error.code === 'MAR_AGENT_RESPONSES_CONTINUATION_RESET';
886
842
  }
887
- function responsesRequestBody(configuration, request, messages, prefixIdentity = '') {
843
+ function responsesRequestBody(configuration, request, messages) {
888
844
  const tools = request.allowTools === false
889
845
  ? []
890
- : request.tools.map((tool) => configuration.responsesEncoding !== 'LITE' && tool.nativeType === 'tool_search'
846
+ : request.tools.map((tool) => tool.nativeType === 'tool_search'
891
847
  ? {
892
848
  type: 'tool_search',
893
849
  execution: 'client',
@@ -910,27 +866,7 @@ function responsesRequestBody(configuration, request, messages, prefixIdentity =
910
866
  if (request.allowTools !== false && configuration.hostedWebSearch)
911
867
  tools.push({ type: 'web_search' });
912
868
  const toolChoice = request.toolChoice ?? 'auto';
913
- const lite = configuration.responsesEncoding === 'LITE';
914
- const input = openAiInput(messages, request.images, configuration.id, true, !lite, usesResponsesConfigurationUpdates(configuration));
915
- if (lite) {
916
- const namespace = uuidV5('6ba7b812-9dad-11d1-80b4-00c04fd430c8', request.promptCacheKey ?? prefixIdentity);
917
- const prefix = [
918
- {
919
- type: 'additional_tools',
920
- role: 'developer',
921
- id: `at_${uuidV5(namespace, JSON.stringify(tools))}`,
922
- tools
923
- }
924
- ];
925
- if (request.system)
926
- prefix.push({
927
- type: 'message',
928
- role: 'developer',
929
- id: `msg_${uuidV5(namespace, request.system)}`,
930
- content: [{ type: 'input_text', text: request.system }]
931
- });
932
- input.unshift(...prefix);
933
- }
869
+ const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration));
934
870
  return {
935
871
  model: configuration.modelId,
936
872
  ...(configuration.serviceTier !== undefined
@@ -938,15 +874,12 @@ function responsesRequestBody(configuration, request, messages, prefixIdentity =
938
874
  : configuration.fastMode === true
939
875
  ? { service_tier: 'priority' }
940
876
  : {}),
941
- ...(lite
942
- ? {
943
- tool_choice: toolChoice,
944
- client_metadata: { [RESPONSES_LITE_METADATA]: 'true' }
945
- }
946
- : { instructions: request.system, tools, tool_choice: toolChoice }),
877
+ instructions: request.system,
878
+ tools,
879
+ tool_choice: toolChoice,
947
880
  input,
948
881
  stream: true,
949
- parallel_tool_calls: !lite,
882
+ parallel_tool_calls: true,
950
883
  store: false,
951
884
  text: { verbosity: 'low' },
952
885
  include: [
@@ -960,23 +893,10 @@ function responsesRequestBody(configuration, request, messages, prefixIdentity =
960
893
  effort: request.requestReasoningEffort?.trim() ||
961
894
  request.reasoningEffort?.trim() ||
962
895
  resolveModelDefaultReasoningEffort(configuration),
963
- summary: 'auto',
964
- ...(lite ? { context: 'all_turns' } : {})
896
+ summary: 'auto'
965
897
  }
966
898
  };
967
899
  }
968
- function uuidV5(namespace, value) {
969
- // UUID v5 matches the reference item-identity algorithm; this is not a secret hash.
970
- const bytes = createHash('sha1')
971
- .update(Buffer.from(namespace.replaceAll('-', ''), 'hex'))
972
- .update(value)
973
- .digest()
974
- .subarray(0, 16);
975
- bytes[6] = (bytes[6] & 0x0f) | 0x50;
976
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
977
- const hex = bytes.toString('hex');
978
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
979
- }
980
900
  function incrementalResponsesInput(state, current) {
981
901
  const previous = state.lastRequest;
982
902
  const completion = state.lastResponse;
@@ -1291,8 +1211,8 @@ async function webSocketMessageText(value) {
1291
1211
  return Buffer.concat(value).toString('utf8');
1292
1212
  throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Model WebSocket returned an unsupported frame.');
1293
1213
  }
1294
- function webSocketHeaders(credential, promptCacheKey, lite = false) {
1295
- const headers = responsesHeaders(credential, promptCacheKey, undefined, lite);
1214
+ function webSocketHeaders(credential, promptCacheKey) {
1215
+ const headers = responsesHeaders(credential, promptCacheKey);
1296
1216
  headers.delete('accept');
1297
1217
  headers.delete('content-type');
1298
1218
  headers.set('openai-beta', 'responses_websockets=2026-02-06');
@@ -1441,8 +1361,8 @@ function responsesEndpoint(baseUrl) {
1441
1361
  return `${normalized}/responses`;
1442
1362
  return `${normalized.endsWith('/v1') ? normalized : `${normalized}/v1`}/responses`;
1443
1363
  }
1444
- async function acquireResponsesCredential(configuration, reason) {
1445
- return acquireModelCredential(configuration, reason, 'OpenAI Responses requires a model credential.');
1364
+ async function acquireResponsesCredential(configuration, reason, signal) {
1365
+ return acquireModelCredential(configuration, reason, 'OpenAI Responses requires a model credential.', signal);
1446
1366
  }
1447
1367
  const RESERVED_CREDENTIAL_HEADERS = new Set([
1448
1368
  'accept',
@@ -1464,9 +1384,9 @@ const RESERVED_CREDENTIAL_HEADERS = new Set([
1464
1384
  'upgrade',
1465
1385
  'x-client-request-id',
1466
1386
  X_CODEX_TURN_STATE,
1467
- RESPONSES_LITE_HEADER
1387
+ 'x-openai-internal-codex-responses-lite'
1468
1388
  ]);
1469
- function responsesHeaders(credential, promptCacheKey, turnState, lite = false) {
1389
+ function responsesHeaders(credential, promptCacheKey, turnState) {
1470
1390
  const headers = new Headers({
1471
1391
  authorization: `Bearer ${credential.bearer.reveal()}`,
1472
1392
  'content-type': 'application/json',
@@ -1481,8 +1401,6 @@ function responsesHeaders(credential, promptCacheKey, turnState, lite = false) {
1481
1401
  });
1482
1402
  if (turnState)
1483
1403
  headers.set(X_CODEX_TURN_STATE, turnState);
1484
- if (lite)
1485
- headers.set(RESPONSES_LITE_HEADER, 'true');
1486
1404
  return appendModelCredentialHeaders(headers, credential, RESERVED_CREDENTIAL_HEADERS);
1487
1405
  }
1488
1406
  function withCredentialRevision(error, revision) {
@@ -1698,7 +1616,7 @@ function responseCitations(response) {
1698
1616
  function responseEventId(event) {
1699
1617
  return text(record(event.response).id);
1700
1618
  }
1701
- export function openAiInput(messages, images, modelId, requireEncryptedReasoning, nativeToolSearch = true, configurationUpdates = true) {
1619
+ export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true) {
1702
1620
  const compatibleMessages = messages.filter((message) => message.role !== 'provider' ||
1703
1621
  (message.provider === 'OPENAI_RESPONSES' &&
1704
1622
  (message.providerModelId === undefined || message.providerModelId === modelId) &&
@@ -1708,13 +1626,16 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
1708
1626
  typeof message.item.encrypted_content === 'string')));
1709
1627
  const lastUser = compatibleMessages.findLast((message) => message.role === 'user');
1710
1628
  const providerCallIds = new Set();
1629
+ const functionCallIds = new Set();
1711
1630
  const customCallIds = new Set();
1712
1631
  const providerMessageTexts = new Set();
1713
1632
  for (const message of compatibleMessages) {
1714
1633
  if (message.role !== 'provider' || !message.item)
1715
1634
  continue;
1716
- if (message.item.type === 'function_call' && typeof message.item.call_id === 'string')
1635
+ if (message.item.type === 'function_call' && typeof message.item.call_id === 'string') {
1717
1636
  providerCallIds.add(message.item.call_id);
1637
+ functionCallIds.add(message.item.call_id);
1638
+ }
1718
1639
  if (message.item.type === 'tool_search_call' && typeof message.item.call_id === 'string')
1719
1640
  providerCallIds.add(message.item.call_id);
1720
1641
  if (message.item.type === 'custom_tool_call' && typeof message.item.call_id === 'string') {
@@ -1752,20 +1673,13 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
1752
1673
  return orderedMessages
1753
1674
  .filter((message) => !(message.role === 'tool_call' && message.callId && providerCallIds.has(message.callId)) &&
1754
1675
  !(message.role === 'assistant' && providerMessageTexts.has(message.content)))
1755
- .map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, nativeToolSearch));
1676
+ .map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds));
1756
1677
  }
1757
- function openAiInputItem(message, images, customCallIds = new Set(), nativeToolSearch = true) {
1678
+ function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set()) {
1758
1679
  const userImages = message.role === 'user' ? uniqueModelImages(message.images, images) : [];
1759
1680
  if (message.role === 'provider') {
1760
1681
  if (message.provider !== 'OPENAI_RESPONSES' || !message.item)
1761
1682
  throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Provider context item is invalid.');
1762
- if (!nativeToolSearch && message.item.type === 'tool_search_call')
1763
- return {
1764
- type: 'function_call',
1765
- call_id: message.item.call_id,
1766
- name: 'tool_search',
1767
- arguments: JSON.stringify(message.item.arguments ?? {})
1768
- };
1769
1683
  if (message.item.type === 'configuration_update') {
1770
1684
  const effort = marAgentReasoningEffortSchema.safeParse(record(message.item.reasoning).effort);
1771
1685
  if (effort.success)
@@ -1783,7 +1697,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), nativeToolS
1783
1697
  name: message.name,
1784
1698
  input: String(message.arguments ?? '')
1785
1699
  };
1786
- if (nativeToolSearch && message.role === 'tool_call' && message.name === 'tool_search')
1700
+ if (message.role === 'tool_call' && message.name === 'tool_search')
1787
1701
  return {
1788
1702
  type: 'tool_search_call',
1789
1703
  call_id: message.callId,
@@ -1798,7 +1712,9 @@ function openAiInputItem(message, images, customCallIds = new Set(), nativeToolS
1798
1712
  name: message.name,
1799
1713
  arguments: JSON.stringify(message.arguments ?? {})
1800
1714
  };
1801
- if (nativeToolSearch && message.role === 'tool' && message.toolSearchTools)
1715
+ if (message.role === 'tool' &&
1716
+ message.toolSearchTools &&
1717
+ !functionCallIds.has(message.callId ?? ''))
1802
1718
  return {
1803
1719
  type: 'tool_search_output',
1804
1720
  call_id: message.callId,
@@ -2,5 +2,5 @@ import { SecretValue, type MarAgentModelConfiguration, type ModelCredentialProvi
2
2
  export declare function acquireModelCredential(configuration: Pick<MarAgentModelConfiguration, 'apiKey' | 'credentialProvider'> | {
3
3
  readonly apiKey?: SecretValue;
4
4
  readonly credentialProvider?: ModelCredentialProvider;
5
- }, reason: ModelCredentialAcquireReason, missingCredentialMessage: string): Promise<ModelCredential>;
5
+ }, reason: ModelCredentialAcquireReason, missingCredentialMessage: string, signal?: AbortSignal): Promise<ModelCredential>;
6
6
  export declare function appendModelCredentialHeaders(headers: Headers, credential: ModelCredential, reservedHeaders: ReadonlySet<string>): Headers;
@@ -1,10 +1,12 @@
1
1
  import { MarAgentError } from '../error.js';
2
2
  import { SecretValue } from './configuration.js';
3
- export async function acquireModelCredential(configuration, reason, missingCredentialMessage) {
3
+ export async function acquireModelCredential(configuration, reason, missingCredentialMessage, signal) {
4
+ signal?.throwIfAborted();
4
5
  if (configuration.credentialProvider !== undefined) {
5
6
  if (configuration.apiKey !== undefined)
6
7
  throw new MarAgentError('MAR_AGENT_MODEL_CONFIG_INVALID', 'Model credential sources are ambiguous.');
7
- const credential = await configuration.credentialProvider.acquire(reason);
8
+ const credential = await waitForModelCredential(configuration.credentialProvider, reason, signal);
9
+ signal?.throwIfAborted();
8
10
  if (!Number.isSafeInteger(credential.revision) ||
9
11
  credential.revision < 0 ||
10
12
  !Number.isFinite(credential.expiresAt) ||
@@ -25,6 +27,26 @@ export async function acquireModelCredential(configuration, reason, missingCrede
25
27
  headers: {}
26
28
  };
27
29
  }
30
+ async function waitForModelCredential(provider, reason, signal) {
31
+ if (signal === undefined)
32
+ return provider.acquire(reason);
33
+ let abort = () => { };
34
+ try {
35
+ return await new Promise((resolve, reject) => {
36
+ // Cancel only this request's wait; the shared provider remains caller-owned.
37
+ abort = () => reject(signal.reason);
38
+ signal.addEventListener('abort', abort, { once: true });
39
+ if (signal.aborted) {
40
+ abort();
41
+ return;
42
+ }
43
+ provider.acquire(reason).then(resolve, reject);
44
+ });
45
+ }
46
+ finally {
47
+ signal.removeEventListener('abort', abort);
48
+ }
49
+ }
28
50
  export function appendModelCredentialHeaders(headers, credential, reservedHeaders) {
29
51
  for (const [name, value] of Object.entries(credential.headers)) {
30
52
  if (reservedHeaders.has(name.toLowerCase()))
@@ -13,7 +13,7 @@ function serializedEntries(input, messages) {
13
13
  (input.modelProtocol === undefined &&
14
14
  (input.messages ?? messages).some((message) => message.provider === 'OPENAI_RESPONSES'));
15
15
  const values = responses
16
- ? openAiInput(messages, undefined, input.modelId, true, true, input.responsesConfigurationUpdates !== false)
16
+ ? openAiInput(messages, undefined, input.modelId, true, input.responsesConfigurationUpdates !== false)
17
17
  : messages
18
18
  .filter((message) => message.role !== 'provider' ||
19
19
  (message.provider === 'ANTHROPIC_MESSAGES' && message.providerModelId === input.modelId))
@@ -83,6 +83,8 @@ export async function createMarAgentWithAdapter(options) {
83
83
  createSession: async (input) => openSession((await store.createSession(input)).id, input.workspace),
84
84
  openSession: (id) => openSession(id),
85
85
  listSessions: (input) => store.list(input),
86
+ getSession: (id) => store.getSession(id),
87
+ setSessionTitle: (id, title, input) => store.updateTitle(id, title, { ...input, automatic: input?.automatic ?? false }, sessionWriterOwner),
86
88
  clearSession: async (id) => {
87
89
  const runtime = sessionRuntimes.get(id);
88
90
  if ([...active.keys()].some((key) => key.startsWith(`${id}:`)))
@@ -9,10 +9,6 @@ export function resolveAgentModels(configurations, requestedDefaultModelId) {
9
9
  for (const model of configuredModels) {
10
10
  if ((model.apiKey === undefined) === (model.credentialProvider === undefined))
11
11
  throw new MarAgentError('MAR_AGENT_MODEL_CONFIG_INVALID', 'Exactly one model credential source is required.');
12
- if (model.responsesEncoding !== undefined &&
13
- (model.protocol !== 'OPENAI_RESPONSES' ||
14
- !['STANDARD', 'LITE'].includes(model.responsesEncoding)))
15
- throw new MarAgentError('MAR_AGENT_MODEL_CONFIG_INVALID', 'Responses encoding is invalid.');
16
12
  if (model.fastMode === true && model.protocol !== 'OPENAI_RESPONSES')
17
13
  throw new MarAgentError('MAR_AGENT_MODEL_CONFIG_INVALID', 'Fast mode requires Responses.');
18
14
  if (model.serviceTier !== undefined &&
@@ -6,7 +6,6 @@ import { compactHistoryMayContainRetainedResources, contextualInstructionInsertI
6
6
  import { eventForPersistence, publicModelUsage } from '../runtime/event-projection.js';
7
7
  import { ExecutionLoop } from '../runtime/execution.js';
8
8
  import { closeModelAdapters, createModelTurnSession } from './model-runtime.js';
9
- import { generateSessionTitle } from './session-title.js';
10
9
  import { createSubagentBackend } from './subagent-backend.js';
11
10
  import { discoveredMcpToolNames, rebindMcpToolSearchOutputs } from '../tools/mcp/history.js';
12
11
  import { currentExecutionImages } from '../tools/output-projection.js';
@@ -193,20 +192,7 @@ export function createSessionOpener(services) {
193
192
  let selectedTurn;
194
193
  const contextGc = new ContextGcRuntime();
195
194
  let selectedAdapter;
196
- let titleAdapter;
197
195
  const executionScopedAdapters = new Set();
198
- let titleTask;
199
- let titleSettled = false;
200
- const settleTitle = async () => {
201
- if (titleSettled)
202
- return;
203
- titleSettled = true;
204
- const generatedTitle = await titleTask;
205
- if (generatedTitle)
206
- await store
207
- .updateTitle(sessionId, generatedTitle, { executionId, turnId })
208
- .catch(() => undefined);
209
- };
210
196
  const subagents = sessionSource.type === 'user' ? sessionRuntime.subagents : undefined;
211
197
  try {
212
198
  releaseSession = await store.acquireSessionLease(sessionId, sessionWriterOwner);
@@ -331,13 +317,6 @@ export function createSessionOpener(services) {
331
317
  currentExecutionUserIndex = messages.length - 1;
332
318
  internal.delivery?.applied();
333
319
  await appendMailboxMessages();
334
- if (sessionState.automaticTitlePending) {
335
- const titleModel = [...executionModels.values()].find((model) => model.titleGeneration) ??
336
- executionModels.get(input.modelId ?? executionDefaultModelId);
337
- titleAdapter = createAdapter(titleModel.id, executionModels);
338
- activeExecutionAdapters.add(titleAdapter);
339
- titleTask = generateSessionTitle(titleAdapter, input.prompt, titleModel.defaultReasoningEffort, controller.signal, recordModelAttempt('TITLE')).catch(() => undefined);
340
- }
341
320
  }
342
321
  await emit({ type: 'execution.started', mode });
343
322
  if (restored.ignoredImages > 0)
@@ -832,8 +811,7 @@ export function createSessionOpener(services) {
832
811
  const codeModeEnabled = roundTools.some((tool) => tool.name === 'code_exec');
833
812
  const residentTools = mcpRegistry.projectDirect(baseTools());
834
813
  const requestTools = (() => {
835
- if (selectedModel.protocol !== 'OPENAI_RESPONSES' ||
836
- selectedModel.responsesEncoding === 'LITE')
814
+ if (selectedModel.protocol !== 'OPENAI_RESPONSES')
837
815
  return projectCodeModeTools(roundTools, residentTools);
838
816
  const directNames = new Set(residentTools.map((tool) => tool.name));
839
817
  // Discovery and caller policy are independent: keep nested declarations for active tools.
@@ -1030,7 +1008,6 @@ export function createSessionOpener(services) {
1030
1008
  });
1031
1009
  if (mode === 'compact' && manualCompactionId !== undefined)
1032
1010
  await emit({ type: 'context.compacted', compactionId: manualCompactionId });
1033
- await settleTitle();
1034
1011
  subagents?.endParentExecution(executionId);
1035
1012
  await emit({ type: 'execution.completed' });
1036
1013
  return {
@@ -1042,7 +1019,6 @@ export function createSessionOpener(services) {
1042
1019
  catch (error) {
1043
1020
  finalAnswer = executionLoop?.finalAnswer ?? finalAnswer;
1044
1021
  acceptsMailbox = false;
1045
- await settleTitle();
1046
1022
  if (controller.signal.aborted) {
1047
1023
  subagents?.endParentExecution(executionId);
1048
1024
  await emit({ type: 'execution.cancelled' });
@@ -1069,8 +1045,6 @@ export function createSessionOpener(services) {
1069
1045
  rolloutBudget.discard();
1070
1046
  await manualCompaction?.dispose();
1071
1047
  await selectedTurn?.close().catch(() => undefined);
1072
- if (titleAdapter !== undefined && titleAdapter !== selectedAdapter)
1073
- executionScopedAdapters.add(titleAdapter);
1074
1048
  try {
1075
1049
  await closeModelAdapters(executionScopedAdapters);
1076
1050
  }
@@ -3,11 +3,12 @@ import type { MarAgentModelConfiguration } from '../model/configuration.js';
3
3
  import type { ExecutionResult, RolloutBudgetOptions, RunInput } from '../runtime/contracts.js';
4
4
  import type { MarAgentEvent } from '../runtime/events.js';
5
5
  import type { MarAgentSessionPage, MarAgentSessionSummary } from '../session/catalog.js';
6
- import type { SessionListInput, SessionSource } from '../session/types.js';
6
+ import type { SessionListInput, SessionSource, SessionTitleUpdateInput } from '../session/types.js';
7
7
  export interface CreateSessionInput {
8
8
  workspace: string;
9
9
  title?: string;
10
10
  }
11
+ export type { SessionTitleUpdateInput } from '../session/types.js';
11
12
  export type { HostExecutionExtensions, HostSessionResourceSnapshot, HostSkill, MarAgentHost } from '../host/contracts.js';
12
13
  export type { AccessMode, ExecutionMode, ExecutionResult, MarAgentImageMetadata, RolloutBudgetOptions, RunInput, RunInputImage } from '../runtime/contracts.js';
13
14
  export type { MarAgentEvent, MarAgentEventBase } from '../runtime/events.js';
@@ -31,6 +32,10 @@ export interface MarAgent {
31
32
  createSession(input: CreateSessionInput): Promise<MarAgentSession>;
32
33
  openSession(sessionId: string): Promise<MarAgentSession>;
33
34
  listSessions(input?: SessionListInput): Promise<MarAgentSessionPage>;
35
+ getSession(sessionId: string): Promise<MarAgentSessionSummary & {
36
+ automaticTitlePending: boolean;
37
+ }>;
38
+ setSessionTitle(sessionId: string, title: string, input?: SessionTitleUpdateInput): Promise<boolean>;
34
39
  clearSession(sessionId: string): Promise<void>;
35
40
  /** Rebinds future main executions only; running subagents keep their captured model providers. */
36
41
  reconfigureModels(input: {
@@ -3,7 +3,7 @@ import type { EnvironmentContextState } from '../runtime/context/contracts.js';
3
3
  import { type CodeModeStateEntry } from '../tools/code-mode/state.js';
4
4
  import { type TodoSnapshot } from '../tools/agent/todo.js';
5
5
  import { type ContextGcCheckpointDraft } from './context-gc-checkpoint.js';
6
- import type { SessionListInput, SessionSource } from './types.js';
6
+ import type { SessionListInput, SessionSource, SessionTitleUpdateInput } from './types.js';
7
7
  export type { SessionListInput, SessionSource } from './types.js';
8
8
  export interface SessionRecord {
9
9
  schemaVersion: 1;
@@ -118,10 +118,7 @@ export declare class JsonlSessionStore {
118
118
  lastActivityAt: string;
119
119
  source: SessionSource;
120
120
  }>;
121
- updateTitle(sessionId: string, title: string, input?: {
122
- executionId?: string;
123
- turnId?: string;
124
- }): Promise<void>;
121
+ updateTitle(sessionId: string, title: string, input?: SessionTitleUpdateInput, owner?: object): Promise<boolean>;
125
122
  /** Reset history with the same recovery journal while preserving the Session identity. */
126
123
  clearSession(sessionId: string): Promise<{
127
124
  id: string;
@@ -138,6 +135,7 @@ export declare class JsonlSessionStore {
138
135
  createdAt: string;
139
136
  lastActivityAt: string;
140
137
  source: SessionSource;
138
+ automaticTitlePending: boolean;
141
139
  }>;
142
140
  append(sessionId: string, input: {
143
141
  type: string;
@@ -745,21 +745,39 @@ export class JsonlSessionStore {
745
745
  await release();
746
746
  }
747
747
  }
748
- async updateTitle(sessionId, title, input = {}) {
748
+ async updateTitle(sessionId, title, input = {}, owner) {
749
749
  this.#assertOpen();
750
750
  const normalized = title.trim();
751
751
  if (!normalized)
752
752
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Session title is required.');
753
- const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
754
- if (!meta)
755
- throw new MarAgentError('MAR_AGENT_SESSION_NOT_FOUND', 'Session was not found.');
756
- await this.append(sessionId, {
757
- type: 'session.title.updated',
758
- payload: { title: normalized, automatic: true },
759
- ...(input.executionId ? { executionId: input.executionId } : {}),
760
- ...(input.turnId ? { turnId: input.turnId } : {})
761
- });
762
- meta.title = normalized;
753
+ const release = await this.acquireSessionLease(sessionId, owner);
754
+ try {
755
+ const meta = this.#shared.index.sessions.find((item) => item.id === sessionId);
756
+ if (input.expectedTitle !== undefined && meta.title !== input.expectedTitle)
757
+ return false;
758
+ if (input.automatic || input.executionId !== undefined) {
759
+ const records = await this.read(sessionId);
760
+ const latestTitle = records.findLast((record) => record.type === 'session.title.updated');
761
+ if (input.automatic &&
762
+ (records[0]?.payload?.automaticTitle !== true ||
763
+ latestTitle?.payload?.automatic === false))
764
+ return false;
765
+ if (input.executionId !== undefined &&
766
+ records.findLast((record) => record.type === 'turn.user')?.executionId !==
767
+ input.executionId)
768
+ return false;
769
+ }
770
+ await this.append(sessionId, {
771
+ type: 'session.title.updated',
772
+ payload: { title: normalized, automatic: input.automatic === true },
773
+ ...(input.executionId ? { executionId: input.executionId } : {}),
774
+ ...(input.turnId ? { turnId: input.turnId } : {})
775
+ });
776
+ return true;
777
+ }
778
+ finally {
779
+ await release();
780
+ }
763
781
  }
764
782
  /** Reset history with the same recovery journal while preserving the Session identity. */
765
783
  async clearSession(sessionId) {
@@ -896,7 +914,8 @@ export class JsonlSessionStore {
896
914
  workspace: meta.workspace,
897
915
  createdAt: meta.createdAt,
898
916
  lastActivityAt: meta.lastActivityAt,
899
- source: normalizeSessionSource(meta.source)
917
+ source: normalizeSessionSource(meta.source),
918
+ automaticTitlePending: meta.automaticTitlePending === true
900
919
  };
901
920
  }
902
921
  async append(sessionId, input) {
@@ -1373,6 +1392,11 @@ export class JsonlSessionStore {
1373
1392
  #applyRecordToResumeIndex(meta, record) {
1374
1393
  if (record.type === 'turn.user')
1375
1394
  meta.automaticTitlePending = false;
1395
+ if (record.type === 'session.title.updated' && isObject(record.payload)) {
1396
+ if (typeof record.payload.title === 'string')
1397
+ meta.title = record.payload.title;
1398
+ meta.automaticTitlePending = false;
1399
+ }
1376
1400
  const resume = meta.resume;
1377
1401
  if (resume?.version !== 1 || !Number.isSafeInteger(resume.todoRevision))
1378
1402
  return;
@@ -2136,6 +2160,8 @@ function deriveExecutionState(records) {
2136
2160
  record.payload?.automaticTitle === true;
2137
2161
  else if (record.type === 'turn.user')
2138
2162
  sawUserTurn = true;
2163
+ else if (record.type === 'session.title.updated')
2164
+ automaticTitle = false;
2139
2165
  else if (isCompactCheckpoint(record))
2140
2166
  compactSequence = record.sequence;
2141
2167
  else if (record.type === 'todo.updated') {
@@ -16,3 +16,9 @@ export interface SessionListInput {
16
16
  readonly parentSessionId?: string;
17
17
  readonly rootSessionId?: string;
18
18
  }
19
+ export interface SessionTitleUpdateInput {
20
+ readonly automatic?: boolean;
21
+ readonly expectedTitle?: string;
22
+ readonly executionId?: string;
23
+ readonly turnId?: string;
24
+ }
@@ -81,7 +81,7 @@ export class CodeCells {
81
81
  throw new MarAgentError('MAR_AGENT_CODE_CANCELLED', 'Code Exec resources are being released.');
82
82
  if ([...this.#cells.values()].filter((cell) => cell.status === 'running').length >=
83
83
  limits.codeMaxConcurrentCells)
84
- throw new MarAgentError('MAR_AGENT_PROCESS_LIMIT', 'The Code Exec cell limit was reached.');
84
+ throw new MarAgentError('MAR_AGENT_PROCESS_LIMIT', `The Local Host shared running-cell limit (${limits.codeMaxConcurrentCells}) was reached.`);
85
85
  const controller = new AbortController();
86
86
  const cell = {
87
87
  cellId: randomUUID(),
@@ -218,7 +218,7 @@ export class CodeModeTool {
218
218
  throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'Code Mode tool dispatch is unavailable.');
219
219
  context.signal.throwIfAborted();
220
220
  if (this.#active.size >= limits.codeMaxConcurrentCells)
221
- throw new MarAgentError('MAR_AGENT_PROCESS_LIMIT', 'The Code Mode process limit was reached.');
221
+ throw new MarAgentError('MAR_AGENT_PROCESS_LIMIT', `The Local Host shared running-cell limit (${limits.codeMaxConcurrentCells}) was reached.`);
222
222
  const controller = new AbortController();
223
223
  const signal = AbortSignal.any([context.signal, controller.signal]);
224
224
  let release;
@@ -42,14 +42,19 @@ export async function createCodeModeEngine(options) {
42
42
  if (Date.now() >= deadline)
43
43
  throw diagnostic('MAR_AGENT_CODE_TIMEOUT', 'JavaScript execution exceeded its deadline.');
44
44
  let message = String(error?.message ?? error).slice(0, 4096);
45
+ const syntaxError = locateSource && error?.name === 'SyntaxError';
46
+ if (syntaxError)
47
+ message = `JavaScript compilation failed before tool execution: ${message}`.slice(0, 4096);
45
48
  const location = locateSource ? compilerSourceLocation(error, input.code) : '';
46
49
  if (location)
47
50
  message = message.slice(0, 4096 - location.length) + location;
48
51
  const code = message.includes('out of memory')
49
52
  ? 'MAR_AGENT_CODE_MEMORY_LIMIT'
50
- : typeof error?.code === 'string'
51
- ? error.code
52
- : 'MAR_AGENT_CODE_FAILED';
53
+ : syntaxError
54
+ ? 'MAR_AGENT_CODE_SYNTAX_ERROR'
55
+ : typeof error?.code === 'string'
56
+ ? error.code
57
+ : 'MAR_AGENT_CODE_FAILED';
53
58
  throw diagnostic(code, message);
54
59
  };
55
60
  const guestError = (code, message) => {
@@ -13,7 +13,7 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
13
13
  execMaxPollYieldMs: 300000;
14
14
  execTerminationGraceMs: 1000;
15
15
  execDefaultMaxOutputBytes: 1000000;
16
- maxProcesses: 8;
16
+ maxProcesses: 64;
17
17
  execMaxCompletedResults: 16;
18
18
  execCompletedRetentionMs: 300000;
19
19
  codeMaxInputBytes: 1048576;
@@ -21,7 +21,7 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
21
21
  codeMaxFrameBytes: 33554432;
22
22
  codeMaxTransportBytes: 67108864;
23
23
  codeMaxCalls: 128;
24
- codeMaxConcurrentCells: 4;
24
+ codeMaxConcurrentCells: 64;
25
25
  codeDefaultYieldMs: 30000;
26
26
  codeDefaultWaitYieldMs: 10000;
27
27
  codeMaxYieldMs: 30000;
@@ -13,7 +13,7 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
13
13
  execMaxPollYieldMs: 300_000,
14
14
  execTerminationGraceMs: 1_000,
15
15
  execDefaultMaxOutputBytes: 1_000_000,
16
- maxProcesses: 8,
16
+ maxProcesses: 64,
17
17
  execMaxCompletedResults: 16,
18
18
  execCompletedRetentionMs: 300_000,
19
19
  codeMaxInputBytes: 1_048_576,
@@ -21,7 +21,7 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
21
21
  codeMaxFrameBytes: 33_554_432,
22
22
  codeMaxTransportBytes: 67_108_864,
23
23
  codeMaxCalls: 128,
24
- codeMaxConcurrentCells: 4,
24
+ codeMaxConcurrentCells: 64,
25
25
  codeDefaultYieldMs: 30_000,
26
26
  codeDefaultWaitYieldMs: 10_000,
27
27
  codeMaxYieldMs: 30_000,
@@ -161,7 +161,7 @@ export class ExecTool {
161
161
  return result;
162
162
  }
163
163
  if (this.#processes.size + this.#startingProcesses.size >= this.maxProcesses)
164
- throw new MarAgentError('MAR_AGENT_PROCESS_LIMIT', 'The managed process limit was reached.');
164
+ throw new MarAgentError('MAR_AGENT_PROCESS_LIMIT', `The Local Host shared managed-process limit (${this.maxProcesses}) was reached.`);
165
165
  let resolveStarting;
166
166
  const starting = {
167
167
  ...(owner === undefined ? {} : { ownerSessionId: owner.sessionId }),
package/dist/cli/main.js CHANGED
@@ -12,6 +12,7 @@ import { OpenAiResponsesAdapter } from '../agent-core/model/openai-responses.js'
12
12
  import { DEFAULT_COMPACTION_FOCUS } from '../agent-core/runtime/compaction/history.js';
13
13
  import { createMarAgent } from '../agent-core/sdk/agent.js';
14
14
  import { CodexAuthFileCredentialProvider } from './codex-auth-file.js';
15
+ import { generateSessionTitle } from '../shared/auto-title.js';
15
16
  const HELP = `maragent - MAR coding agent
16
17
 
17
18
  Usage:
@@ -19,7 +20,7 @@ Usage:
19
20
  maragent run <prompt> [--reasoning-effort EFFORT] [--format text|json|stream-json] [--codex-auth-file PATH]
20
21
  maragent resume [sessionId] [--reasoning-effort EFFORT] [--codex-auth-file PATH]
21
22
  maragent sessions list|delete <sessionId>
22
- maragent models list|add|edit|remove|test [id] [--default-reasoning-effort EFFORT] [--code-mode|--no-code-mode] [--high-density-compaction|--no-high-density-compaction] [--responses-encoding standard|lite] [--fast-mode|--no-fast-mode] [--codex-auth-file PATH]
23
+ maragent models list|add|edit|remove|test [id] [--default-reasoning-effort EFFORT] [--code-mode|--no-code-mode] [--high-density-compaction|--no-high-density-compaction] [--fast-mode|--no-fast-mode] [--codex-auth-file PATH]
23
24
  `;
24
25
  function takeOption(arguments_, name) {
25
26
  const index = arguments_.indexOf(name);
@@ -62,6 +63,8 @@ async function readApiKey() {
62
63
  return value.trim();
63
64
  }
64
65
  async function modelsCommand(home, arguments_, codexAuthFile) {
66
+ if (arguments_.includes('--responses-encoding'))
67
+ throw new MarAgentError('MAR_AGENT_CLI_ARGUMENT_INVALID', 'Responses encoding is no longer configurable.');
65
68
  const action = arguments_[1];
66
69
  const catalog = await loadCatalog(home);
67
70
  if (action === 'list') {
@@ -140,15 +143,6 @@ async function modelsCommand(home, arguments_, codexAuthFile) {
140
143
  if (credential === 'codex-auth-file' && readsApiKey)
141
144
  throw new MarAgentError('MAR_AGENT_CLI_ARGUMENT_INVALID', '--api-key-stdin cannot be combined with an external credential.');
142
145
  const responsesTransport = parseResponsesTransport(option('--responses-transport'), protocol, existing?.responsesTransport);
143
- const encodingOption = option('--responses-encoding');
144
- if (encodingOption !== undefined &&
145
- (protocol !== 'OPENAI_RESPONSES' || !['standard', 'lite'].includes(encodingOption)))
146
- throw new MarAgentError('MAR_AGENT_CLI_ARGUMENT_INVALID', '--responses-encoding is invalid.');
147
- const responsesEncoding = protocol === 'OPENAI_RESPONSES'
148
- ? (encodingOption?.toUpperCase() ??
149
- existing?.responsesEncoding ??
150
- 'STANDARD')
151
- : undefined;
152
146
  const reasoningEffortsOption = option('--reasoning-efforts');
153
147
  const reasoningEfforts = reasoningEffortsOption === undefined
154
148
  ? (existing?.reasoningEfforts ?? [])
@@ -198,7 +192,6 @@ async function modelsCommand(home, arguments_, codexAuthFile) {
198
192
  : {}),
199
193
  hostedWebSearch: booleanFlag(arguments_, '--web-search', '--no-web-search', existing?.hostedWebSearch ?? false),
200
194
  ...(responsesTransport === undefined ? {} : { responsesTransport }),
201
- ...(responsesEncoding === undefined ? {} : { responsesEncoding }),
202
195
  responsesPreviousResponseId: booleanFlag(arguments_, '--responses-continuation', '--no-responses-continuation', existing?.responsesPreviousResponseId ?? false),
203
196
  enabled: booleanFlag(arguments_, '--enabled', '--disabled', existing?.enabled ?? true)
204
197
  };
@@ -321,6 +314,8 @@ async function main() {
321
314
  workspace
322
315
  });
323
316
  let activeHandle;
317
+ let titleController;
318
+ let titleTask;
324
319
  let interrupts = 0;
325
320
  const interrupt = () => {
326
321
  interrupts++;
@@ -328,6 +323,7 @@ async function main() {
328
323
  activeHandle.cancel();
329
324
  else
330
325
  terminal?.close();
326
+ titleController?.abort();
331
327
  process.exitCode = 130;
332
328
  if (interrupts > 1)
333
329
  activeHandle?.cancel();
@@ -345,6 +341,11 @@ async function main() {
345
341
  ...(modelId === undefined ? {} : { modelId }),
346
342
  ...(reasoningEffort === undefined ? {} : { reasoningEffort })
347
343
  };
344
+ const metadata = !resumeId && invocation.mode !== 'compact'
345
+ ? await agent.getSession(session.id).catch(() => undefined)
346
+ : undefined;
347
+ titleController = new AbortController();
348
+ titleTask = undefined;
348
349
  const handle = invocation.mode === 'plan'
349
350
  ? session.plan(runInput)
350
351
  : invocation.mode === 'compact'
@@ -352,12 +353,33 @@ async function main() {
352
353
  : session.run(runInput);
353
354
  activeHandle = handle;
354
355
  for await (const event of handle) {
356
+ if (event.type === 'execution.started' && metadata?.automaticTitlePending && !titleTask)
357
+ titleTask = generateSessionTitle({
358
+ models: runtime.models,
359
+ currentModelId: modelId ??
360
+ catalog.defaultModelId ??
361
+ runtime.models.find((model) => model.enabled).id,
362
+ prompt: invocation.prompt,
363
+ signal: titleController.signal
364
+ }).catch(() => undefined);
355
365
  if (format === 'stream-json')
356
366
  stdout.write(`${JSON.stringify(event)}\n`);
357
367
  else if (event.type === 'message.delta')
358
368
  stdout.write(event.delta);
359
369
  }
360
370
  const result = await handle.result();
371
+ if (result.status === 'cancelled')
372
+ titleController.abort();
373
+ const title = await titleTask;
374
+ if (title && !titleController.signal.aborted)
375
+ await agent
376
+ .setSessionTitle(session.id, title, {
377
+ automatic: true,
378
+ expectedTitle: metadata.title,
379
+ executionId: handle.executionId
380
+ })
381
+ .catch(() => undefined);
382
+ titleController = undefined;
361
383
  activeHandle = undefined;
362
384
  if (format === 'json')
363
385
  stdout.write(`${JSON.stringify(result)}\n`);
@@ -368,6 +390,8 @@ async function main() {
368
390
  } while (interactive);
369
391
  }
370
392
  finally {
393
+ titleController?.abort();
394
+ await titleTask;
371
395
  process.off('SIGINT', interrupt);
372
396
  }
373
397
  }
@@ -391,7 +415,6 @@ function runtimeModels(models, codexAuthFile) {
391
415
  : [
392
416
  {
393
417
  ...configuration,
394
- responsesEncoding: configuration.responsesEncoding ?? 'STANDARD',
395
418
  responsesConfigurationUpdates: true,
396
419
  credentialProvider: provider
397
420
  }
package/dist/index.d.ts CHANGED
@@ -8,7 +8,8 @@ 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 type { CreateMarAgentOptions, CreateSessionInput, ExecutionHandle, MarAgent, MarAgentSession } from './agent-core/sdk/types.js';
11
+ export { generateSessionTitle, type GenerateSessionTitleInput } from './shared/auto-title.js';
12
+ export type { CreateMarAgentOptions, CreateSessionInput, ExecutionHandle, MarAgent, MarAgentSession, SessionTitleUpdateInput } from './agent-core/sdk/types.js';
12
13
  export { openMarAgentSessionCatalog, type MarAgentContextUsage, type MarAgentHistoryEntry, type MarAgentHistoryPage, type MarAgentSessionCatalog, type MarAgentSessionDeleteResult, type MarAgentSessionImage, type MarAgentSessionPage, type MarAgentSessionSummary } from './agent-core/session/catalog.js';
13
14
  export type { SessionListInput, SessionSource } from './agent-core/session/types.js';
14
15
  export type { ApplyPatchFileResult, ApplyPatchResult, ClientToolDefinition, MarAgentToolArtifact, MarAgentToolImageArtifact, McpCatalogTool, McpToolCallContext, McpToolCatalog, McpToolCatalogContext, ToolCall, ToolCaller, ToolExecutionContext, ToolExecutionResult } from './agent-core/tools/contracts.js';
package/dist/index.js CHANGED
@@ -4,5 +4,6 @@ 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 { generateSessionTitle } from './shared/auto-title.js';
7
8
  export { openMarAgentSessionCatalog } from './agent-core/session/catalog.js';
8
9
  export { MCP_DEFINITION_LIMITS, assertMcpToolCatalog, assertMcpToolDefinition } from './agent-core/tools/mcp/catalog.js';
@@ -0,0 +1,11 @@
1
+ import { type MarAgentModelConfiguration } from '../agent-core/model/configuration.js';
2
+ export interface GenerateSessionTitleInput {
3
+ readonly models: readonly MarAgentModelConfiguration[];
4
+ readonly currentModelId: string;
5
+ readonly prompt: string;
6
+ /** Used only when the user message does not establish a language. */
7
+ readonly defaultLanguage?: string;
8
+ readonly signal: AbortSignal;
9
+ }
10
+ /** Generate a title without a Session or Host. Credential providers remain caller-owned. */
11
+ export declare function generateSessionTitle(input: GenerateSessionTitleInput): Promise<string | undefined>;
@@ -0,0 +1,67 @@
1
+ import { MarAgentError } from '../agent-core/error.js';
2
+ import { AnthropicMessagesAdapter } from '../agent-core/model/anthropic-messages.js';
3
+ import { resolveModelDefaultReasoningEffort } from '../agent-core/model/configuration.js';
4
+ import { OpenAiResponsesAdapter } from '../agent-core/model/openai-responses.js';
5
+ const sessionTitlePrompt = `You are a session title generator. Output only one title and nothing else.
6
+
7
+ Create a brief title that helps the user find this conversation later.
8
+ - Use the same language as the user message.
9
+ - Output exactly one non-empty line with at most 50 characters.
10
+ - Focus on the main topic or requested outcome.
11
+ - Preserve exact technical terms, numbers, and filenames.
12
+ - Do not answer the request, explain the title, mention tools, or add quotation marks.
13
+ - For a minimal conversational message, still produce a meaningful title reflecting its intent.`;
14
+ /** Generate a title without a Session or Host. Credential providers remain caller-owned. */
15
+ export async function generateSessionTitle(input) {
16
+ const defaultLanguage = input.defaultLanguage?.trim();
17
+ if (input.defaultLanguage !== undefined && (!defaultLanguage || defaultLanguage.length > 128))
18
+ throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Default title language is invalid.');
19
+ const models = input.models.filter((model) => model.enabled);
20
+ const model = models.find((candidate) => candidate.titleGeneration) ??
21
+ models.find((candidate) => candidate.id === input.currentModelId);
22
+ if (!model)
23
+ throw new MarAgentError('MAR_AGENT_MODEL_DISABLED', 'Selected title model is unavailable.');
24
+ const signal = AbortSignal.any([input.signal, AbortSignal.timeout(30_000)]);
25
+ signal.throwIfAborted();
26
+ const adapter = model.protocol === 'OPENAI_RESPONSES'
27
+ ? new OpenAiResponsesAdapter(model)
28
+ : new AnthropicMessagesAdapter(model);
29
+ let titleInput = '';
30
+ let characters = 0;
31
+ for (const character of input.prompt) {
32
+ if (characters++ >= 1000)
33
+ break;
34
+ titleInput += character;
35
+ }
36
+ let output = '';
37
+ let completed = false;
38
+ try {
39
+ for await (const event of adapter.start({
40
+ retryState: { attempts: 0, maxAttempts: 1 },
41
+ system: defaultLanguage
42
+ ? `${sessionTitlePrompt}\n- If the user message does not establish a language, use ${JSON.stringify(defaultLanguage)} as the default language.`
43
+ : sessionTitlePrompt,
44
+ messages: [{ role: 'user', content: titleInput }],
45
+ tools: [],
46
+ allowTools: false,
47
+ reasoningEffort: resolveModelDefaultReasoningEffort(model)
48
+ }, signal)) {
49
+ if (event.type === 'text.completed')
50
+ output = event.text;
51
+ else if (event.type === 'completed')
52
+ completed = event.reason === 'completed';
53
+ }
54
+ signal.throwIfAborted();
55
+ if (!completed)
56
+ return undefined;
57
+ const line = output
58
+ .replace(/<think>[\s\S]*?<\/think>\s*/giu, '')
59
+ .split(/\r?\n/u)
60
+ .map((value) => value.trim())
61
+ .find(Boolean);
62
+ return line ? [...line].slice(0, 50).join('') : undefined;
63
+ }
64
+ finally {
65
+ await adapter.close?.();
66
+ }
67
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.144",
3
+ "version": "0.9.146",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- export declare const sessionTitlePrompt = "You are a session title generator. Output only one title and nothing else.\n\nCreate a brief title that helps the user find this conversation later.\n- Use the same language as the user message.\n- Output exactly one non-empty line with at most 50 characters.\n- Focus on the main topic or requested outcome.\n- Preserve exact technical terms, numbers, and filenames.\n- Do not answer the request, explain the title, mention tools, or add quotation marks.\n- For a minimal conversational message, still produce a meaningful title reflecting its intent.";
@@ -1,9 +0,0 @@
1
- export const sessionTitlePrompt = `You are a session title generator. Output only one title and nothing else.
2
-
3
- Create a brief title that helps the user find this conversation later.
4
- - Use the same language as the user message.
5
- - Output exactly one non-empty line with at most 50 characters.
6
- - Focus on the main topic or requested outcome.
7
- - Preserve exact technical terms, numbers, and filenames.
8
- - Do not answer the request, explain the title, mention tools, or add quotation marks.
9
- - For a minimal conversational message, still produce a meaningful title reflecting its intent.`;
@@ -1,3 +0,0 @@
1
- import type { MarAgentReasoningEffort } from '../model/configuration.js';
2
- import type { ModelAdapter, ModelAttemptDiagnostic } from '../model/contracts.js';
3
- export declare function generateSessionTitle(adapter: ModelAdapter, prompt: string, reasoningEffort: MarAgentReasoningEffort, signal: AbortSignal, onAttemptDiagnostic: (diagnostic: ModelAttemptDiagnostic) => Promise<void> | void): Promise<string | undefined>;
@@ -1,42 +0,0 @@
1
- import { sessionTitlePrompt } from '../prompts/title.js';
2
- import { createModelTurnSession } from './model-runtime.js';
3
- export async function generateSessionTitle(adapter, prompt, reasoningEffort, signal, onAttemptDiagnostic) {
4
- let titleInput = '';
5
- let characters = 0;
6
- for (const character of prompt) {
7
- if (characters++ >= 1000)
8
- break;
9
- titleInput += character;
10
- }
11
- let output = '';
12
- let completed = false;
13
- const turn = createModelTurnSession(adapter);
14
- try {
15
- for await (const event of turn.start({
16
- retryState: { attempts: 0, maxAttempts: 1 },
17
- system: sessionTitlePrompt,
18
- messages: [{ role: 'user', content: titleInput }],
19
- tools: [],
20
- reasoningEffort,
21
- onAttemptDiagnostic
22
- }, signal)) {
23
- if (event.type === 'text.completed')
24
- output = event.text;
25
- else if (event.type === 'completed')
26
- completed = event.reason === 'completed';
27
- }
28
- }
29
- finally {
30
- await turn.close();
31
- }
32
- if (!completed)
33
- return undefined;
34
- const line = output
35
- .replace(/<think>[\s\S]*?<\/think>\s*/giu, '')
36
- .split(/\r?\n/u)
37
- .map((value) => value.trim())
38
- .find(Boolean);
39
- if (!line)
40
- return undefined;
41
- return [...line].slice(0, 50).join('');
42
- }