u-foo 2.3.10 → 2.3.12
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/package.json +3 -1
- package/src/agent/activityStateWriter.js +14 -3
- package/src/agent/claudeThreadProvider.js +208 -39
- package/src/agent/codexThreadProvider.js +125 -64
- package/src/agent/internalRunner.js +191 -22
- package/src/agent/notifier.js +15 -4
- package/src/agent/ptyRunner.js +8 -92
- package/src/bus/index.js +2 -1
- package/src/bus/store.js +17 -5
- package/src/bus/subscriber.js +57 -1
- package/src/bus/utils.js +6 -0
- package/src/chat/agentViewController.js +4 -1
- package/src/chat/dashboardKeyController.js +17 -2
- package/src/ufoo/agentRegistryDiagnostics.js +91 -0
- package/src/ufoo/agentsStore.js +38 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u-foo",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.12",
|
|
4
4
|
"description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://ufoo.dev",
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"bench:global-switch": "node scripts/global-chat-switch-benchmark.js"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.138",
|
|
49
|
+
"@openai/codex-sdk": "^0.130.0",
|
|
48
50
|
"blessed": "^0.1.81",
|
|
49
51
|
"chalk": "^4.1.2",
|
|
50
52
|
"commander": "^13.1.0",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
|
+
const { readJSON, writeJSON } = require("../bus/utils");
|
|
3
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Centralized helper for writing activity_state to all-agents.json.
|
|
@@ -12,8 +14,17 @@ function writeActivityState(agentsFilePath, subscriber, state, options = {}) {
|
|
|
12
14
|
const { since, force = false } = options;
|
|
13
15
|
try {
|
|
14
16
|
if (!agentsFilePath || !fs.existsSync(agentsFilePath)) return false;
|
|
15
|
-
const data =
|
|
16
|
-
if (!data
|
|
17
|
+
const data = readJSON(agentsFilePath, null);
|
|
18
|
+
if (!data) return false;
|
|
19
|
+
if (!data.agents || !data.agents[subscriber]) {
|
|
20
|
+
appendAgentRegistryDiagnostic(agentsFilePath, "activity_state_subscriber_missing", {
|
|
21
|
+
source: "agent.activityStateWriter.writeActivityState",
|
|
22
|
+
subscriber,
|
|
23
|
+
state,
|
|
24
|
+
known_ids: Object.keys(data.agents || {}).sort(),
|
|
25
|
+
});
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
17
28
|
|
|
18
29
|
const current = data.agents[subscriber].activity_state;
|
|
19
30
|
|
|
@@ -30,7 +41,7 @@ function writeActivityState(agentsFilePath, subscriber, state, options = {}) {
|
|
|
30
41
|
data.agents[subscriber].activity_since = since
|
|
31
42
|
? new Date(since).toISOString()
|
|
32
43
|
: new Date().toISOString();
|
|
33
|
-
|
|
44
|
+
writeJSON(agentsFilePath, data);
|
|
34
45
|
return true;
|
|
35
46
|
} catch {
|
|
36
47
|
return false;
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
const {
|
|
4
4
|
createClaudeEventState,
|
|
5
5
|
normalizeClaudeEvent,
|
|
6
|
+
normalizeClaudeMessage,
|
|
7
|
+
normalizeClaudeUsage,
|
|
6
8
|
} = require("./claudeEventTranslator");
|
|
7
9
|
const { redactUfooEvent } = require("../providerapi/redactor");
|
|
8
|
-
const { sendUpstreamRequest } = require("./upstreamTransport");
|
|
9
10
|
|
|
10
11
|
const CACHE_CONTROL = Object.freeze({ type: "ephemeral" });
|
|
11
12
|
|
|
@@ -26,6 +27,25 @@ function resolveAnthropicSdk() {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
async function resolveClaudeAgentSdk() {
|
|
31
|
+
try {
|
|
32
|
+
return await import("@anthropic-ai/claude-agent-sdk");
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const error = new Error("Claude Agent SDK mode requires @anthropic-ai/claude-agent-sdk");
|
|
35
|
+
error.code = "CLAUDE_AGENT_SDK_UNAVAILABLE";
|
|
36
|
+
error.cause = err;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveClaudeAgentQuery(sdk) {
|
|
42
|
+
const query = sdk && (sdk.query || (sdk.default && sdk.default.query));
|
|
43
|
+
if (typeof query !== "function") {
|
|
44
|
+
throw new Error("Claude Agent SDK module does not export query");
|
|
45
|
+
}
|
|
46
|
+
return query;
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
async function defaultClaudeAuthProvider() {
|
|
30
50
|
const apiKey = String(process.env.ANTHROPIC_API_KEY || "").trim();
|
|
31
51
|
if (!apiKey) {
|
|
@@ -61,43 +81,12 @@ function defaultClaudeStreamFactory({ client, request }) {
|
|
|
61
81
|
});
|
|
62
82
|
}
|
|
63
83
|
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
baseUrl: String(auth.baseUrl || process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com/v1").trim(),
|
|
70
|
-
auth,
|
|
71
|
-
credentialSource: auth.apiKey ? "thread-auth" : "thread-headers",
|
|
72
|
-
};
|
|
73
|
-
const result = await sendUpstreamRequest({
|
|
74
|
-
runtime,
|
|
75
|
-
request,
|
|
76
|
-
timeoutMs: Number.isFinite(Number(request.timeout_ms)) ? Number(request.timeout_ms) : 120000,
|
|
84
|
+
function defaultClaudeAgentStreamFactory({ sdk, input, options }) {
|
|
85
|
+
const query = resolveClaudeAgentQuery(sdk);
|
|
86
|
+
return query({
|
|
87
|
+
prompt: String(input || ""),
|
|
88
|
+
options,
|
|
77
89
|
});
|
|
78
|
-
if (!result.ok) {
|
|
79
|
-
const err = new Error(result.error || "Claude upstream request failed");
|
|
80
|
-
err.code = "CLAUDE_UPSTREAM_FAILED";
|
|
81
|
-
err.attempt = attempt;
|
|
82
|
-
throw err;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
yield {
|
|
86
|
-
type: "message_start",
|
|
87
|
-
message: {
|
|
88
|
-
id: `msg-${Date.now().toString(36)}`,
|
|
89
|
-
usage: result.usage || undefined,
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
yield {
|
|
93
|
-
type: "content_block_delta",
|
|
94
|
-
index: 0,
|
|
95
|
-
delta: {
|
|
96
|
-
type: "text_delta",
|
|
97
|
-
text: String(result.output || ""),
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
yield { type: "message_stop" };
|
|
101
90
|
}
|
|
102
91
|
|
|
103
92
|
function normalizeToolDefinition(tool = {}) {
|
|
@@ -217,6 +206,74 @@ function createAssistantMessageFromState(state) {
|
|
|
217
206
|
};
|
|
218
207
|
}
|
|
219
208
|
|
|
209
|
+
function extraArgsToObject(extraArgs = []) {
|
|
210
|
+
const result = {};
|
|
211
|
+
if (!Array.isArray(extraArgs)) return result;
|
|
212
|
+
for (let i = 0; i < extraArgs.length; i += 1) {
|
|
213
|
+
const raw = String(extraArgs[i] || "");
|
|
214
|
+
if (!raw.startsWith("--")) continue;
|
|
215
|
+
const key = raw.replace(/^--+/, "");
|
|
216
|
+
if (!key) continue;
|
|
217
|
+
const next = i + 1 < extraArgs.length ? String(extraArgs[i + 1] || "") : "";
|
|
218
|
+
if (!next || next.startsWith("--")) {
|
|
219
|
+
result[key] = null;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
result[key] = next;
|
|
223
|
+
i += 1;
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function buildClaudeAgentOptions({
|
|
229
|
+
model = "",
|
|
230
|
+
cwd = "",
|
|
231
|
+
threadId = "",
|
|
232
|
+
extraArgs = [],
|
|
233
|
+
agentOptions = {},
|
|
234
|
+
opts = {},
|
|
235
|
+
} = {}) {
|
|
236
|
+
const options = {
|
|
237
|
+
...agentOptions,
|
|
238
|
+
...(opts && typeof opts.agentOptions === "object" ? opts.agentOptions : {}),
|
|
239
|
+
};
|
|
240
|
+
if (model && options.model === undefined) options.model = model;
|
|
241
|
+
if (cwd && options.cwd === undefined) options.cwd = cwd;
|
|
242
|
+
if (options.includePartialMessages === undefined) options.includePartialMessages = true;
|
|
243
|
+
if (options.extraArgs === undefined) {
|
|
244
|
+
const converted = extraArgsToObject(extraArgs);
|
|
245
|
+
if (Object.keys(converted).length > 0) options.extraArgs = converted;
|
|
246
|
+
}
|
|
247
|
+
if (threadId && !options.resume && !options.continue && !options.sessionId) {
|
|
248
|
+
options.resume = threadId;
|
|
249
|
+
}
|
|
250
|
+
if (opts && opts.abortController && options.abortController === undefined) {
|
|
251
|
+
options.abortController = opts.abortController;
|
|
252
|
+
}
|
|
253
|
+
if (opts && opts.signal && options.abortController === undefined) {
|
|
254
|
+
const controller = new AbortController();
|
|
255
|
+
opts.signal.addEventListener("abort", () => controller.abort(opts.signal.reason), { once: true });
|
|
256
|
+
options.abortController = controller;
|
|
257
|
+
}
|
|
258
|
+
return options;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function messageTextFromContent(content = []) {
|
|
262
|
+
if (!Array.isArray(content)) return "";
|
|
263
|
+
return content
|
|
264
|
+
.map((block) => (block && block.type === "text" ? String(block.text || "") : ""))
|
|
265
|
+
.join("");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function resultErrorMessage(message = {}) {
|
|
269
|
+
if (Array.isArray(message.errors) && message.errors.length > 0) {
|
|
270
|
+
return message.errors.map((item) => String(item || "")).filter(Boolean).join("\n");
|
|
271
|
+
}
|
|
272
|
+
if (typeof message.result === "string" && message.result) return message.result;
|
|
273
|
+
if (message.subtype) return String(message.subtype);
|
|
274
|
+
return "Claude Agent SDK query failed";
|
|
275
|
+
}
|
|
276
|
+
|
|
220
277
|
function shouldRetryClaudeStream(err, attempt) {
|
|
221
278
|
if (attempt >= 1) return false;
|
|
222
279
|
const code = String((err && err.code) || "").trim().toUpperCase();
|
|
@@ -228,23 +285,132 @@ function shouldRetryClaudeStream(err, attempt) {
|
|
|
228
285
|
class ClaudeApiThread {
|
|
229
286
|
constructor({
|
|
230
287
|
model = "",
|
|
288
|
+
cwd = "",
|
|
289
|
+
extraArgs = [],
|
|
231
290
|
authProvider = defaultClaudeAuthProvider,
|
|
232
291
|
clientFactory = defaultClaudeClientFactory,
|
|
233
|
-
streamFactory =
|
|
292
|
+
streamFactory = defaultClaudeAgentStreamFactory,
|
|
234
293
|
sdk,
|
|
294
|
+
agentOptions = {},
|
|
235
295
|
maxTokens = 4096,
|
|
236
296
|
} = {}) {
|
|
237
297
|
this.id = "";
|
|
238
298
|
this.model = model;
|
|
299
|
+
this.cwd = cwd;
|
|
300
|
+
this.extraArgs = Array.isArray(extraArgs) ? extraArgs.slice() : [];
|
|
239
301
|
this.authProvider = authProvider;
|
|
240
302
|
this.clientFactory = clientFactory;
|
|
241
303
|
this.streamFactory = streamFactory;
|
|
242
304
|
this.sdk = sdk;
|
|
305
|
+
this.agentOptions = { ...agentOptions };
|
|
243
306
|
this.maxTokens = maxTokens;
|
|
244
307
|
this.messages = [];
|
|
245
308
|
}
|
|
246
309
|
|
|
247
310
|
async *runStreamed(input, opts = {}) {
|
|
311
|
+
if (this.streamFactory === defaultClaudeAgentStreamFactory) {
|
|
312
|
+
yield* this.runAgentSdkStreamed(input, opts);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
yield* this.runMessagesStreamed(input, opts);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async *runAgentSdkStreamed(input, opts = {}) {
|
|
319
|
+
if (!this.sdk) {
|
|
320
|
+
this.sdk = await resolveClaudeAgentSdk();
|
|
321
|
+
}
|
|
322
|
+
const options = buildClaudeAgentOptions({
|
|
323
|
+
model: this.model,
|
|
324
|
+
cwd: this.cwd,
|
|
325
|
+
threadId: this.id,
|
|
326
|
+
extraArgs: this.extraArgs,
|
|
327
|
+
agentOptions: this.agentOptions,
|
|
328
|
+
opts,
|
|
329
|
+
});
|
|
330
|
+
const stream = await this.streamFactory({
|
|
331
|
+
sdk: this.sdk,
|
|
332
|
+
input,
|
|
333
|
+
options,
|
|
334
|
+
model: this.model,
|
|
335
|
+
cwd: this.cwd,
|
|
336
|
+
threadId: this.id,
|
|
337
|
+
opts,
|
|
338
|
+
});
|
|
339
|
+
const state = createClaudeEventState({ threadId: this.id });
|
|
340
|
+
let threadStarted = false;
|
|
341
|
+
let sawStreamEvents = false;
|
|
342
|
+
let sawText = false;
|
|
343
|
+
let sawTurnCompleted = false;
|
|
344
|
+
|
|
345
|
+
const emitThreadStarted = function* emitThreadStarted(self, sessionId = "") {
|
|
346
|
+
const nextId = String(sessionId || self.id || "").trim();
|
|
347
|
+
if (nextId) self.id = nextId;
|
|
348
|
+
if (!threadStarted && self.id) {
|
|
349
|
+
threadStarted = true;
|
|
350
|
+
yield redactUfooEvent({ type: "thread_started", threadId: self.id });
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
for await (const message of stream) {
|
|
355
|
+
if (!message || typeof message !== "object") continue;
|
|
356
|
+
const sessionId = String(message.session_id || message.sessionId || "").trim();
|
|
357
|
+
for (const event of emitThreadStarted(this, sessionId)) yield event;
|
|
358
|
+
|
|
359
|
+
if (message.type === "stream_event") {
|
|
360
|
+
sawStreamEvents = true;
|
|
361
|
+
const events = normalizeClaudeEvent(message.event || {}, state);
|
|
362
|
+
for (const event of events) {
|
|
363
|
+
if (!event || typeof event !== "object") continue;
|
|
364
|
+
if (event.type === "text_delta" && event.delta) sawText = true;
|
|
365
|
+
if (event.type === "turn_completed") sawTurnCompleted = true;
|
|
366
|
+
yield redactUfooEvent(event);
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (message.type === "assistant") {
|
|
372
|
+
if (sawStreamEvents) continue;
|
|
373
|
+
const events = normalizeClaudeMessage(message.message || {});
|
|
374
|
+
for (const event of events) {
|
|
375
|
+
if (!event || typeof event !== "object") continue;
|
|
376
|
+
if (event.type === "text_delta" && event.delta) sawText = true;
|
|
377
|
+
if (event.type === "turn_completed") sawTurnCompleted = true;
|
|
378
|
+
yield redactUfooEvent(event);
|
|
379
|
+
}
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (message.type === "result") {
|
|
384
|
+
if (message.is_error) {
|
|
385
|
+
yield redactUfooEvent({
|
|
386
|
+
type: "turn_failed",
|
|
387
|
+
turnId: state.turnId || message.uuid || "",
|
|
388
|
+
error: resultErrorMessage(message),
|
|
389
|
+
});
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (!sawText && typeof message.result === "string" && message.result) {
|
|
393
|
+
sawText = true;
|
|
394
|
+
yield redactUfooEvent({
|
|
395
|
+
type: "text_delta",
|
|
396
|
+
delta: message.result,
|
|
397
|
+
itemType: "text",
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
if (!sawTurnCompleted) {
|
|
401
|
+
sawTurnCompleted = true;
|
|
402
|
+
yield redactUfooEvent({
|
|
403
|
+
type: "turn_completed",
|
|
404
|
+
turnId: state.turnId || message.uuid || "",
|
|
405
|
+
usage: normalizeClaudeUsage(message.usage || null),
|
|
406
|
+
stopReason: String(message.stop_reason || ""),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async *runMessagesStreamed(input, opts = {}) {
|
|
248
414
|
if (!this.id) this.id = createThreadId();
|
|
249
415
|
const userMessage = normalizeMessageInput(input);
|
|
250
416
|
const requestMessages = this.messages.slice();
|
|
@@ -332,12 +498,15 @@ module.exports = {
|
|
|
332
498
|
ClaudeApiThread,
|
|
333
499
|
ClaudeThreadProvider,
|
|
334
500
|
createClaudeThreadProvider,
|
|
501
|
+
buildClaudeAgentOptions,
|
|
335
502
|
defaultClaudeAuthProvider,
|
|
503
|
+
defaultClaudeAgentStreamFactory,
|
|
336
504
|
defaultClaudeClientFactory,
|
|
337
505
|
defaultClaudeStreamFactory,
|
|
338
|
-
|
|
506
|
+
extraArgsToObject,
|
|
339
507
|
normalizeMessageInput,
|
|
340
508
|
normalizeToolDefinition,
|
|
509
|
+
resolveClaudeAgentSdk,
|
|
341
510
|
resolveAnthropicSdk,
|
|
342
511
|
withCacheControlOnLastBlock,
|
|
343
512
|
};
|
|
@@ -1,80 +1,79 @@
|
|
|
1
1
|
const { normalizeCodexEvent } = require("./codexEventTranslator");
|
|
2
2
|
const { redactUfooEvent } = require("../providerapi/redactor");
|
|
3
|
-
const { sendUpstreamPrompt } = require("./upstreamTransport");
|
|
4
3
|
|
|
5
|
-
function resolveCodexSdk() {
|
|
4
|
+
async function resolveCodexSdk() {
|
|
6
5
|
try {
|
|
7
|
-
|
|
8
|
-
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
9
|
-
return require("@openai/codex-sdk");
|
|
6
|
+
return await import("@openai/codex-sdk");
|
|
10
7
|
} catch (err) {
|
|
11
|
-
const error = new Error("Codex SDK
|
|
8
|
+
const error = new Error("Codex SDK mode requires @openai/codex-sdk");
|
|
12
9
|
error.code = "CODEX_SDK_UNAVAILABLE";
|
|
13
10
|
error.cause = err;
|
|
14
11
|
throw error;
|
|
15
12
|
}
|
|
16
13
|
}
|
|
17
14
|
|
|
18
|
-
function
|
|
19
|
-
sdk
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
extraArgs = [],
|
|
23
|
-
threadId = "",
|
|
24
|
-
input,
|
|
25
|
-
opts = {},
|
|
26
|
-
}) {
|
|
27
|
-
if (!sdk || typeof sdk.runStreamed !== "function") {
|
|
28
|
-
throw new Error("Codex SDK seam requires runStreamed support");
|
|
15
|
+
function resolveCodexConstructor(sdk) {
|
|
16
|
+
const Codex = sdk && (sdk.Codex || (sdk.default && sdk.default.Codex) || sdk.default);
|
|
17
|
+
if (typeof Codex !== "function") {
|
|
18
|
+
throw new Error("Codex SDK module does not export Codex");
|
|
29
19
|
}
|
|
30
|
-
|
|
31
|
-
void history;
|
|
32
|
-
return sdk.runStreamed({
|
|
33
|
-
model,
|
|
34
|
-
cwd,
|
|
35
|
-
extraArgs,
|
|
36
|
-
threadId,
|
|
37
|
-
input,
|
|
38
|
-
...sdkOpts,
|
|
39
|
-
});
|
|
20
|
+
return Codex;
|
|
40
21
|
}
|
|
41
22
|
|
|
42
|
-
function
|
|
43
|
-
return
|
|
23
|
+
function buildCodexOptions({ codexOptions = {} } = {}) {
|
|
24
|
+
return { ...codexOptions };
|
|
44
25
|
}
|
|
45
26
|
|
|
46
|
-
|
|
47
|
-
model,
|
|
48
|
-
cwd,
|
|
49
|
-
|
|
27
|
+
function buildThreadOptions({
|
|
28
|
+
model = "",
|
|
29
|
+
cwd = "",
|
|
30
|
+
threadOptions = {},
|
|
31
|
+
} = {}) {
|
|
32
|
+
const options = { ...threadOptions };
|
|
33
|
+
if (model && options.model === undefined) {
|
|
34
|
+
options.model = model;
|
|
35
|
+
}
|
|
36
|
+
if (cwd && options.workingDirectory === undefined) {
|
|
37
|
+
options.workingDirectory = cwd;
|
|
38
|
+
}
|
|
39
|
+
if (options.skipGitRepoCheck === undefined) {
|
|
40
|
+
options.skipGitRepoCheck = true;
|
|
41
|
+
}
|
|
42
|
+
if (options.sandboxMode === undefined) {
|
|
43
|
+
options.sandboxMode = "workspace-write";
|
|
44
|
+
}
|
|
45
|
+
return options;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildTurnOptions(opts = {}) {
|
|
49
|
+
const {
|
|
50
|
+
history,
|
|
51
|
+
tools,
|
|
52
|
+
timeoutMs,
|
|
53
|
+
...turnOptions
|
|
54
|
+
} = opts || {};
|
|
55
|
+
void history;
|
|
56
|
+
void tools;
|
|
57
|
+
void timeoutMs;
|
|
58
|
+
return turnOptions;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function* defaultCodexStreamFactory({
|
|
62
|
+
thread,
|
|
50
63
|
input,
|
|
51
64
|
opts = {},
|
|
52
65
|
}) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
err.code = result.errorCode || "CODEX_UPSTREAM_FAILED";
|
|
65
|
-
throw err;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
yield { type: "thread.started", thread_id: nextThreadId };
|
|
69
|
-
yield {
|
|
70
|
-
type: "item.completed",
|
|
71
|
-
item: { type: "message", text: String(result.output || "") },
|
|
72
|
-
};
|
|
73
|
-
yield {
|
|
74
|
-
type: "turn.completed",
|
|
75
|
-
turn_id: `turn-${Date.now().toString(36)}`,
|
|
76
|
-
usage: result.usage || null,
|
|
77
|
-
};
|
|
66
|
+
if (!thread || typeof thread.runStreamed !== "function") {
|
|
67
|
+
throw new Error("Codex SDK thread requires runStreamed support");
|
|
68
|
+
}
|
|
69
|
+
const streamed = await thread.runStreamed(String(input || ""), buildTurnOptions(opts));
|
|
70
|
+
const events = streamed && streamed.events ? streamed.events : streamed;
|
|
71
|
+
if (!events || typeof events[Symbol.asyncIterator] !== "function") {
|
|
72
|
+
throw new Error("Codex SDK runStreamed did not return an async event stream");
|
|
73
|
+
}
|
|
74
|
+
for await (const event of events) {
|
|
75
|
+
yield event;
|
|
76
|
+
}
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
class CodexSdkThread {
|
|
@@ -84,26 +83,67 @@ class CodexSdkThread {
|
|
|
84
83
|
extraArgs = [],
|
|
85
84
|
streamFactory = defaultCodexStreamFactory,
|
|
86
85
|
sdk,
|
|
86
|
+
codexClient,
|
|
87
|
+
sdkThread,
|
|
88
|
+
threadId = "",
|
|
87
89
|
tools = [],
|
|
90
|
+
codexOptions = {},
|
|
91
|
+
threadOptions = {},
|
|
88
92
|
} = {}) {
|
|
89
|
-
this.id = "";
|
|
93
|
+
this.id = String(threadId || "").trim();
|
|
90
94
|
this.model = model;
|
|
91
95
|
this.cwd = cwd;
|
|
92
96
|
this.extraArgs = Array.isArray(extraArgs) ? extraArgs.slice() : [];
|
|
93
97
|
this.streamFactory = streamFactory;
|
|
94
98
|
this.sdk = sdk;
|
|
99
|
+
this.codexClient = codexClient || null;
|
|
100
|
+
this.sdkThread = sdkThread || null;
|
|
95
101
|
this.tools = Array.isArray(tools) ? tools.slice() : [];
|
|
102
|
+
this.codexOptions = buildCodexOptions({ codexOptions });
|
|
103
|
+
this.threadOptions = { ...threadOptions };
|
|
96
104
|
this.messages = [];
|
|
97
105
|
}
|
|
98
106
|
|
|
107
|
+
async getCodexClient() {
|
|
108
|
+
if (this.codexClient) return this.codexClient;
|
|
109
|
+
if (!this.sdk) {
|
|
110
|
+
this.sdk = await resolveCodexSdk();
|
|
111
|
+
}
|
|
112
|
+
const Codex = resolveCodexConstructor(this.sdk);
|
|
113
|
+
this.codexClient = new Codex(this.codexOptions);
|
|
114
|
+
return this.codexClient;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getSdkThread() {
|
|
118
|
+
if (this.sdkThread) return this.sdkThread;
|
|
119
|
+
const client = await this.getCodexClient();
|
|
120
|
+
const options = buildThreadOptions({
|
|
121
|
+
model: this.model,
|
|
122
|
+
cwd: this.cwd,
|
|
123
|
+
threadOptions: this.threadOptions,
|
|
124
|
+
});
|
|
125
|
+
this.sdkThread = this.id && typeof client.resumeThread === "function"
|
|
126
|
+
? client.resumeThread(this.id, options)
|
|
127
|
+
: client.startThread(options);
|
|
128
|
+
if (this.sdkThread && this.sdkThread.id) {
|
|
129
|
+
this.id = this.sdkThread.id;
|
|
130
|
+
}
|
|
131
|
+
return this.sdkThread;
|
|
132
|
+
}
|
|
133
|
+
|
|
99
134
|
async *runStreamed(input, opts = {}) {
|
|
100
135
|
const mergedOpts = { ...opts };
|
|
101
136
|
if (this.tools.length > 0 && !Array.isArray(mergedOpts.tools)) {
|
|
102
137
|
mergedOpts.tools = this.tools.slice();
|
|
103
138
|
}
|
|
104
139
|
mergedOpts.history = this.messages.slice();
|
|
140
|
+
const sdkThread = this.streamFactory === defaultCodexStreamFactory
|
|
141
|
+
? await this.getSdkThread()
|
|
142
|
+
: this.sdkThread;
|
|
105
143
|
const stream = this.streamFactory({
|
|
106
144
|
sdk: this.sdk,
|
|
145
|
+
thread: sdkThread,
|
|
146
|
+
codexClient: this.codexClient,
|
|
107
147
|
model: this.model,
|
|
108
148
|
cwd: this.cwd,
|
|
109
149
|
extraArgs: this.extraArgs,
|
|
@@ -123,6 +163,9 @@ class CodexSdkThread {
|
|
|
123
163
|
}
|
|
124
164
|
yield redactUfooEvent(normalized);
|
|
125
165
|
}
|
|
166
|
+
if (sdkThread && sdkThread.id) {
|
|
167
|
+
this.id = sdkThread.id;
|
|
168
|
+
}
|
|
126
169
|
this.messages.push({ role: "user", content: String(input || "") });
|
|
127
170
|
this.messages.push({ role: "assistant", content: outputText });
|
|
128
171
|
}
|
|
@@ -139,14 +182,20 @@ class CodexThreadProvider {
|
|
|
139
182
|
extraArgs = [],
|
|
140
183
|
streamFactory = defaultCodexStreamFactory,
|
|
141
184
|
sdk,
|
|
185
|
+
codexClient,
|
|
142
186
|
tools = [],
|
|
187
|
+
codexOptions = {},
|
|
188
|
+
threadOptions = {},
|
|
143
189
|
} = {}) {
|
|
144
190
|
this.model = model;
|
|
145
191
|
this.cwd = cwd;
|
|
146
192
|
this.extraArgs = Array.isArray(extraArgs) ? extraArgs.slice() : [];
|
|
147
193
|
this.streamFactory = streamFactory;
|
|
148
|
-
this.sdk = sdk ||
|
|
194
|
+
this.sdk = sdk || null;
|
|
195
|
+
this.codexClient = codexClient || null;
|
|
149
196
|
this.tools = Array.isArray(tools) ? tools.slice() : [];
|
|
197
|
+
this.codexOptions = buildCodexOptions({ codexOptions });
|
|
198
|
+
this.threadOptions = { ...threadOptions };
|
|
150
199
|
}
|
|
151
200
|
|
|
152
201
|
startThread() {
|
|
@@ -156,14 +205,26 @@ class CodexThreadProvider {
|
|
|
156
205
|
extraArgs: this.extraArgs,
|
|
157
206
|
streamFactory: this.streamFactory,
|
|
158
207
|
sdk: this.sdk,
|
|
208
|
+
codexClient: this.codexClient,
|
|
159
209
|
tools: this.tools,
|
|
210
|
+
codexOptions: this.codexOptions,
|
|
211
|
+
threadOptions: this.threadOptions,
|
|
160
212
|
});
|
|
161
213
|
}
|
|
162
214
|
|
|
163
215
|
resumeThread(threadId = "") {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
216
|
+
return new CodexSdkThread({
|
|
217
|
+
model: this.model,
|
|
218
|
+
cwd: this.cwd,
|
|
219
|
+
extraArgs: this.extraArgs,
|
|
220
|
+
streamFactory: this.streamFactory,
|
|
221
|
+
sdk: this.sdk,
|
|
222
|
+
codexClient: this.codexClient,
|
|
223
|
+
threadId,
|
|
224
|
+
tools: this.tools,
|
|
225
|
+
codexOptions: this.codexOptions,
|
|
226
|
+
threadOptions: this.threadOptions,
|
|
227
|
+
});
|
|
167
228
|
}
|
|
168
229
|
}
|
|
169
230
|
|
|
@@ -175,7 +236,7 @@ module.exports = {
|
|
|
175
236
|
CodexSdkThread,
|
|
176
237
|
CodexThreadProvider,
|
|
177
238
|
createCodexThreadProvider,
|
|
239
|
+
buildThreadOptions,
|
|
178
240
|
defaultCodexStreamFactory,
|
|
179
|
-
defaultCodexTransportStreamFactory,
|
|
180
241
|
resolveCodexSdk,
|
|
181
242
|
};
|