@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/index.js
CHANGED
|
@@ -12300,6 +12300,15 @@ var MODELS_WITH_THINKING_MODE = ["kimi-k2.5", "kimi-k2-0324", "kimi-latest"];
|
|
|
12300
12300
|
function needsResponsesApi(model) {
|
|
12301
12301
|
return model.includes("codex") || model.startsWith("gpt-5") || model.startsWith("o4-") || model.startsWith("o3-");
|
|
12302
12302
|
}
|
|
12303
|
+
function needsMaxCompletionTokens(model) {
|
|
12304
|
+
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");
|
|
12305
|
+
}
|
|
12306
|
+
function buildMaxTokensParam(model, maxTokens) {
|
|
12307
|
+
if (needsMaxCompletionTokens(model)) {
|
|
12308
|
+
return { max_completion_tokens: maxTokens };
|
|
12309
|
+
}
|
|
12310
|
+
return { max_tokens: maxTokens };
|
|
12311
|
+
}
|
|
12303
12312
|
var OpenAIProvider = class {
|
|
12304
12313
|
id;
|
|
12305
12314
|
name;
|
|
@@ -12376,9 +12385,10 @@ var OpenAIProvider = class {
|
|
|
12376
12385
|
return withRetry(async () => {
|
|
12377
12386
|
try {
|
|
12378
12387
|
const supportsTemp = this.supportsTemperature(model);
|
|
12388
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
|
|
12379
12389
|
const response = await this.client.chat.completions.create({
|
|
12380
12390
|
model,
|
|
12381
|
-
|
|
12391
|
+
...buildMaxTokensParam(model, maxTokens),
|
|
12382
12392
|
messages: this.convertMessages(messages, options?.system),
|
|
12383
12393
|
stop: options?.stopSequences,
|
|
12384
12394
|
...supportsTemp && {
|
|
@@ -12414,9 +12424,10 @@ var OpenAIProvider = class {
|
|
|
12414
12424
|
try {
|
|
12415
12425
|
const supportsTemp = this.supportsTemperature(model);
|
|
12416
12426
|
const extraBody = this.getExtraBody(model);
|
|
12427
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
|
|
12417
12428
|
const requestParams = {
|
|
12418
12429
|
model,
|
|
12419
|
-
|
|
12430
|
+
...buildMaxTokensParam(model, maxTokens),
|
|
12420
12431
|
messages: this.convertMessages(messages, options?.system),
|
|
12421
12432
|
tools: this.convertTools(options.tools),
|
|
12422
12433
|
tool_choice: this.convertToolChoice(options.toolChoice)
|
|
@@ -12460,9 +12471,10 @@ var OpenAIProvider = class {
|
|
|
12460
12471
|
}
|
|
12461
12472
|
try {
|
|
12462
12473
|
const supportsTemp = this.supportsTemperature(model);
|
|
12474
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
|
|
12463
12475
|
const stream = await this.client.chat.completions.create({
|
|
12464
12476
|
model,
|
|
12465
|
-
|
|
12477
|
+
...buildMaxTokensParam(model, maxTokens),
|
|
12466
12478
|
messages: this.convertMessages(messages, options?.system),
|
|
12467
12479
|
stream: true,
|
|
12468
12480
|
...supportsTemp && { temperature: options?.temperature ?? this.config.temperature ?? 0 }
|
|
@@ -12496,9 +12508,10 @@ var OpenAIProvider = class {
|
|
|
12496
12508
|
try {
|
|
12497
12509
|
const supportsTemp = this.supportsTemperature(model);
|
|
12498
12510
|
const extraBody = this.getExtraBody(model);
|
|
12511
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens ?? 8192;
|
|
12499
12512
|
const requestParams = {
|
|
12500
12513
|
model,
|
|
12501
|
-
|
|
12514
|
+
...buildMaxTokensParam(model, maxTokens),
|
|
12502
12515
|
messages: this.convertMessages(messages, options?.system),
|
|
12503
12516
|
tools: this.convertTools(options.tools),
|
|
12504
12517
|
tool_choice: this.convertToolChoice(options.toolChoice),
|
|
@@ -12527,7 +12540,7 @@ var OpenAIProvider = class {
|
|
|
12527
12540
|
once: true
|
|
12528
12541
|
});
|
|
12529
12542
|
const providerName = this.name;
|
|
12530
|
-
const
|
|
12543
|
+
const parseArguments2 = (builder) => {
|
|
12531
12544
|
let input = {};
|
|
12532
12545
|
try {
|
|
12533
12546
|
input = builder.arguments ? JSON.parse(builder.arguments) : {};
|
|
@@ -12608,7 +12621,7 @@ var OpenAIProvider = class {
|
|
|
12608
12621
|
toolCall: {
|
|
12609
12622
|
id: builder.id,
|
|
12610
12623
|
name: builder.name,
|
|
12611
|
-
input:
|
|
12624
|
+
input: parseArguments2(builder)
|
|
12612
12625
|
}
|
|
12613
12626
|
};
|
|
12614
12627
|
}
|
|
@@ -12621,7 +12634,7 @@ var OpenAIProvider = class {
|
|
|
12621
12634
|
toolCall: {
|
|
12622
12635
|
id: builder.id,
|
|
12623
12636
|
name: builder.name,
|
|
12624
|
-
input:
|
|
12637
|
+
input: parseArguments2(builder)
|
|
12625
12638
|
}
|
|
12626
12639
|
};
|
|
12627
12640
|
}
|
|
@@ -12760,11 +12773,20 @@ var OpenAIProvider = class {
|
|
|
12760
12773
|
} catch {
|
|
12761
12774
|
try {
|
|
12762
12775
|
const model = this.config.model || DEFAULT_MODEL2;
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12776
|
+
if (needsResponsesApi(model)) {
|
|
12777
|
+
await this.client.responses.create({
|
|
12778
|
+
model,
|
|
12779
|
+
input: [{ role: "user", content: [{ type: "input_text", text: "Hi" }] }],
|
|
12780
|
+
max_output_tokens: 1,
|
|
12781
|
+
store: false
|
|
12782
|
+
});
|
|
12783
|
+
} else {
|
|
12784
|
+
await this.client.chat.completions.create({
|
|
12785
|
+
model,
|
|
12786
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
12787
|
+
...buildMaxTokensParam(model, 1)
|
|
12788
|
+
});
|
|
12789
|
+
}
|
|
12768
12790
|
return true;
|
|
12769
12791
|
} catch {
|
|
12770
12792
|
return false;
|
|
@@ -13623,6 +13645,7 @@ var CONTEXT_WINDOWS3 = {
|
|
|
13623
13645
|
"gpt-5.2": 2e5,
|
|
13624
13646
|
"gpt-5.1": 2e5
|
|
13625
13647
|
};
|
|
13648
|
+
var STREAM_TIMEOUT_MS = 12e4;
|
|
13626
13649
|
function parseJwtClaims(token) {
|
|
13627
13650
|
const parts = token.split(".");
|
|
13628
13651
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
@@ -13638,12 +13661,28 @@ function extractAccountId(accessToken) {
|
|
|
13638
13661
|
const auth = claims["https://api.openai.com/auth"];
|
|
13639
13662
|
return claims["chatgpt_account_id"] || auth?.["chatgpt_account_id"] || claims["organizations"]?.[0]?.id;
|
|
13640
13663
|
}
|
|
13664
|
+
function parseArguments(args) {
|
|
13665
|
+
try {
|
|
13666
|
+
return args ? JSON.parse(args) : {};
|
|
13667
|
+
} catch {
|
|
13668
|
+
try {
|
|
13669
|
+
if (args) {
|
|
13670
|
+
const repaired = jsonrepair(args);
|
|
13671
|
+
return JSON.parse(repaired);
|
|
13672
|
+
}
|
|
13673
|
+
} catch {
|
|
13674
|
+
console.error(`[Codex] Cannot parse tool arguments: ${args.slice(0, 200)}`);
|
|
13675
|
+
}
|
|
13676
|
+
return {};
|
|
13677
|
+
}
|
|
13678
|
+
}
|
|
13641
13679
|
var CodexProvider = class {
|
|
13642
13680
|
id = "codex";
|
|
13643
13681
|
name = "OpenAI Codex (ChatGPT Plus/Pro)";
|
|
13644
13682
|
config = {};
|
|
13645
13683
|
accessToken = null;
|
|
13646
13684
|
accountId;
|
|
13685
|
+
retryConfig = DEFAULT_RETRY_CONFIG;
|
|
13647
13686
|
/**
|
|
13648
13687
|
* Initialize the provider with OAuth tokens
|
|
13649
13688
|
*/
|
|
@@ -13728,166 +13767,466 @@ var CodexProvider = class {
|
|
|
13728
13767
|
/**
|
|
13729
13768
|
* Extract text content from a message
|
|
13730
13769
|
*/
|
|
13731
|
-
|
|
13732
|
-
if (typeof
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
if (Array.isArray(msg.content)) {
|
|
13736
|
-
return msg.content.map((part) => {
|
|
13737
|
-
if (part.type === "text") return part.text;
|
|
13738
|
-
if (part.type === "tool_result") return `Tool result: ${JSON.stringify(part.content)}`;
|
|
13739
|
-
return "";
|
|
13740
|
-
}).join("\n");
|
|
13770
|
+
contentToString(content) {
|
|
13771
|
+
if (typeof content === "string") return content;
|
|
13772
|
+
if (Array.isArray(content)) {
|
|
13773
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
13741
13774
|
}
|
|
13742
13775
|
return "";
|
|
13743
13776
|
}
|
|
13744
13777
|
/**
|
|
13745
|
-
* Convert messages to
|
|
13746
|
-
* Codex uses a different format than Chat Completions:
|
|
13747
|
-
* {
|
|
13748
|
-
* "input": [
|
|
13749
|
-
* { "type": "message", "role": "developer|user", "content": [{ "type": "input_text", "text": "..." }] },
|
|
13750
|
-
* { "type": "message", "role": "assistant", "content": [{ "type": "output_text", "text": "..." }] }
|
|
13751
|
-
* ]
|
|
13752
|
-
* }
|
|
13778
|
+
* Convert messages to Responses API input format.
|
|
13753
13779
|
*
|
|
13754
|
-
*
|
|
13780
|
+
* Handles:
|
|
13781
|
+
* - system messages → extracted as instructions
|
|
13782
|
+
* - user text messages → { role: "user", content: "..." }
|
|
13783
|
+
* - user tool_result messages → function_call_output items
|
|
13784
|
+
* - assistant text → { role: "assistant", content: "..." }
|
|
13785
|
+
* - assistant tool_use → function_call items
|
|
13755
13786
|
*/
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13787
|
+
convertToResponsesInput(messages, systemPrompt) {
|
|
13788
|
+
const input = [];
|
|
13789
|
+
let instructions = systemPrompt ?? null;
|
|
13790
|
+
for (const msg of messages) {
|
|
13791
|
+
if (msg.role === "system") {
|
|
13792
|
+
instructions = (instructions ? instructions + "\n\n" : "") + this.contentToString(msg.content);
|
|
13793
|
+
} else if (msg.role === "user") {
|
|
13794
|
+
if (Array.isArray(msg.content) && msg.content.some((b) => b.type === "tool_result")) {
|
|
13795
|
+
for (const block of msg.content) {
|
|
13796
|
+
if (block.type === "tool_result") {
|
|
13797
|
+
const tr = block;
|
|
13798
|
+
input.push({
|
|
13799
|
+
type: "function_call_output",
|
|
13800
|
+
call_id: tr.tool_use_id,
|
|
13801
|
+
output: tr.content
|
|
13802
|
+
});
|
|
13803
|
+
}
|
|
13804
|
+
}
|
|
13805
|
+
} else {
|
|
13806
|
+
input.push({
|
|
13807
|
+
role: "user",
|
|
13808
|
+
content: this.contentToString(msg.content)
|
|
13809
|
+
});
|
|
13810
|
+
}
|
|
13811
|
+
} else if (msg.role === "assistant") {
|
|
13812
|
+
if (typeof msg.content === "string") {
|
|
13813
|
+
input.push({ role: "assistant", content: msg.content });
|
|
13814
|
+
} else if (Array.isArray(msg.content)) {
|
|
13815
|
+
const textParts = [];
|
|
13816
|
+
for (const block of msg.content) {
|
|
13817
|
+
if (block.type === "text") {
|
|
13818
|
+
textParts.push(block.text);
|
|
13819
|
+
} else if (block.type === "tool_use") {
|
|
13820
|
+
if (textParts.length > 0) {
|
|
13821
|
+
input.push({ role: "assistant", content: textParts.join("") });
|
|
13822
|
+
textParts.length = 0;
|
|
13823
|
+
}
|
|
13824
|
+
const tu = block;
|
|
13825
|
+
input.push({
|
|
13826
|
+
type: "function_call",
|
|
13827
|
+
call_id: tu.id,
|
|
13828
|
+
name: tu.name,
|
|
13829
|
+
arguments: JSON.stringify(tu.input)
|
|
13830
|
+
});
|
|
13831
|
+
}
|
|
13832
|
+
}
|
|
13833
|
+
if (textParts.length > 0) {
|
|
13834
|
+
input.push({ role: "assistant", content: textParts.join("") });
|
|
13835
|
+
}
|
|
13836
|
+
}
|
|
13837
|
+
}
|
|
13838
|
+
}
|
|
13839
|
+
return { input, instructions };
|
|
13767
13840
|
}
|
|
13768
13841
|
/**
|
|
13769
|
-
*
|
|
13842
|
+
* Convert tool definitions to Responses API function tool format
|
|
13770
13843
|
*/
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13844
|
+
convertTools(tools) {
|
|
13845
|
+
return tools.map((tool) => ({
|
|
13846
|
+
type: "function",
|
|
13847
|
+
name: tool.name,
|
|
13848
|
+
description: tool.description ?? void 0,
|
|
13849
|
+
parameters: tool.input_schema ?? null,
|
|
13850
|
+
strict: false
|
|
13851
|
+
}));
|
|
13852
|
+
}
|
|
13853
|
+
/**
|
|
13854
|
+
* Build the request body for the Codex Responses API
|
|
13855
|
+
*/
|
|
13856
|
+
buildRequestBody(model, input, instructions, options) {
|
|
13776
13857
|
const body = {
|
|
13777
13858
|
model,
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13859
|
+
input,
|
|
13860
|
+
instructions: instructions ?? "You are a helpful coding assistant.",
|
|
13861
|
+
max_output_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
13862
|
+
temperature: options?.temperature ?? this.config.temperature ?? 0,
|
|
13781
13863
|
store: false,
|
|
13782
13864
|
stream: true
|
|
13783
13865
|
// Codex API requires streaming
|
|
13784
13866
|
};
|
|
13785
|
-
|
|
13867
|
+
if (options?.tools && options.tools.length > 0) {
|
|
13868
|
+
body.tools = this.convertTools(options.tools);
|
|
13869
|
+
}
|
|
13870
|
+
return body;
|
|
13871
|
+
}
|
|
13872
|
+
/**
|
|
13873
|
+
* Read SSE stream and call handler for each parsed event.
|
|
13874
|
+
* Returns when stream ends.
|
|
13875
|
+
*/
|
|
13876
|
+
async readSSEStream(response, onEvent) {
|
|
13786
13877
|
if (!response.body) {
|
|
13787
|
-
throw new ProviderError("No response body from Codex API", {
|
|
13788
|
-
provider: this.id
|
|
13789
|
-
});
|
|
13878
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
13790
13879
|
}
|
|
13791
13880
|
const reader = response.body.getReader();
|
|
13792
13881
|
const decoder = new TextDecoder();
|
|
13793
13882
|
let buffer = "";
|
|
13794
|
-
let
|
|
13795
|
-
|
|
13796
|
-
|
|
13797
|
-
|
|
13798
|
-
|
|
13883
|
+
let lastActivityTime = Date.now();
|
|
13884
|
+
const timeoutController = new AbortController();
|
|
13885
|
+
const timeoutInterval = setInterval(() => {
|
|
13886
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
13887
|
+
clearInterval(timeoutInterval);
|
|
13888
|
+
timeoutController.abort();
|
|
13889
|
+
}
|
|
13890
|
+
}, 5e3);
|
|
13799
13891
|
try {
|
|
13800
13892
|
while (true) {
|
|
13893
|
+
if (timeoutController.signal.aborted) break;
|
|
13801
13894
|
const { done, value } = await reader.read();
|
|
13802
13895
|
if (done) break;
|
|
13896
|
+
lastActivityTime = Date.now();
|
|
13803
13897
|
buffer += decoder.decode(value, { stream: true });
|
|
13804
13898
|
const lines = buffer.split("\n");
|
|
13805
13899
|
buffer = lines.pop() ?? "";
|
|
13806
13900
|
for (const line of lines) {
|
|
13807
|
-
if (line.startsWith("data: "))
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
responseId = parsed.id;
|
|
13814
|
-
}
|
|
13815
|
-
if (parsed.type === "response.output_text.delta" && parsed.delta) {
|
|
13816
|
-
content += parsed.delta;
|
|
13817
|
-
} else if (parsed.type === "response.completed" && parsed.response) {
|
|
13818
|
-
if (parsed.response.usage) {
|
|
13819
|
-
inputTokens = parsed.response.usage.input_tokens ?? 0;
|
|
13820
|
-
outputTokens = parsed.response.usage.output_tokens ?? 0;
|
|
13821
|
-
}
|
|
13822
|
-
status = parsed.response.status ?? "completed";
|
|
13823
|
-
} else if (parsed.type === "response.output_text.done" && parsed.text) {
|
|
13824
|
-
content = parsed.text;
|
|
13825
|
-
}
|
|
13826
|
-
} catch {
|
|
13827
|
-
}
|
|
13901
|
+
if (!line.startsWith("data: ")) continue;
|
|
13902
|
+
const data = line.slice(6).trim();
|
|
13903
|
+
if (!data || data === "[DONE]") continue;
|
|
13904
|
+
try {
|
|
13905
|
+
onEvent(JSON.parse(data));
|
|
13906
|
+
} catch {
|
|
13828
13907
|
}
|
|
13829
13908
|
}
|
|
13830
13909
|
}
|
|
13831
13910
|
} finally {
|
|
13911
|
+
clearInterval(timeoutInterval);
|
|
13832
13912
|
reader.releaseLock();
|
|
13833
13913
|
}
|
|
13834
|
-
if (
|
|
13835
|
-
throw new
|
|
13836
|
-
|
|
13837
|
-
|
|
13914
|
+
if (timeoutController.signal.aborted) {
|
|
13915
|
+
throw new Error(
|
|
13916
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
13917
|
+
);
|
|
13838
13918
|
}
|
|
13839
|
-
const stopReason = status === "completed" ? "end_turn" : status === "incomplete" ? "max_tokens" : "end_turn";
|
|
13840
|
-
return {
|
|
13841
|
-
id: responseId,
|
|
13842
|
-
content,
|
|
13843
|
-
stopReason,
|
|
13844
|
-
model,
|
|
13845
|
-
usage: {
|
|
13846
|
-
inputTokens,
|
|
13847
|
-
outputTokens
|
|
13848
|
-
}
|
|
13849
|
-
};
|
|
13850
13919
|
}
|
|
13851
13920
|
/**
|
|
13852
|
-
* Send a chat message
|
|
13853
|
-
|
|
13854
|
-
|
|
13921
|
+
* Send a chat message using Codex Responses API format
|
|
13922
|
+
*/
|
|
13923
|
+
async chat(messages, options) {
|
|
13924
|
+
return withRetry(async () => {
|
|
13925
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
13926
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
13927
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
13928
|
+
maxTokens: options?.maxTokens,
|
|
13929
|
+
temperature: options?.temperature
|
|
13930
|
+
});
|
|
13931
|
+
const response = await this.makeRequest(body);
|
|
13932
|
+
let content = "";
|
|
13933
|
+
let responseId = `codex-${Date.now()}`;
|
|
13934
|
+
let inputTokens = 0;
|
|
13935
|
+
let outputTokens = 0;
|
|
13936
|
+
let status = "completed";
|
|
13937
|
+
await this.readSSEStream(response, (event) => {
|
|
13938
|
+
if (event.id) responseId = event.id;
|
|
13939
|
+
if (event.type === "response.output_text.delta" && event.delta) {
|
|
13940
|
+
content += event.delta;
|
|
13941
|
+
} else if (event.type === "response.output_text.done" && event.text) {
|
|
13942
|
+
content = event.text;
|
|
13943
|
+
} else if (event.type === "response.completed" && event.response) {
|
|
13944
|
+
const resp = event.response;
|
|
13945
|
+
const usage = resp.usage;
|
|
13946
|
+
if (usage) {
|
|
13947
|
+
inputTokens = usage.input_tokens ?? 0;
|
|
13948
|
+
outputTokens = usage.output_tokens ?? 0;
|
|
13949
|
+
}
|
|
13950
|
+
status = resp.status ?? "completed";
|
|
13951
|
+
}
|
|
13952
|
+
});
|
|
13953
|
+
const stopReason = status === "completed" ? "end_turn" : status === "incomplete" ? "max_tokens" : "end_turn";
|
|
13954
|
+
return {
|
|
13955
|
+
id: responseId,
|
|
13956
|
+
content,
|
|
13957
|
+
stopReason,
|
|
13958
|
+
model,
|
|
13959
|
+
usage: { inputTokens, outputTokens }
|
|
13960
|
+
};
|
|
13961
|
+
}, this.retryConfig);
|
|
13962
|
+
}
|
|
13963
|
+
/**
|
|
13964
|
+
* Send a chat message with tool use via Responses API
|
|
13855
13965
|
*/
|
|
13856
13966
|
async chatWithTools(messages, options) {
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13967
|
+
return withRetry(async () => {
|
|
13968
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
13969
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
13970
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
13971
|
+
tools: options.tools,
|
|
13972
|
+
maxTokens: options?.maxTokens
|
|
13973
|
+
});
|
|
13974
|
+
const response = await this.makeRequest(body);
|
|
13975
|
+
let content = "";
|
|
13976
|
+
let responseId = `codex-${Date.now()}`;
|
|
13977
|
+
let inputTokens = 0;
|
|
13978
|
+
let outputTokens = 0;
|
|
13979
|
+
const toolCalls = [];
|
|
13980
|
+
const fnCallBuilders = /* @__PURE__ */ new Map();
|
|
13981
|
+
await this.readSSEStream(response, (event) => {
|
|
13982
|
+
if (event.id) responseId = event.id;
|
|
13983
|
+
switch (event.type) {
|
|
13984
|
+
case "response.output_text.delta":
|
|
13985
|
+
content += event.delta ?? "";
|
|
13986
|
+
break;
|
|
13987
|
+
case "response.output_text.done":
|
|
13988
|
+
content = event.text ?? content;
|
|
13989
|
+
break;
|
|
13990
|
+
case "response.output_item.added": {
|
|
13991
|
+
const item = event.item;
|
|
13992
|
+
if (item.type === "function_call") {
|
|
13993
|
+
const itemKey = item.id ?? item.call_id;
|
|
13994
|
+
fnCallBuilders.set(itemKey, {
|
|
13995
|
+
callId: item.call_id,
|
|
13996
|
+
name: item.name,
|
|
13997
|
+
arguments: ""
|
|
13998
|
+
});
|
|
13999
|
+
}
|
|
14000
|
+
break;
|
|
14001
|
+
}
|
|
14002
|
+
case "response.function_call_arguments.delta": {
|
|
14003
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
14004
|
+
if (builder) builder.arguments += event.delta ?? "";
|
|
14005
|
+
break;
|
|
14006
|
+
}
|
|
14007
|
+
case "response.function_call_arguments.done": {
|
|
14008
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
14009
|
+
if (builder) {
|
|
14010
|
+
toolCalls.push({
|
|
14011
|
+
id: builder.callId,
|
|
14012
|
+
name: builder.name,
|
|
14013
|
+
input: parseArguments(event.arguments)
|
|
14014
|
+
});
|
|
14015
|
+
fnCallBuilders.delete(event.item_id);
|
|
14016
|
+
}
|
|
14017
|
+
break;
|
|
14018
|
+
}
|
|
14019
|
+
case "response.completed": {
|
|
14020
|
+
const resp = event.response;
|
|
14021
|
+
const usage = resp.usage;
|
|
14022
|
+
if (usage) {
|
|
14023
|
+
inputTokens = usage.input_tokens ?? 0;
|
|
14024
|
+
outputTokens = usage.output_tokens ?? 0;
|
|
14025
|
+
}
|
|
14026
|
+
for (const [, builder] of fnCallBuilders) {
|
|
14027
|
+
toolCalls.push({
|
|
14028
|
+
id: builder.callId,
|
|
14029
|
+
name: builder.name,
|
|
14030
|
+
input: parseArguments(builder.arguments)
|
|
14031
|
+
});
|
|
14032
|
+
}
|
|
14033
|
+
fnCallBuilders.clear();
|
|
14034
|
+
break;
|
|
14035
|
+
}
|
|
14036
|
+
}
|
|
14037
|
+
});
|
|
14038
|
+
return {
|
|
14039
|
+
id: responseId,
|
|
14040
|
+
content,
|
|
14041
|
+
stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn",
|
|
14042
|
+
model,
|
|
14043
|
+
usage: { inputTokens, outputTokens },
|
|
14044
|
+
toolCalls
|
|
14045
|
+
};
|
|
14046
|
+
}, this.retryConfig);
|
|
13863
14047
|
}
|
|
13864
14048
|
/**
|
|
13865
|
-
* Stream a chat response
|
|
13866
|
-
* Note: True streaming with Codex Responses API is complex.
|
|
13867
|
-
* For now, we make a non-streaming call and simulate streaming by emitting chunks.
|
|
14049
|
+
* Stream a chat response (no tools)
|
|
13868
14050
|
*/
|
|
13869
14051
|
async *stream(messages, options) {
|
|
13870
|
-
const
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
14052
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
14053
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
14054
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
14055
|
+
maxTokens: options?.maxTokens
|
|
14056
|
+
});
|
|
14057
|
+
const response = await this.makeRequest(body);
|
|
14058
|
+
if (!response.body) {
|
|
14059
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
14060
|
+
}
|
|
14061
|
+
const reader = response.body.getReader();
|
|
14062
|
+
const decoder = new TextDecoder();
|
|
14063
|
+
let buffer = "";
|
|
14064
|
+
let lastActivityTime = Date.now();
|
|
14065
|
+
const timeoutController = new AbortController();
|
|
14066
|
+
const timeoutInterval = setInterval(() => {
|
|
14067
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
14068
|
+
clearInterval(timeoutInterval);
|
|
14069
|
+
timeoutController.abort();
|
|
14070
|
+
}
|
|
14071
|
+
}, 5e3);
|
|
14072
|
+
try {
|
|
14073
|
+
while (true) {
|
|
14074
|
+
if (timeoutController.signal.aborted) break;
|
|
14075
|
+
const { done, value } = await reader.read();
|
|
14076
|
+
if (done) break;
|
|
14077
|
+
lastActivityTime = Date.now();
|
|
14078
|
+
buffer += decoder.decode(value, { stream: true });
|
|
14079
|
+
const lines = buffer.split("\n");
|
|
14080
|
+
buffer = lines.pop() ?? "";
|
|
14081
|
+
for (const line of lines) {
|
|
14082
|
+
if (!line.startsWith("data: ")) continue;
|
|
14083
|
+
const data = line.slice(6).trim();
|
|
14084
|
+
if (!data || data === "[DONE]") continue;
|
|
14085
|
+
try {
|
|
14086
|
+
const event = JSON.parse(data);
|
|
14087
|
+
if (event.type === "response.output_text.delta" && event.delta) {
|
|
14088
|
+
yield { type: "text", text: event.delta };
|
|
14089
|
+
} else if (event.type === "response.completed") {
|
|
14090
|
+
yield { type: "done", stopReason: "end_turn" };
|
|
14091
|
+
}
|
|
14092
|
+
} catch {
|
|
14093
|
+
}
|
|
13879
14094
|
}
|
|
13880
14095
|
}
|
|
14096
|
+
} finally {
|
|
14097
|
+
clearInterval(timeoutInterval);
|
|
14098
|
+
reader.releaseLock();
|
|
14099
|
+
}
|
|
14100
|
+
if (timeoutController.signal.aborted) {
|
|
14101
|
+
throw new Error(
|
|
14102
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
14103
|
+
);
|
|
13881
14104
|
}
|
|
13882
|
-
yield { type: "done", stopReason: response.stopReason };
|
|
13883
14105
|
}
|
|
13884
14106
|
/**
|
|
13885
|
-
* Stream a chat response with tool use
|
|
13886
|
-
*
|
|
13887
|
-
*
|
|
14107
|
+
* Stream a chat response with tool use via Responses API.
|
|
14108
|
+
*
|
|
14109
|
+
* IMPORTANT: fnCallBuilders is keyed by output item ID (item.id), NOT by
|
|
14110
|
+
* call_id. The streaming events (function_call_arguments.delta/done) use
|
|
14111
|
+
* item_id which references the output item's id field, not call_id.
|
|
13888
14112
|
*/
|
|
13889
14113
|
async *streamWithTools(messages, options) {
|
|
13890
|
-
|
|
14114
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
14115
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
14116
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
14117
|
+
tools: options.tools,
|
|
14118
|
+
maxTokens: options?.maxTokens
|
|
14119
|
+
});
|
|
14120
|
+
const response = await this.makeRequest(body);
|
|
14121
|
+
if (!response.body) {
|
|
14122
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
14123
|
+
}
|
|
14124
|
+
const reader = response.body.getReader();
|
|
14125
|
+
const decoder = new TextDecoder();
|
|
14126
|
+
let buffer = "";
|
|
14127
|
+
const fnCallBuilders = /* @__PURE__ */ new Map();
|
|
14128
|
+
let lastActivityTime = Date.now();
|
|
14129
|
+
const timeoutController = new AbortController();
|
|
14130
|
+
const timeoutInterval = setInterval(() => {
|
|
14131
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
14132
|
+
clearInterval(timeoutInterval);
|
|
14133
|
+
timeoutController.abort();
|
|
14134
|
+
}
|
|
14135
|
+
}, 5e3);
|
|
14136
|
+
try {
|
|
14137
|
+
while (true) {
|
|
14138
|
+
if (timeoutController.signal.aborted) break;
|
|
14139
|
+
const { done, value } = await reader.read();
|
|
14140
|
+
if (done) break;
|
|
14141
|
+
lastActivityTime = Date.now();
|
|
14142
|
+
buffer += decoder.decode(value, { stream: true });
|
|
14143
|
+
const lines = buffer.split("\n");
|
|
14144
|
+
buffer = lines.pop() ?? "";
|
|
14145
|
+
for (const line of lines) {
|
|
14146
|
+
if (!line.startsWith("data: ")) continue;
|
|
14147
|
+
const data = line.slice(6).trim();
|
|
14148
|
+
if (!data || data === "[DONE]") continue;
|
|
14149
|
+
let event;
|
|
14150
|
+
try {
|
|
14151
|
+
event = JSON.parse(data);
|
|
14152
|
+
} catch {
|
|
14153
|
+
continue;
|
|
14154
|
+
}
|
|
14155
|
+
switch (event.type) {
|
|
14156
|
+
case "response.output_text.delta":
|
|
14157
|
+
yield { type: "text", text: event.delta ?? "" };
|
|
14158
|
+
break;
|
|
14159
|
+
case "response.output_item.added": {
|
|
14160
|
+
const item = event.item;
|
|
14161
|
+
if (item.type === "function_call") {
|
|
14162
|
+
const itemKey = item.id ?? item.call_id;
|
|
14163
|
+
fnCallBuilders.set(itemKey, {
|
|
14164
|
+
callId: item.call_id,
|
|
14165
|
+
name: item.name,
|
|
14166
|
+
arguments: ""
|
|
14167
|
+
});
|
|
14168
|
+
yield {
|
|
14169
|
+
type: "tool_use_start",
|
|
14170
|
+
toolCall: { id: item.call_id, name: item.name }
|
|
14171
|
+
};
|
|
14172
|
+
}
|
|
14173
|
+
break;
|
|
14174
|
+
}
|
|
14175
|
+
case "response.function_call_arguments.delta": {
|
|
14176
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
14177
|
+
if (builder) {
|
|
14178
|
+
builder.arguments += event.delta ?? "";
|
|
14179
|
+
}
|
|
14180
|
+
break;
|
|
14181
|
+
}
|
|
14182
|
+
case "response.function_call_arguments.done": {
|
|
14183
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
14184
|
+
if (builder) {
|
|
14185
|
+
yield {
|
|
14186
|
+
type: "tool_use_end",
|
|
14187
|
+
toolCall: {
|
|
14188
|
+
id: builder.callId,
|
|
14189
|
+
name: builder.name,
|
|
14190
|
+
input: parseArguments(event.arguments ?? builder.arguments)
|
|
14191
|
+
}
|
|
14192
|
+
};
|
|
14193
|
+
fnCallBuilders.delete(event.item_id);
|
|
14194
|
+
}
|
|
14195
|
+
break;
|
|
14196
|
+
}
|
|
14197
|
+
case "response.completed": {
|
|
14198
|
+
for (const [, builder] of fnCallBuilders) {
|
|
14199
|
+
yield {
|
|
14200
|
+
type: "tool_use_end",
|
|
14201
|
+
toolCall: {
|
|
14202
|
+
id: builder.callId,
|
|
14203
|
+
name: builder.name,
|
|
14204
|
+
input: parseArguments(builder.arguments)
|
|
14205
|
+
}
|
|
14206
|
+
};
|
|
14207
|
+
}
|
|
14208
|
+
fnCallBuilders.clear();
|
|
14209
|
+
const resp = event.response;
|
|
14210
|
+
const output = resp?.output ?? [];
|
|
14211
|
+
const hasToolCalls = output.some((i) => i.type === "function_call");
|
|
14212
|
+
yield {
|
|
14213
|
+
type: "done",
|
|
14214
|
+
stopReason: hasToolCalls ? "tool_use" : "end_turn"
|
|
14215
|
+
};
|
|
14216
|
+
break;
|
|
14217
|
+
}
|
|
14218
|
+
}
|
|
14219
|
+
}
|
|
14220
|
+
}
|
|
14221
|
+
} finally {
|
|
14222
|
+
clearInterval(timeoutInterval);
|
|
14223
|
+
reader.releaseLock();
|
|
14224
|
+
}
|
|
14225
|
+
if (timeoutController.signal.aborted) {
|
|
14226
|
+
throw new Error(
|
|
14227
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
14228
|
+
);
|
|
14229
|
+
}
|
|
13891
14230
|
}
|
|
13892
14231
|
};
|
|
13893
14232
|
var CONTEXT_WINDOWS4 = {
|