@corbat-tech/coco 2.11.1 → 2.13.0

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/cli/index.js CHANGED
@@ -1424,7 +1424,7 @@ var init_openai = __esm({
1424
1424
  "src/providers/openai.ts"() {
1425
1425
  init_errors();
1426
1426
  init_retry();
1427
- DEFAULT_MODEL2 = "gpt-5.3-codex";
1427
+ DEFAULT_MODEL2 = "gpt-5.4-codex";
1428
1428
  CONTEXT_WINDOWS2 = {
1429
1429
  // OpenAI models
1430
1430
  "gpt-4o": 128e3,
@@ -1447,6 +1447,7 @@ var init_openai = __esm({
1447
1447
  "gpt-5.2-instant": 4e5,
1448
1448
  "gpt-5.2-pro": 4e5,
1449
1449
  "gpt-5.3-codex": 4e5,
1450
+ "gpt-5.4-codex": 4e5,
1450
1451
  // Kimi/Moonshot models
1451
1452
  "kimi-k2.5": 262144,
1452
1453
  "kimi-k2-0324": 131072,
@@ -1503,7 +1504,7 @@ var init_openai = __esm({
1503
1504
  "microsoft/Phi-4": 16384,
1504
1505
  // OpenRouter model IDs
1505
1506
  "anthropic/claude-opus-4-6": 2e5,
1506
- "openai/gpt-5.3-codex": 4e5,
1507
+ "openai/gpt-5.4-codex": 4e5,
1507
1508
  "google/gemini-3-flash-preview": 1e6,
1508
1509
  "meta-llama/llama-3.3-70b-instruct": 128e3
1509
1510
  };
@@ -1755,7 +1756,7 @@ var init_openai = __esm({
1755
1756
  once: true
1756
1757
  });
1757
1758
  const providerName = this.name;
1758
- const parseArguments = (builder) => {
1759
+ const parseArguments2 = (builder) => {
1759
1760
  let input = {};
1760
1761
  try {
1761
1762
  input = builder.arguments ? JSON.parse(builder.arguments) : {};
@@ -1836,7 +1837,7 @@ var init_openai = __esm({
1836
1837
  toolCall: {
1837
1838
  id: builder.id,
1838
1839
  name: builder.name,
1839
- input: parseArguments(builder)
1840
+ input: parseArguments2(builder)
1840
1841
  }
1841
1842
  };
1842
1843
  }
@@ -1849,7 +1850,7 @@ var init_openai = __esm({
1849
1850
  toolCall: {
1850
1851
  id: builder.id,
1851
1852
  name: builder.name,
1852
- input: parseArguments(builder)
1853
+ input: parseArguments2(builder)
1853
1854
  }
1854
1855
  };
1855
1856
  }
@@ -4060,8 +4061,6 @@ var init_auth = __esm({
4060
4061
  init_gcloud();
4061
4062
  }
4062
4063
  });
4063
-
4064
- // src/providers/codex.ts
4065
4064
  function parseJwtClaims(token) {
4066
4065
  const parts = token.split(".");
4067
4066
  if (parts.length !== 3 || !parts[1]) return void 0;
@@ -4077,6 +4076,21 @@ function extractAccountId(accessToken) {
4077
4076
  const auth = claims["https://api.openai.com/auth"];
4078
4077
  return claims["chatgpt_account_id"] || auth?.["chatgpt_account_id"] || claims["organizations"]?.[0]?.id;
4079
4078
  }
4079
+ function parseArguments(args) {
4080
+ try {
4081
+ return args ? JSON.parse(args) : {};
4082
+ } catch {
4083
+ try {
4084
+ if (args) {
4085
+ const repaired = jsonrepair(args);
4086
+ return JSON.parse(repaired);
4087
+ }
4088
+ } catch {
4089
+ console.error(`[Codex] Cannot parse tool arguments: ${args.slice(0, 200)}`);
4090
+ }
4091
+ return {};
4092
+ }
4093
+ }
4080
4094
  function createCodexProvider(config) {
4081
4095
  const provider = new CodexProvider();
4082
4096
  if (config) {
@@ -4085,14 +4099,16 @@ function createCodexProvider(config) {
4085
4099
  }
4086
4100
  return provider;
4087
4101
  }
4088
- var CODEX_API_ENDPOINT, DEFAULT_MODEL3, CONTEXT_WINDOWS3, CodexProvider;
4102
+ var CODEX_API_ENDPOINT, DEFAULT_MODEL3, CONTEXT_WINDOWS3, STREAM_TIMEOUT_MS, CodexProvider;
4089
4103
  var init_codex = __esm({
4090
4104
  "src/providers/codex.ts"() {
4091
4105
  init_errors();
4092
4106
  init_auth();
4107
+ init_retry();
4093
4108
  CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
4094
- DEFAULT_MODEL3 = "gpt-5.3-codex";
4109
+ DEFAULT_MODEL3 = "gpt-5.4-codex";
4095
4110
  CONTEXT_WINDOWS3 = {
4111
+ "gpt-5.4-codex": 2e5,
4096
4112
  "gpt-5.3-codex": 2e5,
4097
4113
  "gpt-5.2-codex": 2e5,
4098
4114
  "gpt-5-codex": 2e5,
@@ -4101,12 +4117,14 @@ var init_codex = __esm({
4101
4117
  "gpt-5.2": 2e5,
4102
4118
  "gpt-5.1": 2e5
4103
4119
  };
4120
+ STREAM_TIMEOUT_MS = 12e4;
4104
4121
  CodexProvider = class {
4105
4122
  id = "codex";
4106
4123
  name = "OpenAI Codex (ChatGPT Plus/Pro)";
4107
4124
  config = {};
4108
4125
  accessToken = null;
4109
4126
  accountId;
4127
+ retryConfig = DEFAULT_RETRY_CONFIG;
4110
4128
  /**
4111
4129
  * Initialize the provider with OAuth tokens
4112
4130
  */
@@ -4191,166 +4209,466 @@ var init_codex = __esm({
4191
4209
  /**
4192
4210
  * Extract text content from a message
4193
4211
  */
4194
- extractTextContent(msg) {
4195
- if (typeof msg.content === "string") {
4196
- return msg.content;
4197
- }
4198
- if (Array.isArray(msg.content)) {
4199
- return msg.content.map((part) => {
4200
- if (part.type === "text") return part.text;
4201
- if (part.type === "tool_result") return `Tool result: ${JSON.stringify(part.content)}`;
4202
- return "";
4203
- }).join("\n");
4212
+ contentToString(content) {
4213
+ if (typeof content === "string") return content;
4214
+ if (Array.isArray(content)) {
4215
+ return content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
4204
4216
  }
4205
4217
  return "";
4206
4218
  }
4207
4219
  /**
4208
- * Convert messages to Codex Responses API format
4209
- * Codex uses a different format than Chat Completions:
4210
- * {
4211
- * "input": [
4212
- * { "type": "message", "role": "developer|user", "content": [{ "type": "input_text", "text": "..." }] },
4213
- * { "type": "message", "role": "assistant", "content": [{ "type": "output_text", "text": "..." }] }
4214
- * ]
4215
- * }
4220
+ * Convert messages to Responses API input format.
4216
4221
  *
4217
- * IMPORTANT: User/developer messages use "input_text", assistant messages use "output_text"
4222
+ * Handles:
4223
+ * - system messages → extracted as instructions
4224
+ * - user text messages → { role: "user", content: "..." }
4225
+ * - user tool_result messages → function_call_output items
4226
+ * - assistant text → { role: "assistant", content: "..." }
4227
+ * - assistant tool_use → function_call items
4218
4228
  */
4219
- convertMessagesToResponsesFormat(messages) {
4220
- return messages.map((msg) => {
4221
- const text13 = this.extractTextContent(msg);
4222
- const role = msg.role === "system" ? "developer" : msg.role;
4223
- const contentType = msg.role === "assistant" ? "output_text" : "input_text";
4224
- return {
4225
- type: "message",
4226
- role,
4227
- content: [{ type: contentType, text: text13 }]
4228
- };
4229
- });
4229
+ convertToResponsesInput(messages, systemPrompt) {
4230
+ const input = [];
4231
+ let instructions = systemPrompt ?? null;
4232
+ for (const msg of messages) {
4233
+ if (msg.role === "system") {
4234
+ instructions = (instructions ? instructions + "\n\n" : "") + this.contentToString(msg.content);
4235
+ } else if (msg.role === "user") {
4236
+ if (Array.isArray(msg.content) && msg.content.some((b) => b.type === "tool_result")) {
4237
+ for (const block of msg.content) {
4238
+ if (block.type === "tool_result") {
4239
+ const tr = block;
4240
+ input.push({
4241
+ type: "function_call_output",
4242
+ call_id: tr.tool_use_id,
4243
+ output: tr.content
4244
+ });
4245
+ }
4246
+ }
4247
+ } else {
4248
+ input.push({
4249
+ role: "user",
4250
+ content: this.contentToString(msg.content)
4251
+ });
4252
+ }
4253
+ } else if (msg.role === "assistant") {
4254
+ if (typeof msg.content === "string") {
4255
+ input.push({ role: "assistant", content: msg.content });
4256
+ } else if (Array.isArray(msg.content)) {
4257
+ const textParts = [];
4258
+ for (const block of msg.content) {
4259
+ if (block.type === "text") {
4260
+ textParts.push(block.text);
4261
+ } else if (block.type === "tool_use") {
4262
+ if (textParts.length > 0) {
4263
+ input.push({ role: "assistant", content: textParts.join("") });
4264
+ textParts.length = 0;
4265
+ }
4266
+ const tu = block;
4267
+ input.push({
4268
+ type: "function_call",
4269
+ call_id: tu.id,
4270
+ name: tu.name,
4271
+ arguments: JSON.stringify(tu.input)
4272
+ });
4273
+ }
4274
+ }
4275
+ if (textParts.length > 0) {
4276
+ input.push({ role: "assistant", content: textParts.join("") });
4277
+ }
4278
+ }
4279
+ }
4280
+ }
4281
+ return { input, instructions };
4230
4282
  }
4231
4283
  /**
4232
- * Send a chat message using Codex Responses API format
4284
+ * Convert tool definitions to Responses API function tool format
4233
4285
  */
4234
- async chat(messages, options) {
4235
- const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
4236
- const systemMsg = messages.find((m) => m.role === "system");
4237
- const instructions = systemMsg ? this.extractTextContent(systemMsg) : "You are a helpful coding assistant.";
4238
- const inputMessages = messages.filter((m) => m.role !== "system").map((msg) => this.convertMessagesToResponsesFormat([msg])[0]);
4286
+ convertTools(tools) {
4287
+ return tools.map((tool) => ({
4288
+ type: "function",
4289
+ name: tool.name,
4290
+ description: tool.description ?? void 0,
4291
+ parameters: tool.input_schema ?? null,
4292
+ strict: false
4293
+ }));
4294
+ }
4295
+ /**
4296
+ * Build the request body for the Codex Responses API
4297
+ */
4298
+ buildRequestBody(model, input, instructions, options) {
4239
4299
  const body = {
4240
4300
  model,
4241
- instructions,
4242
- input: inputMessages,
4243
- tools: [],
4301
+ input,
4302
+ instructions: instructions ?? "You are a helpful coding assistant.",
4303
+ max_output_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
4304
+ temperature: options?.temperature ?? this.config.temperature ?? 0,
4244
4305
  store: false,
4245
4306
  stream: true
4246
4307
  // Codex API requires streaming
4247
4308
  };
4248
- const response = await this.makeRequest(body);
4309
+ if (options?.tools && options.tools.length > 0) {
4310
+ body.tools = this.convertTools(options.tools);
4311
+ }
4312
+ return body;
4313
+ }
4314
+ /**
4315
+ * Read SSE stream and call handler for each parsed event.
4316
+ * Returns when stream ends.
4317
+ */
4318
+ async readSSEStream(response, onEvent) {
4249
4319
  if (!response.body) {
4250
- throw new ProviderError("No response body from Codex API", {
4251
- provider: this.id
4252
- });
4320
+ throw new ProviderError("No response body from Codex API", { provider: this.id });
4253
4321
  }
4254
4322
  const reader = response.body.getReader();
4255
4323
  const decoder = new TextDecoder();
4256
4324
  let buffer = "";
4257
- let content = "";
4258
- let responseId = `codex-${Date.now()}`;
4259
- let inputTokens = 0;
4260
- let outputTokens = 0;
4261
- let status = "completed";
4325
+ let lastActivityTime = Date.now();
4326
+ const timeoutController = new AbortController();
4327
+ const timeoutInterval = setInterval(() => {
4328
+ if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
4329
+ clearInterval(timeoutInterval);
4330
+ timeoutController.abort();
4331
+ }
4332
+ }, 5e3);
4262
4333
  try {
4263
4334
  while (true) {
4335
+ if (timeoutController.signal.aborted) break;
4264
4336
  const { done, value } = await reader.read();
4265
4337
  if (done) break;
4338
+ lastActivityTime = Date.now();
4266
4339
  buffer += decoder.decode(value, { stream: true });
4267
4340
  const lines = buffer.split("\n");
4268
4341
  buffer = lines.pop() ?? "";
4269
4342
  for (const line of lines) {
4270
- if (line.startsWith("data: ")) {
4271
- const data = line.slice(6).trim();
4272
- if (!data || data === "[DONE]") continue;
4273
- try {
4274
- const parsed = JSON.parse(data);
4275
- if (parsed.id) {
4276
- responseId = parsed.id;
4277
- }
4278
- if (parsed.type === "response.output_text.delta" && parsed.delta) {
4279
- content += parsed.delta;
4280
- } else if (parsed.type === "response.completed" && parsed.response) {
4281
- if (parsed.response.usage) {
4282
- inputTokens = parsed.response.usage.input_tokens ?? 0;
4283
- outputTokens = parsed.response.usage.output_tokens ?? 0;
4284
- }
4285
- status = parsed.response.status ?? "completed";
4286
- } else if (parsed.type === "response.output_text.done" && parsed.text) {
4287
- content = parsed.text;
4288
- }
4289
- } catch {
4290
- }
4343
+ if (!line.startsWith("data: ")) continue;
4344
+ const data = line.slice(6).trim();
4345
+ if (!data || data === "[DONE]") continue;
4346
+ try {
4347
+ onEvent(JSON.parse(data));
4348
+ } catch {
4291
4349
  }
4292
4350
  }
4293
4351
  }
4294
4352
  } finally {
4353
+ clearInterval(timeoutInterval);
4295
4354
  reader.releaseLock();
4296
4355
  }
4297
- if (!content) {
4298
- throw new ProviderError("No response content from Codex API", {
4299
- provider: this.id
4300
- });
4356
+ if (timeoutController.signal.aborted) {
4357
+ throw new Error(
4358
+ `Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
4359
+ );
4301
4360
  }
4302
- const stopReason = status === "completed" ? "end_turn" : status === "incomplete" ? "max_tokens" : "end_turn";
4303
- return {
4304
- id: responseId,
4305
- content,
4306
- stopReason,
4307
- model,
4308
- usage: {
4309
- inputTokens,
4310
- outputTokens
4311
- }
4312
- };
4313
4361
  }
4314
4362
  /**
4315
- * Send a chat message with tool use
4316
- * Note: Codex Responses API tool support is complex; for now we delegate to chat()
4317
- * and return empty toolCalls. Full tool support can be added later.
4363
+ * Send a chat message using Codex Responses API format
4364
+ */
4365
+ async chat(messages, options) {
4366
+ return withRetry(async () => {
4367
+ const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
4368
+ const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
4369
+ const body = this.buildRequestBody(model, input, instructions, {
4370
+ maxTokens: options?.maxTokens,
4371
+ temperature: options?.temperature
4372
+ });
4373
+ const response = await this.makeRequest(body);
4374
+ let content = "";
4375
+ let responseId = `codex-${Date.now()}`;
4376
+ let inputTokens = 0;
4377
+ let outputTokens = 0;
4378
+ let status = "completed";
4379
+ await this.readSSEStream(response, (event) => {
4380
+ if (event.id) responseId = event.id;
4381
+ if (event.type === "response.output_text.delta" && event.delta) {
4382
+ content += event.delta;
4383
+ } else if (event.type === "response.output_text.done" && event.text) {
4384
+ content = event.text;
4385
+ } else if (event.type === "response.completed" && event.response) {
4386
+ const resp = event.response;
4387
+ const usage = resp.usage;
4388
+ if (usage) {
4389
+ inputTokens = usage.input_tokens ?? 0;
4390
+ outputTokens = usage.output_tokens ?? 0;
4391
+ }
4392
+ status = resp.status ?? "completed";
4393
+ }
4394
+ });
4395
+ const stopReason = status === "completed" ? "end_turn" : status === "incomplete" ? "max_tokens" : "end_turn";
4396
+ return {
4397
+ id: responseId,
4398
+ content,
4399
+ stopReason,
4400
+ model,
4401
+ usage: { inputTokens, outputTokens }
4402
+ };
4403
+ }, this.retryConfig);
4404
+ }
4405
+ /**
4406
+ * Send a chat message with tool use via Responses API
4318
4407
  */
4319
4408
  async chatWithTools(messages, options) {
4320
- const response = await this.chat(messages, options);
4321
- return {
4322
- ...response,
4323
- toolCalls: []
4324
- // Tools not yet supported in Codex provider
4325
- };
4409
+ return withRetry(async () => {
4410
+ const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
4411
+ const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
4412
+ const body = this.buildRequestBody(model, input, instructions, {
4413
+ tools: options.tools,
4414
+ maxTokens: options?.maxTokens
4415
+ });
4416
+ const response = await this.makeRequest(body);
4417
+ let content = "";
4418
+ let responseId = `codex-${Date.now()}`;
4419
+ let inputTokens = 0;
4420
+ let outputTokens = 0;
4421
+ const toolCalls = [];
4422
+ const fnCallBuilders = /* @__PURE__ */ new Map();
4423
+ await this.readSSEStream(response, (event) => {
4424
+ if (event.id) responseId = event.id;
4425
+ switch (event.type) {
4426
+ case "response.output_text.delta":
4427
+ content += event.delta ?? "";
4428
+ break;
4429
+ case "response.output_text.done":
4430
+ content = event.text ?? content;
4431
+ break;
4432
+ case "response.output_item.added": {
4433
+ const item = event.item;
4434
+ if (item.type === "function_call") {
4435
+ const itemKey = item.id ?? item.call_id;
4436
+ fnCallBuilders.set(itemKey, {
4437
+ callId: item.call_id,
4438
+ name: item.name,
4439
+ arguments: ""
4440
+ });
4441
+ }
4442
+ break;
4443
+ }
4444
+ case "response.function_call_arguments.delta": {
4445
+ const builder = fnCallBuilders.get(event.item_id);
4446
+ if (builder) builder.arguments += event.delta ?? "";
4447
+ break;
4448
+ }
4449
+ case "response.function_call_arguments.done": {
4450
+ const builder = fnCallBuilders.get(event.item_id);
4451
+ if (builder) {
4452
+ toolCalls.push({
4453
+ id: builder.callId,
4454
+ name: builder.name,
4455
+ input: parseArguments(event.arguments)
4456
+ });
4457
+ fnCallBuilders.delete(event.item_id);
4458
+ }
4459
+ break;
4460
+ }
4461
+ case "response.completed": {
4462
+ const resp = event.response;
4463
+ const usage = resp.usage;
4464
+ if (usage) {
4465
+ inputTokens = usage.input_tokens ?? 0;
4466
+ outputTokens = usage.output_tokens ?? 0;
4467
+ }
4468
+ for (const [, builder] of fnCallBuilders) {
4469
+ toolCalls.push({
4470
+ id: builder.callId,
4471
+ name: builder.name,
4472
+ input: parseArguments(builder.arguments)
4473
+ });
4474
+ }
4475
+ fnCallBuilders.clear();
4476
+ break;
4477
+ }
4478
+ }
4479
+ });
4480
+ return {
4481
+ id: responseId,
4482
+ content,
4483
+ stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn",
4484
+ model,
4485
+ usage: { inputTokens, outputTokens },
4486
+ toolCalls
4487
+ };
4488
+ }, this.retryConfig);
4326
4489
  }
4327
4490
  /**
4328
- * Stream a chat response
4329
- * Note: True streaming with Codex Responses API is complex.
4330
- * For now, we make a non-streaming call and simulate streaming by emitting chunks.
4491
+ * Stream a chat response (no tools)
4331
4492
  */
4332
4493
  async *stream(messages, options) {
4333
- const response = await this.chat(messages, options);
4334
- if (response.content) {
4335
- const content = response.content;
4336
- const chunkSize = 20;
4337
- for (let i = 0; i < content.length; i += chunkSize) {
4338
- const chunk = content.slice(i, i + chunkSize);
4339
- yield { type: "text", text: chunk };
4340
- if (i + chunkSize < content.length) {
4341
- await new Promise((resolve4) => setTimeout(resolve4, 5));
4494
+ const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
4495
+ const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
4496
+ const body = this.buildRequestBody(model, input, instructions, {
4497
+ maxTokens: options?.maxTokens
4498
+ });
4499
+ const response = await this.makeRequest(body);
4500
+ if (!response.body) {
4501
+ throw new ProviderError("No response body from Codex API", { provider: this.id });
4502
+ }
4503
+ const reader = response.body.getReader();
4504
+ const decoder = new TextDecoder();
4505
+ let buffer = "";
4506
+ let lastActivityTime = Date.now();
4507
+ const timeoutController = new AbortController();
4508
+ const timeoutInterval = setInterval(() => {
4509
+ if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
4510
+ clearInterval(timeoutInterval);
4511
+ timeoutController.abort();
4512
+ }
4513
+ }, 5e3);
4514
+ try {
4515
+ while (true) {
4516
+ if (timeoutController.signal.aborted) break;
4517
+ const { done, value } = await reader.read();
4518
+ if (done) break;
4519
+ lastActivityTime = Date.now();
4520
+ buffer += decoder.decode(value, { stream: true });
4521
+ const lines = buffer.split("\n");
4522
+ buffer = lines.pop() ?? "";
4523
+ for (const line of lines) {
4524
+ if (!line.startsWith("data: ")) continue;
4525
+ const data = line.slice(6).trim();
4526
+ if (!data || data === "[DONE]") continue;
4527
+ try {
4528
+ const event = JSON.parse(data);
4529
+ if (event.type === "response.output_text.delta" && event.delta) {
4530
+ yield { type: "text", text: event.delta };
4531
+ } else if (event.type === "response.completed") {
4532
+ yield { type: "done", stopReason: "end_turn" };
4533
+ }
4534
+ } catch {
4535
+ }
4342
4536
  }
4343
4537
  }
4538
+ } finally {
4539
+ clearInterval(timeoutInterval);
4540
+ reader.releaseLock();
4541
+ }
4542
+ if (timeoutController.signal.aborted) {
4543
+ throw new Error(
4544
+ `Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
4545
+ );
4344
4546
  }
4345
- yield { type: "done", stopReason: response.stopReason };
4346
4547
  }
4347
4548
  /**
4348
- * Stream a chat response with tool use
4349
- * Note: Tools and true streaming with Codex Responses API are not yet implemented.
4350
- * For now, we delegate to stream() which uses non-streaming under the hood.
4549
+ * Stream a chat response with tool use via Responses API.
4550
+ *
4551
+ * IMPORTANT: fnCallBuilders is keyed by output item ID (item.id), NOT by
4552
+ * call_id. The streaming events (function_call_arguments.delta/done) use
4553
+ * item_id which references the output item's id field, not call_id.
4351
4554
  */
4352
4555
  async *streamWithTools(messages, options) {
4353
- yield* this.stream(messages, options);
4556
+ const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
4557
+ const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
4558
+ const body = this.buildRequestBody(model, input, instructions, {
4559
+ tools: options.tools,
4560
+ maxTokens: options?.maxTokens
4561
+ });
4562
+ const response = await this.makeRequest(body);
4563
+ if (!response.body) {
4564
+ throw new ProviderError("No response body from Codex API", { provider: this.id });
4565
+ }
4566
+ const reader = response.body.getReader();
4567
+ const decoder = new TextDecoder();
4568
+ let buffer = "";
4569
+ const fnCallBuilders = /* @__PURE__ */ new Map();
4570
+ let lastActivityTime = Date.now();
4571
+ const timeoutController = new AbortController();
4572
+ const timeoutInterval = setInterval(() => {
4573
+ if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
4574
+ clearInterval(timeoutInterval);
4575
+ timeoutController.abort();
4576
+ }
4577
+ }, 5e3);
4578
+ try {
4579
+ while (true) {
4580
+ if (timeoutController.signal.aborted) break;
4581
+ const { done, value } = await reader.read();
4582
+ if (done) break;
4583
+ lastActivityTime = Date.now();
4584
+ buffer += decoder.decode(value, { stream: true });
4585
+ const lines = buffer.split("\n");
4586
+ buffer = lines.pop() ?? "";
4587
+ for (const line of lines) {
4588
+ if (!line.startsWith("data: ")) continue;
4589
+ const data = line.slice(6).trim();
4590
+ if (!data || data === "[DONE]") continue;
4591
+ let event;
4592
+ try {
4593
+ event = JSON.parse(data);
4594
+ } catch {
4595
+ continue;
4596
+ }
4597
+ switch (event.type) {
4598
+ case "response.output_text.delta":
4599
+ yield { type: "text", text: event.delta ?? "" };
4600
+ break;
4601
+ case "response.output_item.added": {
4602
+ const item = event.item;
4603
+ if (item.type === "function_call") {
4604
+ const itemKey = item.id ?? item.call_id;
4605
+ fnCallBuilders.set(itemKey, {
4606
+ callId: item.call_id,
4607
+ name: item.name,
4608
+ arguments: ""
4609
+ });
4610
+ yield {
4611
+ type: "tool_use_start",
4612
+ toolCall: { id: item.call_id, name: item.name }
4613
+ };
4614
+ }
4615
+ break;
4616
+ }
4617
+ case "response.function_call_arguments.delta": {
4618
+ const builder = fnCallBuilders.get(event.item_id);
4619
+ if (builder) {
4620
+ builder.arguments += event.delta ?? "";
4621
+ }
4622
+ break;
4623
+ }
4624
+ case "response.function_call_arguments.done": {
4625
+ const builder = fnCallBuilders.get(event.item_id);
4626
+ if (builder) {
4627
+ yield {
4628
+ type: "tool_use_end",
4629
+ toolCall: {
4630
+ id: builder.callId,
4631
+ name: builder.name,
4632
+ input: parseArguments(event.arguments ?? builder.arguments)
4633
+ }
4634
+ };
4635
+ fnCallBuilders.delete(event.item_id);
4636
+ }
4637
+ break;
4638
+ }
4639
+ case "response.completed": {
4640
+ for (const [, builder] of fnCallBuilders) {
4641
+ yield {
4642
+ type: "tool_use_end",
4643
+ toolCall: {
4644
+ id: builder.callId,
4645
+ name: builder.name,
4646
+ input: parseArguments(builder.arguments)
4647
+ }
4648
+ };
4649
+ }
4650
+ fnCallBuilders.clear();
4651
+ const resp = event.response;
4652
+ const output = resp?.output ?? [];
4653
+ const hasToolCalls = output.some((i) => i.type === "function_call");
4654
+ yield {
4655
+ type: "done",
4656
+ stopReason: hasToolCalls ? "tool_use" : "end_turn"
4657
+ };
4658
+ break;
4659
+ }
4660
+ }
4661
+ }
4662
+ }
4663
+ } finally {
4664
+ clearInterval(timeoutInterval);
4665
+ reader.releaseLock();
4666
+ }
4667
+ if (timeoutController.signal.aborted) {
4668
+ throw new Error(
4669
+ `Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
4670
+ );
4671
+ }
4354
4672
  }
4355
4673
  };
4356
4674
  }
@@ -4374,6 +4692,7 @@ var init_copilot2 = __esm({
4374
4692
  // OpenAI models — chat/completions
4375
4693
  "gpt-4.1": 1048576,
4376
4694
  // OpenAI models — /responses API (Codex/GPT-5+)
4695
+ "gpt-5.4-codex": 4e5,
4377
4696
  "gpt-5.3-codex": 4e5,
4378
4697
  "gpt-5.2-codex": 4e5,
4379
4698
  "gpt-5.1-codex-max": 4e5,
@@ -5035,6 +5354,7 @@ var init_pricing = __esm({
5035
5354
  "claude-sonnet-4-20250514": { inputPerMillion: 3, outputPerMillion: 15, contextWindow: 2e5 },
5036
5355
  "claude-opus-4-20250514": { inputPerMillion: 15, outputPerMillion: 75, contextWindow: 2e5 },
5037
5356
  // OpenAI models
5357
+ "gpt-5.4-codex": { inputPerMillion: 2, outputPerMillion: 8, contextWindow: 4e5 },
5038
5358
  "gpt-5.3-codex": { inputPerMillion: 2, outputPerMillion: 8, contextWindow: 4e5 },
5039
5359
  "gpt-5.2-codex": { inputPerMillion: 2, outputPerMillion: 8, contextWindow: 4e5 },
5040
5360
  "gpt-5.1-codex-max": { inputPerMillion: 3, outputPerMillion: 12, contextWindow: 4e5 },
@@ -5640,7 +5960,7 @@ function getDefaultModel(provider) {
5640
5960
  case "anthropic":
5641
5961
  return process.env["ANTHROPIC_MODEL"] ?? "claude-opus-4-6";
5642
5962
  case "openai":
5643
- return process.env["OPENAI_MODEL"] ?? "gpt-5.3-codex";
5963
+ return process.env["OPENAI_MODEL"] ?? "gpt-5.4-codex";
5644
5964
  case "gemini":
5645
5965
  return process.env["GEMINI_MODEL"] ?? "gemini-3.1-pro-preview";
5646
5966
  case "kimi":
@@ -5652,7 +5972,7 @@ function getDefaultModel(provider) {
5652
5972
  case "ollama":
5653
5973
  return process.env["OLLAMA_MODEL"] ?? "llama3.1";
5654
5974
  case "codex":
5655
- return process.env["CODEX_MODEL"] ?? "gpt-5.3-codex";
5975
+ return process.env["CODEX_MODEL"] ?? "gpt-5.4-codex";
5656
5976
  case "copilot":
5657
5977
  return process.env["COPILOT_MODEL"] ?? "claude-sonnet-4.6";
5658
5978
  case "groq":
@@ -5670,7 +5990,7 @@ function getDefaultModel(provider) {
5670
5990
  case "qwen":
5671
5991
  return process.env["QWEN_MODEL"] ?? "qwen-coder-plus";
5672
5992
  default:
5673
- return "gpt-5.3-codex";
5993
+ return "gpt-5.4-codex";
5674
5994
  }
5675
5995
  }
5676
5996
  function getDefaultProvider() {
@@ -25735,13 +26055,20 @@ var PROVIDER_DEFINITIONS = {
25735
26055
  // Updated: March 2026 — from platform.openai.com/docs/models
25736
26056
  models: [
25737
26057
  {
25738
- id: "gpt-5.3-codex",
25739
- name: "GPT-5.3 Codex",
25740
- description: "Latest agentic coding model (Feb 2026)",
26058
+ id: "gpt-5.4-codex",
26059
+ name: "GPT-5.4 Codex",
26060
+ description: "Latest agentic coding model (Mar 2026)",
25741
26061
  contextWindow: 4e5,
25742
26062
  maxOutputTokens: 128e3,
25743
26063
  recommended: true
25744
26064
  },
26065
+ {
26066
+ id: "gpt-5.3-codex",
26067
+ name: "GPT-5.3 Codex",
26068
+ description: "Previous agentic coding model (Feb 2026)",
26069
+ contextWindow: 4e5,
26070
+ maxOutputTokens: 128e3
26071
+ },
25745
26072
  {
25746
26073
  id: "gpt-5.2-codex",
25747
26074
  name: "GPT-5.2 Codex",
@@ -25871,13 +26198,20 @@ var PROVIDER_DEFINITIONS = {
25871
26198
  },
25872
26199
  // OpenAI models (Codex/GPT-5+ use /responses API, others use /chat/completions)
25873
26200
  {
25874
- id: "gpt-5.3-codex",
25875
- name: "GPT-5.3 Codex",
25876
- description: "OpenAI's latest coding model via Copilot",
26201
+ id: "gpt-5.4-codex",
26202
+ name: "GPT-5.4 Codex",
26203
+ description: "OpenAI's latest coding model via Copilot (Mar 2026)",
25877
26204
  contextWindow: 4e5,
25878
26205
  maxOutputTokens: 128e3,
25879
26206
  recommended: true
25880
26207
  },
26208
+ {
26209
+ id: "gpt-5.3-codex",
26210
+ name: "GPT-5.3 Codex",
26211
+ description: "OpenAI's previous coding model via Copilot",
26212
+ contextWindow: 4e5,
26213
+ maxOutputTokens: 128e3
26214
+ },
25881
26215
  {
25882
26216
  id: "gpt-5.2-codex",
25883
26217
  name: "GPT-5.2 Codex",
@@ -25949,13 +26283,20 @@ var PROVIDER_DEFINITIONS = {
25949
26283
  },
25950
26284
  models: [
25951
26285
  {
25952
- id: "gpt-5.3-codex",
25953
- name: "GPT-5.3 Codex",
25954
- description: "Latest coding model via ChatGPT subscription (Feb 2026)",
26286
+ id: "gpt-5.4-codex",
26287
+ name: "GPT-5.4 Codex",
26288
+ description: "Latest coding model via ChatGPT subscription (Mar 2026)",
25955
26289
  contextWindow: 2e5,
25956
26290
  maxOutputTokens: 128e3,
25957
26291
  recommended: true
25958
26292
  },
26293
+ {
26294
+ id: "gpt-5.3-codex",
26295
+ name: "GPT-5.3 Codex",
26296
+ description: "Previous coding model via ChatGPT subscription",
26297
+ contextWindow: 2e5,
26298
+ maxOutputTokens: 128e3
26299
+ },
25959
26300
  {
25960
26301
  id: "gpt-5.2-codex",
25961
26302
  name: "GPT-5.2 Codex",
@@ -26372,9 +26713,9 @@ var PROVIDER_DEFINITIONS = {
26372
26713
  recommended: true
26373
26714
  },
26374
26715
  {
26375
- id: "openai/gpt-5.3-codex",
26376
- name: "GPT-5.3 Codex (via OR)",
26377
- description: "OpenAI's coding model \u2014 via OpenRouter",
26716
+ id: "openai/gpt-5.4-codex",
26717
+ name: "GPT-5.4 Codex (via OR)",
26718
+ description: "OpenAI's latest coding model \u2014 via OpenRouter",
26378
26719
  contextWindow: 4e5,
26379
26720
  maxOutputTokens: 128e3
26380
26721
  },