@jskit-ai/assistant-core 0.1.200 → 0.1.202
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/examples/conversation/README.md +8 -1
- package/examples/conversation/package.json +5 -5
- package/fixtures/responsive-assistant/App.vue +4 -1
- package/package.json +99 -36
- package/src/client/conversation/AssistantConversationElement.vue +27 -4
- package/src/client/conversation/AssistantConversationStatus.vue +24 -0
- package/src/client/conversation/AssistantGoalControl.vue +8 -5
- package/src/client/conversation/AssistantTranscript.vue +10 -3
- package/src/client/conversation/index.js +2 -0
- package/src/client/conversation/messageDelivery.js +115 -32
- package/src/client/conversation/retainedConversation.js +32 -0
- package/src/client/conversation/useAssistantQuestions.js +76 -0
- package/src/client/lib/assistantApi.js +72 -2
- package/src/server/conversation/agentRun.js +222 -0
- package/src/server/conversation/attachments.js +93 -0
- package/src/server/conversation/claudeClient.js +221 -0
- package/src/server/conversation/claudeHistory.js +256 -0
- package/src/server/conversation/claudeProcess.js +327 -0
- package/src/server/conversation/claudeTools.js +42 -0
- package/src/server/conversation/claudeTurn.js +1082 -0
- package/src/server/conversation/codexConfiguration.js +1183 -0
- package/src/server/conversation/codexConversations.js +1082 -0
- package/src/server/conversation/codexDetachedTurn.js +332 -0
- package/src/server/conversation/codexEvents.js +194 -1
- package/src/server/conversation/codexGoalCommands.js +124 -0
- package/src/server/conversation/codexHelperExecution.js +306 -0
- package/src/server/conversation/codexHelperThreadLedger.js +804 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +395 -0
- package/src/server/conversation/codexHelperThreads.js +638 -0
- package/src/server/conversation/codexHistoryAdapter.js +369 -0
- package/src/server/conversation/codexMessageCommands.js +663 -0
- package/src/server/conversation/codexMessageDelivery.js +211 -0
- package/src/server/conversation/codexNativeHistoryExport.js +95 -0
- package/src/server/conversation/codexNotificationObserver.js +482 -0
- package/src/server/conversation/codexNotificationQueue.js +103 -0
- package/src/server/conversation/codexObservationControl.js +174 -0
- package/src/server/conversation/codexOutput.js +931 -0
- package/src/server/conversation/codexProcess.js +1559 -0
- package/src/server/conversation/codexProcessLeader.js +14 -0
- package/src/server/conversation/codexProvider.js +2710 -0
- package/src/server/conversation/codexProviderOwner.js +1058 -0
- package/src/server/conversation/codexProviderSelection.js +218 -0
- package/src/server/conversation/codexReasoning.js +314 -0
- package/src/server/conversation/codexRenewalCommands.js +290 -0
- package/src/server/conversation/codexResultSettlement.js +409 -0
- package/src/server/conversation/codexRunJournal.js +658 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +288 -0
- package/src/server/conversation/codexSessionCleanup.js +96 -0
- package/src/server/conversation/codexSessionReconciliation.js +284 -0
- package/src/server/conversation/codexThreadReadiness.js +397 -0
- package/src/server/conversation/codexThreadStatus.js +354 -0
- package/src/server/conversation/codexTurn.js +701 -255
- package/src/server/conversation/codexTurnControl.js +317 -0
- package/src/server/conversation/codexTurnRecovery.js +990 -0
- package/src/server/conversation/codexTurnState.js +549 -0
- package/src/server/conversation/commandHook.js +18 -0
- package/src/server/conversation/commandWrapper.js +37 -0
- package/src/server/conversation/configuration.js +43 -0
- package/src/server/conversation/continuity.js +289 -0
- package/src/server/conversation/fileStorage.js +55 -0
- package/src/server/conversation/hookBridge.js +19 -0
- package/src/server/conversation/index.js +13 -0
- package/src/server/conversation/localExecution.js +665 -0
- package/src/server/conversation/memoryStorage.js +7 -72
- package/src/server/conversation/nativeHistoryExport.js +90 -0
- package/src/server/conversation/nativeStorage.js +23 -0
- package/src/server/conversation/openCodeClient.js +209 -19
- package/src/server/conversation/openCodePlugin.js +84 -0
- package/src/server/conversation/openCodeProcess.js +476 -0
- package/src/server/conversation/openCodeRuntime.js +1578 -0
- package/src/server/conversation/openCodeTools.js +60 -0
- package/src/server/conversation/openCodeTurn.js +557 -0
- package/src/server/conversation/provider.js +30 -0
- package/src/server/conversation/providers/api.js +136 -0
- package/src/server/conversation/providers/claude.js +63 -0
- package/src/server/conversation/providers/claudeDriver.js +425 -0
- package/src/server/conversation/providers/codex.js +248 -0
- package/src/server/conversation/providers/codexDriver.js +1078 -0
- package/src/server/conversation/providers/opencode.js +49 -0
- package/src/server/conversation/providers/opencodeDriver.js +650 -0
- package/src/server/conversation/providers/scoped.js +312 -0
- package/src/server/conversation/runtime.js +2220 -0
- package/src/server/conversation/runtimeStateUpgrade.js +125 -0
- package/src/server/conversation/storage.js +184 -0
- package/src/server/conversation/streams.js +7 -2
- package/src/server/conversation/structuredOutput.js +240 -0
- package/src/server/conversation/systemPrompt.js +54 -0
- package/src/server/conversation/tools.js +84 -0
- package/src/server/conversation/transcript.js +5 -4
- package/src/server/hosts/claudeHistory.js +5 -0
- package/src/server/hosts/claudeProcess.js +11 -0
- package/src/server/hosts/claudeTurn.js +4 -0
- package/src/server/hosts/codexConfiguration.js +20 -0
- package/src/server/hosts/codexEvents.js +13 -0
- package/src/server/hosts/codexProcess.js +12 -0
- package/src/server/hosts/codexProvider.js +23 -0
- package/src/server/hosts/codexTurn.js +20 -0
- package/src/server/hosts/nativeHistory.js +3 -0
- package/src/server/hosts/openCodeClient.js +3 -0
- package/src/server/hosts/openCodeProcess.js +23 -0
- package/src/server/hosts/openCodeTurn.js +7 -0
- package/src/server/index.js +2 -0
- package/src/server/lib/aiConnectionClient.js +13 -6
- package/src/server/lib/assistantCompletion.js +190 -0
- package/src/server/lib/assistantToolLoop.js +314 -0
- package/src/server/lib/serviceToolCatalog.js +25 -10
- package/src/shared/assistantResponseText.js +29 -0
- package/src/shared/conversation/attachments.js +19 -0
- package/src/shared/conversation/index.js +3 -0
- package/src/shared/conversation/pagination.js +78 -0
- package/src/shared/conversation/patches.js +150 -0
- package/src/shared/conversation/questions.js +48 -0
- package/src/shared/conversation/streaming.js +7 -4
- package/src/shared/index.js +2 -0
- package/src/shared/nativeProviders.js +45 -0
- package/src/testing/conversationStorageContract.js +31 -1
- package/src/testing/nativeCodex.js +11 -0
- package/src/testing/nativeOpenCode.js +3 -0
- package/test/aiConnectionClient.test.js +2 -0
- package/test/apiConversationDriver.test.js +73 -0
- package/test/assistantApiSurfaceHeader.test.js +85 -0
- package/test/assistantBoundedToolLoop.test.js +285 -0
- package/test/assistantScroll.browser.test.js +16 -0
- package/test/claudeClient.test.js +189 -0
- package/test/claudeConversationSession.test.js +1173 -0
- package/test/claudeInstructionResume.test.js +82 -0
- package/test/claudeProcess.test.js +204 -0
- package/test/claudeTurn.test.js +253 -0
- package/test/codexConversationSession.test.js +2122 -0
- package/test/codexGoal.test.js +37 -0
- package/test/codexHelperDeletion.test.js +163 -0
- package/test/codexHelperThreadLedger.test.js +494 -0
- package/test/codexHistoryAdapter.test.js +541 -0
- package/test/codexInteractiveArguments.test.js +30 -0
- package/test/codexIsolation.test.js +756 -0
- package/test/codexModelCatalog.test.js +362 -0
- package/test/codexNativeHistoryExport.test.js +81 -0
- package/test/codexThreadSelection.test.js +136 -0
- package/test/codexTurn.test.js +2158 -0
- package/test/conversation.test.js +110 -0
- package/test/conversationAgentRun.test.js +326 -0
- package/test/conversationApiFacilities.test.js +90 -0
- package/test/conversationAttachments.test.js +238 -0
- package/test/conversationCommandWrapper.test.js +48 -0
- package/test/conversationConfiguration.test.js +174 -0
- package/test/conversationNativeStorage.test.js +25 -0
- package/test/conversationReplacement.test.js +172 -0
- package/test/conversationRuntime.test.js +241 -0
- package/test/conversationRuntimeStateUpgrade.test.js +144 -0
- package/test/conversationSession.test.js +1080 -0
- package/test/conversationStreaming.test.js +68 -0
- package/test/conversationSystemPrompt.test.js +84 -0
- package/test/conversationTools.test.js +416 -0
- package/test/fileConversationStorage.test.js +73 -0
- package/test/fixtures/codexHelperThreadLedger.js +39 -0
- package/test/localConversationExecution.test.js +90 -0
- package/test/messageDelivery.test.js +234 -1
- package/test/nativeConversationRetirement.test.js +215 -0
- package/test/openCodeClient.test.js +515 -0
- package/test/openCodeProcess.test.js +177 -0
- package/test/openCodeRuntime.test.js +322 -0
- package/test/openCodeTools.test.js +287 -0
- package/test/opencodeConversationSession.test.js +789 -0
- package/test/serviceToolCatalog.test.js +24 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import { normalizeText, isPlainObject } from "./normalize.js";
|
|
2
|
+
import { codexAppServerStatusFromValue, codexAppServerThreadRawValue } from "./codexEvents.js";
|
|
3
|
+
|
|
4
|
+
function codexAppServerTurnStatusIsActive(status = "") {
|
|
5
|
+
return normalizeText(status) === "inProgress";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function codexAppServerTurnStatusIsComplete(status = "") {
|
|
9
|
+
return ["completed", "interrupted", "failed"].includes(normalizeText(status));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function codexAppServerTurnStatusIsSuccessfulComplete(status = "") {
|
|
13
|
+
return normalizeText(status) === "completed";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function codexAppServerTurnStatusIsProviderFailure(status = "") {
|
|
17
|
+
return ["failed", "interrupted"].includes(normalizeText(status));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function codexAppServerThreadStatus(thread = {}) {
|
|
21
|
+
const liveStatus = codexAppServerStatusFromValue(
|
|
22
|
+
codexAppServerThreadRawValue(thread).status || thread.status
|
|
23
|
+
);
|
|
24
|
+
// A resumed goal can already be running while turn history still reports
|
|
25
|
+
// interruption. Releasing its chat owner would hide progress and steering.
|
|
26
|
+
if (codexAppServerTurnStatusIsActive(liveStatus)) {
|
|
27
|
+
return liveStatus;
|
|
28
|
+
}
|
|
29
|
+
if (isPlainObject(thread.observedTurn)) {
|
|
30
|
+
const observedStatus = codexAppServerStatusFromValue(thread.observedTurn.status);
|
|
31
|
+
if (observedStatus) {
|
|
32
|
+
return observedStatus;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return liveStatus;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function codexAppServerReadThreadStatus(provider = null, threadId = "", {
|
|
39
|
+
observeLatestTurn = false
|
|
40
|
+
} = {}) {
|
|
41
|
+
const normalizedThreadId = normalizeText(threadId || "");
|
|
42
|
+
if (!normalizedThreadId) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (typeof provider?.readThreadStatus === "function") {
|
|
46
|
+
const thread = await provider.readThreadStatus(normalizedThreadId);
|
|
47
|
+
if (!observeLatestTurn || typeof provider?.listThreadTurns !== "function") {
|
|
48
|
+
return thread;
|
|
49
|
+
}
|
|
50
|
+
const response = await provider.listThreadTurns(normalizedThreadId, {
|
|
51
|
+
itemsView: "summary",
|
|
52
|
+
limit: 1,
|
|
53
|
+
sortDirection: "desc"
|
|
54
|
+
});
|
|
55
|
+
const observedTurn = Array.isArray(response?.data) ? response.data[0] : null;
|
|
56
|
+
if (provider.isControlProbeTurn?.(normalizedThreadId, observedTurn?.id)) return thread;
|
|
57
|
+
return isPlainObject(observedTurn)
|
|
58
|
+
? {
|
|
59
|
+
...thread,
|
|
60
|
+
observedTurn
|
|
61
|
+
}
|
|
62
|
+
: thread;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Original native run projection and guarded mutation sequence. The caller's
|
|
68
|
+
// existing runtime/store owns persistence and its mutation lock; no second
|
|
69
|
+
// request journal or transcript-derived ownership is introduced here.
|
|
70
|
+
const CODEX_APP_SERVER_AGENT_RUN_ID = "codex_app_server";
|
|
71
|
+
const CODEX_APP_SERVER_RESULT_PROCESSED_EVENT = "codex-app-server-result-processed";
|
|
72
|
+
const CODEX_AGENT_PROVIDER = "codex";
|
|
73
|
+
export const CODEX_APP_SERVER_RUN_STATE = Object.freeze({
|
|
74
|
+
ACTIVE: "active",
|
|
75
|
+
CANCELLED: "cancelled",
|
|
76
|
+
COMPLETED: "completed",
|
|
77
|
+
FAILED: "failed",
|
|
78
|
+
FINALIZING: "finalizing",
|
|
79
|
+
INTERRUPTED: "interrupted",
|
|
80
|
+
STARTING: "starting",
|
|
81
|
+
TIMED_OUT: "timed_out"
|
|
82
|
+
});
|
|
83
|
+
const CODEX_APP_SERVER_RUN_STATES = new Set(Object.values(CODEX_APP_SERVER_RUN_STATE));
|
|
84
|
+
const CODEX_APP_SERVER_TERMINAL_RUN_STATES = new Set([
|
|
85
|
+
CODEX_APP_SERVER_RUN_STATE.CANCELLED,
|
|
86
|
+
CODEX_APP_SERVER_RUN_STATE.COMPLETED,
|
|
87
|
+
CODEX_APP_SERVER_RUN_STATE.FAILED,
|
|
88
|
+
CODEX_APP_SERVER_RUN_STATE.INTERRUPTED,
|
|
89
|
+
CODEX_APP_SERVER_RUN_STATE.TIMED_OUT
|
|
90
|
+
]);
|
|
91
|
+
const CODEX_APP_SERVER_ACTIVE_RUN_STATES = new Set([
|
|
92
|
+
CODEX_APP_SERVER_RUN_STATE.ACTIVE,
|
|
93
|
+
CODEX_APP_SERVER_RUN_STATE.FINALIZING,
|
|
94
|
+
CODEX_APP_SERVER_RUN_STATE.STARTING
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
function normalizeCodexRunText(value) {
|
|
98
|
+
return String(value || "").trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function codexAppServerRunInputSource(run = {}) {
|
|
102
|
+
return normalizeCodexRunText(run?.inputSource);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function normalizeCodexAppServerRunState(state) {
|
|
106
|
+
const normalizedState = normalizeCodexRunText(state) || CODEX_APP_SERVER_RUN_STATE.STARTING;
|
|
107
|
+
if (!CODEX_APP_SERVER_RUN_STATES.has(normalizedState)) {
|
|
108
|
+
throw Object.assign(new Error(`Invalid Codex app-server run state: ${normalizedState}`), {
|
|
109
|
+
code: "codex_app_server_invalid_run_state"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return normalizedState;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function codexAppServerRunStateIsActive(state) {
|
|
116
|
+
return CODEX_APP_SERVER_ACTIVE_RUN_STATES.has(normalizeCodexAppServerRunState(state));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function codexAppServerRunStateIsTerminal(state) {
|
|
120
|
+
return CODEX_APP_SERVER_TERMINAL_RUN_STATES.has(normalizeCodexAppServerRunState(state));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function codexAppServerAgentRun(session = {}) {
|
|
124
|
+
const runs = Array.isArray(session.agentRuns) ? session.agentRuns : [];
|
|
125
|
+
return runs.find((run) => normalizeCodexRunText(run?.id) === CODEX_APP_SERVER_AGENT_RUN_ID) || null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function codexAppServerPendingUserMessageClientIds(run = {}) {
|
|
129
|
+
const ids = run?.pendingUserMessageClientIds;
|
|
130
|
+
return (Array.isArray(ids) ? ids : [])
|
|
131
|
+
.map((id) => normalizeCodexRunText(id))
|
|
132
|
+
.filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function codexAppServerPendingUserMessageOwnership(run = {}, clientId = "") {
|
|
136
|
+
const pendingClientIds = codexAppServerPendingUserMessageClientIds(run);
|
|
137
|
+
const normalizedClientId = normalizeCodexRunText(clientId);
|
|
138
|
+
const ownedClientId = normalizedClientId
|
|
139
|
+
? pendingClientIds.find((id) => id === normalizedClientId)
|
|
140
|
+
: pendingClientIds[0];
|
|
141
|
+
if (!ownedClientId) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
clientId: ownedClientId,
|
|
146
|
+
inputSource: normalizeCodexRunText(run?.inputSource)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function codexAppServerProcessedResultEvent(session = {}, threadId = "", turnId = "") {
|
|
151
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
152
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
153
|
+
const events = codexAppServerAgentRun(session)?.events;
|
|
154
|
+
return (Array.isArray(events) ? events : []).findLast((event) => (
|
|
155
|
+
normalizeCodexRunText(event?.kind) === CODEX_APP_SERVER_RESULT_PROCESSED_EVENT &&
|
|
156
|
+
normalizeCodexRunText(event?.providerThreadId) === normalizedThreadId &&
|
|
157
|
+
normalizeCodexRunText(event?.providerTurnId) === normalizedTurnId
|
|
158
|
+
)) || null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function codexAppServerTurnStateFromAgentRun(run = {}, normalizeRunState = normalizeCodexAppServerRunState) {
|
|
162
|
+
const runState = normalizeRunState(run.state);
|
|
163
|
+
const active = codexAppServerRunStateIsActive(runState);
|
|
164
|
+
const state = runState === CODEX_APP_SERVER_RUN_STATE.FINALIZING
|
|
165
|
+
? "finalizing"
|
|
166
|
+
: runState === CODEX_APP_SERVER_RUN_STATE.STARTING
|
|
167
|
+
? "starting"
|
|
168
|
+
: active
|
|
169
|
+
? "active"
|
|
170
|
+
: "idle";
|
|
171
|
+
return {
|
|
172
|
+
active,
|
|
173
|
+
completedAt: normalizeCodexRunText(run.finishedAt),
|
|
174
|
+
error: normalizeCodexRunText(run.error),
|
|
175
|
+
goalStatus: normalizeCodexRunText(run.providerGoalStatus),
|
|
176
|
+
goalThreadId: normalizeCodexRunText(run.providerGoalThreadId),
|
|
177
|
+
inputSource: normalizeCodexRunText(run.inputSource),
|
|
178
|
+
outerTurnId: normalizeCodexRunText(run.outerTurnId),
|
|
179
|
+
phase: active && run.providerStatus !== "observation_lost" ? normalizeCodexRunText(run.providerPhase) : "",
|
|
180
|
+
runId: normalizeCodexRunText(run.id),
|
|
181
|
+
runState,
|
|
182
|
+
startedAt: normalizeCodexRunText(run.startedAt),
|
|
183
|
+
state,
|
|
184
|
+
status: normalizeCodexRunText(run.providerStatus || run.status || runState),
|
|
185
|
+
threadId: normalizeCodexRunText(run.providerThreadId),
|
|
186
|
+
turnId: normalizeCodexRunText(run.providerTurnId),
|
|
187
|
+
updatedAt: normalizeCodexRunText(run.updatedAt)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function codexAppServerTurnState(session = {}, normalizeRunState = normalizeCodexAppServerRunState) {
|
|
192
|
+
const run = codexAppServerAgentRun(session);
|
|
193
|
+
if (run) {
|
|
194
|
+
return codexAppServerTurnStateFromAgentRun(run, normalizeRunState);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
active: false,
|
|
198
|
+
completedAt: "",
|
|
199
|
+
error: "",
|
|
200
|
+
goalStatus: "",
|
|
201
|
+
goalThreadId: "",
|
|
202
|
+
outerTurnId: "",
|
|
203
|
+
runId: "",
|
|
204
|
+
runState: "",
|
|
205
|
+
startedAt: "",
|
|
206
|
+
state: "idle",
|
|
207
|
+
status: "",
|
|
208
|
+
threadId: "",
|
|
209
|
+
turnId: "",
|
|
210
|
+
updatedAt: ""
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function codexAppServerTurnOwnsActiveGoal(turn = {}, threadId = "") {
|
|
215
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
216
|
+
const goalThreadId = normalizeCodexRunText(turn.goalThreadId);
|
|
217
|
+
const inputSource = normalizeCodexRunText(turn.inputSource);
|
|
218
|
+
return normalizeCodexRunText(turn.goalStatus) === "active" &&
|
|
219
|
+
Boolean(normalizedThreadId) &&
|
|
220
|
+
goalThreadId === normalizedThreadId &&
|
|
221
|
+
Boolean(normalizeCodexRunText(turn.outerTurnId)) &&
|
|
222
|
+
Boolean(inputSource) &&
|
|
223
|
+
inputSource !== "terminal";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function codexAppServerTurnMatches(turn = {}, threadId = "", turnId = "") {
|
|
227
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
228
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
229
|
+
const currentThreadId = normalizeCodexRunText(turn.threadId);
|
|
230
|
+
const currentTurnId = normalizeCodexRunText(turn.turnId);
|
|
231
|
+
if (normalizedThreadId && normalizeCodexRunText(turn.threadId) !== normalizedThreadId) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
if (normalizedTurnId && currentTurnId && currentTurnId !== normalizedTurnId) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
if (!normalizedTurnId && currentTurnId) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (!normalizedThreadId && currentThreadId) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function codexAppServerTurnCanReceiveProviderCompletion(turn = {}, threadId = "", turnId = "") {
|
|
247
|
+
return codexAppServerTurnMatches(turn, threadId, turnId) &&
|
|
248
|
+
["active", "finalizing"].includes(normalizeCodexRunText(turn.state));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function codexAppServerTurnAwaitsProviderIdentity(turn = {}, threadId = "", turnId = "") {
|
|
252
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
253
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
254
|
+
const currentThreadId = normalizeCodexRunText(turn.threadId);
|
|
255
|
+
return normalizeCodexRunText(turn.state) === "starting" &&
|
|
256
|
+
Boolean(normalizedTurnId) &&
|
|
257
|
+
!normalizeCodexRunText(turn.turnId) &&
|
|
258
|
+
(!normalizedThreadId || !currentThreadId || normalizedThreadId === currentThreadId);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function codexAppServerTurnCanReceiveProviderActivity(turn = {}, threadId = "", turnId = "") {
|
|
262
|
+
if (turn.status === "observation_lost") return false;
|
|
263
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
264
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
265
|
+
const currentThreadId = normalizeCodexRunText(turn.threadId);
|
|
266
|
+
const currentTurnId = normalizeCodexRunText(turn.turnId);
|
|
267
|
+
if (!["starting", "active"].includes(normalizeCodexRunText(turn.state))) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
if (normalizedThreadId && currentThreadId && currentThreadId !== normalizedThreadId) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
if (normalizedTurnId && currentTurnId && currentTurnId !== normalizedTurnId) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (normalizedThreadId && !currentThreadId) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function codexAppServerTurnCanAdoptSuccessor(turn = {}, threadId = "", turnId = "") {
|
|
283
|
+
if (turn.status === "observation_lost") return false;
|
|
284
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
285
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
286
|
+
const currentThreadId = normalizeCodexRunText(turn.threadId);
|
|
287
|
+
const currentTurnId = normalizeCodexRunText(turn.turnId);
|
|
288
|
+
return turn.active === true &&
|
|
289
|
+
["active", "finalizing"].includes(normalizeCodexRunText(turn.state)) &&
|
|
290
|
+
Boolean(normalizedThreadId) &&
|
|
291
|
+
normalizedThreadId === currentThreadId &&
|
|
292
|
+
Boolean(normalizedTurnId) &&
|
|
293
|
+
Boolean(currentTurnId) &&
|
|
294
|
+
normalizedTurnId !== currentTurnId;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function codexAppServerTurnKey(threadId = "", turnId = "") {
|
|
298
|
+
return `${normalizeCodexRunText(threadId)}:${normalizeCodexRunText(turnId)}`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function codexAppServerAgentRunPatch({
|
|
302
|
+
error = "",
|
|
303
|
+
inputSource = "",
|
|
304
|
+
outerTurnId = "",
|
|
305
|
+
phase,
|
|
306
|
+
runState = CODEX_APP_SERVER_RUN_STATE.COMPLETED,
|
|
307
|
+
session = {},
|
|
308
|
+
status = "",
|
|
309
|
+
threadId = "",
|
|
310
|
+
turnId = "",
|
|
311
|
+
updatedAt = ""
|
|
312
|
+
} = {}, normalizeRunState = normalizeCodexAppServerRunState) {
|
|
313
|
+
const normalizedRunState = normalizeRunState(runState);
|
|
314
|
+
const currentRun = codexAppServerAgentRun(session);
|
|
315
|
+
const sameActiveTurn = normalizedRunState === CODEX_APP_SERVER_RUN_STATE.ACTIVE &&
|
|
316
|
+
currentRun?.providerThreadId === normalizeCodexRunText(threadId) &&
|
|
317
|
+
currentRun?.providerTurnId === normalizeCodexRunText(turnId);
|
|
318
|
+
const patch = {
|
|
319
|
+
error: normalizeCodexRunText(error),
|
|
320
|
+
provider: CODEX_AGENT_PROVIDER,
|
|
321
|
+
providerInterface: "codex_app_server",
|
|
322
|
+
providerPhase: normalizedRunState === CODEX_APP_SERVER_RUN_STATE.ACTIVE && status !== "observation_lost"
|
|
323
|
+
? normalizeCodexRunText(phase === undefined && sameActiveTurn ? currentRun?.providerPhase : phase)
|
|
324
|
+
: "",
|
|
325
|
+
providerStatus: normalizeCodexRunText(status),
|
|
326
|
+
providerThreadId: normalizeCodexRunText(threadId),
|
|
327
|
+
providerTurnId: normalizeCodexRunText(turnId),
|
|
328
|
+
state: normalizedRunState,
|
|
329
|
+
updatedAt: normalizeCodexRunText(updatedAt)
|
|
330
|
+
};
|
|
331
|
+
const normalizedInputSource = normalizeCodexRunText(inputSource);
|
|
332
|
+
if (normalizedInputSource) {
|
|
333
|
+
patch.inputSource = normalizedInputSource;
|
|
334
|
+
}
|
|
335
|
+
// A native terminal turn starts its own chat owner. Reusing the previous
|
|
336
|
+
// owner's id would checkpoint two different turns with one identity.
|
|
337
|
+
const normalizedOuterTurnId = normalizeCodexRunText(outerTurnId) ||
|
|
338
|
+
(normalizedInputSource === "terminal" && normalizedRunState === CODEX_APP_SERVER_RUN_STATE.ACTIVE && !sameActiveTurn
|
|
339
|
+
? `codex:${normalizeCodexRunText(threadId)}:${normalizeCodexRunText(turnId)}`
|
|
340
|
+
: normalizeCodexRunText(currentRun?.outerTurnId));
|
|
341
|
+
if (normalizedOuterTurnId) {
|
|
342
|
+
patch.outerTurnId = normalizedOuterTurnId;
|
|
343
|
+
}
|
|
344
|
+
if (
|
|
345
|
+
normalizedRunState === CODEX_APP_SERVER_RUN_STATE.STARTING ||
|
|
346
|
+
codexAppServerRunStateIsTerminal(normalizedRunState) ||
|
|
347
|
+
normalizedInputSource === "terminal"
|
|
348
|
+
) {
|
|
349
|
+
patch.pendingUserMessageClientIds = [];
|
|
350
|
+
}
|
|
351
|
+
if ([CODEX_APP_SERVER_RUN_STATE.ACTIVE, CODEX_APP_SERVER_RUN_STATE.STARTING].includes(normalizedRunState)) {
|
|
352
|
+
patch.startedAt = normalizeCodexRunText(updatedAt);
|
|
353
|
+
}
|
|
354
|
+
if (!codexAppServerRunStateIsActive(normalizedRunState)) {
|
|
355
|
+
patch.finishedAt = normalizeCodexRunText(updatedAt);
|
|
356
|
+
}
|
|
357
|
+
return patch;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function codexAppServerRunIdentityForPatch(session = {}, {
|
|
361
|
+
threadId = "",
|
|
362
|
+
turnId = ""
|
|
363
|
+
} = {}, normalizeRunState = normalizeCodexAppServerRunState) {
|
|
364
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
365
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
366
|
+
if (normalizedTurnId) {
|
|
367
|
+
return {
|
|
368
|
+
threadId: normalizedThreadId,
|
|
369
|
+
turnId: normalizedTurnId
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const currentTurn = codexAppServerTurnState(session, normalizeRunState);
|
|
373
|
+
const currentThreadId = normalizeCodexRunText(currentTurn.threadId);
|
|
374
|
+
if (
|
|
375
|
+
normalizeCodexRunText(currentTurn.turnId) &&
|
|
376
|
+
["active", "finalizing"].includes(normalizeCodexRunText(currentTurn.state)) &&
|
|
377
|
+
(!normalizedThreadId || !currentThreadId || normalizedThreadId === currentThreadId)
|
|
378
|
+
) {
|
|
379
|
+
return {
|
|
380
|
+
threadId: normalizedThreadId || currentThreadId,
|
|
381
|
+
turnId: currentTurn.turnId
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
threadId: normalizedThreadId,
|
|
386
|
+
turnId: normalizedTurnId
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function codexAppServerRunPatchIsStaleAfterTerminalState(currentTurn = {}, patch = {}, normalizeRunState = normalizeCodexAppServerRunState) {
|
|
391
|
+
const currentRunState = normalizeRunState(currentTurn.runState);
|
|
392
|
+
const patchRunState = normalizeRunState(patch.state);
|
|
393
|
+
if (!codexAppServerRunStateIsTerminal(currentRunState)) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
if (patchRunState === CODEX_APP_SERVER_RUN_STATE.STARTING) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
const currentThreadId = normalizeCodexRunText(currentTurn.threadId);
|
|
400
|
+
const patchThreadId = normalizeCodexRunText(patch.providerThreadId);
|
|
401
|
+
const currentTurnId = normalizeCodexRunText(currentTurn.turnId);
|
|
402
|
+
const patchTurnId = normalizeCodexRunText(patch.providerTurnId);
|
|
403
|
+
const threadMatches = !patchThreadId || !currentThreadId || patchThreadId === currentThreadId;
|
|
404
|
+
const turnMatches = !patchTurnId || (
|
|
405
|
+
Boolean(currentTurnId) &&
|
|
406
|
+
currentTurnId === patchTurnId
|
|
407
|
+
);
|
|
408
|
+
return threadMatches && turnMatches;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function codexAppServerErrorMessage(value, fallback = "Codex could not be prepared.") {
|
|
412
|
+
return normalizeCodexRunText(value?.error || value?.message || value) || fallback;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function codexAppServerSteerFailure(result = {}, steerFailedCode = "codex_turn_steer_failed") {
|
|
416
|
+
if (!isPlainObject(result)) {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
if (result.ok !== false) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
code: normalizeCodexRunText(result.code) || steerFailedCode,
|
|
424
|
+
error: codexAppServerErrorMessage(result, "Codex app-server turn could not be steered."),
|
|
425
|
+
ok: false,
|
|
426
|
+
operationOutcome: normalizeCodexRunText(result.operationOutcome) || "steer_failed",
|
|
427
|
+
refreshRecommended: true,
|
|
428
|
+
retryable: result.retryable === true
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function codexAppServerMessageRequiresNewTurn({
|
|
433
|
+
reason = "provider_idle",
|
|
434
|
+
threadId = "",
|
|
435
|
+
turnId = ""
|
|
436
|
+
} = {}) {
|
|
437
|
+
return {
|
|
438
|
+
delivered: false,
|
|
439
|
+
deliveryMode: "new_turn",
|
|
440
|
+
newTurnRequired: true,
|
|
441
|
+
ok: true,
|
|
442
|
+
operationOutcome: "new_turn_required",
|
|
443
|
+
reason: normalizeCodexRunText(reason),
|
|
444
|
+
threadId: normalizeCodexRunText(threadId),
|
|
445
|
+
turnId: normalizeCodexRunText(turnId)
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function codexAppServerMessageDeferred({
|
|
450
|
+
threadId = "",
|
|
451
|
+
turnId = ""
|
|
452
|
+
} = {}, steerFailedCode = "codex_turn_steer_failed") {
|
|
453
|
+
return {
|
|
454
|
+
code: steerFailedCode,
|
|
455
|
+
delivered: false,
|
|
456
|
+
error: "The active assistant operation cannot accept messages yet.",
|
|
457
|
+
ok: false,
|
|
458
|
+
operationOutcome: "active_turn_not_steerable",
|
|
459
|
+
refreshRecommended: true,
|
|
460
|
+
retryable: true,
|
|
461
|
+
threadId,
|
|
462
|
+
turnId
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function codexAppServerInterruptFailure(result = {}, interruptFailedCode = "codex_turn_interrupt_failed") {
|
|
467
|
+
if (!isPlainObject(result)) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
if (result.ok !== false && result.interrupted !== false) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
code: normalizeCodexRunText(result.code) || interruptFailedCode,
|
|
475
|
+
error: codexAppServerErrorMessage(result, "Codex app-server turn could not be interrupted."),
|
|
476
|
+
ok: false,
|
|
477
|
+
operationOutcome: normalizeCodexRunText(result.operationOutcome) || "interrupt_failed",
|
|
478
|
+
refreshRecommended: true,
|
|
479
|
+
retryable: result.retryable === true
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function codexAppServerInterruptUnavailableResponse({
|
|
484
|
+
active = false,
|
|
485
|
+
threadId = "",
|
|
486
|
+
turnId = ""
|
|
487
|
+
} = {}, interruptFailedCode = "codex_turn_interrupt_failed") {
|
|
488
|
+
return {
|
|
489
|
+
active: active === true,
|
|
490
|
+
code: interruptFailedCode,
|
|
491
|
+
error: active
|
|
492
|
+
? "The active Codex app-server turn is not ready to interrupt yet."
|
|
493
|
+
: "No active Codex app-server turn is available to interrupt.",
|
|
494
|
+
ok: false,
|
|
495
|
+
operationOutcome: "interrupt_unavailable",
|
|
496
|
+
refreshRecommended: true,
|
|
497
|
+
retryable: active === true,
|
|
498
|
+
threadId: normalizeCodexRunText(threadId),
|
|
499
|
+
turnId: normalizeCodexRunText(turnId)
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function codexAppServerFrozenTurnInterruptResponse({
|
|
504
|
+
threadId = "",
|
|
505
|
+
turnId = ""
|
|
506
|
+
} = {}) {
|
|
507
|
+
return {
|
|
508
|
+
interrupted: false,
|
|
509
|
+
ok: true,
|
|
510
|
+
operationOutcome: "already_idle",
|
|
511
|
+
status: "interrupted",
|
|
512
|
+
threadId: normalizeCodexRunText(threadId),
|
|
513
|
+
turnId: normalizeCodexRunText(turnId)
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export function codexAppServerConversationTurnIsActive(status = "") {
|
|
518
|
+
return ["starting", "inProgress"].includes(normalizeCodexRunText(status));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export function codexAppServerMessageText(input = {}) {
|
|
522
|
+
if (typeof input === "string") {
|
|
523
|
+
return normalizeCodexRunText(input);
|
|
524
|
+
}
|
|
525
|
+
if (!isPlainObject(input)) {
|
|
526
|
+
return "";
|
|
527
|
+
}
|
|
528
|
+
return normalizeCodexRunText(input.message);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function codexAppServerMessageDisplayText(input = {}, fallback = "") {
|
|
532
|
+
if (!isPlainObject(input)) {
|
|
533
|
+
return normalizeCodexRunText(fallback || input);
|
|
534
|
+
}
|
|
535
|
+
return normalizeCodexRunText(input.displayMessage || input.message || fallback);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function codexPromptInputFromRequest(input = {}) {
|
|
539
|
+
if (typeof input === "string") {
|
|
540
|
+
return normalizeCodexRunText(input);
|
|
541
|
+
}
|
|
542
|
+
const terminalInput = normalizeCodexRunText(input.terminalInput);
|
|
543
|
+
if (terminalInput) {
|
|
544
|
+
return terminalInput;
|
|
545
|
+
}
|
|
546
|
+
return codexAppServerMessageText(input);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export { codexAppServerRunInputSource, codexAppServerThreadStatus, codexAppServerReadThreadStatus, codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, normalizeCodexRunText, codexAppServerErrorMessage, codexAppServerMessageDeferred, codexPromptInputFromRequest, CODEX_APP_SERVER_AGENT_RUN_ID, CODEX_APP_SERVER_RESULT_PROCESSED_EVENT, CODEX_AGENT_PROVIDER, CODEX_APP_SERVER_ACTIVE_RUN_STATES };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { nativeCommandHook } from "./commandWrapper.js";
|
|
2
|
+
|
|
3
|
+
// Codex invokes a command hook; Claude delivers this same input over its SDK
|
|
4
|
+
// control channel. Neither mechanism owns the application's shell policy.
|
|
5
|
+
const chunks = [];
|
|
6
|
+
let size = 0;
|
|
7
|
+
try {
|
|
8
|
+
for await (const chunk of process.stdin) {
|
|
9
|
+
size += chunk.length;
|
|
10
|
+
if (size > 2 * 1024 * 1024) throw new Error("The native command hook input exceeded its size limit.");
|
|
11
|
+
chunks.push(chunk);
|
|
12
|
+
}
|
|
13
|
+
const input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
14
|
+
process.stdout.write(`${JSON.stringify(nativeCommandHook(process.argv[2], input))}\n`);
|
|
15
|
+
} catch {
|
|
16
|
+
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny",
|
|
17
|
+
permissionDecisionReason: "The host could not bind this native command to its execution wrapper." } })}\n`);
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
const shellQuote = value => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
5
|
+
|
|
6
|
+
export function validateCommandWrapper(wrapper) {
|
|
7
|
+
if (typeof wrapper !== "string" || !isAbsolute(wrapper) || wrapper.includes("\0")) {
|
|
8
|
+
throw new TypeError("host.commandWrapper must name an absolute executable path.");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The host executable receives the original command as one argument. */
|
|
13
|
+
export function wrapNativeCommand(wrapper, command) {
|
|
14
|
+
validateCommandWrapper(wrapper);
|
|
15
|
+
if (typeof command !== "string" || !command.trim() || command.includes("\0")) {
|
|
16
|
+
throw new TypeError("The native shell tool requires valid command text.");
|
|
17
|
+
}
|
|
18
|
+
return `${shellQuote(wrapper)} ${shellQuote(command)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function nativeCommandHook(wrapper, input) {
|
|
22
|
+
if (input?.hook_event_name !== "PreToolUse" || input.tool_name !== "Bash") {
|
|
23
|
+
throw new Error("The command hook received an unexpected native tool.");
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow",
|
|
27
|
+
updatedInput: { ...input.tool_input, command: wrapNativeCommand(wrapper, input.tool_input?.command) } } };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny",
|
|
30
|
+
permissionDecisionReason: error.message } };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function codexCommandHookCommand(wrapper) {
|
|
35
|
+
validateCommandWrapper(wrapper);
|
|
36
|
+
return [process.execPath, fileURLToPath(new URL("./commandHook.js", import.meta.url)), wrapper].map(shellQuote).join(" ");
|
|
37
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { validateConversationOutputSchema } from "./structuredOutput.js";
|
|
2
|
+
|
|
3
|
+
/** Keep one input shape while retaining explicit connection defaults and native login. */
|
|
4
|
+
export function normalizeConversationConfiguration(configuration, { engine, defaultIntegrationId } = {}) {
|
|
5
|
+
const normalized = { ...configuration };
|
|
6
|
+
for (const field of ["integrationId", "model", "effort", "outputSchema"]) {
|
|
7
|
+
if (normalized[field] === undefined) delete normalized[field];
|
|
8
|
+
}
|
|
9
|
+
if (normalized.integrationId === undefined && defaultIntegrationId !== undefined &&
|
|
10
|
+
(engine === "api" || engine === "opencode")) normalized.integrationId = defaultIntegrationId;
|
|
11
|
+
return normalized;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** One configuration shape; connections authorize models and drivers validate native choices. */
|
|
15
|
+
export function validateConversationConfiguration(configuration, { engine, connections, apiClientFactory, connectionRequired = false,
|
|
16
|
+
structuredOutput = false, maxOutputCharacters,
|
|
17
|
+
efforts = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"] } = {}) {
|
|
18
|
+
if (!configuration || typeof configuration.systemPrompt !== "string" || !configuration.systemPrompt.trim() ||
|
|
19
|
+
configuration.systemPrompt.length > 128_000 ||
|
|
20
|
+
Object.keys(configuration).some(key => !["systemPrompt", "integrationId", "model", "effort", "outputSchema"].includes(key)) ||
|
|
21
|
+
[configuration.integrationId, configuration.model, configuration.effort].some(value =>
|
|
22
|
+
value !== undefined && (typeof value !== "string" || !value.trim() || value.length > 256))) {
|
|
23
|
+
throw new TypeError("Conversation configuration requires systemPrompt and optional integrationId, model and effort.");
|
|
24
|
+
}
|
|
25
|
+
if ((connectionRequired || configuration.integrationId !== undefined) &&
|
|
26
|
+
(!configuration.integrationId || typeof connections?.resolve !== "function") &&
|
|
27
|
+
typeof apiClientFactory?.resolveClient !== "function") {
|
|
28
|
+
throw new TypeError(`${engine} requires an authorized integrationId and connection resolver.`);
|
|
29
|
+
}
|
|
30
|
+
if (configuration.effort !== undefined && !efforts.includes(configuration.effort)) {
|
|
31
|
+
throw new TypeError(`${engine} does not support the requested reasoning effort.`);
|
|
32
|
+
}
|
|
33
|
+
if (configuration.outputSchema !== undefined) {
|
|
34
|
+
if (!structuredOutput) throw new TypeError(`${engine} does not support structured output.`);
|
|
35
|
+
validateConversationOutputSchema(configuration.outputSchema, { maxOutputCharacters });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function validateConnectionModel(configuration, connection) {
|
|
40
|
+
if (configuration.model && configuration.model !== connection.model) {
|
|
41
|
+
throw new Error("The configured model differs from the authorized connection's model.");
|
|
42
|
+
}
|
|
43
|
+
}
|