@jskit-ai/assistant-core 0.2.19 → 0.2.21
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 +8 -2
- package/examples/conversation/package.json +1 -1
- package/locales/en.json +1 -0
- package/package.json +6 -1
- package/src/server/conversation/claudeProcess.js +13 -6
- package/src/server/conversation/codexConfiguration.js +22 -4
- package/src/server/conversation/codexHistoryAdapter.js +49 -13
- package/src/server/conversation/codexNotificationObserver.js +3 -3
- package/src/server/conversation/codexProcess.js +4 -2
- package/src/server/conversation/codexPromptDispatch.js +3 -3
- package/src/server/conversation/codexResultSettlement.js +2 -5
- package/src/server/conversation/codexRunJournal.js +55 -3
- package/src/server/conversation/codexThreadStatus.js +6 -2
- package/src/server/conversation/codexTurnEndings.js +3 -3
- package/src/server/conversation/codexTurnRecovery.js +16 -11
- package/src/server/conversation/codexTurnState.js +14 -1
- package/src/server/conversation/customEndpoint.js +188 -0
- package/src/server/conversation/providers/claudeDriver.js +5 -4
- package/src/server/conversation/providers/codexDriverConnection.js +3 -3
- package/src/server/hosts/codexConfiguration.js +1 -0
- package/src/server/hosts/customEndpoint.js +7 -0
- package/src/shared/nativeProviders.js +47 -1
- package/test/codexTurn.test.js +76 -0
- package/test/customEndpoint.test.js +218 -0
- package/test/fixtures/customEndpoint/ollama-0.40.15.json +90 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- A delivered Codex turn is held as not yet started only until Codex reports it: a `turn/started` that arrives before the `turn/start` answer counts, and a thread read listing exactly that turn as completed or failed settles a turn whose notifications were missed, so its answer is no longer kept waiting for a notification that never comes.
|
|
5
6
|
- The API driver's state note, which carries the current time, follows the conversation as its last message instead of ending the system prompt, so the provider's prompt cache keeps the whole history from one turn to the next instead of charging it uncached on each turn's first model call (`@jskit-ai/agent` `runLoop`).
|
|
6
7
|
- Assistant failures that reach a person translate: 63 more undotted codes render from `jskit.errors.<code>`, and 98 sentences that share a code with others, or are sent without one, carry a `messageCode` (`assistant.claude.*`, `assistant.codex.*`, `assistant.opencode.*`, `assistant.conversation.*`, `assistant.turn.*`). The English messages are unchanged.
|
|
7
8
|
- The OpenCode version mismatch, Claude control-request timeout and unreadable saved Claude conversation errors carry `params` for their catalog English.
|
|
8
9
|
- The assistant settings resource sends its system prompt field messages in the coded `{ code, message, params }` form.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
- Custom model endpoints: `customAiProvider({ engineId, baseUrl, model, contextWindow })` (with `CUSTOM_AI_PROVIDER_ID`, `CUSTOM_AI_MINIMUM_CONTEXT`, `CUSTOM_AI_PLACEHOLDER_TOKEN` and `connectionAiProvider`) in `shared/native-providers` describes a model server the host's user runs, such as Ollama, LM Studio, `llama-server` or vLLM, in the native provider shape, so Codex, Claude Code and the Colleague drivers connect to it. Codex gets no bearer token without a key, no freeform `apply_patch` and no parallel tool calls; Claude Code always gets a token (the key, or `local`) so a saved claude.ai login never reaches the endpoint, its model in every slot and an output limit of a quarter of the context. The Codex history adapter routes `custom~<base64url(baseUrl)>` to plain `http(s)` upstreams only and ends a turn whose server is down with `assistant_custom_endpoint_unreachable`. `readCodexCustomModels(codexHome)` reads a custom Codex home's catalogue.
|
|
12
|
+
- `server/custom-endpoint` adds `checkCustomEndpoint` (one bounded request on the engine's own API, up to 2 minutes, then Ollama's loaded context from `/api/ps`, refusing one smaller than entered), `verifyCustomEndpoint`, `readOllamaLoadedContext` and `probeCustomEndpoint` (one model-list read for health). Errors carry `custom_endpoint_*` codes and say what to fix.
|
|
13
|
+
|
|
14
|
+
### New message keys (164)
|
|
11
15
|
|
|
12
16
|
<details><summary>Key list</summary>
|
|
13
17
|
|
|
@@ -174,10 +178,11 @@
|
|
|
174
178
|
- `jskit.errors.assistant_tool_output_invalid`
|
|
175
179
|
- `jskit.errors.assistant_tool_unknown`
|
|
176
180
|
- `jskit.errors.assistant_claude_control_timeout`
|
|
181
|
+
- `jskit.errors.assistant_custom_endpoint_unreachable`
|
|
177
182
|
|
|
178
183
|
</details>
|
|
179
184
|
|
|
180
|
-
### Codes (
|
|
185
|
+
### Codes (164)
|
|
181
186
|
|
|
182
187
|
Error, field-error and message codes this package now sends, with their English. Undotted codes kept their existing values and render from `jskit.errors.<code>`.
|
|
183
188
|
|
|
@@ -346,6 +351,7 @@ Error, field-error and message codes this package now sends, with their English.
|
|
|
346
351
|
| `assistant_tool_output_invalid` | Assistant tool output validation failed. |
|
|
347
352
|
| `assistant_tool_unknown` | Unknown tool. |
|
|
348
353
|
| `assistant_claude_control_timeout` | Claude {request} timed out. |
|
|
354
|
+
| `assistant_custom_endpoint_unreachable` | Your model server at {upstream} isn't answering. Start it, then send your message again. |
|
|
349
355
|
|
|
350
356
|
## 0.2.0 - 2026-10-29
|
|
351
357
|
|
package/locales/en.json
CHANGED
|
@@ -530,6 +530,7 @@
|
|
|
530
530
|
"assistant_codex_provider_switch_busy": "Stop the current turn before immediately changing model providers.",
|
|
531
531
|
"assistant_codex_history_request_too_large": "Codex history exceeds the adapter request limit.",
|
|
532
532
|
"assistant_codex_history_request_unreadable": "Codex sent an unreadable or oversized history request.",
|
|
533
|
+
"assistant_custom_endpoint_unreachable": "Your model server at {upstream} isn't answering. Start it, then send your message again.",
|
|
533
534
|
"assistant_codex_app_server_message_too_large": "Codex app-server message exceeded the configured transport limit.",
|
|
534
535
|
"codex_reconnect_required": "Sign in with codex login before continuing.",
|
|
535
536
|
"codex_provider_connection_changed": "This Codex provider connection changed. Work is stopped; use Send or Resume after reconnecting.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"./server/opencode-turn": "./src/server/hosts/openCodeTurn.js",
|
|
31
31
|
"./server/opencode-process": "./src/server/hosts/openCodeProcess.js",
|
|
32
32
|
"./shared/native-providers": "./src/shared/nativeProviders.js",
|
|
33
|
+
"./server/custom-endpoint": "./src/server/hosts/customEndpoint.js",
|
|
33
34
|
"./server/codex-provider": "./src/server/hosts/codexProvider.js",
|
|
34
35
|
"./server/native-history": "./src/server/hosts/nativeHistory.js",
|
|
35
36
|
"./testing/native-codex": "./src/testing/nativeCodex.js",
|
|
@@ -178,6 +179,10 @@
|
|
|
178
179
|
"subpath": "./shared/native-providers",
|
|
179
180
|
"summary": "Advanced native host: Exact native provider routes, engine configuration facts and qualified model capabilities; applications choose which models to offer."
|
|
180
181
|
},
|
|
182
|
+
{
|
|
183
|
+
"subpath": "./server/custom-endpoint",
|
|
184
|
+
"summary": "Advanced native host: Checks of a model server the person runs: one connect-time request on the engine's own API, the context Ollama really loaded, and a model-list read for health."
|
|
185
|
+
},
|
|
181
186
|
{
|
|
182
187
|
"subpath": "./server/codex-provider",
|
|
183
188
|
"summary": "Advanced native host: Original Codex provider acquisition, thread selection, control, recovery, native history retirement and private renewal identity operations."
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsonHash } from "./normalize.js";
|
|
2
|
-
import { nativeAiProvider } from "../../shared/nativeProviders.js";
|
|
2
|
+
import { CUSTOM_AI_PLACEHOLDER_TOKEN, CUSTOM_AI_PROVIDER_ID, connectionAiProvider, nativeAiProvider } from "../../shared/nativeProviders.js";
|
|
3
3
|
import { Duplex } from "node:stream";
|
|
4
4
|
import { stat } from "node:fs/promises";
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -132,18 +132,24 @@ export function claudeConnectionIdentity(configRoot, providerId, baseUrl, apiKey
|
|
|
132
132
|
|
|
133
133
|
/** Native provider routing; the application supplies only its authorized selection. */
|
|
134
134
|
export function claudeModelConfiguration({ providerId, model: modelId }, connection) {
|
|
135
|
-
const provider =
|
|
135
|
+
const provider = connectionAiProvider(providerId, connection);
|
|
136
136
|
const definition = provider?.models.find(({ id }) => id === modelId);
|
|
137
|
-
if (provider
|
|
137
|
+
if ((provider || providerId === CUSTOM_AI_PROVIDER_ID) &&
|
|
138
|
+
(!definition || !(connection?.apiKey || provider.custom))) throw Object.assign(new Error("Choose a connected Claude Code model."), {
|
|
138
139
|
messageCode: "assistant.claude.chooseConnectedModel", messageParams: {}
|
|
139
140
|
});
|
|
140
|
-
const model = definition?.contextWindow >= 1000000 ? `${definition.id}[1m]` : modelId;
|
|
141
|
+
const model = !provider?.custom && definition?.contextWindow >= 1000000 ? `${definition.id}[1m]` : modelId;
|
|
142
|
+
// A custom endpoint's own window: Claude Code assumes 200K and 32K output for
|
|
143
|
+
// a model it does not know, past what most model servers load.
|
|
144
|
+
const outputTokens = provider?.custom ? Math.min(32000, Math.floor(definition.contextWindow / 4)) : 0;
|
|
141
145
|
const externalModel = provider ? model : "";
|
|
142
146
|
// Reset every provider override when returning to the native subscription.
|
|
143
147
|
// Background calls and subagents must stay on the selected provider too.
|
|
144
148
|
const env = {
|
|
145
149
|
ANTHROPIC_BASE_URL: connection?.baseUrl || "https://api.anthropic.com",
|
|
146
|
-
|
|
150
|
+
// Never empty for a custom endpoint, so the saved claude.ai login is not sent there.
|
|
151
|
+
ANTHROPIC_AUTH_TOKEN: connection?.apiKey || (provider?.custom ? CUSTOM_AI_PLACEHOLDER_TOKEN : ""),
|
|
152
|
+
ANTHROPIC_API_KEY: "", CLAUDE_CODE_OAUTH_TOKEN: "",
|
|
147
153
|
ANTHROPIC_MODEL: externalModel, ANTHROPIC_DEFAULT_MODEL: externalModel,
|
|
148
154
|
ANTHROPIC_DEFAULT_OPUS_MODEL: externalModel,
|
|
149
155
|
ANTHROPIC_DEFAULT_SONNET_MODEL: externalModel,
|
|
@@ -151,6 +157,7 @@ export function claudeModelConfiguration({ providerId, model: modelId }, connect
|
|
|
151
157
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: definition?.id || "",
|
|
152
158
|
CLAUDE_CODE_SUBAGENT_MODEL: definition?.id || "",
|
|
153
159
|
CLAUDE_CODE_AUTO_COMPACT_WINDOW: provider ? String(provider.claudeAutoCompactWindow) : "",
|
|
160
|
+
CLAUDE_CODE_MAX_OUTPUT_TOKENS: outputTokens ? String(outputTokens) : "",
|
|
154
161
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: provider ? "1" : "",
|
|
155
162
|
CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST: "1"
|
|
156
163
|
};
|
|
@@ -244,7 +251,7 @@ export async function stopClaudeCodeProcess({ process: native, executionId, stop
|
|
|
244
251
|
|
|
245
252
|
export async function bindClaudeConversationAccount(entry, { identity, providerId, allowAccountChange = false, stop, save }) {
|
|
246
253
|
entry.accountIdentities ||= {};
|
|
247
|
-
if (!nativeAiProvider(providerId) && entry.accountIdentities[providerId] && entry.accountIdentities[providerId] !== identity) {
|
|
254
|
+
if (!nativeAiProvider(providerId) && providerId !== CUSTOM_AI_PROVIDER_ID && entry.accountIdentities[providerId] && entry.accountIdentities[providerId] !== identity) {
|
|
248
255
|
await stop(entry, "The signed-in Claude account changed.");
|
|
249
256
|
if (!allowAccountChange) {
|
|
250
257
|
throw Object.assign(new Error("This conversation belongs to another Claude account. Reconnect that account or start a new session."), {
|
|
@@ -320,7 +320,7 @@ export function codexProviderModelCatalog({ models }) {
|
|
|
320
320
|
context_window: model.contextWindow,
|
|
321
321
|
max_context_window: model.contextWindow,
|
|
322
322
|
effective_context_window_percent: 95,
|
|
323
|
-
supports_parallel_tool_calls:
|
|
323
|
+
supports_parallel_tool_calls: model.parallelToolCalls !== false,
|
|
324
324
|
experimental_supported_tools: [],
|
|
325
325
|
input_modalities: model.images ? ["text", "image"] : ["text"],
|
|
326
326
|
prefer_websockets: false,
|
|
@@ -336,7 +336,9 @@ export function codexProviderConfiguration(provider, apiKey) {
|
|
|
336
336
|
web_search: provider.webSearch ? "live" : "disabled",
|
|
337
337
|
[`model_providers.${provider.id}`]: {
|
|
338
338
|
name: provider.label || provider.id, base_url: provider.baseUrl, wire_api: "responses",
|
|
339
|
-
requires_openai_auth: false,
|
|
339
|
+
requires_openai_auth: false,
|
|
340
|
+
// A custom endpoint may need no key; Codex then sends no Authorization header.
|
|
341
|
+
...(apiKey ? { experimental_bearer_token: apiKey } : {})
|
|
340
342
|
}
|
|
341
343
|
};
|
|
342
344
|
}
|
|
@@ -356,7 +358,7 @@ export function codexProviderFileConfiguration(provider, key, codexHome) {
|
|
|
356
358
|
`model = ${JSON.stringify(provider.models[0].id)}`,
|
|
357
359
|
`model_provider = ${JSON.stringify(provider.id)}`,
|
|
358
360
|
codexProviderCatalogSetting(codexHome),
|
|
359
|
-
`model_reasoning_effort = ${JSON.stringify(provider.models[0].defaultThinking)}
|
|
361
|
+
...(provider.models[0].defaultThinking ? [`model_reasoning_effort = ${JSON.stringify(provider.models[0].defaultThinking)}`] : []),
|
|
360
362
|
'model_reasoning_summary = "none"',
|
|
361
363
|
`web_search = "${provider.webSearch ? "live" : "disabled"}"`,
|
|
362
364
|
`[model_providers.${provider.id}]`,
|
|
@@ -364,11 +366,27 @@ export function codexProviderFileConfiguration(provider, key, codexHome) {
|
|
|
364
366
|
`base_url = ${JSON.stringify(provider.baseUrl)}`,
|
|
365
367
|
'wire_api = "responses"',
|
|
366
368
|
'requires_openai_auth = false',
|
|
367
|
-
`experimental_bearer_token = ${JSON.stringify(key)}
|
|
369
|
+
...(key ? [`experimental_bearer_token = ${JSON.stringify(key)}`] : []),
|
|
368
370
|
""
|
|
369
371
|
].join("\n");
|
|
370
372
|
}
|
|
371
373
|
|
|
374
|
+
/**
|
|
375
|
+
* The catalogue rows of a home codexProviderFileConfiguration wrote for a
|
|
376
|
+
* custom endpoint, whose model only its saved connection names; none for any
|
|
377
|
+
* other home.
|
|
378
|
+
*/
|
|
379
|
+
export async function readCodexCustomModels(codexHome) {
|
|
380
|
+
const config = await readFile(path.join(codexHome, "config.toml"), "utf8").catch((error) => {
|
|
381
|
+
if (error.code === "ENOENT") return "";
|
|
382
|
+
throw error;
|
|
383
|
+
});
|
|
384
|
+
if (!/^model_provider = "custom"$/mu.test(config)) return [];
|
|
385
|
+
const catalogue = JSON.parse(await readFile(codexProviderCatalogPath(codexHome), "utf8"));
|
|
386
|
+
if (!Array.isArray(catalogue?.models)) throw new Error("Codex custom endpoint home is incomplete.");
|
|
387
|
+
return catalogue.models;
|
|
388
|
+
}
|
|
389
|
+
|
|
372
390
|
/**
|
|
373
391
|
* Copy a curated provider home's config and catalogue into another home, with
|
|
374
392
|
* the copied config naming the copied catalogue. A config that does not name
|
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import { Readable } from "node:stream";
|
|
7
7
|
import { isDeepStrictEqual } from "node:util";
|
|
8
8
|
import { gunzipSync, zstdDecompressSync } from "node:zlib";
|
|
9
|
-
import { nativeAiModel, nativeAiProvider } from "../../shared/nativeProviders.js";
|
|
9
|
+
import { CUSTOM_AI_PROVIDER_ID, nativeAiModel, nativeAiProvider } from "../../shared/nativeProviders.js";
|
|
10
10
|
|
|
11
11
|
const MAX_REQUEST_BYTES = 32 * 1024 * 1024;
|
|
12
12
|
const UPSTREAMS = Object.freeze({
|
|
@@ -19,6 +19,28 @@ const UPSTREAMS = Object.freeze({
|
|
|
19
19
|
const HOP_HEADERS = ["connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
|
|
20
20
|
"te", "trailer", "transfer-encoding", "upgrade"];
|
|
21
21
|
|
|
22
|
+
// A custom endpoint's route segment carries its own base URL, so any runtime can
|
|
23
|
+
// reach the endpoint a thread names; its single model is qualified for history
|
|
24
|
+
// recovery like the curated routes.
|
|
25
|
+
function historyRouted(destination) {
|
|
26
|
+
return Boolean(nativeAiProvider(destination) || destination === CUSTOM_AI_PROVIDER_ID);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function customRouteSegment(baseUrl) {
|
|
30
|
+
return `${CUSTOM_AI_PROVIDER_ID}~${Buffer.from(baseUrl.replace(/\/$/u, "")).toString("base64url")}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function customUpstream(encoded = "") {
|
|
34
|
+
let url;
|
|
35
|
+
try {
|
|
36
|
+
url = new URL(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password && !url.search && !url.hash
|
|
41
|
+
? url.href.replace(/\/$/u, "") : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
function unsupportedHistory(message) {
|
|
23
45
|
return new AppError(422, message, { code: "assistant_codex_history_unsupported" });
|
|
24
46
|
}
|
|
@@ -170,9 +192,10 @@ async function* readCodexHistoryRows(file, start, end, signal, { strictComplete
|
|
|
170
192
|
// cannot read OpenAI's encrypted replacement. Recover that exact boundary as
|
|
171
193
|
// historical data, only when it is actually present in a foreign request.
|
|
172
194
|
async function restoreCompactedHistory(body, { destination, historyPath, codexHome, signal, maxRequestBytes }) {
|
|
173
|
-
if (!
|
|
195
|
+
if (!historyRouted(destination) || !Array.isArray(body?.input) ||
|
|
174
196
|
!body.input.some((item) => item?.type === "compaction")) return body;
|
|
175
|
-
const model =
|
|
197
|
+
const model = destination === CUSTOM_AI_PROVIDER_ID ? { modelProviderId: destination, codexHistoryRouting: true }
|
|
198
|
+
: nativeAiModel(body.model, destination);
|
|
176
199
|
if (model?.modelProviderId !== destination || !model.codexHistoryRouting) {
|
|
177
200
|
throw compactionHistoryError("this model has not been qualified for history recovery.");
|
|
178
201
|
}
|
|
@@ -342,8 +365,8 @@ async function startCodexHistoryAdapter({ token, codexHome, readHistoryPath = re
|
|
|
342
365
|
try {
|
|
343
366
|
const url = new URL(request.url, "http://127.0.0.1");
|
|
344
367
|
const parts = url.pathname.split("/");
|
|
345
|
-
const destination = parts[3];
|
|
346
|
-
const upstream = UPSTREAMS[destination];
|
|
368
|
+
const [destination, encodedUpstream] = (parts[3] || "").split("~", 2);
|
|
369
|
+
const upstream = destination === CUSTOM_AI_PROVIDER_ID ? customUpstream(encodedUpstream) : UPSTREAMS[destination];
|
|
347
370
|
const hasHistory = parts[4] === "history";
|
|
348
371
|
let historyPath = hasHistory ? Buffer.from(parts[5] || "", "base64url").toString("utf8") : "";
|
|
349
372
|
const route = parts.slice(hasHistory ? 6 : 4).join("/");
|
|
@@ -370,7 +393,7 @@ async function startCodexHistoryAdapter({ token, codexHome, readHistoryPath = re
|
|
|
370
393
|
} catch {
|
|
371
394
|
throw new AppError(400, "Codex sent an unreadable or oversized history request.", { code: "assistant_codex_history_request_unreadable" });
|
|
372
395
|
}
|
|
373
|
-
if (!historyPath &&
|
|
396
|
+
if (!historyPath && historyRouted(destination) && Array.isArray(body?.input) && body.input.some(item => item?.type === "compaction")) {
|
|
374
397
|
// An exclusively owned conversation can resolve its current native
|
|
375
398
|
// file, including compaction during its very first turn.
|
|
376
399
|
historyPath = await readHistoryPath(abort.signal);
|
|
@@ -381,9 +404,17 @@ async function startCodexHistoryAdapter({ token, codexHome, readHistoryPath = re
|
|
|
381
404
|
}
|
|
382
405
|
const headers = forwardedHeaders(request.headers);
|
|
383
406
|
headers.set("accept-encoding", "identity");
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
407
|
+
let result;
|
|
408
|
+
try {
|
|
409
|
+
result = await fetchImpl(`${upstream}/${route}${url.search}`, {
|
|
410
|
+
method: request.method, headers, body, signal: abort.signal, redirect: "error"
|
|
411
|
+
});
|
|
412
|
+
} catch (error) {
|
|
413
|
+
// The owner's own server is down more often than a provider: say which and what to do.
|
|
414
|
+
if (destination !== CUSTOM_AI_PROVIDER_ID || abort.signal.aborted) throw error;
|
|
415
|
+
throw Object.assign(new AppError(502, `Your model server at ${upstream} isn't answering. Start it, then send your message again.`,
|
|
416
|
+
{ code: "assistant_custom_endpoint_unreachable" }), { params: { upstream } });
|
|
417
|
+
}
|
|
387
418
|
// DeepSeek labels this explicit context rejection as a generic invalid
|
|
388
419
|
// request. Preserve the provider's details while exposing the native
|
|
389
420
|
// recovery code; the adapter never retries the rejected inference.
|
|
@@ -446,13 +477,18 @@ async function prepareCodexHistory(params, client, { baseUrl, modelProviderId, .
|
|
|
446
477
|
if (!baseUrl) return params;
|
|
447
478
|
const modelProvider = params.modelProvider || modelProviderId || "openai";
|
|
448
479
|
const curatedProvider = nativeAiProvider(modelProvider);
|
|
449
|
-
if (curatedProvider) {
|
|
480
|
+
if (curatedProvider || modelProvider === CUSTOM_AI_PROVIDER_ID) {
|
|
450
481
|
const configKey = `model_providers.${modelProvider}`;
|
|
451
482
|
const config = params.config?.[configKey];
|
|
452
483
|
// Only the curated route owns these credentials. Do not redirect an
|
|
453
|
-
// unrelated
|
|
454
|
-
|
|
455
|
-
if (config?.base_url !==
|
|
484
|
+
// unrelated provider to our fixed upstream. The custom endpoint's route
|
|
485
|
+
// names the base URL its thread configuration gave.
|
|
486
|
+
if (!curatedProvider && typeof config?.base_url !== "string") return params;
|
|
487
|
+
const customPrefix = `${baseUrl}/${CUSTOM_AI_PROVIDER_ID}~`;
|
|
488
|
+
const providerUrl = curatedProvider ? `${baseUrl}/${modelProvider}`
|
|
489
|
+
: config.base_url.startsWith(customPrefix) ? `${baseUrl}/${config.base_url.slice(baseUrl.length + 1).split("/")[0]}`
|
|
490
|
+
: `${baseUrl}/${customRouteSegment(config.base_url)}`;
|
|
491
|
+
if (curatedProvider && config?.base_url !== curatedProvider.baseUrl &&
|
|
456
492
|
config?.base_url !== providerUrl && !config?.base_url?.startsWith(`${providerUrl}/history/`)) return params;
|
|
457
493
|
let historyPath = options.historyPath;
|
|
458
494
|
if (!historyPath && options.threadId) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure,
|
|
1
|
+
import { codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, CODEX_APP_SERVER_AGENT_RUN_ID, CODEX_AGENT_PROVIDER, CODEX_APP_SERVER_RUN_STATE } from "./codexTurnState.js";
|
|
2
2
|
import { codexAppServerProviderConnectionGeneration } from "./codexModelCatalog.js";
|
|
3
3
|
import {
|
|
4
4
|
codexAppServerAssistantItemText,
|
|
@@ -239,8 +239,8 @@ export function createCodexNotificationObserver({
|
|
|
239
239
|
threadId: normalizedThreadId,
|
|
240
240
|
turnId: codexAppServerNotificationTurnId(notification)
|
|
241
241
|
};
|
|
242
|
-
// Anything Codex reports about a turn means it has started that turn.
|
|
243
|
-
journal.
|
|
242
|
+
// Anything Codex reports about a turn means it has started that turn, even before its answer to turn/start.
|
|
243
|
+
journal.noteTurnReported(normalizedThreadId, notificationContext.turnId);
|
|
244
244
|
if (method === "thread/tokenUsage/updated") {
|
|
245
245
|
notificationQueue.run(notificationContext, async () => {
|
|
246
246
|
const store = await createStore(normalizedSessionId);
|
|
@@ -7,7 +7,7 @@ import { setTimeout as delay } from "node:timers/promises";
|
|
|
7
7
|
import { createConversationProcessIdentity } from "./processIdentity.js";
|
|
8
8
|
import { isRecord, normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
|
|
9
9
|
import { normalizeNullableText } from "./normalize.js";
|
|
10
|
-
import { prepareCodexModelCatalog } from "./codexConfiguration.js";
|
|
10
|
+
import { prepareCodexModelCatalog, readCodexCustomModels } from "./codexConfiguration.js";
|
|
11
11
|
import { startCodexHistoryAdapter } from "./codexHistoryAdapter.js";
|
|
12
12
|
|
|
13
13
|
const CODEX_APP_SERVER_METADATA_FILE = "runtime.json";
|
|
@@ -28,11 +28,13 @@ export async function runCodexAppServerProcess({
|
|
|
28
28
|
process.once("SIGTERM", abortStartup);
|
|
29
29
|
process.once("SIGINT", abortStartup);
|
|
30
30
|
const codexHome = env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
31
|
+
// A custom endpoint's home brings its own model's catalogue row.
|
|
32
|
+
const customModels = await readCodexCustomModels(codexHome);
|
|
31
33
|
const catalogPath = await prepareCodexModelCatalog({
|
|
32
34
|
command,
|
|
33
35
|
runtimeDir,
|
|
34
36
|
bundled,
|
|
35
|
-
additionalModels,
|
|
37
|
+
additionalModels: [...additionalModels, ...customModels],
|
|
36
38
|
codexHome,
|
|
37
39
|
signal: startup.signal
|
|
38
40
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerErrorMessage, codexAppServerSteerFailure, codexAppServerMessageRequiresNewTurn, codexAppServerMessageDeferred
|
|
1
|
+
import { codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerErrorMessage, codexAppServerSteerFailure, codexAppServerMessageRequiresNewTurn, codexAppServerMessageDeferred } from "./codexTurnState.js";
|
|
2
2
|
import { codexAppServerRequestIsInvalid } from "./codexProviderThreads.js";
|
|
3
3
|
import { codexLocalImageInput, codexAppServerDeliveredTurn, sendCodexAppServerPrompt } from "./codexProviderTurns.js";
|
|
4
4
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
@@ -74,9 +74,9 @@ export function createCodexPromptDispatch({
|
|
|
74
74
|
if (!deliveredTurnId) {
|
|
75
75
|
throw new Error("Codex app-server accepted the prompt without returning a turn id.");
|
|
76
76
|
}
|
|
77
|
-
// Codex answers turn/start before it starts the turn; its notifications about the turn say when it has.
|
|
77
|
+
// Codex usually answers turn/start before it starts the turn; its notifications about the turn say when it has.
|
|
78
78
|
if (!codexAppServerTurnStatusIsComplete(deliveredTurnStatus)) {
|
|
79
|
-
journal.
|
|
79
|
+
journal.markTurnUnstarted(threadId, deliveredTurnId);
|
|
80
80
|
}
|
|
81
81
|
delivery.pendingUserMessage(promptSessionId, clientUserMessageId)?.onNativeReceipt();
|
|
82
82
|
await recovery.markTurnActive(sessionId, {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CODEX_APP_SERVER_AGENT_RUN_ID, CODEX_APP_SERVER_RESULT_PROCESSED_EVENT, codexAppServerTurnCanReceiveProviderCompletion, codexAppServerProcessedResultEvent, codexAppServerTurnAwaitsProviderIdentity, codexAppServerTurnCanAdoptSuccessor, codexAppServerAgentRun, CODEX_APP_SERVER_RUN_STATE
|
|
1
|
+
import { CODEX_APP_SERVER_AGENT_RUN_ID, CODEX_APP_SERVER_RESULT_PROCESSED_EVENT, codexAppServerTurnCanReceiveProviderCompletion, codexAppServerProcessedResultEvent, codexAppServerTurnAwaitsProviderIdentity, codexAppServerTurnCanAdoptSuccessor, codexAppServerAgentRun, CODEX_APP_SERVER_RUN_STATE } from "./codexTurnState.js";
|
|
2
2
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -325,10 +325,7 @@ export function createCodexResultSettlement({ journal, output, createRuntime, pu
|
|
|
325
325
|
normalizedThreadId,
|
|
326
326
|
previousTurnId
|
|
327
327
|
);
|
|
328
|
-
journal.
|
|
329
|
-
normalizedThreadId,
|
|
330
|
-
previousTurnId
|
|
331
|
-
));
|
|
328
|
+
journal.markTurnCompleted(normalizedThreadId, previousTurnId);
|
|
332
329
|
const previousResultKey = output.resultFinalizationKey(
|
|
333
330
|
normalizedSessionId,
|
|
334
331
|
normalizedThreadId,
|
|
@@ -3,6 +3,7 @@ import crypto from "node:crypto";
|
|
|
3
3
|
import { isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
4
|
|
|
5
5
|
const CODEX_APP_SERVER_PROMPT_CLAIM_GRACE_MS = 15000;
|
|
6
|
+
const CODEX_APP_SERVER_REPORTED_TURN_LIMIT = 256;
|
|
6
7
|
const CODEX_APP_SERVER_STOPPED_BY_SHUTDOWN_EVENT = "codex-app-server-stopped-by-shutdown";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -46,6 +47,55 @@ function codexAppServerDateValueMs(value = "") {
|
|
|
46
47
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Which turns Codex accepted with turn/start but has not reported yet: until it reports one, its thread still reads
|
|
52
|
+
* idle, so an idle read cannot settle it. Codex's turn/started can also arrive before its answer to turn/start, so the
|
|
53
|
+
* turns it reported most recently are remembered and never marked as not started. Only a turn whose answer is still on
|
|
54
|
+
* its way needs that, so the newest few are enough.
|
|
55
|
+
* @param {Set<string>} completedTurns the journal's settled turn keys, which markTurnCompleted adds to
|
|
56
|
+
*/
|
|
57
|
+
function createCodexAppServerTurnStarts(completedTurns) {
|
|
58
|
+
const unstartedTurns = new Set();
|
|
59
|
+
// Oldest first.
|
|
60
|
+
const reportedTurns = new Set();
|
|
61
|
+
|
|
62
|
+
/** Codex reported this turn, in a notification or by listing it as finished, so it has started it. */
|
|
63
|
+
function noteTurnReported(threadId = "", turnId = "") {
|
|
64
|
+
if (!normalizeText(turnId)) return;
|
|
65
|
+
const key = codexAppServerTurnKey(threadId, turnId);
|
|
66
|
+
unstartedTurns.delete(key);
|
|
67
|
+
reportedTurns.delete(key);
|
|
68
|
+
reportedTurns.add(key);
|
|
69
|
+
if (reportedTurns.size > CODEX_APP_SERVER_REPORTED_TURN_LIMIT) {
|
|
70
|
+
reportedTurns.delete(reportedTurns.values().next().value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A turn Codex accepted waits for its report before an idle read can settle it, unless Codex already reported it. */
|
|
75
|
+
function markTurnUnstarted(threadId = "", turnId = "") {
|
|
76
|
+
const key = codexAppServerTurnKey(threadId, turnId);
|
|
77
|
+
if (!reportedTurns.has(key)) unstartedTurns.add(key);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function turnIsUnstarted(threadId = "", turnId = "") {
|
|
81
|
+
return unstartedTurns.has(codexAppServerTurnKey(threadId, turnId));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The run owner settled this turn: nothing waits for Codex to start it any more. */
|
|
85
|
+
function markTurnCompleted(threadId = "", turnId = "") {
|
|
86
|
+
const key = codexAppServerTurnKey(threadId, turnId);
|
|
87
|
+
completedTurns.add(key);
|
|
88
|
+
unstartedTurns.delete(key);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
noteTurnReported,
|
|
93
|
+
markTurnUnstarted,
|
|
94
|
+
turnIsUnstarted,
|
|
95
|
+
markTurnCompleted
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
49
99
|
/**
|
|
50
100
|
* Native run claims, guarded journal writes and status retirement. The containing run owner shares these exact Sets
|
|
51
101
|
* and Maps by reference.
|
|
@@ -88,8 +138,7 @@ export function createCodexRunJournal({
|
|
|
88
138
|
isClosing
|
|
89
139
|
}) {
|
|
90
140
|
const codexAppServerCompletedTurns = new Set();
|
|
91
|
-
|
|
92
|
-
const codexAppServerUnstartedTurns = new Set();
|
|
141
|
+
const turnStarts = createCodexAppServerTurnStarts(codexAppServerCompletedTurns);
|
|
93
142
|
const codexAppServerProcessedTurns = new Set();
|
|
94
143
|
const codexAppServerActiveTimers = new Map();
|
|
95
144
|
const codexAppServerFinalizingTimers = new Map();
|
|
@@ -639,7 +688,10 @@ export function createCodexRunJournal({
|
|
|
639
688
|
|
|
640
689
|
return Object.freeze({
|
|
641
690
|
completedTurns: codexAppServerCompletedTurns,
|
|
642
|
-
|
|
691
|
+
noteTurnReported: turnStarts.noteTurnReported,
|
|
692
|
+
markTurnUnstarted: turnStarts.markTurnUnstarted,
|
|
693
|
+
turnIsUnstarted: turnStarts.turnIsUnstarted,
|
|
694
|
+
markTurnCompleted: turnStarts.markTurnCompleted,
|
|
643
695
|
processedTurns: codexAppServerProcessedTurns,
|
|
644
696
|
activeTimers: codexAppServerActiveTimers,
|
|
645
697
|
finalizingTimers: codexAppServerFinalizingTimers,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerThreadStatus, codexAppServerReadThreadStatus, codexAppServerAgentRun, codexAppServerTurnCanReceiveProviderCompletion, codexAppServerTurnCanAdoptSuccessor, codexAppServerErrorMessage
|
|
1
|
+
import { codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerThreadStatus, codexAppServerObservedTurnFinished, codexAppServerReadThreadStatus, codexAppServerAgentRun, codexAppServerTurnCanReceiveProviderCompletion, codexAppServerTurnCanAdoptSuccessor, codexAppServerErrorMessage } from "./codexTurnState.js";
|
|
2
2
|
import { isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
3
|
import { codexAppServerStatusFromValue } from "./codexEvents.js";
|
|
4
4
|
import { codexAppServerThreadRawValue, codexAppServerThreadTurnId, codexAppServerThreadError } from "./codexThreadSnapshot.js";
|
|
@@ -200,12 +200,16 @@ export function createCodexThreadStatus({ journal, settlement, recovery, output,
|
|
|
200
200
|
turnId
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
|
+
// Codex lists the tracked turn as finished: it ran, even if its notifications about it were missed.
|
|
204
|
+
if (trackedProviderTurn && codexAppServerObservedTurnFinished(thread, trackedProviderTurn.turnId)) {
|
|
205
|
+
journal.noteTurnReported(trackedProviderTurn.threadId, trackedProviderTurn.turnId);
|
|
206
|
+
}
|
|
203
207
|
// Codex has accepted the tracked turn but not started it. Until it does, its thread reads idle and lists the
|
|
204
208
|
// turn as interrupted, so only Codex's own report about the turn settles it.
|
|
205
209
|
if (
|
|
206
210
|
trackedProviderTurn?.state === "active" &&
|
|
207
211
|
!codexAppServerTurnStatusIsActive(status) &&
|
|
208
|
-
journal.
|
|
212
|
+
journal.turnIsUnstarted(trackedProviderTurn.threadId, trackedProviderTurn.turnId)
|
|
209
213
|
) {
|
|
210
214
|
debugLog("appServerThread.reconcile.awaitingProviderStart", {
|
|
211
215
|
sessionId: normalizedSessionId,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { codexAppServerTurnStatusIsActive, codexAppServerThreadStatus, codexAppServerReadThreadStatus, codexAppServerAgentRun, codexAppServerTurnOwnsActiveGoal, codexAppServerTurnCanReceiveProviderCompletion, codexAppServerTurnAwaitsProviderIdentity, codexAppServerTurnCanReceiveProviderActivity
|
|
1
|
+
import { codexAppServerTurnStatusIsActive, codexAppServerThreadStatus, codexAppServerReadThreadStatus, codexAppServerAgentRun, codexAppServerTurnOwnsActiveGoal, codexAppServerTurnCanReceiveProviderCompletion, codexAppServerTurnAwaitsProviderIdentity, codexAppServerTurnCanReceiveProviderActivity } from "./codexTurnState.js";
|
|
2
2
|
import { codexAppServerThreadTurnId } from "./codexThreadSnapshot.js";
|
|
3
3
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
4
|
|
|
@@ -201,7 +201,7 @@ export function createCodexTurnEndings({
|
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
if (journal.runInputSource(codexAppServerAgentRun(session)) === "terminal") {
|
|
204
|
-
journal.
|
|
204
|
+
journal.markTurnCompleted(normalizedThreadId, normalizedTurnId);
|
|
205
205
|
output.cleanupUntrackedTurn(normalizedThreadId, normalizedTurnId);
|
|
206
206
|
await journal.markTurnIdle(normalizedSessionId, {
|
|
207
207
|
status: normalizedStatus,
|
|
@@ -217,7 +217,7 @@ export function createCodexTurnEndings({
|
|
|
217
217
|
const alreadyFinalizing = existingTurn.state === "finalizing" &&
|
|
218
218
|
existingTurn.threadId === normalizedThreadId &&
|
|
219
219
|
existingTurn.turnId === normalizedTurnId;
|
|
220
|
-
journal.
|
|
220
|
+
journal.markTurnCompleted(normalizedThreadId, normalizedTurnId);
|
|
221
221
|
if (!alreadyFinalizing) {
|
|
222
222
|
await journal.markTurnFinalizing(normalizedSessionId, {
|
|
223
223
|
status: normalizedStatus,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerThreadStatus, codexAppServerReadThreadStatus, codexAppServerAgentRun, codexAppServerPendingUserMessageOwnership, codexAppServerTurnCanAdoptSuccessor
|
|
1
|
+
import { codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerThreadStatus, codexAppServerObservedTurnFinished, codexAppServerReadThreadStatus, codexAppServerAgentRun, codexAppServerPendingUserMessageOwnership, codexAppServerTurnCanAdoptSuccessor } from "./codexTurnState.js";
|
|
2
2
|
import { codexAppServerProviderTurnForOperation, codexAppServerThreadTurnId, codexAppServerThreadError } from "./codexThreadSnapshot.js";
|
|
3
3
|
import { createCodexTurnActivity } from "./codexTurnActivity.js";
|
|
4
4
|
import { createCodexTurnEndings } from "./codexTurnEndings.js";
|
|
@@ -270,16 +270,21 @@ export function createCodexTurnRecovery({
|
|
|
270
270
|
if (!codexAppServerTurnStatusIsComplete(status)) {
|
|
271
271
|
return currentSession;
|
|
272
272
|
}
|
|
273
|
-
if (journal.
|
|
274
|
-
// Codex
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
273
|
+
if (journal.turnIsUnstarted(currentTurn.threadId, currentTurn.turnId)) {
|
|
274
|
+
// Codex may have accepted this turn without starting it: its thread reads idle until it does. Only Codex's own
|
|
275
|
+
// listing of the turn as finished settles it before a notification about the turn arrives.
|
|
276
|
+
const observed = await codexAppServerReadThreadStatus(provider, currentTurn.threadId, { observeLatestTurn: true });
|
|
277
|
+
if (!codexAppServerObservedTurnFinished(observed, currentTurn.turnId)) {
|
|
278
|
+
debugLog("appServerTurn.reconcile.awaitingProviderStart", {
|
|
279
|
+
sessionId,
|
|
280
|
+
status,
|
|
281
|
+
threadId: currentTurn.threadId,
|
|
282
|
+
turnId: currentTurn.turnId
|
|
283
|
+
});
|
|
284
|
+
scheduleCodexAppServerActiveRecovery(sessionId);
|
|
285
|
+
return currentSession;
|
|
286
|
+
}
|
|
287
|
+
journal.noteTurnReported(currentTurn.threadId, currentTurn.turnId);
|
|
283
288
|
}
|
|
284
289
|
const completedTurnId = providerTurnId || currentTurn.turnId;
|
|
285
290
|
debugLog("appServerTurn.reconcile.complete", {
|
|
@@ -38,6 +38,19 @@ function codexAppServerThreadStatus(thread = {}) {
|
|
|
38
38
|
return liveStatus;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Whether a thread read lists exactly this turn as finished, completed or failed. Codex lists a turn it has accepted
|
|
43
|
+
* but not yet started as interrupted, so only these two say that it ran.
|
|
44
|
+
* @param {Record<string, unknown> | null} thread a read with its latest turn observed
|
|
45
|
+
* @param {string} turnId
|
|
46
|
+
*/
|
|
47
|
+
function codexAppServerObservedTurnFinished(thread = null, turnId = "") {
|
|
48
|
+
const observed = isRecord(thread?.observedTurn) ? thread.observedTurn : null;
|
|
49
|
+
const normalizedTurnId = normalizeNullableText(turnId);
|
|
50
|
+
if (!observed || !normalizedTurnId || normalizeNullableText(observed.id) !== normalizedTurnId) return false;
|
|
51
|
+
return ["completed", "failed"].includes(normalizeNullableText(observed.status));
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
/**
|
|
42
55
|
* Reads a native thread's status, and with observeLatestTurn its latest turn too.
|
|
43
56
|
* @param {import("./codexProvider.js").CodexAppServerAgentProvider | null} provider
|
|
@@ -604,4 +617,4 @@ function codexPromptInputFromRequest(input = {}) {
|
|
|
604
617
|
return codexAppServerMessageText(input);
|
|
605
618
|
}
|
|
606
619
|
|
|
607
|
-
export { codexAppServerRunInputSource, codexAppServerThreadStatus, codexAppServerReadThreadStatus, codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerErrorMessage, codexAppServerMessageDeferred, codexPromptInputFromRequest, CODEX_APP_SERVER_AGENT_RUN_ID, CODEX_APP_SERVER_RESULT_PROCESSED_EVENT, CODEX_AGENT_PROVIDER, CODEX_APP_SERVER_ACTIVE_RUN_STATES };
|
|
620
|
+
export { codexAppServerRunInputSource, codexAppServerThreadStatus, codexAppServerObservedTurnFinished, codexAppServerReadThreadStatus, codexAppServerTurnStatusIsActive, codexAppServerTurnStatusIsComplete, codexAppServerTurnStatusIsSuccessfulComplete, codexAppServerTurnStatusIsProviderFailure, codexAppServerErrorMessage, codexAppServerMessageDeferred, codexPromptInputFromRequest, CODEX_APP_SERVER_AGENT_RUN_ID, CODEX_APP_SERVER_RESULT_PROCESSED_EVENT, CODEX_AGENT_PROVIDER, CODEX_APP_SERVER_ACTIVE_RUN_STATES };
|