@nextclaw/nextclaw-ncp-runtime-codex-sdk 0.1.55-beta.0 → 0.1.55
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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/services/codex-app-server-ncp-agent-runtime.service.d.ts +2 -0
- package/dist/services/codex-app-server-ncp-agent-runtime.service.d.ts.map +1 -1
- package/dist/services/codex-app-server-ncp-agent-runtime.service.js +21 -0
- package/dist/services/codex-app-server-ncp-agent-runtime.service.js.map +1 -1
- package/dist/services/codex-desktop-thread-index-sync.service.d.ts +47 -0
- package/dist/services/codex-desktop-thread-index-sync.service.d.ts.map +1 -0
- package/dist/services/codex-desktop-thread-index-sync.service.js +256 -0
- package/dist/services/codex-desktop-thread-index-sync.service.js.map +1 -0
- package/dist/types/codex-app-server-runtime.types.d.ts +2 -0
- package/dist/types/codex-app-server-runtime.types.d.ts.map +1 -1
- package/dist/utils/codex-rollout-thread-summary.utils.js +121 -0
- package/dist/utils/codex-rollout-thread-summary.utils.js.map +1 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ 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 { CodexDesktopThreadIndexSync, CodexDesktopThreadIndexSyncService, CodexDesktopThreadIndexSyncServiceOptions } from "./services/codex-desktop-thread-index-sync.service.js";
|
|
6
7
|
import { CodexAppServerNcpAgentRuntimeConfig } from "./types/codex-app-server-runtime.types.js";
|
|
7
8
|
import { CodexAppServerNcpAgentRuntime } from "./services/codex-app-server-ncp-agent-runtime.service.js";
|
|
8
9
|
import { NcpAgentConversationStateManager, NcpAgentRunInput, NcpAgentRunOptions, NcpAgentRuntime, NcpEndpointEvent } from "@nextclaw/ncp";
|
|
@@ -45,5 +46,5 @@ declare class CodexSdkNcpAgentRuntime implements NcpAgentRuntime {
|
|
|
45
46
|
private updateThreadId;
|
|
46
47
|
}
|
|
47
48
|
//#endregion
|
|
48
|
-
export { CodexAppServerNcpAgentRuntime, type CodexAppServerNcpAgentRuntimeConfig, type CodexAssetContentPathResolver, type CodexLiveOutputChannel, type CodexLiveOutputEvent, CodexLiveOutputStream, type CodexOpenAiResponsesBridgeResult, type CodexOpenAiResponsesBridgeRuntimeConfig, CodexSdkNcpAgentRuntime, CodexSdkNcpAgentRuntimeConfig, buildCodexBridgeModelProviderId, buildCodexInputBuilder, ensureCodexOpenAiResponsesBridge };
|
|
49
|
+
export { CodexAppServerNcpAgentRuntime, type CodexAppServerNcpAgentRuntimeConfig, type CodexAssetContentPathResolver, type CodexDesktopThreadIndexSync, CodexDesktopThreadIndexSyncService, type CodexDesktopThreadIndexSyncServiceOptions, type CodexLiveOutputChannel, type CodexLiveOutputEvent, CodexLiveOutputStream, type CodexOpenAiResponsesBridgeResult, type CodexOpenAiResponsesBridgeRuntimeConfig, CodexSdkNcpAgentRuntime, CodexSdkNcpAgentRuntimeConfig, buildCodexBridgeModelProviderId, buildCodexInputBuilder, ensureCodexOpenAiResponsesBridge };
|
|
49
50
|
//# 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":";;;;;;;;;;;;KA0DY,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 { CodexDesktopThreadIndexSyncService } from "./services/codex-desktop-thread-index-sync.service.js";
|
|
9
10
|
import { CodexAppServerNcpAgentRuntime } from "./services/codex-app-server-ncp-agent-runtime.service.js";
|
|
10
11
|
import { createRequire } from "node:module";
|
|
11
12
|
import "@nextclaw/ncp";
|
|
@@ -179,6 +180,6 @@ function buildThreadModelScope(config) {
|
|
|
179
180
|
return config.threadOptions?.model ?? config.model ?? "__nextclaw_runtime_default__";
|
|
180
181
|
}
|
|
181
182
|
//#endregion
|
|
182
|
-
export { CodexAppServerNcpAgentRuntime, CodexLiveOutputStream, CodexSdkNcpAgentRuntime, buildCodexBridgeModelProviderId, buildCodexInputBuilder, ensureCodexOpenAiResponsesBridge };
|
|
183
|
+
export { CodexAppServerNcpAgentRuntime, CodexDesktopThreadIndexSyncService, CodexLiveOutputStream, CodexSdkNcpAgentRuntime, buildCodexBridgeModelProviderId, buildCodexInputBuilder, ensureCodexOpenAiResponsesBridge };
|
|
183
184
|
|
|
184
185
|
//# 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 {\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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"}
|
|
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 {\n CodexDesktopThreadIndexSyncService,\n} from \"./services/codex-desktop-thread-index-sync.service.js\";\nexport type { CodexAppServerNcpAgentRuntimeConfig } from \"./types/codex-app-server-runtime.types.js\";\nexport type {\n CodexDesktopThreadIndexSync,\n CodexDesktopThreadIndexSyncServiceOptions,\n} from \"./services/codex-desktop-thread-index-sync.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 | 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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"}
|
|
@@ -6,6 +6,7 @@ declare class CodexAppServerNcpAgentRuntime implements NcpAgentRuntime {
|
|
|
6
6
|
private readonly config;
|
|
7
7
|
private readonly eventEmitter;
|
|
8
8
|
private readonly sessionMetadata;
|
|
9
|
+
private readonly desktopThreadIndexSync;
|
|
9
10
|
private clientPromise;
|
|
10
11
|
private threadId;
|
|
11
12
|
constructor(config: CodexAppServerNcpAgentRuntimeConfig);
|
|
@@ -24,6 +25,7 @@ declare class CodexAppServerNcpAgentRuntime implements NcpAgentRuntime {
|
|
|
24
25
|
private handleReasoningItem;
|
|
25
26
|
private handleToolItem;
|
|
26
27
|
private updateThreadId;
|
|
28
|
+
private syncDesktopThreadIndex;
|
|
27
29
|
}
|
|
28
30
|
//#endregion
|
|
29
31
|
export { CodexAppServerNcpAgentRuntime };
|
|
@@ -1 +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":";;;;
|
|
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":";;;;cAsCa,6BAAA,YAAyC,eAAA;EAAA,iBAOvB,MAAA;EAAA,iBANZ,YAAA;EAAA,iBACA,eAAA;EAAA,iBACA,sBAAA;EAAA,QACT,aAAA;EAAA,QACA,QAAA;cAEqB,MAAA,EAAQ,mCAAA;EASrC,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,QAsFA,mBAAA;EAAA,QAOA,aAAA;EAAA,QA0BA,kBAAA;EAAA,QA0BA,mBAAA;EAAA,QAwDA,sBAAA;EAAA,QAmCA,mBAAA;EAAA,QAqCA,cAAA;EAAA,QAuCA,cAAA;EAAA,QAkBA,sBAAA;AAAA"}
|
|
@@ -3,16 +3,19 @@ import { buildCodexTurnInputFromRunInput } from "../codex-input.utils.js";
|
|
|
3
3
|
import { isAppServerToolLikeItem, readAppServerReasoningText, readAppServerToolArgs, readAppServerToolName, readAppServerToolResult, stringifyAppServerToolArgs } from "../utils/codex-app-server-item-mapper.utils.js";
|
|
4
4
|
import { compactObject, normalizeSandbox, splitModelRoute, toAppServerInput } from "../utils/codex-app-server-request.utils.js";
|
|
5
5
|
import { CodexAppServerClient } from "./codex-app-server-client.service.js";
|
|
6
|
+
import { CodexDesktopThreadIndexSyncService } from "./codex-desktop-thread-index-sync.service.js";
|
|
6
7
|
import { NcpEventType } from "@nextclaw/ncp";
|
|
7
8
|
//#region src/services/codex-app-server-ncp-agent-runtime.service.ts
|
|
8
9
|
var CodexAppServerNcpAgentRuntime = class {
|
|
9
10
|
eventEmitter;
|
|
10
11
|
sessionMetadata;
|
|
12
|
+
desktopThreadIndexSync;
|
|
11
13
|
clientPromise = null;
|
|
12
14
|
threadId;
|
|
13
15
|
constructor(config) {
|
|
14
16
|
this.config = config;
|
|
15
17
|
this.eventEmitter = new CodexNcpRunEventEmitter(config.stateManager);
|
|
18
|
+
this.desktopThreadIndexSync = resolveDesktopThreadIndexSync(config);
|
|
16
19
|
this.threadId = config.threadId?.trim() || null;
|
|
17
20
|
this.sessionMetadata = { ...config.sessionMetadata ? structuredClone(config.sessionMetadata) : {} };
|
|
18
21
|
}
|
|
@@ -155,6 +158,7 @@ var CodexAppServerNcpAgentRuntime = class {
|
|
|
155
158
|
}
|
|
156
159
|
if (notification.method === "turn/completed") {
|
|
157
160
|
yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);
|
|
161
|
+
await this.syncDesktopThreadIndex();
|
|
158
162
|
return true;
|
|
159
163
|
}
|
|
160
164
|
if (notification.method === "turn/failed") {
|
|
@@ -361,10 +365,24 @@ var CodexAppServerNcpAgentRuntime = class {
|
|
|
361
365
|
this.sessionMetadata.session_type = "codex";
|
|
362
366
|
await this.config.setSessionMetadata?.(nextMetadata);
|
|
363
367
|
};
|
|
368
|
+
syncDesktopThreadIndex = async () => {
|
|
369
|
+
try {
|
|
370
|
+
await this.desktopThreadIndexSync?.syncThread({ threadId: this.threadId });
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error(`[nextclaw-codex-app-server] failed to run Codex Desktop thread index sync: ${formatError(error)}`);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
364
375
|
};
|
|
365
376
|
function createId(prefix) {
|
|
366
377
|
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
367
378
|
}
|
|
379
|
+
function resolveDesktopThreadIndexSync(config) {
|
|
380
|
+
if (config.desktopThreadIndexSync === false) return null;
|
|
381
|
+
return config.desktopThreadIndexSync ?? new CodexDesktopThreadIndexSyncService({ env: {
|
|
382
|
+
...process.env,
|
|
383
|
+
...config.env
|
|
384
|
+
} });
|
|
385
|
+
}
|
|
368
386
|
function readString(value) {
|
|
369
387
|
if (typeof value !== "string") return;
|
|
370
388
|
const trimmed = value.trim();
|
|
@@ -383,6 +401,9 @@ function toAbortError(reason) {
|
|
|
383
401
|
error.name = "AbortError";
|
|
384
402
|
return error;
|
|
385
403
|
}
|
|
404
|
+
function formatError(error) {
|
|
405
|
+
return error instanceof Error ? error.message : String(error);
|
|
406
|
+
}
|
|
386
407
|
//#endregion
|
|
387
408
|
export { CodexAppServerNcpAgentRuntime };
|
|
388
409
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-app-server-ncp-agent-runtime.service.js","names":[],"sources":["../../src/services/codex-app-server-ncp-agent-runtime.service.ts"],"sourcesContent":["import {\n type NcpAgentRunInput,\n type NcpAgentRunOptions,\n type NcpAgentRuntime,\n type NcpEndpointEvent,\n NcpEventType,\n} from \"@nextclaw/ncp\";\nimport {\n buildCodexTurnInputFromRunInput,\n type CodexThreadInput,\n} from \"@/codex-input.utils.js\";\nimport {\n isAppServerToolLikeItem,\n readAppServerReasoningText,\n readAppServerToolArgs,\n readAppServerToolName,\n readAppServerToolResult,\n stringifyAppServerToolArgs,\n} from \"@/utils/codex-app-server-item-mapper.utils.js\";\nimport {\n compactObject,\n normalizeSandbox,\n splitModelRoute,\n toAppServerInput,\n} from \"@/utils/codex-app-server-request.utils.js\";\nimport { CodexAppServerClient } from \"./codex-app-server-client.service.js\";\nimport type {\n AppServerNotification,\n AppServerThreadItem,\n CodexAppServerNcpAgentRuntimeConfig,\n JsonObject,\n} from \"@/types/codex-app-server-runtime.types.js\";\nimport { CodexNcpRunEventEmitter } from \"./codex-ncp-run-event-emitter.service.js\";\n\nexport class CodexAppServerNcpAgentRuntime implements NcpAgentRuntime {\n private readonly eventEmitter: CodexNcpRunEventEmitter;\n private readonly sessionMetadata: Record<string, unknown>;\n private clientPromise: Promise<CodexAppServerClient> | null = null;\n private threadId: string | null;\n\n constructor(private readonly config: CodexAppServerNcpAgentRuntimeConfig) {\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: CodexAppServerNcpAgentRuntime,\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 textState = new Set<string>();\n const textDeltaState = new Set<string>();\n const reasoningState = new Set<string>();\n const reasoningDeltaState = new Set<string>();\n const toolState = new Set<string>();\n\n yield* this.eventEmitter.emitRunStarted(input.sessionId, messageId, runId);\n yield* this.eventEmitter.emitReadyMetadata(input.sessionId, messageId, runId);\n\n const client = await this.resolveClient();\n await this.resolveThread(client);\n const turnInput = await this.buildTurnInput(input);\n const turn = await client.request<{ turn?: { id?: string } }>(\"turn/start\", {\n threadId: this.threadId ?? \"\",\n input: toAppServerInput(turnInput),\n ...this.buildTurnOverrides(),\n });\n const turnId = readString(turn.turn?.id);\n const abortListener = (): void => {\n if (turnId && this.threadId) {\n void client.request(\"turn/interrupt\", { threadId: this.threadId, turnId }, 5000).catch(() => {\n client.dispose();\n });\n } else {\n client.dispose();\n }\n };\n signal?.addEventListener(\"abort\", abortListener, { once: true });\n try {\n while (true) {\n if (signal?.aborted) {\n throw toAbortError(signal.reason);\n }\n const next = await client.nextNotification();\n if (next.done) {\n return;\n }\n const shouldFinish = yield* this.handleNotification({\n notification: next.value,\n sessionId: input.sessionId,\n messageId,\n runId,\n textState,\n textDeltaState,\n reasoningState,\n reasoningDeltaState,\n toolState,\n });\n if (shouldFinish) {\n return;\n }\n }\n } finally {\n signal?.removeEventListener(\"abort\", abortListener);\n }\n };\n\n private resolveClient = async (): Promise<CodexAppServerClient> => {\n if (!this.clientPromise) {\n const client = new CodexAppServerClient(this.config);\n this.clientPromise = client.initialize().then(() => client);\n }\n return this.clientPromise;\n };\n\n private resolveThread = async (client: CodexAppServerClient): Promise<void> => {\n if (this.threadId) {\n const response = await client.request<{ thread?: { id?: string } }>(\"thread/resume\", {\n threadId: this.threadId,\n ...this.buildThreadOverrides(),\n });\n await this.updateThreadId(readString(response.thread?.id) ?? this.threadId);\n return;\n }\n const response = await client.request<{ thread?: { id?: string } }>(\"thread/start\", {\n ...this.buildThreadOverrides(),\n });\n const nextThreadId = readString(response.thread?.id);\n if (nextThreadId) {\n await this.updateThreadId(nextThreadId);\n }\n };\n\n private buildThreadOverrides = (): JsonObject => {\n const threadOptions = this.config.threadOptions;\n const route = splitModelRoute(threadOptions?.model ?? this.config.model);\n return compactObject({\n cwd: threadOptions?.workingDirectory,\n model: route.model,\n modelProvider: route.modelProvider,\n approvalPolicy: threadOptions?.approvalPolicy,\n sandbox: normalizeSandbox(threadOptions?.sandboxMode),\n config: this.config.cliConfig,\n });\n };\n\n private buildTurnOverrides = (): JsonObject => {\n const threadOptions = this.config.threadOptions;\n const route = splitModelRoute(threadOptions?.model ?? this.config.model);\n return compactObject({\n cwd: threadOptions?.workingDirectory,\n model: route.model,\n effort: threadOptions?.modelReasoningEffort,\n sandboxPolicy: normalizeSandbox(threadOptions?.sandboxMode),\n approvalPolicy: threadOptions?.approvalPolicy,\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 handleNotification = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n notification: AppServerNotification;\n sessionId: string;\n messageId: string;\n runId: string;\n textState: Set<string>;\n textDeltaState: Set<string>;\n reasoningState: Set<string>;\n reasoningDeltaState: Set<string>;\n toolState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent, boolean> {\n const {\n messageId,\n notification,\n reasoningDeltaState,\n reasoningState,\n runId,\n sessionId,\n textDeltaState,\n textState,\n toolState,\n } = params;\n if (notification.method === \"thread/started\") {\n await this.handleThreadStarted(notification.params);\n return false;\n }\n if (notification.method === \"item/agentMessage/delta\") {\n yield* this.emitTextDelta({\n messageId,\n params: notification.params,\n sessionId,\n textDeltaState,\n textState,\n });\n return false;\n }\n if (\n notification.method === \"item/reasoning/textDelta\" ||\n notification.method === \"item/reasoning/summaryTextDelta\"\n ) {\n yield* this.emitReasoningDelta({\n messageId,\n params: notification.params,\n reasoningDeltaState,\n reasoningState,\n sessionId,\n });\n return false;\n }\n if (notification.method === \"item/started\" || notification.method === \"item/completed\") {\n const item = notification.params.item as AppServerThreadItem | undefined;\n if (item) {\n yield* this.handleItemLifecycle({\n item,\n eventType: notification.method,\n sessionId,\n messageId,\n reasoningDeltaState,\n textState,\n textDeltaState,\n reasoningState,\n toolState,\n });\n }\n return false;\n }\n if (notification.method === \"turn/completed\") {\n yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);\n return true;\n }\n if (notification.method === \"turn/failed\") {\n yield* this.eventEmitter.emitRunError(\n sessionId,\n messageId,\n runId,\n readString(notification.params.error) ?? \"Codex turn failed.\",\n );\n return true;\n }\n return false;\n };\n\n private handleThreadStarted = async (params: JsonObject): Promise<void> => {\n const threadId = readString((params.thread as JsonObject | undefined)?.id);\n if (threadId) {\n await this.updateThreadId(threadId);\n }\n };\n\n private emitTextDelta = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n params: JsonObject;\n sessionId: string;\n messageId: string;\n textState: Set<string>;\n textDeltaState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { messageId, sessionId, textDeltaState, textState } = params;\n const itemId = readString(params.params.itemId) ?? \"agent-message\";\n if (!textState.has(itemId)) {\n textState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextStart,\n payload: { sessionId, messageId },\n });\n }\n textDeltaState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextDelta,\n payload: { sessionId, messageId, delta: readRawString(params.params.delta) ?? \"\" },\n });\n };\n\n private emitReasoningDelta = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n params: JsonObject;\n sessionId: string;\n messageId: string;\n reasoningState: Set<string>;\n reasoningDeltaState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { messageId, reasoningDeltaState, reasoningState, sessionId } = params;\n const itemId = readString(params.params.itemId) ?? \"reasoning\";\n if (!reasoningState.has(itemId)) {\n reasoningState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningStart,\n payload: { sessionId, messageId },\n });\n }\n reasoningDeltaState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningDelta,\n payload: { sessionId, messageId, delta: readRawString(params.params.delta) ?? \"\" },\n });\n };\n\n private handleItemLifecycle = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n item: AppServerThreadItem;\n eventType: \"item/started\" | \"item/completed\";\n sessionId: string;\n messageId: string;\n textState: Set<string>;\n textDeltaState: Set<string>;\n reasoningState: Set<string>;\n reasoningDeltaState: Set<string>;\n toolState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const {\n eventType,\n item,\n messageId,\n reasoningDeltaState,\n reasoningState,\n sessionId,\n textDeltaState,\n textState,\n toolState,\n } = params;\n const itemId = readString(item.id) ?? createId(\"codex-item\");\n if (item.type === \"agentMessage\") {\n yield* this.handleAgentMessageItem({\n eventType,\n item,\n itemId,\n messageId,\n sessionId,\n textDeltaState,\n textState,\n });\n return;\n }\n if (item.type === \"reasoning\") {\n yield* this.handleReasoningItem({\n eventType,\n item,\n itemId,\n messageId,\n reasoningDeltaState,\n reasoningState,\n sessionId,\n });\n return;\n }\n if (!isAppServerToolLikeItem(item.type)) {\n return;\n }\n yield* this.handleToolItem({ eventType, item, itemId, messageId, sessionId, toolState });\n };\n\n private handleAgentMessageItem = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n item: AppServerThreadItem;\n itemId: string;\n eventType: \"item/started\" | \"item/completed\";\n sessionId: string;\n messageId: string;\n textState: Set<string>;\n textDeltaState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { eventType, item, itemId, messageId, sessionId, textDeltaState, textState } = params;\n if (!textState.has(itemId)) {\n textState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextStart,\n payload: { sessionId, messageId },\n });\n }\n const text = readRawString(item.text);\n if (text && !textDeltaState.has(itemId)) {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextDelta,\n payload: { sessionId, messageId, delta: text },\n });\n }\n if (eventType === \"item/completed\") {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextEnd,\n payload: { sessionId, messageId },\n });\n }\n };\n\n private handleReasoningItem = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n item: AppServerThreadItem;\n itemId: string;\n eventType: \"item/started\" | \"item/completed\";\n sessionId: string;\n messageId: string;\n reasoningState: Set<string>;\n reasoningDeltaState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { eventType, item, itemId, messageId, reasoningDeltaState, reasoningState, sessionId } =\n params;\n const reasoningText = readAppServerReasoningText(item);\n const alreadyStarted = reasoningState.has(itemId);\n if (!alreadyStarted && (eventType === \"item/started\" || reasoningText)) {\n reasoningState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningStart,\n payload: { sessionId, messageId },\n });\n }\n if (!reasoningDeltaState.has(itemId) && reasoningText) {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningDelta,\n payload: { sessionId, messageId, delta: reasoningText },\n });\n }\n if (eventType === \"item/completed\" && reasoningState.has(itemId)) {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningEnd,\n payload: { sessionId, messageId },\n });\n }\n };\n\n private handleToolItem = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n item: AppServerThreadItem;\n itemId: string;\n eventType: \"item/started\" | \"item/completed\";\n sessionId: string;\n messageId: string;\n toolState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { eventType, item, itemId, messageId, sessionId, toolState } = params;\n if (!toolState.has(itemId)) {\n toolState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageToolCallStart,\n payload: { sessionId, messageId, toolCallId: itemId, toolName: readAppServerToolName(item) },\n });\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageToolCallArgs,\n payload: {\n sessionId,\n toolCallId: itemId,\n args: stringifyAppServerToolArgs(readAppServerToolArgs(item)),\n },\n });\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageToolCallEnd,\n payload: { sessionId, toolCallId: itemId },\n });\n }\n if (eventType === \"item/completed\") {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageToolCallResult,\n payload: { sessionId, toolCallId: itemId, content: readAppServerToolResult(item) },\n });\n }\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 createId(prefix: string): string {\n return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction readString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction readRawString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction buildThreadModelScope(config: CodexAppServerNcpAgentRuntimeConfig): string {\n return readString(config.threadOptions?.model) ??\n readString(config.model) ??\n \"__nextclaw_runtime_default__\";\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"codex-app-server-ncp-agent-runtime.service.js","names":[],"sources":["../../src/services/codex-app-server-ncp-agent-runtime.service.ts"],"sourcesContent":["import {\n type NcpAgentRunInput,\n type NcpAgentRunOptions,\n type NcpAgentRuntime,\n type NcpEndpointEvent,\n NcpEventType,\n} from \"@nextclaw/ncp\";\nimport {\n buildCodexTurnInputFromRunInput,\n type CodexThreadInput,\n} from \"@/codex-input.utils.js\";\nimport {\n isAppServerToolLikeItem,\n readAppServerReasoningText,\n readAppServerToolArgs,\n readAppServerToolName,\n readAppServerToolResult,\n stringifyAppServerToolArgs,\n} from \"@/utils/codex-app-server-item-mapper.utils.js\";\nimport {\n compactObject,\n normalizeSandbox,\n splitModelRoute,\n toAppServerInput,\n} from \"@/utils/codex-app-server-request.utils.js\";\nimport { CodexAppServerClient } from \"./codex-app-server-client.service.js\";\nimport type {\n AppServerNotification,\n AppServerThreadItem,\n CodexAppServerNcpAgentRuntimeConfig,\n JsonObject,\n} from \"@/types/codex-app-server-runtime.types.js\";\nimport { CodexNcpRunEventEmitter } from \"./codex-ncp-run-event-emitter.service.js\";\nimport {\n CodexDesktopThreadIndexSyncService,\n type CodexDesktopThreadIndexSync,\n} from \"./codex-desktop-thread-index-sync.service.js\";\n\nexport class CodexAppServerNcpAgentRuntime implements NcpAgentRuntime {\n private readonly eventEmitter: CodexNcpRunEventEmitter;\n private readonly sessionMetadata: Record<string, unknown>;\n private readonly desktopThreadIndexSync: CodexDesktopThreadIndexSync | null;\n private clientPromise: Promise<CodexAppServerClient> | null = null;\n private threadId: string | null;\n\n constructor(private readonly config: CodexAppServerNcpAgentRuntimeConfig) {\n this.eventEmitter = new CodexNcpRunEventEmitter(config.stateManager);\n this.desktopThreadIndexSync = resolveDesktopThreadIndexSync(config);\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: CodexAppServerNcpAgentRuntime,\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 textState = new Set<string>();\n const textDeltaState = new Set<string>();\n const reasoningState = new Set<string>();\n const reasoningDeltaState = new Set<string>();\n const toolState = new Set<string>();\n\n yield* this.eventEmitter.emitRunStarted(input.sessionId, messageId, runId);\n yield* this.eventEmitter.emitReadyMetadata(input.sessionId, messageId, runId);\n\n const client = await this.resolveClient();\n await this.resolveThread(client);\n const turnInput = await this.buildTurnInput(input);\n const turn = await client.request<{ turn?: { id?: string } }>(\"turn/start\", {\n threadId: this.threadId ?? \"\",\n input: toAppServerInput(turnInput),\n ...this.buildTurnOverrides(),\n });\n const turnId = readString(turn.turn?.id);\n const abortListener = (): void => {\n if (turnId && this.threadId) {\n void client.request(\"turn/interrupt\", { threadId: this.threadId, turnId }, 5000).catch(() => {\n client.dispose();\n });\n } else {\n client.dispose();\n }\n };\n signal?.addEventListener(\"abort\", abortListener, { once: true });\n try {\n while (true) {\n if (signal?.aborted) {\n throw toAbortError(signal.reason);\n }\n const next = await client.nextNotification();\n if (next.done) {\n return;\n }\n const shouldFinish = yield* this.handleNotification({\n notification: next.value,\n sessionId: input.sessionId,\n messageId,\n runId,\n textState,\n textDeltaState,\n reasoningState,\n reasoningDeltaState,\n toolState,\n });\n if (shouldFinish) {\n return;\n }\n }\n } finally {\n signal?.removeEventListener(\"abort\", abortListener);\n }\n };\n\n private resolveClient = async (): Promise<CodexAppServerClient> => {\n if (!this.clientPromise) {\n const client = new CodexAppServerClient(this.config);\n this.clientPromise = client.initialize().then(() => client);\n }\n return this.clientPromise;\n };\n\n private resolveThread = async (client: CodexAppServerClient): Promise<void> => {\n if (this.threadId) {\n const response = await client.request<{ thread?: { id?: string } }>(\"thread/resume\", {\n threadId: this.threadId,\n ...this.buildThreadOverrides(),\n });\n await this.updateThreadId(readString(response.thread?.id) ?? this.threadId);\n return;\n }\n const response = await client.request<{ thread?: { id?: string } }>(\"thread/start\", {\n ...this.buildThreadOverrides(),\n });\n const nextThreadId = readString(response.thread?.id);\n if (nextThreadId) {\n await this.updateThreadId(nextThreadId);\n }\n };\n\n private buildThreadOverrides = (): JsonObject => {\n const threadOptions = this.config.threadOptions;\n const route = splitModelRoute(threadOptions?.model ?? this.config.model);\n return compactObject({\n cwd: threadOptions?.workingDirectory,\n model: route.model,\n modelProvider: route.modelProvider,\n approvalPolicy: threadOptions?.approvalPolicy,\n sandbox: normalizeSandbox(threadOptions?.sandboxMode),\n config: this.config.cliConfig,\n });\n };\n\n private buildTurnOverrides = (): JsonObject => {\n const threadOptions = this.config.threadOptions;\n const route = splitModelRoute(threadOptions?.model ?? this.config.model);\n return compactObject({\n cwd: threadOptions?.workingDirectory,\n model: route.model,\n effort: threadOptions?.modelReasoningEffort,\n sandboxPolicy: normalizeSandbox(threadOptions?.sandboxMode),\n approvalPolicy: threadOptions?.approvalPolicy,\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 handleNotification = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n notification: AppServerNotification;\n sessionId: string;\n messageId: string;\n runId: string;\n textState: Set<string>;\n textDeltaState: Set<string>;\n reasoningState: Set<string>;\n reasoningDeltaState: Set<string>;\n toolState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent, boolean> {\n const {\n messageId,\n notification,\n reasoningDeltaState,\n reasoningState,\n runId,\n sessionId,\n textDeltaState,\n textState,\n toolState,\n } = params;\n if (notification.method === \"thread/started\") {\n await this.handleThreadStarted(notification.params);\n return false;\n }\n if (notification.method === \"item/agentMessage/delta\") {\n yield* this.emitTextDelta({\n messageId,\n params: notification.params,\n sessionId,\n textDeltaState,\n textState,\n });\n return false;\n }\n if (\n notification.method === \"item/reasoning/textDelta\" ||\n notification.method === \"item/reasoning/summaryTextDelta\"\n ) {\n yield* this.emitReasoningDelta({\n messageId,\n params: notification.params,\n reasoningDeltaState,\n reasoningState,\n sessionId,\n });\n return false;\n }\n if (notification.method === \"item/started\" || notification.method === \"item/completed\") {\n const item = notification.params.item as AppServerThreadItem | undefined;\n if (item) {\n yield* this.handleItemLifecycle({\n item,\n eventType: notification.method,\n sessionId,\n messageId,\n reasoningDeltaState,\n textState,\n textDeltaState,\n reasoningState,\n toolState,\n });\n }\n return false;\n }\n if (notification.method === \"turn/completed\") {\n yield* this.eventEmitter.emitRunCompleted(sessionId, messageId, runId);\n await this.syncDesktopThreadIndex();\n return true;\n }\n if (notification.method === \"turn/failed\") {\n yield* this.eventEmitter.emitRunError(\n sessionId,\n messageId,\n runId,\n readString(notification.params.error) ?? \"Codex turn failed.\",\n );\n return true;\n }\n return false;\n };\n\n private handleThreadStarted = async (params: JsonObject): Promise<void> => {\n const threadId = readString((params.thread as JsonObject | undefined)?.id);\n if (threadId) {\n await this.updateThreadId(threadId);\n }\n };\n\n private emitTextDelta = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n params: JsonObject;\n sessionId: string;\n messageId: string;\n textState: Set<string>;\n textDeltaState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { messageId, sessionId, textDeltaState, textState } = params;\n const itemId = readString(params.params.itemId) ?? \"agent-message\";\n if (!textState.has(itemId)) {\n textState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextStart,\n payload: { sessionId, messageId },\n });\n }\n textDeltaState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextDelta,\n payload: { sessionId, messageId, delta: readRawString(params.params.delta) ?? \"\" },\n });\n };\n\n private emitReasoningDelta = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n params: JsonObject;\n sessionId: string;\n messageId: string;\n reasoningState: Set<string>;\n reasoningDeltaState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { messageId, reasoningDeltaState, reasoningState, sessionId } = params;\n const itemId = readString(params.params.itemId) ?? \"reasoning\";\n if (!reasoningState.has(itemId)) {\n reasoningState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningStart,\n payload: { sessionId, messageId },\n });\n }\n reasoningDeltaState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningDelta,\n payload: { sessionId, messageId, delta: readRawString(params.params.delta) ?? \"\" },\n });\n };\n\n private handleItemLifecycle = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n item: AppServerThreadItem;\n eventType: \"item/started\" | \"item/completed\";\n sessionId: string;\n messageId: string;\n textState: Set<string>;\n textDeltaState: Set<string>;\n reasoningState: Set<string>;\n reasoningDeltaState: Set<string>;\n toolState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const {\n eventType,\n item,\n messageId,\n reasoningDeltaState,\n reasoningState,\n sessionId,\n textDeltaState,\n textState,\n toolState,\n } = params;\n const itemId = readString(item.id) ?? createId(\"codex-item\");\n if (item.type === \"agentMessage\") {\n yield* this.handleAgentMessageItem({\n eventType,\n item,\n itemId,\n messageId,\n sessionId,\n textDeltaState,\n textState,\n });\n return;\n }\n if (item.type === \"reasoning\") {\n yield* this.handleReasoningItem({\n eventType,\n item,\n itemId,\n messageId,\n reasoningDeltaState,\n reasoningState,\n sessionId,\n });\n return;\n }\n if (!isAppServerToolLikeItem(item.type)) {\n return;\n }\n yield* this.handleToolItem({ eventType, item, itemId, messageId, sessionId, toolState });\n };\n\n private handleAgentMessageItem = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n item: AppServerThreadItem;\n itemId: string;\n eventType: \"item/started\" | \"item/completed\";\n sessionId: string;\n messageId: string;\n textState: Set<string>;\n textDeltaState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { eventType, item, itemId, messageId, sessionId, textDeltaState, textState } = params;\n if (!textState.has(itemId)) {\n textState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextStart,\n payload: { sessionId, messageId },\n });\n }\n const text = readRawString(item.text);\n if (text && !textDeltaState.has(itemId)) {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextDelta,\n payload: { sessionId, messageId, delta: text },\n });\n }\n if (eventType === \"item/completed\") {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageTextEnd,\n payload: { sessionId, messageId },\n });\n }\n };\n\n private handleReasoningItem = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n item: AppServerThreadItem;\n itemId: string;\n eventType: \"item/started\" | \"item/completed\";\n sessionId: string;\n messageId: string;\n reasoningState: Set<string>;\n reasoningDeltaState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { eventType, item, itemId, messageId, reasoningDeltaState, reasoningState, sessionId } =\n params;\n const reasoningText = readAppServerReasoningText(item);\n const alreadyStarted = reasoningState.has(itemId);\n if (!alreadyStarted && (eventType === \"item/started\" || reasoningText)) {\n reasoningState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningStart,\n payload: { sessionId, messageId },\n });\n }\n if (!reasoningDeltaState.has(itemId) && reasoningText) {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningDelta,\n payload: { sessionId, messageId, delta: reasoningText },\n });\n }\n if (eventType === \"item/completed\" && reasoningState.has(itemId)) {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageReasoningEnd,\n payload: { sessionId, messageId },\n });\n }\n };\n\n private handleToolItem = async function* (\n this: CodexAppServerNcpAgentRuntime,\n params: {\n item: AppServerThreadItem;\n itemId: string;\n eventType: \"item/started\" | \"item/completed\";\n sessionId: string;\n messageId: string;\n toolState: Set<string>;\n },\n ): AsyncGenerator<NcpEndpointEvent> {\n const { eventType, item, itemId, messageId, sessionId, toolState } = params;\n if (!toolState.has(itemId)) {\n toolState.add(itemId);\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageToolCallStart,\n payload: { sessionId, messageId, toolCallId: itemId, toolName: readAppServerToolName(item) },\n });\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageToolCallArgs,\n payload: {\n sessionId,\n toolCallId: itemId,\n args: stringifyAppServerToolArgs(readAppServerToolArgs(item)),\n },\n });\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageToolCallEnd,\n payload: { sessionId, toolCallId: itemId },\n });\n }\n if (eventType === \"item/completed\") {\n yield* this.eventEmitter.emitEvent({\n type: NcpEventType.MessageToolCallResult,\n payload: { sessionId, toolCallId: itemId, content: readAppServerToolResult(item) },\n });\n }\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 private syncDesktopThreadIndex = async (): Promise<void> => {\n try {\n await this.desktopThreadIndexSync?.syncThread({ threadId: this.threadId });\n } catch (error) {\n console.error(\n `[nextclaw-codex-app-server] failed to run Codex Desktop thread index sync: ${formatError(error)}`,\n );\n }\n };\n}\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction resolveDesktopThreadIndexSync(\n config: CodexAppServerNcpAgentRuntimeConfig,\n): CodexDesktopThreadIndexSync | null {\n if (config.desktopThreadIndexSync === false) {\n return null;\n }\n return config.desktopThreadIndexSync ?? new CodexDesktopThreadIndexSyncService({\n env: { ...process.env, ...config.env },\n });\n}\n\nfunction readString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction readRawString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction buildThreadModelScope(config: CodexAppServerNcpAgentRuntimeConfig): string {\n return readString(config.threadOptions?.model) ??\n readString(config.model) ??\n \"__nextclaw_runtime_default__\";\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 formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/services/codex-desktop-thread-index-sync.service.d.ts
|
|
2
|
+
type CodexDesktopStatement = {
|
|
3
|
+
all: (...params: unknown[]) => unknown[];
|
|
4
|
+
get: (...params: unknown[]) => unknown;
|
|
5
|
+
run: (...params: unknown[]) => unknown;
|
|
6
|
+
};
|
|
7
|
+
type CodexDesktopDatabase = {
|
|
8
|
+
close: () => void;
|
|
9
|
+
exec: (sql: string) => void;
|
|
10
|
+
prepare: (sql: string) => CodexDesktopStatement;
|
|
11
|
+
};
|
|
12
|
+
type CodexDesktopDatabaseSyncCtor = new (path: string) => CodexDesktopDatabase;
|
|
13
|
+
interface CodexDesktopThreadIndexSync {
|
|
14
|
+
syncThread(params: {
|
|
15
|
+
threadId?: string | null;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
interface CodexDesktopThreadIndexSyncServiceOptions {
|
|
19
|
+
databasePath?: string;
|
|
20
|
+
env?: NodeJS.ProcessEnv;
|
|
21
|
+
homeDirectory?: string;
|
|
22
|
+
loadDatabaseSync?: () => Promise<CodexDesktopDatabaseSyncCtor>;
|
|
23
|
+
logger?: Pick<Console, "error" | "warn">;
|
|
24
|
+
sessionsDirectory?: string;
|
|
25
|
+
}
|
|
26
|
+
declare class CodexDesktopThreadIndexSyncService implements CodexDesktopThreadIndexSync {
|
|
27
|
+
private readonly databasePath?;
|
|
28
|
+
private readonly env;
|
|
29
|
+
private readonly homeDirectory;
|
|
30
|
+
private readonly loadDatabaseSync;
|
|
31
|
+
private readonly logger;
|
|
32
|
+
private readonly sessionsDirectory?;
|
|
33
|
+
constructor(options?: CodexDesktopThreadIndexSyncServiceOptions);
|
|
34
|
+
syncThread: (params: {
|
|
35
|
+
threadId?: string | null;
|
|
36
|
+
}) => Promise<void>;
|
|
37
|
+
private syncThreadIndex;
|
|
38
|
+
private insertThreadRow;
|
|
39
|
+
private updateThreadRow;
|
|
40
|
+
private resolveRolloutPath;
|
|
41
|
+
private resolveDatabasePath;
|
|
42
|
+
private resolveSessionsDirectory;
|
|
43
|
+
private resolveCodexHome;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { CodexDesktopThreadIndexSync, CodexDesktopThreadIndexSyncService, CodexDesktopThreadIndexSyncServiceOptions };
|
|
47
|
+
//# sourceMappingURL=codex-desktop-thread-index-sync.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-desktop-thread-index-sync.service.d.ts","names":[],"sources":["../../src/services/codex-desktop-thread-index-sync.service.ts"],"mappings":";KA2CK,qBAAA;EACH,GAAA,MAAS,MAAA;EACT,GAAA,MAAS,MAAA;EACT,GAAA,MAAS,MAAA;AAAA;AAAA,KAGN,oBAAA;EACH,KAAA;EACA,IAAA,GAAO,GAAA;EACP,OAAA,GAAU,GAAA,aAAgB,qBAAA;AAAA;AAAA,KAGvB,4BAAA,QAAoC,IAAA,aAAiB,oBAAA;AAAA,UAUzC,2BAAA;EACf,UAAA,CAAW,MAAA;IAAU,QAAA;EAAA,IAA6B,OAAA;AAAA;AAAA,UAGnC,yCAAA;EACf,YAAA;EACA,GAAA,GAAM,MAAA,CAAO,UAAA;EACb,aAAA;EACA,gBAAA,SAAyB,OAAA,CAAQ,4BAAA;EACjC,MAAA,GAAS,IAAA,CAAK,OAAA;EACd,iBAAA;AAAA;AAAA,cAGW,kCAAA,YAA8C,2BAAA;EAAA,iBACxC,YAAA;EAAA,iBACA,GAAA;EAAA,iBACA,aAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,MAAA;EAAA,iBACA,iBAAA;cAEL,OAAA,GAAS,yCAAA;EAUrB,UAAA,GAAoB,MAAA;IAAU,QAAA;EAAA,MAA6B,OAAA;EAAA,QAiBnD,eAAA;EAAA,QA6CA,eAAA;EAAA,QA2DA,eAAA;EAAA,QA8CA,kBAAA;EAAA,QAcA,mBAAA;EAAA,QAOA,wBAAA;EAAA,QAOA,gBAAA;AAAA"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { findCodexRolloutPathForThreadId, readCodexRolloutSummary } from "../utils/codex-rollout-thread-summary.utils.js";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
//#region src/services/codex-desktop-thread-index-sync.service.ts
|
|
6
|
+
const THREAD_INDEX_SYNC_ENV = "NEXTCLAW_CODEX_DESKTOP_THREAD_INDEX_SYNC";
|
|
7
|
+
const CODEX_SQLITE_DATABASE = "state_5.sqlite";
|
|
8
|
+
const CODEX_SESSIONS_DIRECTORY = "sessions";
|
|
9
|
+
const REQUIRED_THREAD_COLUMNS = [
|
|
10
|
+
"id",
|
|
11
|
+
"rollout_path",
|
|
12
|
+
"updated_at",
|
|
13
|
+
"tokens_used",
|
|
14
|
+
"has_user_event"
|
|
15
|
+
];
|
|
16
|
+
const THREAD_INSERT_COLUMN_ORDER = [
|
|
17
|
+
"id",
|
|
18
|
+
"rollout_path",
|
|
19
|
+
"created_at",
|
|
20
|
+
"updated_at",
|
|
21
|
+
"source",
|
|
22
|
+
"model_provider",
|
|
23
|
+
"cwd",
|
|
24
|
+
"title",
|
|
25
|
+
"sandbox_policy",
|
|
26
|
+
"approval_mode",
|
|
27
|
+
"tokens_used",
|
|
28
|
+
"has_user_event",
|
|
29
|
+
"archived",
|
|
30
|
+
"cli_version",
|
|
31
|
+
"first_user_message",
|
|
32
|
+
"memory_mode",
|
|
33
|
+
"model",
|
|
34
|
+
"reasoning_effort",
|
|
35
|
+
"created_at_ms",
|
|
36
|
+
"updated_at_ms",
|
|
37
|
+
"preview"
|
|
38
|
+
];
|
|
39
|
+
var CodexDesktopThreadIndexSyncService = class {
|
|
40
|
+
databasePath;
|
|
41
|
+
env;
|
|
42
|
+
homeDirectory;
|
|
43
|
+
loadDatabaseSync;
|
|
44
|
+
logger;
|
|
45
|
+
sessionsDirectory;
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
this.databasePath = options.databasePath;
|
|
48
|
+
this.env = options.env ?? process.env;
|
|
49
|
+
this.homeDirectory = options.homeDirectory ?? homedir();
|
|
50
|
+
this.loadDatabaseSync = options.loadDatabaseSync ?? loadCodexDesktopDatabaseSync;
|
|
51
|
+
this.logger = options.logger ?? console;
|
|
52
|
+
this.sessionsDirectory = options.sessionsDirectory;
|
|
53
|
+
}
|
|
54
|
+
syncThread = async (params) => {
|
|
55
|
+
if (isDisabled(this.env[THREAD_INDEX_SYNC_ENV])) return;
|
|
56
|
+
const threadId = readString(params.threadId);
|
|
57
|
+
if (!threadId) return;
|
|
58
|
+
try {
|
|
59
|
+
await this.syncThreadIndex(threadId);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
this.logger.error(`[nextclaw-codex-app-server] failed to sync Codex Desktop thread index: ${formatError(error)}`);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
syncThreadIndex = async (threadId) => {
|
|
65
|
+
const databasePath = this.resolveDatabasePath();
|
|
66
|
+
if (!existsSync(databasePath)) return;
|
|
67
|
+
const database = new (await (this.loadDatabaseSync()))(databasePath);
|
|
68
|
+
try {
|
|
69
|
+
database.exec("PRAGMA busy_timeout = 100;");
|
|
70
|
+
const columns = readThreadTableColumns(database);
|
|
71
|
+
if (!hasRequiredThreadColumns(columns)) {
|
|
72
|
+
this.logger.warn("[nextclaw-codex-app-server] skipped Codex Desktop thread index sync: unknown threads schema.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const row = readThreadRow(database, threadId, columns);
|
|
76
|
+
const rolloutPath = this.resolveRolloutPath(row, threadId);
|
|
77
|
+
if (!rolloutPath) return;
|
|
78
|
+
const summary = readCodexRolloutSummary(rolloutPath);
|
|
79
|
+
if (!row) {
|
|
80
|
+
this.insertThreadRow({
|
|
81
|
+
columns,
|
|
82
|
+
database,
|
|
83
|
+
rolloutPath,
|
|
84
|
+
summary,
|
|
85
|
+
threadId
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.updateThreadRow({
|
|
90
|
+
columns,
|
|
91
|
+
database,
|
|
92
|
+
rolloutPath,
|
|
93
|
+
row,
|
|
94
|
+
summary,
|
|
95
|
+
threadId
|
|
96
|
+
});
|
|
97
|
+
} finally {
|
|
98
|
+
database.close();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
insertThreadRow = (params) => {
|
|
102
|
+
const { columns, database, rolloutPath, summary, threadId } = params;
|
|
103
|
+
const sessionMeta = summary.sessionMeta;
|
|
104
|
+
const cwd = readString(sessionMeta?.cwd);
|
|
105
|
+
const createdAtMs = summary.createdAtMs ?? sessionMeta?.timestampMs ?? summary.updatedAtMs;
|
|
106
|
+
const updatedAtMs = summary.updatedAtMs ?? createdAtMs;
|
|
107
|
+
if (!cwd || createdAtMs === void 0 || updatedAtMs === void 0) return;
|
|
108
|
+
const firstUserMessage = limitText(summary.firstUserMessage ?? threadId);
|
|
109
|
+
const valuesByColumn = {
|
|
110
|
+
approval_mode: readString(sessionMeta?.approvalMode) ?? "never",
|
|
111
|
+
archived: 0,
|
|
112
|
+
cli_version: readString(sessionMeta?.cliVersion) ?? "",
|
|
113
|
+
created_at: Math.floor(createdAtMs / 1e3),
|
|
114
|
+
created_at_ms: createdAtMs,
|
|
115
|
+
cwd,
|
|
116
|
+
first_user_message: firstUserMessage,
|
|
117
|
+
has_user_event: summary.hasUserEvent ? 1 : 0,
|
|
118
|
+
id: threadId,
|
|
119
|
+
memory_mode: readString(sessionMeta?.memoryMode) ?? "enabled",
|
|
120
|
+
model: readString(sessionMeta?.model) ?? null,
|
|
121
|
+
model_provider: readString(sessionMeta?.modelProvider) ?? "openai",
|
|
122
|
+
preview: firstUserMessage,
|
|
123
|
+
reasoning_effort: readString(sessionMeta?.reasoningEffort) ?? null,
|
|
124
|
+
rollout_path: rolloutPath,
|
|
125
|
+
sandbox_policy: readString(sessionMeta?.sandboxPolicy) ?? JSON.stringify({ type: "disabled" }),
|
|
126
|
+
source: readString(sessionMeta?.source) ?? "vscode",
|
|
127
|
+
title: firstUserMessage,
|
|
128
|
+
tokens_used: summary.tokensUsed ?? 0,
|
|
129
|
+
updated_at: Math.floor(updatedAtMs / 1e3),
|
|
130
|
+
updated_at_ms: updatedAtMs
|
|
131
|
+
};
|
|
132
|
+
const insertColumns = THREAD_INSERT_COLUMN_ORDER.filter((column) => columns.has(column));
|
|
133
|
+
if (!hasRequiredInsertValues(insertColumns, valuesByColumn)) return;
|
|
134
|
+
database.prepare(`
|
|
135
|
+
INSERT INTO threads (${insertColumns.join(", ")})
|
|
136
|
+
VALUES (${insertColumns.map(() => "?").join(", ")})
|
|
137
|
+
`).run(...insertColumns.map((column) => valuesByColumn[column]));
|
|
138
|
+
};
|
|
139
|
+
updateThreadRow = (params) => {
|
|
140
|
+
const updates = [];
|
|
141
|
+
const values = [];
|
|
142
|
+
const { columns, database, rolloutPath, row, summary, threadId } = params;
|
|
143
|
+
if (readString(row.rolloutPath) !== rolloutPath) {
|
|
144
|
+
updates.push("rollout_path = ?");
|
|
145
|
+
values.push(rolloutPath);
|
|
146
|
+
}
|
|
147
|
+
if (shouldUpdateTimestamp(row, summary)) {
|
|
148
|
+
const updatedAtMs = summary.updatedAtMs;
|
|
149
|
+
if (updatedAtMs !== void 0) {
|
|
150
|
+
updates.push("updated_at = ?");
|
|
151
|
+
values.push(Math.floor(updatedAtMs / 1e3));
|
|
152
|
+
if (columns.has("updated_at_ms")) {
|
|
153
|
+
updates.push("updated_at_ms = ?");
|
|
154
|
+
values.push(updatedAtMs);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (summary.tokensUsed !== void 0 && summary.tokensUsed > (readNumber(row.tokensUsed) ?? 0)) {
|
|
159
|
+
updates.push("tokens_used = ?");
|
|
160
|
+
values.push(summary.tokensUsed);
|
|
161
|
+
}
|
|
162
|
+
if (summary.hasUserEvent && (readNumber(row.hasUserEvent) ?? 0) !== 1) {
|
|
163
|
+
updates.push("has_user_event = ?");
|
|
164
|
+
values.push(1);
|
|
165
|
+
}
|
|
166
|
+
if (updates.length === 0) return;
|
|
167
|
+
database.prepare(`UPDATE threads SET ${updates.join(", ")} WHERE id = ?`).run(...values, threadId);
|
|
168
|
+
};
|
|
169
|
+
resolveRolloutPath = (row, threadId) => {
|
|
170
|
+
const rolloutPath = readString(row?.rolloutPath);
|
|
171
|
+
if (rolloutPath && isAbsolute(rolloutPath) && existsSync(rolloutPath)) return rolloutPath;
|
|
172
|
+
return findCodexRolloutPathForThreadId(this.resolveSessionsDirectory(), threadId);
|
|
173
|
+
};
|
|
174
|
+
resolveDatabasePath = () => {
|
|
175
|
+
if (this.databasePath) return this.databasePath;
|
|
176
|
+
return join(this.resolveCodexHome(), "sqlite", CODEX_SQLITE_DATABASE);
|
|
177
|
+
};
|
|
178
|
+
resolveSessionsDirectory = () => {
|
|
179
|
+
return this.sessionsDirectory ?? join(this.resolveCodexHome(), CODEX_SESSIONS_DIRECTORY);
|
|
180
|
+
};
|
|
181
|
+
resolveCodexHome = () => {
|
|
182
|
+
return readString(this.env.CODEX_HOME) ?? join(this.homeDirectory, ".codex");
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
async function loadCodexDesktopDatabaseSync() {
|
|
186
|
+
return (await import("node:sqlite")).DatabaseSync;
|
|
187
|
+
}
|
|
188
|
+
function readThreadTableColumns(database) {
|
|
189
|
+
const rows = database.prepare("PRAGMA table_info(threads)").all();
|
|
190
|
+
const columns = /* @__PURE__ */ new Set();
|
|
191
|
+
for (const row of rows) {
|
|
192
|
+
const column = readString(row.name);
|
|
193
|
+
if (column) columns.add(column);
|
|
194
|
+
}
|
|
195
|
+
return columns;
|
|
196
|
+
}
|
|
197
|
+
function hasRequiredThreadColumns(columns) {
|
|
198
|
+
return REQUIRED_THREAD_COLUMNS.every((column) => columns.has(column));
|
|
199
|
+
}
|
|
200
|
+
function readThreadRow(database, threadId, columns) {
|
|
201
|
+
const selectedColumns = [
|
|
202
|
+
"rollout_path AS rolloutPath",
|
|
203
|
+
"updated_at AS updatedAt",
|
|
204
|
+
"tokens_used AS tokensUsed",
|
|
205
|
+
"has_user_event AS hasUserEvent"
|
|
206
|
+
];
|
|
207
|
+
if (columns.has("updated_at_ms")) selectedColumns.push("updated_at_ms AS updatedAtMs");
|
|
208
|
+
const row = database.prepare(`
|
|
209
|
+
SELECT ${selectedColumns.join(", ")}
|
|
210
|
+
FROM threads
|
|
211
|
+
WHERE id = ?
|
|
212
|
+
`).get(threadId);
|
|
213
|
+
return isRecord(row) ? row : void 0;
|
|
214
|
+
}
|
|
215
|
+
function shouldUpdateTimestamp(row, summary) {
|
|
216
|
+
const nextUpdatedAtMs = summary.updatedAtMs;
|
|
217
|
+
if (nextUpdatedAtMs === void 0) return false;
|
|
218
|
+
const currentUpdatedAtMs = readNumber(row.updatedAtMs);
|
|
219
|
+
if (currentUpdatedAtMs !== void 0) return nextUpdatedAtMs > currentUpdatedAtMs;
|
|
220
|
+
return Math.floor(nextUpdatedAtMs / 1e3) > (readNumber(row.updatedAt) ?? 0);
|
|
221
|
+
}
|
|
222
|
+
function hasRequiredInsertValues(columns, valuesByColumn) {
|
|
223
|
+
return REQUIRED_THREAD_COLUMNS.every((column) => columns.includes(column)) && [
|
|
224
|
+
"created_at",
|
|
225
|
+
"source",
|
|
226
|
+
"model_provider",
|
|
227
|
+
"cwd",
|
|
228
|
+
"title"
|
|
229
|
+
].every((column) => columns.includes(column) && valuesByColumn[column] != null);
|
|
230
|
+
}
|
|
231
|
+
function limitText(value) {
|
|
232
|
+
const normalized = value.trim().replace(/\s+/g, " ");
|
|
233
|
+
return normalized.length > 2e3 ? `${normalized.slice(0, 2e3).trimEnd()}...` : normalized;
|
|
234
|
+
}
|
|
235
|
+
function readNumber(value, fallback) {
|
|
236
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
237
|
+
}
|
|
238
|
+
function readString(value) {
|
|
239
|
+
if (typeof value !== "string") return;
|
|
240
|
+
const trimmed = value.trim();
|
|
241
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
242
|
+
}
|
|
243
|
+
function isDisabled(value) {
|
|
244
|
+
const normalized = readString(value)?.toLowerCase();
|
|
245
|
+
return normalized === "0" || normalized === "false" || normalized === "off";
|
|
246
|
+
}
|
|
247
|
+
function isRecord(value) {
|
|
248
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
249
|
+
}
|
|
250
|
+
function formatError(error) {
|
|
251
|
+
return error instanceof Error ? error.message : String(error);
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
export { CodexDesktopThreadIndexSyncService };
|
|
255
|
+
|
|
256
|
+
//# sourceMappingURL=codex-desktop-thread-index-sync.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-desktop-thread-index-sync.service.js","names":[],"sources":["../../src/services/codex-desktop-thread-index-sync.service.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join } from \"node:path\";\nimport {\n findCodexRolloutPathForThreadId,\n readCodexRolloutSummary,\n type CodexRolloutSummary,\n} from \"@/utils/codex-rollout-thread-summary.utils.js\";\n\nconst THREAD_INDEX_SYNC_ENV = \"NEXTCLAW_CODEX_DESKTOP_THREAD_INDEX_SYNC\";\nconst CODEX_SQLITE_DATABASE = \"state_5.sqlite\";\nconst CODEX_SESSIONS_DIRECTORY = \"sessions\";\nconst REQUIRED_THREAD_COLUMNS = [\n \"id\",\n \"rollout_path\",\n \"updated_at\",\n \"tokens_used\",\n \"has_user_event\",\n] as const;\nconst THREAD_INSERT_COLUMN_ORDER = [\n \"id\",\n \"rollout_path\",\n \"created_at\",\n \"updated_at\",\n \"source\",\n \"model_provider\",\n \"cwd\",\n \"title\",\n \"sandbox_policy\",\n \"approval_mode\",\n \"tokens_used\",\n \"has_user_event\",\n \"archived\",\n \"cli_version\",\n \"first_user_message\",\n \"memory_mode\",\n \"model\",\n \"reasoning_effort\",\n \"created_at_ms\",\n \"updated_at_ms\",\n \"preview\",\n] as const;\n\ntype CodexDesktopStatement = {\n all: (...params: unknown[]) => unknown[];\n get: (...params: unknown[]) => unknown;\n run: (...params: unknown[]) => unknown;\n};\n\ntype CodexDesktopDatabase = {\n close: () => void;\n exec: (sql: string) => void;\n prepare: (sql: string) => CodexDesktopStatement;\n};\n\ntype CodexDesktopDatabaseSyncCtor = new (path: string) => CodexDesktopDatabase;\n\ntype CodexDesktopThreadRow = {\n hasUserEvent?: number;\n rolloutPath?: string;\n tokensUsed?: number;\n updatedAt?: number;\n updatedAtMs?: number | null;\n};\n\nexport interface CodexDesktopThreadIndexSync {\n syncThread(params: { threadId?: string | null }): Promise<void>;\n}\n\nexport interface CodexDesktopThreadIndexSyncServiceOptions {\n databasePath?: string;\n env?: NodeJS.ProcessEnv;\n homeDirectory?: string;\n loadDatabaseSync?: () => Promise<CodexDesktopDatabaseSyncCtor>;\n logger?: Pick<Console, \"error\" | \"warn\">;\n sessionsDirectory?: string;\n}\n\nexport class CodexDesktopThreadIndexSyncService implements CodexDesktopThreadIndexSync {\n private readonly databasePath?: string;\n private readonly env: NodeJS.ProcessEnv;\n private readonly homeDirectory: string;\n private readonly loadDatabaseSync: () => Promise<CodexDesktopDatabaseSyncCtor>;\n private readonly logger: Pick<Console, \"error\" | \"warn\">;\n private readonly sessionsDirectory?: string;\n\n constructor(options: CodexDesktopThreadIndexSyncServiceOptions = {}) {\n this.databasePath = options.databasePath;\n this.env = options.env ?? process.env;\n this.homeDirectory = options.homeDirectory ?? homedir();\n this.loadDatabaseSync =\n options.loadDatabaseSync ?? loadCodexDesktopDatabaseSync;\n this.logger = options.logger ?? console;\n this.sessionsDirectory = options.sessionsDirectory;\n }\n\n syncThread = async (params: { threadId?: string | null }): Promise<void> => {\n if (isDisabled(this.env[THREAD_INDEX_SYNC_ENV])) {\n return;\n }\n const threadId = readString(params.threadId);\n if (!threadId) {\n return;\n }\n try {\n await this.syncThreadIndex(threadId);\n } catch (error) {\n this.logger.error(\n `[nextclaw-codex-app-server] failed to sync Codex Desktop thread index: ${formatError(error)}`,\n );\n }\n };\n\n private syncThreadIndex = async (threadId: string): Promise<void> => {\n const databasePath = this.resolveDatabasePath();\n if (!existsSync(databasePath)) {\n return;\n }\n const DatabaseSync = await this.loadDatabaseSync();\n const database = new DatabaseSync(databasePath);\n try {\n database.exec(\"PRAGMA busy_timeout = 100;\");\n const columns = readThreadTableColumns(database);\n if (!hasRequiredThreadColumns(columns)) {\n this.logger.warn(\n \"[nextclaw-codex-app-server] skipped Codex Desktop thread index sync: unknown threads schema.\",\n );\n return;\n }\n const row = readThreadRow(database, threadId, columns);\n const rolloutPath = this.resolveRolloutPath(row, threadId);\n if (!rolloutPath) {\n return;\n }\n const summary = readCodexRolloutSummary(rolloutPath);\n if (!row) {\n this.insertThreadRow({\n columns,\n database,\n rolloutPath,\n summary,\n threadId,\n });\n return;\n }\n this.updateThreadRow({\n columns,\n database,\n rolloutPath,\n row,\n summary,\n threadId,\n });\n } finally {\n database.close();\n }\n };\n\n private insertThreadRow = (params: {\n columns: Set<string>;\n database: CodexDesktopDatabase;\n rolloutPath: string;\n summary: CodexRolloutSummary;\n threadId: string;\n }): void => {\n const { columns, database, rolloutPath, summary, threadId } = params;\n const sessionMeta = summary.sessionMeta;\n const cwd = readString(sessionMeta?.cwd);\n const createdAtMs =\n summary.createdAtMs ?? sessionMeta?.timestampMs ?? summary.updatedAtMs;\n const updatedAtMs = summary.updatedAtMs ?? createdAtMs;\n if (!cwd || createdAtMs === undefined || updatedAtMs === undefined) {\n return;\n }\n\n const firstUserMessage = limitText(summary.firstUserMessage ?? threadId);\n const valuesByColumn: Record<string, unknown> = {\n approval_mode: readString(sessionMeta?.approvalMode) ?? \"never\",\n archived: 0,\n cli_version: readString(sessionMeta?.cliVersion) ?? \"\",\n created_at: Math.floor(createdAtMs / 1000),\n created_at_ms: createdAtMs,\n cwd,\n first_user_message: firstUserMessage,\n has_user_event: summary.hasUserEvent ? 1 : 0,\n id: threadId,\n memory_mode: readString(sessionMeta?.memoryMode) ?? \"enabled\",\n model: readString(sessionMeta?.model) ?? null,\n model_provider: readString(sessionMeta?.modelProvider) ?? \"openai\",\n preview: firstUserMessage,\n reasoning_effort: readString(sessionMeta?.reasoningEffort) ?? null,\n rollout_path: rolloutPath,\n sandbox_policy:\n readString(sessionMeta?.sandboxPolicy) ??\n JSON.stringify({ type: \"disabled\" }),\n source: readString(sessionMeta?.source) ?? \"vscode\",\n title: firstUserMessage,\n tokens_used: summary.tokensUsed ?? 0,\n updated_at: Math.floor(updatedAtMs / 1000),\n updated_at_ms: updatedAtMs,\n };\n const insertColumns = THREAD_INSERT_COLUMN_ORDER.filter((column) =>\n columns.has(column),\n );\n if (!hasRequiredInsertValues(insertColumns, valuesByColumn)) {\n return;\n }\n database\n .prepare(\n `\n INSERT INTO threads (${insertColumns.join(\", \")})\n VALUES (${insertColumns.map(() => \"?\").join(\", \")})\n `,\n )\n .run(...insertColumns.map((column) => valuesByColumn[column]));\n };\n\n private updateThreadRow = (params: {\n columns: Set<string>;\n database: CodexDesktopDatabase;\n rolloutPath: string;\n row: CodexDesktopThreadRow;\n summary: CodexRolloutSummary;\n threadId: string;\n }): void => {\n const updates: string[] = [];\n const values: unknown[] = [];\n const { columns, database, rolloutPath, row, summary, threadId } = params;\n\n if (readString(row.rolloutPath) !== rolloutPath) {\n updates.push(\"rollout_path = ?\");\n values.push(rolloutPath);\n }\n if (shouldUpdateTimestamp(row, summary)) {\n const updatedAtMs = summary.updatedAtMs;\n if (updatedAtMs !== undefined) {\n updates.push(\"updated_at = ?\");\n values.push(Math.floor(updatedAtMs / 1000));\n if (columns.has(\"updated_at_ms\")) {\n updates.push(\"updated_at_ms = ?\");\n values.push(updatedAtMs);\n }\n }\n }\n if (\n summary.tokensUsed !== undefined &&\n summary.tokensUsed > (readNumber(row.tokensUsed) ?? 0)\n ) {\n updates.push(\"tokens_used = ?\");\n values.push(summary.tokensUsed);\n }\n if (summary.hasUserEvent && (readNumber(row.hasUserEvent) ?? 0) !== 1) {\n updates.push(\"has_user_event = ?\");\n values.push(1);\n }\n if (updates.length === 0) {\n return;\n }\n database\n .prepare(`UPDATE threads SET ${updates.join(\", \")} WHERE id = ?`)\n .run(...values, threadId);\n };\n\n private resolveRolloutPath = (\n row: CodexDesktopThreadRow | undefined,\n threadId: string,\n ): string | undefined => {\n const rolloutPath = readString(row?.rolloutPath);\n if (rolloutPath && isAbsolute(rolloutPath) && existsSync(rolloutPath)) {\n return rolloutPath;\n }\n return findCodexRolloutPathForThreadId(\n this.resolveSessionsDirectory(),\n threadId,\n );\n };\n\n private resolveDatabasePath = (): string => {\n if (this.databasePath) {\n return this.databasePath;\n }\n return join(this.resolveCodexHome(), \"sqlite\", CODEX_SQLITE_DATABASE);\n };\n\n private resolveSessionsDirectory = (): string => {\n return (\n this.sessionsDirectory ??\n join(this.resolveCodexHome(), CODEX_SESSIONS_DIRECTORY)\n );\n };\n\n private resolveCodexHome = (): string => {\n return readString(this.env.CODEX_HOME) ?? join(this.homeDirectory, \".codex\");\n };\n}\n\nasync function loadCodexDesktopDatabaseSync(): Promise<CodexDesktopDatabaseSyncCtor> {\n const moduleName = \"node:sqlite\";\n const module = (await import(moduleName)) as {\n DatabaseSync: CodexDesktopDatabaseSyncCtor;\n };\n return module.DatabaseSync;\n}\n\nfunction readThreadTableColumns(database: CodexDesktopDatabase): Set<string> {\n const rows = database.prepare(\"PRAGMA table_info(threads)\").all();\n const columns = new Set<string>();\n for (const row of rows) {\n const column = readString((row as { name?: unknown }).name);\n if (column) {\n columns.add(column);\n }\n }\n return columns;\n}\n\nfunction hasRequiredThreadColumns(columns: Set<string>): boolean {\n return REQUIRED_THREAD_COLUMNS.every((column) => columns.has(column));\n}\n\nfunction readThreadRow(\n database: CodexDesktopDatabase,\n threadId: string,\n columns: Set<string>,\n): CodexDesktopThreadRow | undefined {\n const selectedColumns = [\n \"rollout_path AS rolloutPath\",\n \"updated_at AS updatedAt\",\n \"tokens_used AS tokensUsed\",\n \"has_user_event AS hasUserEvent\",\n ];\n if (columns.has(\"updated_at_ms\")) {\n selectedColumns.push(\"updated_at_ms AS updatedAtMs\");\n }\n const row = database\n .prepare(\n `\n SELECT ${selectedColumns.join(\", \")}\n FROM threads\n WHERE id = ?\n `,\n )\n .get(threadId);\n return isRecord(row) ? (row as CodexDesktopThreadRow) : undefined;\n}\n\nfunction shouldUpdateTimestamp(\n row: CodexDesktopThreadRow,\n summary: CodexRolloutSummary,\n): boolean {\n const nextUpdatedAtMs = summary.updatedAtMs;\n if (nextUpdatedAtMs === undefined) {\n return false;\n }\n const currentUpdatedAtMs = readNumber(row.updatedAtMs);\n if (currentUpdatedAtMs !== undefined) {\n return nextUpdatedAtMs > currentUpdatedAtMs;\n }\n return Math.floor(nextUpdatedAtMs / 1000) > (readNumber(row.updatedAt) ?? 0);\n}\n\nfunction hasRequiredInsertValues(\n columns: readonly string[],\n valuesByColumn: Record<string, unknown>,\n): boolean {\n return REQUIRED_THREAD_COLUMNS.every((column) => columns.includes(column)) &&\n [\"created_at\", \"source\", \"model_provider\", \"cwd\", \"title\"].every(\n (column) => columns.includes(column) && valuesByColumn[column] != null,\n );\n}\n\nfunction limitText(value: string): string {\n const normalized = value.trim().replace(/\\s+/g, \" \");\n return normalized.length > 2_000\n ? `${normalized.slice(0, 2_000).trimEnd()}...`\n : normalized;\n}\n\nfunction readNumber(value: unknown, fallback?: number): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : fallback;\n}\n\nfunction readString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction isDisabled(value: string | undefined): boolean {\n const normalized = readString(value)?.toLowerCase();\n return normalized === \"0\" || normalized === \"false\" || normalized === \"off\";\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CodexAssetContentPathResolver, CodexThreadInput } from "../codex-input.utils.js";
|
|
2
|
+
import { CodexDesktopThreadIndexSync } from "../services/codex-desktop-thread-index-sync.service.js";
|
|
2
3
|
import { NcpAgentConversationStateManager, NcpAgentRunInput } from "@nextclaw/ncp";
|
|
3
4
|
import { CodexOptions, ThreadOptions } from "@openai/codex-sdk";
|
|
4
5
|
|
|
@@ -18,6 +19,7 @@ type CodexAppServerNcpAgentRuntimeConfig = {
|
|
|
18
19
|
inputBuilder?: (input: NcpAgentRunInput) => Promise<CodexThreadInput> | CodexThreadInput;
|
|
19
20
|
resolveAssetContentPath?: CodexAssetContentPathResolver;
|
|
20
21
|
stateManager?: NcpAgentConversationStateManager;
|
|
22
|
+
desktopThreadIndexSync?: CodexDesktopThreadIndexSync | false;
|
|
21
23
|
};
|
|
22
24
|
//#endregion
|
|
23
25
|
export { CodexAppServerNcpAgentRuntimeConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-app-server-runtime.types.d.ts","names":[],"sources":["../../src/types/codex-app-server-runtime.types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"codex-app-server-runtime.types.d.ts","names":[],"sources":["../../src/types/codex-app-server-runtime.types.ts"],"mappings":";;;;;;KA0BY,mCAAA;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,uBAAA,GAA0B,6BAAA;EAC1B,YAAA,GAAe,gCAAA;EACf,sBAAA,GAAyB,2BAAA;AAAA"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
//#region src/utils/codex-rollout-thread-summary.utils.ts
|
|
4
|
+
function readCodexRolloutSummary(rolloutPath) {
|
|
5
|
+
let createdAtMs;
|
|
6
|
+
let firstEventUserMessage;
|
|
7
|
+
let firstResponseUserMessage;
|
|
8
|
+
let hasUserEvent = false;
|
|
9
|
+
let sessionMeta;
|
|
10
|
+
let tokensUsed;
|
|
11
|
+
let updatedAtMs;
|
|
12
|
+
for (const line of readFileSync(rolloutPath, "utf8").split(/\r?\n/)) {
|
|
13
|
+
const entry = readRolloutEntry(line);
|
|
14
|
+
if (!entry) continue;
|
|
15
|
+
const timestampMs = readTimestampMs(entry.timestamp);
|
|
16
|
+
createdAtMs ??= timestampMs;
|
|
17
|
+
updatedAtMs = timestampMs ?? updatedAtMs;
|
|
18
|
+
const payload = isRecord(entry.payload) ? entry.payload : void 0;
|
|
19
|
+
if (entry.type === "session_meta" && isRecord(payload)) {
|
|
20
|
+
sessionMeta = readRolloutSessionMeta(payload);
|
|
21
|
+
createdAtMs ??= sessionMeta.timestampMs;
|
|
22
|
+
updatedAtMs ??= sessionMeta.timestampMs;
|
|
23
|
+
}
|
|
24
|
+
if (entry.type === "event_msg" && isRecord(payload)) {
|
|
25
|
+
hasUserEvent = hasUserEvent || payload.type === "user_message";
|
|
26
|
+
firstEventUserMessage ??= payload.type === "user_message" ? readString(payload.message) : void 0;
|
|
27
|
+
tokensUsed = payload.type === "token_count" ? readTokenCount(payload) ?? tokensUsed : tokensUsed;
|
|
28
|
+
}
|
|
29
|
+
if (isResponseUserMessage(entry, payload)) {
|
|
30
|
+
hasUserEvent = true;
|
|
31
|
+
firstResponseUserMessage ??= readResponseUserMessage(payload);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
createdAtMs,
|
|
36
|
+
firstUserMessage: firstEventUserMessage ?? firstResponseUserMessage,
|
|
37
|
+
hasUserEvent,
|
|
38
|
+
sessionMeta,
|
|
39
|
+
tokensUsed,
|
|
40
|
+
updatedAtMs
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function isResponseUserMessage(entry, payload) {
|
|
44
|
+
return entry.type === "response_item" && isRecord(payload) && payload.role === "user";
|
|
45
|
+
}
|
|
46
|
+
function findCodexRolloutPathForThreadId(sessionsDirectory, threadId) {
|
|
47
|
+
if (!existsSync(sessionsDirectory)) return;
|
|
48
|
+
const directories = [sessionsDirectory];
|
|
49
|
+
while (directories.length > 0) {
|
|
50
|
+
const directory = directories.pop();
|
|
51
|
+
if (!directory) continue;
|
|
52
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
53
|
+
const entryPath = join(directory, entry.name);
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
directories.push(entryPath);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl") && entry.name.includes(threadId)) return entryPath;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function readRolloutEntry(line) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
if (!trimmed) return;
|
|
65
|
+
const entry = JSON.parse(trimmed);
|
|
66
|
+
return isRecord(entry) ? entry : void 0;
|
|
67
|
+
}
|
|
68
|
+
function readRolloutSessionMeta(payload) {
|
|
69
|
+
return {
|
|
70
|
+
approvalMode: readString(payload.approval_mode),
|
|
71
|
+
cliVersion: readString(payload.cli_version),
|
|
72
|
+
cwd: readString(payload.cwd),
|
|
73
|
+
memoryMode: readString(payload.memory_mode),
|
|
74
|
+
model: readString(payload.model),
|
|
75
|
+
modelProvider: readString(payload.model_provider),
|
|
76
|
+
reasoningEffort: readString(payload.reasoning_effort),
|
|
77
|
+
sandboxPolicy: readStringOrJson(payload.sandbox_policy),
|
|
78
|
+
source: readString(payload.source),
|
|
79
|
+
timestampMs: readTimestampMs(payload.timestamp)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function readResponseUserMessage(payload) {
|
|
83
|
+
const content = Array.isArray(payload.content) ? payload.content : void 0;
|
|
84
|
+
const parts = [];
|
|
85
|
+
for (const item of content ?? []) {
|
|
86
|
+
if (!isRecord(item)) continue;
|
|
87
|
+
const text = readString(item.text);
|
|
88
|
+
if (text) parts.push(text);
|
|
89
|
+
}
|
|
90
|
+
return readString(parts.join("\n"));
|
|
91
|
+
}
|
|
92
|
+
function readTokenCount(payload) {
|
|
93
|
+
const info = isRecord(payload.info) ? payload.info : void 0;
|
|
94
|
+
return readNumber((isRecord(info?.total_token_usage) ? info.total_token_usage : void 0)?.total_tokens);
|
|
95
|
+
}
|
|
96
|
+
function readStringOrJson(value) {
|
|
97
|
+
const stringValue = readString(value);
|
|
98
|
+
if (stringValue) return stringValue;
|
|
99
|
+
return isRecord(value) ? JSON.stringify(value) : void 0;
|
|
100
|
+
}
|
|
101
|
+
function readTimestampMs(value) {
|
|
102
|
+
const timestamp = readString(value);
|
|
103
|
+
if (!timestamp) return;
|
|
104
|
+
const timestampMs = Date.parse(timestamp);
|
|
105
|
+
return Number.isFinite(timestampMs) ? timestampMs : void 0;
|
|
106
|
+
}
|
|
107
|
+
function readNumber(value) {
|
|
108
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
109
|
+
}
|
|
110
|
+
function readString(value) {
|
|
111
|
+
if (typeof value !== "string") return;
|
|
112
|
+
const trimmed = value.trim();
|
|
113
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
114
|
+
}
|
|
115
|
+
function isRecord(value) {
|
|
116
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
export { findCodexRolloutPathForThreadId, readCodexRolloutSummary };
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=codex-rollout-thread-summary.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-rollout-thread-summary.utils.js","names":[],"sources":["../../src/utils/codex-rollout-thread-summary.utils.ts"],"sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type CodexRolloutSessionMeta = {\n approvalMode?: string;\n cliVersion?: string;\n cwd?: string;\n memoryMode?: string;\n model?: string;\n modelProvider?: string;\n reasoningEffort?: string;\n sandboxPolicy?: string;\n source?: string;\n timestampMs?: number;\n};\n\nexport type CodexRolloutSummary = {\n createdAtMs?: number;\n firstUserMessage?: string;\n hasUserEvent: boolean;\n sessionMeta?: CodexRolloutSessionMeta;\n tokensUsed?: number;\n updatedAtMs?: number;\n};\n\nexport function readCodexRolloutSummary(\n rolloutPath: string,\n): CodexRolloutSummary {\n let createdAtMs: number | undefined;\n let firstEventUserMessage: string | undefined;\n let firstResponseUserMessage: string | undefined;\n let hasUserEvent = false;\n let sessionMeta: CodexRolloutSessionMeta | undefined;\n let tokensUsed: number | undefined;\n let updatedAtMs: number | undefined;\n for (const line of readFileSync(rolloutPath, \"utf8\").split(/\\r?\\n/)) {\n const entry = readRolloutEntry(line);\n if (!entry) {\n continue;\n }\n const timestampMs = readTimestampMs(entry.timestamp);\n createdAtMs ??= timestampMs;\n updatedAtMs = timestampMs ?? updatedAtMs;\n const payload = isRecord(entry.payload) ? entry.payload : undefined;\n if (entry.type === \"session_meta\" && isRecord(payload)) {\n sessionMeta = readRolloutSessionMeta(payload);\n createdAtMs ??= sessionMeta.timestampMs;\n updatedAtMs ??= sessionMeta.timestampMs;\n }\n if (entry.type === \"event_msg\" && isRecord(payload)) {\n hasUserEvent = hasUserEvent || payload.type === \"user_message\";\n firstEventUserMessage ??=\n payload.type === \"user_message\"\n ? readString(payload.message)\n : undefined;\n tokensUsed =\n payload.type === \"token_count\"\n ? readTokenCount(payload) ?? tokensUsed\n : tokensUsed;\n }\n if (isResponseUserMessage(entry, payload)) {\n hasUserEvent = true;\n firstResponseUserMessage ??= readResponseUserMessage(payload);\n }\n }\n return {\n createdAtMs,\n firstUserMessage: firstEventUserMessage ?? firstResponseUserMessage,\n hasUserEvent,\n sessionMeta,\n tokensUsed,\n updatedAtMs,\n };\n}\n\nfunction isResponseUserMessage(\n entry: Record<string, unknown>,\n payload: Record<string, unknown> | undefined,\n): payload is Record<string, unknown> {\n return (\n entry.type === \"response_item\" &&\n isRecord(payload) &&\n payload.role === \"user\"\n );\n}\n\nexport function findCodexRolloutPathForThreadId(\n sessionsDirectory: string,\n threadId: string,\n): string | undefined {\n if (!existsSync(sessionsDirectory)) {\n return undefined;\n }\n const directories = [sessionsDirectory];\n while (directories.length > 0) {\n const directory = directories.pop();\n if (!directory) {\n continue;\n }\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n const entryPath = join(directory, entry.name);\n if (entry.isDirectory()) {\n directories.push(entryPath);\n continue;\n }\n if (\n entry.isFile() &&\n entry.name.endsWith(\".jsonl\") &&\n entry.name.includes(threadId)\n ) {\n return entryPath;\n }\n }\n }\n return undefined;\n}\n\nfunction readRolloutEntry(line: string): Record<string, unknown> | undefined {\n const trimmed = line.trim();\n if (!trimmed) {\n return undefined;\n }\n const entry = JSON.parse(trimmed) as unknown;\n return isRecord(entry) ? entry : undefined;\n}\n\nfunction readRolloutSessionMeta(\n payload: Record<string, unknown>,\n): CodexRolloutSessionMeta {\n return {\n approvalMode: readString(payload.approval_mode),\n cliVersion: readString(payload.cli_version),\n cwd: readString(payload.cwd),\n memoryMode: readString(payload.memory_mode),\n model: readString(payload.model),\n modelProvider: readString(payload.model_provider),\n reasoningEffort: readString(payload.reasoning_effort),\n sandboxPolicy: readStringOrJson(payload.sandbox_policy),\n source: readString(payload.source),\n timestampMs: readTimestampMs(payload.timestamp),\n };\n}\n\nfunction readResponseUserMessage(\n payload: Record<string, unknown>,\n): string | undefined {\n const content = Array.isArray(payload.content) ? payload.content : undefined;\n const parts: string[] = [];\n for (const item of content ?? []) {\n if (!isRecord(item)) {\n continue;\n }\n const text = readString(item.text);\n if (text) {\n parts.push(text);\n }\n }\n return readString(parts.join(\"\\n\"));\n}\n\nfunction readTokenCount(payload: Record<string, unknown>): number | undefined {\n const info = isRecord(payload.info) ? payload.info : undefined;\n const usage = isRecord(info?.total_token_usage)\n ? info.total_token_usage\n : undefined;\n return readNumber(usage?.total_tokens);\n}\n\nfunction readStringOrJson(value: unknown): string | undefined {\n const stringValue = readString(value);\n if (stringValue) {\n return stringValue;\n }\n return isRecord(value) ? JSON.stringify(value) : undefined;\n}\n\nfunction readTimestampMs(value: unknown): number | undefined {\n const timestamp = readString(value);\n if (!timestamp) {\n return undefined;\n }\n const timestampMs = Date.parse(timestamp);\n return Number.isFinite(timestampMs) ? timestampMs : undefined;\n}\n\nfunction readNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction readString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/nextclaw-ncp-runtime-codex-sdk",
|
|
3
|
-
"version": "0.1.55
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Optional NCP runtime adapter backed by OpenAI Codex SDK.",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@openai/codex-sdk": "^0.139.0",
|
|
21
|
-
"@nextclaw/ncp": "0.6.4
|
|
21
|
+
"@nextclaw/ncp": "0.6.4"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^20.17.6",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsdown src/index.ts --dts.sourcemap --clean --target es2022 --no-fixedExtension --unbundle",
|
|
30
30
|
"lint": "eslint .",
|
|
31
|
-
"test": "vitest run src/services/codex-sdk-runtime-thread-metadata.service.test.ts src/utils/codex-model-provider.utils.test.ts src/utils/codex-sdk-ncp-event-mapper.utils.test.ts src/services/codex-live-output-stream.service.test.ts src/utils/codex-openai-sse-chunks.utils.test.ts src/utils/codex-openai-responses-stream-writer.utils.test.ts",
|
|
31
|
+
"test": "vitest run src/services/codex-sdk-runtime-thread-metadata.service.test.ts src/services/codex-desktop-thread-index-sync.service.test.ts src/utils/codex-model-provider.utils.test.ts src/utils/codex-sdk-ncp-event-mapper.utils.test.ts src/services/codex-live-output-stream.service.test.ts src/utils/codex-openai-sse-chunks.utils.test.ts src/utils/codex-openai-responses-stream-writer.utils.test.ts",
|
|
32
32
|
"tsc": "tsc -p tsconfig.json"
|
|
33
33
|
}
|
|
34
34
|
}
|