@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk 0.1.14 → 0.1.16

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.js CHANGED
@@ -1,20 +1,44 @@
1
1
  import {
2
2
  findProviderByModel,
3
3
  findProviderByName,
4
- buildRequestedSkillsUserPrompt,
5
4
  resolveProviderRuntime,
6
- getWorkspacePath,
7
- SkillsLoader
5
+ getWorkspacePath
8
6
  } from "@nextclaw/core";
9
7
  import {
10
8
  CodexSdkNcpAgentRuntime
11
9
  } from "@nextclaw/nextclaw-ncp-runtime-codex-sdk";
12
10
  import {
13
11
  buildUserFacingModelRoute,
12
+ buildCodexBridgeModelProviderId,
14
13
  resolveExternalModelProvider
15
14
  } from "./codex-model-provider.js";
15
+ import { buildCodexInputBuilder } from "./codex-input-builder.js";
16
+ import { ensureCodexOpenAiResponsesBridge } from "./codex-openai-responses-bridge.js";
17
+ import { resolveCodexResponsesApiSupport } from "./codex-responses-capability.js";
18
+ import {
19
+ createDescribeCodexSessionType
20
+ } from "./codex-session-type.js";
16
21
  const PLUGIN_ID = "nextclaw-ncp-runtime-plugin-codex-sdk";
17
22
  const CODEX_RUNTIME_KIND = "codex";
