@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.
- package/dist/model/contracts.d.ts +17 -1
- package/dist/model/openai-responses.d.ts +7 -4
- package/dist/model/openai-responses.js +111 -59
- package/dist/runtime/compact.js +3 -1
- package/dist/runtime/context-projection.js +1 -0
- package/dist/sdk/agent.js +116 -10
- package/dist/session/jsonl-store.js +4 -2
- package/package.json +1 -1
|
@@ -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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
734
|
-
|
|
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
|
-
|
|
869
|
-
|
|
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
|
-
|
|
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);
|
package/dist/runtime/compact.js
CHANGED
|
@@ -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
|
|
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 };
|
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
|
-
|
|
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.
|
|
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
|
|
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 = [];
|