@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk 0.1.15 → 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,23 +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";
16
18
  import {
17
19
  createDescribeCodexSessionType
18
20
  } from "./codex-session-type.js";
19
21
  const PLUGIN_ID = "nextclaw-ncp-runtime-plugin-codex-sdk";
20
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
+ }
21
42
  function readString(value) {
22
43
  if (typeof value !== "string") {
23
44
  return void 0;
@@ -85,7 +106,7 @@ function resolveCodexExecutionOptions(params) {
85
106
  }
86
107
  function resolveCodexCliConfig(params) {
87
108
  const explicitConfig = readRecord(params.pluginConfig.config);
88
- const modelProvider = resolveExternalModelProvider({
109
+ const modelProvider = readString(params.modelProviderOverride) ?? resolveExternalModelProvider({
89
110
  explicitModelProvider: params.pluginConfig.modelProvider,
90
111
  providerName: params.providerName,
91
112
  providerDisplayName: params.providerDisplayName,
@@ -112,35 +133,6 @@ function resolveCodexCliConfig(params) {
112
133
  ...explicitConfig ?? {}
113
134
  };
114
135
  }
115
- function readRequestedSkills(metadata) {
116
- const raw = metadata.requested_skills ?? metadata.requestedSkills;
117
- if (!Array.isArray(raw)) {
118
- return [];
119
- }
120
- return raw.map((entry) => readString(entry)).filter((entry) => Boolean(entry)).slice(0, 8);
121
- }
122
- function readUserText(input) {
123
- for (let index = input.messages.length - 1; index >= 0; index -= 1) {
124
- const message = input.messages[index];
125
- if (message?.role !== "user") {
126
- continue;
127
- }
128
- const text = message.parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
129
- if (text) {
130
- return text;
131
- }
132
- }
133
- return "";
134
- }
135
- function buildCodexInputBuilder(workspace) {
136
- const skillsLoader = new SkillsLoader(workspace);
137
- return async (input) => {
138
- const userText = readUserText(input);
139
- const metadata = input.metadata && typeof input.metadata === "object" && !Array.isArray(input.metadata) ? input.metadata : {};
140
- const requestedSkills = readRequestedSkills(metadata);
141
- return buildRequestedSkillsUserPrompt(skillsLoader, requestedSkills, userText);
142
- };
143
- }
144
136
  function resolveCodexModel(params) {
145
137
  return readString(params.sessionMetadata.preferred_model) ?? readString(params.sessionMetadata.model) ?? readString(params.pluginConfig.model) ?? params.config.agents.defaults.model;
146
138
  }
@@ -164,77 +156,106 @@ const plugin = {
164
156
  label: "Codex",
165
157
  describeSessionType: describeCodexSessionType,
166
158
  createRuntime: (runtimeParams) => {
167
- const nextConfig = api.config;
168
- const model = resolveCodexModel({
169
- config: nextConfig,
170
- pluginConfig,
171
- sessionMetadata: runtimeParams.sessionMetadata
172
- });
173
- const resolvedProviderRuntime = resolveProviderRuntime(nextConfig, model);
174
- const providerName = resolvedProviderRuntime.providerName;
175
- const capabilitySpec = resolveCodexCapabilitySpec({
176
- model,
177
- providerName
178
- });
179
- const externalModelProvider = resolveExternalModelProvider({
180
- explicitModelProvider: pluginConfig.modelProvider,
181
- providerName,
182
- providerDisplayName: resolvedProviderRuntime.providerDisplayName,
183
- pluginId: PLUGIN_ID
184
- });
185
- const userFacingModelRoute = buildUserFacingModelRoute({
186
- externalModelProvider,
187
- providerLocalModel: resolvedProviderRuntime.providerLocalModel,
188
- resolvedModel: resolvedProviderRuntime.resolvedModel
189
- });
190
- const apiBase = readString(pluginConfig.apiBase) ?? resolvedProviderRuntime.apiBase ?? void 0;
191
- const apiKey = readString(pluginConfig.apiKey) ?? resolvedProviderRuntime.apiKey ?? void 0;
192
- if (!apiKey) {
193
- throw new Error(
194
- `[codex] missing apiKey. Set plugins.entries.${PLUGIN_ID}.config.apiKey or providers.*.apiKey for model "${userFacingModelRoute}".`
195
- );
196
- }
197
- if (capabilitySpec?.supportsResponsesApi === false) {
198
- const capabilityProviderName = capabilitySpec.displayName ?? capabilitySpec.name ?? resolvedProviderRuntime.providerDisplayName ?? externalModelProvider;
199
- throw new Error(
200
- `[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.`
201
- );
202
- }
203
- const executionOptions = resolveCodexExecutionOptions({
204
- config: nextConfig,
205
- pluginConfig
206
- });
207
- const thinkingLevel = readThinkingLevel(runtimeParams.sessionMetadata.preferred_thinking) ?? readThinkingLevel(runtimeParams.sessionMetadata.thinking) ?? void 0;
208
- return new CodexSdkNcpAgentRuntime({
209
- sessionId: runtimeParams.sessionId,
210
- apiKey,
211
- apiBase,
212
- model: resolvedProviderRuntime.providerLocalModel,
213
- threadId: readString(runtimeParams.sessionMetadata.codex_thread_id) ?? null,
214
- codexPathOverride: readString(pluginConfig.codexPathOverride),
215
- env: readStringRecord(pluginConfig.env),
216
- cliConfig: resolveCodexCliConfig({
159
+ return new DeferredCodexSdkNcpAgentRuntime(async () => {
160
+ const nextConfig = api.config;
161
+ const model = resolveCodexModel({
162
+ config: nextConfig,
217
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,
218
174
  providerName,
219
175
  providerDisplayName: resolvedProviderRuntime.providerDisplayName,
220
- apiBase
221
- }),
222
- stateManager: runtimeParams.stateManager,
223
- sessionMetadata: runtimeParams.sessionMetadata,
224
- setSessionMetadata: runtimeParams.setSessionMetadata,
225
- inputBuilder: buildCodexInputBuilder(executionOptions.workingDirectory),
226
- threadOptions: {
227
- model,
228
- sandboxMode: readString(pluginConfig.sandboxMode),
229
- workingDirectory: executionOptions.workingDirectory,
230
- skipGitRepoCheck: executionOptions.skipGitRepoCheck,
231
- modelReasoningEffort: thinkingLevel,
232
- networkAccessEnabled: readBoolean(pluginConfig.networkAccessEnabled),
233
- webSearchMode: readString(pluginConfig.webSearchMode),
234
- webSearchEnabled: readBoolean(pluginConfig.webSearchEnabled),
235
- approvalPolicy: readString(pluginConfig.approvalPolicy),
236
- 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
+ );
237
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
+ });
238
259
  });
239
260
  }
240
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.15",
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",