23
+ class DeferredCodexSdkNcpAgentRuntime {
24
+ constructor(createRuntime) {
25
+ this.createRuntime = createRuntime;
26
+ }
27
+ runtimePromise = null;
28
+ run(input, options) {
29
+ const resolveRuntime = async () => {
30
+ if (!this.runtimePromise) {
31
+ this.runtimePromise = this.createRuntime();
32
+ }
33
+ return await this.runtimePromise;
34
+ };
35
+ const stream = async function* () {
36
+ const runtime = await resolveRuntime();
37
+ yield* runtime.run(input, options);
38
+ }.bind(this);
39
+ return stream();
40
+ }
41
+ }
18
42
  function readString(value) {
19
43
  if (typeof value !== "string") {
20
44
  return void 0;
@@ -82,7 +106,7 @@ function resolveCodexExecutionOptions(params) {
82
106
  }
83
107
  function resolveCodexCliConfig(params) {
84
108
  const explicitConfig = readRecord(params.pluginConfig.config);
85
- const modelProvider = resolveExternalModelProvider({
109
+ const modelProvider = readString(params.modelProviderOverride) ?? resolveExternalModelProvider({
86
110
  explicitModelProvider: params.pluginConfig.modelProvider,
87
111
  providerName: params.providerName,
88
112
  providerDisplayName: params.providerDisplayName,
@@ -109,35 +133,6 @@ function resolveCodexCliConfig(params) {
109
133
  ...explicitConfig ?? {}
110
134
  };
111
135
  }
112
- function readRequestedSkills(metadata) {
113
- const raw = metadata.requested_skills ?? metadata.requestedSkills;
114
- if (!Array.isArray(raw)) {
115
- return [];
116
- }
117
- return raw.map((entry) => readString(entry)).filter((entry) => Boolean(entry)).slice(0, 8);
118
- }
119
- function readUserText(input) {
120
- for (let index = input.messages.length - 1; index >= 0; index -= 1) {
121
- const message = input.messages[index];
122
- if (message?.role !== "user") {
123
- continue;
124
- }
125
- const text = message.parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
126
- if (text) {
127
- return text;
128
- }
129
- }
130
- return "";
131
- }
132
- function buildCodexInputBuilder(workspace) {
133
- const skillsLoader = new SkillsLoader(workspace);
134
- return async (input) => {
135
- const userText = readUserText(input);
136
- const metadata = input.metadata && typeof input.metadata === "object" && !Array.isArray(input.metadata) ? input.metadata : {};
137
- const requestedSkills = readRequestedSkills(metadata);
138
- return buildRequestedSkillsUserPrompt(skillsLoader, requestedSkills, userText);
139
- };
140
- }
141
136
  function resolveCodexModel(params) {
142
137
  return readString(params.sessionMetadata.preferred_model) ?? readString(params.sessionMetadata.model) ?? readString(params.pluginConfig.model) ?? params.config.agents.defaults.model;
143
138
  }
@@ -152,81 +147,115 @@ const plugin = {
152
147
  },
153
148
  register(api) {
154
149
  const pluginConfig = readRecord(api.pluginConfig) ?? {};
150
+ const describeCodexSessionType = createDescribeCodexSessionType({
151
+ config: api.config,
152
+ pluginConfig
153
+ });
155
154
  api.registerNcpAgentRuntime({
156
155
  kind: CODEX_RUNTIME_KIND,
157
156
  label: "Codex",
157
+ describeSessionType: describeCodexSessionType,
158
158
  createRuntime: (runtimeParams) => {
159
- const nextConfig = api.config;
160
- const model = resolveCodexModel({
161
- config: nextConfig,
162
- pluginConfig,
163
- sessionMetadata: runtimeParams.sessionMetadata
164
- });
165
- const resolvedProviderRuntime = resolveProviderRuntime(nextConfig, model);
166
- const providerName = resolvedProviderRuntime.providerName;
167
- const capabilitySpec = resolveCodexCapabilitySpec({
168
- model,
169
- providerName
170
- });
171
- const externalModelProvider = resolveExternalModelProvider({
172
- explicitModelProvider: pluginConfig.modelProvider,
173
- providerName,
174
- providerDisplayName: resolvedProviderRuntime.providerDisplayName,
175
- pluginId: PLUGIN_ID
176
- });
177
- const userFacingModelRoute = buildUserFacingModelRoute({
178
- externalModelProvider,
179
- providerLocalModel: resolvedProviderRuntime.providerLocalModel,
180
- resolvedModel: resolvedProviderRuntime.resolvedModel
181
- });
182
- const apiBase = readString(pluginConfig.apiBase) ?? resolvedProviderRuntime.apiBase ?? void 0;
183
- const apiKey = readString(pluginConfig.apiKey) ?? resolvedProviderRuntime.apiKey ?? void 0;
184
- if (!apiKey) {
185
- throw new Error(
186
- `[codex] missing apiKey. Set plugins.entries.${PLUGIN_ID}.config.apiKey or providers.*.apiKey for model "${userFacingModelRoute}".`
187
- );
188
- }
189
- if (capabilitySpec?.supportsResponsesApi === false) {
190
- const capabilityProviderName = capabilitySpec.displayName ?? capabilitySpec.name ?? resolvedProviderRuntime.providerDisplayName ?? externalModelProvider;
191
- throw new Error(
192
- `[codex] model "${userFacingModelRoute}" is routed through "${capabilityProviderName}", which does not support the Responses API. Codex SDK currently only supports models available through the Responses API.`
193
- );
194
- }
195
- const executionOptions = resolveCodexExecutionOptions({
196
- config: nextConfig,
197
- pluginConfig
198
- });
199
- const thinkingLevel = readThinkingLevel(runtimeParams.sessionMetadata.preferred_thinking) ?? readThinkingLevel(runtimeParams.sessionMetadata.thinking) ?? void 0;
200
- return new CodexSdkNcpAgentRuntime({
201
- sessionId: runtimeParams.sessionId,
202
- apiKey,
203
- apiBase,
204
- model: resolvedProviderRuntime.providerLocalModel,
205
- threadId: readString(runtimeParams.sessionMetadata.codex_thread_id) ?? null,
206
- codexPathOverride: readString(pluginConfig.codexPathOverride),
207
- env: readStringRecord(pluginConfig.env),
208
- cliConfig: resolveCodexCliConfig({
159
+ return new DeferredCodexSdkNcpAgentRuntime(async () => {
160
+ const nextConfig = api.config;
161
+ const model = resolveCodexModel({
162
+ config: nextConfig,
209
163
  pluginConfig,
164
+ sessionMetadata: runtimeParams.sessionMetadata
165
+ });
166
+ const resolvedProviderRuntime = resolveProviderRuntime(nextConfig, model);
167
+ const providerName = resolvedProviderRuntime.providerName;
168
+ const capabilitySpec = resolveCodexCapabilitySpec({
169
+ model,
170
+ providerName
171
+ });
172
+ const externalModelProvider = resolveExternalModelProvider({
173
+ explicitModelProvider: pluginConfig.modelProvider,
210
174
  providerName,
211
175
  providerDisplayName: resolvedProviderRuntime.providerDisplayName,
212
- apiBase
213
- }),
214
- stateManager: runtimeParams.stateManager,
215
- sessionMetadata: runtimeParams.sessionMetadata,
216
- setSessionMetadata: runtimeParams.setSessionMetadata,
217
- inputBuilder: buildCodexInputBuilder(executionOptions.workingDirectory),
218
- threadOptions: {
219
- model,
220
- sandboxMode: readString(pluginConfig.sandboxMode),
221
- workingDirectory: executionOptions.workingDirectory,
222
- skipGitRepoCheck: executionOptions.skipGitRepoCheck,
223
- modelReasoningEffort: thinkingLevel,
224
- networkAccessEnabled: readBoolean(pluginConfig.networkAccessEnabled),
225
- webSearchMode: readString(pluginConfig.webSearchMode),
226
- webSearchEnabled: readBoolean(pluginConfig.webSearchEnabled),
227
- approvalPolicy: readString(pluginConfig.approvalPolicy),
228
- additionalDirectories: readStringArray(pluginConfig.additionalDirectories)
176
+ pluginId: PLUGIN_ID
177
+ });
178
+ const userFacingModelRoute = buildUserFacingModelRoute({
179
+ externalModelProvider,
180
+ providerLocalModel: resolvedProviderRuntime.providerLocalModel,
181
+ resolvedModel: resolvedProviderRuntime.resolvedModel
182
+ });
183
+ const upstreamApiBase = readString(pluginConfig.apiBase) ?? resolvedProviderRuntime.apiBase ?? void 0;
184
+ const apiKey = readString(pluginConfig.apiKey) ?? resolvedProviderRuntime.apiKey ?? void 0;
185
+ if (!apiKey) {
186
+ throw new Error(
187
+ `[codex] missing apiKey. Set plugins.entries.${PLUGIN_ID}.config.apiKey or providers.*.apiKey for model "${userFacingModelRoute}".`
188
+ );
189
+ }
190
+ if (!upstreamApiBase) {
191
+ throw new Error(
192
+ `[codex] missing apiBase for model "${userFacingModelRoute}". Configure plugins.entries.${PLUGIN_ID}.config.apiBase or providers.*.apiBase.`
193
+ );
194
+ }
195
+ let codexApiBase = upstreamApiBase;
196
+ let codexModelProviderOverride;
197
+ const supportsResponsesApi = await resolveCodexResponsesApiSupport({
198
+ capabilitySpec,
199
+ wireApi: readString(resolvedProviderRuntime.provider?.wireApi),
200
+ apiBase: upstreamApiBase,
201
+ apiKey,
202
+ extraHeaders: resolvedProviderRuntime.provider?.extraHeaders ?? null,
203
+ model: resolvedProviderRuntime.providerLocalModel
204
+ });
205
+ if (!supportsResponsesApi) {
206
+ const bridge = await ensureCodexOpenAiResponsesBridge({
207
+ upstreamApiBase,
208
+ upstreamApiKey: apiKey,
209
+ upstreamExtraHeaders: resolvedProviderRuntime.provider?.extraHeaders ?? void 0,
210
+ defaultModel: resolvedProviderRuntime.providerLocalModel,
211
+ modelPrefixes: [
212
+ providerName ?? "",
213
+ externalModelProvider,
214
+ resolvedProviderRuntime.providerDisplayName ?? ""
215
+ ]
216
+ });
217
+ codexApiBase = bridge.baseUrl;
218
+ codexModelProviderOverride = buildCodexBridgeModelProviderId(
219
+ externalModelProvider
220
+ );
229
221
  }
222
+ const executionOptions = resolveCodexExecutionOptions({
223
+ config: nextConfig,
224
+ pluginConfig
225
+ });
226
+ const thinkingLevel = readThinkingLevel(runtimeParams.sessionMetadata.preferred_thinking) ?? readThinkingLevel(runtimeParams.sessionMetadata.thinking) ?? void 0;
227
+ return new CodexSdkNcpAgentRuntime({
228
+ sessionId: runtimeParams.sessionId,
229
+ apiKey,
230
+ apiBase: codexApiBase,
231
+ model: resolvedProviderRuntime.providerLocalModel,
232
+ threadId: readString(runtimeParams.sessionMetadata.codex_thread_id) ?? null,
233
+ codexPathOverride: readString(pluginConfig.codexPathOverride),
234
+ env: readStringRecord(pluginConfig.env),
235
+ cliConfig: resolveCodexCliConfig({
236
+ pluginConfig,
237
+ providerName,
238
+ providerDisplayName: resolvedProviderRuntime.providerDisplayName,
239
+ apiBase: codexApiBase,
240
+ modelProviderOverride: codexModelProviderOverride
241
+ }),
242
+ stateManager: runtimeParams.stateManager,
243
+ sessionMetadata: runtimeParams.sessionMetadata,
244
+ setSessionMetadata: runtimeParams.setSessionMetadata,
245
+ inputBuilder: buildCodexInputBuilder(executionOptions.workingDirectory),
246
+ threadOptions: {
247
+ model,
248
+ sandboxMode: readString(pluginConfig.sandboxMode),
249
+ workingDirectory: executionOptions.workingDirectory,
250
+ skipGitRepoCheck: executionOptions.skipGitRepoCheck,
251
+ modelReasoningEffort: thinkingLevel,
252
+ networkAccessEnabled: readBoolean(pluginConfig.networkAccessEnabled),
253
+ webSearchMode: readString(pluginConfig.webSearchMode),
254
+ webSearchEnabled: readBoolean(pluginConfig.webSearchEnabled),
255
+ approvalPolicy: readString(pluginConfig.approvalPolicy),
256
+ additionalDirectories: readStringArray(pluginConfig.additionalDirectories)
257
+ }
258
+ });
230
259
  });
231
260
  }
232
261
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "private": false,
5
5
  "description": "NextClaw plugin that registers Codex SDK as an optional NCP runtime.",
6
6
  "type": "module",
@@ -21,10 +21,10 @@
21
21
  ]
22
22
  },
23
23
  "dependencies": {
24
- "@nextclaw/core": "0.9.12",
24
+ "@nextclaw/core": "0.10.0",
25
+ "@nextclaw/ncp": "0.3.2",
25
26
  "@nextclaw/ncp-toolkit": "0.4.2",
26
- "@nextclaw/nextclaw-ncp-runtime-codex-sdk": "0.1.2",
27
- "@nextclaw/ncp": "0.3.2"
27
+ "@nextclaw/nextclaw-ncp-runtime-codex-sdk": "0.1.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^20.17.6",