@jskit-ai/assistant-core 0.2.12 → 0.2.14
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/CHANGELOG.md +346 -0
- package/examples/conversation/package.json +5 -5
- package/fixtures/responsive-assistant/ConversationFixture.vue +1 -0
- package/locales/en.json +171 -6
- package/package.json +5 -5
- package/src/client/conversation/AssistantGoalControl.vue +1 -1
- package/src/server/conversation/boundedToolLoop.js +4 -2
- package/src/server/conversation/claudeClient.js +15 -10
- package/src/server/conversation/claudeCommandRun.js +16 -6
- package/src/server/conversation/claudeNativeTurn.js +6 -5
- package/src/server/conversation/claudeProcess.js +20 -8
- package/src/server/conversation/claudeProcessStart.js +1 -1
- package/src/server/conversation/claudeSessionCleanup.js +6 -10
- package/src/server/conversation/claudeTurn.js +2 -3
- package/src/server/conversation/codexAccountReader.js +15 -6
- package/src/server/conversation/codexConversationStops.js +3 -3
- package/src/server/conversation/codexConversations.js +1 -1
- package/src/server/conversation/codexDetachedTurn.js +4 -2
- package/src/server/conversation/codexGoalCommands.js +3 -1
- package/src/server/conversation/codexHelperExecution.js +4 -4
- package/src/server/conversation/codexHelperThreadRecovery.js +2 -2
- package/src/server/conversation/codexHistoryAdapter.js +1 -1
- package/src/server/conversation/codexMessageCommands.js +4 -2
- package/src/server/conversation/codexModelCatalog.js +1 -1
- package/src/server/conversation/codexObservationControl.js +1 -1
- package/src/server/conversation/codexProcess.js +3 -1
- package/src/server/conversation/codexProvider.js +4 -2
- package/src/server/conversation/codexProviderAuth.js +17 -9
- package/src/server/conversation/codexProviderInvalidation.js +6 -2
- package/src/server/conversation/codexProviderThreads.js +4 -2
- package/src/server/conversation/codexProviderTurns.js +4 -2
- package/src/server/conversation/codexRenewalThreads.js +2 -2
- package/src/server/conversation/conversationTurnDelivery.js +3 -1
- package/src/server/conversation/conversationTurns.js +3 -1
- package/src/server/conversation/openCodeProcess.js +5 -9
- package/src/server/conversation/openCodeRuntime.js +3 -1
- package/src/server/conversation/openCodeTurn.js +3 -2
- package/src/server/conversation/providers/claudeDriver.js +12 -4
- package/src/server/conversation/providers/codex.js +8 -4
- package/src/server/conversation/providers/codexDriver.js +23 -10
- package/src/server/conversation/providers/codexDriverConnection.js +18 -6
- package/src/server/conversation/providers/codexDriverTurns.js +3 -1
- package/src/server/conversation/providers/opencode.js +3 -1
- package/src/server/conversation/providers/opencodeDriver.js +3 -1
- package/src/server/conversation/providers/opencodeStandalone.js +37 -13
- package/src/server/conversation/providers/scoped.js +3 -1
- package/src/server/conversation/systemPrompt.js +3 -1
- package/src/server/conversation/tools.js +6 -2
- package/src/shared/assistantSettingsResource.js +14 -2
- package/test/assistantScroll.browser.test.js +4 -0
- package/test/claudeLifecycle.test.js +17 -0
|
@@ -262,7 +262,7 @@ export class CodexAppServerAgentProvider {
|
|
|
262
262
|
|
|
263
263
|
staleRuntimeError() {
|
|
264
264
|
return Object.assign(new Error("Codex (OpenAI) changed account. Send your message again."), {
|
|
265
|
-
code: `${this.errorPrefix}codex_account_changed`, retryable: true
|
|
265
|
+
code: `${this.errorPrefix}codex_account_changed`, retryable: true, messageCode: "assistant.codex.accountChangedSendAgain", messageParams: {}
|
|
266
266
|
});
|
|
267
267
|
}
|
|
268
268
|
|
|
@@ -610,7 +610,9 @@ export class CodexAppServerAgentProvider {
|
|
|
610
610
|
close() {
|
|
611
611
|
const { onClose = closeNothing } = this.options;
|
|
612
612
|
onClose();
|
|
613
|
-
for (const reject of this.settingsWaiters) reject(new Error("The assistant connection closed before confirming its settings.")
|
|
613
|
+
for (const reject of this.settingsWaiters) reject(Object.assign(new Error("The assistant connection closed before confirming its settings."), {
|
|
614
|
+
messageCode: "assistant.codex.connectionClosedBeforeSettings", messageParams: {}
|
|
615
|
+
}));
|
|
614
616
|
this.settingsWaiters.clear();
|
|
615
617
|
this.conversationRuntime.close();
|
|
616
618
|
this.turns.clear();
|
|
@@ -24,7 +24,8 @@ function codexAppServerHelperLoginParams(auth = {}, errorPrefix = "") {
|
|
|
24
24
|
}
|
|
25
25
|
throw codexAppServerHelperAuthError(
|
|
26
26
|
`${errorPrefix}codex_helper_auth_invalid`,
|
|
27
|
-
"The selected Codex authentication mode is unsupported. Reconnect Codex and retry."
|
|
27
|
+
"The selected Codex authentication mode is unsupported. Reconnect Codex and retry.",
|
|
28
|
+
{ messageCode: "assistant.codex.authModeUnsupported" }
|
|
28
29
|
);
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -65,7 +66,7 @@ export function codexAppServerHelperRequestError(error = null, reason = "", erro
|
|
|
65
66
|
) {
|
|
66
67
|
return error;
|
|
67
68
|
}
|
|
68
|
-
const failure = new Error("Codex isolated helper execution failed. Retry the task.");
|
|
69
|
+
const failure = Object.assign(new Error("Codex isolated helper execution failed. Retry the task."), { messageCode: "assistant.codex.helperExecutionFailed", messageParams: {} });
|
|
69
70
|
failure.code = `${errorPrefix}codex_helper_provider_request_failed`;
|
|
70
71
|
const providerCode = Number(error?.code);
|
|
71
72
|
if (Number.isSafeInteger(providerCode)) {
|
|
@@ -109,7 +110,8 @@ export function createCodexProviderAuth({ provider }) {
|
|
|
109
110
|
if (helperAuthBlocked) {
|
|
110
111
|
throw provider.accountReader.error(
|
|
111
112
|
`${provider.errorPrefix}codex_helper_auth_changed`,
|
|
112
|
-
"The selected Codex account changed during helper work. Retry the task with the current account."
|
|
113
|
+
"The selected Codex account changed during helper work. Retry the task with the current account.",
|
|
114
|
+
{ messageCode: "assistant.codex.helperAccountChanged" }
|
|
113
115
|
);
|
|
114
116
|
}
|
|
115
117
|
const current = await selectedHelperAuth();
|
|
@@ -117,7 +119,8 @@ export function createCodexProviderAuth({ provider }) {
|
|
|
117
119
|
helperAuthBlocked = true;
|
|
118
120
|
throw provider.accountReader.error(
|
|
119
121
|
`${provider.errorPrefix}codex_helper_auth_changed`,
|
|
120
|
-
"The selected Codex account changed during helper work. Retry the task with the current account."
|
|
122
|
+
"The selected Codex account changed during helper work. Retry the task with the current account.",
|
|
123
|
+
{ messageCode: "assistant.codex.helperAccountChanged" }
|
|
121
124
|
);
|
|
122
125
|
}
|
|
123
126
|
}
|
|
@@ -133,7 +136,8 @@ export function createCodexProviderAuth({ provider }) {
|
|
|
133
136
|
) {
|
|
134
137
|
throw provider.accountReader.error(
|
|
135
138
|
`${provider.errorPrefix}codex_helper_auth_changed`,
|
|
136
|
-
"The selected Codex account changed while helper execution was starting. Retry the task."
|
|
139
|
+
"The selected Codex account changed while helper execution was starting. Retry the task.",
|
|
140
|
+
{ messageCode: "assistant.codex.helperAccountChangedStarting" }
|
|
137
141
|
);
|
|
138
142
|
}
|
|
139
143
|
helperAuth = auth;
|
|
@@ -162,7 +166,8 @@ export function createCodexProviderAuth({ provider }) {
|
|
|
162
166
|
helperAuthBlocked = true;
|
|
163
167
|
throw provider.accountReader.error(
|
|
164
168
|
`${provider.errorPrefix}codex_helper_auth_unavailable`,
|
|
165
|
-
"Codex could not activate the selected account for isolated helper work. Reconnect Codex and retry."
|
|
169
|
+
"Codex could not activate the selected account for isolated helper work. Reconnect Codex and retry.",
|
|
170
|
+
{ messageCode: "assistant.codex.helperAccountActivationFailed" }
|
|
166
171
|
);
|
|
167
172
|
}
|
|
168
173
|
}
|
|
@@ -182,7 +187,8 @@ export function createCodexProviderAuth({ provider }) {
|
|
|
182
187
|
helperAuthBlocked = true;
|
|
183
188
|
throw provider.accountReader.error(
|
|
184
189
|
`${provider.errorPrefix}codex_helper_auth_refresh_rejected`,
|
|
185
|
-
"Codex helper authentication refresh was rejected. Reconnect Codex and retry."
|
|
190
|
+
"Codex helper authentication refresh was rejected. Reconnect Codex and retry.",
|
|
191
|
+
{ messageCode: "assistant.codex.helperAuthRefreshRejected" }
|
|
186
192
|
);
|
|
187
193
|
}
|
|
188
194
|
const refreshed = await selectedHelperAuth();
|
|
@@ -193,14 +199,16 @@ export function createCodexProviderAuth({ provider }) {
|
|
|
193
199
|
helperAuthBlocked = true;
|
|
194
200
|
throw provider.accountReader.error(
|
|
195
201
|
`${provider.errorPrefix}codex_helper_auth_changed`,
|
|
196
|
-
"The selected Codex account changed during helper work. Retry the task with the current account."
|
|
202
|
+
"The selected Codex account changed during helper work. Retry the task with the current account.",
|
|
203
|
+
{ messageCode: "assistant.codex.helperAccountChanged" }
|
|
197
204
|
);
|
|
198
205
|
}
|
|
199
206
|
if (refreshed.secretSignature === currentAuth.secretSignature) {
|
|
200
207
|
helperAuthBlocked = true;
|
|
201
208
|
throw provider.accountReader.error(
|
|
202
209
|
`${provider.errorPrefix}codex_helper_auth_refresh_pending`,
|
|
203
|
-
"The selected Codex account has not produced a newer access token yet. Reconnect Codex and retry."
|
|
210
|
+
"The selected Codex account has not produced a newer access token yet. Reconnect Codex and retry.",
|
|
211
|
+
{ messageCode: "assistant.codex.helperAccessTokenStale" }
|
|
204
212
|
);
|
|
205
213
|
}
|
|
206
214
|
helperAuth = refreshed;
|
|
@@ -60,7 +60,9 @@ export function createCodexProviderInvalidation({ inScope, withLifecycle, forget
|
|
|
60
60
|
let cursor;
|
|
61
61
|
do {
|
|
62
62
|
const page = await request(inspector, "thread/loaded/list", { limit: 100, ...(cursor ? { cursor } : {}) });
|
|
63
|
-
if (!Array.isArray(page?.data)) throw new Error("Codex returned an invalid loaded-thread inventory. Restore its connection before retrying.")
|
|
63
|
+
if (!Array.isArray(page?.data)) throw Object.assign(new Error("Codex returned an invalid loaded-thread inventory. Restore its connection before retrying."), {
|
|
64
|
+
messageCode: "assistant.codex.loadedThreadsInvalid", messageParams: {}
|
|
65
|
+
});
|
|
64
66
|
for (const id of page.data) {
|
|
65
67
|
if (typeof id !== "string" || !id) throw new Error("Codex returned an invalid loaded-thread identity.");
|
|
66
68
|
candidates.add(id);
|
|
@@ -77,7 +79,9 @@ export function createCodexProviderInvalidation({ inScope, withLifecycle, forget
|
|
|
77
79
|
const { thread } = await request(inspector, "thread/read", { threadId: id, includeTurns: false });
|
|
78
80
|
if (!thread || thread.id !== id || typeof thread.modelProvider !== "string" || !thread.modelProvider ||
|
|
79
81
|
!(typeof thread.status === "string" ? thread.status : thread.status?.type)) {
|
|
80
|
-
throw new Error("Codex returned an untrustworthy thread/provider inventory. Restore its connection before changing credentials.")
|
|
82
|
+
throw Object.assign(new Error("Codex returned an untrustworthy thread/provider inventory. Restore its connection before changing credentials."), {
|
|
83
|
+
messageCode: "assistant.codex.threadInventoryUntrusted", messageParams: {}
|
|
84
|
+
});
|
|
81
85
|
}
|
|
82
86
|
if (dedicated && thread.modelProvider !== modelProviderId) {
|
|
83
87
|
throw new Error("Codex's provider credential home retains another provider thread. Close its native terminal/helper and retry; unrelated threads remain running.");
|
|
@@ -243,7 +243,9 @@ export function codexApplicationToolConfiguration(tools) {
|
|
|
243
243
|
|
|
244
244
|
export function assertCodexApplicationToolSchemaIdentity(threadId, savedIdentity, toolSchemaIdentity) {
|
|
245
245
|
if (threadId && (savedIdentity || codexApplicationToolConfiguration().toolSchemaIdentity) !== toolSchemaIdentity) {
|
|
246
|
-
throw new Error("This Codex conversation was created with different application tool entry points. Start a new conversation to enable or remove application tools.")
|
|
246
|
+
throw Object.assign(new Error("This Codex conversation was created with different application tool entry points. Start a new conversation to enable or remove application tools."), {
|
|
247
|
+
messageCode: "assistant.codex.toolEntryPointsChanged", messageParams: {}
|
|
248
|
+
});
|
|
247
249
|
}
|
|
248
250
|
}
|
|
249
251
|
|
|
@@ -394,7 +396,7 @@ export function createCodexProviderThreads({ provider }) {
|
|
|
394
396
|
// Paginated history is required only for persistent conversations.
|
|
395
397
|
if (params.ephemeral !== true && response?.thread?.historyMode === "legacy") {
|
|
396
398
|
throw Object.assign(new Error("Codex did not create the required paginated conversation history. Update Codex before starting a conversation."), {
|
|
397
|
-
code: `${provider.errorPrefix}codex_history_unsupported
|
|
399
|
+
code: `${provider.errorPrefix}codex_history_unsupported`, messageCode: "assistant.codex.paginatedConversationHistoryMissing", messageParams: {}
|
|
398
400
|
});
|
|
399
401
|
}
|
|
400
402
|
if (response?.thread?.historyMode === "paginated" && response.thread.ephemeral !== true) {
|
|
@@ -410,7 +410,7 @@ export function createCodexProviderTurns({ provider }) {
|
|
|
410
410
|
if (goal?.status === "active") {
|
|
411
411
|
const result = await request("thread/goal/set", { threadId, status: "paused" });
|
|
412
412
|
if (result.goal?.status !== "paused" || result.goal?.createdAt !== goal.createdAt || result.goal?.objective !== goal.objective) {
|
|
413
|
-
throw new Error("Codex did not confirm that the same goal was paused.");
|
|
413
|
+
throw Object.assign(new Error("Codex did not confirm that the same goal was paused."), { messageCode: "assistant.codex.goalPauseUnconfirmed", messageParams: {} });
|
|
414
414
|
}
|
|
415
415
|
goal = result.goal;
|
|
416
416
|
}
|
|
@@ -425,7 +425,9 @@ export function createCodexProviderTurns({ provider }) {
|
|
|
425
425
|
}
|
|
426
426
|
if (!turnId) throw new Error("Codex's running turn could not be identified.");
|
|
427
427
|
await interruptNativeTurn(client, threadId, turnId, signal);
|
|
428
|
-
if (await readStatus() !== "idle") throw new Error("Codex did not confirm that its turn stopped.")
|
|
428
|
+
if (await readStatus() !== "idle") throw Object.assign(new Error("Codex did not confirm that its turn stopped."), {
|
|
429
|
+
messageCode: "assistant.codex.turnStopUnconfirmed", messageParams: {}
|
|
430
|
+
});
|
|
429
431
|
}
|
|
430
432
|
return { goal };
|
|
431
433
|
}
|
|
@@ -194,14 +194,14 @@ export async function resumeExactCodexAppServerThread({
|
|
|
194
194
|
if (error?.code === errorCode) {
|
|
195
195
|
throw error;
|
|
196
196
|
}
|
|
197
|
-
throw codexRenewalThreadError(
|
|
197
|
+
throw Object.assign(codexRenewalThreadError(
|
|
198
198
|
errorCode,
|
|
199
199
|
"The old assistant thread cannot be read. Write or edit the handover manually instead.",
|
|
200
200
|
{
|
|
201
201
|
expectedThreadId: normalizedExpectedThreadId,
|
|
202
202
|
providerError: normalizeAgentText(error?.message)
|
|
203
203
|
}
|
|
204
|
-
);
|
|
204
|
+
), { messageCode: "assistant.codex.oldThreadUnreadable", messageParams: {} });
|
|
205
205
|
}
|
|
206
206
|
throw error;
|
|
207
207
|
}
|
|
@@ -297,7 +297,9 @@ function createConversationTurnDelivery({ storage, streams, events, reads, deliv
|
|
|
297
297
|
admitted.resolve(result.value);
|
|
298
298
|
return;
|
|
299
299
|
}
|
|
300
|
-
if (!request.accepted) throw new Error("The engine did not acknowledge the message.")
|
|
300
|
+
if (!request.accepted) throw Object.assign(new Error("The engine did not acknowledge the message."), {
|
|
301
|
+
messageCode: "assistant.conversation.messageNotAcknowledged", messageParams: {}
|
|
302
|
+
});
|
|
301
303
|
}).catch(async error => {
|
|
302
304
|
if (!request.accepted && (error.delivery === "not-sent" || !entry.conversation && error.delivery !== "uncertain")) {
|
|
303
305
|
try { await input.onPromptRejected(); }
|
|
@@ -494,7 +494,9 @@ function createConversationTurns({ storage, streams, events, entries, reads, del
|
|
|
494
494
|
entry.storageFailure.cause = saveError;
|
|
495
495
|
problem ||= saveError;
|
|
496
496
|
}
|
|
497
|
-
if (!initial.admissionSettled) initial.admission.reject(problem || new Error("The engine did not acknowledge the message.")
|
|
497
|
+
if (!initial.admissionSettled) initial.admission.reject(problem || Object.assign(new Error("The engine did not acknowledge the message."), {
|
|
498
|
+
messageCode: "assistant.conversation.messageNotAcknowledged", messageParams: {}
|
|
499
|
+
}));
|
|
498
500
|
entry.active = null;
|
|
499
501
|
await events.setPhase(entry, "", active.request.turnId);
|
|
500
502
|
await events.emit(entry, { type: "settled", turnId: active.request.turnId, status: entry.storageFailure || entry.executionFailure ? "failed" : status,
|
|
@@ -130,10 +130,10 @@ async function waitForOpenCodeReady({
|
|
|
130
130
|
signal?.throwIfAborted();
|
|
131
131
|
if (health?.healthy === true) {
|
|
132
132
|
if (expectedVersion && normalizeNullableText(health.version) !== expectedVersion) {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
);
|
|
133
|
+
const installed = normalizeNullableText(health.version) || "(unknown)";
|
|
134
|
+
const error = new Error(`OpenCode ${installed} is installed; this host requires ${expectedVersion}.`);
|
|
136
135
|
error.code = "assistant_opencode_version_mismatch";
|
|
136
|
+
error.params = { installed, required: expectedVersion };
|
|
137
137
|
throw error;
|
|
138
138
|
}
|
|
139
139
|
return health;
|
|
@@ -569,12 +569,8 @@ export async function verifyOpenCodeApiKey({
|
|
|
569
569
|
Boolean(normalizeNullableText(result?.code)) ||
|
|
570
570
|
(Number.isSafeInteger(exitCode) && exitCode !== 1);
|
|
571
571
|
const error = unavailable
|
|
572
|
-
? new AppError(503, "OpenCode API-key verification could not run.", {
|
|
573
|
-
|
|
574
|
-
})
|
|
575
|
-
: new AppError(422, "OpenCode rejected the API-key verification request.", {
|
|
576
|
-
code: "assistant_opencode_key_verification_failed"
|
|
577
|
-
});
|
|
572
|
+
? new AppError(503, "OpenCode API-key verification could not run.", { code: "assistant_opencode_key_verification_unavailable" })
|
|
573
|
+
: new AppError(422, "OpenCode rejected the API-key verification request.", { code: "assistant_opencode_key_verification_failed" });
|
|
578
574
|
error.retryable = unavailable;
|
|
579
575
|
throw error;
|
|
580
576
|
}
|
|
@@ -188,7 +188,9 @@ export function createOpenCodeSharedRuntime({ onStop = ignoreRuntimeStop, scope,
|
|
|
188
188
|
function allowTerminalAttachments(sessionId, terminalSessionId, input) {
|
|
189
189
|
const target = [...processes.values()].find((candidate) =>
|
|
190
190
|
candidate.sessionId === sessionId && candidate.terminalSessionId === terminalSessionId);
|
|
191
|
-
if (!target) throw new Error("Reopen the OpenCode terminal before attaching files.")
|
|
191
|
+
if (!target) throw Object.assign(new Error("Reopen the OpenCode terminal before attaching files."), {
|
|
192
|
+
messageCode: "assistant.opencode.reopenTerminalToAttach", messageParams: {}
|
|
193
|
+
});
|
|
192
194
|
return target.server.client.allowConversationAttachments(target.upstreamSessionId, input.attachments);
|
|
193
195
|
}
|
|
194
196
|
|
|
@@ -83,7 +83,7 @@ async function consumeOpenCodeEvents(client, conversationId, {
|
|
|
83
83
|
) {
|
|
84
84
|
const failure = normalizeObject(event.data?.properties?.error);
|
|
85
85
|
onError(Object.assign(new Error(readError({ error: failure }) || "OpenCode turn failed."), {
|
|
86
|
-
name: normalizeNullableText(failure.name) || "Error"
|
|
86
|
+
name: normalizeNullableText(failure.name) || "Error", messageCode: "assistant.opencode.turnFailed", messageParams: {}
|
|
87
87
|
}));
|
|
88
88
|
}
|
|
89
89
|
if (summary.type && current && typeof onEvent === "function") {
|
|
@@ -527,7 +527,8 @@ export async function runOpenCodeConversationTurn(client, conversationId, input
|
|
|
527
527
|
createError = (reason, message, details) => new AppError(
|
|
528
528
|
["thread_mismatch", "fresh_thread_required", "unrelated_history"].includes(reason) ? 409 : 502,
|
|
529
529
|
message || (reason === "unreadable" ? "The exact OpenCode turn did not produce a readable result." : "OpenCode did not finish the turn."),
|
|
530
|
-
{ code: `assistant_opencode_turn_${reason}`, details
|
|
530
|
+
{ code: `assistant_opencode_turn_${reason}`, details,
|
|
531
|
+
...(message ? {} : reason === "unreadable" ? { messageCode: "assistant.opencode.turnUnreadable" } : { messageCode: "assistant.opencode.turnNotFinished" }) })
|
|
531
532
|
} = input;
|
|
532
533
|
if (
|
|
533
534
|
(expectedThreadId && expectedThreadId !== conversationId) ||
|
|
@@ -124,7 +124,9 @@ export function createClaudeConversationDriver({
|
|
|
124
124
|
credentialHome: { home: env.HOME || homedir() }, commandRunner: captureAccountStatus, signal });
|
|
125
125
|
signal.throwIfAborted();
|
|
126
126
|
if (!account.loggedIn || !account.email) {
|
|
127
|
-
throw new Error(account.error || "Sign in with claude auth login before using this conversation.")
|
|
127
|
+
throw Object.assign(new Error(account.error || "Sign in with claude auth login before using this conversation."), {
|
|
128
|
+
messageCode: "assistant.claude.signInRequired", messageParams: {}
|
|
129
|
+
});
|
|
128
130
|
}
|
|
129
131
|
return hash([configRoot, account.authMethod, account.email.toLowerCase()]);
|
|
130
132
|
}
|
|
@@ -221,9 +223,13 @@ export function createClaudeConversationDriver({
|
|
|
221
223
|
}
|
|
222
224
|
const provider = nativeAiProvider(providerId);
|
|
223
225
|
const model = nativeAiModel(connection.model, providerId);
|
|
224
|
-
if (providerId !== "anthropic" && (!provider || !model)) throw new Error("This exact provider/model is not supported by the Claude integration. Choose a supported model without renaming it.")
|
|
226
|
+
if (providerId !== "anthropic" && (!provider || !model)) throw Object.assign(new Error("This exact provider/model is not supported by the Claude integration. Choose a supported model without renaming it."), {
|
|
227
|
+
messageCode: "assistant.claude.modelUnsupported", messageParams: {}
|
|
228
|
+
});
|
|
225
229
|
validateConnectionModel(configuration, connection);
|
|
226
|
-
if (model && configuration.effort && !model.variants.includes(configuration.effort)) throw new Error("The selected Claude model does not support this reasoning effort.")
|
|
230
|
+
if (model && configuration.effort && !model.variants.includes(configuration.effort)) throw Object.assign(new Error("The selected Claude model does not support this reasoning effort."), {
|
|
231
|
+
messageCode: "assistant.claude.reasoningEffortUnsupported", messageParams: {}
|
|
232
|
+
});
|
|
227
233
|
selected = claudeModelConfiguration({ providerId, model: connection.model }, {
|
|
228
234
|
apiKey: connection.apiKey, baseUrl: provider?.claudeBaseUrl || connection.baseURL || "https://api.anthropic.com"
|
|
229
235
|
});
|
|
@@ -390,7 +396,9 @@ export function createClaudeConversationDriver({
|
|
|
390
396
|
? await realpath(env.HOME || homedir()) : scopeWorkdir;
|
|
391
397
|
if (binding.workdir !== nativeWorkdir || binding.configRoot !== configRoot ||
|
|
392
398
|
(Object.hasOwn(binding, "scopeWorkdir") && binding.scopeWorkdir !== scopeWorkdir)) {
|
|
393
|
-
throw new Error("This Claude conversation belongs to another working directory or credential home. Restore its original host configuration.")
|
|
399
|
+
throw Object.assign(new Error("This Claude conversation belongs to another working directory or credential home. Restore its original host configuration."), {
|
|
400
|
+
messageCode: "assistant.claude.otherHostConfiguration", messageParams: {}
|
|
401
|
+
});
|
|
394
402
|
}
|
|
395
403
|
owner = standaloneOwner();
|
|
396
404
|
const context = { key: scopeWorkdir, workdir: binding.workdir, binding, reportFailure,
|
|
@@ -133,7 +133,9 @@ export function createCodexConversationAdapter(host) {
|
|
|
133
133
|
(bound?.managed === false ? bound.params.config.shell_environment_policy : null);
|
|
134
134
|
if (!settings && fixedPolicy && (params.developerInstructions == null || params.ephemeral || bound?.params.ephemeral)) {
|
|
135
135
|
if (bound && params.developerInstructions != null && instructionPrompts.get(threadId) !== params.developerInstructions) {
|
|
136
|
-
throw new Error("Start a new ephemeral conversation to change its instructions.")
|
|
136
|
+
throw Object.assign(new Error("Start a new ephemeral conversation to change its instructions."), {
|
|
137
|
+
messageCode: "assistant.codex.ephemeralInstructionsFixed", messageParams: {}
|
|
138
|
+
});
|
|
137
139
|
}
|
|
138
140
|
assertRecoveryOpen();
|
|
139
141
|
return operation(threadParameters({ ...bound?.params, ...params }, {}));
|
|
@@ -162,7 +164,7 @@ export function createCodexConversationAdapter(host) {
|
|
|
162
164
|
const { thread } = await request("thread/read", { threadId, includeTurns: false });
|
|
163
165
|
if (thread?.historyMode !== "paginated") {
|
|
164
166
|
throw Object.assign(new Error("This conversation uses an unsupported Codex history format. Renew the session to keep your files and saved chat and start a fresh conversation. You can write the handover yourself."), {
|
|
165
|
-
code: "assistant_codex_history_unsupported"
|
|
167
|
+
code: "assistant_codex_history_unsupported", messageCode: "assistant.codex.historyFormatUnsupported", messageParams: {}
|
|
166
168
|
});
|
|
167
169
|
}
|
|
168
170
|
nativeModelProvider = thread?.modelProvider || "";
|
|
@@ -331,10 +333,12 @@ export function createCodexConversationAdapter(host) {
|
|
|
331
333
|
host.onRecoveryFailure(threadId);
|
|
332
334
|
bindings.delete(threadId);
|
|
333
335
|
log("recovery_failed", { code: cause?.code || "", reason: cause.message });
|
|
334
|
-
|
|
336
|
+
const retained = ["assistant_agent_control_binding_retained", "assistant_codex_provider_binding_retained"].includes(cause?.code);
|
|
337
|
+
throw Object.assign(new Error(retained
|
|
335
338
|
? `${cause.message} Your conversation and work are preserved.`
|
|
336
339
|
: "The assistant connection could not be verified. Your conversation and files are preserved. Retry the connection, or renew the session to continue in a fresh conversation.", { cause }), {
|
|
337
|
-
code: "assistant_agent_control_recovery_failed", retryable: true
|
|
340
|
+
code: "assistant_agent_control_recovery_failed", retryable: true,
|
|
341
|
+
...(retained ? {} : { messageCode: "assistant.codex.connectionUnverified", messageParams: {} })
|
|
338
342
|
});
|
|
339
343
|
}
|
|
340
344
|
// A newer settings save supersedes the older choice before scheduling
|
|
@@ -50,6 +50,19 @@ function assertCodexApplicationEnvironment(host) {
|
|
|
50
50
|
if (!host.env) throw new TypeError("createCodexConversationDriver requires the application's environment, such as process.env.");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Refusals Codex conversations send from several places; each names its sentence.
|
|
54
|
+
function codexOutputTooLarge() {
|
|
55
|
+
return Object.assign(new Error("Codex output exceeded the configured limit."), {
|
|
56
|
+
messageCode: "assistant.codex.outputTooLarge", messageParams: {}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function nativeGoalChanged() {
|
|
61
|
+
return Object.assign(new Error("The native goal changed. Read it again before changing it."), {
|
|
62
|
+
messageCode: "assistant.codex.nativeGoalChanged", messageParams: {}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
/**
|
|
54
67
|
* Conversations own threads and observers on the account's shared native runtime.
|
|
55
68
|
* @param {object} dependencies
|
|
@@ -95,7 +108,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
95
108
|
} catch (error) {
|
|
96
109
|
// Native keychain logins can have no auth file. Never expose malformed
|
|
97
110
|
// credential contents in a parser's error message.
|
|
98
|
-
if (error.code !== "ENOENT") throw new Error("Codex credential data could not be read. Restore its account configuration.");
|
|
111
|
+
if (error.code !== "ENOENT") throw Object.assign(new Error("Codex credential data could not be read. Restore its account configuration."), { messageCode: "assistant.codex.credentialsUnreadable", messageParams: {} });
|
|
99
112
|
} finally { await file?.close(); }
|
|
100
113
|
return auth;
|
|
101
114
|
}
|
|
@@ -110,7 +123,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
110
123
|
}
|
|
111
124
|
const key = env.OPENAI_API_KEY || auth.OPENAI_API_KEY;
|
|
112
125
|
if (account?.type === "apiKey" && typeof key === "string" && key) return hash([configRoot, account.type, key]);
|
|
113
|
-
throw new Error("Sign in with codex login before using this conversation.");
|
|
126
|
+
throw Object.assign(new Error("Sign in with codex login before using this conversation."), { messageCode: "assistant.codex.signInRequired", messageParams: {} });
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
async function nativeHost(selected) {
|
|
@@ -137,7 +150,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
137
150
|
identity: generation,
|
|
138
151
|
generation,
|
|
139
152
|
async assertCurrent(captured) {
|
|
140
|
-
if (captured && captured !== await generation()) throw new Error("Codex authentication changed. Reopen the conversation with its original account.");
|
|
153
|
+
if (captured && captured !== await generation()) throw Object.assign(new Error("Codex authentication changed. Reopen the conversation with its original account."), { messageCode: "assistant.codex.authChangedReopen", messageParams: {} });
|
|
141
154
|
},
|
|
142
155
|
markInvalid() {},
|
|
143
156
|
reconnectRequiredCode: "codex_reconnect_required",
|
|
@@ -297,7 +310,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
297
310
|
}
|
|
298
311
|
if (!supplied) assertCodexApplicationEnvironment(host);
|
|
299
312
|
if (!supplied && (!binding || typeof binding.threadId !== "string" || binding.workdir !== await realpath(workdir) || binding.configRoot !== configRoot)) {
|
|
300
|
-
throw new Error("This Codex conversation belongs to another working directory or credential home. Restore its original host configuration.");
|
|
313
|
+
throw Object.assign(new Error("This Codex conversation belongs to another working directory or credential home. Restore its original host configuration."), { messageCode: "assistant.codex.otherHostConfiguration", messageParams: {} });
|
|
301
314
|
}
|
|
302
315
|
const session = {
|
|
303
316
|
binding,
|
|
@@ -325,7 +338,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
325
338
|
: name.includes("AssistantMessage") ? Math.min(maximumOutput, maximumFinalReply) : maximumOutput;
|
|
326
339
|
const length = input.text?.length || 0;
|
|
327
340
|
if (length <= limit) return nativeStore[name](id, input);
|
|
328
|
-
if (!name.includes("AssistantMessage") || length > maximumOutput) throw
|
|
341
|
+
if (!name.includes("AssistantMessage") || length > maximumOutput) throw codexOutputTooLarge();
|
|
329
342
|
return nativeStore.mutateSession(id, async transaction => {
|
|
330
343
|
const { runtime: state } = await transaction.readMetadata();
|
|
331
344
|
const identity = input.nativeIdentity;
|
|
@@ -344,7 +357,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
344
357
|
break;
|
|
345
358
|
}
|
|
346
359
|
}
|
|
347
|
-
throw
|
|
360
|
+
throw codexOutputTooLarge();
|
|
348
361
|
});
|
|
349
362
|
};
|
|
350
363
|
const runtime = supplied ? conversation.runtime : { ...conversation.runtime, store: { ...nativeStore,
|
|
@@ -353,9 +366,9 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
353
366
|
writeConversationCommentaryMessage: boundedWriter("writeConversationCommentaryMessage"),
|
|
354
367
|
writeConversationThinkingMessage: boundedWriter("writeConversationThinkingMessage"),
|
|
355
368
|
async updateConversationStream(id, input) {
|
|
356
|
-
if ((input.delta?.length || input.text?.length || 0) > maximumOutput) throw
|
|
369
|
+
if ((input.delta?.length || input.text?.length || 0) > maximumOutput) throw codexOutputTooLarge();
|
|
357
370
|
const stream = await nativeStore.updateConversationStream(id, input);
|
|
358
|
-
if (stream?.messages.some(message => message.text.length > maximumOutput)) throw
|
|
371
|
+
if (stream?.messages.some(message => message.text.length > maximumOutput)) throw codexOutputTooLarge();
|
|
359
372
|
return stream;
|
|
360
373
|
},
|
|
361
374
|
writeConversationUserMessage(id, input) {
|
|
@@ -497,7 +510,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
497
510
|
if (representation !== "native") {
|
|
498
511
|
session.binding.threadId = nativeContext.threadId;
|
|
499
512
|
const goal = session.binding.goal || null;
|
|
500
|
-
if ((goalValue(goal)?.id || null) !== (input.expectedGoalId || null)) throw
|
|
513
|
+
if ((goalValue(goal)?.id || null) !== (input.expectedGoalId || null)) throw nativeGoalChanged();
|
|
501
514
|
command = { ...input, threadId: session.binding.threadId,
|
|
502
515
|
createdAt: goal?.createdAt, objective: input.action === "set" ? input.objective : goal?.objective };
|
|
503
516
|
}
|
|
@@ -523,7 +536,7 @@ export function createCodexConversationDriver({ connections, host = {}, limits =
|
|
|
523
536
|
prepareThread: Boolean(session.binding.threadId && ["set", "resume"].includes(input.action)), tools
|
|
524
537
|
});
|
|
525
538
|
const goal = session.binding.threadId ? (await session.native.readGoal(session.binding.threadId, { signal })).goal : null;
|
|
526
|
-
if ((goalValue(goal)?.id || null) !== (input.expectedGoalId || null)) throw
|
|
539
|
+
if ((goalValue(goal)?.id || null) !== (input.expectedGoalId || null)) throw nativeGoalChanged();
|
|
527
540
|
const result = await owner.updateGoal(sessionId, { ...input, threadId: session.binding.threadId,
|
|
528
541
|
createdAt: goal?.createdAt, objective: input.action === "set" ? input.objective : goal?.objective }, {
|
|
529
542
|
context: nativeContext, signal: context?.signal,
|
|
@@ -93,7 +93,9 @@ export function createCodexDriverConnection({
|
|
|
93
93
|
prepareThreadEnvironment: environment => session.preparedThreadSettings?.config.shell_environment_policy.set || environment,
|
|
94
94
|
prepareThreadResumeParams(threadId, params) {
|
|
95
95
|
if (threadId !== session.binding.threadId || !session.preparedThreadSettings) {
|
|
96
|
-
throw new Error("Codex requires current authorized settings before resuming this conversation.")
|
|
96
|
+
throw Object.assign(new Error("Codex requires current authorized settings before resuming this conversation."), {
|
|
97
|
+
messageCode: "assistant.codex.authorizedSettingsRequired", messageParams: {}
|
|
98
|
+
});
|
|
97
99
|
}
|
|
98
100
|
return { ...session.preparedThreadSettings, ...params,
|
|
99
101
|
config: { ...session.preparedThreadSettings.config, ...params.config } };
|
|
@@ -197,9 +199,13 @@ export function createCodexDriverConnection({
|
|
|
197
199
|
signal.throwIfAborted();
|
|
198
200
|
const provider = nativeAiProvider(connection.providerId);
|
|
199
201
|
const model = nativeAiModel(connection.model, connection.providerId);
|
|
200
|
-
if (!provider || !model) throw new Error("This exact provider/model is not supported by the Codex integration. Choose a supported model without renaming it.")
|
|
202
|
+
if (!provider || !model) throw Object.assign(new Error("This exact provider/model is not supported by the Codex integration. Choose a supported model without renaming it."), {
|
|
203
|
+
messageCode: "assistant.codex.modelUnsupported", messageParams: {}
|
|
204
|
+
});
|
|
201
205
|
validateConnectionModel(configuration, connection);
|
|
202
|
-
if (configuration.effort && !model.variants.includes(configuration.effort)) throw new Error("The selected Codex model does not support this reasoning effort.")
|
|
206
|
+
if (configuration.effort && !model.variants.includes(configuration.effort)) throw Object.assign(new Error("The selected Codex model does not support this reasoning effort."), {
|
|
207
|
+
messageCode: "assistant.codex.reasoningEffortUnsupported", messageParams: {}
|
|
208
|
+
});
|
|
203
209
|
const identity = hash([provider.id, provider.baseUrl, connection.apiKey]);
|
|
204
210
|
const previous = session.binding.connectionIdentities?.[provider.id];
|
|
205
211
|
const expected = receiptBinding?.connectionIdentities?.[provider.id];
|
|
@@ -207,7 +213,9 @@ export function createCodexDriverConnection({
|
|
|
207
213
|
if (requireBound && (previous !== expected || identity !== expected)) {
|
|
208
214
|
throw new Error("The completed Codex response belongs to a different provider account binding.");
|
|
209
215
|
}
|
|
210
|
-
if (previous && previous !== identity) throw new Error("This conversation belongs to another Codex provider account. Restore that connection or start a new conversation.")
|
|
216
|
+
if (previous && previous !== identity) throw Object.assign(new Error("This conversation belongs to another Codex provider account. Restore that connection or start a new conversation."), {
|
|
217
|
+
messageCode: "assistant.codex.otherProviderAccount", messageParams: {}
|
|
218
|
+
});
|
|
211
219
|
if (!previous) await updateBinding({ connectionIdentities: { ...session.binding.connectionIdentities, [provider.id]: identity } });
|
|
212
220
|
return { model: model.id, providerId: provider.id, accountIdentity: identity, config: codexProviderConfiguration(provider, connection.apiKey) };
|
|
213
221
|
}
|
|
@@ -250,7 +258,9 @@ export function createCodexDriverConnection({
|
|
|
250
258
|
const identity = await accountIdentity(session.native.client, signal);
|
|
251
259
|
if (session.binding.accountIdentity && session.binding.accountIdentity !== identity) {
|
|
252
260
|
providerOwner.closeProvider(providerKey);
|
|
253
|
-
throw new Error("This conversation belongs to another Codex account. Restore that account or start a new conversation.")
|
|
261
|
+
throw Object.assign(new Error("This conversation belongs to another Codex account. Restore that account or start a new conversation."), {
|
|
262
|
+
messageCode: "assistant.codex.otherAccount", messageParams: {}
|
|
263
|
+
});
|
|
254
264
|
}
|
|
255
265
|
if (!session.binding.accountIdentity) await updateBinding({ accountIdentity: identity });
|
|
256
266
|
}
|
|
@@ -315,7 +325,9 @@ export function createCodexDriverConnection({
|
|
|
315
325
|
async write({ threadId }) { await updateBinding({ threadId, toolSchemaIdentity }); } }
|
|
316
326
|
});
|
|
317
327
|
if (!retainedThreadId && prepared.thread.raw?.historyMode !== "paginated") {
|
|
318
|
-
throw new Error("Codex did not create the required paginated history. Update Codex before starting this conversation.")
|
|
328
|
+
throw Object.assign(new Error("Codex did not create the required paginated history. Update Codex before starting this conversation."), {
|
|
329
|
+
messageCode: "assistant.codex.paginatedHistoryMissing", messageParams: {}
|
|
330
|
+
});
|
|
319
331
|
}
|
|
320
332
|
owner.subscribeEvents(sessionId, session.native, prepared.threadId, { providerKey });
|
|
321
333
|
return { ...selected, resumeOptions: params };
|
|
@@ -274,7 +274,9 @@ export function createCodexDriverTurns({
|
|
|
274
274
|
const admission = Promise.withResolvers();
|
|
275
275
|
completion.promise.catch(() => {});
|
|
276
276
|
admission.promise.catch(() => {});
|
|
277
|
-
if (input.goal) throw new Error("Codex goals are controls. Inspect any unresolved goal-message record before continuing.")
|
|
277
|
+
if (input.goal) throw Object.assign(new Error("Codex goals are controls. Inspect any unresolved goal-message record before continuing."), {
|
|
278
|
+
messageCode: "assistant.codex.goalsAreControls", messageParams: {}
|
|
279
|
+
});
|
|
278
280
|
const active = session.current = { configuration, context, inputId: input.messageId,
|
|
279
281
|
onEvent, onNativeTurn, signal, completion, admission, inputReady: admission.promise, finishIfCurrent,
|
|
280
282
|
admitted: false, dispatched: false, turnId: "" };
|
|
@@ -39,7 +39,9 @@ export function createOpenCodeConversationAdapter({ resolveInstructions }) {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
const text = await current.text;
|
|
42
|
-
if (contexts.get(id) !== current) throw new Error("Conversation context changed while reading instructions. Retry before sending.")
|
|
42
|
+
if (contexts.get(id) !== current) throw Object.assign(new Error("Conversation context changed while reading instructions. Retry before sending."), {
|
|
43
|
+
messageCode: "assistant.conversation.contextChangedReadingInstructions", messageParams: {}
|
|
44
|
+
});
|
|
43
45
|
const previous = contributions.get(output.system);
|
|
44
46
|
if (previous !== undefined) {
|
|
45
47
|
const index = output.system.indexOf(previous);
|
|
@@ -272,7 +272,9 @@ export function createOpenCodeConversationDriver({
|
|
|
272
272
|
return representation === "native" ? result : { accepted: result.admission === "accepted" };
|
|
273
273
|
},
|
|
274
274
|
async run(command) {
|
|
275
|
-
if (disposed || current) throw new Error("This OpenCode conversation is closed or already working.")
|
|
275
|
+
if (disposed || current) throw Object.assign(new Error("This OpenCode conversation is closed or already working."), {
|
|
276
|
+
messageCode: "assistant.opencode.closedOrWorking", messageParams: {}
|
|
277
|
+
});
|
|
276
278
|
const active = { context: command.context, failure: Promise.withResolvers(),
|
|
277
279
|
...(command.tools ? { tools: command.tools, messageIds: new Map() } : {}) };
|
|
278
280
|
let failure;
|