@love-moon/ai-sdk 0.2.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.
@@ -0,0 +1,284 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { spawn } from "node:child_process";
3
+ import readline from "node:readline";
4
+ import { emitLog, normalizeLogger, parseCommandParts, sanitizeForLog, serializeError, } from "../shared.js";
5
+ const DEFAULT_CODEX_APP_SERVER_COMMAND = "codex app-server --listen stdio://";
6
+ function createRpcError(payload) {
7
+ const message = String(payload?.message || payload?.error?.message || "Codex app-server request failed");
8
+ const error = new Error(message);
9
+ const source = payload?.error && typeof payload.error === "object" ? payload.error : payload;
10
+ if (source && typeof source === "object") {
11
+ if (source.code !== undefined) {
12
+ error.code = source.code;
13
+ }
14
+ if (source.data !== undefined) {
15
+ error.data = source.data;
16
+ }
17
+ }
18
+ return error;
19
+ }
20
+ export class CodexAppServerTransport extends EventEmitter {
21
+ constructor(options = {}) {
22
+ super();
23
+ this.options = options;
24
+ this.logger = normalizeLogger(options.logger);
25
+ this.cwd =
26
+ typeof options.cwd === "string" && options.cwd.trim()
27
+ ? options.cwd.trim()
28
+ : process.cwd();
29
+ const commandLine = process.env.CONDUCTOR_CODEX_APP_SERVER_COMMAND ||
30
+ options.commandLine ||
31
+ DEFAULT_CODEX_APP_SERVER_COMMAND;
32
+ const { command, args } = parseCommandParts(commandLine);
33
+ if (!command) {
34
+ throw new Error("Invalid codex app-server command");
35
+ }
36
+ this.command = command;
37
+ this.args = args;
38
+ this.env = options.env && typeof options.env === "object" ? { ...options.env } : {};
39
+ this.child = null;
40
+ this.stdoutReader = null;
41
+ this.stderrReader = null;
42
+ this.pending = new Map();
43
+ this.nextRequestId = 1;
44
+ this.bootPromise = null;
45
+ this.booted = false;
46
+ this.closeRequested = false;
47
+ this.closed = false;
48
+ this.stderrTail = [];
49
+ this.stderrTailMax = 20;
50
+ this.protocolTrace = process.env.CONDUCTOR_CODEX_APP_SERVER_TRACE === "1";
51
+ }
52
+ log(message) {
53
+ emitLog(this.logger, message);
54
+ }
55
+ async boot() {
56
+ if (this.booted) {
57
+ return;
58
+ }
59
+ if (this.bootPromise) {
60
+ return this.bootPromise;
61
+ }
62
+ this.bootPromise = this.bootInternal();
63
+ try {
64
+ await this.bootPromise;
65
+ this.booted = true;
66
+ }
67
+ finally {
68
+ this.bootPromise = null;
69
+ }
70
+ }
71
+ async bootInternal() {
72
+ this.spawnChild();
73
+ const initializeResult = await this.requestRaw("initialize", {
74
+ clientInfo: {
75
+ name: "conductor-ai-sdk",
76
+ version: "0.0.0",
77
+ },
78
+ capabilities: {
79
+ experimentalApi: true,
80
+ },
81
+ });
82
+ this.log(`[codex-app-server] initialized ua="${sanitizeForLog(initializeResult?.userAgent || "", 160)}" cwd=${this.cwd}`);
83
+ this.sendNotification("initialized", {});
84
+ }
85
+ spawnChild() {
86
+ if (this.child) {
87
+ return;
88
+ }
89
+ this.log(`[codex-app-server] spawn ${[this.command, ...this.args].join(" ")} (cwd: ${this.cwd})`);
90
+ const child = spawn(this.command, this.args, {
91
+ cwd: this.cwd,
92
+ env: {
93
+ ...process.env,
94
+ ...this.env,
95
+ },
96
+ stdio: ["pipe", "pipe", "pipe"],
97
+ });
98
+ this.child = child;
99
+ this.stdoutReader = readline.createInterface({ input: child.stdout });
100
+ this.stdoutReader.on("line", (line) => {
101
+ this.handleStdoutLine(line);
102
+ });
103
+ this.stderrReader = readline.createInterface({ input: child.stderr });
104
+ this.stderrReader.on("line", (line) => {
105
+ this.handleStderrLine(line);
106
+ });
107
+ child.on("error", (error) => {
108
+ this.failPendingRequests(error);
109
+ this.emit("process_error", serializeError(error));
110
+ });
111
+ child.on("exit", (code, signal) => {
112
+ const info = {
113
+ code,
114
+ signal,
115
+ stderr: this.stderrTail.slice(),
116
+ };
117
+ const reason = this.closeRequested
118
+ ? new Error("Codex app-server closed")
119
+ : new Error(`Codex app-server exited (code=${code ?? "null"} signal=${signal ?? "null"})`);
120
+ reason.reason = this.closeRequested ? "session_closed" : "transport_exited";
121
+ reason.exitCode = code;
122
+ reason.signal = signal;
123
+ reason.stderr = info.stderr;
124
+ this.failPendingRequests(reason);
125
+ this.closed = true;
126
+ this.emit("process_exit", info);
127
+ });
128
+ }
129
+ handleStdoutLine(line) {
130
+ const normalized = String(line || "").trim();
131
+ if (!normalized) {
132
+ return;
133
+ }
134
+ if (this.protocolTrace) {
135
+ this.log(`[codex-app-server] stdout ${sanitizeForLog(normalized, 400)}`);
136
+ }
137
+ let payload;
138
+ try {
139
+ payload = JSON.parse(normalized);
140
+ }
141
+ catch {
142
+ this.log(`[codex-app-server] invalid stdout line: ${sanitizeForLog(normalized, 240)}`);
143
+ return;
144
+ }
145
+ if (payload && Object.prototype.hasOwnProperty.call(payload, "id")) {
146
+ const pending = this.pending.get(payload.id);
147
+ if (!pending) {
148
+ return;
149
+ }
150
+ this.pending.delete(payload.id);
151
+ if (payload.error) {
152
+ pending.reject(createRpcError(payload));
153
+ return;
154
+ }
155
+ pending.resolve(payload.result);
156
+ return;
157
+ }
158
+ if (payload?.method) {
159
+ this.emit("notification", {
160
+ method: payload.method,
161
+ params: payload.params ?? {},
162
+ });
163
+ }
164
+ }
165
+ handleStderrLine(line) {
166
+ const normalized = String(line || "");
167
+ if (!normalized.trim()) {
168
+ return;
169
+ }
170
+ this.stderrTail.push(normalized);
171
+ if (this.stderrTail.length > this.stderrTailMax) {
172
+ this.stderrTail.shift();
173
+ }
174
+ this.log(`[codex-app-server] stderr ${sanitizeForLog(normalized, 300)}`);
175
+ this.emit("stderr", { line: normalized });
176
+ }
177
+ failPendingRequests(error) {
178
+ if (this.pending.size === 0) {
179
+ return;
180
+ }
181
+ for (const pending of this.pending.values()) {
182
+ pending.reject(error);
183
+ }
184
+ this.pending.clear();
185
+ }
186
+ async request(method, params = {}) {
187
+ await this.boot();
188
+ return this.requestRaw(method, params);
189
+ }
190
+ requestRaw(method, params = {}) {
191
+ if (!this.child || this.closed) {
192
+ const error = new Error("Codex app-server transport is not running");
193
+ error.reason = "transport_not_running";
194
+ return Promise.reject(error);
195
+ }
196
+ const id = this.nextRequestId++;
197
+ const payload = {
198
+ jsonrpc: "2.0",
199
+ id,
200
+ method,
201
+ params,
202
+ };
203
+ if (this.protocolTrace) {
204
+ this.log(`[codex-app-server] -> ${sanitizeForLog(JSON.stringify(payload), 400)}`);
205
+ }
206
+ return new Promise((resolve, reject) => {
207
+ this.pending.set(id, { resolve, reject });
208
+ try {
209
+ this.child.stdin.write(`${JSON.stringify(payload)}\n`);
210
+ }
211
+ catch (error) {
212
+ this.pending.delete(id);
213
+ reject(error);
214
+ }
215
+ });
216
+ }
217
+ sendNotification(method, params = {}) {
218
+ if (!this.child || this.closed) {
219
+ return;
220
+ }
221
+ const payload = {
222
+ jsonrpc: "2.0",
223
+ method,
224
+ params,
225
+ };
226
+ if (this.protocolTrace) {
227
+ this.log(`[codex-app-server] -> ${sanitizeForLog(JSON.stringify(payload), 400)}`);
228
+ }
229
+ try {
230
+ this.child.stdin.write(`${JSON.stringify(payload)}\n`);
231
+ }
232
+ catch {
233
+ // best effort
234
+ }
235
+ }
236
+ get pid() {
237
+ return this.child?.pid || null;
238
+ }
239
+ getRecentStderr() {
240
+ return this.stderrTail.slice();
241
+ }
242
+ async close() {
243
+ if (this.closed) {
244
+ return;
245
+ }
246
+ this.closeRequested = true;
247
+ if (!this.child) {
248
+ this.closed = true;
249
+ return;
250
+ }
251
+ const child = this.child;
252
+ await new Promise((resolve) => {
253
+ let settled = false;
254
+ const finalize = () => {
255
+ if (settled) {
256
+ return;
257
+ }
258
+ settled = true;
259
+ resolve();
260
+ };
261
+ child.once("exit", finalize);
262
+ try {
263
+ child.kill("SIGTERM");
264
+ }
265
+ catch {
266
+ finalize();
267
+ return;
268
+ }
269
+ const timer = setTimeout(() => {
270
+ try {
271
+ child.kill("SIGKILL");
272
+ }
273
+ catch {
274
+ // ignore
275
+ }
276
+ finalize();
277
+ }, 1500);
278
+ if (typeof timer.unref === "function") {
279
+ timer.unref();
280
+ }
281
+ });
282
+ this.closed = true;
283
+ }
284
+ }
@@ -0,0 +1,153 @@
1
+ export function profileNameForBackend(backend: any): any;
2
+ export function parseCommandParts(commandLine: any): {
3
+ command: string;
4
+ args: string[];
5
+ };
6
+ export function buildResumeArgsForBackend(backend: any, sessionId: any): string[];
7
+ export function createAiSession(backend: any, options?: {}): TuiAiSession;
8
+ export class TuiAiSession extends EventEmitter<[never]> {
9
+ constructor(backend: any, options?: {});
10
+ backend: any;
11
+ options: {};
12
+ logger: any;
13
+ cwd: any;
14
+ sessionId: any;
15
+ history: any[];
16
+ pendingHistorySeed: boolean;
17
+ sessionInfo: {
18
+ backend: any;
19
+ sessionId: any;
20
+ sessionFilePath: any;
21
+ } | null;
22
+ command: string;
23
+ args: string[];
24
+ tuiDebug: boolean;
25
+ tuiTrace: boolean;
26
+ tuiTraceLines: number;
27
+ lastSignalSignature: string;
28
+ lastPollSignature: string;
29
+ lastSnapshotHash: string;
30
+ closeRequested: boolean;
31
+ closed: boolean;
32
+ closeWaiters: Set<any>;
33
+ sessionMessageHandler: any;
34
+ sessionMonitorPromise: Promise<void> | null;
35
+ sessionMonitorStopRequested: boolean;
36
+ sessionMonitorCursor: number;
37
+ sessionMonitorSessionId: string;
38
+ sessionMonitorSessionFilePath: string;
39
+ sessionMonitorActiveReplyTo: string;
40
+ sessionMonitorHasActiveReplyTarget: boolean;
41
+ sessionMonitorLastReplyTo: string;
42
+ sessionMonitorAwaitingFirstReply: boolean;
43
+ workingStatusHandler: any;
44
+ workingStatusMonitorPromise: Promise<void> | null;
45
+ workingStatusMonitorStopRequested: boolean;
46
+ lastReportedWorkingStatusLine: string;
47
+ turnDeadlineMs: any;
48
+ useSessionFileReplyStream: boolean;
49
+ sessionMonitorFastPollMs: any;
50
+ sessionMonitorSlowPollMs: any;
51
+ workingStatusPollMs: any;
52
+ driver: TuiDriver;
53
+ writeLog(message: any): void;
54
+ get threadId(): any;
55
+ get threadOptions(): {
56
+ model: any;
57
+ };
58
+ getSnapshot(): {
59
+ backend: any;
60
+ command: string;
61
+ args: string[];
62
+ cwd: any;
63
+ sessionId: any;
64
+ sessionInfo: {
65
+ backend: any;
66
+ sessionId: any;
67
+ sessionFilePath: any;
68
+ } | null;
69
+ useSessionFileReplyStream: boolean;
70
+ };
71
+ applySessionInfo(session: any): void;
72
+ getSessionInfo(): {
73
+ backend: any;
74
+ sessionId: any;
75
+ sessionFilePath: any;
76
+ } | null;
77
+ ensureSessionInfo(): Promise<{
78
+ backend: any;
79
+ sessionId: any;
80
+ sessionFilePath: any;
81
+ } | null>;
82
+ getSessionUsageSummary(): Promise<import("@love-moon/tui-driver").TuiSessionUsageSummary | null>;
83
+ usesSessionFileReplyStream(): boolean;
84
+ setSessionMessageHandler(handler: any): void;
85
+ setWorkingStatusHandler(handler: any): void;
86
+ setSessionReplyTarget(replyTo: any): void;
87
+ ensureSessionFileMonitor(): Promise<void>;
88
+ ensureWorkingStatusMonitor(): Promise<void>;
89
+ runSessionFileMonitor(): Promise<void>;
90
+ runWorkingStatusMonitor(): Promise<void>;
91
+ resolveSessionMonitorPollMs(): any;
92
+ normalizeCodexWorkingStatusLine(statusLine: any): string;
93
+ normalizeCopilotWorkingStatusLine(statusLine: any): string;
94
+ normalizeWorkingStatusLine(statusLine: any): string;
95
+ getCurrentReplyTarget(): string | undefined;
96
+ pollWorkingStatus(): Promise<void>;
97
+ pollSessionFileMessages(): Promise<void>;
98
+ createSessionClosedError(): Error;
99
+ createTurnTimeoutError(timeoutMs: any): Error;
100
+ createCloseGuard(): {
101
+ promise: Promise<any>;
102
+ cleanup: () => void;
103
+ };
104
+ createTurnTimeoutGuard(): {
105
+ promise: Promise<any>;
106
+ cleanup: () => void;
107
+ };
108
+ flushCloseWaiters(): void;
109
+ close(): Promise<void>;
110
+ getHealthStatus(): import("@love-moon/tui-driver").HealthStatus | {
111
+ healthy: boolean;
112
+ reason: string;
113
+ message: string;
114
+ };
115
+ buildPrompt(promptText: any, { useInitialImages }?: {
116
+ useInitialImages?: boolean | undefined;
117
+ }): string;
118
+ emitProgress(onProgress: any, payload: any): void;
119
+ trace(message: any): void;
120
+ formatSignalSummary(signals?: {}): {
121
+ prompt: string | undefined;
122
+ replyInProgress: boolean;
123
+ status: string | undefined;
124
+ done: string | undefined;
125
+ replyPreview: string | undefined;
126
+ blocks: any;
127
+ };
128
+ logSignals(state: any, signals: any, snapshot: any): void;
129
+ logSnapshot(state: any, snapshot: any): void;
130
+ runTurn(promptText: any, { useInitialImages, onProgress }?: {
131
+ useInitialImages?: boolean | undefined;
132
+ }): Promise<{
133
+ text: string;
134
+ usage: null;
135
+ items: never[];
136
+ events: never[];
137
+ provider?: undefined;
138
+ metadata?: undefined;
139
+ } | {
140
+ text: string;
141
+ usage: null;
142
+ items: never[];
143
+ events: never[];
144
+ provider: any;
145
+ metadata: {
146
+ source: string;
147
+ elapsed_ms: any;
148
+ signals: any;
149
+ };
150
+ }>;
151
+ }
152
+ import { EventEmitter } from "node:events";
153
+ import { TuiDriver } from "@love-moon/tui-driver";