@oh-my-pi/pi-ai 6.7.670 → 6.8.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/README.md +31 -31
- package/package.json +2 -1
- package/src/cli.ts +126 -52
- package/src/providers/google-gemini-cli.ts +4 -20
- package/src/providers/openai-codex/response-handler.ts +4 -43
- package/src/providers/openai-codex-responses.ts +3 -2
- package/src/storage.ts +185 -0
- package/src/utils/event-stream.ts +3 -3
- package/src/utils/oauth/anthropic.ts +73 -97
- package/src/utils/oauth/callback-server.ts +247 -0
- package/src/utils/oauth/cursor.ts +7 -7
- package/src/utils/oauth/github-copilot.ts +1 -23
- package/src/utils/oauth/google-antigravity.ts +73 -263
- package/src/utils/oauth/google-gemini-cli.ts +73 -281
- package/src/utils/oauth/oauth.html +199 -0
- package/src/utils/oauth/openai-codex.ts +108 -329
- package/src/utils/oauth/types.ts +8 -0
package/README.md
CHANGED
|
@@ -10,38 +10,38 @@ Unified LLM API with automatic model discovery, provider configuration, token an
|
|
|
10
10
|
- [Installation](#installation)
|
|
11
11
|
- [Quick Start](#quick-start)
|
|
12
12
|
- [Tools](#tools)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
- [Defining Tools](#defining-tools)
|
|
14
|
+
- [Handling Tool Calls](#handling-tool-calls)
|
|
15
|
+
- [Streaming Tool Calls with Partial JSON](#streaming-tool-calls-with-partial-json)
|
|
16
|
+
- [Validating Tool Arguments](#validating-tool-arguments)
|
|
17
|
+
- [Complete Event Reference](#complete-event-reference)
|
|
18
18
|
- [Image Input](#image-input)
|
|
19
19
|
- [Thinking/Reasoning](#thinkingreasoning)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
- [Unified Interface](#unified-interface-streamsimplecompletesimple)
|
|
21
|
+
- [Provider-Specific Options](#provider-specific-options-streamcomplete)
|
|
22
|
+
- [Streaming Thinking Content](#streaming-thinking-content)
|
|
23
23
|
- [Stop Reasons](#stop-reasons)
|
|
24
24
|
- [Error Handling](#error-handling)
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
- [Aborting Requests](#aborting-requests)
|
|
26
|
+
- [Continuing After Abort](#continuing-after-abort)
|
|
27
27
|
- [APIs, Models, and Providers](#apis-models-and-providers)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
- [Providers and Models](#providers-and-models)
|
|
29
|
+
- [Querying Providers and Models](#querying-providers-and-models)
|
|
30
|
+
- [Custom Models](#custom-models)
|
|
31
|
+
- [OpenAI Compatibility Settings](#openai-compatibility-settings)
|
|
32
|
+
- [Type Safety](#type-safety)
|
|
33
33
|
- [Cross-Provider Handoffs](#cross-provider-handoffs)
|
|
34
34
|
- [Context Serialization](#context-serialization)
|
|
35
35
|
- [Browser Usage](#browser-usage)
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
- [Environment Variables](#environment-variables-nodejs-only)
|
|
37
|
+
- [Checking Environment Variables](#checking-environment-variables)
|
|
38
38
|
- [OAuth Providers](#oauth-providers)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
- [Vertex AI (ADC)](#vertex-ai-adc)
|
|
40
|
+
- [CLI Login](#cli-login)
|
|
41
|
+
- [Programmatic OAuth](#programmatic-oauth)
|
|
42
|
+
- [Login Flow Example](#login-flow-example)
|
|
43
|
+
- [Using OAuth Tokens](#using-oauth-tokens)
|
|
44
|
+
- [Provider Notes](#provider-notes)
|
|
45
45
|
- [License](#license)
|
|
46
46
|
|
|
47
47
|
## Supported Providers
|
|
@@ -156,7 +156,7 @@ for (const call of toolCalls) {
|
|
|
156
156
|
timeZone: call.arguments.timezone || "UTC",
|
|
157
157
|
dateStyle: "full",
|
|
158
158
|
timeStyle: "long",
|
|
159
|
-
|
|
159
|
+
})
|
|
160
160
|
: "Unknown tool";
|
|
161
161
|
|
|
162
162
|
// Add tool result to context (supports text and images)
|
|
@@ -443,7 +443,7 @@ const response = await completeSimple(
|
|
|
443
443
|
},
|
|
444
444
|
{
|
|
445
445
|
reasoning: "medium", // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' (xhigh maps to high on non-OpenAI providers)
|
|
446
|
-
}
|
|
446
|
+
},
|
|
447
447
|
);
|
|
448
448
|
|
|
449
449
|
// Access thinking and text blocks
|
|
@@ -563,7 +563,7 @@ const s = stream(
|
|
|
563
563
|
},
|
|
564
564
|
{
|
|
565
565
|
signal: controller.signal,
|
|
566
|
-
}
|
|
566
|
+
},
|
|
567
567
|
);
|
|
568
568
|
|
|
569
569
|
for await (const event of s) {
|
|
@@ -856,7 +856,7 @@ const response = await complete(
|
|
|
856
856
|
},
|
|
857
857
|
{
|
|
858
858
|
apiKey: "your-api-key",
|
|
859
|
-
}
|
|
859
|
+
},
|
|
860
860
|
);
|
|
861
861
|
```
|
|
862
862
|
|
|
@@ -957,9 +957,9 @@ Official docs: [Application Default Credentials](https://cloud.google.com/docs/a
|
|
|
957
957
|
The quickest way to authenticate:
|
|
958
958
|
|
|
959
959
|
```bash
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
960
|
+
bunx @oh-my-pi/pi-ai login # interactive provider selection
|
|
961
|
+
bunx @oh-my-pi/pi-ai login anthropic # login to specific provider
|
|
962
|
+
bunx @oh-my-pi/pi-ai list # list available providers
|
|
963
963
|
```
|
|
964
964
|
|
|
965
965
|
Credentials are saved to `auth.json` in the current directory.
|
|
@@ -1035,7 +1035,7 @@ const response = await complete(
|
|
|
1035
1035
|
{
|
|
1036
1036
|
messages: [{ role: "user", content: "Hello!" }],
|
|
1037
1037
|
},
|
|
1038
|
-
{ apiKey: result.apiKey }
|
|
1038
|
+
{ apiKey: result.apiKey },
|
|
1039
1039
|
);
|
|
1040
1040
|
```
|
|
1041
1041
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.8.1",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"test": "bun test"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@oh-my-pi/pi-utils": "6.8.1",
|
|
20
21
|
"@anthropic-ai/sdk": "0.71.2",
|
|
21
22
|
"@aws-sdk/client-bedrock-runtime": "^3.968.0",
|
|
22
23
|
"@bufbuild/protobuf": "^2.10.2",
|
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import "./utils/migrate-env";
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
2
|
import { createInterface } from "readline";
|
|
3
|
+
import { CliAuthStorage } from "./storage";
|
|
4
|
+
import "./utils/migrate-env";
|
|
5
5
|
import { loginAnthropic } from "./utils/oauth/anthropic";
|
|
6
|
+
import { loginCursor } from "./utils/oauth/cursor";
|
|
6
7
|
import { loginGitHubCopilot } from "./utils/oauth/github-copilot";
|
|
7
8
|
import { loginAntigravity } from "./utils/oauth/google-antigravity";
|
|
8
9
|
import { loginGeminiCli } from "./utils/oauth/google-gemini-cli";
|
|
@@ -10,105 +11,104 @@ import { getOAuthProviders } from "./utils/oauth/index";
|
|
|
10
11
|
import { loginOpenAICodex } from "./utils/oauth/openai-codex";
|
|
11
12
|
import type { OAuthCredentials, OAuthProvider } from "./utils/oauth/types";
|
|
12
13
|
|
|
13
|
-
const AUTH_FILE = "auth.json";
|
|
14
14
|
const PROVIDERS = getOAuthProviders();
|
|
15
15
|
|
|
16
16
|
function prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function loadAuth(): Record<string, { type: "oauth" } & OAuthCredentials> {
|
|
21
|
-
if (!existsSync(AUTH_FILE)) return {};
|
|
22
|
-
try {
|
|
23
|
-
return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
|
|
24
|
-
} catch {
|
|
25
|
-
return {};
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function saveAuth(auth: Record<string, { type: "oauth" } & OAuthCredentials>): void {
|
|
30
|
-
writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2), "utf-8");
|
|
17
|
+
const { promise, resolve } = Promise.withResolvers<string>();
|
|
18
|
+
rl.question(question, resolve);
|
|
19
|
+
return promise;
|
|
31
20
|
}
|
|
32
21
|
|
|
33
22
|
async function login(provider: OAuthProvider): Promise<void> {
|
|
34
23
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
35
24
|
|
|
36
25
|
const promptFn = (msg: string) => prompt(rl, `${msg} `);
|
|
26
|
+
const storage = new CliAuthStorage();
|
|
37
27
|
|
|
38
28
|
try {
|
|
39
29
|
let credentials: OAuthCredentials;
|
|
40
30
|
|
|
41
31
|
switch (provider) {
|
|
42
32
|
case "anthropic":
|
|
43
|
-
credentials = await loginAnthropic(
|
|
44
|
-
(
|
|
33
|
+
credentials = await loginAnthropic({
|
|
34
|
+
onAuth(info) {
|
|
35
|
+
const { url } = info;
|
|
45
36
|
console.log(`\nOpen this URL in your browser:\n${url}\n`);
|
|
46
37
|
},
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
onProgress(message) {
|
|
39
|
+
console.log(message);
|
|
49
40
|
},
|
|
50
|
-
);
|
|
41
|
+
});
|
|
51
42
|
break;
|
|
52
43
|
|
|
53
44
|
case "github-copilot":
|
|
54
45
|
credentials = await loginGitHubCopilot({
|
|
55
|
-
onAuth
|
|
46
|
+
onAuth(url, instructions) {
|
|
56
47
|
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
57
48
|
if (instructions) console.log(instructions);
|
|
58
49
|
console.log();
|
|
59
50
|
},
|
|
60
|
-
|
|
51
|
+
async onPrompt(p) {
|
|
61
52
|
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
62
53
|
},
|
|
63
|
-
onProgress: (msg) => console.log(msg),
|
|
64
54
|
});
|
|
65
55
|
break;
|
|
66
56
|
|
|
67
57
|
case "google-gemini-cli":
|
|
68
|
-
credentials = await loginGeminiCli(
|
|
69
|
-
(info)
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
credentials = await loginGeminiCli({
|
|
59
|
+
onAuth(info) {
|
|
60
|
+
const { url, instructions } = info;
|
|
61
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
62
|
+
if (instructions) console.log(instructions);
|
|
72
63
|
console.log();
|
|
73
64
|
},
|
|
74
|
-
|
|
75
|
-
);
|
|
65
|
+
});
|
|
76
66
|
break;
|
|
77
67
|
|
|
78
68
|
case "google-antigravity":
|
|
79
|
-
credentials = await loginAntigravity(
|
|
80
|
-
(info)
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
credentials = await loginAntigravity({
|
|
70
|
+
onAuth(info) {
|
|
71
|
+
const { url, instructions } = info;
|
|
72
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
73
|
+
if (instructions) console.log(instructions);
|
|
83
74
|
console.log();
|
|
84
75
|
},
|
|
85
|
-
|
|
86
|
-
);
|
|
76
|
+
});
|
|
87
77
|
break;
|
|
88
78
|
case "openai-codex":
|
|
89
79
|
credentials = await loginOpenAICodex({
|
|
90
|
-
onAuth
|
|
91
|
-
|
|
92
|
-
|
|
80
|
+
onAuth(info) {
|
|
81
|
+
const { url, instructions } = info;
|
|
82
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
83
|
+
if (instructions) console.log(instructions);
|
|
93
84
|
console.log();
|
|
94
85
|
},
|
|
95
|
-
|
|
86
|
+
async onPrompt(p) {
|
|
96
87
|
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
97
88
|
},
|
|
98
|
-
onProgress: (msg) => console.log(msg),
|
|
99
89
|
});
|
|
100
90
|
break;
|
|
101
91
|
|
|
92
|
+
case "cursor":
|
|
93
|
+
credentials = await loginCursor(
|
|
94
|
+
(url) => {
|
|
95
|
+
console.log(`\nOpen this URL in your browser:\n${url}\n`);
|
|
96
|
+
},
|
|
97
|
+
() => {
|
|
98
|
+
console.log("Waiting for browser authentication...");
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
break;
|
|
102
|
+
|
|
102
103
|
default:
|
|
103
104
|
throw new Error(`Unknown provider: ${provider}`);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
auth[provider] = { type: "oauth", ...credentials };
|
|
108
|
-
saveAuth(auth);
|
|
107
|
+
storage.saveOAuth(provider, credentials);
|
|
109
108
|
|
|
110
|
-
console.log(`\nCredentials saved to
|
|
109
|
+
console.log(`\nCredentials saved to ~/.omp/agent/agent.db`);
|
|
111
110
|
} finally {
|
|
111
|
+
storage.close();
|
|
112
112
|
rl.close();
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -118,10 +118,12 @@ async function main(): Promise<void> {
|
|
|
118
118
|
const command = args[0];
|
|
119
119
|
|
|
120
120
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
121
|
-
console.log(`Usage:
|
|
121
|
+
console.log(`Usage: bunx @oh-my-pi/pi-ai <command> [provider]
|
|
122
122
|
|
|
123
123
|
Commands:
|
|
124
124
|
login [provider] Login to an OAuth provider
|
|
125
|
+
logout [provider] Logout from an OAuth provider
|
|
126
|
+
status Show logged-in providers
|
|
125
127
|
list List available providers
|
|
126
128
|
|
|
127
129
|
Providers:
|
|
@@ -130,15 +132,43 @@ Providers:
|
|
|
130
132
|
google-gemini-cli Google Gemini CLI
|
|
131
133
|
google-antigravity Antigravity (Gemini 3, Claude, GPT-OSS)
|
|
132
134
|
openai-codex OpenAI Codex (ChatGPT Plus/Pro)
|
|
135
|
+
cursor Cursor (Claude, GPT, etc.)
|
|
133
136
|
|
|
134
137
|
Examples:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
bunx @oh-my-pi/pi-ai login # interactive provider selection
|
|
139
|
+
bunx @oh-my-pi/pi-ai login anthropic # login to specific provider
|
|
140
|
+
bunx @oh-my-pi/pi-ai logout anthropic # logout from specific provider
|
|
141
|
+
bunx @oh-my-pi/pi-ai status # show logged-in providers
|
|
142
|
+
bunx @oh-my-pi/pi-ai list # list providers
|
|
138
143
|
`);
|
|
139
144
|
return;
|
|
140
145
|
}
|
|
141
146
|
|
|
147
|
+
if (command === "status") {
|
|
148
|
+
const storage = new CliAuthStorage();
|
|
149
|
+
try {
|
|
150
|
+
const providers = storage.listProviders();
|
|
151
|
+
if (providers.length === 0) {
|
|
152
|
+
console.log("No OAuth credentials stored.");
|
|
153
|
+
console.log(`Use 'bunx @oh-my-pi/pi-ai login' to authenticate.`);
|
|
154
|
+
} else {
|
|
155
|
+
console.log("Logged-in providers:\n");
|
|
156
|
+
for (const provider of providers) {
|
|
157
|
+
const oauth = storage.getOAuth(provider);
|
|
158
|
+
if (oauth) {
|
|
159
|
+
const expires = new Date(oauth.expires);
|
|
160
|
+
const expired = Date.now() >= oauth.expires;
|
|
161
|
+
const status = expired ? "(expired)" : `(expires ${expires.toLocaleString()})`;
|
|
162
|
+
console.log(` ${provider.padEnd(20)} ${status}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} finally {
|
|
167
|
+
storage.close();
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
142
172
|
if (command === "list") {
|
|
143
173
|
console.log("Available OAuth providers:\n");
|
|
144
174
|
for (const p of PROVIDERS) {
|
|
@@ -147,6 +177,50 @@ Examples:
|
|
|
147
177
|
return;
|
|
148
178
|
}
|
|
149
179
|
|
|
180
|
+
if (command === "logout") {
|
|
181
|
+
let provider = args[1] as OAuthProvider | undefined;
|
|
182
|
+
const storage = new CliAuthStorage();
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
if (!provider) {
|
|
186
|
+
const providers = storage.listProviders();
|
|
187
|
+
if (providers.length === 0) {
|
|
188
|
+
console.log("No OAuth credentials stored.");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
193
|
+
console.log("Select a provider to logout:\n");
|
|
194
|
+
for (let i = 0; i < providers.length; i++) {
|
|
195
|
+
console.log(` ${i + 1}. ${providers[i]}`);
|
|
196
|
+
}
|
|
197
|
+
console.log();
|
|
198
|
+
|
|
199
|
+
const choice = await prompt(rl, `Enter number (1-${providers.length}): `);
|
|
200
|
+
rl.close();
|
|
201
|
+
|
|
202
|
+
const index = parseInt(choice, 10) - 1;
|
|
203
|
+
if (index < 0 || index >= providers.length) {
|
|
204
|
+
console.error("Invalid selection");
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
provider = providers[index] as OAuthProvider;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const oauth = storage.getOAuth(provider);
|
|
211
|
+
if (!oauth) {
|
|
212
|
+
console.error(`Not logged in to ${provider}`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
storage.deleteProvider(provider);
|
|
217
|
+
console.log(`Logged out from ${provider}`);
|
|
218
|
+
} finally {
|
|
219
|
+
storage.close();
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
150
224
|
if (command === "login") {
|
|
151
225
|
let provider = args[1] as OAuthProvider | undefined;
|
|
152
226
|
|
|
@@ -171,7 +245,7 @@ Examples:
|
|
|
171
245
|
|
|
172
246
|
if (!PROVIDERS.some((p) => p.id === provider)) {
|
|
173
247
|
console.error(`Unknown provider: ${provider}`);
|
|
174
|
-
console.error(`Use '
|
|
248
|
+
console.error(`Use 'bunx @oh-my-pi/pi-ai list' to see available providers`);
|
|
175
249
|
process.exit(1);
|
|
176
250
|
}
|
|
177
251
|
|
|
@@ -181,7 +255,7 @@ Examples:
|
|
|
181
255
|
}
|
|
182
256
|
|
|
183
257
|
console.error(`Unknown command: ${command}`);
|
|
184
|
-
console.error(`Use '
|
|
258
|
+
console.error(`Use 'bunx @oh-my-pi/pi-ai --help' for usage`);
|
|
185
259
|
process.exit(1);
|
|
186
260
|
}
|
|
187
261
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import type { Content, ThinkingConfig } from "@google/genai";
|
|
9
|
+
import { abortableSleep } from "@oh-my-pi/pi-utils";
|
|
9
10
|
import { calculateCost } from "../models";
|
|
10
11
|
import type {
|
|
11
12
|
Api,
|
|
@@ -301,23 +302,6 @@ function extractErrorMessage(errorText: string): string {
|
|
|
301
302
|
return errorText;
|
|
302
303
|
}
|
|
303
304
|
|
|
304
|
-
/**
|
|
305
|
-
* Sleep for a given number of milliseconds, respecting abort signal.
|
|
306
|
-
*/
|
|
307
|
-
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
308
|
-
return new Promise((resolve, reject) => {
|
|
309
|
-
if (signal?.aborted) {
|
|
310
|
-
reject(new Error("Request was aborted"));
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
const timeout = setTimeout(resolve, ms);
|
|
314
|
-
signal?.addEventListener("abort", () => {
|
|
315
|
-
clearTimeout(timeout);
|
|
316
|
-
reject(new Error("Request was aborted"));
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
305
|
interface CloudCodeAssistRequest {
|
|
322
306
|
project: string;
|
|
323
307
|
model: string;
|
|
@@ -468,7 +452,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
468
452
|
// Use server-provided delay or exponential backoff
|
|
469
453
|
const serverDelay = extractRetryDelay(errorText, response);
|
|
470
454
|
const delayMs = serverDelay ?? BASE_DELAY_MS * 2 ** attempt;
|
|
471
|
-
await
|
|
455
|
+
await abortableSleep(delayMs, options?.signal);
|
|
472
456
|
continue;
|
|
473
457
|
}
|
|
474
458
|
|
|
@@ -489,7 +473,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
489
473
|
// Network errors are retryable
|
|
490
474
|
if (attempt < MAX_RETRIES) {
|
|
491
475
|
const delayMs = BASE_DELAY_MS * 2 ** attempt;
|
|
492
|
-
await
|
|
476
|
+
await abortableSleep(delayMs, options?.signal);
|
|
493
477
|
continue;
|
|
494
478
|
}
|
|
495
479
|
throw lastError;
|
|
@@ -769,7 +753,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
769
753
|
|
|
770
754
|
if (emptyAttempt > 0) {
|
|
771
755
|
const backoffMs = EMPTY_STREAM_BASE_DELAY_MS * 2 ** (emptyAttempt - 1);
|
|
772
|
-
await
|
|
756
|
+
await abortableSleep(backoffMs, options?.signal);
|
|
773
757
|
|
|
774
758
|
if (!requestUrl) {
|
|
775
759
|
throw new Error("Missing request URL");
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readSseData } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
1
3
|
export type CodexRateLimit = {
|
|
2
4
|
used_percent?: number;
|
|
3
5
|
window_minutes?: number;
|
|
@@ -74,49 +76,8 @@ export async function* parseCodexSseStream(response: Response): AsyncGenerator<R
|
|
|
74
76
|
return;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
let buffer = "";
|
|
80
|
-
|
|
81
|
-
while (true) {
|
|
82
|
-
const { done, value } = await reader.read();
|
|
83
|
-
if (done) break;
|
|
84
|
-
buffer += decoder.decode(value, { stream: true });
|
|
85
|
-
|
|
86
|
-
let index = buffer.indexOf("\n\n");
|
|
87
|
-
while (index !== -1) {
|
|
88
|
-
const chunk = buffer.slice(0, index);
|
|
89
|
-
buffer = buffer.slice(index + 2);
|
|
90
|
-
const event = parseSseChunk(chunk);
|
|
91
|
-
if (event) yield event;
|
|
92
|
-
index = buffer.indexOf("\n\n");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (buffer.trim()) {
|
|
97
|
-
const event = parseSseChunk(buffer);
|
|
98
|
-
if (event) yield event;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function parseSseChunk(chunk: string): Record<string, unknown> | null {
|
|
103
|
-
const lines = chunk.split("\n");
|
|
104
|
-
const dataLines: string[] = [];
|
|
105
|
-
|
|
106
|
-
for (const line of lines) {
|
|
107
|
-
if (line.startsWith("data:")) {
|
|
108
|
-
dataLines.push(line.slice(5).trim());
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (dataLines.length === 0) return null;
|
|
113
|
-
const data = dataLines.join("\n").trim();
|
|
114
|
-
if (!data || data === "[DONE]") return null;
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
return JSON.parse(data) as Record<string, unknown>;
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
79
|
+
for await (const data of readSseData<Record<string, unknown>>(response.body)) {
|
|
80
|
+
yield data;
|
|
120
81
|
}
|
|
121
82
|
}
|
|
122
83
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
|
+
import { abortableSleep } from "@oh-my-pi/pi-utils";
|
|
2
3
|
import type {
|
|
3
4
|
ResponseFunctionToolCall,
|
|
4
5
|
ResponseInput,
|
|
@@ -440,13 +441,13 @@ async function fetchWithRetry(url: string, init: RequestInit, signal?: AbortSign
|
|
|
440
441
|
}
|
|
441
442
|
if (signal?.aborted) return response;
|
|
442
443
|
const delay = getRetryDelayMs(response, attempt);
|
|
443
|
-
await
|
|
444
|
+
await abortableSleep(delay, signal);
|
|
444
445
|
} catch (error) {
|
|
445
446
|
if (attempt >= CODEX_MAX_RETRIES || signal?.aborted) {
|
|
446
447
|
throw error;
|
|
447
448
|
}
|
|
448
449
|
const delay = CODEX_RETRY_DELAY_MS * (attempt + 1);
|
|
449
|
-
await
|
|
450
|
+
await abortableSleep(delay, signal);
|
|
450
451
|
}
|
|
451
452
|
attempt += 1;
|
|
452
453
|
}
|