@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/index.js
CHANGED
|
@@ -12527,7 +12527,7 @@ var OpenAIProvider = class {
|
|
|
12527
12527
|
once: true
|
|
12528
12528
|
});
|
|
12529
12529
|
const providerName = this.name;
|
|
12530
|
-
const
|
|
12530
|
+
const parseArguments2 = (builder) => {
|
|
12531
12531
|
let input = {};
|
|
12532
12532
|
try {
|
|
12533
12533
|
input = builder.arguments ? JSON.parse(builder.arguments) : {};
|
|
@@ -12608,7 +12608,7 @@ var OpenAIProvider = class {
|
|
|
12608
12608
|
toolCall: {
|
|
12609
12609
|
id: builder.id,
|
|
12610
12610
|
name: builder.name,
|
|
12611
|
-
input:
|
|
12611
|
+
input: parseArguments2(builder)
|
|
12612
12612
|
}
|
|
12613
12613
|
};
|
|
12614
12614
|
}
|
|
@@ -12621,7 +12621,7 @@ var OpenAIProvider = class {
|
|
|
12621
12621
|
toolCall: {
|
|
12622
12622
|
id: builder.id,
|
|
12623
12623
|
name: builder.name,
|
|
12624
|
-
input:
|
|
12624
|
+
input: parseArguments2(builder)
|
|
12625
12625
|
}
|
|
12626
12626
|
};
|
|
12627
12627
|
}
|
|
@@ -13623,6 +13623,7 @@ var CONTEXT_WINDOWS3 = {
|
|
|
13623
13623
|
"gpt-5.2": 2e5,
|
|
13624
13624
|
"gpt-5.1": 2e5
|
|
13625
13625
|
};
|
|
13626
|
+
var STREAM_TIMEOUT_MS = 12e4;
|
|
13626
13627
|
function parseJwtClaims(token) {
|
|
13627
13628
|
const parts = token.split(".");
|
|
13628
13629
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
@@ -13638,12 +13639,28 @@ function extractAccountId(accessToken) {
|
|
|
13638
13639
|
const auth = claims["https://api.openai.com/auth"];
|
|
13639
13640
|
return claims["chatgpt_account_id"] || auth?.["chatgpt_account_id"] || claims["organizations"]?.[0]?.id;
|
|
13640
13641
|
}
|
|
13642
|
+
function parseArguments(args) {
|
|
13643
|
+
try {
|
|
13644
|
+
return args ? JSON.parse(args) : {};
|
|
13645
|
+
} catch {
|
|
13646
|
+
try {
|
|
13647
|
+
if (args) {
|
|
13648
|
+
const repaired = jsonrepair(args);
|
|
13649
|
+
return JSON.parse(repaired);
|
|
13650
|
+
}
|
|
13651
|
+
} catch {
|
|
13652
|
+
console.error(`[Codex] Cannot parse tool arguments: ${args.slice(0, 200)}`);
|
|
13653
|
+
}
|
|
13654
|
+
return {};
|
|
13655
|
+
}
|
|
13656
|
+
}
|
|
13641
13657
|
var CodexProvider = class {
|
|
13642
13658
|
id = "codex";
|
|
13643
13659
|
name = "OpenAI Codex (ChatGPT Plus/Pro)";
|
|
13644
13660
|
config = {};
|
|
13645
13661
|
accessToken = null;
|
|
13646
13662
|
accountId;
|
|
13663
|
+
retryConfig = DEFAULT_RETRY_CONFIG;
|
|
13647
13664
|
/**
|
|
13648
13665
|
* Initialize the provider with OAuth tokens
|
|
13649
13666
|
*/
|
|
@@ -13728,166 +13745,466 @@ var CodexProvider = class {
|
|
|
13728
13745
|
/**
|
|
13729
13746
|
* Extract text content from a message
|
|
13730
13747
|
*/
|
|
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");
|
|
13748
|
+
contentToString(content) {
|
|
13749
|
+
if (typeof content === "string") return content;
|
|
13750
|
+
if (Array.isArray(content)) {
|
|
13751
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
13741
13752
|
}
|
|
13742
13753
|
return "";
|
|
13743
13754
|
}
|
|
13744
13755
|
/**
|
|
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
|
-
* }
|
|
13756
|
+
* Convert messages to Responses API input format.
|
|
13753
13757
|
*
|
|
13754
|
-
*
|
|
13758
|
+
* Handles:
|
|
13759
|
+
* - system messages → extracted as instructions
|
|
13760
|
+
* - user text messages → { role: "user", content: "..." }
|
|
13761
|
+
* - user tool_result messages → function_call_output items
|
|
13762
|
+
* - assistant text → { role: "assistant", content: "..." }
|
|
13763
|
+
* - assistant tool_use → function_call items
|
|
13755
13764
|
*/
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13765
|
+
convertToResponsesInput(messages, systemPrompt) {
|
|
13766
|
+
const input = [];
|
|
13767
|
+
let instructions = systemPrompt ?? null;
|
|
13768
|
+
for (const msg of messages) {
|
|
13769
|
+
if (msg.role === "system") {
|
|
13770
|
+
instructions = (instructions ? instructions + "\n\n" : "") + this.contentToString(msg.content);
|
|
13771
|
+
} else if (msg.role === "user") {
|
|
13772
|
+
if (Array.isArray(msg.content) && msg.content.some((b) => b.type === "tool_result")) {
|
|
13773
|
+
for (const block of msg.content) {
|
|
13774
|
+
if (block.type === "tool_result") {
|
|
13775
|
+
const tr = block;
|
|
13776
|
+
input.push({
|
|
13777
|
+
type: "function_call_output",
|
|
13778
|
+
call_id: tr.tool_use_id,
|
|
13779
|
+
output: tr.content
|
|
13780
|
+
});
|
|
13781
|
+
}
|
|
13782
|
+
}
|
|
13783
|
+
} else {
|
|
13784
|
+
input.push({
|
|
13785
|
+
role: "user",
|
|
13786
|
+
content: this.contentToString(msg.content)
|
|
13787
|
+
});
|
|
13788
|
+
}
|
|
13789
|
+
} else if (msg.role === "assistant") {
|
|
13790
|
+
if (typeof msg.content === "string") {
|
|
13791
|
+
input.push({ role: "assistant", content: msg.content });
|
|
13792
|
+
} else if (Array.isArray(msg.content)) {
|
|
13793
|
+
const textParts = [];
|
|
13794
|
+
for (const block of msg.content) {
|
|
13795
|
+
if (block.type === "text") {
|
|
13796
|
+
textParts.push(block.text);
|
|
13797
|
+
} else if (block.type === "tool_use") {
|
|
13798
|
+
if (textParts.length > 0) {
|
|
13799
|
+
input.push({ role: "assistant", content: textParts.join("") });
|
|
13800
|
+
textParts.length = 0;
|
|
13801
|
+
}
|
|
13802
|
+
const tu = block;
|
|
13803
|
+
input.push({
|
|
13804
|
+
type: "function_call",
|
|
13805
|
+
call_id: tu.id,
|
|
13806
|
+
name: tu.name,
|
|
13807
|
+
arguments: JSON.stringify(tu.input)
|
|
13808
|
+
});
|
|
13809
|
+
}
|
|
13810
|
+
}
|
|
13811
|
+
if (textParts.length > 0) {
|
|
13812
|
+
input.push({ role: "assistant", content: textParts.join("") });
|
|
13813
|
+
}
|
|
13814
|
+
}
|
|
13815
|
+
}
|
|
13816
|
+
}
|
|
13817
|
+
return { input, instructions };
|
|
13767
13818
|
}
|
|
13768
13819
|
/**
|
|
13769
|
-
*
|
|
13820
|
+
* Convert tool definitions to Responses API function tool format
|
|
13770
13821
|
*/
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13822
|
+
convertTools(tools) {
|
|
13823
|
+
return tools.map((tool) => ({
|
|
13824
|
+
type: "function",
|
|
13825
|
+
name: tool.name,
|
|
13826
|
+
description: tool.description ?? void 0,
|
|
13827
|
+
parameters: tool.input_schema ?? null,
|
|
13828
|
+
strict: false
|
|
13829
|
+
}));
|
|
13830
|
+
}
|
|
13831
|
+
/**
|
|
13832
|
+
* Build the request body for the Codex Responses API
|
|
13833
|
+
*/
|
|
13834
|
+
buildRequestBody(model, input, instructions, options) {
|
|
13776
13835
|
const body = {
|
|
13777
13836
|
model,
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13837
|
+
input,
|
|
13838
|
+
instructions: instructions ?? "You are a helpful coding assistant.",
|
|
13839
|
+
max_output_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
13840
|
+
temperature: options?.temperature ?? this.config.temperature ?? 0,
|
|
13781
13841
|
store: false,
|
|
13782
13842
|
stream: true
|
|
13783
13843
|
// Codex API requires streaming
|
|
13784
13844
|
};
|
|
13785
|
-
|
|
13845
|
+
if (options?.tools && options.tools.length > 0) {
|
|
13846
|
+
body.tools = this.convertTools(options.tools);
|
|
13847
|
+
}
|
|
13848
|
+
return body;
|
|
13849
|
+
}
|
|
13850
|
+
/**
|
|
13851
|
+
* Read SSE stream and call handler for each parsed event.
|
|
13852
|
+
* Returns when stream ends.
|
|
13853
|
+
*/
|
|
13854
|
+
async readSSEStream(response, onEvent) {
|
|
13786
13855
|
if (!response.body) {
|
|
13787
|
-
throw new ProviderError("No response body from Codex API", {
|
|
13788
|
-
provider: this.id
|
|
13789
|
-
});
|
|
13856
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
13790
13857
|
}
|
|
13791
13858
|
const reader = response.body.getReader();
|
|
13792
13859
|
const decoder = new TextDecoder();
|
|
13793
13860
|
let buffer = "";
|
|
13794
|
-
let
|
|
13795
|
-
|
|
13796
|
-
|
|
13797
|
-
|
|
13798
|
-
|
|
13861
|
+
let lastActivityTime = Date.now();
|
|
13862
|
+
const timeoutController = new AbortController();
|
|
13863
|
+
const timeoutInterval = setInterval(() => {
|
|
13864
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
13865
|
+
clearInterval(timeoutInterval);
|
|
13866
|
+
timeoutController.abort();
|
|
13867
|
+
}
|
|
13868
|
+
}, 5e3);
|
|
13799
13869
|
try {
|
|
13800
13870
|
while (true) {
|
|
13871
|
+
if (timeoutController.signal.aborted) break;
|
|
13801
13872
|
const { done, value } = await reader.read();
|
|
13802
13873
|
if (done) break;
|
|
13874
|
+
lastActivityTime = Date.now();
|
|
13803
13875
|
buffer += decoder.decode(value, { stream: true });
|
|
13804
13876
|
const lines = buffer.split("\n");
|
|
13805
13877
|
buffer = lines.pop() ?? "";
|
|
13806
13878
|
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
|
-
}
|
|
13879
|
+
if (!line.startsWith("data: ")) continue;
|
|
13880
|
+
const data = line.slice(6).trim();
|
|
13881
|
+
if (!data || data === "[DONE]") continue;
|
|
13882
|
+
try {
|
|
13883
|
+
onEvent(JSON.parse(data));
|
|
13884
|
+
} catch {
|
|
13828
13885
|
}
|
|
13829
13886
|
}
|
|
13830
13887
|
}
|
|
13831
13888
|
} finally {
|
|
13889
|
+
clearInterval(timeoutInterval);
|
|
13832
13890
|
reader.releaseLock();
|
|
13833
13891
|
}
|
|
13834
|
-
if (
|
|
13835
|
-
throw new
|
|
13836
|
-
|
|
13837
|
-
|
|
13892
|
+
if (timeoutController.signal.aborted) {
|
|
13893
|
+
throw new Error(
|
|
13894
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
13895
|
+
);
|
|
13838
13896
|
}
|
|
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
13897
|
}
|
|
13851
13898
|
/**
|
|
13852
|
-
* Send a chat message
|
|
13853
|
-
|
|
13854
|
-
|
|
13899
|
+
* Send a chat message using Codex Responses API format
|
|
13900
|
+
*/
|
|
13901
|
+
async chat(messages, options) {
|
|
13902
|
+
return withRetry(async () => {
|
|
13903
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
13904
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
13905
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
13906
|
+
maxTokens: options?.maxTokens,
|
|
13907
|
+
temperature: options?.temperature
|
|
13908
|
+
});
|
|
13909
|
+
const response = await this.makeRequest(body);
|
|
13910
|
+
let content = "";
|
|
13911
|
+
let responseId = `codex-${Date.now()}`;
|
|
13912
|
+
let inputTokens = 0;
|
|
13913
|
+
let outputTokens = 0;
|
|
13914
|
+
let status = "completed";
|
|
13915
|
+
await this.readSSEStream(response, (event) => {
|
|
13916
|
+
if (event.id) responseId = event.id;
|
|
13917
|
+
if (event.type === "response.output_text.delta" && event.delta) {
|
|
13918
|
+
content += event.delta;
|
|
13919
|
+
} else if (event.type === "response.output_text.done" && event.text) {
|
|
13920
|
+
content = event.text;
|
|
13921
|
+
} else if (event.type === "response.completed" && event.response) {
|
|
13922
|
+
const resp = event.response;
|
|
13923
|
+
const usage = resp.usage;
|
|
13924
|
+
if (usage) {
|
|
13925
|
+
inputTokens = usage.input_tokens ?? 0;
|
|
13926
|
+
outputTokens = usage.output_tokens ?? 0;
|
|
13927
|
+
}
|
|
13928
|
+
status = resp.status ?? "completed";
|
|
13929
|
+
}
|
|
13930
|
+
});
|
|
13931
|
+
const stopReason = status === "completed" ? "end_turn" : status === "incomplete" ? "max_tokens" : "end_turn";
|
|
13932
|
+
return {
|
|
13933
|
+
id: responseId,
|
|
13934
|
+
content,
|
|
13935
|
+
stopReason,
|
|
13936
|
+
model,
|
|
13937
|
+
usage: { inputTokens, outputTokens }
|
|
13938
|
+
};
|
|
13939
|
+
}, this.retryConfig);
|
|
13940
|
+
}
|
|
13941
|
+
/**
|
|
13942
|
+
* Send a chat message with tool use via Responses API
|
|
13855
13943
|
*/
|
|
13856
13944
|
async chatWithTools(messages, options) {
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13945
|
+
return withRetry(async () => {
|
|
13946
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
13947
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
13948
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
13949
|
+
tools: options.tools,
|
|
13950
|
+
maxTokens: options?.maxTokens
|
|
13951
|
+
});
|
|
13952
|
+
const response = await this.makeRequest(body);
|
|
13953
|
+
let content = "";
|
|
13954
|
+
let responseId = `codex-${Date.now()}`;
|
|
13955
|
+
let inputTokens = 0;
|
|
13956
|
+
let outputTokens = 0;
|
|
13957
|
+
const toolCalls = [];
|
|
13958
|
+
const fnCallBuilders = /* @__PURE__ */ new Map();
|
|
13959
|
+
await this.readSSEStream(response, (event) => {
|
|
13960
|
+
if (event.id) responseId = event.id;
|
|
13961
|
+
switch (event.type) {
|
|
13962
|
+
case "response.output_text.delta":
|
|
13963
|
+
content += event.delta ?? "";
|
|
13964
|
+
break;
|
|
13965
|
+
case "response.output_text.done":
|
|
13966
|
+
content = event.text ?? content;
|
|
13967
|
+
break;
|
|
13968
|
+
case "response.output_item.added": {
|
|
13969
|
+
const item = event.item;
|
|
13970
|
+
if (item.type === "function_call") {
|
|
13971
|
+
const itemKey = item.id ?? item.call_id;
|
|
13972
|
+
fnCallBuilders.set(itemKey, {
|
|
13973
|
+
callId: item.call_id,
|
|
13974
|
+
name: item.name,
|
|
13975
|
+
arguments: ""
|
|
13976
|
+
});
|
|
13977
|
+
}
|
|
13978
|
+
break;
|
|
13979
|
+
}
|
|
13980
|
+
case "response.function_call_arguments.delta": {
|
|
13981
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
13982
|
+
if (builder) builder.arguments += event.delta ?? "";
|
|
13983
|
+
break;
|
|
13984
|
+
}
|
|
13985
|
+
case "response.function_call_arguments.done": {
|
|
13986
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
13987
|
+
if (builder) {
|
|
13988
|
+
toolCalls.push({
|
|
13989
|
+
id: builder.callId,
|
|
13990
|
+
name: builder.name,
|
|
13991
|
+
input: parseArguments(event.arguments)
|
|
13992
|
+
});
|
|
13993
|
+
fnCallBuilders.delete(event.item_id);
|
|
13994
|
+
}
|
|
13995
|
+
break;
|
|
13996
|
+
}
|
|
13997
|
+
case "response.completed": {
|
|
13998
|
+
const resp = event.response;
|
|
13999
|
+
const usage = resp.usage;
|
|
14000
|
+
if (usage) {
|
|
14001
|
+
inputTokens = usage.input_tokens ?? 0;
|
|
14002
|
+
outputTokens = usage.output_tokens ?? 0;
|
|
14003
|
+
}
|
|
14004
|
+
for (const [, builder] of fnCallBuilders) {
|
|
14005
|
+
toolCalls.push({
|
|
14006
|
+
id: builder.callId,
|
|
14007
|
+
name: builder.name,
|
|
14008
|
+
input: parseArguments(builder.arguments)
|
|
14009
|
+
});
|
|
14010
|
+
}
|
|
14011
|
+
fnCallBuilders.clear();
|
|
14012
|
+
break;
|
|
14013
|
+
}
|
|
14014
|
+
}
|
|
14015
|
+
});
|
|
14016
|
+
return {
|
|
14017
|
+
id: responseId,
|
|
14018
|
+
content,
|
|
14019
|
+
stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn",
|
|
14020
|
+
model,
|
|
14021
|
+
usage: { inputTokens, outputTokens },
|
|
14022
|
+
toolCalls
|
|
14023
|
+
};
|
|
14024
|
+
}, this.retryConfig);
|
|
13863
14025
|
}
|
|
13864
14026
|
/**
|
|
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.
|
|
14027
|
+
* Stream a chat response (no tools)
|
|
13868
14028
|
*/
|
|
13869
14029
|
async *stream(messages, options) {
|
|
13870
|
-
const
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
14030
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
14031
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
14032
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
14033
|
+
maxTokens: options?.maxTokens
|
|
14034
|
+
});
|
|
14035
|
+
const response = await this.makeRequest(body);
|
|
14036
|
+
if (!response.body) {
|
|
14037
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
14038
|
+
}
|
|
14039
|
+
const reader = response.body.getReader();
|
|
14040
|
+
const decoder = new TextDecoder();
|
|
14041
|
+
let buffer = "";
|
|
14042
|
+
let lastActivityTime = Date.now();
|
|
14043
|
+
const timeoutController = new AbortController();
|
|
14044
|
+
const timeoutInterval = setInterval(() => {
|
|
14045
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
14046
|
+
clearInterval(timeoutInterval);
|
|
14047
|
+
timeoutController.abort();
|
|
14048
|
+
}
|
|
14049
|
+
}, 5e3);
|
|
14050
|
+
try {
|
|
14051
|
+
while (true) {
|
|
14052
|
+
if (timeoutController.signal.aborted) break;
|
|
14053
|
+
const { done, value } = await reader.read();
|
|
14054
|
+
if (done) break;
|
|
14055
|
+
lastActivityTime = Date.now();
|
|
14056
|
+
buffer += decoder.decode(value, { stream: true });
|
|
14057
|
+
const lines = buffer.split("\n");
|
|
14058
|
+
buffer = lines.pop() ?? "";
|
|
14059
|
+
for (const line of lines) {
|
|
14060
|
+
if (!line.startsWith("data: ")) continue;
|
|
14061
|
+
const data = line.slice(6).trim();
|
|
14062
|
+
if (!data || data === "[DONE]") continue;
|
|
14063
|
+
try {
|
|
14064
|
+
const event = JSON.parse(data);
|
|
14065
|
+
if (event.type === "response.output_text.delta" && event.delta) {
|
|
14066
|
+
yield { type: "text", text: event.delta };
|
|
14067
|
+
} else if (event.type === "response.completed") {
|
|
14068
|
+
yield { type: "done", stopReason: "end_turn" };
|
|
14069
|
+
}
|
|
14070
|
+
} catch {
|
|
14071
|
+
}
|
|
13879
14072
|
}
|
|
13880
14073
|
}
|
|
14074
|
+
} finally {
|
|
14075
|
+
clearInterval(timeoutInterval);
|
|
14076
|
+
reader.releaseLock();
|
|
14077
|
+
}
|
|
14078
|
+
if (timeoutController.signal.aborted) {
|
|
14079
|
+
throw new Error(
|
|
14080
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
14081
|
+
);
|
|
13881
14082
|
}
|
|
13882
|
-
yield { type: "done", stopReason: response.stopReason };
|
|
13883
14083
|
}
|
|
13884
14084
|
/**
|
|
13885
|
-
* Stream a chat response with tool use
|
|
13886
|
-
*
|
|
13887
|
-
*
|
|
14085
|
+
* Stream a chat response with tool use via Responses API.
|
|
14086
|
+
*
|
|
14087
|
+
* IMPORTANT: fnCallBuilders is keyed by output item ID (item.id), NOT by
|
|
14088
|
+
* call_id. The streaming events (function_call_arguments.delta/done) use
|
|
14089
|
+
* item_id which references the output item's id field, not call_id.
|
|
13888
14090
|
*/
|
|
13889
14091
|
async *streamWithTools(messages, options) {
|
|
13890
|
-
|
|
14092
|
+
const model = options?.model ?? this.config.model ?? DEFAULT_MODEL3;
|
|
14093
|
+
const { input, instructions } = this.convertToResponsesInput(messages, options?.system);
|
|
14094
|
+
const body = this.buildRequestBody(model, input, instructions, {
|
|
14095
|
+
tools: options.tools,
|
|
14096
|
+
maxTokens: options?.maxTokens
|
|
14097
|
+
});
|
|
14098
|
+
const response = await this.makeRequest(body);
|
|
14099
|
+
if (!response.body) {
|
|
14100
|
+
throw new ProviderError("No response body from Codex API", { provider: this.id });
|
|
14101
|
+
}
|
|
14102
|
+
const reader = response.body.getReader();
|
|
14103
|
+
const decoder = new TextDecoder();
|
|
14104
|
+
let buffer = "";
|
|
14105
|
+
const fnCallBuilders = /* @__PURE__ */ new Map();
|
|
14106
|
+
let lastActivityTime = Date.now();
|
|
14107
|
+
const timeoutController = new AbortController();
|
|
14108
|
+
const timeoutInterval = setInterval(() => {
|
|
14109
|
+
if (Date.now() - lastActivityTime > STREAM_TIMEOUT_MS) {
|
|
14110
|
+
clearInterval(timeoutInterval);
|
|
14111
|
+
timeoutController.abort();
|
|
14112
|
+
}
|
|
14113
|
+
}, 5e3);
|
|
14114
|
+
try {
|
|
14115
|
+
while (true) {
|
|
14116
|
+
if (timeoutController.signal.aborted) break;
|
|
14117
|
+
const { done, value } = await reader.read();
|
|
14118
|
+
if (done) break;
|
|
14119
|
+
lastActivityTime = Date.now();
|
|
14120
|
+
buffer += decoder.decode(value, { stream: true });
|
|
14121
|
+
const lines = buffer.split("\n");
|
|
14122
|
+
buffer = lines.pop() ?? "";
|
|
14123
|
+
for (const line of lines) {
|
|
14124
|
+
if (!line.startsWith("data: ")) continue;
|
|
14125
|
+
const data = line.slice(6).trim();
|
|
14126
|
+
if (!data || data === "[DONE]") continue;
|
|
14127
|
+
let event;
|
|
14128
|
+
try {
|
|
14129
|
+
event = JSON.parse(data);
|
|
14130
|
+
} catch {
|
|
14131
|
+
continue;
|
|
14132
|
+
}
|
|
14133
|
+
switch (event.type) {
|
|
14134
|
+
case "response.output_text.delta":
|
|
14135
|
+
yield { type: "text", text: event.delta ?? "" };
|
|
14136
|
+
break;
|
|
14137
|
+
case "response.output_item.added": {
|
|
14138
|
+
const item = event.item;
|
|
14139
|
+
if (item.type === "function_call") {
|
|
14140
|
+
const itemKey = item.id ?? item.call_id;
|
|
14141
|
+
fnCallBuilders.set(itemKey, {
|
|
14142
|
+
callId: item.call_id,
|
|
14143
|
+
name: item.name,
|
|
14144
|
+
arguments: ""
|
|
14145
|
+
});
|
|
14146
|
+
yield {
|
|
14147
|
+
type: "tool_use_start",
|
|
14148
|
+
toolCall: { id: item.call_id, name: item.name }
|
|
14149
|
+
};
|
|
14150
|
+
}
|
|
14151
|
+
break;
|
|
14152
|
+
}
|
|
14153
|
+
case "response.function_call_arguments.delta": {
|
|
14154
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
14155
|
+
if (builder) {
|
|
14156
|
+
builder.arguments += event.delta ?? "";
|
|
14157
|
+
}
|
|
14158
|
+
break;
|
|
14159
|
+
}
|
|
14160
|
+
case "response.function_call_arguments.done": {
|
|
14161
|
+
const builder = fnCallBuilders.get(event.item_id);
|
|
14162
|
+
if (builder) {
|
|
14163
|
+
yield {
|
|
14164
|
+
type: "tool_use_end",
|
|
14165
|
+
toolCall: {
|
|
14166
|
+
id: builder.callId,
|
|
14167
|
+
name: builder.name,
|
|
14168
|
+
input: parseArguments(event.arguments ?? builder.arguments)
|
|
14169
|
+
}
|
|
14170
|
+
};
|
|
14171
|
+
fnCallBuilders.delete(event.item_id);
|
|
14172
|
+
}
|
|
14173
|
+
break;
|
|
14174
|
+
}
|
|
14175
|
+
case "response.completed": {
|
|
14176
|
+
for (const [, builder] of fnCallBuilders) {
|
|
14177
|
+
yield {
|
|
14178
|
+
type: "tool_use_end",
|
|
14179
|
+
toolCall: {
|
|
14180
|
+
id: builder.callId,
|
|
14181
|
+
name: builder.name,
|
|
14182
|
+
input: parseArguments(builder.arguments)
|
|
14183
|
+
}
|
|
14184
|
+
};
|
|
14185
|
+
}
|
|
14186
|
+
fnCallBuilders.clear();
|
|
14187
|
+
const resp = event.response;
|
|
14188
|
+
const output = resp?.output ?? [];
|
|
14189
|
+
const hasToolCalls = output.some((i) => i.type === "function_call");
|
|
14190
|
+
yield {
|
|
14191
|
+
type: "done",
|
|
14192
|
+
stopReason: hasToolCalls ? "tool_use" : "end_turn"
|
|
14193
|
+
};
|
|
14194
|
+
break;
|
|
14195
|
+
}
|
|
14196
|
+
}
|
|
14197
|
+
}
|
|
14198
|
+
}
|
|
14199
|
+
} finally {
|
|
14200
|
+
clearInterval(timeoutInterval);
|
|
14201
|
+
reader.releaseLock();
|
|
14202
|
+
}
|
|
14203
|
+
if (timeoutController.signal.aborted) {
|
|
14204
|
+
throw new Error(
|
|
14205
|
+
`Stream timeout: No response from Codex API for ${STREAM_TIMEOUT_MS / 1e3}s`
|
|
14206
|
+
);
|
|
14207
|
+
}
|
|
13891
14208
|
}
|
|
13892
14209
|
};
|
|
13893
14210
|
var CONTEXT_WINDOWS4 = {
|