@meetopenbot/codex 1.2.0 → 1.2.2
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 +142 -404
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,44 +1,71 @@
|
|
|
1
|
-
// index.ts
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { defineAgentPlugin } from "@meetopenbot/plugin-sdk";
|
|
3
|
+
|
|
4
|
+
// src/config.ts
|
|
2
5
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
agentOutput,
|
|
6
|
-
uiWidget,
|
|
7
|
-
toolTraceWidget,
|
|
8
|
-
buildDiffWidget,
|
|
9
|
-
diffFileFromWrite,
|
|
10
|
-
resolveRunDiffFiles,
|
|
11
|
-
snapshotWorkspace
|
|
6
|
+
trimmedString,
|
|
7
|
+
vendorModelId
|
|
12
8
|
} from "@meetopenbot/plugin-sdk";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
var SANDBOX_MODES = [
|
|
10
|
+
"read-only",
|
|
11
|
+
"workspace-write",
|
|
12
|
+
"danger-full-access"
|
|
13
|
+
];
|
|
14
|
+
var APPROVAL_MODES = [
|
|
15
|
+
"never",
|
|
16
|
+
"on-request",
|
|
17
|
+
"on-failure",
|
|
18
|
+
"untrusted"
|
|
19
|
+
];
|
|
20
|
+
var pluginConfigSchema = {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
sandboxMode: {
|
|
24
|
+
type: "string",
|
|
25
|
+
enum: [...SANDBOX_MODES],
|
|
26
|
+
description: "Filesystem sandbox: read-only | workspace-write | danger-full-access",
|
|
27
|
+
default: "workspace-write"
|
|
28
|
+
},
|
|
29
|
+
approvalPolicy: {
|
|
30
|
+
type: "string",
|
|
31
|
+
enum: [...APPROVAL_MODES],
|
|
32
|
+
description: "When to require approval: never | on-request | on-failure | untrusted",
|
|
33
|
+
default: "never"
|
|
34
|
+
}
|
|
25
35
|
}
|
|
26
|
-
|
|
36
|
+
};
|
|
37
|
+
function resolveCodexConfig(config) {
|
|
38
|
+
const sandboxRaw = trimmedString(config.sandboxMode);
|
|
39
|
+
const approvalRaw = trimmedString(config.approvalPolicy);
|
|
40
|
+
return {
|
|
41
|
+
model: vendorModelId(trimmedString(config.model)),
|
|
42
|
+
sandboxMode: sandboxRaw && SANDBOX_MODES.includes(sandboxRaw) ? sandboxRaw : "workspace-write",
|
|
43
|
+
approvalPolicy: approvalRaw && APPROVAL_MODES.includes(approvalRaw) ? approvalRaw : "never",
|
|
44
|
+
skipGitRepoCheck: true,
|
|
45
|
+
codexPathOverride: trimmedString(process.env.CODEX_PATH) ?? trimmedString(process.env.CODEX_CLI_PATH)
|
|
46
|
+
};
|
|
27
47
|
}
|
|
28
48
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
// src/runtime.ts
|
|
50
|
+
import {
|
|
51
|
+
isCloudMode,
|
|
52
|
+
llmAuthNotConfiguredMessage,
|
|
53
|
+
resolveByokApiKey,
|
|
54
|
+
textReply as textReply2
|
|
55
|
+
} from "@meetopenbot/plugin-sdk";
|
|
56
|
+
import { Codex } from "@openai/codex-sdk";
|
|
57
|
+
|
|
58
|
+
// src/credits.ts
|
|
59
|
+
import {
|
|
60
|
+
CREDITS_API_KEY_PLACEHOLDER,
|
|
61
|
+
CREDITS_NOT_CONFIGURED_MESSAGE,
|
|
62
|
+
INTEGRATIONS_TOKEN_HEADER,
|
|
63
|
+
creditsErrorMessage as mapCreditsError,
|
|
64
|
+
creditsProviderBaseUrl,
|
|
65
|
+
isAuthErrorMessage,
|
|
66
|
+
resolveCreditsAuthConfig
|
|
67
|
+
} from "@meetopenbot/plugin-sdk";
|
|
32
68
|
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
69
|
function buildCreditsCodexConfig(config) {
|
|
43
70
|
return {
|
|
44
71
|
model_provider: CREDITS_PROVIDER_ID,
|
|
@@ -56,387 +83,98 @@ function buildCreditsCodexConfig(config) {
|
|
|
56
83
|
}
|
|
57
84
|
};
|
|
58
85
|
}
|
|
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
86
|
function creditsErrorMessage(message) {
|
|
75
|
-
|
|
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;
|
|
87
|
+
return mapCreditsError(message, { agentName: "Codex", providerLabel: "OpenAI" });
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
var LEGACY_SANDBOX = {
|
|
98
|
-
"workspace-read": "read-only",
|
|
99
|
-
"full-read": "read-only",
|
|
100
|
-
"full-write": "danger-full-access"
|
|
101
|
-
};
|
|
102
|
-
var LEGACY_APPROVAL = {
|
|
103
|
-
always: "on-request",
|
|
104
|
-
automatic: "on-request"
|
|
105
|
-
};
|
|
106
|
-
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
107
|
-
var asString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
108
|
-
var readPersistedThreadId = (state) => {
|
|
109
|
-
const source = state.threadDetails?.state ?? state.channelDetails?.state;
|
|
110
|
-
const record = asRecord(source);
|
|
111
|
-
return asString(record.codexThreadId);
|
|
112
|
-
};
|
|
113
|
-
var persistThreadId = async (state, storage, codexThreadId) => {
|
|
114
|
-
if (!storage || !state.channelId) return;
|
|
115
|
-
const patch = { codexThreadId };
|
|
116
|
-
if (state.threadId) {
|
|
117
|
-
await storage.patchThreadState({
|
|
118
|
-
channelId: state.channelId,
|
|
119
|
-
threadId: state.threadId,
|
|
120
|
-
state: patch
|
|
121
|
-
});
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
await storage.patchChannelState({ channelId: state.channelId, state: patch });
|
|
125
|
-
};
|
|
126
|
-
var parseSandboxMode = (value) => {
|
|
127
|
-
const raw = asString(value);
|
|
128
|
-
if (!raw) return "workspace-write";
|
|
129
|
-
if (SANDBOX_MODES.includes(raw)) return raw;
|
|
130
|
-
return LEGACY_SANDBOX[raw] ?? "workspace-write";
|
|
131
|
-
};
|
|
132
|
-
var parseApprovalPolicy = (value) => {
|
|
133
|
-
const raw = asString(value);
|
|
134
|
-
if (!raw) return "never";
|
|
135
|
-
if (APPROVAL_MODES.includes(raw)) return raw;
|
|
136
|
-
return LEGACY_APPROVAL[raw] ?? "never";
|
|
137
|
-
};
|
|
138
|
-
var parseModel = (value) => {
|
|
139
|
-
const raw = asString(value);
|
|
140
|
-
if (!raw) return void 0;
|
|
141
|
-
return raw.split("/").pop() || void 0;
|
|
142
|
-
};
|
|
143
|
-
var itemWidget = (item) => {
|
|
144
|
-
switch (item.type) {
|
|
145
|
-
case "command_execution":
|
|
146
|
-
return {
|
|
147
|
-
title: "Command",
|
|
148
|
-
body: [item.command, item.aggregated_output].filter(Boolean).join("\n\n")
|
|
149
|
-
};
|
|
150
|
-
case "file_change":
|
|
151
|
-
return {
|
|
152
|
-
title: "File change",
|
|
153
|
-
body: item.changes.map((change) => `${change.kind} ${change.path}`).join("\n")
|
|
154
|
-
};
|
|
155
|
-
case "mcp_tool_call":
|
|
156
|
-
return {
|
|
157
|
-
title: `${item.server}: ${item.tool}`,
|
|
158
|
-
body: item.error?.message ?? (item.result ? JSON.stringify(item.result, null, 2) : "")
|
|
159
|
-
};
|
|
160
|
-
case "web_search":
|
|
161
|
-
return { title: "Web search", body: item.query };
|
|
162
|
-
case "error":
|
|
163
|
-
return { title: "Error", body: item.message };
|
|
164
|
-
default:
|
|
165
|
-
return null;
|
|
90
|
+
// src/session.ts
|
|
91
|
+
import { threadSession } from "@meetopenbot/plugin-sdk";
|
|
92
|
+
var codexSession = threadSession("codexThreadId");
|
|
93
|
+
|
|
94
|
+
// src/stream.ts
|
|
95
|
+
import {
|
|
96
|
+
createTextReplyBuffer,
|
|
97
|
+
textReply
|
|
98
|
+
} from "@meetopenbot/plugin-sdk";
|
|
99
|
+
var classifyCodexEvent = (chunk) => {
|
|
100
|
+
if (chunk.type === "item.completed" && chunk.item.type === "agent_message" && chunk.item.text.trim()) {
|
|
101
|
+
return { type: "text", text: chunk.item.text };
|
|
166
102
|
}
|
|
103
|
+
if (chunk.type === "turn.completed") return { type: "flush" };
|
|
104
|
+
return { type: "skip" };
|
|
167
105
|
};
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
106
|
+
async function* runCodexStream(args) {
|
|
107
|
+
const { events, state, storage } = args;
|
|
108
|
+
const replies = createTextReplyBuffer();
|
|
109
|
+
for await (const chunk of events) {
|
|
110
|
+
if (chunk.type === "thread.started") {
|
|
111
|
+
await codexSession.write(state, storage, chunk.thread_id);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (chunk.type === "turn.failed") {
|
|
115
|
+
yield* replies.end();
|
|
116
|
+
yield textReply(creditsErrorMessage(chunk.error.message) ?? chunk.error.message);
|
|
172
117
|
return;
|
|
173
|
-
} catch {
|
|
174
118
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
});
|
|
180
|
-
};
|
|
181
|
-
var errorText = (error) => error instanceof Error ? error.message : "Codex request failed.";
|
|
182
|
-
var buildApiKeyWidget = (agentId, threadId, reason) => uiWidget({
|
|
183
|
-
agentId,
|
|
184
|
-
threadId,
|
|
185
|
-
widget: {
|
|
186
|
-
kind: "form",
|
|
187
|
-
widgetId: `codex_api_key_request_${Date.now()}`,
|
|
188
|
-
title: "OpenAI API Key Required",
|
|
189
|
-
description: `Codex could not authenticate (${reason}). Provide an OpenAI API key to continue. You can get one from the OpenAI dashboard. The key is stored as a workspace variable on your machine and never leaves your local runtime.`,
|
|
190
|
-
fields: [
|
|
191
|
-
{
|
|
192
|
-
id: "apiKey",
|
|
193
|
-
label: "API Key",
|
|
194
|
-
type: "password",
|
|
195
|
-
placeholder: "sk-...",
|
|
196
|
-
required: true
|
|
197
|
-
}
|
|
198
|
-
],
|
|
199
|
-
submitLabel: "Save API Key",
|
|
200
|
-
metadata: {
|
|
201
|
-
type: "api_key_request",
|
|
202
|
-
provider: "openai",
|
|
203
|
-
envVar: "OPENAI_API_KEY",
|
|
204
|
-
source: "codex"
|
|
119
|
+
if (chunk.type === "error") {
|
|
120
|
+
yield* replies.end();
|
|
121
|
+
yield textReply(creditsErrorMessage(chunk.message) ?? chunk.message);
|
|
122
|
+
return;
|
|
205
123
|
}
|
|
124
|
+
yield* replies.apply(classifyCodexEvent(chunk));
|
|
206
125
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
126
|
+
yield* replies.end();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/runtime.ts
|
|
130
|
+
async function* runCodexTurn({
|
|
131
|
+
prompt,
|
|
132
|
+
handlerCtx,
|
|
133
|
+
context
|
|
134
|
+
}) {
|
|
135
|
+
const byok = resolveByokApiKey("openai", ["CODEX_API_KEY"]);
|
|
136
|
+
const credits = !byok && isCloudMode() ? resolveCreditsAuthConfig() : void 0;
|
|
137
|
+
if (!byok && !credits) {
|
|
138
|
+
yield textReply2(llmAuthNotConfiguredMessage("OPENAI_API_KEY"));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const config = resolveCodexConfig(context.config);
|
|
142
|
+
const client = new Codex({
|
|
143
|
+
apiKey: byok ?? credits?.token ?? CREDITS_API_KEY_PLACEHOLDER,
|
|
144
|
+
...config.codexPathOverride && { codexPathOverride: config.codexPathOverride },
|
|
145
|
+
...credits ? { config: buildCreditsCodexConfig(credits) } : {}
|
|
146
|
+
});
|
|
147
|
+
const workingDirectory = handlerCtx.state.channelDetails?.cwd || process.cwd();
|
|
148
|
+
const threadOptions = {
|
|
149
|
+
workingDirectory,
|
|
150
|
+
skipGitRepoCheck: config.skipGitRepoCheck,
|
|
151
|
+
sandboxMode: config.sandboxMode,
|
|
152
|
+
approvalPolicy: config.approvalPolicy,
|
|
153
|
+
...config.model && { model: config.model }
|
|
154
|
+
};
|
|
155
|
+
const savedId = codexSession.read(handlerCtx.state);
|
|
156
|
+
const thread = savedId ? client.resumeThread(savedId, threadOptions) : client.startThread(threadOptions);
|
|
157
|
+
const { events } = await thread.runStreamed(prompt, {
|
|
158
|
+
signal: context.abortSignal
|
|
159
|
+
});
|
|
160
|
+
yield* runCodexStream({
|
|
161
|
+
events,
|
|
162
|
+
state: handlerCtx.state,
|
|
163
|
+
storage: context.storage
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/index.ts
|
|
168
|
+
var plugin = await defineAgentPlugin({
|
|
210
169
|
name: "Codex",
|
|
211
170
|
description: "OpenAI Codex agent. Uses the Codex SDK to read code, edit files, and run shell commands inside the channel's workspace.",
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
} : {},
|
|
223
|
-
model: {
|
|
224
|
-
type: "string",
|
|
225
|
-
description: "Codex model id (e.g. gpt-5.6). Leave empty to use the CLI default."
|
|
226
|
-
},
|
|
227
|
-
sandboxMode: {
|
|
228
|
-
type: "string",
|
|
229
|
-
enum: [...SANDBOX_MODES],
|
|
230
|
-
description: "Filesystem sandbox: read-only | workspace-write | danger-full-access",
|
|
231
|
-
default: "workspace-write"
|
|
232
|
-
},
|
|
233
|
-
approvalPolicy: {
|
|
234
|
-
type: "string",
|
|
235
|
-
enum: [...APPROVAL_MODES],
|
|
236
|
-
description: "When to require approval: never | on-request | on-failure | untrusted",
|
|
237
|
-
default: "never"
|
|
238
|
-
},
|
|
239
|
-
skipGitRepoCheck: {
|
|
240
|
-
type: "boolean",
|
|
241
|
-
description: "Skip the Codex git-repo check (needed for non-git channel workspaces)",
|
|
242
|
-
default: true
|
|
243
|
-
},
|
|
244
|
-
codexPathOverride: {
|
|
245
|
-
type: "string",
|
|
246
|
-
description: "Path to a local Codex CLI binary"
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
factory: (context) => {
|
|
251
|
-
const config = context.config;
|
|
252
|
-
const authMode = resolveAuthMode(config);
|
|
253
|
-
const codexPathOverride = asString(config.codexPathOverride) ?? process.env.CODEX_PATH ?? process.env.CODEX_CLI_PATH;
|
|
254
|
-
const model = parseModel(config.model);
|
|
255
|
-
const sandboxMode = parseSandboxMode(config.sandboxMode);
|
|
256
|
-
const approvalPolicy = parseApprovalPolicy(config.approvalPolicy);
|
|
257
|
-
const skipGitRepoCheck = config.skipGitRepoCheck !== false;
|
|
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,
|
|
264
|
-
...codexPathOverride && { codexPathOverride },
|
|
265
|
-
config: buildCreditsCodexConfig(credits)
|
|
266
|
-
});
|
|
267
|
-
}
|
|
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
|
-
});
|
|
273
|
-
};
|
|
274
|
-
return (builder) => {
|
|
275
|
-
builder.on("agent:invoke", async function* (event, ctx) {
|
|
276
|
-
if (!shouldHandleInvoke(event, context.agentId)) return;
|
|
277
|
-
const content = asString(event.data?.content);
|
|
278
|
-
const openbotThreadId = event.meta?.threadId || ctx.state.threadId;
|
|
279
|
-
if (!content) {
|
|
280
|
-
yield agentOutput({
|
|
281
|
-
agentId: context.agentId,
|
|
282
|
-
content: "No content provided.",
|
|
283
|
-
threadId: openbotThreadId
|
|
284
|
-
});
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
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();
|
|
297
|
-
const threadOptions = {
|
|
298
|
-
workingDirectory,
|
|
299
|
-
skipGitRepoCheck,
|
|
300
|
-
sandboxMode,
|
|
301
|
-
approvalPolicy,
|
|
302
|
-
...model && { model }
|
|
303
|
-
};
|
|
304
|
-
const savedId = readPersistedThreadId(ctx.state);
|
|
305
|
-
const thread = savedId ? clientOrError.resumeThread(savedId, threadOptions) : clientOrError.startThread(threadOptions);
|
|
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
|
-
}
|
|
315
|
-
if (isAuthErrorMessage(message)) {
|
|
316
|
-
yield buildApiKeyWidget(context.agentId, openbotThreadId, message);
|
|
317
|
-
}
|
|
318
|
-
yield agentOutput({
|
|
319
|
-
agentId: context.agentId,
|
|
320
|
-
content: `Error: ${message}`,
|
|
321
|
-
threadId: openbotThreadId
|
|
322
|
-
});
|
|
323
|
-
};
|
|
324
|
-
try {
|
|
325
|
-
const { events } = await thread.runStreamed(content, {
|
|
326
|
-
signal: context.abortSignal
|
|
327
|
-
});
|
|
328
|
-
const snapshot = snapshotWorkspace(workingDirectory);
|
|
329
|
-
const changedFiles = /* @__PURE__ */ new Map();
|
|
330
|
-
for await (const chunk of events) {
|
|
331
|
-
if (chunk.type === "thread.started") {
|
|
332
|
-
await persistThreadId(ctx.state, context.storage, chunk.thread_id);
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
if (chunk.type === "turn.failed") {
|
|
336
|
-
yield* fail(chunk.error.message);
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
if (chunk.type === "error") {
|
|
340
|
-
yield* fail(chunk.message);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
if (chunk.type !== "item.started" && chunk.type !== "item.completed") continue;
|
|
344
|
-
if (chunk.item.type === "agent_message") {
|
|
345
|
-
if (chunk.type === "item.completed") {
|
|
346
|
-
yield agentOutput({
|
|
347
|
-
agentId: context.agentId,
|
|
348
|
-
content: chunk.item.text,
|
|
349
|
-
threadId: openbotThreadId
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
if (chunk.type === "item.completed" && chunk.item.type === "file_change" && chunk.item.status === "completed") {
|
|
355
|
-
for (const change of chunk.item.changes) {
|
|
356
|
-
collectCodexChange(change.path, change.kind, workingDirectory, changedFiles);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
const widget = itemWidget(chunk.item);
|
|
360
|
-
if (!widget) continue;
|
|
361
|
-
yield uiWidget({
|
|
362
|
-
agentId: context.agentId,
|
|
363
|
-
threadId: openbotThreadId,
|
|
364
|
-
widget: toolTraceWidget({
|
|
365
|
-
widgetId: `codex_${chunk.item.id}`,
|
|
366
|
-
groupId: "codex:tools",
|
|
367
|
-
title: widget.title,
|
|
368
|
-
body: widget.body,
|
|
369
|
-
state: chunk.type === "item.completed" && (chunk.item.type === "command_execution" && chunk.item.status === "failed" || chunk.item.type === "file_change" && chunk.item.status === "failed" || chunk.item.type === "mcp_tool_call" && chunk.item.status === "failed" || chunk.item.type === "error") ? "error" : chunk.type === "item.completed" ? "submitted" : void 0
|
|
370
|
-
})
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
const diff = buildDiffWidget({
|
|
374
|
-
widgetId: `codex-diff:${openbotThreadId ?? "run"}:${Date.now()}`,
|
|
375
|
-
files: resolveRunDiffFiles({
|
|
376
|
-
snapshot,
|
|
377
|
-
fallback: changedFiles.values()
|
|
378
|
-
})
|
|
379
|
-
});
|
|
380
|
-
if (diff) {
|
|
381
|
-
yield uiWidget({
|
|
382
|
-
agentId: context.agentId,
|
|
383
|
-
threadId: openbotThreadId,
|
|
384
|
-
widget: diff
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
} catch (error) {
|
|
388
|
-
yield* fail(errorText(error));
|
|
389
|
-
}
|
|
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
|
-
});
|
|
435
|
-
};
|
|
436
|
-
}
|
|
171
|
+
models: { providers: ["openai"], default: "openai/gpt-5.6-luna" },
|
|
172
|
+
configSchema: pluginConfigSchema,
|
|
173
|
+
emptyPrompt: "Send a message to run Codex in this workspace.",
|
|
174
|
+
run: runCodexTurn
|
|
437
175
|
});
|
|
438
|
-
var
|
|
176
|
+
var src_default = plugin;
|
|
439
177
|
export {
|
|
440
|
-
|
|
178
|
+
src_default as default,
|
|
441
179
|
plugin
|
|
442
180
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetopenbot/codex",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenAI Codex agent plugin for OpenBot",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@openai/codex-sdk": "0.148.0",
|
|
22
|
-
"@meetopenbot/plugin-sdk": "^0.
|
|
22
|
+
"@meetopenbot/plugin-sdk": "^0.4.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^25.6.0",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"scripts": {
|
|
31
|
-
"build": "esbuild index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@meetopenbot/plugin-sdk --external:@openai/codex-sdk --external:zod && node ../../scripts/write-plugin-declaration.mjs",
|
|
32
|
-
"dev": "esbuild index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@meetopenbot/plugin-sdk --external:@openai/codex-sdk --external:zod --watch",
|
|
33
|
-
"typecheck": "tsc
|
|
31
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@meetopenbot/plugin-sdk --external:@openai/codex-sdk --external:zod && node ../../scripts/write-plugin-declaration.mjs",
|
|
32
|
+
"dev": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@meetopenbot/plugin-sdk --external:@openai/codex-sdk --external:zod --watch",
|
|
33
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
34
34
|
}
|
|
35
35
|
}
|