@oh-my-pi/pi-ai 6.8.1 → 6.8.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "6.8.
|
|
3
|
+
"version": "6.8.2",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "bun test"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@oh-my-pi/pi-utils": "6.8.
|
|
20
|
+
"@oh-my-pi/pi-utils": "6.8.2",
|
|
21
21
|
"@anthropic-ai/sdk": "0.71.2",
|
|
22
22
|
"@aws-sdk/client-bedrock-runtime": "^3.968.0",
|
|
23
23
|
"@bufbuild/protobuf": "^2.10.2",
|
|
@@ -753,7 +753,12 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
753
753
|
|
|
754
754
|
if (emptyAttempt > 0) {
|
|
755
755
|
const backoffMs = EMPTY_STREAM_BASE_DELAY_MS * 2 ** (emptyAttempt - 1);
|
|
756
|
-
|
|
756
|
+
try {
|
|
757
|
+
await abortableSleep(backoffMs, options?.signal);
|
|
758
|
+
} catch {
|
|
759
|
+
// Normalize AbortError to expected message for consistent error handling
|
|
760
|
+
throw new Error("Request was aborted");
|
|
761
|
+
}
|
|
757
762
|
|
|
758
763
|
if (!requestUrl) {
|
|
759
764
|
throw new Error("Missing request URL");
|
package/src/storage.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Database } from "bun:sqlite";
|
|
7
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import type { OAuthCredentials } from "./utils/oauth/types";
|
|
@@ -88,13 +88,19 @@ export class CliAuthStorage {
|
|
|
88
88
|
private deleteByProviderStmt: ReturnType<Database["prepare"]>;
|
|
89
89
|
|
|
90
90
|
constructor(dbPath: string = getAgentDbPath()) {
|
|
91
|
-
// Ensure directory exists
|
|
91
|
+
// Ensure directory exists with secure permissions
|
|
92
92
|
const dir = dirname(dbPath);
|
|
93
93
|
if (!existsSync(dir)) {
|
|
94
|
-
mkdirSync(dir, { recursive: true });
|
|
94
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
this.db = new Database(dbPath);
|
|
98
|
+
// Harden database file permissions to prevent credential leakage
|
|
99
|
+
try {
|
|
100
|
+
chmodSync(dbPath, 0o600);
|
|
101
|
+
} catch {
|
|
102
|
+
// Ignore chmod failures (e.g., Windows)
|
|
103
|
+
}
|
|
98
104
|
this.initializeSchema();
|
|
99
105
|
|
|
100
106
|
this.insertStmt = this.db.prepare(
|
|
@@ -197,17 +197,24 @@ export abstract class OAuthCallbackFlow {
|
|
|
197
197
|
});
|
|
198
198
|
|
|
199
199
|
// Manual input race (if supported)
|
|
200
|
+
// Errors from manual input should not abort the flow - only successful input wins the race
|
|
200
201
|
if (this.ctrl.onManualCodeInput) {
|
|
201
|
-
const manualPromise = this.ctrl
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
const manualPromise = this.ctrl
|
|
203
|
+
.onManualCodeInput()
|
|
204
|
+
.then((input): CallbackResult => {
|
|
205
|
+
const parsed = parseCallbackInput(input);
|
|
206
|
+
if (!parsed.code) {
|
|
207
|
+
throw new Error("No authorization code found in input");
|
|
208
|
+
}
|
|
209
|
+
if (expectedState && parsed.state && parsed.state !== expectedState) {
|
|
210
|
+
throw new Error("State mismatch - possible CSRF attack");
|
|
211
|
+
}
|
|
212
|
+
return { code: parsed.code, state: parsed.state ?? "" };
|
|
213
|
+
})
|
|
214
|
+
.catch((): Promise<CallbackResult> => {
|
|
215
|
+
// On manual input error, wait forever - let callback or abort signal win
|
|
216
|
+
return new Promise(() => {});
|
|
217
|
+
});
|
|
211
218
|
|
|
212
219
|
return Promise.race([callbackPromise, manualPromise]);
|
|
213
220
|
}
|
|
@@ -174,20 +174,32 @@ async function pollForGitHubAccessToken(
|
|
|
174
174
|
if (raw && typeof raw === "object" && typeof (raw as DeviceTokenErrorResponse).error === "string") {
|
|
175
175
|
const err = (raw as DeviceTokenErrorResponse).error;
|
|
176
176
|
if (err === "authorization_pending") {
|
|
177
|
-
|
|
177
|
+
try {
|
|
178
|
+
await abortableSleep(intervalMs, signal);
|
|
179
|
+
} catch {
|
|
180
|
+
throw new Error("Login cancelled");
|
|
181
|
+
}
|
|
178
182
|
continue;
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
if (err === "slow_down") {
|
|
182
186
|
intervalMs += 5000;
|
|
183
|
-
|
|
187
|
+
try {
|
|
188
|
+
await abortableSleep(intervalMs, signal);
|
|
189
|
+
} catch {
|
|
190
|
+
throw new Error("Login cancelled");
|
|
191
|
+
}
|
|
184
192
|
continue;
|
|
185
193
|
}
|
|
186
194
|
|
|
187
195
|
throw new Error(`Device flow failed: ${err}`);
|
|
188
196
|
}
|
|
189
197
|
|
|
190
|
-
|
|
198
|
+
try {
|
|
199
|
+
await abortableSleep(intervalMs, signal);
|
|
200
|
+
} catch {
|
|
201
|
+
throw new Error("Login cancelled");
|
|
202
|
+
}
|
|
191
203
|
}
|
|
192
204
|
|
|
193
205
|
throw new Error("Device flow timed out");
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* OpenAI Codex (ChatGPT OAuth) flow
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { OAuthCallbackFlow
|
|
5
|
+
import { OAuthCallbackFlow } from "./callback-server";
|
|
6
6
|
import { generatePKCE } from "./pkce";
|
|
7
7
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
8
8
|
|
|
@@ -125,28 +125,8 @@ async function exchangeCodeForToken(code: string, verifier: string, redirectUri:
|
|
|
125
125
|
export async function loginOpenAICodex(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
126
126
|
const pkce = await generatePKCE();
|
|
127
127
|
const flow = new OpenAICodexOAuthFlow(ctrl, pkce);
|
|
128
|
-
const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
return await flow.login();
|
|
132
|
-
} catch (error) {
|
|
133
|
-
if (!ctrl.onPrompt) {
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
ctrl.onProgress?.("Callback server failed, falling back to manual input");
|
|
138
|
-
|
|
139
|
-
const input = await ctrl.onPrompt({
|
|
140
|
-
message: "Paste the authorization code (or full redirect URL):",
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const parsed = parseCallbackInput(input);
|
|
144
|
-
if (!parsed.code) {
|
|
145
|
-
throw new Error("No authorization code found in input");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return exchangeCodeForToken(parsed.code, pkce.verifier, redirectUri);
|
|
149
|
-
}
|
|
129
|
+
return flow.login();
|
|
150
130
|
}
|
|
151
131
|
|
|
152
132
|
/**
|