@myagentroam/agent 0.9.145 → 0.9.147
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/agent-core/model/catalog-file.d.ts +0 -2
- package/dist/agent-core/model/catalog-file.js +8 -0
- package/dist/agent-core/model/configuration.d.ts +1 -5
- package/dist/agent-core/model/configuration.js +2 -8
- package/dist/agent-core/model/contracts.d.ts +0 -1
- package/dist/agent-core/model/openai-responses.d.ts +1 -2
- package/dist/agent-core/model/openai-responses.js +32 -116
- package/dist/agent-core/model/runtime-credential.d.ts +1 -1
- package/dist/agent-core/model/runtime-credential.js +24 -2
- package/dist/agent-core/runtime/context/gc-tokens.js +1 -1
- package/dist/agent-core/sdk/agent.js +2 -0
- package/dist/agent-core/sdk/model-runtime.js +0 -4
- package/dist/agent-core/sdk/session.js +1 -27
- package/dist/agent-core/sdk/types.d.ts +6 -1
- package/dist/agent-core/session/jsonl-store.d.ts +3 -5
- package/dist/agent-core/session/jsonl-store.js +38 -12
- package/dist/agent-core/session/types.d.ts +6 -0
- package/dist/agent-core/tools/code-mode/index.js +4 -1
- package/dist/cli/main.js +35 -12
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/shared/auto-title.d.ts +11 -0
- package/dist/shared/auto-title.js +67 -0
- package/package.json +1 -1
- package/dist/agent-core/prompts/title.d.ts +0 -1
- package/dist/agent-core/prompts/title.js +0 -9
- package/dist/agent-core/sdk/session-title.d.ts +0 -3
- 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' | '
|
|
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.
|
|
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
|
|
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,
|
|
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()
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
843
|
+
function responsesRequestBody(configuration, request, messages) {
|
|
888
844
|
const tools = request.allowTools === false
|
|
889
845
|
? []
|
|
890
|
-
: request.tools.map((tool) =>
|
|
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
|
|
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
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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:
|
|
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
|
|
1295
|
-
const headers = responsesHeaders(credential, promptCacheKey
|
|
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
|
-
|
|
1387
|
+
'x-openai-internal-codex-responses-lite'
|
|
1468
1388
|
]);
|
|
1469
|
-
function responsesHeaders(credential, promptCacheKey, turnState
|
|
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,
|
|
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,
|
|
1676
|
+
.map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds));
|
|
1756
1677
|
}
|
|
1757
|
-
function openAiInputItem(message, images, customCallIds = new Set(),
|
|
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 (
|
|
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 (
|
|
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
|
|
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,
|
|
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
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
+
}
|
|
@@ -18,7 +18,10 @@ export const codeModeToolDefinition = {
|
|
|
18
18
|
name: 'code_exec',
|
|
19
19
|
description: `Run JavaScript to orchestrate or compose tool calls.
|
|
20
20
|
- Code Mode Only: call the programmatic tools listed below through tools.NAME inside this tool, including single-tool operations. They are omitted from the top-level tool list. Direct-only tools remain separate model tools and cannot be called here.
|
|
21
|
-
-
|
|
21
|
+
- Run independent calls concurrently with Promise.all; await sequentially when data, side effects, or failure handling require ordering.
|
|
22
|
+
- Complete known, bounded steps and deterministic result processing in code when this avoids a model round trip. Return to the model when interpretation or a new decision is needed; do not invent extra work to fill a cell.
|
|
23
|
+
- Observe only evidence needed for the next decision or verification: use observe.result for original results, observe.text for derived values, and observe.image for image values. Preserve source and failure details needed for reasoning; keep other intermediate data in the cell.
|
|
24
|
+
- Single-tool cells are valid. Keep loops and retries bounded, and do not blindly replay operations with side effects.
|
|
22
25
|
- Evaluates source in a fresh restricted ECMAScript runtime with top-level await and Promise.all. It is not Node.js or a browser. JavaScript globals do not survive between cells; bounded JSON state is available through state.
|
|
23
26
|
- Accepts raw JavaScript source text, not JSON, quoted strings, or markdown code fences. Responses uses the source directly; Anthropic uses the code field of its tool input object. In either case, do not quote or fence the source again.
|
|
24
27
|
- Use only standard ECMAScript built-ins and the explicitly documented tools, observe, state, catalog and cell namespaces. Do not use Node.js, browser or Web APIs. Module imports, filesystem and network APIs are unavailable. Obtain external capabilities only through available tools. Use tools.exec to start managed processes when permitted; use tools.exec_wait afterward to wait, write stdin or cancel.
|
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] [--
|
|
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
|
|
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 +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
|
-
}
|