@corbat-tech/coco 2.12.0 → 2.13.1
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 +463 -125
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +461 -122
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1397,6 +1397,15 @@ var init_anthropic = __esm({
|
|
|
1397
1397
|
function needsResponsesApi(model) {
|
|
1398
1398
|
return model.includes("codex") || model.startsWith("gpt-5") || model.startsWith("o4-") || model.startsWith("o3-");
|
|
1399
1399
|
}
|
|
1400
|
+
function needsMaxCompletionTokens(model) {
|
|
1401
|
+
return model.startsWith("o1") || model.startsWith("o3") || model.startsWith("o4") || model.startsWith("gpt-4o") || model.startsWith("gpt-4.1") || model.startsWith("gpt-5") || model.startsWith("chatgpt-4o");
|
|
1402
|
+
}
|
|
1403
|
+
function buildMaxTokensParam(model, maxTokens) {
|
|
1404
|
+
if (needsMaxCompletionTokens(model)) {
|
|
1405
|
+
return { max_completion_tokens: maxTokens };
|
|
1406
|
+
}
|
|
1407
|
+
return { max_tokens: maxTokens };
|
|
1408
|
+
}
|
|
1400
1409
|
function createOpenAIProvider(config) {
|
|
1401
1410
|
const provider = new OpenAIProvider();
|
|
1402
1411
|
if (config) {
|
|
@@ -1605,9 +1614,10 @@ var init_openai = __esm({
|
|
|
1605
1614
|
return withRetry(async () => {
|
|
1606
1615
|
try {
|
|
1607
1616
|
const supportsTemp = this.supportsTemperature(model);
|
|
1617
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
|
|
1608
1618
|
const response = await this.client.chat.completions.create({
|
|
1609
1619
|
model,
|
|
1610
|
-
|
|
1620
|
+
...buildMaxTokensParam(model, maxTokens),
|
|
1611
1621
|
messages: this.convertMessages(messages, options?.system),
|
|
1612
1622
|
stop: options?.stopSequences,
|
|
1613
1623
|
...supportsTemp && {
|
|
@@ -1643,9 +1653,10 @@ var init_openai = __esm({
|
|
|
1643
1653
|
try {
|
|
1644
1654
|
const supportsTemp = this.supportsTemperature(model);
|
|
1645
1655
|
const extraBody = this.getExtraBody(model);
|
|
1656
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
|
|
1646
1657
|
const requestParams = {
|
|
1647
1658
|
model,
|
|
1648
|
-
|
|
1659
|
+
...buildMaxTokensParam(model, maxTokens),
|
|
1649
1660
|
messages: this.convertMessages(messages, options?.system),
|
|
1650
1661
|
tools: this.convertTools(options.tools),
|
|
1651
1662
|
tool_choice: this.convertToolChoice(options.toolChoice)
|
|
@@ -1689,9 +1700,10 @@ var init_openai = __esm({
|
|
|
1689
1700
|
}
|
|
1690
1701
|
try {
|
|
1691
1702
|
const supportsTemp = this.supportsTemperature(model);
|
|
1703
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
|
|
1692
1704
|
const stream = await this.client.chat.completions.create({
|
|
1693
1705
|
model,
|
|
1694
|
-
|
|
1706
|
+
...buildMaxTokensParam(model, maxTokens),
|
|
1695
1707
|
messages: this.convertMessages(messages, options?.system),
|
|
1696
1708
|
stream: true,
|
|
1697
1709
|
...supportsTemp && { temperature: options?.temperature ?? this.config.temperature ?? 0 }
|
|
@@ -1725,9 +1737,10 @@ var init_openai = __esm({
|
|
|
1725
1737
|
try {
|
|
1726
1738
|
const supportsTemp = this.supportsTemperature(model);
|
|
1727
1739
|
const extraBody = this.getExtraBody(model);
|
|
1740
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
|
|
1728
1741
|
const requestParams = {
|
|
1729
1742
|
model,
|
|
1730
|
-
|
|
1743
|
+
...buildMaxTokensParam(model, maxTokens),
|
|
1731
1744
|
messages: this.convertMessages(messages, options?.system),
|
|
1732
1745
|
tools: this.convertTools(options.tools),
|
|
1733
1746
|
tool_choice: this.convertToolChoice(options.toolChoice),
|
|
@@ -1756,7 +1769,7 @@ var init_openai = __esm({
|
|
|
1756
1769
|
once: true
|
|
1757
1770
|
});
|
|
1758
1771
|
const providerName = this.name;
|
|
1759
|
-
const
|
|
1772
|
+
const parseArguments2 = (builder) => {
|
|
1760
1773
|
let input = {};
|
|
1761
1774
|
try {
|
|
1762
1775
|
input = builder.arguments ? JSON.parse(builder.arguments) : {};
|
|
@@ -1837,7 +1850,7 @@ var init_openai = __esm({
|
|
|
1837
1850
|
toolCall: {
|
|
1838
1851
|
id: builder.id,
|
|
1839
1852
|
name: builder.name,
|
|
1840
|
-
input:
|
|
1853
|
+
input: parseArguments2(builder)
|
|
1841
1854
|
}
|
|
1842
1855
|
};
|
|
1843
1856
|
}
|
|
@@ -1850,7 +1863,7 @@ var init_openai = __esm({
|
|
|
1850
1863
|
toolCall: {
|
|
1851
1864
|
id: builder.id,
|
|
1852
1865
|
name: builder.name,
|
|
1853
|
-
input:
|
|
1866
|
+
input: parseArguments2(builder)
|
|
1854
1867
|
}
|
|
1855
1868
|
};
|
|
1856
1869
|
}
|
|
@@ -1989,11 +2002,20 @@ var init_openai = __esm({
|
|
|
1989
2002
|
} catch {
|
|
1990
2003
|
try {
|
|
1991
2004
|
const model = this.config.model || DEFAULT_MODEL2;
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2005
|
+
if (needsResponsesApi(model)) {
|
|
2006
|
+
await this.client.responses.create({
|
|
2007
|
+
model,
|
|
2008
|
+
input: [{ role: "user", content: [{ type: "input_text", text: "Hi" }] }],
|
|
2009
|
+
max_output_tokens: 1,
|
|
2010
|
+
store: false
|
|
2011
|
+
});
|
|
2012
|
+
} else {
|
|
2013
|
+
await this.client.chat.completions.create({
|
|
2014
|
+
model,
|
|
2015
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
2016
|
+
...buildMaxTokensParam(model, 1)
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
1997
2019
|
return true;
|
|
1998
2020
|
} catch {
|
|
1999
2021
|
return false;
|
|
@@ -4061,8 +4083,6 @@ var init_auth = __esm({
|
|
|
4061
4083
|
init_gcloud();
|
|
4062
4084
|
}
|
|
4063
4085
|
});
|
|
4064
|
-
|
|
4065
|
-
// src/providers/codex.ts
|
|
4066
4086
|
function parseJwtClaims(token) {
|
|
4067
4087
|
const parts = token.split(".");
|
|
4068
4088
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
@@ -4078,6 +4098,21 @@ function extractAccountId(accessToken) {
|
|
|
4078
4098
|
const auth = claims["https://api.openai.com/auth"];
|
|
4079
4099
|
return claims["chatgpt_account_id"] || auth?.["chatgpt_account_id"] || claims["organizations"]?.[0]?.id;
|
|
4080
4100
|
}
|
|
4101
|
+
function parseArguments(args) {
|
|
4102
|
+
try {
|
|
4103
|
+
return args ? JSON.parse(args) : {};
|
|
4104
|
+
} catch {
|
|
4105
|
+
try {
|
|
4106
|
+
if (args) {
|
|
4107
|
+
const repaired = jsonrepair(args);
|
|
4108
|
+
return JSON.parse(repaired);
|
|
4109
|
+
}
|
|
4110
|
+
} catch {
|
|
4111
|
+
console.error(`[Codex] Cannot parse tool arguments: ${args.slice(0, 200)}`);
|
|
4112
|
+
}
|
|
4113
|
+
return {};
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4081
4116
|
function createCodexProvider(config) {
|
|
4082
4117
|
const provider = new CodexProvider();
|
|
4083
4118
|
if (config) {
|
|
@@ -4086,11 +4121,12 @@ function createCodexProvider(config) {
|
|
|
4086
4121
|
}
|
|
4087
4122
|
return provider;
|
|
4088
4123
|
}
|
|
4089
|
-
var CODEX_API_ENDPOINT, DEFAULT_MODEL3, CONTEXT_WINDOWS3, CodexProvider;
|
|
4124
|
+
var CODEX_API_ENDPOINT, DEFAULT_MODEL3, CONTEXT_WINDOWS3, STREAM_TIMEOUT_MS, CodexProvider;
|
|
4090
4125
|
var init_codex = __esm({
|
|
4091
4126
|
"src/providers/codex.ts"() {
|
|
4092
4127
|
init_errors();
|
|
4093
4128
|
init_auth();
|
|
4129
|
+
init_retry();
|
|
4094
4130
|
CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
|
|
4095
4131
|
DEFAULT_MODEL3 = "gpt-5.4-codex";
|
|
4096
4132
|
CONTEXT_WINDOWS3 = {
|
|
@@ -4103,12 +4139,14 @@ var init_codex = __esm({
|
|
|
4103
4139
|
"gpt-5.2": 2e5,
|
|
4104
4140
|
"gpt-5.1": 2e5
|
|
4105
4141
|
};
|
|
4142
|
+
STREAM_TIMEOUT_MS = 12e4;
|
|
4106
4143
|
CodexProvider = class {
|
|
4107
4144
|
id = "codex";
|
|
4108
4145
|
name = "OpenAI Codex (ChatGPT Plus/Pro)";
|
|
4109
4146
|
config = {};
|
|
4110
4147
|
accessToken = null;
|
|
4111
4148
|
accountId;
|
|
4149
|
+
retryConfig = DEFAULT_RETRY_CONFIG;
|
|
4112
4150
|
/**
|
|
4113
4151
|
* Initialize the provider with OAuth tokens
|
|
4114
4152
|
*/
|
|
@@ -4193,166 +4231,466 @@ var init_codex = __esm({
|
|
|
4193
4231
|
/**
|
|
4194
4232
|
* Extract text content from a message
|
|
4195
4233
|
*/
|
|
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");
|
|
4234
|
+
contentToString(content) {
|
|
4235
|
+
if (typeof content === "string") return content;
|
|
4236
|
+
if (Array.isArray(content)) {
|
|
4237
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
4206
4238
|
}
|
|
4207
4239
|
return "";
|
|
4208
4240
|
}
|
|
4209
4241
|
/**
|
|
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
|
-
* }
|
|
4242
|
+
* Convert messages to Responses API input format.
|
|
4218
4243
|
*
|
|
4219
|
-
*
|
|
4244
|
+
* Handles:
|
|
4245
|
+
* - system messages → extracted as instructions
|
|
4246
|
+
* - user text messages → { role: "user", content: "..." }
|
|
4247
|
+
* - user tool_result messages → function_call_output items
|
|
4248
|
+
* - assistant text → { role: "assistant", content: "..." }
|
|
4249
|
+
* - assistant tool_use → function_call items
|
|
4220
4250
|
*/
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4251
|
+
convertToResponsesInput(messages, systemPrompt) {
|
|
4252
|
+
const input = [];
|
|
4253
|
+
let instructions = systemPrompt ?? null;
|
|
4254
|
+
for (const msg of messages) {
|
|
4255
|
+
if (msg.role === "system") {
|
|
4256
|
+
instructions = (instructions ? instructions + "\n\n" : "") + this.contentToString(msg.content);
|
|
4257
|
+
} else if (msg.role === "user") {
|
|
4258
|
+
if (Array.isArray(msg.content) && msg.content.some((b) => b.type === "tool_result")) {
|
|
4259
|
+
for (const block of msg.content) {
|
|
4260
|
+
if (block.type === "tool_result") {
|
|
4261
|
+
const tr = block;
|
|
4262
|
+
input.push({
|
|
4263
|
+
type: "function_call_output",
|
|
4264
|
+
call_id: tr.tool_use_id,
|
|
4265
|
+
output: tr.content
|
|
4266
|
+
});
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
} else {
|
|
4270
|
+
input.push({
|
|
4271
|
+
role: "user",
|
|
4272
|
+
content: this.contentToString(msg.content)
|
|
4273
|
+
});
|
|
4274
|
+
}
|
|
4275
|
+
} else if (msg.role === "assistant") {
|
|
4276
|
+
if (typeof msg.content === "string") {
|
|
4277
|
+
input.push({ role: "assistant", content: msg.content });
|
|
4278
|
+
} else if (Array.isArray(msg.content)) {
|
|
4279
|
+
const textParts = [];
|
|
4280
|
+
for (const block of msg.content) {
|
|
4281
|
+
if (block.type === "text") {
|
|
4282
|
+
textParts.push(block.text);
|
|
4283
|
+
} else if (block.type === "tool_use") {
|
|
4284
|
+
if (textParts.length > 0) {
|
|
4285
|
+
input.push({ role: "assistant", content: textParts.join("") });
|
|
4286
|
+
textParts.length = 0;
|
|
4287
|
+
}
|
|
4288
|
+
const tu = block;
|
|
4289
|
+
input.push({
|
|
4290
|
+
type: "function_call",
|
|
4291
|
+
call_id: tu.id,
|
|
4292
|
+
name: tu.name,
|
|
4293
|
+
arguments: JSON.stringify(tu.input)
|
|
4294
|
+
});
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
if (textParts.length > 0) {
|
|
4298
|
+
input.push({ role: "assistant", content: textParts.join("") });
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
return { input, instructions };
|
|
4232
4304
|
}
|
|
4233
4305
|
/**
|
|
4234
|
-
*
|
|
4306
|
+
* Convert tool definitions to Responses API function tool format
|
|
4235
4307
|
*/
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4308
|
+
convertTools(tools) {
|
|
4309
|
+
return tools.map((tool) => ({
|
|
4310
|
+
type: "function",
|
|
4311
|
+
name: tool.name,
|
|
4312
|
+
description: tool.description ?? void 0,
|
|
4313
|
+
parameters: tool.input_schema ?? null,
|
|
4314
|
+
strict: false
|
|
4315
|
+
}));
|
|
4316
|
+
}
|
|
4317
|
+
/**
|
|
4318
|
+
* Build the request body for the Codex Responses API
|
|
4319
|
+
*/
|
|
4320
|
+
buildRequestBody(model, input, instructions, options) {
|
|
4241
4321
|
const body = {
|
|
4242
4322
|
model,
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4323
|
+
input,
|
|
4324
|
+
instructions: instructions ?? "You are a helpful coding assistant.",
|
|
4325
|
+
max_output_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
4326
|
+
temperature: options?.temperature ?? this.config.temperature ?? 0,
|
|
4246
4327
|
store: false,
|
|
4247
4328
|
stream: true
|
|
4248
4329
|
// Codex API requires streaming
|
|
4249
4330
|
};
|
|
4250
|
-
|
|
4331
|
+
if (options?.tools && options.tools.length > 0) {
|
|
4332
|
+
body.tools = this.convertTools(options.tools);
|
|
4333
|
+
}
|
|
4334
|
+
return body;
|
|
4335
|
+
}
|
|
4336
|
+
/**
|
|
4337
|
+
* Read SSE stream and call handler for each parsed event.
|
|
4338
|
+
* Returns when stream ends.
|
|
4339
|
+
*/
|
|
4340
|
+
async readSSEStream(response, onEvent) {
|
|
4251
4341
|
if (!response.body) {
|
|
4252
|
-
throw new ProviderError("No response body from Codex API", {
|
|
4253
|
-
provider: this.id
|
|
4254
|
-
});
|
|
4342
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
4255
4343
|
}
|
|
4256
4344
|
const reader = response.body.getReader();
|
|
4257
4345
|
const decoder = new TextDecoder();
|
|
4258
4346
|
let buffer = "";
|
|
4259
|
-
let
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4347
|
+
let lastActivityTime = Date.now();
|
|
4348
|
+
const timeoutController = new AbortController();
|
|
4349
|
+
const timeoutInterval = setInterval(() => {
|
|
4350
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
4351
|
+
clearInterval(timeoutInterval);
|
|
4352
|
+
timeoutController.abort();
|
|
4353
|
+
}
|
|
4354
|
+
}, 5e3);
|
|
4264
4355
|
try {
|
|
4265
4356
|
while (true) {
|
|
4357
|
+
if (timeoutController.signal.aborted) break;
|
|
4266
4358
|
const { done, value } = await reader.read();
|
|
4267
4359
|
if (done) break;
|
|
4360
|
+
lastActivityTime = Date.now();
|
|
4268
4361
|
buffer += decoder.decode(value, { stream: true });
|
|
4269
4362
|
const lines = buffer.split("\n");
|
|
4270
4363
|
buffer = lines.pop() ?? "";
|
|
4271
4364
|
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
|
-
}
|
|
4365
|
+
if (!line.startsWith("data: ")) continue;
|
|
4366
|
+
const data = line.slice(6).trim();
|
|
4367
|
+
if (!data || data === "[DONE]") continue;
|
|
4368
|
+
try {
|
|
4369
|
+
onEvent(JSON.parse(data));
|
|
4370
|
+
} catch {
|
|
4293
4371
|
}
|
|
4294
4372
|
}
|
|
4295
4373
|
}
|
|
4296
4374
|
} finally {
|
|
4375
|
+
clearInterval(timeoutInterval);
|
|
4297
4376
|
reader.releaseLock();
|
|
4298
4377
|
}
|
|
4299
|
-
if (
|
|
4300
|
-
throw new
|
|
4301
|
-
|
|
4302
|
-
|
|
4378
|
+
if (timeoutController.signal.aborted) {
|
|
4379
|
+
throw new Error(
|
|
4380
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
4381
|
+
);
|
|
4303
4382
|
}
|
|
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
4383
|
}
|
|
4316
4384
|
/**
|
|
4317
|
-
* Send a chat message
|
|
4318
|
-
|
|
4319
|
-
|
|
4385
|
+
* Send a chat message using Codex Responses API format
|
|
4386
|
+
*/
|
|
4387
|
+
async chat(messages, options) {
|
|
4388
|
+
return withRetry(async () => {
|
|
4389
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
4390
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
4391
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
4392
|
+
maxTokens: options?.maxTokens,
|
|
4393
|
+
temperature: options?.temperature
|
|
4394
|
+
});
|
|
4395
|
+
const response = await this.makeRequest(body);
|
|
4396
|
+
let content = "";
|
|
4397
|
+
let responseId = `codex-${Date.now()}`;
|
|
4398
|
+
let inputTokens = 0;
|
|
4399
|
+
let outputTokens = 0;
|
|
4400
|
+
let status = "completed";
|
|
4401
|
+
await this.readSSEStream(response, (event) => {
|
|
4402
|
+
if (event.id) responseId = event.id;
|
|
4403
|
+
if (event.type === "response.output_text.delta" && event.delta) {
|
|
4404
|
+
content += event.delta;
|
|
4405
|
+
} else if (event.type === "response.output_text.done" && event.text) {
|
|
4406
|
+
content = event.text;
|
|
4407
|
+
} else if (event.type === "response.completed" && event.response) {
|
|
4408
|
+
const resp = event.response;
|
|
4409
|
+
const usage = resp.usage;
|
|
4410
|
+
if (usage) {
|
|
4411
|
+
inputTokens = usage.input_tokens ?? 0;
|
|
4412
|
+
outputTokens = usage.output_tokens ?? 0;
|
|
4413
|
+
}
|
|
4414
|
+
status = resp.status ?? "completed";
|
|
4415
|
+
}
|
|
4416
|
+
});
|
|
4417
|
+
const stopReason = status === "completed" ? "end_turn" : status === "incomplete" ? "max_tokens" : "end_turn";
|
|
4418
|
+
return {
|
|
4419
|
+
id: responseId,
|
|
4420
|
+
content,
|
|
4421
|
+
stopReason,
|
|
4422
|
+
model,
|
|
4423
|
+
usage: { inputTokens, outputTokens }
|
|
4424
|
+
};
|
|
4425
|
+
}, this.retryConfig);
|
|
4426
|
+
}
|
|
4427
|
+
/**
|
|
4428
|
+
* Send a chat message with tool use via Responses API
|
|
4320
4429
|
*/
|
|
4321
4430
|
async chatWithTools(messages, options) {
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4431
|
+
return withRetry(async () => {
|
|
4432
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
4433
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
4434
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
4435
|
+
tools: options.tools,
|
|
4436
|
+
maxTokens: options?.maxTokens
|
|
4437
|
+
});
|
|
4438
|
+
const response = await this.makeRequest(body);
|
|
4439
|
+
let content = "";
|
|
4440
|
+
let responseId = `codex-${Date.now()}`;
|
|
4441
|
+
let inputTokens = 0;
|
|
4442
|
+
let outputTokens = 0;
|
|
4443
|
+
const toolCalls = [];
|
|
4444
|
+
const fnCallBuilders = /* @__PURE__ */ new Map();
|
|
4445
|
+
await this.readSSEStream(response, (event) => {
|
|
4446
|
+
if (event.id) responseId = event.id;
|
|
4447
|
+
switch (event.type) {
|
|
4448
|
+
case "response.output_text.delta":
|
|
4449
|
+
content += event.delta ?? "";
|
|
4450
|
+
break;
|
|
4451
|
+
case "response.output_text.done":
|
|
4452
|
+
content = event.text ?? content;
|
|
4453
|
+
break;
|
|
4454
|
+
case "response.output_item.added": {
|
|
4455
|
+
const item = event.item;
|
|
4456
|
+
if (item.type === "function_call") {
|
|
4457
|
+
const itemKey = item.id ?? item.call_id;
|
|
4458
|
+
fnCallBuilders.set(itemKey, {
|
|
4459
|
+
callId: item.call_id,
|
|
4460
|
+
name: item.name,
|
|
4461
|
+
arguments: ""
|
|
4462
|
+
});
|
|
4463
|
+
}
|
|
4464
|
+
break;
|
|
4465
|
+
}
|
|
4466
|
+
case "response.function_call_arguments.delta": {
|
|
4467
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
4468
|
+
if (builder) builder.arguments += event.delta ?? "";
|
|
4469
|
+
break;
|
|
4470
|
+
}
|
|
4471
|
+
case "response.function_call_arguments.done": {
|
|
4472
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
4473
|
+
if (builder) {
|
|
4474
|
+
toolCalls.push({
|
|
4475
|
+
id: builder.callId,
|
|
4476
|
+
name: builder.name,
|
|
4477
|
+
input: parseArguments(event.arguments)
|
|
4478
|
+
});
|
|
4479
|
+
fnCallBuilders.delete(event.item_id);
|
|
4480
|
+
}
|
|
4481
|
+
break;
|
|
4482
|
+
}
|
|
4483
|
+
case "response.completed": {
|
|
4484
|
+
const resp = event.response;
|
|
4485
|
+
const usage = resp.usage;
|
|
4486
|
+
if (usage) {
|
|
4487
|
+
inputTokens = usage.input_tokens ?? 0;
|
|
4488
|
+
outputTokens = usage.output_tokens ?? 0;
|
|
4489
|
+
}
|
|
4490
|
+
for (const [, builder] of fnCallBuilders) {
|
|
4491
|
+
toolCalls.push({
|
|
4492
|
+
id: builder.callId,
|
|
4493
|
+
name: builder.name,
|
|
4494
|
+
input: parseArguments(builder.arguments)
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
fnCallBuilders.clear();
|
|
4498
|
+
break;
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
});
|
|
4502
|
+
return {
|
|
4503
|
+
id: responseId,
|
|
4504
|
+
content,
|
|
4505
|
+
stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn",
|
|
4506
|
+
model,
|
|
4507
|
+
usage: { inputTokens, outputTokens },
|
|
4508
|
+
toolCalls
|
|
4509
|
+
};
|
|
4510
|
+
}, this.retryConfig);
|
|
4328
4511
|
}
|
|
4329
4512
|
/**
|
|
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.
|
|
4513
|
+
* Stream a chat response (no tools)
|
|
4333
4514
|
*/
|
|
4334
4515
|
async *stream(messages, options) {
|
|
4335
|
-
const
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4516
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
4517
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
4518
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
4519
|
+
maxTokens: options?.maxTokens
|
|
4520
|
+
});
|
|
4521
|
+
const response = await this.makeRequest(body);
|
|
4522
|
+
if (!response.body) {
|
|
4523
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
4524
|
+
}
|
|
4525
|
+
const reader = response.body.getReader();
|
|
4526
|
+
const decoder = new TextDecoder();
|
|
4527
|
+
let buffer = "";
|
|
4528
|
+
let lastActivityTime = Date.now();
|
|
4529
|
+
const timeoutController = new AbortController();
|
|
4530
|
+
const timeoutInterval = setInterval(() => {
|
|
4531
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
4532
|
+
clearInterval(timeoutInterval);
|
|
4533
|
+
timeoutController.abort();
|
|
4534
|
+
}
|
|
4535
|
+
}, 5e3);
|
|
4536
|
+
try {
|
|
4537
|
+
while (true) {
|
|
4538
|
+
if (timeoutController.signal.aborted) break;
|
|
4539
|
+
const { done, value } = await reader.read();
|
|
4540
|
+
if (done) break;
|
|
4541
|
+
lastActivityTime = Date.now();
|
|
4542
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4543
|
+
const lines = buffer.split("\n");
|
|
4544
|
+
buffer = lines.pop() ?? "";
|
|
4545
|
+
for (const line of lines) {
|
|
4546
|
+
if (!line.startsWith("data: ")) continue;
|
|
4547
|
+
const data = line.slice(6).trim();
|
|
4548
|
+
if (!data || data === "[DONE]") continue;
|
|
4549
|
+
try {
|
|
4550
|
+
const event = JSON.parse(data);
|
|
4551
|
+
if (event.type === "response.output_text.delta" && event.delta) {
|
|
4552
|
+
yield { type: "text", text: event.delta };
|
|
4553
|
+
} else if (event.type === "response.completed") {
|
|
4554
|
+
yield { type: "done", stopReason: "end_turn" };
|
|
4555
|
+
}
|
|
4556
|
+
} catch {
|
|
4557
|
+
}
|
|
4344
4558
|
}
|
|
4345
4559
|
}
|
|
4560
|
+
} finally {
|
|
4561
|
+
clearInterval(timeoutInterval);
|
|
4562
|
+
reader.releaseLock();
|
|
4563
|
+
}
|
|
4564
|
+
if (timeoutController.signal.aborted) {
|
|
4565
|
+
throw new Error(
|
|
4566
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
4567
|
+
);
|
|
4346
4568
|
}
|
|
4347
|
-
yield { type: "done", stopReason: response.stopReason };
|
|
4348
4569
|
}
|
|
4349
4570
|
/**
|
|
4350
|
-
* Stream a chat response with tool use
|
|
4351
|
-
*
|
|
4352
|
-
*
|
|
4571
|
+
* Stream a chat response with tool use via Responses API.
|
|
4572
|
+
*
|
|
4573
|
+
* IMPORTANT: fnCallBuilders is keyed by output item ID (item.id), NOT by
|
|
4574
|
+
* call_id. The streaming events (function_call_arguments.delta/done) use
|
|
4575
|
+
* item_id which references the output item's id field, not call_id.
|
|
4353
4576
|
*/
|
|
4354
4577
|
async *streamWithTools(messages, options) {
|
|
4355
|
-
|
|
4578
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
4579
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
4580
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
4581
|
+
tools: options.tools,
|
|
4582
|
+
maxTokens: options?.maxTokens
|
|
4583
|
+
});
|
|
4584
|
+
const response = await this.makeRequest(body);
|
|
4585
|
+
if (!response.body) {
|
|
4586
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
4587
|
+
}
|
|
4588
|
+
const reader = response.body.getReader();
|
|
4589
|
+
const decoder = new TextDecoder();
|
|
4590
|
+
let buffer = "";
|
|
4591
|
+
const fnCallBuilders = /* @__PURE__ */ new Map();
|
|
4592
|
+
let lastActivityTime = Date.now();
|
|
4593
|
+
const timeoutController = new AbortController();
|
|
4594
|
+
const timeoutInterval = setInterval(() => {
|
|
4595
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
4596
|
+
clearInterval(timeoutInterval);
|
|
4597
|
+
timeoutController.abort();
|
|
4598
|
+
}
|
|
4599
|
+
}, 5e3);
|
|
4600
|
+
try {
|
|
4601
|
+
while (true) {
|
|
4602
|
+
if (timeoutController.signal.aborted) break;
|
|
4603
|
+
const { done, value } = await reader.read();
|
|
4604
|
+
if (done) break;
|
|
4605
|
+
lastActivityTime = Date.now();
|
|
4606
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4607
|
+
const lines = buffer.split("\n");
|
|
4608
|
+
buffer = lines.pop() ?? "";
|
|
4609
|
+
for (const line of lines) {
|
|
4610
|
+
if (!line.startsWith("data: ")) continue;
|
|
4611
|
+
const data = line.slice(6).trim();
|
|
4612
|
+
if (!data || data === "[DONE]") continue;
|
|
4613
|
+
let event;
|
|
4614
|
+
try {
|
|
4615
|
+
event = JSON.parse(data);
|
|
4616
|
+
} catch {
|
|
4617
|
+
continue;
|
|
4618
|
+
}
|
|
4619
|
+
switch (event.type) {
|
|
4620
|
+
case "response.output_text.delta":
|
|
4621
|
+
yield { type: "text", text: event.delta ?? "" };
|
|
4622
|
+
break;
|
|
4623
|
+
case "response.output_item.added": {
|
|
4624
|
+
const item = event.item;
|
|
4625
|
+
if (item.type === "function_call") {
|
|
4626
|
+
const itemKey = item.id ?? item.call_id;
|
|
4627
|
+
fnCallBuilders.set(itemKey, {
|
|
4628
|
+
callId: item.call_id,
|
|
4629
|
+
name: item.name,
|
|
4630
|
+
arguments: ""
|
|
4631
|
+
});
|
|
4632
|
+
yield {
|
|
4633
|
+
type: "tool_use_start",
|
|
4634
|
+
toolCall: { id: item.call_id, name: item.name }
|
|
4635
|
+
};
|
|
4636
|
+
}
|
|
4637
|
+
break;
|
|
4638
|
+
}
|
|
4639
|
+
case "response.function_call_arguments.delta": {
|
|
4640
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
4641
|
+
if (builder) {
|
|
4642
|
+
builder.arguments += event.delta ?? "";
|
|
4643
|
+
}
|
|
4644
|
+
break;
|
|
4645
|
+
}
|
|
4646
|
+
case "response.function_call_arguments.done": {
|
|
4647
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
4648
|
+
if (builder) {
|
|
4649
|
+
yield {
|
|
4650
|
+
type: "tool_use_end",
|
|
4651
|
+
toolCall: {
|
|
4652
|
+
id: builder.callId,
|
|
4653
|
+
name: builder.name,
|
|
4654
|
+
input: parseArguments(event.arguments ?? builder.arguments)
|
|
4655
|
+
}
|
|
4656
|
+
};
|
|
4657
|
+
fnCallBuilders.delete(event.item_id);
|
|
4658
|
+
}
|
|
4659
|
+
break;
|
|
4660
|
+
}
|
|
4661
|
+
case "response.completed": {
|
|
4662
|
+
for (const [, builder] of fnCallBuilders) {
|
|
4663
|
+
yield {
|
|
4664
|
+
type: "tool_use_end",
|
|
4665
|
+
toolCall: {
|
|
4666
|
+
id: builder.callId,
|
|
4667
|
+
name: builder.name,
|
|
4668
|
+
input: parseArguments(builder.arguments)
|
|
4669
|
+
}
|
|
4670
|
+
};
|
|
4671
|
+
}
|
|
4672
|
+
fnCallBuilders.clear();
|
|
4673
|
+
const resp = event.response;
|
|
4674
|
+
const output = resp?.output ?? [];
|
|
4675
|
+
const hasToolCalls = output.some((i) => i.type === "function_call");
|
|
4676
|
+
yield {
|
|
4677
|
+
type: "done",
|
|
4678
|
+
stopReason: hasToolCalls ? "tool_use" : "end_turn"
|
|
4679
|
+
};
|
|
4680
|
+
break;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
} finally {
|
|
4686
|
+
clearInterval(timeoutInterval);
|
|
4687
|
+
reader.releaseLock();
|
|
4688
|
+
}
|
|
4689
|
+
if (timeoutController.signal.aborted) {
|
|
4690
|
+
throw new Error(
|
|
4691
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
4692
|
+
);
|
|
4693
|
+
}
|
|
4356
4694
|
}
|
|
4357
4695
|
};
|
|
4358
4696
|
}
|