@meetopenbot/codex 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/dist/index.js +152 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
OpenAI Codex runtime plugin for OpenBot. Each `agent:invoke` starts or resumes a Codex SDK thread in the channel workspace.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Auth
|
|
6
|
+
|
|
7
|
+
On cloud, `authMode: credits` (the default) uses your workspace credit balance via OpenBot. For BYOK, set `authMode: byok` and add `OPENAI_API_KEY` (or `CODEX_API_KEY`) under workspace settings — there is no API key field in plugin config.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
Locally, Codex always uses BYOK from `CODEX_API_KEY` / `OPENAI_API_KEY`.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
- `CODEX_API_KEY`
|
|
11
|
-
- `OPENAI_API_KEY`
|
|
11
|
+
## Config
|
|
12
12
|
|
|
13
13
|
Optional:
|
|
14
14
|
|
|
15
|
+
- `authMode` — cloud only: `credits` (default) or `byok`
|
|
15
16
|
- `model` — Codex model id (e.g. `gpt-5.6`). Omit to use the CLI default.
|
|
16
17
|
- `sandboxMode` — `read-only` | `workspace-write` | `danger-full-access` (default `workspace-write`)
|
|
17
18
|
- `approvalPolicy` — `never` | `on-request` | `on-failure` | `untrusted` (default `never`)
|
|
18
|
-
- `workingDirectory` — override the channel cwd
|
|
19
19
|
- `skipGitRepoCheck` — default `true` for non-git channel workspaces
|
|
20
|
-
- `baseURL` — custom OpenAI-compatible endpoint
|
|
21
20
|
- `codexPathOverride` — path to a local `codex` binary (`CODEX_PATH` / `CODEX_CLI_PATH` also work)
|
|
22
21
|
|
|
23
22
|
Codex conversation state is stored as `codexThreadId` on the OpenBot thread, not as the OpenBot thread id.
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,74 @@ import { join } from "node:path";
|
|
|
15
15
|
import {
|
|
16
16
|
Codex
|
|
17
17
|
} from "@openai/codex-sdk";
|
|
18
|
+
|
|
19
|
+
// cloud-mode.ts
|
|
20
|
+
var isCloudMode = () => process.env.OPENBOT_CLOUD_MODE === "1";
|
|
21
|
+
var defaultAuthMode = () => isCloudMode() ? "credits" : "byok";
|
|
22
|
+
function resolveAuthMode(config) {
|
|
23
|
+
if (config.authMode === "byok" || config.authMode === "credits") {
|
|
24
|
+
return config.authMode;
|
|
25
|
+
}
|
|
26
|
+
return defaultAuthMode();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// credits-auth.ts
|
|
30
|
+
var INTEGRATIONS_TOKEN_HEADER = "x-openbot-integrations-token";
|
|
31
|
+
var CREDITS_API_KEY_PLACEHOLDER = "openbot-credits";
|
|
32
|
+
var CREDITS_PROVIDER_ID = "openbot_credits";
|
|
33
|
+
function resolveCreditsAuthConfig() {
|
|
34
|
+
const baseUrl = process.env.OPENBOT_INTEGRATIONS_BASE_URL?.trim();
|
|
35
|
+
const token = process.env.OPENBOT_INTEGRATIONS_TOKEN?.trim();
|
|
36
|
+
if (!baseUrl || !token) return void 0;
|
|
37
|
+
return { baseUrl: baseUrl.replace(/\/$/, ""), token };
|
|
38
|
+
}
|
|
39
|
+
function creditsProviderBaseUrl(config) {
|
|
40
|
+
return `${config.baseUrl}/openai/v1`;
|
|
41
|
+
}
|
|
42
|
+
function buildCreditsCodexConfig(config) {
|
|
43
|
+
return {
|
|
44
|
+
model_provider: CREDITS_PROVIDER_ID,
|
|
45
|
+
model_providers: {
|
|
46
|
+
[CREDITS_PROVIDER_ID]: {
|
|
47
|
+
name: "OpenBot Credits",
|
|
48
|
+
base_url: creditsProviderBaseUrl(config),
|
|
49
|
+
env_key: "CODEX_API_KEY",
|
|
50
|
+
wire_api: "responses",
|
|
51
|
+
supports_websockets: false,
|
|
52
|
+
http_headers: {
|
|
53
|
+
[INTEGRATIONS_TOKEN_HEADER]: config.token
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function isCreditsErrorMessage(message) {
|
|
60
|
+
const lower = message.toLowerCase();
|
|
61
|
+
return lower.includes("insufficient_credits") || lower.includes("insufficient credits") || lower.includes("402");
|
|
62
|
+
}
|
|
63
|
+
function isAuthErrorMessage(message) {
|
|
64
|
+
const lower = message.toLowerCase();
|
|
65
|
+
return lower.includes("api key") || lower.includes("apikey") || lower.includes("401") || lower.includes("unauthorized") || lower.includes("authentication") || lower.includes("not logged in") || lower.includes("login");
|
|
66
|
+
}
|
|
67
|
+
function isIntegrationsProviderError(message) {
|
|
68
|
+
const lower = message.toLowerCase();
|
|
69
|
+
return lower.includes("provider api key not configured") || lower.includes("503") && lower.includes("provider");
|
|
70
|
+
}
|
|
71
|
+
var CREDITS_NOT_CONFIGURED_MESSAGE = "OpenBot Credits is not configured on this runtime. The cloud host must set OPENBOT_INTEGRATIONS_BASE_URL and OPENBOT_INTEGRATIONS_TOKEN (try redeploying the workspace).";
|
|
72
|
+
var CREDITS_PROVIDER_UNAVAILABLE_MESSAGE = "OpenBot Credits could not reach OpenAI \u2014 the platform provider API key is not configured yet. Try again later or switch this agent to BYOK mode.";
|
|
73
|
+
var CREDITS_AUTH_FAILED_MESSAGE = "Codex could not authenticate via OpenBot Credits. Check your workspace credit balance in settings, or switch this agent to BYOK mode.";
|
|
74
|
+
function creditsErrorMessage(message) {
|
|
75
|
+
if (isIntegrationsProviderError(message)) {
|
|
76
|
+
return CREDITS_PROVIDER_UNAVAILABLE_MESSAGE;
|
|
77
|
+
}
|
|
78
|
+
if (isCreditsErrorMessage(message)) {
|
|
79
|
+
return "Insufficient workspace credits. Add credits in workspace settings or switch this agent to BYOK mode.";
|
|
80
|
+
}
|
|
81
|
+
if (isAuthErrorMessage(message)) return CREDITS_AUTH_FAILED_MESSAGE;
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// index.ts
|
|
18
86
|
var SANDBOX_MODES = [
|
|
19
87
|
"read-only",
|
|
20
88
|
"workspace-write",
|
|
@@ -35,21 +103,8 @@ var LEGACY_APPROVAL = {
|
|
|
35
103
|
always: "on-request",
|
|
36
104
|
automatic: "on-request"
|
|
37
105
|
};
|
|
38
|
-
var AUTH_ERROR_PATTERNS = [
|
|
39
|
-
"api key",
|
|
40
|
-
"apikey",
|
|
41
|
-
"unauthorized",
|
|
42
|
-
"401",
|
|
43
|
-
"authentication",
|
|
44
|
-
"not logged in",
|
|
45
|
-
"login"
|
|
46
|
-
];
|
|
47
106
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
48
107
|
var asString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
49
|
-
var isAuthErrorMessage = (message) => {
|
|
50
|
-
const lower = message.toLowerCase();
|
|
51
|
-
return AUTH_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
|
|
52
|
-
};
|
|
53
108
|
var readPersistedThreadId = (state) => {
|
|
54
109
|
const source = state.threadDetails?.state ?? state.channelDetails?.state;
|
|
55
110
|
const record = asRecord(source);
|
|
@@ -157,11 +212,14 @@ var plugin = definePlugin({
|
|
|
157
212
|
configSchema: {
|
|
158
213
|
type: "object",
|
|
159
214
|
properties: {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
215
|
+
...isCloudMode() ? {
|
|
216
|
+
authMode: {
|
|
217
|
+
type: "string",
|
|
218
|
+
description: "Credits \u2014 use your workspace credit balance via OpenBot. BYOK \u2014 bring your own OpenAI API key.",
|
|
219
|
+
enum: ["credits", "byok"],
|
|
220
|
+
default: "credits"
|
|
221
|
+
}
|
|
222
|
+
} : {},
|
|
165
223
|
model: {
|
|
166
224
|
type: "string",
|
|
167
225
|
description: "Codex model id (e.g. gpt-5.6). Leave empty to use the CLI default."
|
|
@@ -178,19 +236,11 @@ var plugin = definePlugin({
|
|
|
178
236
|
description: "When to require approval: never | on-request | on-failure | untrusted",
|
|
179
237
|
default: "never"
|
|
180
238
|
},
|
|
181
|
-
workingDirectory: {
|
|
182
|
-
type: "string",
|
|
183
|
-
description: "Override the channel working directory"
|
|
184
|
-
},
|
|
185
239
|
skipGitRepoCheck: {
|
|
186
240
|
type: "boolean",
|
|
187
241
|
description: "Skip the Codex git-repo check (needed for non-git channel workspaces)",
|
|
188
242
|
default: true
|
|
189
243
|
},
|
|
190
|
-
baseURL: {
|
|
191
|
-
type: "string",
|
|
192
|
-
description: "Custom OpenAI-compatible endpoint"
|
|
193
|
-
},
|
|
194
244
|
codexPathOverride: {
|
|
195
245
|
type: "string",
|
|
196
246
|
description: "Path to a local Codex CLI binary"
|
|
@@ -199,24 +249,27 @@ var plugin = definePlugin({
|
|
|
199
249
|
},
|
|
200
250
|
factory: (context) => {
|
|
201
251
|
const config = context.config;
|
|
202
|
-
const
|
|
252
|
+
const authMode = resolveAuthMode(config);
|
|
203
253
|
const codexPathOverride = asString(config.codexPathOverride) ?? process.env.CODEX_PATH ?? process.env.CODEX_CLI_PATH;
|
|
204
254
|
const model = parseModel(config.model);
|
|
205
255
|
const sandboxMode = parseSandboxMode(config.sandboxMode);
|
|
206
256
|
const approvalPolicy = parseApprovalPolicy(config.approvalPolicy);
|
|
207
257
|
const skipGitRepoCheck = config.skipGitRepoCheck !== false;
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
...apiKey && { apiKey },
|
|
258
|
+
const createClient = () => {
|
|
259
|
+
if (authMode === "credits") {
|
|
260
|
+
const credits = resolveCreditsAuthConfig();
|
|
261
|
+
if (!credits) return CREDITS_NOT_CONFIGURED_MESSAGE;
|
|
262
|
+
return new Codex({
|
|
263
|
+
apiKey: credits.token || CREDITS_API_KEY_PLACEHOLDER,
|
|
215
264
|
...codexPathOverride && { codexPathOverride },
|
|
216
|
-
|
|
265
|
+
config: buildCreditsCodexConfig(credits)
|
|
217
266
|
});
|
|
218
267
|
}
|
|
219
|
-
|
|
268
|
+
const apiKey = process.env.CODEX_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
269
|
+
return new Codex({
|
|
270
|
+
...asString(apiKey) && { apiKey: asString(apiKey) },
|
|
271
|
+
...codexPathOverride && { codexPathOverride }
|
|
272
|
+
});
|
|
220
273
|
};
|
|
221
274
|
return (builder) => {
|
|
222
275
|
builder.on("agent:invoke", async function* (event, ctx) {
|
|
@@ -231,7 +284,16 @@ var plugin = definePlugin({
|
|
|
231
284
|
});
|
|
232
285
|
return;
|
|
233
286
|
}
|
|
234
|
-
const
|
|
287
|
+
const clientOrError = createClient();
|
|
288
|
+
if (typeof clientOrError === "string") {
|
|
289
|
+
yield agentOutput({
|
|
290
|
+
agentId: context.agentId,
|
|
291
|
+
content: clientOrError,
|
|
292
|
+
threadId: openbotThreadId
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const workingDirectory = ctx.state.channelDetails?.cwd || process.cwd();
|
|
235
297
|
const threadOptions = {
|
|
236
298
|
workingDirectory,
|
|
237
299
|
skipGitRepoCheck,
|
|
@@ -240,8 +302,16 @@ var plugin = definePlugin({
|
|
|
240
302
|
...model && { model }
|
|
241
303
|
};
|
|
242
304
|
const savedId = readPersistedThreadId(ctx.state);
|
|
243
|
-
const thread = savedId ?
|
|
305
|
+
const thread = savedId ? clientOrError.resumeThread(savedId, threadOptions) : clientOrError.startThread(threadOptions);
|
|
244
306
|
const fail = function* (message) {
|
|
307
|
+
if (authMode === "credits") {
|
|
308
|
+
yield agentOutput({
|
|
309
|
+
agentId: context.agentId,
|
|
310
|
+
content: creditsErrorMessage(message) ?? `Error: ${message}`,
|
|
311
|
+
threadId: openbotThreadId
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
245
315
|
if (isAuthErrorMessage(message)) {
|
|
246
316
|
yield buildApiKeyWidget(context.agentId, openbotThreadId, message);
|
|
247
317
|
}
|
|
@@ -318,6 +388,50 @@ var plugin = definePlugin({
|
|
|
318
388
|
yield* fail(errorText(error));
|
|
319
389
|
}
|
|
320
390
|
});
|
|
391
|
+
builder.on("client:ui:widget:response", async function* (event, ctx) {
|
|
392
|
+
const { metadata, values, widgetId } = event.data ?? {};
|
|
393
|
+
if (!metadata || metadata.type !== "api_key_request") return;
|
|
394
|
+
if (metadata.source !== "codex") return;
|
|
395
|
+
const apiKey = values?.apiKey;
|
|
396
|
+
if (typeof apiKey !== "string" || !apiKey) return;
|
|
397
|
+
const envVar = typeof metadata.envVar === "string" ? metadata.envVar : "OPENAI_API_KEY";
|
|
398
|
+
const storage = context.storage;
|
|
399
|
+
if (!storage) {
|
|
400
|
+
yield agentOutput({
|
|
401
|
+
agentId: context.agentId,
|
|
402
|
+
content: "[codex] no storage available; cannot persist API key.",
|
|
403
|
+
threadId: ctx.state.threadId
|
|
404
|
+
});
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
await storage.createVariable({ key: envVar, value: apiKey, secret: true });
|
|
409
|
+
process.env[envVar] = apiKey;
|
|
410
|
+
process.env.CODEX_API_KEY = apiKey;
|
|
411
|
+
yield uiWidget({
|
|
412
|
+
agentId: context.agentId,
|
|
413
|
+
widget: {
|
|
414
|
+
widgetId: widgetId ?? `codex_api_key_saved_${Date.now()}`,
|
|
415
|
+
kind: "message",
|
|
416
|
+
title: "API Key Saved",
|
|
417
|
+
body: `Saved ${envVar} as a workspace variable. You can now continue the conversation.`,
|
|
418
|
+
state: "submitted"
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
yield agentOutput({
|
|
422
|
+
agentId: context.agentId,
|
|
423
|
+
content: "Saved OpenAI API key to workspace variables. Re-send your last message to retry.",
|
|
424
|
+
threadId: ctx.state.threadId
|
|
425
|
+
});
|
|
426
|
+
} catch (error) {
|
|
427
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
428
|
+
yield agentOutput({
|
|
429
|
+
agentId: context.agentId,
|
|
430
|
+
content: `[codex] failed to save API key: ${errorMessage}`,
|
|
431
|
+
threadId: ctx.state.threadId
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
});
|
|
321
435
|
};
|
|
322
436
|
}
|
|
323
437
|
});
|