@myagentroam/agent 0.9.83 → 0.9.84

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.
@@ -1,4 +1,13 @@
1
1
  import type { MarAgentModelConfiguration, MarAgentReasoningEffort, ModelCredentialAcquireReason } from './configuration.js';
2
+ export interface ModelContinuationCheckpoint {
3
+ readonly version: 1;
4
+ readonly protocol: 'OPENAI_RESPONSES';
5
+ readonly configurationHash: string;
6
+ readonly responseId: string;
7
+ readonly requestPropertiesHash: string;
8
+ readonly inputPrefixLength: number;
9
+ readonly inputPrefixHash: string;
10
+ }
2
11
  export interface ClientToolDefinition {
3
12
  name: string;
4
13
  description: string;
@@ -11,7 +20,7 @@ export interface ClientToolDefinition {
11
20
  export interface ModelMessage {
12
21
  role: 'user' | 'assistant' | 'tool' | 'tool_call' | 'provider';
13
22
  content: string;
14
- contextKind?: 'environment';
23
+ contextKind?: 'environment' | 'private';
15
24
  callId?: string;
16
25
  name?: string;
17
26
  arguments?: unknown;
@@ -146,11 +155,18 @@ export interface ModelAdapter {
146
155
  readonly configuration: MarAgentModelConfiguration;
147
156
  start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
148
157
  createTurnSession?(): ModelTurnSession;
158
+ /**
159
+ * Prepares an equivalent model rebind without mutating live state.
160
+ * The returned commit callback must be synchronous and non-throwing.
161
+ */
162
+ prepareReconfigure?(configuration: MarAgentModelConfiguration): (() => void) | undefined;
163
+ restoreContinuation?(checkpoint: ModelContinuationCheckpoint): boolean;
149
164
  close?(): Promise<void>;
150
165
  }
151
166
  export interface ModelTurnSession {
152
167
  start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
153
168
  /** Drops incremental response state after a local context replacement. */
154
169
  resetContinuation?(): void;
170
+ continuationCheckpoint?(): ModelContinuationCheckpoint | undefined;
155
171
  close(): Promise<void>;
156
172
  }
@@ -1,7 +1,7 @@
1
1
  import { MarAgentError } from '../error.js';
2
2
  import { type MarAgentModelConfiguration, type ModelCredentialAcquireReason } from './configuration.js';
3
3
  import WebSocket from 'ws';
4
- import type { ModelAdapter, ModelAttemptDiagnostic, ModelEvent, ModelRequest, ModelTurnSession } from './contracts.js';
4
+ import type { ModelAdapter, ModelAttemptDiagnostic, ModelContinuationCheckpoint, ModelEvent, ModelRequest, ModelTurnSession } from './contracts.js';
5
5
  export declare const OPENAI_RESPONSES_EARLY_EOF_REASON = "OPENAI_RESPONSES_EARLY_EOF";
6
6
  export declare const OPENAI_RESPONSES_WEBSOCKET_CONNECTION_LIMIT_REASON = "OPENAI_RESPONSES_WEBSOCKET_CONNECTION_LIMIT";
7
7
  type ResponsesRequestBody = Readonly<Record<string, unknown>> & {
@@ -11,6 +11,7 @@ interface ResponsesSessionState {
11
11
  socket?: WebSocket;
12
12
  endpoint?: string;
13
13
  credentialRevision?: number;
14
+ credentialFingerprint?: string;
14
15
  connectionId?: string;
15
16
  proxyType?: 'DIRECT' | 'SOCKS5';
16
17
  lastRequest?: ResponsesRequestBody;
@@ -18,18 +19,20 @@ interface ResponsesSessionState {
18
19
  readonly responseId: string;
19
20
  readonly outputItems: readonly Record<string, unknown>[];
20
21
  };
22
+ restoredContinuation?: ModelContinuationCheckpoint;
21
23
  }
22
24
  interface PreparedResponsesRequest {
23
25
  readonly fullBody: ResponsesRequestBody;
24
26
  readonly body: ResponsesRequestBody;
25
27
  readonly continuation: boolean;
26
- readonly requiresExistingSocket: boolean;
27
28
  }
28
29
  export declare class OpenAiResponsesAdapter implements ModelAdapter {
29
30
  #private;
30
- readonly configuration: MarAgentModelConfiguration;
31
31
  readonly prefixIdentity: `${string}-${string}-${string}-${string}-${string}`;
32
32
  constructor(configuration: MarAgentModelConfiguration);
33
+ get configuration(): MarAgentModelConfiguration;
34
+ prepareReconfigure(configuration: MarAgentModelConfiguration): (() => void) | undefined;
35
+ restoreContinuation(checkpoint: ModelContinuationCheckpoint): boolean;
33
36
  start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
34
37
  createTurnSession(): ModelTurnSession;
35
38
  close(): Promise<void>;
@@ -47,6 +50,7 @@ declare class OpenAiResponsesTurnSession implements ModelTurnSession {
47
50
  start(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
48
51
  close(): Promise<void>;
49
52
  resetContinuation(): void;
53
+ continuationCheckpoint(): ModelContinuationCheckpoint | undefined;
50
54
  prepareRequest(request: ModelRequest): PreparedResponsesRequest;
51
55
  fullRequestBody(): ResponsesRequestBody;
52
56
  currentTurnState(): string | undefined;
@@ -61,7 +65,6 @@ declare class OpenAiResponsesTurnSession implements ModelTurnSession {
61
65
  readonly reason: ModelCredentialAcquireReason;
62
66
  readonly signal: AbortSignal;
63
67
  readonly continuation: boolean;
64
- readonly requiresExistingSocket: boolean;
65
68
  readonly credentialAcquired: (revision: number) => void;
66
69
  }): Promise<WebSocket>;
67
70
  private connectWebSocket;
@@ -13,13 +13,33 @@ const X_CODEX_TURN_STATE = 'x-codex-turn-state';
13
13
  const RESPONSES_LITE_HEADER = 'x-openai-internal-codex-responses-lite';
14
14
  const RESPONSES_LITE_METADATA = 'ws_request_header_x_openai_internal_codex_responses_lite';
15
15
  export class OpenAiResponsesAdapter {
16
- configuration;
17
16
  prefixIdentity = randomUUID();
18
17
  #cachedState = {};
19
18
  #cacheLeased = false;
20
19
  #closed = false;
20
+ #configuration;
21
21
  constructor(configuration) {
22
- this.configuration = configuration;
22
+ this.#configuration = configuration;
23
+ }
24
+ get configuration() {
25
+ return this.#configuration;
26
+ }
27
+ prepareReconfigure(configuration) {
28
+ if (!sameResponsesAdapterConfiguration(this.#configuration, configuration))
29
+ return undefined;
30
+ return () => {
31
+ this.#configuration = configuration;
32
+ };
33
+ }
34
+ restoreContinuation(checkpoint) {
35
+ if (this.#cacheLeased ||
36
+ this.#cachedState.lastRequest !== undefined ||
37
+ this.#cachedState.lastResponse !== undefined ||
38
+ checkpoint.protocol !== 'OPENAI_RESPONSES' ||
39
+ checkpoint.configurationHash !== responsesConfigurationHash(this.#configuration))
40
+ return false;
41
+ this.#cachedState.restoredContinuation = checkpoint;
42
+ return true;
23
43
  }
24
44
  async *start(request, signal) {
25
45
  const turn = new OpenAiResponsesTurnSession(this, {}, false, true);
@@ -53,14 +73,13 @@ export class OpenAiResponsesAdapter {
53
73
  return;
54
74
  }
55
75
  if (state.socket !== undefined && state.socket.readyState !== WebSocket.OPEN)
56
- invalidateResponsesWebSocket(state, true);
76
+ invalidateResponsesWebSocket(state, false);
57
77
  this.#cachedState = state;
58
78
  this.#cacheLeased = false;
59
79
  }
60
80
  async *startTurn(request, signal, turn, prepared) {
61
81
  let useContinuation = prepared.continuation;
62
82
  let requestBody = prepared.body;
63
- let requiresExistingSocket = prepared.requiresExistingSocket;
64
83
  let credentialReason = 'REQUEST';
65
84
  let unauthorizedRetryUsed = false;
66
85
  let invalidContinuationFallbackUsed = false;
@@ -87,7 +106,7 @@ export class OpenAiResponsesAdapter {
87
106
  credentialReason: attemptCredentialReason
88
107
  });
89
108
  try {
90
- for await (const event of this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, requiresExistingSocket, turn)) {
109
+ for await (const event of this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, turn)) {
91
110
  if (!sawSemanticEvent && !isSemanticEvent(event)) {
92
111
  prelude.push(event);
93
112
  continue;
@@ -143,7 +162,6 @@ export class OpenAiResponsesAdapter {
143
162
  invalidContinuationFallbackUsed = true;
144
163
  useContinuation = false;
145
164
  requestBody = prepared.fullBody;
146
- requiresExistingSocket = false;
147
165
  turn.invalidateWebSocket();
148
166
  credentialReason = 'RECONNECT';
149
167
  await reportFailure(true, {
@@ -192,14 +210,13 @@ export class OpenAiResponsesAdapter {
192
210
  : 'REQUEST';
193
211
  useContinuation = false;
194
212
  requestBody = prepared.fullBody;
195
- requiresExistingSocket = false;
196
213
  if (error instanceof MarAgentError && error.details?.transport === 'WEBSOCKET')
197
214
  turn.invalidateWebSocket();
198
215
  await waitForModelRetry(retryAfter(error), attempt, signal, delayMs);
199
216
  }
200
217
  }
201
218
  }
202
- async *startAttempt(request, signal, body, continuation, credentialReason, requiresExistingSocket, turn) {
219
+ async *startAttempt(request, signal, body, continuation, credentialReason, turn) {
203
220
  let latestCredentialRevision;
204
221
  const fetchResponse = async (requestBody) => {
205
222
  const serializedBody = JSON.stringify(requestBody);
@@ -233,7 +250,6 @@ export class OpenAiResponsesAdapter {
233
250
  reason: credentialReason,
234
251
  signal,
235
252
  continuation,
236
- requiresExistingSocket,
237
253
  turn,
238
254
  credentialAcquired: (revision) => (latestCredentialRevision = revision)
239
255
  });
@@ -517,6 +533,15 @@ export class OpenAiResponsesAdapter {
517
533
  });
518
534
  }
519
535
  }
536
+ function sameResponsesAdapterConfiguration(current, next) {
537
+ const { apiKey: currentApiKey, credentialProvider: currentCredentialProvider, imageGeneration: _currentImageGeneration, ...currentTransport } = current;
538
+ const { apiKey: nextApiKey, credentialProvider: nextCredentialProvider, imageGeneration: _nextImageGeneration, ...nextTransport } = next;
539
+ const credentialsAreRebindable = (currentCredentialProvider !== undefined && nextCredentialProvider !== undefined) ||
540
+ (currentApiKey !== undefined &&
541
+ nextApiKey !== undefined &&
542
+ currentApiKey.reveal() === nextApiKey.reveal());
543
+ return credentialsAreRebindable && isDeepStrictEqual(currentTransport, nextTransport);
544
+ }
520
545
  class OpenAiResponsesTurnSession {
521
546
  adapter;
522
547
  state;
@@ -561,7 +586,7 @@ class OpenAiResponsesTurnSession {
561
586
  for await (const event of this.adapter.startTurn({
562
587
  ...request,
563
588
  onAttemptDiagnostic: async (diagnostic) => request.onAttemptDiagnostic?.({ ...diagnostic, prewarm: true })
564
- }, signal, this, { fullBody: warmBody, body: warmBody, continuation: false, requiresExistingSocket: false })) {
589
+ }, signal, this, { fullBody: warmBody, body: warmBody, continuation: false })) {
565
590
  if (event.type === 'response.started' && event.responseId !== 'unknown')
566
591
  warmId = event.responseId;
567
592
  if (event.type === 'completed')
@@ -607,6 +632,7 @@ class OpenAiResponsesTurnSession {
607
632
  (outputItems.length > 0 || !sawUnrepresentedOutput)) {
608
633
  this.state.lastRequest = prepared.fullBody;
609
634
  this.state.lastResponse = { responseId, outputItems };
635
+ delete this.state.restoredContinuation;
610
636
  }
611
637
  else
612
638
  clearResponsesContinuation(this.state);
@@ -629,17 +655,31 @@ class OpenAiResponsesTurnSession {
629
655
  resetContinuation() {
630
656
  clearResponsesContinuation(this.state);
631
657
  }
658
+ continuationCheckpoint() {
659
+ const request = this.state.lastRequest;
660
+ const response = this.state.lastResponse;
661
+ if (request === undefined || response === undefined)
662
+ return this.state.restoredContinuation;
663
+ const baseline = [...request.input, ...response.outputItems];
664
+ return {
665
+ version: 1,
666
+ protocol: 'OPENAI_RESPONSES',
667
+ configurationHash: responsesConfigurationHash(this.adapter.configuration),
668
+ responseId: response.responseId,
669
+ requestPropertiesHash: responsesRequestPropertiesHash(request),
670
+ inputPrefixLength: baseline.length,
671
+ inputPrefixHash: hashResponsesValue(baseline)
672
+ };
673
+ }
632
674
  prepareRequest(request) {
633
675
  if (this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET' &&
634
676
  this.state.socket !== undefined &&
635
677
  this.state.socket.readyState !== WebSocket.OPEN)
636
- invalidateResponsesWebSocket(this.state, true);
678
+ invalidateResponsesWebSocket(this.state, false);
637
679
  const fullBody = responsesRequestBody(this.adapter.configuration, request, request.messages, this.adapter.prefixIdentity);
638
680
  if (this.adapter.configuration.responsesPreviousResponseId) {
639
681
  const incremental = incrementalResponsesInput(this.state, fullBody);
640
- if (incremental !== undefined &&
641
- (this.adapter.configuration.responsesTransport?.transport !== 'WEBSOCKET' ||
642
- this.state.socket?.readyState === WebSocket.OPEN)) {
682
+ if (incremental !== undefined) {
643
683
  const responseId = this.state.lastResponse.responseId;
644
684
  delete this.state.lastResponse;
645
685
  return {
@@ -649,8 +689,7 @@ class OpenAiResponsesTurnSession {
649
689
  input: incremental,
650
690
  previous_response_id: responseId
651
691
  },
652
- continuation: true,
653
- requiresExistingSocket: this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET'
692
+ continuation: true
654
693
  };
655
694
  }
656
695
  if (this.allowExternalContinuation &&
@@ -664,16 +703,14 @@ class OpenAiResponsesTurnSession {
664
703
  input: deltaBody.input,
665
704
  previous_response_id: request.continuation.previousResponseId
666
705
  },
667
- continuation: true,
668
- requiresExistingSocket: false
706
+ continuation: true
669
707
  };
670
708
  }
671
709
  }
672
710
  return {
673
711
  fullBody,
674
712
  body: fullBody,
675
- continuation: false,
676
- requiresExistingSocket: false
713
+ continuation: false
677
714
  };
678
715
  }
679
716
  fullRequestBody() {
@@ -714,14 +751,9 @@ class OpenAiResponsesTurnSession {
714
751
  async webSocketForRequest(input) {
715
752
  const endpoint = webSocketEndpoint(input.configuration.baseUrl);
716
753
  const existingSocket = this.state.socket;
717
- if (input.requiresExistingSocket &&
718
- (existingSocket?.readyState !== WebSocket.OPEN || this.state.endpoint !== endpoint)) {
719
- invalidateResponsesWebSocket(this.state, true);
720
- throw unavailableContinuationConnection();
721
- }
722
754
  if (existingSocket !== undefined &&
723
755
  (existingSocket.readyState !== WebSocket.OPEN || this.state.endpoint !== endpoint))
724
- invalidateResponsesWebSocket(this.state, true);
756
+ invalidateResponsesWebSocket(this.state, false);
725
757
  if (this.state.socket === undefined) {
726
758
  const handshakeCredential = await acquireResponsesCredential(input.configuration, input.reason);
727
759
  input.credentialAcquired(handshakeCredential.revision);
@@ -729,10 +761,9 @@ class OpenAiResponsesTurnSession {
729
761
  }
730
762
  const requestCredential = await acquireResponsesCredential(input.configuration, input.reason);
731
763
  input.credentialAcquired(requestCredential.revision);
732
- if (requestCredential.revision !== this.state.credentialRevision) {
733
- invalidateResponsesWebSocket(this.state, true);
734
- if (input.continuation)
735
- throw unavailableContinuationConnection();
764
+ if (requestCredential.revision !== this.state.credentialRevision ||
765
+ responsesCredentialFingerprint(requestCredential) !== this.state.credentialFingerprint) {
766
+ invalidateResponsesWebSocket(this.state, false);
736
767
  await this.connectWebSocket(endpoint, requestCredential, input.promptCacheKey, input.signal);
737
768
  }
738
769
  if (input.signal.aborted) {
@@ -787,6 +818,7 @@ class OpenAiResponsesTurnSession {
787
818
  this.state.socket = socket;
788
819
  this.state.endpoint = endpoint;
789
820
  this.state.credentialRevision = credential.revision;
821
+ this.state.credentialFingerprint = responsesCredentialFingerprint(credential);
790
822
  this.state.connectionId = connectionId;
791
823
  this.state.proxyType = proxyType;
792
824
  }
@@ -864,9 +896,18 @@ function uuidV5(namespace, value) {
864
896
  function incrementalResponsesInput(state, current) {
865
897
  const previous = state.lastRequest;
866
898
  const completion = state.lastResponse;
867
- if (previous === undefined ||
868
- completion === undefined ||
869
- !responsesRequestPropertiesMatch(previous, current))
899
+ if (previous === undefined || completion === undefined) {
900
+ const restored = state.restoredContinuation;
901
+ if (restored === undefined ||
902
+ restored.requestPropertiesHash !== responsesRequestPropertiesHash(current) ||
903
+ current.input.length < restored.inputPrefixLength ||
904
+ restored.inputPrefixHash !==
905
+ hashResponsesValue(current.input.slice(0, restored.inputPrefixLength)))
906
+ return undefined;
907
+ state.lastResponse = { responseId: restored.responseId, outputItems: [] };
908
+ return current.input.slice(restored.inputPrefixLength);
909
+ }
910
+ if (!responsesRequestPropertiesMatch(previous, current))
870
911
  return undefined;
871
912
  const baseline = [...previous.input, ...completion.outputItems];
872
913
  if (current.input.length < baseline.length)
@@ -877,31 +918,53 @@ function incrementalResponsesInput(state, current) {
877
918
  return current.input.slice(baseline.length);
878
919
  }
879
920
  function responsesRequestPropertiesMatch(previous, current) {
880
- const keys = [
881
- 'model',
882
- 'instructions',
883
- 'tools',
884
- 'tool_choice',
885
- 'parallel_tool_calls',
886
- 'reasoning',
887
- 'store',
888
- 'stream',
889
- 'include',
890
- 'prompt_cache_key',
891
- 'text',
892
- 'client_metadata'
893
- ];
894
- return keys.every((key) => isDeepStrictEqual(previous[key], current[key]));
921
+ return RESPONSES_REQUEST_PROPERTY_KEYS.every((key) => isDeepStrictEqual(previous[key], current[key]));
895
922
  }
896
923
  function clearResponsesContinuation(state) {
897
924
  delete state.lastRequest;
898
925
  delete state.lastResponse;
926
+ delete state.restoredContinuation;
927
+ }
928
+ const RESPONSES_REQUEST_PROPERTY_KEYS = [
929
+ 'model',
930
+ 'instructions',
931
+ 'tools',
932
+ 'tool_choice',
933
+ 'parallel_tool_calls',
934
+ 'reasoning',
935
+ 'store',
936
+ 'stream',
937
+ 'include',
938
+ 'prompt_cache_key',
939
+ 'text',
940
+ 'client_metadata'
941
+ ];
942
+ function responsesRequestPropertiesHash(request) {
943
+ return hashResponsesValue(Object.fromEntries(RESPONSES_REQUEST_PROPERTY_KEYS.map((key) => [key, request[key]])));
944
+ }
945
+ function hashResponsesValue(value) {
946
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
947
+ }
948
+ function responsesConfigurationHash(configuration) {
949
+ const { apiKey: _apiKey, credentialProvider: _credentialProvider, imageGeneration: _imageGeneration, ...identity } = configuration;
950
+ return hashResponsesValue(identity);
951
+ }
952
+ function responsesCredentialFingerprint(credential) {
953
+ return hashResponsesValue({
954
+ bearer: credential.bearer.reveal(),
955
+ headers: Object.fromEntries(Object.entries(credential.headers).map(([name, value]) => [
956
+ name,
957
+ typeof value === 'string' ? value : value.reveal()
958
+ ])),
959
+ socks5Url: credential.socks5Url?.reveal()
960
+ });
899
961
  }
900
962
  function invalidateResponsesWebSocket(state, clearContinuation) {
901
963
  const socket = state.socket;
902
964
  delete state.socket;
903
965
  delete state.endpoint;
904
966
  delete state.credentialRevision;
967
+ delete state.credentialFingerprint;
905
968
  delete state.connectionId;
906
969
  delete state.proxyType;
907
970
  if (clearContinuation)
@@ -911,16 +974,6 @@ function invalidateResponsesWebSocket(state, clearContinuation) {
911
974
  socket.readyState !== WebSocket.CLOSING)
912
975
  terminateWebSocket(socket);
913
976
  }
914
- function unavailableContinuationConnection() {
915
- return new MarAgentError('previous_response_not_found', 'The previous response is unavailable on the current WebSocket connection.', {
916
- details: {
917
- reason: 'OPENAI_RESPONSES_CONTINUATION_CONNECTION_UNAVAILABLE',
918
- transport: 'WEBSOCKET',
919
- continuation: true
920
- },
921
- retryable: true
922
- });
923
- }
924
977
  function turnStateFromEvent(event) {
925
978
  for (const headers of [
926
979
  record(event.headers),
@@ -958,7 +1011,6 @@ async function* webSocketResponseEvents(input) {
958
1011
  reason: input.reason,
959
1012
  signal: input.signal,
960
1013
  continuation: input.continuation,
961
- requiresExistingSocket: input.requiresExistingSocket,
962
1014
  credentialAcquired: input.credentialAcquired
963
1015
  });
964
1016
  const body = input.turn.withTurnStateMetadata(input.body);
@@ -95,7 +95,9 @@ export function buildNativeCompactionMessages(messages, input = {}) {
95
95
  const focus = input.focus?.trim();
96
96
  const contract = input.highDensityCompaction ? highDensityCompactionPrompt() : compactionPrompt();
97
97
  return [
98
- ...messages.map((message) => {
98
+ ...messages
99
+ .filter((message) => message.contextKind !== 'private')
100
+ .map((message) => {
99
101
  if (!message.images?.length)
100
102
  return message;
101
103
  const textOnly = { ...message };
@@ -139,6 +139,7 @@ export function restoreModelContext(payload, providerModelId) {
139
139
  role: 'provider',
140
140
  provider: 'ANTHROPIC_MESSAGES',
141
141
  providerModelId: modelId,
142
+ contextKind: 'private',
142
143
  content: '',
143
144
  item
144
145
  }
package/dist/sdk/agent.js CHANGED
@@ -55,6 +55,7 @@ export async function createMarAgent(options) {
55
55
  throw new MarAgentError('MAR_AGENT_HOST_INCOMPATIBLE', 'Host mode does not match Agent mode.');
56
56
  const store = await JsonlSessionStore.open(description.homeDirectory);
57
57
  const active = new Map();
58
+ const activeExecutionAdapters = new Set();
58
59
  const executionResults = new Map();
59
60
  const sessionRuntimes = new Map();
60
61
  let disposed = false;
@@ -354,6 +355,7 @@ export async function createMarAgent(options) {
354
355
  const titleModel = [...executionModels.values()].find((model) => model.titleGeneration) ??
355
356
  executionModels.get(input.modelId ?? executionDefaultModelId);
356
357
  titleAdapter = createAdapter(titleModel.id, executionModels);
358
+ activeExecutionAdapters.add(titleAdapter);
357
359
  titleTask = generateSessionTitle(titleAdapter, input.prompt, titleModel.defaultReasoningEffort, controller.signal, recordModelAttempt('TITLE')).catch(() => undefined);
358
360
  }
359
361
  }
@@ -370,12 +372,17 @@ export async function createMarAgent(options) {
370
372
  turnId
371
373
  });
372
374
  selectedAdapter = sessionAdapter(input.modelId ?? executionDefaultModelId, executionModels);
375
+ const selectedModel = executionModels.get(input.modelId ?? executionDefaultModelId);
376
+ const restoredContinuation = latestModelContinuationCheckpoint(contextRecords, selectedModel.id);
377
+ if (restoredContinuation !== undefined)
378
+ selectedAdapter.restoreContinuation?.(restoredContinuation);
373
379
  if (options.adapterFactory !== undefined &&
374
- selectedAdapter.createTurnSession === undefined)
380
+ selectedAdapter.createTurnSession === undefined) {
375
381
  executionScopedAdapters.add(selectedAdapter);
382
+ activeExecutionAdapters.add(selectedAdapter);
383
+ }
376
384
  const selected = createModelTurnSession(selectedAdapter);
377
385
  selectedTurn = selected;
378
- const selectedModel = executionModels.get(input.modelId ?? executionDefaultModelId);
379
386
  let lastServerUsage = latestModelUsage(contextRecords, selectedModel.id);
380
387
  const compactUserTokenLimit = Math.min(20_000, Math.floor(selectedModel.contextWindowTokens / 8));
381
388
  const reasoningEffort = input.reasoningEffort ?? selectedModel.defaultReasoningEffort;
@@ -819,6 +826,9 @@ export async function createMarAgent(options) {
819
826
  content: '',
820
827
  provider: event.provider,
821
828
  providerModelId: selectedModel.id,
829
+ ...(event.provider === 'ANTHROPIC_MESSAGES'
830
+ ? { contextKind: 'private' }
831
+ : {}),
822
832
  item: event.item
823
833
  };
824
834
  messages.push(message);
@@ -991,6 +1001,17 @@ export async function createMarAgent(options) {
991
1001
  };
992
1002
  else if (selectedModel.responsesPreviousResponseId)
993
1003
  sessionRuntime.responsesChain = undefined;
1004
+ const continuationCheckpoint = selected.continuationCheckpoint?.();
1005
+ if (mode !== 'compact' && continuationCheckpoint !== undefined)
1006
+ await store.append(sessionId, {
1007
+ type: 'responses.continuation',
1008
+ payload: {
1009
+ modelId: selectedModel.id,
1010
+ checkpoint: continuationCheckpoint
1011
+ },
1012
+ executionId,
1013
+ turnId
1014
+ });
994
1015
  if (pendingTools.length === 0 && mailbox.length > 0) {
995
1016
  await appendMailboxMessages();
996
1017
  finalAnswer = '';
@@ -1270,7 +1291,16 @@ export async function createMarAgent(options) {
1270
1291
  await selectedTurn?.close().catch(() => undefined);
1271
1292
  if (titleAdapter !== undefined && titleAdapter !== selectedAdapter)
1272
1293
  executionScopedAdapters.add(titleAdapter);
1273
- await closeModelAdapters(executionScopedAdapters).catch(() => undefined);
1294
+ try {
1295
+ await closeModelAdapters(executionScopedAdapters);
1296
+ }
1297
+ catch {
1298
+ // Execution cleanup is best effort.
1299
+ }
1300
+ finally {
1301
+ for (const adapter of executionScopedAdapters)
1302
+ activeExecutionAdapters.delete(adapter);
1303
+ }
1274
1304
  subagents?.endParentExecution(executionId);
1275
1305
  await releaseSession?.();
1276
1306
  active.delete(`${sessionId}:${executionId}`);
@@ -1346,30 +1376,60 @@ export async function createMarAgent(options) {
1346
1376
  const activeSessions = activeSessionIds();
1347
1377
  if ([...activeSessions].some((sessionId) => sessionRuntimes.get(sessionId)?.sourceType !== 'subagent'))
1348
1378
  throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'Models cannot be reconfigured while a main execution is active.');
1349
- const activeAdapters = new Set();
1379
+ const activeAdapters = new Set(activeExecutionAdapters);
1350
1380
  for (const [sessionId, runtime] of sessionRuntimes)
1351
1381
  if (activeSessions.has(sessionId))
1352
1382
  for (const adapter of runtime.adapters.values())
1353
1383
  activeAdapters.add(adapter);
1354
1384
  const inactiveRuntimes = [...sessionRuntimes].flatMap(([sessionId, runtime]) => activeSessions.has(sessionId) ? [] : [runtime]);
1355
- const adapters = inactiveRuntimes
1356
- .flatMap((runtime) => [...runtime.adapters.values()])
1357
- .filter((adapter) => !activeAdapters.has(adapter));
1358
1385
  reconfiguring = true;
1359
1386
  try {
1387
+ const reconfigurationCommits = new Map();
1388
+ for (const runtime of inactiveRuntimes)
1389
+ for (const [modelId, adapter] of runtime.adapters) {
1390
+ if (activeAdapters.has(adapter))
1391
+ continue;
1392
+ const configuration = next.models.get(modelId);
1393
+ const commit = configuration === undefined ? undefined : adapter.prepareReconfigure?.(configuration);
1394
+ if (commit !== undefined)
1395
+ reconfigurationCommits.set(adapter, commit);
1396
+ }
1397
+ const retainedAdapters = new Set(reconfigurationCommits.keys());
1398
+ const adapters = [
1399
+ ...new Set(inactiveRuntimes.flatMap((runtime) => [...runtime.adapters.values()]))
1400
+ ].filter((adapter) => !activeAdapters.has(adapter) && !retainedAdapters.has(adapter));
1401
+ for (const runtime of inactiveRuntimes) {
1402
+ for (const [modelId, adapter] of runtime.adapters)
1403
+ if (!retainedAdapters.has(adapter))
1404
+ runtime.adapters.delete(modelId);
1405
+ if (runtime.responsesChain !== undefined &&
1406
+ !retainedAdapters.has(runtime.adapters.get(runtime.responsesChain.modelId)))
1407
+ runtime.responsesChain = undefined;
1408
+ }
1360
1409
  await closeModelAdapters(adapters);
1361
1410
  if (disposed)
1362
1411
  throw new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.');
1412
+ for (const commit of reconfigurationCommits.values())
1413
+ commit();
1363
1414
  models = next.models;
1364
1415
  defaultModelId = next.defaultModelId;
1365
1416
  modelGeneration++;
1366
1417
  for (const provider of next.credentialProviders)
1367
1418
  credentialProviders.add(provider);
1368
1419
  for (const runtime of inactiveRuntimes) {
1369
- runtime.responsesChain = undefined;
1370
- runtime.adapters.clear();
1371
1420
  runtime.adapterGeneration = modelGeneration;
1372
1421
  }
1422
+ const liveProviders = new Set(next.credentialProviders);
1423
+ for (const adapter of activeExecutionAdapters)
1424
+ addModelCredentialProviders(liveProviders, adapter.configuration);
1425
+ for (const runtime of sessionRuntimes.values())
1426
+ for (const adapter of runtime.adapters.values())
1427
+ addModelCredentialProviders(liveProviders, adapter.configuration);
1428
+ const obsoleteProviders = [...credentialProviders].filter((provider) => !liveProviders.has(provider));
1429
+ const providerCloseResults = await Promise.allSettled(obsoleteProviders.map((provider) => provider.close()));
1430
+ for (const [index, result] of providerCloseResults.entries())
1431
+ if (result.status === 'fulfilled')
1432
+ credentialProviders.delete(obsoleteProviders[index]);
1373
1433
  }
1374
1434
  finally {
1375
1435
  reconfiguring = false;
@@ -1512,9 +1572,46 @@ function createModelTurnSession(adapter) {
1512
1572
  }
1513
1573
  },
1514
1574
  resetContinuation: () => turn.resetContinuation?.(),
1575
+ continuationCheckpoint: () => turn.continuationCheckpoint?.(),
1515
1576
  close: () => turn.close()
1516
1577
  };
1517
1578
  }
1579
+ function latestModelContinuationCheckpoint(records, modelId) {
1580
+ let checkpoint;
1581
+ for (const record of records) {
1582
+ if (record.type === 'compact.completed' || record.type === 'context.gc.completed') {
1583
+ checkpoint = undefined;
1584
+ continue;
1585
+ }
1586
+ if (record.type !== 'responses.continuation')
1587
+ continue;
1588
+ const payload = record.payload;
1589
+ if (payload === null || typeof payload !== 'object' || Array.isArray(payload))
1590
+ continue;
1591
+ const candidateModelId = payload.modelId;
1592
+ const candidate = payload.checkpoint;
1593
+ if (candidateModelId !== modelId || !isModelContinuationCheckpoint(candidate))
1594
+ continue;
1595
+ checkpoint = candidate;
1596
+ }
1597
+ return checkpoint;
1598
+ }
1599
+ function isModelContinuationCheckpoint(value) {
1600
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
1601
+ return false;
1602
+ const checkpoint = value;
1603
+ return (checkpoint.version === 1 &&
1604
+ checkpoint.protocol === 'OPENAI_RESPONSES' &&
1605
+ isBoundedString(checkpoint.configurationHash, 64) &&
1606
+ isBoundedString(checkpoint.responseId, 1_024) &&
1607
+ isBoundedString(checkpoint.requestPropertiesHash, 64) &&
1608
+ Number.isSafeInteger(checkpoint.inputPrefixLength) &&
1609
+ checkpoint.inputPrefixLength >= 0 &&
1610
+ isBoundedString(checkpoint.inputPrefixHash, 64));
1611
+ }
1612
+ function isBoundedString(value, maximumLength) {
1613
+ return typeof value === 'string' && value.length > 0 && value.length <= maximumLength;
1614
+ }
1518
1615
  function modelAttemptFailureDiagnostic(diagnostic, summary) {
1519
1616
  if (summary === undefined)
1520
1617
  return diagnostic;
@@ -1526,7 +1623,16 @@ function modelAttemptFailureDiagnostic(diagnostic, summary) {
1526
1623
  };
1527
1624
  }
1528
1625
  async function closeModelAdapters(adapters) {
1529
- await Promise.all([...new Set(adapters)].flatMap((adapter) => adapter.close === undefined ? [] : [adapter.close()]));
1626
+ const results = await Promise.allSettled([...new Set(adapters)].flatMap((adapter) => adapter.close === undefined ? [] : [adapter.close()]));
1627
+ const failure = results.find((result) => result.status === 'rejected');
1628
+ if (failure !== undefined)
1629
+ throw failure.reason;
1630
+ }
1631
+ function addModelCredentialProviders(target, configuration) {
1632
+ if (configuration.credentialProvider !== undefined)
1633
+ target.add(configuration.credentialProvider);
1634
+ if (configuration.imageGeneration !== undefined)
1635
+ target.add(configuration.imageGeneration.credentialProvider);
1530
1636
  }
1531
1637
  function truncateUtf8(value, maximumBytes) {
1532
1638
  const bytes = Buffer.from(value);
@@ -302,7 +302,7 @@ export class JsonlSessionStore {
302
302
  });
303
303
  const retained = input.boundaryTurnId === null ? [] : records.slice(1, boundaryIndex + 1);
304
304
  for (const record of retained) {
305
- if (record.type === 'context.gc.completed')
305
+ if (record.type === 'context.gc.completed' || record.type === 'responses.continuation')
306
306
  continue;
307
307
  await this.append(target.id, {
308
308
  type: record.type,
@@ -368,7 +368,9 @@ export class JsonlSessionStore {
368
368
  : records.findLastIndex((record) => record.turnId === input.boundaryTurnId);
369
369
  if (input.boundaryTurnId !== null && boundaryIndex < 0)
370
370
  throw new MarAgentError('MAR_AGENT_ROLLBACK_TARGET_INVALID', 'Session rollback target was not found.');
371
- const retained = records.slice(0, boundaryIndex + 1);
371
+ const retained = records
372
+ .slice(0, boundaryIndex + 1)
373
+ .filter((record) => record.type !== 'responses.continuation');
372
374
  const staged = await stageRollbackSegments(this.sessionDirectory(sessionId), retained);
373
375
  const transactionId = randomUUID();
374
376
  let generatedTrash = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.83",
3
+ "version": "0.9.84",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",