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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "2.3.10",
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 = JSON.parse(fs.readFileSync(agentsFilePath, "utf8"));
16
- if (!data.agents || !data.agents[subscriber]) return false;
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
- fs.writeFileSync(agentsFilePath, JSON.stringify(data, null, 2));
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
- async function* defaultClaudeTransportStreamFactory({ request, auth = {}, model = "", attempt = 0 }) {
65
- const runtime = {
66
- provider: "claude",
67
- transport: "anthropic-messages",
68
- model: String(model || request.model || "").trim(),
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 = defaultClaudeStreamFactory,
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
- defaultClaudeTransportStreamFactory,
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
- // Optional dependency during Phase 1a seam work.
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 seam enabled but @openai/codex-sdk is not installed");
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 defaultCodexStreamFactory({
19
- sdk,
20
- model,
21
- cwd,
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
- const { history, ...sdkOpts } = opts;
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 createThreadId() {
43
- return `codex-thread-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
23
+ function buildCodexOptions({ codexOptions = {} } = {}) {
24
+ return { ...codexOptions };
44
25
  }
45
26
 
46
- async function* defaultCodexTransportStreamFactory({
47
- model,
48
- cwd,
49
- threadId = "",
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
- const nextThreadId = String(threadId || "").trim() || createThreadId();
54
- const result = await sendUpstreamPrompt({
55
- projectRoot: cwd,
56
- provider: "codex",
57
- model,
58
- prompt: String(input || ""),
59
- messages: Array.isArray(opts.history) ? opts.history : [],
60
- timeoutMs: Number.isFinite(Number(opts.timeoutMs)) ? Number(opts.timeoutMs) : 120000,
61
- });
62
- if (!result.ok) {
63
- const err = new Error(result.error || "Codex upstream request failed");
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 || (streamFactory === defaultCodexStreamFactory ? resolveCodexSdk() : null);
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
- const thread = this.startThread();
165
- thread.id = String(threadId || "").trim();
166
- return thread;
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
  };