@corbat-tech/coco 2.12.0 → 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 +432 -116
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +430 -113
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1756,7 +1756,7 @@ var init_openai = __esm({
|
|
|
1756
1756
|
once: true
|
|
1757
1757
|
});
|
|
1758
1758
|
const providerName = this.name;
|
|
1759
|
-
const
|
|
1759
|
+
const parseArguments2 = (builder) => {
|
|
1760
1760
|
let input = {};
|
|
1761
1761
|
try {
|
|
1762
1762
|
input = builder.arguments ? JSON.parse(builder.arguments) : {};
|
|
@@ -1837,7 +1837,7 @@ var init_openai = __esm({
|
|
|
1837
1837
|
toolCall: {
|
|
1838
1838
|
id: builder.id,
|
|
1839
1839
|
name: builder.name,
|
|
1840
|
-
input:
|
|
1840
|
+
input: parseArguments2(builder)
|
|
1841
1841
|
}
|
|
1842
1842
|
};
|
|
1843
1843
|
}
|
|
@@ -1850,7 +1850,7 @@ var init_openai = __esm({
|
|
|
1850
1850
|
toolCall: {
|
|
1851
1851
|
id: builder.id,
|
|
1852
1852
|
name: builder.name,
|
|
1853
|
-
input:
|
|
1853
|
+
input: parseArguments2(builder)
|
|
1854
1854
|
}
|
|
1855
1855
|
};
|
|
1856
1856
|
}
|
|
@@ -4061,8 +4061,6 @@ var init_auth = __esm({
|
|
|
4061
4061
|
init_gcloud();
|
|
4062
4062
|
}
|
|
4063
4063
|
});
|
|
4064
|
-
|
|
4065
|
-
// src/providers/codex.ts
|
|
4066
4064
|
function parseJwtClaims(token) {
|
|
4067
4065
|
const parts = token.split(".");
|
|
4068
4066
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
@@ -4078,6 +4076,21 @@ function extractAccountId(accessToken) {
|
|
|
4078
4076
|
const auth = claims["https://api.openai.com/auth"];
|
|
4079
4077
|
return claims["chatgpt_account_id"] || auth?.["chatgpt_account_id"] || claims["organizations"]?.[0]?.id;
|
|
4080
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
|
+
}
|
|
4081
4094
|
function createCodexProvider(config) {
|
|
4082
4095
|
const provider = new CodexProvider();
|
|
4083
4096
|
if (config) {
|
|
@@ -4086,11 +4099,12 @@ function createCodexProvider(config) {
|
|
|
4086
4099
|
}
|
|
4087
4100
|
return provider;
|
|
4088
4101
|
}
|
|
4089
|
-
var CODEX_API_ENDPOINT, DEFAULT_MODEL3, CONTEXT_WINDOWS3, CodexProvider;
|
|
4102
|
+
var CODEX_API_ENDPOINT, DEFAULT_MODEL3, CONTEXT_WINDOWS3, STREAM_TIMEOUT_MS, CodexProvider;
|
|
4090
4103
|
var init_codex = __esm({
|
|
4091
4104
|
"src/providers/codex.ts"() {
|
|
4092
4105
|
init_errors();
|
|
4093
4106
|
init_auth();
|
|
4107
|
+
init_retry();
|
|
4094
4108
|
CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
|
|
4095
4109
|
DEFAULT_MODEL3 = "gpt-5.4-codex";
|
|
4096
4110
|
CONTEXT_WINDOWS3 = {
|
|
@@ -4103,12 +4117,14 @@ var init_codex = __esm({
|
|
|
4103
4117
|
"gpt-5.2": 2e5,
|
|
4104
4118
|
"gpt-5.1": 2e5
|
|
4105
4119
|
};
|
|
4120
|
+
STREAM_TIMEOUT_MS = 12e4;
|
|
4106
4121
|
CodexProvider = class {
|
|
4107
4122
|
id = "codex";
|
|
4108
4123
|
name = "OpenAI Codex (ChatGPT Plus/Pro)";
|
|
4109
4124
|
config = {};
|
|
4110
4125
|
accessToken = null;
|
|
4111
4126
|
accountId;
|
|
4127
|
+
retryConfig = DEFAULT_RETRY_CONFIG;
|
|
4112
4128
|
/**
|
|
4113
4129
|
* Initialize the provider with OAuth tokens
|
|
4114
4130
|
*/
|
|
@@ -4193,166 +4209,466 @@ var init_codex = __esm({
|
|
|
4193
4209
|
/**
|
|
4194
4210
|
* Extract text content from a message
|
|
4195
4211
|
*/
|
|
4196
|
-
|
|
4197
|
-
if (typeof
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
if (Array.isArray(msg.content)) {
|
|
4201
|
-
return msg.content.map((part) => {
|
|
4202
|
-
if (part.type === "text") return part.text;
|
|
4203
|
-
if (part.type === "tool_result") return `Tool result: ${JSON.stringify(part.content)}`;
|
|
4204
|
-
return "";
|
|
4205
|
-
}).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");
|
|
4206
4216
|
}
|
|
4207
4217
|
return "";
|
|
4208
4218
|
}
|
|
4209
4219
|
/**
|
|
4210
|
-
* Convert messages to
|
|
4211
|
-
* Codex uses a different format than Chat Completions:
|
|
4212
|
-
* {
|
|
4213
|
-
* "input": [
|
|
4214
|
-
* { "type": "message", "role": "developer|user", "content": [{ "type": "input_text", "text": "..." }] },
|
|
4215
|
-
* { "type": "message", "role": "assistant", "content": [{ "type": "output_text", "text": "..." }] }
|
|
4216
|
-
* ]
|
|
4217
|
-
* }
|
|
4220
|
+
* Convert messages to Responses API input format.
|
|
4218
4221
|
*
|
|
4219
|
-
*
|
|
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
|
|
4220
4228
|
*/
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
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 };
|
|
4232
4282
|
}
|
|
4233
4283
|
/**
|
|
4234
|
-
*
|
|
4284
|
+
* Convert tool definitions to Responses API function tool format
|
|
4235
4285
|
*/
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
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) {
|
|
4241
4299
|
const body = {
|
|
4242
4300
|
model,
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
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,
|
|
4246
4305
|
store: false,
|
|
4247
4306
|
stream: true
|
|
4248
4307
|
// Codex API requires streaming
|
|
4249
4308
|
};
|
|
4250
|
-
|
|
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) {
|
|
4251
4319
|
if (!response.body) {
|
|
4252
|
-
throw new ProviderError("No response body from Codex API", {
|
|
4253
|
-
provider: this.id
|
|
4254
|
-
});
|
|
4320
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
4255
4321
|
}
|
|
4256
4322
|
const reader = response.body.getReader();
|
|
4257
4323
|
const decoder = new TextDecoder();
|
|
4258
4324
|
let buffer = "";
|
|
4259
|
-
let
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
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);
|
|
4264
4333
|
try {
|
|
4265
4334
|
while (true) {
|
|
4335
|
+
if (timeoutController.signal.aborted) break;
|
|
4266
4336
|
const { done, value } = await reader.read();
|
|
4267
4337
|
if (done) break;
|
|
4338
|
+
lastActivityTime = Date.now();
|
|
4268
4339
|
buffer += decoder.decode(value, { stream: true });
|
|
4269
4340
|
const lines = buffer.split("\n");
|
|
4270
4341
|
buffer = lines.pop() ?? "";
|
|
4271
4342
|
for (const line of lines) {
|
|
4272
|
-
if (line.startsWith("data: "))
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
responseId = parsed.id;
|
|
4279
|
-
}
|
|
4280
|
-
if (parsed.type === "response.output_text.delta" && parsed.delta) {
|
|
4281
|
-
content += parsed.delta;
|
|
4282
|
-
} else if (parsed.type === "response.completed" && parsed.response) {
|
|
4283
|
-
if (parsed.response.usage) {
|
|
4284
|
-
inputTokens = parsed.response.usage.input_tokens ?? 0;
|
|
4285
|
-
outputTokens = parsed.response.usage.output_tokens ?? 0;
|
|
4286
|
-
}
|
|
4287
|
-
status = parsed.response.status ?? "completed";
|
|
4288
|
-
} else if (parsed.type === "response.output_text.done" && parsed.text) {
|
|
4289
|
-
content = parsed.text;
|
|
4290
|
-
}
|
|
4291
|
-
} catch {
|
|
4292
|
-
}
|
|
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 {
|
|
4293
4349
|
}
|
|
4294
4350
|
}
|
|
4295
4351
|
}
|
|
4296
4352
|
} finally {
|
|
4353
|
+
clearInterval(timeoutInterval);
|
|
4297
4354
|
reader.releaseLock();
|
|
4298
4355
|
}
|
|
4299
|
-
if (
|
|
4300
|
-
throw new
|
|
4301
|
-
|
|
4302
|
-
|
|
4356
|
+
if (timeoutController.signal.aborted) {
|
|
4357
|
+
throw new Error(
|
|
4358
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
4359
|
+
);
|
|
4303
4360
|
}
|
|
4304
|
-
const stopReason = status === "completed" ? "end_turn" : status === "incomplete" ? "max_tokens" : "end_turn";
|
|
4305
|
-
return {
|
|
4306
|
-
id: responseId,
|
|
4307
|
-
content,
|
|
4308
|
-
stopReason,
|
|
4309
|
-
model,
|
|
4310
|
-
usage: {
|
|
4311
|
-
inputTokens,
|
|
4312
|
-
outputTokens
|
|
4313
|
-
}
|
|
4314
|
-
};
|
|
4315
4361
|
}
|
|
4316
4362
|
/**
|
|
4317
|
-
* Send a chat message
|
|
4318
|
-
|
|
4319
|
-
|
|
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
|
|
4320
4407
|
*/
|
|
4321
4408
|
async chatWithTools(messages, options) {
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
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);
|
|
4328
4489
|
}
|
|
4329
4490
|
/**
|
|
4330
|
-
* Stream a chat response
|
|
4331
|
-
* Note: True streaming with Codex Responses API is complex.
|
|
4332
|
-
* For now, we make a non-streaming call and simulate streaming by emitting chunks.
|
|
4491
|
+
* Stream a chat response (no tools)
|
|
4333
4492
|
*/
|
|
4334
4493
|
async *stream(messages, options) {
|
|
4335
|
-
const
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
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
|
+
}
|
|
4344
4536
|
}
|
|
4345
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
|
+
);
|
|
4346
4546
|
}
|
|
4347
|
-
yield { type: "done", stopReason: response.stopReason };
|
|
4348
4547
|
}
|
|
4349
4548
|
/**
|
|
4350
|
-
* Stream a chat response with tool use
|
|
4351
|
-
*
|
|
4352
|
-
*
|
|
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.
|
|
4353
4554
|
*/
|
|
4354
4555
|
async *streamWithTools(messages, options) {
|
|
4355
|
-
|
|
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
|
+
}
|
|
4356
4672
|
}
|
|
4357
4673
|
};
|
|
4358
4674
|
}
|