gsd-pi 2.10.2 → 2.10.5
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 +2 -0
- package/dist/cli.js +7 -0
- package/dist/loader.js +1 -0
- package/dist/onboarding.js +104 -59
- package/dist/update-cmd.d.ts +1 -0
- package/dist/update-cmd.js +40 -0
- package/node_modules/@gsd/native/dist/hasher/index.d.ts +32 -0
- package/node_modules/@gsd/native/dist/hasher/index.js +37 -0
- package/node_modules/@gsd/native/dist/native.d.ts +4 -1
- package/node_modules/@gsd/native/dist/native.js +39 -9
- package/node_modules/@gsd/native/dist/xxhash/index.d.ts +14 -0
- package/node_modules/@gsd/native/dist/xxhash/index.js +17 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/agent-session.d.ts +6 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/agent-session.js +58 -9
- package/node_modules/@gsd/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/auth-storage.d.ts +72 -12
- package/node_modules/@gsd/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/auth-storage.js +254 -43
- package/node_modules/@gsd/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/auth-storage.test.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/auth-storage.test.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/auth-storage.test.js +159 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/model-registry.d.ts +4 -2
- package/node_modules/@gsd/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/model-registry.js +6 -4
- package/node_modules/@gsd/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/settings-manager.d.ts +15 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/settings-manager.js +12 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash-interceptor.d.ts +40 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash-interceptor.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash-interceptor.js +92 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash-interceptor.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash-interceptor.test.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash-interceptor.test.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash-interceptor.test.js +156 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash-interceptor.test.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts +8 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js +18 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts +2 -2
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/core/agent-session.ts +65 -9
- package/node_modules/@gsd/pi-coding-agent/src/core/auth-storage.test.ts +194 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/auth-storage.ts +283 -53
- package/node_modules/@gsd/pi-coding-agent/src/core/model-registry.ts +6 -4
- package/node_modules/@gsd/pi-coding-agent/src/core/settings-manager.ts +29 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/bash-interceptor.test.ts +198 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/bash-interceptor.ts +115 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/bash.ts +29 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/index.ts +8 -0
- package/node_modules/@gsd/pi-coding-agent/src/index.ts +6 -0
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/package.json +8 -2
- package/packages/native/dist/hasher/index.d.ts +32 -0
- package/packages/native/dist/hasher/index.js +37 -0
- package/packages/native/dist/native.d.ts +4 -1
- package/packages/native/dist/native.js +39 -9
- package/packages/native/dist/xxhash/index.d.ts +14 -0
- package/packages/native/dist/xxhash/index.js +17 -0
- package/packages/native/src/native.ts +39 -9
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +58 -9
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +72 -12
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +254 -43
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +159 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +4 -2
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +6 -4
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +12 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.d.ts +40 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.js +92 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.js +156 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +18 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -2
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +65 -9
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +194 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +283 -53
- package/packages/pi-coding-agent/src/core/model-registry.ts +6 -4
- package/packages/pi-coding-agent/src/core/settings-manager.ts +29 -0
- package/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts +198 -0
- package/packages/pi-coding-agent/src/core/tools/bash-interceptor.ts +115 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +29 -0
- package/packages/pi-coding-agent/src/core/tools/index.ts +8 -0
- package/packages/pi-coding-agent/src/index.ts +6 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +211 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +101 -0
- package/src/resources/extensions/async-jobs/cancel-job-tool.ts +34 -0
- package/src/resources/extensions/async-jobs/index.ts +133 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +250 -0
- package/src/resources/extensions/gsd/git-service.ts +13 -3
- package/src/resources/extensions/gsd/prompts/system.md +5 -2
- package/src/resources/extensions/gsd/tests/git-service.test.ts +36 -0
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Credential storage for API keys and OAuth tokens.
|
|
3
3
|
* Handles loading, saving, and refreshing credentials from auth.json.
|
|
4
4
|
*
|
|
5
|
+
* Supports multiple credentials per provider with round-robin selection,
|
|
6
|
+
* session-sticky hashing, and automatic rate-limit fallback.
|
|
7
|
+
*
|
|
5
8
|
* Uses file locking to prevent race conditions when multiple pi instances
|
|
6
9
|
* try to refresh tokens simultaneously.
|
|
7
10
|
*/
|
|
@@ -14,7 +17,11 @@ export type OAuthCredential = {
|
|
|
14
17
|
type: "oauth";
|
|
15
18
|
} & OAuthCredentials;
|
|
16
19
|
export type AuthCredential = ApiKeyCredential | OAuthCredential;
|
|
17
|
-
|
|
20
|
+
/**
|
|
21
|
+
* On-disk format: each provider maps to a single credential or an array of credentials.
|
|
22
|
+
* Single credentials are normalized to arrays at load time for internal use.
|
|
23
|
+
*/
|
|
24
|
+
export type AuthStorageData = Record<string, AuthCredential | AuthCredential[]>;
|
|
18
25
|
type LockResult<T> = {
|
|
19
26
|
result: T;
|
|
20
27
|
next?: string;
|
|
@@ -37,8 +44,10 @@ export declare class InMemoryAuthStorageBackend implements AuthStorageBackend {
|
|
|
37
44
|
withLock<T>(fn: (current: string | undefined) => LockResult<T>): T;
|
|
38
45
|
withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T>;
|
|
39
46
|
}
|
|
47
|
+
export type UsageLimitErrorType = "rate_limit" | "quota_exhausted" | "server_error" | "unknown";
|
|
40
48
|
/**
|
|
41
49
|
* Credential storage backed by a JSON file.
|
|
50
|
+
* Supports multiple credentials per provider with round-robin rotation and rate-limit fallback.
|
|
42
51
|
*/
|
|
43
52
|
export declare class AuthStorage {
|
|
44
53
|
private storage;
|
|
@@ -47,6 +56,16 @@ export declare class AuthStorage {
|
|
|
47
56
|
private fallbackResolver?;
|
|
48
57
|
private loadError;
|
|
49
58
|
private errors;
|
|
59
|
+
/**
|
|
60
|
+
* Round-robin index per provider. Incremented on each call to getApiKey
|
|
61
|
+
* when no sessionId is provided.
|
|
62
|
+
*/
|
|
63
|
+
private providerRoundRobinIndex;
|
|
64
|
+
/**
|
|
65
|
+
* Backoff tracking per provider per credential index.
|
|
66
|
+
* Map<provider, Map<credentialIndex, backoffExpiresAt>>
|
|
67
|
+
*/
|
|
68
|
+
private credentialBackoff;
|
|
50
69
|
private constructor();
|
|
51
70
|
static create(authPath?: string): AuthStorage;
|
|
52
71
|
static fromStorage(storage: AuthStorageBackend): AuthStorage;
|
|
@@ -67,21 +86,28 @@ export declare class AuthStorage {
|
|
|
67
86
|
setFallbackResolver(resolver: (provider: string) => string | undefined): void;
|
|
68
87
|
private recordError;
|
|
69
88
|
private parseStorageData;
|
|
89
|
+
/**
|
|
90
|
+
* Normalize a storage entry to an array of credentials.
|
|
91
|
+
* Handles both single credential (backward compat) and array formats.
|
|
92
|
+
*/
|
|
93
|
+
getCredentialsForProvider(provider: string): AuthCredential[];
|
|
70
94
|
/**
|
|
71
95
|
* Reload credentials from storage.
|
|
72
96
|
*/
|
|
73
97
|
reload(): void;
|
|
74
98
|
private persistProviderChange;
|
|
75
99
|
/**
|
|
76
|
-
* Get credential for a provider.
|
|
100
|
+
* Get the first credential for a provider (backward-compatible).
|
|
77
101
|
*/
|
|
78
102
|
get(provider: string): AuthCredential | undefined;
|
|
79
103
|
/**
|
|
80
|
-
* Set credential for a provider.
|
|
104
|
+
* Set credential for a provider. For API key credentials, appends to
|
|
105
|
+
* existing credentials (accumulation on duplicate login). For OAuth,
|
|
106
|
+
* replaces (only one OAuth token per provider makes sense).
|
|
81
107
|
*/
|
|
82
108
|
set(provider: string, credential: AuthCredential): void;
|
|
83
109
|
/**
|
|
84
|
-
* Remove
|
|
110
|
+
* Remove all credentials for a provider.
|
|
85
111
|
*/
|
|
86
112
|
remove(provider: string): void;
|
|
87
113
|
/**
|
|
@@ -99,8 +125,14 @@ export declare class AuthStorage {
|
|
|
99
125
|
hasAuth(provider: string): boolean;
|
|
100
126
|
/**
|
|
101
127
|
* Get all credentials (for passing to getOAuthApiKey).
|
|
102
|
-
|
|
103
|
-
|
|
128
|
+
* Returns normalized format where each provider has a single credential
|
|
129
|
+
* (the first one) for backward compatibility with OAuth refresh.
|
|
130
|
+
*
|
|
131
|
+
* NOTE: For providers with multiple API keys, only the first credential is
|
|
132
|
+
* returned. This is intentional — callers use this for OAuth refresh only,
|
|
133
|
+
* which is always single-credential. Do not use for API key enumeration.
|
|
134
|
+
*/
|
|
135
|
+
getAll(): Record<string, AuthCredential>;
|
|
104
136
|
drainErrors(): Error[];
|
|
105
137
|
/**
|
|
106
138
|
* Login to an OAuth provider.
|
|
@@ -110,21 +142,49 @@ export declare class AuthStorage {
|
|
|
110
142
|
* Logout from a provider.
|
|
111
143
|
*/
|
|
112
144
|
logout(provider: string): void;
|
|
145
|
+
/**
|
|
146
|
+
* Check if a credential index is currently backed off.
|
|
147
|
+
*/
|
|
148
|
+
private isCredentialBackedOff;
|
|
149
|
+
/**
|
|
150
|
+
* Select the best credential index for a provider.
|
|
151
|
+
* - If sessionId is provided, uses session-sticky hashing as the starting point.
|
|
152
|
+
* - Otherwise, uses round-robin as the starting point.
|
|
153
|
+
* - Skips credentials that are currently backed off.
|
|
154
|
+
* - Returns -1 if all credentials are backed off.
|
|
155
|
+
*/
|
|
156
|
+
private selectCredentialIndex;
|
|
157
|
+
/**
|
|
158
|
+
* Mark a credential as rate-limited. Finds the credential that was most
|
|
159
|
+
* recently used for this provider+session and backs it off.
|
|
160
|
+
*
|
|
161
|
+
* @returns true if another credential is available (caller should retry),
|
|
162
|
+
* false if all credentials for this provider are backed off.
|
|
163
|
+
*/
|
|
164
|
+
markUsageLimitReached(provider: string, sessionId?: string, options?: {
|
|
165
|
+
errorType?: UsageLimitErrorType;
|
|
166
|
+
}): boolean;
|
|
113
167
|
/**
|
|
114
168
|
* Refresh OAuth token with backend locking to prevent race conditions.
|
|
115
169
|
* Multiple pi instances may try to refresh simultaneously when tokens expire.
|
|
116
170
|
*/
|
|
117
171
|
private refreshOAuthTokenWithLock;
|
|
172
|
+
/**
|
|
173
|
+
* Resolve an API key from a single credential.
|
|
174
|
+
*/
|
|
175
|
+
private resolveCredentialApiKey;
|
|
118
176
|
/**
|
|
119
177
|
* Get API key for a provider.
|
|
120
178
|
* Priority:
|
|
121
179
|
* 1. Runtime override (CLI --api-key)
|
|
122
|
-
* 2.
|
|
123
|
-
* 3.
|
|
124
|
-
* 4.
|
|
125
|
-
*
|
|
126
|
-
|
|
127
|
-
|
|
180
|
+
* 2. Credential(s) from auth.json (with round-robin / session-sticky selection)
|
|
181
|
+
* 3. Environment variable
|
|
182
|
+
* 4. Fallback resolver (models.json custom providers)
|
|
183
|
+
*
|
|
184
|
+
* @param providerId - The provider to get an API key for
|
|
185
|
+
* @param sessionId - Optional session ID for sticky credential selection
|
|
186
|
+
*/
|
|
187
|
+
getApiKey(providerId: string, sessionId?: string): Promise<string | undefined>;
|
|
128
188
|
/**
|
|
129
189
|
* Get all registered OAuth providers
|
|
130
190
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-storage.d.ts","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth-storage.d.ts","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Credential storage for API keys and OAuth tokens.
|
|
3
3
|
* Handles loading, saving, and refreshing credentials from auth.json.
|
|
4
4
|
*
|
|
5
|
+
* Supports multiple credentials per provider with round-robin selection,
|
|
6
|
+
* session-sticky hashing, and automatic rate-limit fallback.
|
|
7
|
+
*
|
|
5
8
|
* Uses file locking to prevent race conditions when multiple pi instances
|
|
6
9
|
* try to refresh tokens simultaneously.
|
|
7
10
|
*/
|
|
@@ -137,8 +140,43 @@ export class InMemoryAuthStorageBackend {
|
|
|
137
140
|
return result;
|
|
138
141
|
}
|
|
139
142
|
}
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// Backoff durations for different error types (milliseconds)
|
|
145
|
+
// ============================================================================
|
|
146
|
+
const BACKOFF_RATE_LIMIT_MS = 30_000; // 30s for rate limit / 429
|
|
147
|
+
const BACKOFF_QUOTA_EXHAUSTED_MS = 30 * 60_000; // 30min for quota exhausted
|
|
148
|
+
const BACKOFF_SERVER_ERROR_MS = 20_000; // 20s for 5xx server errors
|
|
149
|
+
const BACKOFF_DEFAULT_MS = 60_000; // 60s fallback
|
|
150
|
+
/**
|
|
151
|
+
* Get backoff duration for an error type.
|
|
152
|
+
*/
|
|
153
|
+
function getBackoffDuration(errorType) {
|
|
154
|
+
switch (errorType) {
|
|
155
|
+
case "rate_limit":
|
|
156
|
+
return BACKOFF_RATE_LIMIT_MS;
|
|
157
|
+
case "quota_exhausted":
|
|
158
|
+
return BACKOFF_QUOTA_EXHAUSTED_MS;
|
|
159
|
+
case "server_error":
|
|
160
|
+
return BACKOFF_SERVER_ERROR_MS;
|
|
161
|
+
default:
|
|
162
|
+
return BACKOFF_DEFAULT_MS;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Simple string hash for session-sticky credential selection.
|
|
167
|
+
* Returns a positive integer.
|
|
168
|
+
*/
|
|
169
|
+
function hashString(str) {
|
|
170
|
+
let hash = 0;
|
|
171
|
+
for (let i = 0; i < str.length; i++) {
|
|
172
|
+
const char = str.charCodeAt(i);
|
|
173
|
+
hash = ((hash << 5) - hash + char) | 0;
|
|
174
|
+
}
|
|
175
|
+
return Math.abs(hash);
|
|
176
|
+
}
|
|
140
177
|
/**
|
|
141
178
|
* Credential storage backed by a JSON file.
|
|
179
|
+
* Supports multiple credentials per provider with round-robin rotation and rate-limit fallback.
|
|
142
180
|
*/
|
|
143
181
|
export class AuthStorage {
|
|
144
182
|
constructor(storage) {
|
|
@@ -147,6 +185,16 @@ export class AuthStorage {
|
|
|
147
185
|
this.runtimeOverrides = new Map();
|
|
148
186
|
this.loadError = null;
|
|
149
187
|
this.errors = [];
|
|
188
|
+
/**
|
|
189
|
+
* Round-robin index per provider. Incremented on each call to getApiKey
|
|
190
|
+
* when no sessionId is provided.
|
|
191
|
+
*/
|
|
192
|
+
this.providerRoundRobinIndex = new Map();
|
|
193
|
+
/**
|
|
194
|
+
* Backoff tracking per provider per credential index.
|
|
195
|
+
* Map<provider, Map<credentialIndex, backoffExpiresAt>>
|
|
196
|
+
*/
|
|
197
|
+
this.credentialBackoff = new Map();
|
|
150
198
|
this.reload();
|
|
151
199
|
}
|
|
152
200
|
static create(authPath) {
|
|
@@ -190,6 +238,18 @@ export class AuthStorage {
|
|
|
190
238
|
}
|
|
191
239
|
return JSON.parse(content);
|
|
192
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Normalize a storage entry to an array of credentials.
|
|
243
|
+
* Handles both single credential (backward compat) and array formats.
|
|
244
|
+
*/
|
|
245
|
+
getCredentialsForProvider(provider) {
|
|
246
|
+
const entry = this.data[provider];
|
|
247
|
+
if (!entry)
|
|
248
|
+
return [];
|
|
249
|
+
if (Array.isArray(entry))
|
|
250
|
+
return entry;
|
|
251
|
+
return [entry];
|
|
252
|
+
}
|
|
193
253
|
/**
|
|
194
254
|
* Reload credentials from storage.
|
|
195
255
|
*/
|
|
@@ -230,23 +290,50 @@ export class AuthStorage {
|
|
|
230
290
|
}
|
|
231
291
|
}
|
|
232
292
|
/**
|
|
233
|
-
* Get credential for a provider.
|
|
293
|
+
* Get the first credential for a provider (backward-compatible).
|
|
234
294
|
*/
|
|
235
295
|
get(provider) {
|
|
236
|
-
|
|
296
|
+
const creds = this.getCredentialsForProvider(provider);
|
|
297
|
+
return creds[0] ?? undefined;
|
|
237
298
|
}
|
|
238
299
|
/**
|
|
239
|
-
* Set credential for a provider.
|
|
300
|
+
* Set credential for a provider. For API key credentials, appends to
|
|
301
|
+
* existing credentials (accumulation on duplicate login). For OAuth,
|
|
302
|
+
* replaces (only one OAuth token per provider makes sense).
|
|
240
303
|
*/
|
|
241
304
|
set(provider, credential) {
|
|
242
|
-
|
|
243
|
-
|
|
305
|
+
if (credential.type === "api_key") {
|
|
306
|
+
const existing = this.getCredentialsForProvider(provider);
|
|
307
|
+
// Deduplicate: don't add if same key already exists
|
|
308
|
+
const isDuplicate = existing.some((c) => c.type === "api_key" && c.key === credential.key);
|
|
309
|
+
if (isDuplicate)
|
|
310
|
+
return;
|
|
311
|
+
const updated = [...existing, credential];
|
|
312
|
+
this.data[provider] = updated.length === 1 ? updated[0] : updated;
|
|
313
|
+
this.persistProviderChange(provider, updated.length === 1 ? updated[0] : updated);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// OAuth: replace any existing OAuth credential, keep API keys
|
|
317
|
+
const existing = this.getCredentialsForProvider(provider);
|
|
318
|
+
const apiKeys = existing.filter((c) => c.type === "api_key");
|
|
319
|
+
if (apiKeys.length === 0) {
|
|
320
|
+
this.data[provider] = credential;
|
|
321
|
+
this.persistProviderChange(provider, credential);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
const updated = [...apiKeys, credential];
|
|
325
|
+
this.data[provider] = updated;
|
|
326
|
+
this.persistProviderChange(provider, updated);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
244
329
|
}
|
|
245
330
|
/**
|
|
246
|
-
* Remove
|
|
331
|
+
* Remove all credentials for a provider.
|
|
247
332
|
*/
|
|
248
333
|
remove(provider) {
|
|
249
334
|
delete this.data[provider];
|
|
335
|
+
this.providerRoundRobinIndex.delete(provider);
|
|
336
|
+
this.credentialBackoff.delete(provider);
|
|
250
337
|
this.persistProviderChange(provider, undefined);
|
|
251
338
|
}
|
|
252
339
|
/**
|
|
@@ -278,9 +365,19 @@ export class AuthStorage {
|
|
|
278
365
|
}
|
|
279
366
|
/**
|
|
280
367
|
* Get all credentials (for passing to getOAuthApiKey).
|
|
368
|
+
* Returns normalized format where each provider has a single credential
|
|
369
|
+
* (the first one) for backward compatibility with OAuth refresh.
|
|
370
|
+
*
|
|
371
|
+
* NOTE: For providers with multiple API keys, only the first credential is
|
|
372
|
+
* returned. This is intentional — callers use this for OAuth refresh only,
|
|
373
|
+
* which is always single-credential. Do not use for API key enumeration.
|
|
281
374
|
*/
|
|
282
375
|
getAll() {
|
|
283
|
-
|
|
376
|
+
const result = {};
|
|
377
|
+
for (const [provider, entry] of Object.entries(this.data)) {
|
|
378
|
+
result[provider] = Array.isArray(entry) ? entry[0] : entry;
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
284
381
|
}
|
|
285
382
|
drainErrors() {
|
|
286
383
|
const drained = [...this.errors];
|
|
@@ -304,6 +401,101 @@ export class AuthStorage {
|
|
|
304
401
|
logout(provider) {
|
|
305
402
|
this.remove(provider);
|
|
306
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Check if a credential index is currently backed off.
|
|
406
|
+
*/
|
|
407
|
+
isCredentialBackedOff(provider, index) {
|
|
408
|
+
const providerBackoff = this.credentialBackoff.get(provider);
|
|
409
|
+
if (!providerBackoff)
|
|
410
|
+
return false;
|
|
411
|
+
const expiresAt = providerBackoff.get(index);
|
|
412
|
+
if (expiresAt === undefined)
|
|
413
|
+
return false;
|
|
414
|
+
if (Date.now() >= expiresAt) {
|
|
415
|
+
providerBackoff.delete(index);
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Select the best credential index for a provider.
|
|
422
|
+
* - If sessionId is provided, uses session-sticky hashing as the starting point.
|
|
423
|
+
* - Otherwise, uses round-robin as the starting point.
|
|
424
|
+
* - Skips credentials that are currently backed off.
|
|
425
|
+
* - Returns -1 if all credentials are backed off.
|
|
426
|
+
*/
|
|
427
|
+
selectCredentialIndex(provider, credentials, sessionId) {
|
|
428
|
+
if (credentials.length === 0)
|
|
429
|
+
return -1;
|
|
430
|
+
if (credentials.length === 1) {
|
|
431
|
+
return this.isCredentialBackedOff(provider, 0) ? -1 : 0;
|
|
432
|
+
}
|
|
433
|
+
let startIndex;
|
|
434
|
+
if (sessionId) {
|
|
435
|
+
startIndex = hashString(sessionId) % credentials.length;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
const current = this.providerRoundRobinIndex.get(provider) ?? 0;
|
|
439
|
+
startIndex = current % credentials.length;
|
|
440
|
+
this.providerRoundRobinIndex.set(provider, current + 1);
|
|
441
|
+
}
|
|
442
|
+
// Try starting from the preferred index, wrapping around
|
|
443
|
+
for (let offset = 0; offset < credentials.length; offset++) {
|
|
444
|
+
const index = (startIndex + offset) % credentials.length;
|
|
445
|
+
if (!this.isCredentialBackedOff(provider, index)) {
|
|
446
|
+
return index;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// All credentials are backed off
|
|
450
|
+
return -1;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Mark a credential as rate-limited. Finds the credential that was most
|
|
454
|
+
* recently used for this provider+session and backs it off.
|
|
455
|
+
*
|
|
456
|
+
* @returns true if another credential is available (caller should retry),
|
|
457
|
+
* false if all credentials for this provider are backed off.
|
|
458
|
+
*/
|
|
459
|
+
markUsageLimitReached(provider, sessionId, options) {
|
|
460
|
+
const credentials = this.getCredentialsForProvider(provider);
|
|
461
|
+
if (credentials.length === 0)
|
|
462
|
+
return false;
|
|
463
|
+
const errorType = options?.errorType ?? "rate_limit";
|
|
464
|
+
const backoffMs = getBackoffDuration(errorType);
|
|
465
|
+
// Determine which credential was just used (same logic as selectCredentialIndex
|
|
466
|
+
// but without incrementing round-robin)
|
|
467
|
+
let usedIndex;
|
|
468
|
+
if (credentials.length === 1) {
|
|
469
|
+
usedIndex = 0;
|
|
470
|
+
}
|
|
471
|
+
else if (sessionId) {
|
|
472
|
+
usedIndex = hashString(sessionId) % credentials.length;
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
// Round-robin was already incremented in getApiKey, so the last-used
|
|
476
|
+
// index is (current - 1). Note: in a concurrent scenario where another
|
|
477
|
+
// getApiKey call fires between the original request and this backoff call,
|
|
478
|
+
// we may back off the wrong credential index. This is acceptable because:
|
|
479
|
+
// (a) pi runs single-threaded event loop, (b) backing off the wrong key
|
|
480
|
+
// is safe — it self-heals when the backoff expires.
|
|
481
|
+
const current = this.providerRoundRobinIndex.get(provider) ?? 0;
|
|
482
|
+
usedIndex = ((current - 1) % credentials.length + credentials.length) % credentials.length;
|
|
483
|
+
}
|
|
484
|
+
// Set backoff for this credential
|
|
485
|
+
let providerBackoff = this.credentialBackoff.get(provider);
|
|
486
|
+
if (!providerBackoff) {
|
|
487
|
+
providerBackoff = new Map();
|
|
488
|
+
this.credentialBackoff.set(provider, providerBackoff);
|
|
489
|
+
}
|
|
490
|
+
providerBackoff.set(usedIndex, Date.now() + backoffMs);
|
|
491
|
+
// Check if any credential is still available
|
|
492
|
+
for (let i = 0; i < credentials.length; i++) {
|
|
493
|
+
if (!this.isCredentialBackedOff(provider, i)) {
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
307
499
|
/**
|
|
308
500
|
* Refresh OAuth token with backend locking to prevent race conditions.
|
|
309
501
|
* Multiple pi instances may try to refresh simultaneously when tokens expire.
|
|
@@ -317,8 +509,10 @@ export class AuthStorage {
|
|
|
317
509
|
const currentData = this.parseStorageData(current);
|
|
318
510
|
this.data = currentData;
|
|
319
511
|
this.loadError = null;
|
|
320
|
-
|
|
321
|
-
|
|
512
|
+
// Find the OAuth credential for this provider
|
|
513
|
+
const creds = this.getCredentialsForProvider(providerId);
|
|
514
|
+
const cred = creds.find((c) => c.type === "oauth");
|
|
515
|
+
if (!cred || cred.type !== "oauth") {
|
|
322
516
|
return { result: null };
|
|
323
517
|
}
|
|
324
518
|
if (Date.now() < cred.expires) {
|
|
@@ -326,17 +520,28 @@ export class AuthStorage {
|
|
|
326
520
|
}
|
|
327
521
|
const oauthCreds = {};
|
|
328
522
|
for (const [key, value] of Object.entries(currentData)) {
|
|
329
|
-
|
|
330
|
-
|
|
523
|
+
const first = Array.isArray(value) ? value.find((c) => c.type === "oauth") : value;
|
|
524
|
+
if (first?.type === "oauth") {
|
|
525
|
+
oauthCreds[key] = first;
|
|
331
526
|
}
|
|
332
527
|
}
|
|
333
528
|
const refreshed = await getOAuthApiKey(providerId, oauthCreds);
|
|
334
529
|
if (!refreshed) {
|
|
335
530
|
return { result: null };
|
|
336
531
|
}
|
|
532
|
+
// Update the OAuth credential in-place within the array
|
|
533
|
+
const existingEntry = currentData[providerId];
|
|
534
|
+
const newOAuthCred = { type: "oauth", ...refreshed.newCredentials };
|
|
535
|
+
let updatedEntry;
|
|
536
|
+
if (Array.isArray(existingEntry)) {
|
|
537
|
+
updatedEntry = existingEntry.map((c) => (c.type === "oauth" ? newOAuthCred : c));
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
updatedEntry = newOAuthCred;
|
|
541
|
+
}
|
|
337
542
|
const merged = {
|
|
338
543
|
...currentData,
|
|
339
|
-
[providerId]:
|
|
544
|
+
[providerId]: updatedEntry,
|
|
340
545
|
};
|
|
341
546
|
this.data = merged;
|
|
342
547
|
this.loadError = null;
|
|
@@ -345,59 +550,65 @@ export class AuthStorage {
|
|
|
345
550
|
return result;
|
|
346
551
|
}
|
|
347
552
|
/**
|
|
348
|
-
*
|
|
349
|
-
* Priority:
|
|
350
|
-
* 1. Runtime override (CLI --api-key)
|
|
351
|
-
* 2. API key from auth.json
|
|
352
|
-
* 3. OAuth token from auth.json (auto-refreshed with locking)
|
|
353
|
-
* 4. Environment variable
|
|
354
|
-
* 5. Fallback resolver (models.json custom providers)
|
|
553
|
+
* Resolve an API key from a single credential.
|
|
355
554
|
*/
|
|
356
|
-
async
|
|
357
|
-
|
|
358
|
-
const runtimeKey = this.runtimeOverrides.get(providerId);
|
|
359
|
-
if (runtimeKey) {
|
|
360
|
-
return runtimeKey;
|
|
361
|
-
}
|
|
362
|
-
const cred = this.data[providerId];
|
|
363
|
-
if (cred?.type === "api_key") {
|
|
555
|
+
async resolveCredentialApiKey(providerId, cred) {
|
|
556
|
+
if (cred.type === "api_key") {
|
|
364
557
|
return resolveConfigValue(cred.key);
|
|
365
558
|
}
|
|
366
|
-
if (cred
|
|
559
|
+
if (cred.type === "oauth") {
|
|
367
560
|
const provider = getOAuthProvider(providerId);
|
|
368
|
-
if (!provider)
|
|
369
|
-
// Unknown OAuth provider, can't get API key
|
|
561
|
+
if (!provider)
|
|
370
562
|
return undefined;
|
|
371
|
-
}
|
|
372
|
-
// Check if token needs refresh
|
|
373
563
|
const needsRefresh = Date.now() >= cred.expires;
|
|
374
564
|
if (needsRefresh) {
|
|
375
|
-
// Use locked refresh to prevent race conditions
|
|
376
565
|
try {
|
|
377
566
|
const result = await this.refreshOAuthTokenWithLock(providerId);
|
|
378
|
-
if (result)
|
|
567
|
+
if (result)
|
|
379
568
|
return result.apiKey;
|
|
380
|
-
}
|
|
381
569
|
}
|
|
382
570
|
catch (error) {
|
|
383
571
|
this.recordError(error);
|
|
384
|
-
// Refresh failed - re-read file to check if another instance succeeded
|
|
385
572
|
this.reload();
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return provider.getApiKey(
|
|
573
|
+
const updatedCreds = this.getCredentialsForProvider(providerId);
|
|
574
|
+
const updatedOAuth = updatedCreds.find((c) => c.type === "oauth");
|
|
575
|
+
if (updatedOAuth?.type === "oauth" && Date.now() < updatedOAuth.expires) {
|
|
576
|
+
return provider.getApiKey(updatedOAuth);
|
|
390
577
|
}
|
|
391
|
-
// Refresh truly failed - return undefined so model discovery skips this provider
|
|
392
|
-
// User can /login to re-authenticate (credentials preserved for retry)
|
|
393
578
|
return undefined;
|
|
394
579
|
}
|
|
395
580
|
}
|
|
396
581
|
else {
|
|
397
|
-
// Token not expired, use current access token
|
|
398
582
|
return provider.getApiKey(cred);
|
|
399
583
|
}
|
|
400
584
|
}
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get API key for a provider.
|
|
589
|
+
* Priority:
|
|
590
|
+
* 1. Runtime override (CLI --api-key)
|
|
591
|
+
* 2. Credential(s) from auth.json (with round-robin / session-sticky selection)
|
|
592
|
+
* 3. Environment variable
|
|
593
|
+
* 4. Fallback resolver (models.json custom providers)
|
|
594
|
+
*
|
|
595
|
+
* @param providerId - The provider to get an API key for
|
|
596
|
+
* @param sessionId - Optional session ID for sticky credential selection
|
|
597
|
+
*/
|
|
598
|
+
async getApiKey(providerId, sessionId) {
|
|
599
|
+
// Runtime override takes highest priority
|
|
600
|
+
const runtimeKey = this.runtimeOverrides.get(providerId);
|
|
601
|
+
if (runtimeKey) {
|
|
602
|
+
return runtimeKey;
|
|
603
|
+
}
|
|
604
|
+
const credentials = this.getCredentialsForProvider(providerId);
|
|
605
|
+
if (credentials.length > 0) {
|
|
606
|
+
const index = this.selectCredentialIndex(providerId, credentials, sessionId);
|
|
607
|
+
if (index >= 0) {
|
|
608
|
+
return this.resolveCredentialApiKey(providerId, credentials[index]);
|
|
609
|
+
}
|
|
610
|
+
// All credentials backed off - fall through to env/fallback
|
|
611
|
+
}
|
|
401
612
|
// Fall back to environment variable
|
|
402
613
|
const envKey = getEnvApiKey(providerId);
|
|
403
614
|
if (envKey)
|