@nextclaw/nextclaw-ncp-runtime-codex-sdk 0.1.53 → 0.1.55-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -8
- package/dist/index.js.map +1 -1
- package/dist/services/codex-app-server-client.service.js +242 -0
- package/dist/services/codex-app-server-client.service.js.map +1 -0
- package/dist/services/codex-app-server-ncp-agent-runtime.service.d.ts +30 -0
- package/dist/services/codex-app-server-ncp-agent-runtime.service.d.ts.map +1 -0
- package/dist/services/codex-app-server-ncp-agent-runtime.service.js +389 -0
- package/dist/services/codex-app-server-ncp-agent-runtime.service.js.map +1 -0
- package/dist/types/codex-app-server-runtime.types.d.ts +24 -0
- package/dist/types/codex-app-server-runtime.types.d.ts.map +1 -0
- package/dist/utils/codex-app-server-item-mapper.utils.js +67 -0
- package/dist/utils/codex-app-server-item-mapper.utils.js.map +1 -0
- package/dist/utils/codex-app-server-request.utils.js +49 -0
- package/dist/utils/codex-app-server-request.utils.js.map +1 -0
- package/dist/{codex-openai-responses-bridge-assistant-output.utils.js → utils/codex-openai-responses-bridge-assistant-output.utils.js} +2 -2
- package/dist/utils/codex-openai-responses-bridge-assistant-output.utils.js.map +1 -0
- package/dist/utils/codex-openai-responses-bridge-request.utils.js.map +1 -1
- package/dist/utils/codex-openai-responses-bridge-stream.utils.js +1 -1
- package/dist/utils/codex-openai-responses-bridge-stream.utils.js.map +1 -1
- package/dist/utils/codex-openai-responses-bridge.utils.js.map +1 -1
- package/dist/utils/codex-openai-responses-stream-writer.utils.d.ts.map +1 -1
- package/dist/utils/codex-openai-responses-stream-writer.utils.js +2 -9
- package/dist/utils/codex-openai-responses-stream-writer.utils.js.map +1 -1
- package/dist/utils/codex-openai-sse-chunks.utils.js.map +1 -1
- package/package.json +5 -4
- package/dist/codex-openai-responses-bridge-assistant-output.utils.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { CodexAssetContentPathResolver, CodexThreadInput, buildCodexInputBuilder
|
|
|
3
3
|
import { buildCodexBridgeModelProviderId } from "./codex-model-provider.js";
|
|
4
4
|
import { CodexOpenAiResponsesBridgeResult } from "./codex-openai-responses-bridge-shared.utils.js";
|
|
5
5
|
import { CodexOpenAiResponsesBridgeRuntimeConfig, ensureCodexOpenAiResponsesBridge } from "./utils/codex-openai-responses-bridge.utils.js";
|
|
6
|
+
import { CodexAppServerNcpAgentRuntimeConfig } from "./types/codex-app-server-runtime.types.js";
|
|
7
|
+
import { CodexAppServerNcpAgentRuntime } from "./services/codex-app-server-ncp-agent-runtime.service.js";
|
|
6
8
|
import { NcpAgentConversationStateManager, NcpAgentRunInput, NcpAgentRunOptions, NcpAgentRuntime, NcpEndpointEvent } from "@nextclaw/ncp";
|
|
7
9
|
import { CodexOptions, ThreadOptions } from "@openai/codex-sdk";
|
|
8
10
|
|
|
@@ -18,7 +20,7 @@ type CodexSdkNcpAgentRuntimeConfig = {
|
|
|
18
20
|
cliConfig?: CodexOptions["config"];
|
|
19
21
|
threadOptions?: ThreadOptions;
|
|
20
22
|
sessionMetadata?: Record<string, unknown>;
|
|
21
|
-
setSessionMetadata?: (nextMetadata: Record<string, unknown>) => void
|
|
23
|
+
setSessionMetadata?: (nextMetadata: Record<string, unknown>) => void | Promise<void>;
|
|
22
24
|
inputBuilder?: (input: NcpAgentRunInput) => Promise<CodexThreadInput> | CodexThreadInput;
|
|
23
25
|
liveOutputStream?: CodexLiveOutputStream;
|
|
24
26
|
resolveAssetContentPath?: CodexAssetContentPathResolver;
|
|
@@ -36,11 +38,12 @@ declare class CodexSdkNcpAgentRuntime implements NcpAgentRuntime {
|
|
|
36
38
|
run: (this: CodexSdkNcpAgentRuntime, input: NcpAgentRunInput, options?: NcpAgentRunOptions) => AsyncGenerator<NcpEndpointEvent>;
|
|
37
39
|
private getCodex;
|
|
38
40
|
private resolveThread;
|
|
41
|
+
private runStreamedWithCurrentThread;
|
|
39
42
|
private buildTurnInput;
|
|
40
43
|
private streamTurnEvents;
|
|
41
44
|
private handleThreadEvent;
|
|
42
45
|
private updateThreadId;
|
|
43
46
|
}
|
|
44
47
|
//#endregion
|
|
45
|
-
export { type CodexAssetContentPathResolver, type CodexLiveOutputChannel, type CodexLiveOutputEvent, CodexLiveOutputStream, type CodexOpenAiResponsesBridgeResult, type CodexOpenAiResponsesBridgeRuntimeConfig, CodexSdkNcpAgentRuntime, CodexSdkNcpAgentRuntimeConfig, buildCodexBridgeModelProviderId, buildCodexInputBuilder, ensureCodexOpenAiResponsesBridge };
|
|
48
|
+
export { CodexAppServerNcpAgentRuntime, type CodexAppServerNcpAgentRuntimeConfig, type CodexAssetContentPathResolver, type CodexLiveOutputChannel, type CodexLiveOutputEvent, CodexLiveOutputStream, type CodexOpenAiResponsesBridgeResult, type CodexOpenAiResponsesBridgeRuntimeConfig, CodexSdkNcpAgentRuntime, CodexSdkNcpAgentRuntimeConfig, buildCodexBridgeModelProviderId, buildCodexInputBuilder, ensureCodexOpenAiResponsesBridge };
|
|
46
49
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;KAmDY,6BAAA;EACV,SAAA;EACA,MAAA;EACA,OAAA;EACA,KAAA;EACA,QAAA;EACA,iBAAA;EACA,GAAA,GAAM,MAAA;EACN,SAAA,GAAY,YAAA;EACZ,aAAA,GAAgB,aAAA;EAChB,eAAA,GAAkB,MAAA;EAClB,kBAAA,IAAsB,YAAA,EAAc,MAAA,6BAAmC,OAAA;EACvE,YAAA,IAAgB,KAAA,EAAO,gBAAA,KAAqB,OAAA,CAAQ,gBAAA,IAAoB,gBAAA;EACxE,gBAAA,GAAmB,qBAAA;EACnB,uBAAA,GAA0B,6BAAA;EAC1B,YAAA,GAAe,gCAAA;AAAA;AAAA,cAkCJ,uBAAA,YAAmC,eAAA;EAAA,iBAQjB,MAAA;EAAA,QAPrB,YAAA;EAAA,iBACS,YAAA;EAAA,iBACA,2BAAA;EAAA,QACT,MAAA;EAAA,QACA,QAAA;EAAA,iBACS,eAAA;cAEY,MAAA,EAAQ,6BAAA;EAQrC,GAAA,GACE,IAAA,EAAM,uBAAA,EACN,KAAA,EAAO,gBAAA,EACP,OAAA,GAAU,kBAAA,KACT,cAAA,CAAe,gBAAA;EAAA,QA+BV,QAAA;EAAA,QAgBA,aAAA;EAAA,QAcA,4BAAA;EAAA,QAWA,cAAA;EAAA,QASA,gBAAA;EAAA,QAyDA,iBAAA;EAAA,QAkFA,cAAA;AAAA"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { buildCodexInputBuilder, buildCodexTurnInputFromRunInput } from "./codex
|
|
|
6
6
|
import { buildCodexBridgeModelProviderId } from "./codex-model-provider.js";
|
|
7
7
|
import { ensureCodexOpenAiResponsesBridge } from "./utils/codex-openai-responses-bridge.utils.js";
|
|
8
8
|
import { CodexLiveOutputStream } from "./services/codex-live-output-stream.service.js";
|
|
9
|
+
import { CodexAppServerNcpAgentRuntime } from "./services/codex-app-server-ncp-agent-runtime.service.js";
|
|
9
10
|
import { createRequire } from "node:module";
|
|
10
11
|
import "@nextclaw/ncp";
|
|
11
12
|
//#region src/index.ts
|
|
@@ -51,10 +52,8 @@ var CodexSdkNcpAgentRuntime = class {
|
|
|
51
52
|
const toolStateById = /* @__PURE__ */ new Map();
|
|
52
53
|
yield* this.eventEmitter.emitRunStarted(input.sessionId, messageId, runId);
|
|
53
54
|
yield* this.eventEmitter.emitReadyMetadata(input.sessionId, messageId, runId);
|
|
54
|
-
const thread = await this.resolveThread();
|
|
55
55
|
const turnInput = await this.buildTurnInput(input);
|
|
56
|
-
this.
|
|
57
|
-
const streamed = await thread.runStreamed(turnInput, { ...signal ? { signal } : {} });
|
|
56
|
+
const streamed = await this.runStreamedWithCurrentThread(turnInput, signal);
|
|
58
57
|
try {
|
|
59
58
|
yield* this.streamTurnEvents({
|
|
60
59
|
sessionId: input.sessionId,
|
|
@@ -90,6 +89,11 @@ var CodexSdkNcpAgentRuntime = class {
|
|
|
90
89
|
this.thread = this.threadId ? codex.resumeThread(this.threadId, threadOptions) : codex.startThread(threadOptions);
|
|
91
90
|
return this.thread;
|
|
92
91
|
};
|
|
92
|
+
runStreamedWithCurrentThread = async (turnInput, signal) => {
|
|
93
|
+
const thread = await this.resolveThread();
|
|
94
|
+
this.config.liveOutputStream?.reset();
|
|
95
|
+
return await thread.runStreamed(turnInput, { ...signal ? { signal } : {} });
|
|
96
|
+
};
|
|
93
97
|
buildTurnInput = async (input) => {
|
|
94
98
|
if (this.config.inputBuilder) return await this.config.inputBuilder(input);
|
|
95
99
|
return await buildCodexTurnInputFromRunInput(input, { resolveAssetContentPath: this.config.resolveAssetContentPath });
|
|
@@ -126,7 +130,7 @@ var CodexSdkNcpAgentRuntime = class {
|
|
|
126
130
|
const { event, itemTextById, messageId, runId, sessionId, signal, suppressLiveChannels, toolStateById } = params;
|
|
127
131
|
if (signal?.aborted) throw toAbortError(signal.reason);
|
|
128
132
|
if (event.type === "thread.started") {
|
|
129
|
-
this.updateThreadId(event.thread_id);
|
|
133
|
+
await this.updateThreadId(event.thread_id);
|
|
130
134
|
return false;
|
|
131
135
|
}
|
|
132
136
|
if (event.type === "turn.failed") {
|
|
@@ -155,21 +159,26 @@ var CodexSdkNcpAgentRuntime = class {
|
|
|
155
159
|
}
|
|
156
160
|
return false;
|
|
157
161
|
};
|
|
158
|
-
updateThreadId = (nextThreadId) => {
|
|
162
|
+
updateThreadId = async (nextThreadId) => {
|
|
159
163
|
const normalizedThreadId = nextThreadId.trim();
|
|
160
164
|
if (!normalizedThreadId || normalizedThreadId === this.threadId) return;
|
|
161
165
|
this.threadId = normalizedThreadId;
|
|
162
166
|
const nextMetadata = {
|
|
163
167
|
...this.sessionMetadata,
|
|
164
168
|
session_type: "codex",
|
|
165
|
-
codex_thread_id: normalizedThreadId
|
|
169
|
+
codex_thread_id: normalizedThreadId,
|
|
170
|
+
codex_thread_model: buildThreadModelScope(this.config)
|
|
166
171
|
};
|
|
167
172
|
this.sessionMetadata.codex_thread_id = normalizedThreadId;
|
|
173
|
+
this.sessionMetadata.codex_thread_model = nextMetadata.codex_thread_model;
|
|
168
174
|
this.sessionMetadata.session_type = "codex";
|
|
169
|
-
this.config.setSessionMetadata?.(nextMetadata);
|
|
175
|
+
await this.config.setSessionMetadata?.(nextMetadata);
|
|
170
176
|
};
|
|
171
177
|
};
|
|
178
|
+
function buildThreadModelScope(config) {
|
|
179
|
+
return config.threadOptions?.model ?? config.model ?? "__nextclaw_runtime_default__";
|
|
180
|
+
}
|
|
172
181
|
//#endregion
|
|
173
|
-
export { CodexLiveOutputStream, CodexSdkNcpAgentRuntime, buildCodexBridgeModelProviderId, buildCodexInputBuilder, ensureCodexOpenAiResponsesBridge };
|
|
182
|
+
export { CodexAppServerNcpAgentRuntime, CodexLiveOutputStream, CodexSdkNcpAgentRuntime, buildCodexBridgeModelProviderId, buildCodexInputBuilder, ensureCodexOpenAiResponsesBridge };
|
|
174
183
|
|
|
175
184
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport type { Codex as CodexClient, CodexOptions, Thread, ThreadOptions } from \"@openai/codex-sdk\";\nimport {\n type NcpAgentConversationStateManager,\n type NcpAgentRunInput,\n type NcpAgentRunOptions,\n type NcpAgentRuntime,\n type NcpEndpointEvent,\n} from \"@nextclaw/ncp\";\nimport type {\n CodexLiveOutputChannel,\n CodexLiveOutputStream,\n} from \"./services/codex-live-output-stream.service.js\";\nimport { CodexLiveOutputEventMergeService } from \"./services/codex-live-output-event-merge.service.js\";\nimport { CodexNcpRunEventEmitter } from \"./services/codex-ncp-run-event-emitter.service.js\";\nimport {\n type ItemTextSnapshot,\n mapCodexItemEvent,\n type ToolSnapshot,\n} from \"./utils/codex-sdk-ncp-event-mapper.utils.js\";\nimport { buildCodexCliEnv } from \"./codex-cli-env.js\";\nimport {\n buildCodexTurnInputFromRunInput,\n type CodexAssetContentPathResolver,\n type CodexThreadInput,\n} from \"./codex-input.utils.js\";\nexport { buildCodexInputBuilder } from \"./codex-input.utils.js\";\nexport type { CodexAssetContentPathResolver } from \"./codex-input.utils.js\";\nexport { buildCodexBridgeModelProviderId } from \"./codex-model-provider.js\";\nexport { ensureCodexOpenAiResponsesBridge } from \"./utils/codex-openai-responses-bridge.utils.js\";\nexport type { CodexOpenAiResponsesBridgeRuntimeConfig } from \"./utils/codex-openai-responses-bridge.utils.js\";\nexport type { CodexOpenAiResponsesBridgeResult } from \"./codex-openai-responses-bridge-shared.utils.js\";\nexport { CodexLiveOutputStream } from \"./services/codex-live-output-stream.service.js\";\nexport type {\n CodexLiveOutputChannel,\n CodexLiveOutputEvent,\n} from \"./services/codex-live-output-stream.service.js\";\n\ntype CodexCtor = new (options: CodexOptions) => CodexClient;\n\ntype CodexLoader = {\n loadCodexConstructor: () => Promise<CodexCtor>;\n};\n\nconst require = createRequire(import.meta.url);\nconst codexLoader = require(\"../codex-sdk-loader.cjs\") as CodexLoader;\n\nexport type CodexSdkNcpAgentRuntimeConfig = {\n sessionId: string;\n apiKey: string;\n apiBase?: string;\n model?: string;\n threadId?: string | null;\n codexPathOverride?: string;\n env?: Record<string, string>;\n cliConfig?: CodexOptions[\"config\"];\n threadOptions?: ThreadOptions;\n sessionMetadata?: Record<string, unknown>;\n setSessionMetadata?: (nextMetadata: Record<string, unknown>) => void;\n inputBuilder?: (input: NcpAgentRunInput) => Promise<CodexThreadInput> | CodexThreadInput;\n liveOutputStream?: CodexLiveOutputStream;\n resolveAssetContentPath?: CodexAssetContentPathResolver;\n stateManager?: NcpAgentConversationStateManager;\n};\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction toAbortError(reason: unknown): Error {\n if (reason instanceof Error) {\n return reason;\n }\n const message = typeof reason === \"string\" && reason.trim() ? reason.trim() : \"operation aborted\";\n const error = new Error(message);\n error.name = \"AbortError\";\n return error;\n}\n\nfunction normalizeThreadOptions(\n options: ThreadOptions | undefined,\n model: string | undefined,\n): ThreadOptions {\n return {\n ...options,\n skipGitRepoCheck: options?.skipGitRepoCheck ?? true,\n ...(model ? { model } : {}),\n };\n}\n\nfunction isItemLifecycleEvent(\n event: Parameters<typeof mapCodexItemEvent>[0][\"event\"] | { type: string },\n): event is Parameters<typeof mapCodexItemEvent>[0][\"event\"] {\n return event.type === \"item.started\" || event.type === \"item.updated\" || event.type === \"item.completed\";\n}\n\nexport class CodexSdkNcpAgentRuntime implements NcpAgentRuntime {\n private codexPromise: Promise<CodexClient> | null = null;\n private readonly eventEmitter: CodexNcpRunEventEmitter;\n private readonly liveOutputEventMergeService = new CodexLiveOutputEventMergeService();\n private thread: Thread | null = null;\n private threadId: string | null;\n private readonly sessionMetadata: Record<string, unknown>;\n\n constructor(private readonly config: CodexSdkNcpAgentRuntimeConfig) {\n this.eventEmitter = new CodexNcpRunEventEmitter(config.stateManager);\n this.threadId = config.threadId?.trim() || null;\n this.sessionMetadata = {\n ...(config.sessionMetadata ? structuredClone(config.sessionMetadata) : {}),\n };\n }\n\n run = async function* (\n this: CodexSdkNcpAgentRuntime,\n input: NcpAgentRunInput,\n options?: NcpAgentRunOptions,\n ): AsyncGenerator<NcpEndpointEvent> {\n const signal = options?.signal;\n const messageId = createId(\"codex-message\");\n const runId = (input as NcpAgentRunInput & { runId?: string }).runId ?? createId(\"codex-run\");\n const itemTextById = new Map<string, ItemTextSnapshot>();\n const toolStateById = new Map<string, ToolSnapshot>();\n\n yield* this.eventEmitter.emitRunStarted(input.sessionId, messageId, runId);\n yield* this.eventEmitter.emitReadyMetadata(input.sessionId, messageId, runId);\n\n const thread = await this.resolveThread();\n const turnInput = await this.buildTurnInput(input);\n this.config.liveOutputStream?.reset();\n const streamed = await thread.runStreamed(turnInput, {\n ...(signal ? { signal } : {}),\n });\n\n try {\n yield* this.streamTurnEvents({\n sessionId: input.sessionId,\n messageId,\n runId,\n streamed,\n signal,\n itemTextById,\n toolStateById,\n });\n } catch (error) {\n if (signal?.aborted) {\n throw toAbortError(signal.reason);\n }\n throw error;\n }\n };\n\n private getCodex = async (): Promise<CodexClient> => {\n if (!this.codexPromise) {\n const env = buildCodexCliEnv(this.config);\n this.codexPromise = codexLoader.loadCodexConstructor().then((Ctor) =>\n new Ctor({\n apiKey: this.config.apiKey,\n baseUrl: this.config.apiBase,\n ...(this.config.codexPathOverride ? { codexPathOverride: this.config.codexPathOverride } : {}),\n ...(env ? { env } : {}),\n ...(this.config.cliConfig ? { config: this.config.cliConfig } : {}),\n }),\n );\n }\n return this.codexPromise;\n };\n\n private resolveThread = async (): Promise<Thread> => {\n if (this.thread) {\n return this.thread;\n }\n\n const codex = await this.getCodex();\n const threadOptions = normalizeThreadOptions(this.config.threadOptions, this.config.model);\n\n this.thread = this.threadId\n ? codex.resumeThread(this.threadId, threadOptions)\n : codex.startThread(threadOptions);\n return this.thread;\n };\n\n private buildTurnInput = async (input: NcpAgentRunInput): Promise<CodexThreadInput> => {\n if (this.config.inputBuilder) {\n return await this.config.inputBuilder(input);\n }\n return await buildCodexTurnInputFromRunInput(input, {\n resolveAssetContentPath: this.config.resolveAssetContentPath,\n });\n };\n\n private streamTurnEvents = async function* (\n this: CodexSdkNcpAgentRuntime,\n params: {\n sessionId: string;\n messageId: string;\n runId: string;\n streamed: Awaited<ReturnType<Thread[\"runStreamed\"]>>;\n signal?: AbortSignal;\n itemTextById: Map<string, ItemTextSnapshot>;\n toolStateById: Map<string, ToolSnapshot>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { itemTextById, messageId, runId, sessionId, signal, streamed, toolStateById } =\n params;\n const liveOutputStream = this.config.liveOutputStream;\n if (liveOutputStream) {\n yield* this.liveOutputEventMergeService.stream({\n ...params,\n liveOutputStream,\n emitEvent: (event) => this.eventEmitter.emitEvent(event),\n emitRunCompleted: (sessionId, messageId, runId) =>\n this.eventEmitter.emitRunCompleted(sessionId, messageId, runId),\n handleThreadEvent: (handlerParams) => this.handleThreadEvent(handlerParams),\n });\n return;\n }\n\n let finished = false;\n for await (const event of streamed.events) {\n const shouldFinish = yield* this.handleThreadEvent({\n sessionId,\n messageId,\n runId,\n event,\n signal,\n itemTextById,\n toolStateById,\n });\n if (shouldFinish) {\n finished = true;\n return;\n }\n }\n\n if (!finished) {\n yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);\n }\n };\n\n private handleThreadEvent = async function* (\n this: CodexSdkNcpAgentRuntime,\n params: {\n sessionId: string;\n messageId: string;\n runId: string;\n event: Awaited<ReturnType<Thread[\"runStreamed\"]>>[\"events\"] extends AsyncGenerator<infer T> ? T : never;\n signal?: AbortSignal;\n itemTextById: Map<string, ItemTextSnapshot>;\n suppressLiveChannels?: Set<CodexLiveOutputChannel>;\n toolStateById: Map<string, ToolSnapshot>;\n },\n ): AsyncGenerator<NcpEndpointEvent, boolean> {\n const {\n event,\n itemTextById,\n messageId,\n runId,\n sessionId,\n signal,\n suppressLiveChannels,\n toolStateById,\n } = params;\n if (signal?.aborted) {\n throw toAbortError(signal.reason);\n }\n\n if (event.type === \"thread.started\") {\n this.updateThreadId(event.thread_id);\n return false;\n }\n\n if (event.type === \"turn.failed\") {\n yield* this.eventEmitter.emitRunError(\n sessionId,\n messageId,\n runId,\n event.error.message,\n );\n return true;\n }\n\n if (event.type === \"error\") {\n yield* this.eventEmitter.emitRunError(\n sessionId,\n messageId,\n runId,\n event.message,\n );\n return true;\n }\n\n if (isItemLifecycleEvent(event)) {\n const suppressedChannel =\n event.item.type === \"agent_message\"\n ? \"text\"\n : event.item.type === \"reasoning\"\n ? \"reasoning\"\n : null;\n if (suppressedChannel && suppressLiveChannels?.has(suppressedChannel)) {\n return false;\n }\n for await (const mappedEvent of mapCodexItemEvent({\n sessionId,\n messageId,\n event,\n itemTextById,\n toolStateById,\n })) {\n yield* this.eventEmitter.emitEvent(mappedEvent);\n }\n return false;\n }\n\n if (event.type === \"turn.completed\") {\n yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);\n return true;\n }\n\n return false;\n };\n\n private updateThreadId = (nextThreadId: string): void => {\n const normalizedThreadId = nextThreadId.trim();\n if (!normalizedThreadId || normalizedThreadId === this.threadId) {\n return;\n }\n this.threadId = normalizedThreadId;\n const nextMetadata = {\n ...this.sessionMetadata,\n session_type: \"codex\",\n codex_thread_id: normalizedThreadId,\n };\n this.sessionMetadata.codex_thread_id = normalizedThreadId;\n this.sessionMetadata.session_type = \"codex\";\n this.config.setSessionMetadata?.(nextMetadata);\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport type { Codex as CodexClient, CodexOptions, Thread, ThreadOptions } from \"@openai/codex-sdk\";\nimport {\n type NcpAgentConversationStateManager,\n type NcpAgentRunInput,\n type NcpAgentRunOptions,\n type NcpAgentRuntime,\n type NcpEndpointEvent,\n} from \"@nextclaw/ncp\";\nimport type {\n CodexLiveOutputChannel,\n CodexLiveOutputStream,\n} from \"./services/codex-live-output-stream.service.js\";\nimport { CodexLiveOutputEventMergeService } from \"./services/codex-live-output-event-merge.service.js\";\nimport { CodexNcpRunEventEmitter } from \"./services/codex-ncp-run-event-emitter.service.js\";\nimport {\n type ItemTextSnapshot,\n mapCodexItemEvent,\n type ToolSnapshot,\n} from \"./utils/codex-sdk-ncp-event-mapper.utils.js\";\nimport { buildCodexCliEnv } from \"./codex-cli-env.js\";\nimport {\n buildCodexTurnInputFromRunInput,\n type CodexAssetContentPathResolver,\n type CodexThreadInput,\n} from \"./codex-input.utils.js\";\nexport { buildCodexInputBuilder } from \"./codex-input.utils.js\";\nexport type { CodexAssetContentPathResolver } from \"./codex-input.utils.js\";\nexport { buildCodexBridgeModelProviderId } from \"./codex-model-provider.js\";\nexport { ensureCodexOpenAiResponsesBridge } from \"./utils/codex-openai-responses-bridge.utils.js\";\nexport type { CodexOpenAiResponsesBridgeRuntimeConfig } from \"./utils/codex-openai-responses-bridge.utils.js\";\nexport type { CodexOpenAiResponsesBridgeResult } from \"./codex-openai-responses-bridge-shared.utils.js\";\nexport { CodexLiveOutputStream } from \"./services/codex-live-output-stream.service.js\";\nexport {\n CodexAppServerNcpAgentRuntime,\n} from \"./services/codex-app-server-ncp-agent-runtime.service.js\";\nexport type { CodexAppServerNcpAgentRuntimeConfig } from \"./types/codex-app-server-runtime.types.js\";\nexport type {\n CodexLiveOutputChannel,\n CodexLiveOutputEvent,\n} from \"./services/codex-live-output-stream.service.js\";\n\ntype CodexCtor = new (options: CodexOptions) => CodexClient;\n\ntype CodexLoader = {\n loadCodexConstructor: () => Promise<CodexCtor>;\n};\n\nconst require = createRequire(import.meta.url);\nconst codexLoader = require(\"../codex-sdk-loader.cjs\") as CodexLoader;\n\nexport type CodexSdkNcpAgentRuntimeConfig = {\n sessionId: string;\n apiKey: string;\n apiBase?: string;\n model?: string;\n threadId?: string | null;\n codexPathOverride?: string;\n env?: Record<string, string>;\n cliConfig?: CodexOptions[\"config\"];\n threadOptions?: ThreadOptions;\n sessionMetadata?: Record<string, unknown>;\n setSessionMetadata?: (nextMetadata: Record<string, unknown>) => void | Promise<void>;\n inputBuilder?: (input: NcpAgentRunInput) => Promise<CodexThreadInput> | CodexThreadInput;\n liveOutputStream?: CodexLiveOutputStream;\n resolveAssetContentPath?: CodexAssetContentPathResolver;\n stateManager?: NcpAgentConversationStateManager;\n};\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction toAbortError(reason: unknown): Error {\n if (reason instanceof Error) {\n return reason;\n }\n const message = typeof reason === \"string\" && reason.trim() ? reason.trim() : \"operation aborted\";\n const error = new Error(message);\n error.name = \"AbortError\";\n return error;\n}\n\nfunction normalizeThreadOptions(\n options: ThreadOptions | undefined,\n model: string | undefined,\n): ThreadOptions {\n return {\n ...options,\n skipGitRepoCheck: options?.skipGitRepoCheck ?? true,\n ...(model ? { model } : {}),\n };\n}\n\nfunction isItemLifecycleEvent(\n event: Parameters<typeof mapCodexItemEvent>[0][\"event\"] | { type: string },\n): event is Parameters<typeof mapCodexItemEvent>[0][\"event\"] {\n return event.type === \"item.started\" || event.type === \"item.updated\" || event.type === \"item.completed\";\n}\n\nexport class CodexSdkNcpAgentRuntime implements NcpAgentRuntime {\n private codexPromise: Promise<CodexClient> | null = null;\n private readonly eventEmitter: CodexNcpRunEventEmitter;\n private readonly liveOutputEventMergeService = new CodexLiveOutputEventMergeService();\n private thread: Thread | null = null;\n private threadId: string | null;\n private readonly sessionMetadata: Record<string, unknown>;\n\n constructor(private readonly config: CodexSdkNcpAgentRuntimeConfig) {\n this.eventEmitter = new CodexNcpRunEventEmitter(config.stateManager);\n this.threadId = config.threadId?.trim() || null;\n this.sessionMetadata = {\n ...(config.sessionMetadata ? structuredClone(config.sessionMetadata) : {}),\n };\n }\n\n run = async function* (\n this: CodexSdkNcpAgentRuntime,\n input: NcpAgentRunInput,\n options?: NcpAgentRunOptions,\n ): AsyncGenerator<NcpEndpointEvent> {\n const signal = options?.signal;\n const messageId = createId(\"codex-message\");\n const runId = (input as NcpAgentRunInput & { runId?: string }).runId ?? createId(\"codex-run\");\n const itemTextById = new Map<string, ItemTextSnapshot>();\n const toolStateById = new Map<string, ToolSnapshot>();\n\n yield* this.eventEmitter.emitRunStarted(input.sessionId, messageId, runId);\n yield* this.eventEmitter.emitReadyMetadata(input.sessionId, messageId, runId);\n\n const turnInput = await this.buildTurnInput(input);\n const streamed = await this.runStreamedWithCurrentThread(turnInput, signal);\n\n try {\n yield* this.streamTurnEvents({\n sessionId: input.sessionId,\n messageId,\n runId,\n streamed,\n signal,\n itemTextById,\n toolStateById,\n });\n } catch (error) {\n if (signal?.aborted) {\n throw toAbortError(signal.reason);\n }\n throw error;\n }\n };\n\n private getCodex = async (): Promise<CodexClient> => {\n if (!this.codexPromise) {\n const env = buildCodexCliEnv(this.config);\n this.codexPromise = codexLoader.loadCodexConstructor().then((Ctor) =>\n new Ctor({\n apiKey: this.config.apiKey,\n baseUrl: this.config.apiBase,\n ...(this.config.codexPathOverride ? { codexPathOverride: this.config.codexPathOverride } : {}),\n ...(env ? { env } : {}),\n ...(this.config.cliConfig ? { config: this.config.cliConfig } : {}),\n }),\n );\n }\n return this.codexPromise;\n };\n\n private resolveThread = async (): Promise<Thread> => {\n if (this.thread) {\n return this.thread;\n }\n\n const codex = await this.getCodex();\n const threadOptions = normalizeThreadOptions(this.config.threadOptions, this.config.model);\n\n this.thread = this.threadId\n ? codex.resumeThread(this.threadId, threadOptions)\n : codex.startThread(threadOptions);\n return this.thread;\n };\n\n private runStreamedWithCurrentThread = async (\n turnInput: CodexThreadInput,\n signal?: AbortSignal,\n ): ReturnType<Thread[\"runStreamed\"]> => {\n const thread = await this.resolveThread();\n this.config.liveOutputStream?.reset();\n return await thread.runStreamed(turnInput, {\n ...(signal ? { signal } : {}),\n });\n };\n\n private buildTurnInput = async (input: NcpAgentRunInput): Promise<CodexThreadInput> => {\n if (this.config.inputBuilder) {\n return await this.config.inputBuilder(input);\n }\n return await buildCodexTurnInputFromRunInput(input, {\n resolveAssetContentPath: this.config.resolveAssetContentPath,\n });\n };\n\n private streamTurnEvents = async function* (\n this: CodexSdkNcpAgentRuntime,\n params: {\n sessionId: string;\n messageId: string;\n runId: string;\n streamed: Awaited<ReturnType<Thread[\"runStreamed\"]>>;\n signal?: AbortSignal;\n itemTextById: Map<string, ItemTextSnapshot>;\n toolStateById: Map<string, ToolSnapshot>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const {\n itemTextById,\n messageId,\n runId,\n sessionId,\n signal,\n streamed,\n toolStateById,\n } =\n params;\n const liveOutputStream = this.config.liveOutputStream;\n if (liveOutputStream) {\n yield* this.liveOutputEventMergeService.stream({\n ...params,\n liveOutputStream,\n emitEvent: (event) => this.eventEmitter.emitEvent(event),\n emitRunCompleted: (sessionId, messageId, runId) =>\n this.eventEmitter.emitRunCompleted(sessionId, messageId, runId),\n handleThreadEvent: (handlerParams) => this.handleThreadEvent(handlerParams),\n });\n return;\n }\n\n let finished = false;\n for await (const event of streamed.events) {\n const shouldFinish = yield* this.handleThreadEvent({\n sessionId,\n messageId,\n runId,\n event,\n signal,\n itemTextById,\n toolStateById,\n });\n if (shouldFinish) {\n finished = true;\n return;\n }\n }\n\n if (!finished) {\n yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);\n }\n };\n\n private handleThreadEvent = async function* (\n this: CodexSdkNcpAgentRuntime,\n params: {\n sessionId: string;\n messageId: string;\n runId: string;\n event: Awaited<ReturnType<Thread[\"runStreamed\"]>>[\"events\"] extends AsyncGenerator<infer T> ? T : never;\n signal?: AbortSignal;\n itemTextById: Map<string, ItemTextSnapshot>;\n suppressLiveChannels?: Set<CodexLiveOutputChannel>;\n toolStateById: Map<string, ToolSnapshot>;\n },\n ): AsyncGenerator<NcpEndpointEvent, boolean> {\n const {\n event,\n itemTextById,\n messageId,\n runId,\n sessionId,\n signal,\n suppressLiveChannels,\n toolStateById,\n } = params;\n if (signal?.aborted) {\n throw toAbortError(signal.reason);\n }\n\n if (event.type === \"thread.started\") {\n await this.updateThreadId(event.thread_id);\n return false;\n }\n\n if (event.type === \"turn.failed\") {\n yield* this.eventEmitter.emitRunError(\n sessionId,\n messageId,\n runId,\n event.error.message,\n );\n return true;\n }\n\n if (event.type === \"error\") {\n yield* this.eventEmitter.emitRunError(\n sessionId,\n messageId,\n runId,\n event.message,\n );\n return true;\n }\n\n if (isItemLifecycleEvent(event)) {\n const suppressedChannel =\n event.item.type === \"agent_message\"\n ? \"text\"\n : event.item.type === \"reasoning\"\n ? \"reasoning\"\n : null;\n if (suppressedChannel && suppressLiveChannels?.has(suppressedChannel)) {\n return false;\n }\n for await (const mappedEvent of mapCodexItemEvent({\n sessionId,\n messageId,\n event,\n itemTextById,\n toolStateById,\n })) {\n yield* this.eventEmitter.emitEvent(mappedEvent);\n }\n return false;\n }\n\n if (event.type === \"turn.completed\") {\n yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);\n return true;\n }\n\n return false;\n };\n\n private updateThreadId = async (nextThreadId: string): Promise<void> => {\n const normalizedThreadId = nextThreadId.trim();\n if (!normalizedThreadId || normalizedThreadId === this.threadId) {\n return;\n }\n this.threadId = normalizedThreadId;\n const nextMetadata = {\n ...this.sessionMetadata,\n session_type: \"codex\",\n codex_thread_id: normalizedThreadId,\n codex_thread_model: buildThreadModelScope(this.config),\n };\n this.sessionMetadata.codex_thread_id = normalizedThreadId;\n this.sessionMetadata.codex_thread_model = nextMetadata.codex_thread_model;\n this.sessionMetadata.session_type = \"codex\";\n await this.config.setSessionMetadata?.(nextMetadata);\n };\n}\n\nfunction buildThreadModelScope(config: CodexSdkNcpAgentRuntimeConfig): string {\n return config.threadOptions?.model ?? config.model ?? \"__nextclaw_runtime_default__\";\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { buildCodexCliEnv } from "../codex-cli-env.js";
|
|
2
|
+
import { delimiter, dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { statSync } from "node:fs";
|
|
6
|
+
import { createInterface } from "node:readline";
|
|
7
|
+
//#region src/services/codex-app-server-client.service.ts
|
|
8
|
+
var AsyncNotificationQueue = class {
|
|
9
|
+
values = [];
|
|
10
|
+
waiters = [];
|
|
11
|
+
closed = false;
|
|
12
|
+
push = (value) => {
|
|
13
|
+
const waiter = this.waiters.shift();
|
|
14
|
+
if (waiter) {
|
|
15
|
+
waiter({
|
|
16
|
+
done: false,
|
|
17
|
+
value
|
|
18
|
+
});
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.values.push(value);
|
|
22
|
+
};
|
|
23
|
+
close = () => {
|
|
24
|
+
this.closed = true;
|
|
25
|
+
for (const waiter of this.waiters.splice(0)) waiter({
|
|
26
|
+
done: true,
|
|
27
|
+
value: void 0
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
next = async () => {
|
|
31
|
+
const value = this.values.shift();
|
|
32
|
+
if (value) return {
|
|
33
|
+
done: false,
|
|
34
|
+
value
|
|
35
|
+
};
|
|
36
|
+
if (this.closed) return {
|
|
37
|
+
done: true,
|
|
38
|
+
value: void 0
|
|
39
|
+
};
|
|
40
|
+
return new Promise((resolve) => this.waiters.push(resolve));
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
var CodexAppServerClient = class {
|
|
44
|
+
child = null;
|
|
45
|
+
nextRequestId = 1;
|
|
46
|
+
pending = /* @__PURE__ */ new Map();
|
|
47
|
+
notifications = new AsyncNotificationQueue();
|
|
48
|
+
stderr = "";
|
|
49
|
+
constructor(config) {
|
|
50
|
+
this.config = config;
|
|
51
|
+
}
|
|
52
|
+
initialize = async () => {
|
|
53
|
+
this.startProcess();
|
|
54
|
+
await this.request("initialize", {
|
|
55
|
+
clientInfo: {
|
|
56
|
+
name: "nextclaw",
|
|
57
|
+
title: "NextClaw",
|
|
58
|
+
version: "0.0.0"
|
|
59
|
+
},
|
|
60
|
+
capabilities: { experimentalApi: true }
|
|
61
|
+
});
|
|
62
|
+
this.notify("initialized", {});
|
|
63
|
+
};
|
|
64
|
+
request = async (method, params, timeoutMs = 3e4) => {
|
|
65
|
+
const child = this.ensureProcess();
|
|
66
|
+
const id = this.nextRequestId;
|
|
67
|
+
this.nextRequestId += 1;
|
|
68
|
+
const promise = new Promise((resolve, reject) => {
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
this.pending.delete(id);
|
|
71
|
+
reject(/* @__PURE__ */ new Error(`Codex app-server request timed out: ${method}`));
|
|
72
|
+
}, timeoutMs);
|
|
73
|
+
this.pending.set(id, {
|
|
74
|
+
resolve: (value) => resolve(value),
|
|
75
|
+
reject,
|
|
76
|
+
timer
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
child.stdin.write(`${JSON.stringify({
|
|
80
|
+
id,
|
|
81
|
+
method,
|
|
82
|
+
params
|
|
83
|
+
})}\n`);
|
|
84
|
+
return promise;
|
|
85
|
+
};
|
|
86
|
+
notify = (method, params) => {
|
|
87
|
+
this.ensureProcess().stdin.write(`${JSON.stringify({
|
|
88
|
+
method,
|
|
89
|
+
params
|
|
90
|
+
})}\n`);
|
|
91
|
+
};
|
|
92
|
+
nextNotification = () => this.notifications.next();
|
|
93
|
+
dispose = () => {
|
|
94
|
+
for (const [id, pending] of this.pending.entries()) {
|
|
95
|
+
clearTimeout(pending.timer);
|
|
96
|
+
pending.reject(/* @__PURE__ */ new Error(`Codex app-server request ${id} was interrupted.`));
|
|
97
|
+
}
|
|
98
|
+
this.pending.clear();
|
|
99
|
+
this.notifications.close();
|
|
100
|
+
this.child?.kill("SIGTERM");
|
|
101
|
+
this.child = null;
|
|
102
|
+
};
|
|
103
|
+
startProcess = () => {
|
|
104
|
+
if (this.child) return;
|
|
105
|
+
const resolved = this.resolveCodexExecutable();
|
|
106
|
+
const args = ["app-server", "--stdio"];
|
|
107
|
+
for (const override of serializeConfigOverrides(this.config.cliConfig)) args.push("--config", override);
|
|
108
|
+
if (this.config.apiBase?.trim()) args.push("--config", `openai_base_url=${toTomlValue(this.config.apiBase.trim(), "openai_base_url")}`);
|
|
109
|
+
const env = buildCodexCliEnv(this.config) ?? {};
|
|
110
|
+
if (this.config.apiKey.trim()) env.CODEX_API_KEY = this.config.apiKey.trim();
|
|
111
|
+
this.prependCodexPathDirs(env, resolved.pathDirs);
|
|
112
|
+
this.child = spawn(resolved.executablePath, args, { env });
|
|
113
|
+
this.child.stderr.on("data", (data) => this.captureStderr(data.toString()));
|
|
114
|
+
this.child.once("error", (error) => {
|
|
115
|
+
this.rejectAll(error);
|
|
116
|
+
this.notifications.close();
|
|
117
|
+
});
|
|
118
|
+
this.child.once("exit", (code, signal) => this.handleExit(code, signal));
|
|
119
|
+
const rl = createInterface({
|
|
120
|
+
input: this.child.stdout,
|
|
121
|
+
crlfDelay: Infinity
|
|
122
|
+
});
|
|
123
|
+
rl.on("line", (line) => this.handleLine(line));
|
|
124
|
+
rl.once("close", () => this.notifications.close());
|
|
125
|
+
};
|
|
126
|
+
ensureProcess = () => {
|
|
127
|
+
if (!this.child) throw new Error("Codex app-server is not running.");
|
|
128
|
+
return this.child;
|
|
129
|
+
};
|
|
130
|
+
handleLine = (line) => {
|
|
131
|
+
if (!line.trim()) return;
|
|
132
|
+
let message;
|
|
133
|
+
try {
|
|
134
|
+
message = JSON.parse(line);
|
|
135
|
+
} catch {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if ("id" in message) {
|
|
139
|
+
this.handleResponse(message);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.notifications.push({
|
|
143
|
+
method: message.method,
|
|
144
|
+
params: isJsonObject(message.params) ? message.params : {}
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
handleResponse = (message) => {
|
|
148
|
+
const pending = this.pending.get(message.id);
|
|
149
|
+
if (!pending) return;
|
|
150
|
+
clearTimeout(pending.timer);
|
|
151
|
+
this.pending.delete(message.id);
|
|
152
|
+
if (message.error) {
|
|
153
|
+
pending.reject(new Error(message.error.message));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
pending.resolve(message.result);
|
|
157
|
+
};
|
|
158
|
+
handleExit = (code, signal) => {
|
|
159
|
+
const detail = signal ? `signal ${signal}` : `code ${code ?? 1}`;
|
|
160
|
+
this.rejectAll(/* @__PURE__ */ new Error(`Codex app-server exited with ${detail}: ${this.stderr.slice(-4e3)}`));
|
|
161
|
+
this.notifications.close();
|
|
162
|
+
this.child = null;
|
|
163
|
+
};
|
|
164
|
+
captureStderr = (chunk) => {
|
|
165
|
+
this.stderr += chunk;
|
|
166
|
+
if (this.stderr.length > 2e4) this.stderr = this.stderr.slice(-12e3);
|
|
167
|
+
};
|
|
168
|
+
rejectAll = (error) => {
|
|
169
|
+
for (const pending of this.pending.values()) {
|
|
170
|
+
clearTimeout(pending.timer);
|
|
171
|
+
pending.reject(error);
|
|
172
|
+
}
|
|
173
|
+
this.pending.clear();
|
|
174
|
+
};
|
|
175
|
+
resolveCodexExecutable = () => {
|
|
176
|
+
if (this.config.codexPathOverride?.trim()) return {
|
|
177
|
+
executablePath: this.config.codexPathOverride.trim(),
|
|
178
|
+
pathDirs: []
|
|
179
|
+
};
|
|
180
|
+
const localBin = join(dirname(dirname(fileURLToPath(import.meta.resolve("@openai/codex-sdk")))), "node_modules", ".bin", binaryName());
|
|
181
|
+
if (isFile(localBin)) return {
|
|
182
|
+
executablePath: localBin,
|
|
183
|
+
pathDirs: []
|
|
184
|
+
};
|
|
185
|
+
throw new Error("Unable to locate Codex app-server binary from @openai/codex-sdk.");
|
|
186
|
+
};
|
|
187
|
+
prependCodexPathDirs = (env, pathDirs) => {
|
|
188
|
+
if (pathDirs.length === 0) return;
|
|
189
|
+
const key = process.platform === "win32" ? "Path" : "PATH";
|
|
190
|
+
const existing = (env[key] ?? "").split(delimiter).filter(Boolean);
|
|
191
|
+
env[key] = [...pathDirs, ...existing.filter((entry) => !pathDirs.includes(entry))].join(delimiter);
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
function isJsonObject(value) {
|
|
195
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
196
|
+
}
|
|
197
|
+
function serializeConfigOverrides(configOverrides) {
|
|
198
|
+
if (!configOverrides) return [];
|
|
199
|
+
return flattenConfigOverrides(configOverrides, "");
|
|
200
|
+
}
|
|
201
|
+
function flattenConfigOverrides(value, prefix) {
|
|
202
|
+
if (!isJsonObject(value)) {
|
|
203
|
+
if (!prefix) throw new Error("Codex config overrides must be a plain object.");
|
|
204
|
+
return [`${prefix}=${toTomlValue(value, prefix)}`];
|
|
205
|
+
}
|
|
206
|
+
const entries = Object.entries(value);
|
|
207
|
+
if (prefix && entries.length === 0) return [`${prefix}={}`];
|
|
208
|
+
const overrides = [];
|
|
209
|
+
for (const [key, child] of entries) {
|
|
210
|
+
if (child === void 0) continue;
|
|
211
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
212
|
+
if (isJsonObject(child)) overrides.push(...flattenConfigOverrides(child, path));
|
|
213
|
+
else overrides.push(`${path}=${toTomlValue(child, path)}`);
|
|
214
|
+
}
|
|
215
|
+
return overrides;
|
|
216
|
+
}
|
|
217
|
+
function toTomlValue(value, path) {
|
|
218
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
219
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
220
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
221
|
+
if (Array.isArray(value)) return `[${value.map((item, index) => toTomlValue(item, `${path}[${index}]`)).join(", ")}]`;
|
|
222
|
+
if (isJsonObject(value)) return `{${Object.entries(value).filter(([, child]) => child !== void 0).map(([key, child]) => `${formatTomlKey(key)} = ${toTomlValue(child, `${path}.${key}`)}`).join(", ")}}`;
|
|
223
|
+
throw new Error(`Unsupported Codex config override value at ${path}.`);
|
|
224
|
+
}
|
|
225
|
+
const TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
226
|
+
function formatTomlKey(key) {
|
|
227
|
+
return TOML_BARE_KEY.test(key) ? key : JSON.stringify(key);
|
|
228
|
+
}
|
|
229
|
+
function binaryName() {
|
|
230
|
+
return process.platform === "win32" ? "codex.exe" : "codex";
|
|
231
|
+
}
|
|
232
|
+
function isFile(path) {
|
|
233
|
+
try {
|
|
234
|
+
return statSync(path).isFile();
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
export { CodexAppServerClient };
|
|
241
|
+
|
|
242
|
+
//# sourceMappingURL=codex-app-server-client.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-app-server-client.service.js","names":[],"sources":["../../src/services/codex-app-server-client.service.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport { statSync } from \"node:fs\";\nimport { delimiter, dirname, join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { fileURLToPath } from \"node:url\";\nimport type { CodexOptions } from \"@openai/codex-sdk\";\nimport { buildCodexCliEnv } from \"@/codex-cli-env.js\";\nimport type {\n AppServerNotification,\n CodexAppServerNcpAgentRuntimeConfig,\n JsonObject,\n} from \"@/types/codex-app-server-runtime.types.js\";\n\ntype CodexAppServerMessage =\n | { id: number; result?: unknown; error?: { code: number; message: string; data?: unknown } }\n | { method: string; params?: unknown };\n\ntype PendingRequest = {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n timer: NodeJS.Timeout;\n};\n\nclass AsyncNotificationQueue {\n private readonly values: AppServerNotification[] = [];\n private readonly waiters: Array<(value: IteratorResult<AppServerNotification>) => void> = [];\n private closed = false;\n\n push = (value: AppServerNotification): void => {\n const waiter = this.waiters.shift();\n if (waiter) {\n waiter({ done: false, value });\n return;\n }\n this.values.push(value);\n };\n\n close = (): void => {\n this.closed = true;\n for (const waiter of this.waiters.splice(0)) {\n waiter({ done: true, value: undefined });\n }\n };\n\n next = async (): Promise<IteratorResult<AppServerNotification>> => {\n const value = this.values.shift();\n if (value) {\n return { done: false, value };\n }\n if (this.closed) {\n return { done: true, value: undefined };\n }\n return new Promise((resolve) => this.waiters.push(resolve));\n };\n}\n\nexport class CodexAppServerClient {\n private child: ChildProcessWithoutNullStreams | null = null;\n private nextRequestId = 1;\n private readonly pending = new Map<number, PendingRequest>();\n private readonly notifications = new AsyncNotificationQueue();\n private stderr = \"\";\n\n constructor(private readonly config: CodexAppServerNcpAgentRuntimeConfig) {}\n\n initialize = async (): Promise<void> => {\n this.startProcess();\n await this.request(\"initialize\", {\n clientInfo: {\n name: \"nextclaw\",\n title: \"NextClaw\",\n version: \"0.0.0\",\n },\n capabilities: { experimentalApi: true },\n });\n this.notify(\"initialized\", {});\n };\n\n request = async <T = unknown>(\n method: string,\n params: JsonObject,\n timeoutMs = 30000,\n ): Promise<T> => {\n const child = this.ensureProcess();\n const id = this.nextRequestId;\n this.nextRequestId += 1;\n const promise = new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`Codex app-server request timed out: ${method}`));\n }, timeoutMs);\n this.pending.set(id, {\n resolve: (value) => resolve(value as T),\n reject,\n timer,\n });\n });\n child.stdin.write(`${JSON.stringify({ id, method, params })}\\n`);\n return promise;\n };\n\n notify = (method: string, params: JsonObject): void => {\n this.ensureProcess().stdin.write(`${JSON.stringify({ method, params })}\\n`);\n };\n\n nextNotification = (): Promise<IteratorResult<AppServerNotification>> =>\n this.notifications.next();\n\n dispose = (): void => {\n for (const [id, pending] of this.pending.entries()) {\n clearTimeout(pending.timer);\n pending.reject(new Error(`Codex app-server request ${id} was interrupted.`));\n }\n this.pending.clear();\n this.notifications.close();\n this.child?.kill(\"SIGTERM\");\n this.child = null;\n };\n\n private startProcess = (): void => {\n if (this.child) {\n return;\n }\n const resolved = this.resolveCodexExecutable();\n const args = [\"app-server\", \"--stdio\"];\n for (const override of serializeConfigOverrides(this.config.cliConfig)) {\n args.push(\"--config\", override);\n }\n if (this.config.apiBase?.trim()) {\n args.push(\n \"--config\",\n `openai_base_url=${toTomlValue(this.config.apiBase.trim(), \"openai_base_url\")}`,\n );\n }\n const env = buildCodexCliEnv(this.config) ?? {};\n if (this.config.apiKey.trim()) {\n env.CODEX_API_KEY = this.config.apiKey.trim();\n }\n this.prependCodexPathDirs(env, resolved.pathDirs);\n this.child = spawn(resolved.executablePath, args, { env });\n this.child.stderr.on(\"data\", (data) => this.captureStderr(data.toString()));\n this.child.once(\"error\", (error) => {\n this.rejectAll(error);\n this.notifications.close();\n });\n this.child.once(\"exit\", (code, signal) => this.handleExit(code, signal));\n const rl = createInterface({ input: this.child.stdout, crlfDelay: Infinity });\n rl.on(\"line\", (line) => this.handleLine(line));\n rl.once(\"close\", () => this.notifications.close());\n };\n\n private ensureProcess = (): ChildProcessWithoutNullStreams => {\n if (!this.child) {\n throw new Error(\"Codex app-server is not running.\");\n }\n return this.child;\n };\n\n private handleLine = (line: string): void => {\n if (!line.trim()) {\n return;\n }\n let message: CodexAppServerMessage;\n try {\n message = JSON.parse(line) as CodexAppServerMessage;\n } catch {\n return;\n }\n if (\"id\" in message) {\n this.handleResponse(message);\n return;\n }\n this.notifications.push({\n method: message.method,\n params: isJsonObject(message.params) ? message.params : {},\n });\n };\n\n private handleResponse = (\n message: Extract<CodexAppServerMessage, { id: number }>,\n ): void => {\n const pending = this.pending.get(message.id);\n if (!pending) {\n return;\n }\n clearTimeout(pending.timer);\n this.pending.delete(message.id);\n if (message.error) {\n pending.reject(new Error(message.error.message));\n return;\n }\n pending.resolve(message.result);\n };\n\n private handleExit = (code: number | null, signal: NodeJS.Signals | null): void => {\n const detail = signal ? `signal ${signal}` : `code ${code ?? 1}`;\n this.rejectAll(\n new Error(`Codex app-server exited with ${detail}: ${this.stderr.slice(-4000)}`),\n );\n this.notifications.close();\n this.child = null;\n };\n\n private captureStderr = (chunk: string): void => {\n this.stderr += chunk;\n if (this.stderr.length > 20000) {\n this.stderr = this.stderr.slice(-12000);\n }\n };\n\n private rejectAll = (error: Error): void => {\n for (const pending of this.pending.values()) {\n clearTimeout(pending.timer);\n pending.reject(error);\n }\n this.pending.clear();\n };\n\n private resolveCodexExecutable = (): { executablePath: string; pathDirs: string[] } => {\n if (this.config.codexPathOverride?.trim()) {\n return { executablePath: this.config.codexPathOverride.trim(), pathDirs: [] };\n }\n const codexSdkEntry = fileURLToPath(import.meta.resolve(\"@openai/codex-sdk\"));\n const codexSdkRoot = dirname(dirname(codexSdkEntry));\n const localBin = join(codexSdkRoot, \"node_modules\", \".bin\", binaryName());\n if (isFile(localBin)) {\n return { executablePath: localBin, pathDirs: [] };\n }\n throw new Error(\"Unable to locate Codex app-server binary from @openai/codex-sdk.\");\n };\n\n private prependCodexPathDirs = (env: Record<string, string>, pathDirs: string[]): void => {\n if (pathDirs.length === 0) {\n return;\n }\n const key = process.platform === \"win32\" ? \"Path\" : \"PATH\";\n const existing = (env[key] ?? \"\").split(delimiter).filter(Boolean);\n env[key] = [...pathDirs, ...existing.filter((entry) => !pathDirs.includes(entry))].join(delimiter);\n };\n}\n\nfunction isJsonObject(value: unknown): value is JsonObject {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction serializeConfigOverrides(configOverrides: CodexOptions[\"config\"] | undefined): string[] {\n if (!configOverrides) {\n return [];\n }\n return flattenConfigOverrides(configOverrides, \"\");\n}\n\nfunction flattenConfigOverrides(value: unknown, prefix: string): string[] {\n if (!isJsonObject(value)) {\n if (!prefix) {\n throw new Error(\"Codex config overrides must be a plain object.\");\n }\n return [`${prefix}=${toTomlValue(value, prefix)}`];\n }\n const entries = Object.entries(value);\n if (prefix && entries.length === 0) {\n return [`${prefix}={}`];\n }\n const overrides: string[] = [];\n for (const [key, child] of entries) {\n if (child === undefined) {\n continue;\n }\n const path = prefix ? `${prefix}.${key}` : key;\n if (isJsonObject(child)) {\n overrides.push(...flattenConfigOverrides(child, path));\n } else {\n overrides.push(`${path}=${toTomlValue(child, path)}`);\n }\n }\n return overrides;\n}\n\nfunction toTomlValue(value: unknown, path: string): string {\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return String(value);\n }\n if (typeof value === \"boolean\") {\n return value ? \"true\" : \"false\";\n }\n if (Array.isArray(value)) {\n return `[${value.map((item, index) => toTomlValue(item, `${path}[${index}]`)).join(\", \")}]`;\n }\n if (isJsonObject(value)) {\n return `{${Object.entries(value)\n .filter(([, child]) => child !== undefined)\n .map(([key, child]) => `${formatTomlKey(key)} = ${toTomlValue(child, `${path}.${key}`)}`)\n .join(\", \")}}`;\n }\n throw new Error(`Unsupported Codex config override value at ${path}.`);\n}\n\nconst TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;\n\nfunction formatTomlKey(key: string): string {\n return TOML_BARE_KEY.test(key) ? key : JSON.stringify(key);\n}\n\nfunction binaryName(): string {\n return process.platform === \"win32\" ? \"codex.exe\" : \"codex\";\n}\n\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CodexAppServerNcpAgentRuntimeConfig } from "../types/codex-app-server-runtime.types.js";
|
|
2
|
+
import { NcpAgentRunInput, NcpAgentRunOptions, NcpAgentRuntime, NcpEndpointEvent } from "@nextclaw/ncp";
|
|
3
|
+
|
|
4
|
+
//#region src/services/codex-app-server-ncp-agent-runtime.service.d.ts
|
|
5
|
+
declare class CodexAppServerNcpAgentRuntime implements NcpAgentRuntime {
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly eventEmitter;
|
|
8
|
+
private readonly sessionMetadata;
|
|
9
|
+
private clientPromise;
|
|
10
|
+
private threadId;
|
|
11
|
+
constructor(config: CodexAppServerNcpAgentRuntimeConfig);
|
|
12
|
+
run: (this: CodexAppServerNcpAgentRuntime, input: NcpAgentRunInput, options?: NcpAgentRunOptions) => AsyncGenerator<NcpEndpointEvent>;
|
|
13
|
+
private resolveClient;
|
|
14
|
+
private resolveThread;
|
|
15
|
+
private buildThreadOverrides;
|
|
16
|
+
private buildTurnOverrides;
|
|
17
|
+
private buildTurnInput;
|
|
18
|
+
private handleNotification;
|
|
19
|
+
private handleThreadStarted;
|
|
20
|
+
private emitTextDelta;
|
|
21
|
+
private emitReasoningDelta;
|
|
22
|
+
private handleItemLifecycle;
|
|
23
|
+
private handleAgentMessageItem;
|
|
24
|
+
private handleReasoningItem;
|
|
25
|
+
private handleToolItem;
|
|
26
|
+
private updateThreadId;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { CodexAppServerNcpAgentRuntime };
|
|
30
|
+
//# sourceMappingURL=codex-app-server-ncp-agent-runtime.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-app-server-ncp-agent-runtime.service.d.ts","names":[],"sources":["../../src/services/codex-app-server-ncp-agent-runtime.service.ts"],"mappings":";;;;cAkCa,6BAAA,YAAyC,eAAA;EAAA,iBAMvB,MAAA;EAAA,iBALZ,YAAA;EAAA,iBACA,eAAA;EAAA,QACT,aAAA;EAAA,QACA,QAAA;cAEqB,MAAA,EAAQ,mCAAA;EAQrC,GAAA,GACE,IAAA,EAAM,6BAAA,EACN,KAAA,EAAO,gBAAA,EACP,OAAA,GAAU,kBAAA,KACT,cAAA,CAAe,gBAAA;EAAA,QA6DV,aAAA;EAAA,QAQA,aAAA;EAAA,QAkBA,oBAAA;EAAA,QAaA,kBAAA;EAAA,QAYA,cAAA;EAAA,QASA,kBAAA;EAAA,QAqFA,mBAAA;EAAA,QAOA,aAAA;EAAA,QA0BA,kBAAA;EAAA,QA0BA,mBAAA;EAAA,QAwDA,sBAAA;EAAA,QAmCA,mBAAA;EAAA,QAqCA,cAAA;EAAA,QAuCA,cAAA;AAAA"}
|