@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
package/src/cli.ts
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import * as readline from "node:readline";
|
|
3
|
+
import { AuthCredentialStore } from "./auth-storage";
|
|
4
|
+
import { getOAuthProviders } from "./utils/oauth";
|
|
5
|
+
import { loginAnthropic } from "./utils/oauth/anthropic";
|
|
6
|
+
import { loginCursor } from "./utils/oauth/cursor";
|
|
7
|
+
import { loginGitHubCopilot } from "./utils/oauth/github-copilot";
|
|
8
|
+
import { loginAntigravity } from "./utils/oauth/google-antigravity";
|
|
9
|
+
import { loginGeminiCli } from "./utils/oauth/google-gemini-cli";
|
|
10
|
+
import { loginKagi } from "./utils/oauth/kagi";
|
|
11
|
+
import { loginKilo } from "./utils/oauth/kilo";
|
|
12
|
+
import { loginKimi } from "./utils/oauth/kimi";
|
|
13
|
+
import { loginMiniMaxCode, loginMiniMaxCodeCn } from "./utils/oauth/minimax-code";
|
|
14
|
+
import { loginNanoGPT } from "./utils/oauth/nanogpt";
|
|
15
|
+
import { loginOpenAICodex } from "./utils/oauth/openai-codex";
|
|
16
|
+
import { loginParallel } from "./utils/oauth/parallel";
|
|
17
|
+
import { loginTavily } from "./utils/oauth/tavily";
|
|
18
|
+
import type { OAuthCredentials, OAuthProvider } from "./utils/oauth/types";
|
|
19
|
+
import { loginZai } from "./utils/oauth/zai";
|
|
20
|
+
import { loginZenMux } from "./utils/oauth/zenmux";
|
|
21
|
+
|
|
22
|
+
const PROVIDERS = getOAuthProviders();
|
|
23
|
+
|
|
24
|
+
function prompt(rl: readline.Interface, question: string): Promise<string> {
|
|
25
|
+
const { promise, resolve, reject } = Promise.withResolvers<string>();
|
|
26
|
+
const input = process.stdin as NodeJS.ReadStream;
|
|
27
|
+
const supportsRawMode = input.isTTY && typeof input.setRawMode === "function";
|
|
28
|
+
const wasRaw = supportsRawMode ? input.isRaw : false;
|
|
29
|
+
let settled = false;
|
|
30
|
+
|
|
31
|
+
const cleanup = () => {
|
|
32
|
+
rl.off("SIGINT", onSigint);
|
|
33
|
+
if (supportsRawMode) {
|
|
34
|
+
input.off("keypress", onKeypress);
|
|
35
|
+
input.setRawMode?.(wasRaw);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const finish = (result: () => void) => {
|
|
40
|
+
if (settled) return;
|
|
41
|
+
settled = true;
|
|
42
|
+
cleanup();
|
|
43
|
+
result();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const cancel = () => {
|
|
47
|
+
finish(() => reject(new Error("Login cancelled")));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const onSigint = () => {
|
|
51
|
+
cancel();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const onKeypress = (_str: string, key: readline.Key) => {
|
|
55
|
+
if (key.name === "escape" || (key.ctrl && key.name === "c")) {
|
|
56
|
+
cancel();
|
|
57
|
+
rl.close();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (supportsRawMode) {
|
|
62
|
+
readline.emitKeypressEvents(input, rl);
|
|
63
|
+
input.setRawMode(true);
|
|
64
|
+
input.on("keypress", onKeypress);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
rl.once("SIGINT", onSigint);
|
|
68
|
+
rl.question(question, answer => {
|
|
69
|
+
finish(() => resolve(answer));
|
|
70
|
+
});
|
|
71
|
+
return promise;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function login(provider: OAuthProvider): Promise<void> {
|
|
75
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
76
|
+
|
|
77
|
+
const promptFn = (msg: string) => prompt(rl, `${msg} `);
|
|
78
|
+
const storage = await AuthCredentialStore.open();
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
let credentials: OAuthCredentials;
|
|
82
|
+
|
|
83
|
+
switch (provider) {
|
|
84
|
+
case "anthropic":
|
|
85
|
+
credentials = await loginAnthropic({
|
|
86
|
+
onAuth(info) {
|
|
87
|
+
const { url } = info;
|
|
88
|
+
console.log(`\nOpen this URL in your browser:\n${url}\n`);
|
|
89
|
+
},
|
|
90
|
+
onProgress(message) {
|
|
91
|
+
console.log(message);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case "github-copilot":
|
|
97
|
+
credentials = await loginGitHubCopilot({
|
|
98
|
+
onAuth(url, instructions) {
|
|
99
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
100
|
+
if (instructions) console.log(instructions);
|
|
101
|
+
console.log();
|
|
102
|
+
},
|
|
103
|
+
async onPrompt(p) {
|
|
104
|
+
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case "google-gemini-cli":
|
|
110
|
+
credentials = await loginGeminiCli({
|
|
111
|
+
onAuth(info) {
|
|
112
|
+
const { url, instructions } = info;
|
|
113
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
114
|
+
if (instructions) console.log(instructions);
|
|
115
|
+
console.log();
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case "google-antigravity":
|
|
121
|
+
credentials = await loginAntigravity({
|
|
122
|
+
onAuth(info) {
|
|
123
|
+
const { url, instructions } = info;
|
|
124
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
125
|
+
if (instructions) console.log(instructions);
|
|
126
|
+
console.log();
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
break;
|
|
130
|
+
case "openai-codex":
|
|
131
|
+
credentials = await loginOpenAICodex({
|
|
132
|
+
onAuth(info) {
|
|
133
|
+
const { url, instructions } = info;
|
|
134
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
135
|
+
if (instructions) console.log(instructions);
|
|
136
|
+
console.log();
|
|
137
|
+
},
|
|
138
|
+
async onPrompt(p) {
|
|
139
|
+
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case "kimi-code":
|
|
145
|
+
credentials = await loginKimi({
|
|
146
|
+
onAuth(info) {
|
|
147
|
+
const { url, instructions } = info;
|
|
148
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
149
|
+
if (instructions) console.log(instructions);
|
|
150
|
+
console.log();
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
break;
|
|
154
|
+
case "kilo":
|
|
155
|
+
credentials = await loginKilo({
|
|
156
|
+
onAuth(info) {
|
|
157
|
+
const { url, instructions } = info;
|
|
158
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
159
|
+
if (instructions) console.log(instructions);
|
|
160
|
+
console.log();
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
break;
|
|
164
|
+
case "kagi": {
|
|
165
|
+
const apiKey = await loginKagi({
|
|
166
|
+
onAuth(info) {
|
|
167
|
+
const { url, instructions } = info;
|
|
168
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
169
|
+
if (instructions) console.log(instructions);
|
|
170
|
+
console.log();
|
|
171
|
+
},
|
|
172
|
+
onPrompt(p) {
|
|
173
|
+
return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
storage.saveApiKey(provider, apiKey);
|
|
177
|
+
console.log(`\nAPI key saved to ~/.xcsh/agent/agent.db`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
case "tavily": {
|
|
181
|
+
const apiKey = await loginTavily({
|
|
182
|
+
onAuth(info) {
|
|
183
|
+
const { url, instructions } = info;
|
|
184
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
185
|
+
if (instructions) console.log(instructions);
|
|
186
|
+
console.log();
|
|
187
|
+
},
|
|
188
|
+
onPrompt(p) {
|
|
189
|
+
return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
storage.saveApiKey(provider, apiKey);
|
|
193
|
+
console.log(`\nAPI key saved to ~/.xcsh/agent/agent.db`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
case "parallel": {
|
|
197
|
+
const apiKey = await loginParallel({
|
|
198
|
+
onAuth(info) {
|
|
199
|
+
const { url, instructions } = info;
|
|
200
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
201
|
+
if (instructions) console.log(instructions);
|
|
202
|
+
console.log();
|
|
203
|
+
},
|
|
204
|
+
onPrompt(p) {
|
|
205
|
+
return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
storage.saveApiKey(provider, apiKey);
|
|
209
|
+
console.log(`\nAPI key saved to ~/.xcsh/agent/agent.db`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
case "cursor":
|
|
214
|
+
credentials = await loginCursor(
|
|
215
|
+
url => {
|
|
216
|
+
console.log(`\nOpen this URL in your browser:\n${url}\n`);
|
|
217
|
+
},
|
|
218
|
+
() => {
|
|
219
|
+
console.log("Waiting for browser authentication...");
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
case "zai": {
|
|
225
|
+
const apiKey = await loginZai({
|
|
226
|
+
onAuth(info) {
|
|
227
|
+
const { url, instructions } = info;
|
|
228
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
229
|
+
if (instructions) console.log(instructions);
|
|
230
|
+
console.log();
|
|
231
|
+
},
|
|
232
|
+
onPrompt(p) {
|
|
233
|
+
return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
storage.saveApiKey(provider, apiKey);
|
|
237
|
+
console.log(`\nAPI key saved to ~/.xcsh/agent/agent.db`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
case "nanogpt": {
|
|
242
|
+
const apiKey = await loginNanoGPT({
|
|
243
|
+
onAuth(info) {
|
|
244
|
+
const { url, instructions } = info;
|
|
245
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
246
|
+
if (instructions) console.log(instructions);
|
|
247
|
+
console.log();
|
|
248
|
+
},
|
|
249
|
+
onPrompt(p) {
|
|
250
|
+
return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
storage.saveApiKey(provider, apiKey);
|
|
254
|
+
console.log(`\nAPI key saved to ~/.xcsh/agent/agent.db`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case "zenmux": {
|
|
259
|
+
const apiKey = await loginZenMux({
|
|
260
|
+
onAuth(info) {
|
|
261
|
+
const { url, instructions } = info;
|
|
262
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
263
|
+
if (instructions) console.log(instructions);
|
|
264
|
+
console.log();
|
|
265
|
+
},
|
|
266
|
+
onPrompt(p) {
|
|
267
|
+
return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
storage.saveApiKey(provider, apiKey);
|
|
271
|
+
console.log(`\nAPI key saved to ~/.xcsh/agent/agent.db`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
case "minimax-code": {
|
|
275
|
+
const apiKey = await loginMiniMaxCode({
|
|
276
|
+
onAuth(info) {
|
|
277
|
+
const { url, instructions } = info;
|
|
278
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
279
|
+
if (instructions) console.log(instructions);
|
|
280
|
+
console.log();
|
|
281
|
+
},
|
|
282
|
+
onPrompt(p) {
|
|
283
|
+
return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
storage.saveApiKey(provider, apiKey);
|
|
287
|
+
console.log(`\nAPI key saved to ~/.xcsh/agent/agent.db`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case "minimax-code-cn": {
|
|
292
|
+
const apiKey = await loginMiniMaxCodeCn({
|
|
293
|
+
onAuth(info) {
|
|
294
|
+
const { url, instructions } = info;
|
|
295
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
296
|
+
if (instructions) console.log(instructions);
|
|
297
|
+
console.log();
|
|
298
|
+
},
|
|
299
|
+
onPrompt(p) {
|
|
300
|
+
return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
storage.saveApiKey(provider, apiKey);
|
|
304
|
+
console.log(`\nAPI key saved to ~/.xcsh/agent/agent.db`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
default:
|
|
309
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
storage.saveOAuth(provider, credentials);
|
|
313
|
+
|
|
314
|
+
console.log(`\nCredentials saved to ~/.xcsh/agent/agent.db`);
|
|
315
|
+
} finally {
|
|
316
|
+
storage.close();
|
|
317
|
+
rl.close();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function main(): Promise<void> {
|
|
322
|
+
const args = process.argv.slice(2);
|
|
323
|
+
const command = args[0];
|
|
324
|
+
|
|
325
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
326
|
+
console.log(`Usage: bunx @f5xc-salesdemos/pi-ai <command> [provider]
|
|
327
|
+
|
|
328
|
+
Commands:
|
|
329
|
+
login [provider] Login to a provider
|
|
330
|
+
logout [provider] Logout from a provider
|
|
331
|
+
status Show logged-in providers
|
|
332
|
+
list List available providers
|
|
333
|
+
|
|
334
|
+
Providers:
|
|
335
|
+
anthropic Anthropic (Claude Pro/Max)
|
|
336
|
+
github-copilot GitHub Copilot
|
|
337
|
+
google-gemini-cli Google Gemini CLI
|
|
338
|
+
google-antigravity Antigravity (Gemini 3, Claude, GPT-OSS)
|
|
339
|
+
openai-codex OpenAI Codex (ChatGPT Plus/Pro)
|
|
340
|
+
kimi-code Kimi Code
|
|
341
|
+
kilo Kilo Gateway
|
|
342
|
+
kagi Kagi
|
|
343
|
+
tavily Tavily
|
|
344
|
+
zai Z.AI (GLM Coding Plan)
|
|
345
|
+
nanogpt NanoGPT
|
|
346
|
+
minimax-code MiniMax Coding Plan (International)
|
|
347
|
+
minimax-code-cn MiniMax Coding Plan (China)
|
|
348
|
+
cursor Cursor (Claude, GPT, etc.)
|
|
349
|
+
zenmux ZenMux
|
|
350
|
+
|
|
351
|
+
Examples:
|
|
352
|
+
bunx @f5xc-salesdemos/pi-ai login # interactive provider selection
|
|
353
|
+
bunx @f5xc-salesdemos/pi-ai login anthropic # login to specific provider
|
|
354
|
+
bunx @f5xc-salesdemos/pi-ai logout anthropic # logout from specific provider
|
|
355
|
+
bunx @f5xc-salesdemos/pi-ai status # show logged-in providers
|
|
356
|
+
bunx @f5xc-salesdemos/pi-ai list # list providers
|
|
357
|
+
`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (command === "status") {
|
|
362
|
+
const storage = await AuthCredentialStore.open();
|
|
363
|
+
try {
|
|
364
|
+
const providers = storage.listProviders();
|
|
365
|
+
if (providers.length === 0) {
|
|
366
|
+
console.log("No credentials stored.");
|
|
367
|
+
console.log(`Use 'bunx @f5xc-salesdemos/pi-ai login' to authenticate.`);
|
|
368
|
+
} else {
|
|
369
|
+
console.log("Logged-in providers:\n");
|
|
370
|
+
for (const provider of providers) {
|
|
371
|
+
const oauth = storage.getOAuth(provider);
|
|
372
|
+
if (oauth) {
|
|
373
|
+
const expires = new Date(oauth.expires);
|
|
374
|
+
const expired = Date.now() >= oauth.expires;
|
|
375
|
+
const status = expired ? "(expired)" : `(expires ${expires.toLocaleString()})`;
|
|
376
|
+
console.log(` ${provider.padEnd(20)} ${status}`);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const apiKey = storage.getApiKey(provider);
|
|
380
|
+
if (apiKey) {
|
|
381
|
+
console.log(` ${provider.padEnd(20)} (api key)`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} finally {
|
|
386
|
+
storage.close();
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (command === "list") {
|
|
392
|
+
console.log("Available providers:\n");
|
|
393
|
+
for (const p of PROVIDERS) {
|
|
394
|
+
console.log(` ${p.id.padEnd(20)} ${p.name}`);
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (command === "logout") {
|
|
400
|
+
let provider = args[1] as OAuthProvider | undefined;
|
|
401
|
+
const storage = await AuthCredentialStore.open();
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
if (!provider) {
|
|
405
|
+
const providers = storage.listProviders();
|
|
406
|
+
if (providers.length === 0) {
|
|
407
|
+
console.log("No credentials stored.");
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
412
|
+
console.log("Select a provider to logout:\n");
|
|
413
|
+
for (let i = 0; i < providers.length; i++) {
|
|
414
|
+
console.log(` ${i + 1}. ${providers[i]}`);
|
|
415
|
+
}
|
|
416
|
+
console.log();
|
|
417
|
+
|
|
418
|
+
const choice = await prompt(rl, `Enter number (1-${providers.length}): `);
|
|
419
|
+
rl.close();
|
|
420
|
+
|
|
421
|
+
const index = parseInt(choice, 10) - 1;
|
|
422
|
+
if (index < 0 || index >= providers.length) {
|
|
423
|
+
console.error("Invalid selection");
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
provider = providers[index] as OAuthProvider;
|
|
427
|
+
}
|
|
428
|
+
if (!provider) {
|
|
429
|
+
console.error("No provider selected");
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const oauth = storage.getOAuth(provider);
|
|
434
|
+
const apiKey = storage.getApiKey(provider);
|
|
435
|
+
if (!oauth && !apiKey) {
|
|
436
|
+
console.error(`Not logged in to ${provider}`);
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
storage.deleteProvider(provider);
|
|
441
|
+
console.log(`Logged out from ${provider}`);
|
|
442
|
+
} finally {
|
|
443
|
+
storage.close();
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (command === "login") {
|
|
449
|
+
let provider = args[1] as OAuthProvider | undefined;
|
|
450
|
+
|
|
451
|
+
if (!provider) {
|
|
452
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
453
|
+
console.log("Select a provider:\n");
|
|
454
|
+
for (let i = 0; i < PROVIDERS.length; i++) {
|
|
455
|
+
console.log(` ${i + 1}. ${PROVIDERS[i].name}`);
|
|
456
|
+
}
|
|
457
|
+
console.log();
|
|
458
|
+
|
|
459
|
+
const choice = await prompt(rl, `Enter number (1-${PROVIDERS.length}): `);
|
|
460
|
+
rl.close();
|
|
461
|
+
|
|
462
|
+
const index = parseInt(choice, 10) - 1;
|
|
463
|
+
if (index < 0 || index >= PROVIDERS.length) {
|
|
464
|
+
console.error("Invalid selection");
|
|
465
|
+
process.exit(1);
|
|
466
|
+
}
|
|
467
|
+
provider = PROVIDERS[index].id as OAuthProvider;
|
|
468
|
+
}
|
|
469
|
+
if (!provider) {
|
|
470
|
+
console.error("No provider selected");
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (!PROVIDERS.some(p => p.id === provider)) {
|
|
475
|
+
console.error(`Unknown provider: ${provider}`);
|
|
476
|
+
console.error(`Use 'bunx @f5xc-salesdemos/pi-ai list' to see available providers`);
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
console.log(`Logging in to ${provider}…`);
|
|
481
|
+
await login(provider);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
console.error(`Unknown command: ${command}`);
|
|
486
|
+
console.error(`Use 'bunx @f5xc-salesdemos/pi-ai --help' for usage`);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
main().catch(err => {
|
|
491
|
+
console.error("Error:", err.message);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type { Static, TSchema } from "@sinclair/typebox";
|
|
2
|
+
export { Type } from "@sinclair/typebox";
|
|
3
|
+
export * from "./api-registry";
|
|
4
|
+
export * from "./auth-storage";
|
|
5
|
+
export * from "./model-cache";
|
|
6
|
+
export * from "./model-manager";
|
|
7
|
+
export * from "./model-thinking";
|
|
8
|
+
export * from "./models";
|
|
9
|
+
export * from "./provider-details";
|
|
10
|
+
export * from "./provider-models";
|
|
11
|
+
export * from "./providers/anthropic";
|
|
12
|
+
export * from "./providers/azure-openai-responses";
|
|
13
|
+
export * from "./providers/cursor";
|
|
14
|
+
export * from "./providers/gitlab-duo";
|
|
15
|
+
export * from "./providers/google";
|
|
16
|
+
export * from "./providers/google-gemini-cli";
|
|
17
|
+
export * from "./providers/google-vertex";
|
|
18
|
+
export * from "./providers/kimi";
|
|
19
|
+
export type { OpenAICodexResponsesOptions } from "./providers/openai-codex-responses";
|
|
20
|
+
export * from "./providers/openai-completions";
|
|
21
|
+
export * from "./providers/openai-responses";
|
|
22
|
+
export * from "./providers/synthetic";
|
|
23
|
+
export * from "./rate-limit-utils";
|
|
24
|
+
export * from "./stream";
|
|
25
|
+
export * from "./types";
|
|
26
|
+
export * from "./usage";
|
|
27
|
+
export * from "./usage/claude";
|
|
28
|
+
export * from "./usage/gemini";
|
|
29
|
+
export * from "./usage/github-copilot";
|
|
30
|
+
export * from "./usage/google-antigravity";
|
|
31
|
+
export * from "./usage/kimi";
|
|
32
|
+
export * from "./usage/minimax-code";
|
|
33
|
+
export * from "./usage/openai-codex";
|
|
34
|
+
export * from "./usage/zai";
|
|
35
|
+
export * from "./utils/anthropic-auth";
|
|
36
|
+
export * from "./utils/discovery";
|
|
37
|
+
export * from "./utils/event-stream";
|
|
38
|
+
export * from "./utils/oauth";
|
|
39
|
+
export * from "./utils/overflow";
|
|
40
|
+
export * from "./utils/retry";
|
|
41
|
+
export * from "./utils/schema";
|
|
42
|
+
export * from "./utils/validation";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed model cache for atomic cross-process access.
|
|
3
|
+
* Replaces per-provider JSON files with a single cache.db.
|
|
4
|
+
*/
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
import { getModelDbPath } from "@f5xc-salesdemos/pi-utils";
|
|
7
|
+
import type { Api, Model } from "./types";
|
|
8
|
+
|
|
9
|
+
const CACHE_SCHEMA_VERSION = 2;
|
|
10
|
+
|
|
11
|
+
interface CacheRow {
|
|
12
|
+
provider_id: string;
|
|
13
|
+
version: number;
|
|
14
|
+
updated_at: number;
|
|
15
|
+
authoritative: number;
|
|
16
|
+
models: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface CacheEntry<TApi extends Api = Api> {
|
|
20
|
+
models: Model<TApi>[];
|
|
21
|
+
fresh: boolean;
|
|
22
|
+
authoritative: boolean;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let sharedDb: Database | null = null;
|
|
27
|
+
let sharedDbPath: string | null = null;
|
|
28
|
+
|
|
29
|
+
function getDb(dbPath?: string): Database {
|
|
30
|
+
const resolvedPath = dbPath ?? getModelDbPath();
|
|
31
|
+
if (sharedDb && sharedDbPath === resolvedPath) {
|
|
32
|
+
return sharedDb;
|
|
33
|
+
}
|
|
34
|
+
if (sharedDb) {
|
|
35
|
+
sharedDb.close();
|
|
36
|
+
}
|
|
37
|
+
const db = new Database(resolvedPath, { create: true });
|
|
38
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
39
|
+
db.run("PRAGMA busy_timeout = 3000");
|
|
40
|
+
db.run(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS model_cache (
|
|
42
|
+
provider_id TEXT PRIMARY KEY,
|
|
43
|
+
version INTEGER NOT NULL,
|
|
44
|
+
updated_at INTEGER NOT NULL,
|
|
45
|
+
authoritative INTEGER NOT NULL DEFAULT 0,
|
|
46
|
+
models TEXT NOT NULL
|
|
47
|
+
)
|
|
48
|
+
`);
|
|
49
|
+
sharedDb = db;
|
|
50
|
+
sharedDbPath = resolvedPath;
|
|
51
|
+
return db;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readModelCache<TApi extends Api>(
|
|
55
|
+
providerId: string,
|
|
56
|
+
ttlMs: number,
|
|
57
|
+
now: () => number,
|
|
58
|
+
dbPath?: string,
|
|
59
|
+
): CacheEntry<TApi> | null {
|
|
60
|
+
try {
|
|
61
|
+
const db = getDb(dbPath);
|
|
62
|
+
const row = db.query<CacheRow, [string]>("SELECT * FROM model_cache WHERE provider_id = ?").get(providerId);
|
|
63
|
+
if (!row || row.version !== CACHE_SCHEMA_VERSION) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const models = JSON.parse(row.models) as Model<TApi>[];
|
|
67
|
+
const ageMs = now() - row.updated_at;
|
|
68
|
+
const fresh = Number.isFinite(ageMs) && ageMs >= 0 && ageMs <= ttlMs;
|
|
69
|
+
return {
|
|
70
|
+
models,
|
|
71
|
+
fresh,
|
|
72
|
+
authoritative: row.authoritative === 1,
|
|
73
|
+
updatedAt: row.updated_at,
|
|
74
|
+
};
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function writeModelCache<TApi extends Api>(
|
|
81
|
+
providerId: string,
|
|
82
|
+
updatedAt: number,
|
|
83
|
+
models: Model<TApi>[],
|
|
84
|
+
authoritative: boolean,
|
|
85
|
+
dbPath?: string,
|
|
86
|
+
): void {
|
|
87
|
+
try {
|
|
88
|
+
const db = getDb(dbPath);
|
|
89
|
+
db.run(
|
|
90
|
+
`INSERT OR REPLACE INTO model_cache (provider_id, version, updated_at, authoritative, models)
|
|
91
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
92
|
+
[providerId, CACHE_SCHEMA_VERSION, updatedAt, authoritative ? 1 : 0, JSON.stringify(models)],
|
|
93
|
+
);
|
|
94
|
+
} catch {
|
|
95
|
+
// Cache writes are best-effort; failures should not break model resolution.
|
|
96
|
+
}
|
|
97
|
+
}
|