@jskit-ai/assistant-core 0.2.6 → 0.2.8
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/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +18 -12
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { codexAppServerTextHasControlCharacters } from "./codexConfiguration.js";
|
|
2
|
+
import { isRecord, normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import { normalizeNullableText as normalizeAgentText } from "./normalize.js";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
/** @typedef {import("./codexProvider.js").CodexAppServerAgentProvider} CodexAppServerAgentProvider */
|
|
7
|
+
|
|
8
|
+
const CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT = 100;
|
|
9
|
+
|
|
10
|
+
const CODEX_APP_SERVER_MODEL_CATALOG_MAX_PAGES = 100;
|
|
11
|
+
|
|
12
|
+
const CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRIES = 1000;
|
|
13
|
+
|
|
14
|
+
const CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRY_BYTES = 32 * 1024;
|
|
15
|
+
|
|
16
|
+
const CODEX_APP_SERVER_MODEL_CATALOG_MAX_TOTAL_BYTES = 2 * 1024 * 1024;
|
|
17
|
+
|
|
18
|
+
const CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH = 512;
|
|
19
|
+
|
|
20
|
+
const CODEX_APP_SERVER_THREAD_INVENTORY_PAGE_LIMIT = 100;
|
|
21
|
+
|
|
22
|
+
const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_PAGES = 100;
|
|
23
|
+
|
|
24
|
+
export const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT = 1000;
|
|
25
|
+
|
|
26
|
+
export const CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH = 512;
|
|
27
|
+
|
|
28
|
+
const CODEX_NATIVE_STORAGE_SOURCE_KINDS = ["cli", "vscode", "exec", "appServer", "subAgent", "subAgentReview",
|
|
29
|
+
"subAgentCompact", "subAgentThreadSpawn", "subAgentOther", "unknown"];
|
|
30
|
+
|
|
31
|
+
function codexAppServerThreadInventoryError(errorCode, label, detail) {
|
|
32
|
+
const error = new Error(`Codex ${label} thread inventory ${detail}.`);
|
|
33
|
+
error.code = errorCode;
|
|
34
|
+
return error;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Why a listed inventory entry is refused, or "" for a valid thread.
|
|
38
|
+
function codexAppServerThreadInventoryRefusal(entry, threadId, { cwd, verifyCwd }) {
|
|
39
|
+
if (!isRecord(entry)) return "listed an entry that is not a thread";
|
|
40
|
+
if (!threadId) return "listed a thread without an id";
|
|
41
|
+
if (threadId.length > CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH) {
|
|
42
|
+
return `listed a thread id longer than ${CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH} characters`;
|
|
43
|
+
}
|
|
44
|
+
if (codexAppServerTextHasControlCharacters(threadId)) return "listed a thread id with control characters";
|
|
45
|
+
if (verifyCwd && normalizeAgentText(entry.cwd) !== cwd) return `listed thread ${threadId} outside the requested directory`;
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function listBoundedCodexAppServerThreadIds({
|
|
50
|
+
ancestorThreadId = "",
|
|
51
|
+
archived = false,
|
|
52
|
+
client,
|
|
53
|
+
cwd = "",
|
|
54
|
+
errorCode = "",
|
|
55
|
+
findThreadId = "",
|
|
56
|
+
label = "",
|
|
57
|
+
requestLabel = "",
|
|
58
|
+
runRequest,
|
|
59
|
+
signal = null,
|
|
60
|
+
sourceKinds = ["appServer"],
|
|
61
|
+
state,
|
|
62
|
+
verifyCwd = false
|
|
63
|
+
} = {}) {
|
|
64
|
+
const seenCursors = new Set();
|
|
65
|
+
let cursor = "";
|
|
66
|
+
for (let page = 0; page < CODEX_APP_SERVER_THREAD_INVENTORY_MAX_PAGES; page += 1) {
|
|
67
|
+
if (cursor && seenCursors.has(cursor)) {
|
|
68
|
+
throw codexAppServerThreadInventoryError(
|
|
69
|
+
errorCode,
|
|
70
|
+
label,
|
|
71
|
+
"repeated a pagination cursor"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (cursor) {
|
|
75
|
+
seenCursors.add(cursor);
|
|
76
|
+
}
|
|
77
|
+
const response = await runRequest(
|
|
78
|
+
() => client.request("thread/list", {
|
|
79
|
+
archived,
|
|
80
|
+
...(cursor ? { cursor } : {}),
|
|
81
|
+
...(ancestorThreadId ? { ancestorThreadId } : cwd ? { cwd } : {}),
|
|
82
|
+
limit: CODEX_APP_SERVER_THREAD_INVENTORY_PAGE_LIMIT,
|
|
83
|
+
modelProviders: [],
|
|
84
|
+
sourceKinds,
|
|
85
|
+
useStateDbOnly: false
|
|
86
|
+
}, { signal }),
|
|
87
|
+
requestLabel
|
|
88
|
+
);
|
|
89
|
+
if (!Array.isArray(response?.data) || response.data.length > CODEX_APP_SERVER_THREAD_INVENTORY_PAGE_LIMIT) {
|
|
90
|
+
throw codexAppServerThreadInventoryError(errorCode, label, "returned a malformed page");
|
|
91
|
+
}
|
|
92
|
+
// Only thread ids are retained, so only they are bounded. A search for one
|
|
93
|
+
// thread retains none and is bounded by the page limit alone.
|
|
94
|
+
if (!findThreadId && state.entryCount + response.data.length > CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT) {
|
|
95
|
+
throw codexAppServerThreadInventoryError(errorCode, label,
|
|
96
|
+
`exceeded its ${CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT}-thread limit`);
|
|
97
|
+
}
|
|
98
|
+
for (const entry of response.data) {
|
|
99
|
+
const threadId = isRecord(entry) ? normalizeAgentText(entry.id) : "";
|
|
100
|
+
const refusal = codexAppServerThreadInventoryRefusal(entry, threadId, { cwd, verifyCwd });
|
|
101
|
+
if (refusal) {
|
|
102
|
+
throw codexAppServerThreadInventoryError(errorCode, label, refusal);
|
|
103
|
+
}
|
|
104
|
+
if (findThreadId) {
|
|
105
|
+
if (threadId === findThreadId) {
|
|
106
|
+
state.found = true;
|
|
107
|
+
return state;
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
state.entryCount += 1;
|
|
112
|
+
state.threadIds.add(threadId);
|
|
113
|
+
}
|
|
114
|
+
const nextCursor = response.nextCursor === undefined || response.nextCursor === null || response.nextCursor === ""
|
|
115
|
+
? ""
|
|
116
|
+
: typeof response.nextCursor === "string"
|
|
117
|
+
? response.nextCursor.trim()
|
|
118
|
+
: null;
|
|
119
|
+
if (
|
|
120
|
+
nextCursor === null ||
|
|
121
|
+
nextCursor.length > CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH ||
|
|
122
|
+
(nextCursor && seenCursors.has(nextCursor))
|
|
123
|
+
) {
|
|
124
|
+
throw codexAppServerThreadInventoryError(
|
|
125
|
+
errorCode,
|
|
126
|
+
label,
|
|
127
|
+
"returned an invalid pagination cursor"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (!nextCursor) {
|
|
131
|
+
return state;
|
|
132
|
+
}
|
|
133
|
+
cursor = nextCursor;
|
|
134
|
+
}
|
|
135
|
+
throw codexAppServerThreadInventoryError(
|
|
136
|
+
errorCode,
|
|
137
|
+
label,
|
|
138
|
+
"exceeded its page limit"
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The provider's inventories: loaded and stored threads, models, configuration and hooks.
|
|
143
|
+
* @param {object} dependencies
|
|
144
|
+
* @param {CodexAppServerAgentProvider} dependencies.provider
|
|
145
|
+
*/
|
|
146
|
+
export function createCodexProviderInventories({ provider }) {
|
|
147
|
+
async function listLoadedThreads(params = {}) {
|
|
148
|
+
const client = await provider.activeClient();
|
|
149
|
+
return provider.runRequest(
|
|
150
|
+
() => client.request("thread/loaded/list", params),
|
|
151
|
+
"codex-app-server-thread-loaded-list"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function listAppServerThreadsForCwd({
|
|
156
|
+
cwd = "",
|
|
157
|
+
signal = null
|
|
158
|
+
} = {}) {
|
|
159
|
+
const normalizedCwd = normalizeAgentText(cwd);
|
|
160
|
+
if (!normalizedCwd || codexAppServerTextHasControlCharacters(normalizedCwd)) {
|
|
161
|
+
const error = new Error("Codex session thread inventory requires an exact cwd.");
|
|
162
|
+
error.code = `${provider.errorPrefix}codex_session_thread_inventory_invalid`;
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
const client = await provider.activeClient();
|
|
166
|
+
const state = await listBoundedCodexAppServerThreadIds({
|
|
167
|
+
archived: false,
|
|
168
|
+
client,
|
|
169
|
+
cwd: normalizedCwd,
|
|
170
|
+
errorCode: `${provider.errorPrefix}codex_session_thread_inventory_invalid`,
|
|
171
|
+
label: "session",
|
|
172
|
+
requestLabel: "codex-app-server-session-thread-list",
|
|
173
|
+
runRequest: provider.runRequest.bind(provider),
|
|
174
|
+
signal,
|
|
175
|
+
state: {
|
|
176
|
+
entryCount: 0,
|
|
177
|
+
threadIds: new Set()
|
|
178
|
+
},
|
|
179
|
+
verifyCwd: true
|
|
180
|
+
});
|
|
181
|
+
return Object.freeze({
|
|
182
|
+
cwd: normalizedCwd,
|
|
183
|
+
threadIds: Object.freeze([...state.threadIds].sort())
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Native thread/delete cascades through all spawned descendants, including
|
|
189
|
+
* archived and non-app-server threads. Never infer its scope from cwd alone.
|
|
190
|
+
* @param {CodexAppServerAgentProvider} provider
|
|
191
|
+
*/
|
|
192
|
+
async function listThreadDescendants(threadId) {
|
|
193
|
+
const ancestorThreadId = normalizeAgentText(threadId);
|
|
194
|
+
if (!ancestorThreadId || ancestorThreadId.length > CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH ||
|
|
195
|
+
codexAppServerTextHasControlCharacters(ancestorThreadId)) {
|
|
196
|
+
throw new TypeError("Codex descendant inventory requires an exact thread id.");
|
|
197
|
+
}
|
|
198
|
+
const client = await provider.activeClient();
|
|
199
|
+
const state = { entryCount: 0, threadIds: new Set() };
|
|
200
|
+
for (const archived of [false, true]) {
|
|
201
|
+
await listBoundedCodexAppServerThreadIds({
|
|
202
|
+
ancestorThreadId, archived, client, state,
|
|
203
|
+
sourceKinds: CODEX_NATIVE_STORAGE_SOURCE_KINDS,
|
|
204
|
+
errorCode: `${provider.errorPrefix}codex_retirement_inventory_invalid`, label: "retirement",
|
|
205
|
+
requestLabel: "codex-app-server-descendant-list", runRequest: provider.runRequest.bind(provider)
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return [...state.threadIds].sort();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function listNativeThreadsForCwd(cwd) {
|
|
212
|
+
if (!path.isAbsolute(cwd || "") || codexAppServerTextHasControlCharacters(cwd)) throw new TypeError("Native inventory requires an exact absolute directory.");
|
|
213
|
+
const client = await provider.activeClient();
|
|
214
|
+
const state = { entryCount: 0, threadIds: new Set() };
|
|
215
|
+
for (const archived of [false, true]) {
|
|
216
|
+
await listBoundedCodexAppServerThreadIds({ archived, client, cwd, state, verifyCwd: true,
|
|
217
|
+
sourceKinds: CODEX_NATIVE_STORAGE_SOURCE_KINDS,
|
|
218
|
+
errorCode: `${provider.errorPrefix}codex_retirement_inventory_invalid`, label: "native storage",
|
|
219
|
+
requestLabel: "codex-app-server-native-storage-list", runRequest: provider.runRequest.bind(provider) });
|
|
220
|
+
}
|
|
221
|
+
return [...state.threadIds].sort();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function nativeThreadExists(threadId, { signal } = {}) {
|
|
225
|
+
const id = normalizeAgentText(threadId);
|
|
226
|
+
if (!id || id.length > CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH || codexAppServerTextHasControlCharacters(id)) {
|
|
227
|
+
throw new TypeError("Native existence checks require an exact thread id.");
|
|
228
|
+
}
|
|
229
|
+
const client = await provider.activeClient();
|
|
230
|
+
const state = { entryCount: 0, found: false, threadIds: new Set() };
|
|
231
|
+
for (const archived of [false, true]) {
|
|
232
|
+
await listBoundedCodexAppServerThreadIds({ archived, client, findThreadId: id, state, signal,
|
|
233
|
+
sourceKinds: CODEX_NATIVE_STORAGE_SOURCE_KINDS,
|
|
234
|
+
errorCode: `${provider.errorPrefix}codex_retirement_inventory_invalid`, label: "native storage",
|
|
235
|
+
requestLabel: "codex-app-server-native-existence", runRequest: provider.runRequest.bind(provider) });
|
|
236
|
+
if (state.found) return true;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function listHelperThreads({
|
|
242
|
+
signal = null
|
|
243
|
+
} = {}) {
|
|
244
|
+
const client = await provider.activeClient();
|
|
245
|
+
const execution = await provider.currentHelperExecutionContext();
|
|
246
|
+
const state = {
|
|
247
|
+
entryCount: 0,
|
|
248
|
+
threadIds: new Set()
|
|
249
|
+
};
|
|
250
|
+
for (const archived of [false, true]) {
|
|
251
|
+
await listBoundedCodexAppServerThreadIds({
|
|
252
|
+
archived,
|
|
253
|
+
client,
|
|
254
|
+
cwd: execution.cwd,
|
|
255
|
+
errorCode: `${provider.errorPrefix}codex_helper_thread_inventory_invalid`,
|
|
256
|
+
label: "helper",
|
|
257
|
+
requestLabel: "codex-app-server-helper-thread-list",
|
|
258
|
+
runRequest: provider.runRequest.bind(provider),
|
|
259
|
+
signal,
|
|
260
|
+
state
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return Object.freeze({
|
|
264
|
+
threadIds: Object.freeze([...state.threadIds].sort())
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function listModels(params = {}, {
|
|
269
|
+
signal = null
|
|
270
|
+
} = {}) {
|
|
271
|
+
const client = await provider.activeClient();
|
|
272
|
+
const data = [];
|
|
273
|
+
const seenCursors = new Set();
|
|
274
|
+
const includeHidden = params.includeHidden === true;
|
|
275
|
+
const limit = normalizePositiveInteger(
|
|
276
|
+
params.limit,
|
|
277
|
+
{ fallback: CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT }
|
|
278
|
+
);
|
|
279
|
+
const boundedLimit = Math.min(limit, CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT);
|
|
280
|
+
let totalBytes = 0;
|
|
281
|
+
let cursor = params.cursor === undefined || params.cursor === null || params.cursor === ""
|
|
282
|
+
? ""
|
|
283
|
+
: typeof params.cursor === "string"
|
|
284
|
+
? params.cursor.trim()
|
|
285
|
+
: null;
|
|
286
|
+
if (cursor === null || cursor.length > CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH) {
|
|
287
|
+
throw provider.modelCatalogError("Codex model catalog cursor is invalid.");
|
|
288
|
+
}
|
|
289
|
+
for (let page = 0; page < CODEX_APP_SERVER_MODEL_CATALOG_MAX_PAGES; page += 1) {
|
|
290
|
+
if (cursor && seenCursors.has(cursor)) {
|
|
291
|
+
throw provider.modelCatalogError("Codex model catalog repeated a pagination cursor.");
|
|
292
|
+
}
|
|
293
|
+
if (cursor) {
|
|
294
|
+
seenCursors.add(cursor);
|
|
295
|
+
}
|
|
296
|
+
const response = await provider.runRequest(
|
|
297
|
+
() => client.request("model/list", {
|
|
298
|
+
...(cursor ? { cursor } : {}),
|
|
299
|
+
includeHidden,
|
|
300
|
+
limit: boundedLimit
|
|
301
|
+
}, { signal }),
|
|
302
|
+
"codex-app-server-model-list"
|
|
303
|
+
);
|
|
304
|
+
if (!Array.isArray(response?.data)) {
|
|
305
|
+
throw provider.modelCatalogError(
|
|
306
|
+
"Codex model catalog response did not contain a data array."
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
if (
|
|
310
|
+
response.data.length > boundedLimit ||
|
|
311
|
+
data.length + response.data.length > CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRIES
|
|
312
|
+
) {
|
|
313
|
+
throw provider.modelCatalogError("Codex model catalog exceeded its entry limit.");
|
|
314
|
+
}
|
|
315
|
+
for (const entry of response.data) {
|
|
316
|
+
if (!isRecord(entry)) {
|
|
317
|
+
throw provider.modelCatalogError("Codex model catalog contained an invalid entry.");
|
|
318
|
+
}
|
|
319
|
+
let entryBytes = 0;
|
|
320
|
+
try {
|
|
321
|
+
entryBytes = Buffer.byteLength(JSON.stringify(entry), "utf8");
|
|
322
|
+
} catch {
|
|
323
|
+
throw provider.modelCatalogError("Codex model catalog contained an invalid entry.");
|
|
324
|
+
}
|
|
325
|
+
if (
|
|
326
|
+
entryBytes > CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRY_BYTES ||
|
|
327
|
+
totalBytes + entryBytes > CODEX_APP_SERVER_MODEL_CATALOG_MAX_TOTAL_BYTES
|
|
328
|
+
) {
|
|
329
|
+
throw provider.modelCatalogError("Codex model catalog exceeded its response limit.");
|
|
330
|
+
}
|
|
331
|
+
totalBytes += entryBytes;
|
|
332
|
+
data.push(entry);
|
|
333
|
+
}
|
|
334
|
+
const nextCursor = response.nextCursor === undefined || response.nextCursor === null || response.nextCursor === ""
|
|
335
|
+
? ""
|
|
336
|
+
: typeof response.nextCursor === "string"
|
|
337
|
+
? response.nextCursor.trim()
|
|
338
|
+
: null;
|
|
339
|
+
if (nextCursor === null || nextCursor.length > CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH) {
|
|
340
|
+
throw provider.modelCatalogError("Codex model catalog cursor is invalid.");
|
|
341
|
+
}
|
|
342
|
+
if (!nextCursor) {
|
|
343
|
+
return {
|
|
344
|
+
data,
|
|
345
|
+
nextCursor: null
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (seenCursors.has(nextCursor)) {
|
|
349
|
+
throw provider.modelCatalogError("Codex model catalog repeated a pagination cursor.");
|
|
350
|
+
}
|
|
351
|
+
cursor = nextCursor;
|
|
352
|
+
}
|
|
353
|
+
throw provider.modelCatalogError(
|
|
354
|
+
`Codex model catalog exceeded ${CODEX_APP_SERVER_MODEL_CATALOG_MAX_PAGES} pages.`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function readConfig(params = {}) {
|
|
359
|
+
const client = await provider.activeClient();
|
|
360
|
+
const cwd = normalizeAgentText(params.cwd);
|
|
361
|
+
return provider.runRequest(
|
|
362
|
+
() => client.request("config/read", {
|
|
363
|
+
...(cwd ? { cwd } : {}),
|
|
364
|
+
includeLayers: params.includeLayers === true
|
|
365
|
+
}),
|
|
366
|
+
"codex-app-server-config-read"
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function trustProject(cwd = "") {
|
|
371
|
+
if (!path.isAbsolute(cwd)) throw new TypeError("Codex project trust requires an absolute worktree path.");
|
|
372
|
+
const projectRoot = path.resolve(cwd);
|
|
373
|
+
const client = await provider.activeClient();
|
|
374
|
+
return provider.runRequest(async () => {
|
|
375
|
+
const { config } = await client.request("config/read", { cwd: projectRoot, includeLayers: false });
|
|
376
|
+
if (config?.projects?.[projectRoot]?.trust_level === "trusted") return;
|
|
377
|
+
// Codex discovers project hooks before applying thread config overrides.
|
|
378
|
+
// Trust only this managed worktree through its native configuration API.
|
|
379
|
+
await client.request("config/batchWrite", {
|
|
380
|
+
edits: [{
|
|
381
|
+
keyPath: `projects.${JSON.stringify(projectRoot)}.trust_level`,
|
|
382
|
+
value: "trusted",
|
|
383
|
+
mergeStrategy: "upsert"
|
|
384
|
+
}],
|
|
385
|
+
expectedVersion: null,
|
|
386
|
+
filePath: null,
|
|
387
|
+
reloadUserConfig: true
|
|
388
|
+
});
|
|
389
|
+
}, "codex-app-server-project-trust");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function listHooks(cwds = []) {
|
|
393
|
+
const client = await provider.activeClient();
|
|
394
|
+
return provider.runRequest(
|
|
395
|
+
() => client.request("hooks/list", {
|
|
396
|
+
cwds: (Array.isArray(cwds) ? cwds : [cwds])
|
|
397
|
+
.map(normalizeAgentText)
|
|
398
|
+
.filter(Boolean)
|
|
399
|
+
}),
|
|
400
|
+
"codex-app-server-hooks-list"
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function writeHookTrustState(state = {}) {
|
|
405
|
+
if (!isRecord(state) || Object.keys(state).length === 0) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const client = await provider.activeClient();
|
|
409
|
+
return provider.runRequest(
|
|
410
|
+
() => client.request("config/batchWrite", {
|
|
411
|
+
edits: [{
|
|
412
|
+
keyPath: "hooks.state",
|
|
413
|
+
mergeStrategy: "upsert",
|
|
414
|
+
value: state
|
|
415
|
+
}],
|
|
416
|
+
expectedVersion: null,
|
|
417
|
+
filePath: null,
|
|
418
|
+
reloadUserConfig: true
|
|
419
|
+
}),
|
|
420
|
+
"codex-app-server-hook-trust-write"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return Object.freeze({
|
|
425
|
+
listLoadedThreads,
|
|
426
|
+
listAppServerThreadsForCwd,
|
|
427
|
+
listThreadDescendants,
|
|
428
|
+
listNativeThreadsForCwd,
|
|
429
|
+
nativeThreadExists,
|
|
430
|
+
listHelperThreads,
|
|
431
|
+
listModels,
|
|
432
|
+
readConfig,
|
|
433
|
+
trustProject,
|
|
434
|
+
listHooks,
|
|
435
|
+
writeHookTrustState
|
|
436
|
+
});
|
|
437
|
+
}
|