workspacecord 1.0.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.
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils.ts
4
+ import { resolve, isAbsolute } from "path";
5
+ import { homedir } from "os";
6
+ function sanitizeName(name) {
7
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50) || "session";
8
+ }
9
+ function resolvePath(p) {
10
+ if (p.startsWith("~/") || p === "~") {
11
+ return p.replace("~", homedir());
12
+ }
13
+ return isAbsolute(p) ? p : resolve(process.cwd(), p);
14
+ }
15
+ function isPathAllowed(path, allowedPaths) {
16
+ if (allowedPaths.length === 0) return true;
17
+ const resolved = resolvePath(path);
18
+ return allowedPaths.some((allowed) => {
19
+ const resolvedAllowed = resolvePath(allowed);
20
+ return resolved === resolvedAllowed || resolved.startsWith(resolvedAllowed + "/");
21
+ });
22
+ }
23
+ function projectNameFromChannel(channelName) {
24
+ return channelName;
25
+ }
26
+ function formatDuration(ms) {
27
+ const s = Math.floor(ms / 1e3);
28
+ if (s < 60) return `${s}s`;
29
+ const m = Math.floor(s / 60);
30
+ if (m < 60) return `${m}m ${s % 60}s`;
31
+ const h = Math.floor(m / 60);
32
+ return `${h}h ${m % 60}m`;
33
+ }
34
+ function formatRelative(ts) {
35
+ const diff = Date.now() - ts;
36
+ if (diff < 6e4) return "just now";
37
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
38
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
39
+ return `${Math.floor(diff / 864e5)}d ago`;
40
+ }
41
+ function truncate(s, max) {
42
+ if (s.length <= max) return s;
43
+ return s.slice(0, max - 1) + "\u2026";
44
+ }
45
+ function isUserAllowed(userId, allowedUsers, allowAll) {
46
+ if (allowAll) return true;
47
+ if (allowedUsers.length === 0) return true;
48
+ return allowedUsers.includes(userId);
49
+ }
50
+ var ABORT_PATTERNS = ["abort", "cancel", "interrupt", "killed", "signal"];
51
+ function isAbortError(err) {
52
+ if (err instanceof Error && err.name === "AbortError") return true;
53
+ const msg = (err.message || "").toLowerCase();
54
+ return ABORT_PATTERNS.some((p) => msg.includes(p));
55
+ }
56
+ function isAbortErrorMessage(messages) {
57
+ return messages.some((m) => ABORT_PATTERNS.some((p) => m.toLowerCase().includes(p)));
58
+ }
59
+ function detectNumberedOptions(text) {
60
+ const lines = text.trim().split("\n");
61
+ const options = [];
62
+ const optionRegex = /^\s*(\d+)[.)]\s+(.+)$/;
63
+ let firstOptionLine = -1;
64
+ let lastOptionLine = -1;
65
+ for (let i = 0; i < lines.length; i++) {
66
+ const match = lines[i].match(optionRegex);
67
+ if (match) {
68
+ if (firstOptionLine === -1) firstOptionLine = i;
69
+ lastOptionLine = i;
70
+ options.push(match[2].trim());
71
+ }
72
+ }
73
+ if (options.length < 2 || options.length > 6) return null;
74
+ if (options.some((o) => o.length > 80)) return null;
75
+ const linesAfter = lines.slice(lastOptionLine + 1).filter((l) => l.trim()).length;
76
+ if (linesAfter > 3) return null;
77
+ const preamble = lines.slice(0, firstOptionLine).join(" ").toLowerCase();
78
+ const hasQuestion = /\?\s*$/.test(preamble.trim()) || /\b(which|choose|select|pick|prefer|would you like|how would you|what approach|option)\b/.test(
79
+ preamble
80
+ );
81
+ return hasQuestion ? options : null;
82
+ }
83
+ function detectYesNoPrompt(text) {
84
+ const lower = text.toLowerCase();
85
+ return /\b(y\/n|yes\/no|confirm|proceed)\b/.test(lower) || /\?\s*$/.test(text.trim()) && /\b(should|would you|do you want|shall)\b/.test(lower);
86
+ }
87
+ function formatUptime(startTime) {
88
+ const ms = Date.now() - startTime;
89
+ const seconds = Math.floor(ms / 1e3);
90
+ const minutes = Math.floor(seconds / 60);
91
+ const hours = Math.floor(minutes / 60);
92
+ const days = Math.floor(hours / 24);
93
+ if (days > 0) return `${days}d ${hours % 24}h`;
94
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
95
+ if (minutes > 0) return `${minutes}m`;
96
+ return `${seconds}s`;
97
+ }
98
+ function splitMessage(text, max = 1900) {
99
+ if (text.length <= max) return [text];
100
+ const chunks = [];
101
+ let i = 0;
102
+ while (i < text.length) {
103
+ chunks.push(text.slice(i, i + max));
104
+ i += max;
105
+ }
106
+ return chunks;
107
+ }
108
+ function formatCost(usd) {
109
+ if (usd === 0) return "$0.00";
110
+ if (usd < 0.01) return `$${usd.toFixed(4)}`;
111
+ return `$${usd.toFixed(2)}`;
112
+ }
113
+
114
+ export {
115
+ sanitizeName,
116
+ resolvePath,
117
+ isPathAllowed,
118
+ projectNameFromChannel,
119
+ formatDuration,
120
+ formatRelative,
121
+ truncate,
122
+ isUserAllowed,
123
+ isAbortError,
124
+ isAbortErrorMessage,
125
+ detectNumberedOptions,
126
+ detectYesNoPrompt,
127
+ formatUptime,
128
+ splitMessage,
129
+ formatCost
130
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ var command = process.argv[2];
5
+ switch (command) {
6
+ case "setup": {
7
+ const { handleConfig } = await import("./config-cli-F2B5SYHJ.js");
8
+ await handleConfig(["setup"]);
9
+ break;
10
+ }
11
+ case "config": {
12
+ const { handleConfig } = await import("./config-cli-F2B5SYHJ.js");
13
+ await handleConfig(process.argv.slice(3));
14
+ break;
15
+ }
16
+ case "start":
17
+ case void 0: {
18
+ const { startBot } = await import("./bot-B5HN4ZW6.js");
19
+ console.log("workspacecord starting...");
20
+ await startBot();
21
+ break;
22
+ }
23
+ case "project": {
24
+ const { handleProject } = await import("./project-cli-FEMPZIRQ.js");
25
+ await handleProject(process.argv.slice(3));
26
+ break;
27
+ }
28
+ case "daemon": {
29
+ const { handleDaemon } = await import("./daemon-NW4WRMQK.js");
30
+ await handleDaemon(process.argv[3]);
31
+ break;
32
+ }
33
+ case "codex": {
34
+ const { handleCodexCommand } = await import("./codex-launcher-IF2IPLBP.js");
35
+ await handleCodexCommand(process.argv.slice(3));
36
+ break;
37
+ }
38
+ case "help":
39
+ case "--help":
40
+ case "-h": {
41
+ console.log(`
42
+ \x1B[1mworkspacecord\x1B[0m \u2014 Discord bot for multi-agent coding sessions
43
+
44
+ \x1B[1mUsage:\x1B[0m
45
+ workspacecord Start the bot
46
+ workspacecord config setup Interactive configuration wizard
47
+ workspacecord config get <key> Read a config value
48
+ workspacecord config set <k> <v> Write a config value
49
+ workspacecord config list List all config values
50
+ workspacecord config path Show config file path
51
+ workspacecord project <subcommand> Manage mounted projects
52
+ workspacecord daemon Manage background service (install/uninstall/status)
53
+ workspacecord codex [options] Launch managed Codex session with remote approval
54
+ workspacecord help Show this help message
55
+
56
+ \x1B[1mQuick start:\x1B[0m
57
+ 1. workspacecord config setup Configure Discord app, token, permissions
58
+ 2. workspacecord project init Mount a local project
59
+ 3. workspacecord Start the bot
60
+ 4. /project setup project:<name> Bind a Discord category to the mounted project
61
+ 5. /agent spawn label:<task> Create an agent session
62
+
63
+ \x1B[2mhttps://github.com/xuhongbo/WorkspaceCord\x1B[0m
64
+ `);
65
+ break;
66
+ }
67
+ default:
68
+ console.error(`Unknown command: ${command}`);
69
+ console.error("Run \x1B[36mworkspacecord help\x1B[0m for usage.");
70
+ process.exit(1);
71
+ }
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getConfigValue
4
+ } from "./chunk-OKI4UVGY.js";
5
+ import "./chunk-K3NQKI34.js";
6
+
7
+ // src/cli/codex-launcher.ts
8
+ import { spawn } from "child_process";
9
+ import { existsSync } from "fs";
10
+ async function launchManagedCodex(options = {}) {
11
+ const cwd = options.cwd || process.cwd();
12
+ const codexBin = resolveCodexPath();
13
+ if (!existsSync(cwd)) {
14
+ console.error(`\u274C \u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728: ${cwd}`);
15
+ process.exit(1);
16
+ }
17
+ const codexArgs = [];
18
+ if (options.model) {
19
+ codexArgs.push("--model", options.model);
20
+ }
21
+ if (options.sandboxMode) {
22
+ codexArgs.push("--sandbox-mode", options.sandboxMode);
23
+ }
24
+ if (options.approvalPolicy) {
25
+ codexArgs.push("--approval-policy", options.approvalPolicy);
26
+ }
27
+ const env = {
28
+ ...process.env,
29
+ workspacecord_MANAGED: "1",
30
+ workspacecord_SESSION_CWD: cwd
31
+ };
32
+ if (options.args) {
33
+ codexArgs.push(...options.args);
34
+ }
35
+ console.log("\u{1F680} \u542F\u52A8\u53D7\u7BA1 Codex \u4F1A\u8BDD...");
36
+ console.log(`\u{1F4C1} \u5DE5\u4F5C\u76EE\u5F55: ${cwd}`);
37
+ console.log(`\u{1F527} \u53C2\u6570: ${codexArgs.join(" ")}`);
38
+ console.log("");
39
+ console.log("\u{1F4A1} \u6B64\u4F1A\u8BDD\u652F\u6301 Discord \u8FDC\u7A0B\u5BA1\u6279\u80FD\u529B");
40
+ console.log(" \u5728 Discord \u4E2D\u53EF\u4EE5\u8FDC\u7A0B\u5141\u8BB8/\u62D2\u7EDD\u64CD\u4F5C");
41
+ console.log("");
42
+ const codex = spawn(codexBin, codexArgs, {
43
+ cwd,
44
+ env,
45
+ stdio: "inherit"
46
+ });
47
+ codex.on("error", (err) => {
48
+ console.error("\u274C \u542F\u52A8 Codex \u5931\u8D25:", err.message);
49
+ console.error("");
50
+ console.error("\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 Codex CLI:");
51
+ console.error(" npm install -g @openai/codex");
52
+ process.exit(1);
53
+ });
54
+ codex.on("exit", (code) => {
55
+ if (code !== 0 && code !== null) {
56
+ console.error(`
57
+ \u274C Codex \u9000\u51FA\uFF0C\u4EE3\u7801: ${code}`);
58
+ process.exit(code);
59
+ }
60
+ });
61
+ }
62
+ function resolveCodexPath() {
63
+ return process.env.CODEX_PATH || getConfigValue("CODEX_PATH") || "codex";
64
+ }
65
+ function isManagedSession() {
66
+ return process.env.workspacecord_MANAGED === "1";
67
+ }
68
+ function getManagedSessionCwd() {
69
+ return process.env.workspacecord_SESSION_CWD;
70
+ }
71
+ async function handleCodexCommand(args) {
72
+ const options = {};
73
+ for (let i = 0; i < args.length; i++) {
74
+ const arg = args[i];
75
+ switch (arg) {
76
+ case "--cwd":
77
+ options.cwd = args[++i];
78
+ break;
79
+ case "--model":
80
+ options.model = args[++i];
81
+ break;
82
+ case "--sandbox-mode":
83
+ options.sandboxMode = args[++i];
84
+ break;
85
+ case "--approval-policy":
86
+ options.approvalPolicy = args[++i];
87
+ break;
88
+ case "--help":
89
+ case "-h":
90
+ printHelp();
91
+ return;
92
+ default:
93
+ if (!options.args) options.args = [];
94
+ options.args.push(arg);
95
+ }
96
+ }
97
+ await launchManagedCodex(options);
98
+ }
99
+ function printHelp() {
100
+ console.log(`
101
+ \x1B[1mworkspacecord codex\x1B[0m \u2014 \u542F\u52A8\u53D7\u7BA1 Codex \u4F1A\u8BDD
102
+
103
+ \x1B[1m\u7528\u6CD5:\x1B[0m
104
+ workspacecord codex [\u9009\u9879]
105
+
106
+ \x1B[1m\u9009\u9879:\x1B[0m
107
+ --cwd <path> \u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\uFF1A\u5F53\u524D\u76EE\u5F55\uFF09
108
+ --model <model> \u6A21\u578B\u540D\u79F0\uFF08\u5982\uFF1Agpt-4\uFF09
109
+ --sandbox-mode <mode> \u6C99\u7BB1\u6A21\u5F0F\uFF1Aread-only | workspace-write | danger-full-access
110
+ --approval-policy <policy> \u5BA1\u6279\u7B56\u7565\uFF1Anever | on-request | on-failure | untrusted
111
+ --help, -h \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F
112
+
113
+ \x1B[1m\u53D7\u7BA1\u4F1A\u8BDD\u7279\u6027:\x1B[0m
114
+ \u2713 \u5FEB\u901F\u53D1\u73B0 - \u4F1A\u8BDD\u81EA\u52A8\u540C\u6B65\u5230 Discord
115
+ \u2713 \u72B6\u6001\u76D1\u63A7 - \u5B9E\u65F6\u663E\u793A\u6267\u884C\u72B6\u6001
116
+ \u2713 \u8FDC\u7A0B\u5BA1\u6279 - \u5728 Discord \u4E2D\u5141\u8BB8/\u62D2\u7EDD\u64CD\u4F5C
117
+ \u2713 \u7EC8\u7AEF\u5904\u7406 - \u4ECD\u53EF\u5728\u7EC8\u7AEF\u76F4\u63A5\u5904\u7406
118
+
119
+ \x1B[1m\u793A\u4F8B:\x1B[0m
120
+ workspacecord codex
121
+ workspacecord codex --cwd /path/to/project
122
+ workspacecord codex --model gpt-4 --sandbox-mode workspace-write
123
+
124
+ \x1B[2m\u66F4\u591A\u4FE1\u606F: https://github.com/xuhongbo/workspacecord\x1B[0m
125
+ `);
126
+ }
127
+ export {
128
+ getManagedSessionCwd,
129
+ handleCodexCommand,
130
+ isManagedSession,
131
+ launchManagedCodex
132
+ };
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ config
4
+ } from "./chunk-2LBNM64L.js";
5
+ import "./chunk-OKI4UVGY.js";
6
+ import "./chunk-K3NQKI34.js";
7
+
8
+ // src/providers/codex-provider.ts
9
+ import { writeFileSync, readFileSync, unlinkSync, existsSync, mkdtempSync } from "fs";
10
+ import { join } from "path";
11
+ import { tmpdir } from "os";
12
+
13
+ // src/providers/codex/helpers.ts
14
+ function buildCodexOptions() {
15
+ const codexOpts = {};
16
+ if (config.codexApiKey) codexOpts.apiKey = config.codexApiKey;
17
+ if (config.codexBaseUrl) codexOpts.baseUrl = config.codexBaseUrl;
18
+ if (config.codexPath) codexOpts.codexPathOverride = config.codexPath;
19
+ return codexOpts;
20
+ }
21
+ function buildThreadOptions(options) {
22
+ const threadOptions = {
23
+ workingDirectory: options.directory,
24
+ skipGitRepoCheck: true
25
+ };
26
+ if (options.model) threadOptions.model = options.model;
27
+ if (options.sandboxMode) threadOptions.sandboxMode = options.sandboxMode;
28
+ if (options.approvalPolicy) threadOptions.approvalPolicy = options.approvalPolicy;
29
+ if (options.networkAccessEnabled !== void 0) {
30
+ threadOptions.networkAccessEnabled = options.networkAccessEnabled;
31
+ }
32
+ if (options.webSearchMode && options.webSearchMode !== "disabled") {
33
+ threadOptions.webSearchMode = options.webSearchMode;
34
+ }
35
+ if (options.modelReasoningEffort) {
36
+ threadOptions.modelReasoningEffort = options.modelReasoningEffort;
37
+ }
38
+ return threadOptions;
39
+ }
40
+ function parseFileChanges(item) {
41
+ const raw = item.changes || item.files || [];
42
+ return raw.map((f) => ({
43
+ filePath: String(f.path || f.file_path || f.filePath || ""),
44
+ changeKind: f.kind || f.change_kind || f.changeKind || "update"
45
+ }));
46
+ }
47
+ function parseTodoItems(item) {
48
+ const raw = item.items || item.todos || [];
49
+ return raw.map((t) => ({
50
+ text: String(t.text || t.description || ""),
51
+ completed: Boolean(t.completed ?? t.done ?? false)
52
+ }));
53
+ }
54
+
55
+ // src/providers/codex-provider.ts
56
+ var Codex = null;
57
+ async function loadSdk() {
58
+ if (Codex) return;
59
+ const mod = await import("@openai/codex-sdk");
60
+ Codex = mod.Codex;
61
+ }
62
+ var SENTINEL_START = "<!-- workspacecord-persona-start -->";
63
+ var SENTINEL_END = "<!-- workspacecord-persona-end -->";
64
+ function stripInjectedAgentsMd(content) {
65
+ return content.replace(
66
+ new RegExp(`${escapeRegex(SENTINEL_START)}[\\s\\S]*?${escapeRegex(SENTINEL_END)}\\n?`, "g"),
67
+ ""
68
+ );
69
+ }
70
+ function injectAgentsMd(directory, parts) {
71
+ if (parts.length === 0) return null;
72
+ const agentsPath = join(directory, "AGENTS.md");
73
+ const injected = `${SENTINEL_START}
74
+ ${parts.join("\n\n")}
75
+ ${SENTINEL_END}`;
76
+ const existed = existsSync(agentsPath);
77
+ const current = existed ? readFileSync(agentsPath, "utf-8") : "";
78
+ const cleaned = stripInjectedAgentsMd(current).trimEnd();
79
+ writeFileSync(agentsPath, cleaned ? `${cleaned}
80
+
81
+ ${injected}
82
+ ` : `${injected}
83
+ `, "utf-8");
84
+ return { existed };
85
+ }
86
+ function restoreAgentsMd(directory, state) {
87
+ if (!state) return;
88
+ const agentsPath = join(directory, "AGENTS.md");
89
+ if (!existsSync(agentsPath)) return;
90
+ const cleaned = stripInjectedAgentsMd(readFileSync(agentsPath, "utf-8")).trimEnd();
91
+ if (!state.existed && !cleaned) {
92
+ try {
93
+ unlinkSync(agentsPath);
94
+ } catch {
95
+ }
96
+ return;
97
+ }
98
+ writeFileSync(agentsPath, cleaned ? `${cleaned}
99
+ ` : "", "utf-8");
100
+ }
101
+ function escapeRegex(s) {
102
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
103
+ }
104
+ function writeImagesToTemp(blocks) {
105
+ const textParts = [];
106
+ const localImages = [];
107
+ for (const block of blocks) {
108
+ if (block.type === "text") {
109
+ textParts.push(block.text);
110
+ } else if (block.type === "image") {
111
+ const dir = mkdtempSync(join(tmpdir(), "workspacecord-img-"));
112
+ const ext = block.source.media_type.split("/")[1] || "png";
113
+ const filePath = join(dir, `image.${ext}`);
114
+ writeFileSync(filePath, Buffer.from(block.source.data, "base64"));
115
+ localImages.push({ type: "local_image", path: filePath });
116
+ } else if (block.type === "local_image") {
117
+ localImages.push(block);
118
+ }
119
+ }
120
+ return { textParts, localImages };
121
+ }
122
+ var CodexProvider = class {
123
+ name = "codex";
124
+ supports(feature) {
125
+ return ["command_execution", "file_changes", "reasoning", "todo_list", "continue"].includes(
126
+ feature
127
+ );
128
+ }
129
+ async *sendPrompt(prompt, options) {
130
+ await loadSdk();
131
+ let input;
132
+ if (typeof prompt === "string") {
133
+ input = prompt;
134
+ } else {
135
+ const { textParts, localImages } = writeImagesToTemp(prompt);
136
+ const inputParts = [];
137
+ for (const img of localImages) {
138
+ inputParts.push({ type: "local_image", path: img.path });
139
+ }
140
+ if (textParts.length > 0) {
141
+ inputParts.push({ type: "text", text: textParts.join("\n") });
142
+ }
143
+ input = inputParts.length === 1 && inputParts[0].type === "text" ? inputParts[0].text ?? "" : inputParts;
144
+ }
145
+ let originalAgents = null;
146
+ try {
147
+ originalAgents = injectAgentsMd(options.directory, options.systemPromptParts);
148
+ const codex = new Codex(buildCodexOptions());
149
+ const threadOptions = buildThreadOptions(options);
150
+ const thread = options.providerSessionId ? codex.resumeThread(options.providerSessionId, threadOptions) : codex.startThread(threadOptions);
151
+ const { events } = await thread.runStreamed(input);
152
+ yield* this.translateEvents(events, options.abortController);
153
+ } finally {
154
+ restoreAgentsMd(options.directory, originalAgents);
155
+ }
156
+ }
157
+ async *continueSession(options) {
158
+ await loadSdk();
159
+ if (!options.providerSessionId) {
160
+ yield { type: "error", message: "No session to continue \u2014 no previous thread ID." };
161
+ return;
162
+ }
163
+ let originalAgents = null;
164
+ try {
165
+ originalAgents = injectAgentsMd(options.directory, options.systemPromptParts);
166
+ const codex = new Codex(buildCodexOptions());
167
+ const thread = codex.resumeThread(options.providerSessionId, buildThreadOptions(options));
168
+ const { events } = await thread.runStreamed("Continue from where you left off.");
169
+ yield* this.translateEvents(events, options.abortController);
170
+ } finally {
171
+ restoreAgentsMd(options.directory, originalAgents);
172
+ }
173
+ }
174
+ async *translateEvents(events, abortController) {
175
+ const messageText = /* @__PURE__ */ new Map();
176
+ const startTime = Date.now();
177
+ try {
178
+ for await (const event of events) {
179
+ if (abortController.signal.aborted) break;
180
+ switch (event.type) {
181
+ case "thread.started":
182
+ yield { type: "session_init", providerSessionId: event.thread_id || "" };
183
+ break;
184
+ case "item.started":
185
+ case "item.updated": {
186
+ const item = event.item;
187
+ if (!item) break;
188
+ if (item.type === "agent_message") {
189
+ const itemId = String(item.id || "");
190
+ const prev = messageText.get(itemId) || "";
191
+ const text = String(item.text || "");
192
+ if (text.length > prev.length) {
193
+ yield { type: "text_delta", text: text.slice(prev.length) };
194
+ messageText.set(itemId, text);
195
+ }
196
+ }
197
+ if (item.type === "reasoning" && event.type === "item.updated") {
198
+ const text = String(item.summary || item.content || "");
199
+ if (text) yield { type: "reasoning", text };
200
+ }
201
+ break;
202
+ }
203
+ case "item.completed": {
204
+ const item = event.item;
205
+ if (!item) break;
206
+ switch (item.type) {
207
+ case "agent_message": {
208
+ const itemId = String(item.id || "");
209
+ const prev = messageText.get(itemId) || "";
210
+ const text = String(item.text || "");
211
+ if (text.length > prev.length) {
212
+ yield { type: "text_delta", text: text.slice(prev.length) };
213
+ }
214
+ messageText.delete(itemId);
215
+ break;
216
+ }
217
+ case "command_execution":
218
+ yield {
219
+ type: "command_execution",
220
+ command: String(item.command || ""),
221
+ output: String(item.aggregated_output ?? item.output ?? ""),
222
+ exitCode: typeof item.exit_code === "number" ? item.exit_code : typeof item.exitCode === "number" ? item.exitCode : null,
223
+ status: String(item.status || "completed")
224
+ };
225
+ break;
226
+ case "file_change": {
227
+ const changes = parseFileChanges(item);
228
+ if (changes.length > 0) yield { type: "file_change", changes };
229
+ break;
230
+ }
231
+ case "reasoning": {
232
+ const text = String(item.summary || item.content || "");
233
+ if (text) yield { type: "reasoning", text };
234
+ break;
235
+ }
236
+ case "todo_list": {
237
+ const items = parseTodoItems(item);
238
+ if (items.length > 0) yield { type: "todo_list", items };
239
+ break;
240
+ }
241
+ case "mcp_tool_call":
242
+ yield {
243
+ type: "tool_start",
244
+ toolName: `${String(item.server || "")}/${String(item.tool || "")}`,
245
+ toolInput: JSON.stringify(item.arguments || item.input || {})
246
+ };
247
+ if (item.status === "completed" || item.status === "failed") {
248
+ yield {
249
+ type: "tool_result",
250
+ toolName: `${String(item.server || "")}/${String(item.tool || "")}`,
251
+ result: typeof item.output === "string" ? item.output : JSON.stringify(item.output || ""),
252
+ isError: item.status === "failed"
253
+ };
254
+ }
255
+ break;
256
+ case "web_search":
257
+ yield { type: "web_search", query: String(item.query || "") };
258
+ break;
259
+ case "error":
260
+ yield { type: "error", message: String(item.message || "Unknown error") };
261
+ break;
262
+ }
263
+ break;
264
+ }
265
+ case "turn.completed": {
266
+ const usage = event.usage;
267
+ const inputTokens = usage?.input_tokens || 0;
268
+ const outputTokens = usage?.output_tokens || 0;
269
+ const costUsd = (inputTokens * 2 + outputTokens * 8) / 1e6;
270
+ yield {
271
+ type: "result",
272
+ success: true,
273
+ costUsd,
274
+ durationMs: Date.now() - startTime,
275
+ numTurns: 1,
276
+ errors: []
277
+ };
278
+ break;
279
+ }
280
+ case "turn.failed":
281
+ yield {
282
+ type: "result",
283
+ success: false,
284
+ costUsd: 0,
285
+ durationMs: Date.now() - startTime,
286
+ numTurns: 1,
287
+ errors: [event.error || "Turn failed"]
288
+ };
289
+ break;
290
+ case "error":
291
+ yield { type: "error", message: event.message || "Unknown error" };
292
+ break;
293
+ }
294
+ }
295
+ } catch (err) {
296
+ if (!abortController.signal.aborted) {
297
+ yield { type: "error", message: err.message || "Codex stream error" };
298
+ }
299
+ }
300
+ }
301
+ };
302
+ export {
303
+ CodexProvider
304
+ };