@f5-sales-demo/pi-ai 19.51.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/CHANGELOG.md +1997 -0
- package/README.md +1160 -0
- package/package.json +135 -0
- package/src/api-registry.ts +95 -0
- package/src/auth-storage.ts +2694 -0
- package/src/cli.ts +493 -0
- package/src/index.ts +42 -0
- package/src/model-cache.ts +97 -0
- package/src/model-manager.ts +349 -0
- package/src/model-thinking.ts +561 -0
- package/src/models.json +49439 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +81 -0
- package/src/provider-models/descriptors.ts +285 -0
- package/src/provider-models/google.ts +90 -0
- package/src/provider-models/index.ts +4 -0
- package/src/provider-models/openai-compat.ts +2074 -0
- package/src/provider-models/special.ts +106 -0
- package/src/providers/amazon-bedrock.ts +706 -0
- package/src/providers/anthropic.ts +1682 -0
- package/src/providers/azure-openai-responses.ts +391 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2218 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +381 -0
- package/src/providers/google-gemini-cli.ts +1133 -0
- package/src/providers/google-shared.ts +354 -0
- package/src/providers/google-vertex.ts +436 -0
- package/src/providers/google.ts +381 -0
- package/src/providers/kimi.ts +151 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +158 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +2345 -0
- package/src/providers/openai-completions-compat.ts +159 -0
- package/src/providers/openai-completions.ts +1290 -0
- package/src/providers/openai-responses-shared.ts +452 -0
- package/src/providers/openai-responses.ts +519 -0
- package/src/providers/register-builtins.ts +329 -0
- package/src/providers/synthetic.ts +154 -0
- package/src/providers/transform-messages.ts +234 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +728 -0
- package/src/types.ts +546 -0
- package/src/usage/claude.ts +337 -0
- package/src/usage/gemini.ts +248 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +200 -0
- package/src/usage/kimi.ts +286 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +387 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +130 -0
- package/src/utils/abort.ts +36 -0
- package/src/utils/anthropic-auth.ts +293 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +5 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +209 -0
- package/src/utils/http-inspector.ts +165 -0
- package/src/utils/idle-iterator.ts +176 -0
- package/src/utils/json-parse.ts +28 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +134 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +59 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/github-copilot.ts +358 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +275 -0
- package/src/utils/oauth/google-gemini-cli.ts +334 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +512 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +251 -0
- package/src/utils/oauth/litellm.ts +81 -0
- package/src/utils/oauth/lm-studio.ts +40 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +59 -0
- package/src/utils/oauth/nanogpt.ts +51 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +199 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +190 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +200 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +60 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +59 -0
- package/src/utils/oauth/types.ts +89 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/xiaomi.ts +88 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +51 -0
- package/src/utils/overflow.ts +134 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +93 -0
- package/src/utils/schema/CONSTRAINTS.md +160 -0
- package/src/utils/schema/adapt.ts +20 -0
- package/src/utils/schema/compatibility.ts +397 -0
- package/src/utils/schema/dereference.ts +93 -0
- package/src/utils/schema/equality.ts +93 -0
- package/src/utils/schema/fields.ts +147 -0
- package/src/utils/schema/index.ts +9 -0
- package/src/utils/schema/normalize-cca.ts +479 -0
- package/src/utils/schema/sanitize-google.ts +212 -0
- package/src/utils/schema/strict-mode.ts +385 -0
- package/src/utils/schema/types.ts +5 -0
- package/src/utils/tool-choice.ts +81 -0
- package/src/utils/validation.ts +664 -0
- package/src/utils.ts +147 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// High-level API
|
|
3
|
+
// ============================================================================
|
|
4
|
+
import { refreshAnthropicToken } from "./anthropic";
|
|
5
|
+
import { refreshCursorToken } from "./cursor";
|
|
6
|
+
import { refreshGitHubCopilotToken } from "./github-copilot";
|
|
7
|
+
import { refreshGitLabDuoToken } from "./gitlab-duo";
|
|
8
|
+
import { refreshAntigravityToken } from "./google-antigravity";
|
|
9
|
+
import { refreshGoogleCloudToken } from "./google-gemini-cli";
|
|
10
|
+
import { refreshKimiToken } from "./kimi";
|
|
11
|
+
import { refreshOpenAICodexToken } from "./openai-codex";
|
|
12
|
+
import type {
|
|
13
|
+
OAuthCredentials,
|
|
14
|
+
OAuthProvider,
|
|
15
|
+
OAuthProviderId,
|
|
16
|
+
OAuthProviderInfo,
|
|
17
|
+
OAuthProviderInterface,
|
|
18
|
+
} from "./types";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* OAuth credential management for AI providers.
|
|
22
|
+
*
|
|
23
|
+
* This module handles login, token refresh, and credential storage
|
|
24
|
+
* for OAuth-based providers:
|
|
25
|
+
* - Anthropic (Claude Pro/Max)
|
|
26
|
+
* - GitHub Copilot
|
|
27
|
+
* - Google Cloud Code Assist (Gemini CLI)
|
|
28
|
+
* - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
29
|
+
* - Kimi Code
|
|
30
|
+
* - Kilo Gateway
|
|
31
|
+
* - Kagi
|
|
32
|
+
* - Cerebras
|
|
33
|
+
* - Hugging Face Inference
|
|
34
|
+
* - Synthetic
|
|
35
|
+
* - Perplexity (Pro/Max — desktop app extraction or manual cookie)
|
|
36
|
+
* - NVIDIA
|
|
37
|
+
* - NanoGPT
|
|
38
|
+
* - Venice
|
|
39
|
+
* - vLLM
|
|
40
|
+
* - ZenMux
|
|
41
|
+
* - Alibaba Coding Plan
|
|
42
|
+
*/
|
|
43
|
+
// Alibaba Coding Plan
|
|
44
|
+
export { loginAlibabaCodingPlan } from "./alibaba-coding-plan";
|
|
45
|
+
// Anthropic
|
|
46
|
+
export { loginAnthropic, refreshAnthropicToken } from "./anthropic";
|
|
47
|
+
// Cerebras (API key)
|
|
48
|
+
export { loginCerebras } from "./cerebras";
|
|
49
|
+
// Cloudflare AI Gateway (API key)
|
|
50
|
+
export { loginCloudflareAiGateway } from "./cloudflare-ai-gateway";
|
|
51
|
+
// Cursor
|
|
52
|
+
export {
|
|
53
|
+
generateCursorAuthParams,
|
|
54
|
+
isCursorTokenExpiringSoon,
|
|
55
|
+
loginCursor,
|
|
56
|
+
pollCursorAuth,
|
|
57
|
+
refreshCursorToken,
|
|
58
|
+
} from "./cursor";
|
|
59
|
+
// GitHub Copilot
|
|
60
|
+
export {
|
|
61
|
+
getGitHubCopilotBaseUrl,
|
|
62
|
+
loginGitHubCopilot,
|
|
63
|
+
normalizeDomain,
|
|
64
|
+
refreshGitHubCopilotToken,
|
|
65
|
+
} from "./github-copilot";
|
|
66
|
+
// GitLab Duo
|
|
67
|
+
export { loginGitLabDuo, refreshGitLabDuoToken } from "./gitlab-duo";
|
|
68
|
+
// Google Antigravity
|
|
69
|
+
export { loginAntigravity, refreshAntigravityToken } from "./google-antigravity";
|
|
70
|
+
// Google Gemini CLI
|
|
71
|
+
export { loginGeminiCli, refreshGoogleCloudToken } from "./google-gemini-cli";
|
|
72
|
+
// Hugging Face Inference (API key)
|
|
73
|
+
export { loginHuggingface } from "./huggingface";
|
|
74
|
+
// Kagi (API key)
|
|
75
|
+
export { loginKagi } from "./kagi";
|
|
76
|
+
// Kilo Gateway
|
|
77
|
+
export { loginKilo } from "./kilo";
|
|
78
|
+
// Kimi Code
|
|
79
|
+
export { loginKimi, refreshKimiToken } from "./kimi";
|
|
80
|
+
// LiteLLM (API key)
|
|
81
|
+
export {
|
|
82
|
+
type LiteLLMLoginDefaults,
|
|
83
|
+
type LiteLLMLoginOptions,
|
|
84
|
+
type LiteLLMLoginResult,
|
|
85
|
+
loginLiteLLM,
|
|
86
|
+
maskApiKey,
|
|
87
|
+
} from "./litellm";
|
|
88
|
+
// LM Studio (optional API key)
|
|
89
|
+
export { DEFAULT_LOCAL_TOKEN, loginLmStudio } from "./lm-studio";
|
|
90
|
+
// MiniMax Coding Plan (API key)
|
|
91
|
+
export { loginMiniMaxCode, loginMiniMaxCodeCn } from "./minimax-code";
|
|
92
|
+
// Moonshot (API key)
|
|
93
|
+
export { loginMoonshot } from "./moonshot";
|
|
94
|
+
// NanoGPT (API key)
|
|
95
|
+
export { loginNanoGPT } from "./nanogpt";
|
|
96
|
+
// NVIDIA (API key)
|
|
97
|
+
export { loginNvidia } from "./nvidia";
|
|
98
|
+
// Ollama (optional API key)
|
|
99
|
+
export { loginOllama } from "./ollama";
|
|
100
|
+
export type { OpenAICodexLoginOptions } from "./openai-codex";
|
|
101
|
+
// OpenAI Codex (ChatGPT OAuth)
|
|
102
|
+
export { loginOpenAICodex, refreshOpenAICodexToken } from "./openai-codex";
|
|
103
|
+
// OpenCode Zen / OpenCode Go (API key)
|
|
104
|
+
export { loginOpenCode } from "./opencode";
|
|
105
|
+
// Parallel (API key)
|
|
106
|
+
export { loginParallel } from "./parallel";
|
|
107
|
+
// Perplexity
|
|
108
|
+
export { loginPerplexity } from "./perplexity";
|
|
109
|
+
// Qianfan (API key)
|
|
110
|
+
export { loginQianfan } from "./qianfan";
|
|
111
|
+
// Qwen Portal (OAuth token/API key)
|
|
112
|
+
export { loginQwenPortal } from "./qwen-portal";
|
|
113
|
+
// Synthetic (API key)
|
|
114
|
+
export { loginSynthetic } from "./synthetic";
|
|
115
|
+
// Tavily (API key)
|
|
116
|
+
export { loginTavily } from "./tavily";
|
|
117
|
+
// Together (API key)
|
|
118
|
+
export { loginTogether } from "./together";
|
|
119
|
+
export * from "./types";
|
|
120
|
+
// Venice (API key)
|
|
121
|
+
export { loginVenice } from "./venice";
|
|
122
|
+
// Vercel AI Gateway (API key)
|
|
123
|
+
export { loginVercelAiGateway } from "./vercel-ai-gateway";
|
|
124
|
+
// vLLM (API key)
|
|
125
|
+
export { loginVllm } from "./vllm";
|
|
126
|
+
// Xiaomi MiMo (API key)
|
|
127
|
+
export { loginXiaomi } from "./xiaomi";
|
|
128
|
+
// Z.AI (API key)
|
|
129
|
+
export { loginZai } from "./zai";
|
|
130
|
+
// ZenMux (API key)
|
|
131
|
+
export { loginZenMux } from "./zenmux";
|
|
132
|
+
|
|
133
|
+
const builtInOAuthProviders: OAuthProviderInfo[] = [
|
|
134
|
+
{
|
|
135
|
+
id: "anthropic",
|
|
136
|
+
name: "Anthropic (Claude Pro/Max)",
|
|
137
|
+
available: true,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "alibaba-coding-plan",
|
|
141
|
+
name: "Alibaba Coding Plan",
|
|
142
|
+
available: true,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "openai-codex",
|
|
146
|
+
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
147
|
+
available: true,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "gitlab-duo",
|
|
151
|
+
name: "GitLab Duo",
|
|
152
|
+
available: true,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "kimi-code",
|
|
156
|
+
name: "Kimi Code",
|
|
157
|
+
available: true,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: "kilo",
|
|
161
|
+
name: "Kilo Gateway",
|
|
162
|
+
available: true,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "kagi",
|
|
166
|
+
name: "Kagi",
|
|
167
|
+
available: true,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "cerebras",
|
|
171
|
+
name: "Cerebras",
|
|
172
|
+
available: true,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "github-copilot",
|
|
176
|
+
name: "GitHub Copilot",
|
|
177
|
+
available: true,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "google-gemini-cli",
|
|
181
|
+
name: "Google Cloud Code Assist (Gemini CLI)",
|
|
182
|
+
available: true,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: "google-antigravity",
|
|
186
|
+
name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
|
|
187
|
+
available: true,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "cursor",
|
|
191
|
+
name: "Cursor (Claude, GPT, etc.)",
|
|
192
|
+
available: true,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "litellm",
|
|
196
|
+
name: "LiteLLM",
|
|
197
|
+
available: true,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "lm-studio",
|
|
201
|
+
name: "LM Studio (Local OpenAI-compatible)",
|
|
202
|
+
available: true,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "ollama",
|
|
206
|
+
name: "Ollama (Local OpenAI-compatible)",
|
|
207
|
+
available: true,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "huggingface",
|
|
211
|
+
name: "Hugging Face Inference",
|
|
212
|
+
available: true,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: "synthetic",
|
|
216
|
+
name: "Synthetic",
|
|
217
|
+
available: true,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "tavily",
|
|
221
|
+
name: "Tavily",
|
|
222
|
+
available: true,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: "together",
|
|
226
|
+
name: "Together",
|
|
227
|
+
available: true,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "xiaomi",
|
|
231
|
+
name: "Xiaomi MiMo",
|
|
232
|
+
available: true,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: "opencode-zen",
|
|
236
|
+
name: "OpenCode Zen",
|
|
237
|
+
available: true,
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "opencode-go",
|
|
241
|
+
name: "OpenCode Go",
|
|
242
|
+
available: true,
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: "zai",
|
|
246
|
+
name: "Z.AI (GLM Coding Plan)",
|
|
247
|
+
available: true,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "minimax-code",
|
|
251
|
+
name: "MiniMax Coding Plan (International)",
|
|
252
|
+
available: true,
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: "minimax-code-cn",
|
|
256
|
+
name: "MiniMax Coding Plan (China)",
|
|
257
|
+
available: true,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: "moonshot",
|
|
261
|
+
name: "Moonshot (Kimi API)",
|
|
262
|
+
available: true,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "nanogpt",
|
|
266
|
+
name: "NanoGPT",
|
|
267
|
+
available: true,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: "parallel",
|
|
271
|
+
name: "Parallel",
|
|
272
|
+
available: true,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: "perplexity",
|
|
276
|
+
name: "Perplexity (Pro/Max)",
|
|
277
|
+
available: true,
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: "nvidia",
|
|
281
|
+
name: "NVIDIA",
|
|
282
|
+
available: true,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "qwen-portal",
|
|
286
|
+
name: "Qwen Portal",
|
|
287
|
+
available: true,
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: "qianfan",
|
|
291
|
+
name: "Qianfan",
|
|
292
|
+
available: true,
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "venice",
|
|
296
|
+
name: "Venice",
|
|
297
|
+
available: true,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "zenmux",
|
|
301
|
+
name: "ZenMux",
|
|
302
|
+
available: true,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: "vllm",
|
|
306
|
+
name: "vLLM (Local OpenAI-compatible)",
|
|
307
|
+
available: true,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "cloudflare-ai-gateway",
|
|
311
|
+
name: "Cloudflare AI Gateway",
|
|
312
|
+
available: true,
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: "vercel-ai-gateway",
|
|
316
|
+
name: "Vercel AI Gateway",
|
|
317
|
+
available: true,
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
const customOAuthProviders = new Map<string, OAuthProviderInterface>();
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Register a custom OAuth provider.
|
|
325
|
+
*/
|
|
326
|
+
export function registerOAuthProvider(provider: OAuthProviderInterface): void {
|
|
327
|
+
customOAuthProviders.set(provider.id, provider);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get a custom OAuth provider by ID.
|
|
332
|
+
*/
|
|
333
|
+
export function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {
|
|
334
|
+
return customOAuthProviders.get(id);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Remove all custom OAuth providers registered by a source.
|
|
339
|
+
*/
|
|
340
|
+
export function unregisterOAuthProviders(sourceId: string): void {
|
|
341
|
+
for (const [id, provider] of customOAuthProviders.entries()) {
|
|
342
|
+
if (provider.sourceId === sourceId) {
|
|
343
|
+
customOAuthProviders.delete(id);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Refresh token for any OAuth provider.
|
|
350
|
+
* Saves the new credentials and returns the new access token.
|
|
351
|
+
*/
|
|
352
|
+
export async function refreshOAuthToken(
|
|
353
|
+
provider: OAuthProvider,
|
|
354
|
+
credentials: OAuthCredentials,
|
|
355
|
+
): Promise<OAuthCredentials> {
|
|
356
|
+
if (!credentials) {
|
|
357
|
+
throw new Error(`No OAuth credentials found for ${provider}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let newCredentials: OAuthCredentials;
|
|
361
|
+
switch (provider) {
|
|
362
|
+
case "anthropic":
|
|
363
|
+
newCredentials = await refreshAnthropicToken(credentials.refresh);
|
|
364
|
+
break;
|
|
365
|
+
case "github-copilot":
|
|
366
|
+
newCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);
|
|
367
|
+
break;
|
|
368
|
+
case "google-gemini-cli":
|
|
369
|
+
if (!credentials.projectId) {
|
|
370
|
+
throw new Error("Google Cloud credentials missing projectId");
|
|
371
|
+
}
|
|
372
|
+
newCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);
|
|
373
|
+
break;
|
|
374
|
+
case "google-antigravity":
|
|
375
|
+
if (!credentials.projectId) {
|
|
376
|
+
throw new Error("Antigravity credentials missing projectId");
|
|
377
|
+
}
|
|
378
|
+
newCredentials = await refreshAntigravityToken(credentials.refresh, credentials.projectId);
|
|
379
|
+
break;
|
|
380
|
+
case "openai-codex":
|
|
381
|
+
newCredentials = await refreshOpenAICodexToken(credentials.refresh);
|
|
382
|
+
break;
|
|
383
|
+
case "kimi-code":
|
|
384
|
+
newCredentials = await refreshKimiToken(credentials.refresh);
|
|
385
|
+
break;
|
|
386
|
+
case "kilo":
|
|
387
|
+
newCredentials = credentials;
|
|
388
|
+
break;
|
|
389
|
+
case "gitlab-duo":
|
|
390
|
+
newCredentials = await refreshGitLabDuoToken(credentials);
|
|
391
|
+
break;
|
|
392
|
+
case "cursor":
|
|
393
|
+
newCredentials = await refreshCursorToken(credentials.refresh);
|
|
394
|
+
break;
|
|
395
|
+
case "perplexity":
|
|
396
|
+
case "huggingface":
|
|
397
|
+
case "opencode-zen":
|
|
398
|
+
case "opencode-go":
|
|
399
|
+
case "cerebras":
|
|
400
|
+
case "nvidia":
|
|
401
|
+
case "nanogpt":
|
|
402
|
+
case "synthetic":
|
|
403
|
+
case "together":
|
|
404
|
+
case "litellm":
|
|
405
|
+
case "lm-studio":
|
|
406
|
+
case "ollama":
|
|
407
|
+
case "xiaomi":
|
|
408
|
+
case "zai":
|
|
409
|
+
case "qianfan":
|
|
410
|
+
case "venice":
|
|
411
|
+
case "minimax-code":
|
|
412
|
+
case "minimax-code-cn":
|
|
413
|
+
case "moonshot":
|
|
414
|
+
case "kagi":
|
|
415
|
+
case "cloudflare-ai-gateway":
|
|
416
|
+
case "vercel-ai-gateway":
|
|
417
|
+
case "qwen-portal":
|
|
418
|
+
case "zenmux":
|
|
419
|
+
case "vllm":
|
|
420
|
+
// API keys / static bearer tokens don't expire, return as-is
|
|
421
|
+
newCredentials = credentials;
|
|
422
|
+
break;
|
|
423
|
+
default:
|
|
424
|
+
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
425
|
+
}
|
|
426
|
+
return newCredentials;
|
|
427
|
+
}
|
|
428
|
+
function getPerplexityJwtExpiryMs(token: string): number | undefined {
|
|
429
|
+
const parts = token.split(".");
|
|
430
|
+
if (parts.length !== 3) return undefined;
|
|
431
|
+
const payload = parts[1];
|
|
432
|
+
if (!payload) return undefined;
|
|
433
|
+
try {
|
|
434
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as { exp?: unknown };
|
|
435
|
+
if (typeof decoded.exp !== "number" || !Number.isFinite(decoded.exp)) return undefined;
|
|
436
|
+
return decoded.exp * 1000 - 5 * 60_000;
|
|
437
|
+
} catch {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get API key for a provider from OAuth credentials.
|
|
444
|
+
* Automatically refreshes expired tokens.
|
|
445
|
+
*
|
|
446
|
+
* For providers that need credential metadata at request time, returns JSON-encoded credentials
|
|
447
|
+
* plus refresh/expiry metadata for proactive refresh support.
|
|
448
|
+
* @returns API key string, or null if no credentials
|
|
449
|
+
* @throws Error if refresh fails
|
|
450
|
+
*/
|
|
451
|
+
export async function getOAuthApiKey(
|
|
452
|
+
provider: OAuthProvider,
|
|
453
|
+
credentials: Record<string, OAuthCredentials>,
|
|
454
|
+
): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {
|
|
455
|
+
let creds = credentials[provider];
|
|
456
|
+
if (!creds) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (provider === "perplexity") {
|
|
461
|
+
const normalizedExpires =
|
|
462
|
+
creds.expires > 0 && creds.expires < 10_000_000_000 ? creds.expires * 1000 : creds.expires;
|
|
463
|
+
const jwtExpiry = getPerplexityJwtExpiryMs(creds.access);
|
|
464
|
+
const expires = jwtExpiry && jwtExpiry > normalizedExpires ? jwtExpiry : normalizedExpires;
|
|
465
|
+
if (expires !== creds.expires) {
|
|
466
|
+
creds = { ...creds, expires };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// Refresh if expired
|
|
470
|
+
if (Date.now() >= creds.expires) {
|
|
471
|
+
try {
|
|
472
|
+
creds = await refreshOAuthToken(provider, creds);
|
|
473
|
+
} catch (refreshError) {
|
|
474
|
+
if (provider === "perplexity") {
|
|
475
|
+
const jwtExpiry = getPerplexityJwtExpiryMs(creds.access);
|
|
476
|
+
if (jwtExpiry && Date.now() < jwtExpiry) {
|
|
477
|
+
const fallbackCredentials = { ...creds, expires: jwtExpiry };
|
|
478
|
+
return { newCredentials: fallbackCredentials, apiKey: fallbackCredentials.access };
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const reason = refreshError instanceof Error ? refreshError.message : String(refreshError);
|
|
482
|
+
throw new Error(`Failed to refresh OAuth token for ${provider}: ${reason}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// For providers that need request-time credential metadata, return JSON.
|
|
486
|
+
const needsStructuredApiKey =
|
|
487
|
+
provider === "github-copilot" || provider === "google-gemini-cli" || provider === "google-antigravity";
|
|
488
|
+
const apiKey = needsStructuredApiKey
|
|
489
|
+
? JSON.stringify({
|
|
490
|
+
token: creds.access,
|
|
491
|
+
enterpriseUrl: creds.enterpriseUrl,
|
|
492
|
+
projectId: creds.projectId,
|
|
493
|
+
refreshToken: creds.refresh,
|
|
494
|
+
expiresAt: creds.expires,
|
|
495
|
+
email: creds.email,
|
|
496
|
+
accountId: creds.accountId,
|
|
497
|
+
})
|
|
498
|
+
: creds.access;
|
|
499
|
+
return { newCredentials: creds, apiKey };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Get list of OAuth providers.
|
|
504
|
+
*/
|
|
505
|
+
export function getOAuthProviders(): OAuthProviderInfo[] {
|
|
506
|
+
const customProviders = Array.from(customOAuthProviders.values(), provider => ({
|
|
507
|
+
id: provider.id,
|
|
508
|
+
name: provider.name,
|
|
509
|
+
available: true,
|
|
510
|
+
}));
|
|
511
|
+
return [...builtInOAuthProviders, ...customProviders];
|
|
512
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kagi login flow.
|
|
3
|
+
*
|
|
4
|
+
* Kagi web search uses an API key from the account settings page.
|
|
5
|
+
* This is an API key flow:
|
|
6
|
+
* 1. Open browser to Kagi API settings
|
|
7
|
+
* 2. User copies API key
|
|
8
|
+
* 3. User pastes key into CLI
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { OAuthController } from "./types";
|
|
12
|
+
|
|
13
|
+
const AUTH_URL = "https://kagi.com/settings/api";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Login to Kagi.
|
|
17
|
+
*
|
|
18
|
+
* Opens browser to API settings and prompts user to paste their API key.
|
|
19
|
+
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
20
|
+
*/
|
|
21
|
+
export async function loginKagi(options: OAuthController): Promise<string> {
|
|
22
|
+
if (!options.onPrompt) {
|
|
23
|
+
throw new Error("Kagi login requires onPrompt callback");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
options.onAuth?.({
|
|
27
|
+
url: AUTH_URL,
|
|
28
|
+
instructions:
|
|
29
|
+
"Copy your Kagi Search API key from Kagi API settings. Search API access is beta-only; if unavailable, email support@kagi.com.",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const apiKey = await options.onPrompt({
|
|
33
|
+
message: "Paste your Kagi API key",
|
|
34
|
+
placeholder: "KG_...",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (options.signal?.aborted) {
|
|
38
|
+
throw new Error("Login cancelled");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const trimmed = apiKey.trim();
|
|
42
|
+
if (!trimmed) {
|
|
43
|
+
throw new Error("API key is required");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return trimmed;
|
|
47
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
2
|
+
|
|
3
|
+
const KILO_DEVICE_AUTH_BASE_URL = "https://api.kilo.ai/api/device-auth";
|
|
4
|
+
const POLL_INTERVAL_MS = 5000;
|
|
5
|
+
const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
interface KiloDeviceAuthCodeResponse {
|
|
8
|
+
code?: string;
|
|
9
|
+
verificationUrl?: string;
|
|
10
|
+
expiresIn?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface KiloDeviceAuthPollResponse {
|
|
14
|
+
status?: string;
|
|
15
|
+
token?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Login with Kilo Gateway OAuth (device code flow).
|
|
20
|
+
*/
|
|
21
|
+
export async function loginKilo(callbacks: OAuthController): Promise<OAuthCredentials> {
|
|
22
|
+
const initiateResponse = await fetch(`${KILO_DEVICE_AUTH_BASE_URL}/codes`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { "Content-Type": "application/json" },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (!initiateResponse.ok) {
|
|
28
|
+
if (initiateResponse.status === 429) {
|
|
29
|
+
throw new Error("Too many pending authorization requests. Please try again later.");
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Failed to initiate device authorization: ${initiateResponse.status}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const initiateData = (await initiateResponse.json()) as KiloDeviceAuthCodeResponse;
|
|
35
|
+
const userCode = initiateData.code;
|
|
36
|
+
const verificationUrl = initiateData.verificationUrl;
|
|
37
|
+
const expiresInSeconds = initiateData.expiresIn;
|
|
38
|
+
if (!userCode || !verificationUrl || typeof expiresInSeconds !== "number" || expiresInSeconds <= 0) {
|
|
39
|
+
throw new Error("Kilo device authorization response missing required fields");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
callbacks.onAuth?.({
|
|
43
|
+
url: verificationUrl,
|
|
44
|
+
instructions: `Enter code: ${userCode}`,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const deadline = Date.now() + expiresInSeconds * 1000;
|
|
48
|
+
while (Date.now() < deadline) {
|
|
49
|
+
if (callbacks.signal?.aborted) {
|
|
50
|
+
throw new Error("Login cancelled");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const pollResponse = await fetch(`${KILO_DEVICE_AUTH_BASE_URL}/codes/${encodeURIComponent(userCode)}`);
|
|
54
|
+
if (pollResponse.status === 202) {
|
|
55
|
+
await Bun.sleep(POLL_INTERVAL_MS);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (pollResponse.status === 403) {
|
|
59
|
+
throw new Error("Authorization was denied");
|
|
60
|
+
}
|
|
61
|
+
if (pollResponse.status === 410) {
|
|
62
|
+
throw new Error("Authorization code expired. Please try again.");
|
|
63
|
+
}
|
|
64
|
+
if (!pollResponse.ok) {
|
|
65
|
+
throw new Error(`Failed to poll device authorization: ${pollResponse.status}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const pollData = (await pollResponse.json()) as KiloDeviceAuthPollResponse;
|
|
69
|
+
if (pollData.status === "approved" && pollData.token) {
|
|
70
|
+
return {
|
|
71
|
+
refresh: "",
|
|
72
|
+
access: pollData.token,
|
|
73
|
+
expires: Date.now() + ONE_YEAR_MS,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (pollData.status === "denied") {
|
|
77
|
+
throw new Error("Authorization was denied");
|
|
78
|
+
}
|
|
79
|
+
if (pollData.status === "expired") {
|
|
80
|
+
throw new Error("Authorization code expired. Please try again.");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await Bun.sleep(POLL_INTERVAL_MS);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw new Error("Authentication timed out. Please try again.");
|
|
87
|
+
}
|