@oh-my-pi/pi-ai 3.20.0 → 3.34.0
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 +69 -12
- package/package.json +3 -10
- package/src/cli.ts +89 -89
- package/src/index.ts +2 -2
- package/src/models.generated.ts +871 -151
- package/src/models.ts +11 -17
- package/src/providers/anthropic.ts +92 -28
- package/src/providers/google-gemini-cli.ts +268 -133
- package/src/providers/google-shared.ts +48 -5
- package/src/providers/google-vertex.ts +13 -3
- package/src/providers/google.ts +13 -3
- package/src/providers/openai-codex/index.ts +7 -0
- package/src/providers/openai-codex/prompts/codex.ts +26 -59
- package/src/providers/openai-codex/prompts/pi-codex-bridge.ts +38 -31
- package/src/providers/openai-codex/prompts/system-prompt.ts +26 -0
- package/src/providers/openai-codex/request-transformer.ts +38 -203
- package/src/providers/openai-codex-responses.ts +91 -24
- package/src/providers/openai-completions.ts +33 -26
- package/src/providers/openai-responses.ts +1 -1
- package/src/providers/transorm-messages.ts +4 -3
- package/src/stream.ts +34 -25
- package/src/types.ts +21 -4
- package/src/utils/oauth/github-copilot.ts +38 -3
- package/src/utils/oauth/google-antigravity.ts +146 -55
- package/src/utils/oauth/google-gemini-cli.ts +146 -55
- package/src/utils/oauth/index.ts +5 -5
- package/src/utils/oauth/openai-codex.ts +129 -54
- package/src/utils/overflow.ts +1 -1
- package/src/bun-imports.d.ts +0 -14
package/README.md
CHANGED
|
@@ -4,17 +4,61 @@ Unified LLM API with automatic model discovery, provider configuration, token an
|
|
|
4
4
|
|
|
5
5
|
**Note**: This library only includes models that support tool calling (function calling), as this is essential for agentic workflows.
|
|
6
6
|
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Supported Providers](#supported-providers)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Tools](#tools)
|
|
13
|
+
- [Defining Tools](#defining-tools)
|
|
14
|
+
- [Handling Tool Calls](#handling-tool-calls)
|
|
15
|
+
- [Streaming Tool Calls with Partial JSON](#streaming-tool-calls-with-partial-json)
|
|
16
|
+
- [Validating Tool Arguments](#validating-tool-arguments)
|
|
17
|
+
- [Complete Event Reference](#complete-event-reference)
|
|
18
|
+
- [Image Input](#image-input)
|
|
19
|
+
- [Thinking/Reasoning](#thinkingreasoning)
|
|
20
|
+
- [Unified Interface](#unified-interface-streamsimplecompletesimple)
|
|
21
|
+
- [Provider-Specific Options](#provider-specific-options-streamcomplete)
|
|
22
|
+
- [Streaming Thinking Content](#streaming-thinking-content)
|
|
23
|
+
- [Stop Reasons](#stop-reasons)
|
|
24
|
+
- [Error Handling](#error-handling)
|
|
25
|
+
- [Aborting Requests](#aborting-requests)
|
|
26
|
+
- [Continuing After Abort](#continuing-after-abort)
|
|
27
|
+
- [APIs, Models, and Providers](#apis-models-and-providers)
|
|
28
|
+
- [Providers and Models](#providers-and-models)
|
|
29
|
+
- [Querying Providers and Models](#querying-providers-and-models)
|
|
30
|
+
- [Custom Models](#custom-models)
|
|
31
|
+
- [OpenAI Compatibility Settings](#openai-compatibility-settings)
|
|
32
|
+
- [Type Safety](#type-safety)
|
|
33
|
+
- [Cross-Provider Handoffs](#cross-provider-handoffs)
|
|
34
|
+
- [Context Serialization](#context-serialization)
|
|
35
|
+
- [Browser Usage](#browser-usage)
|
|
36
|
+
- [Environment Variables](#environment-variables-nodejs-only)
|
|
37
|
+
- [Checking Environment Variables](#checking-environment-variables)
|
|
38
|
+
- [OAuth Providers](#oauth-providers)
|
|
39
|
+
- [Vertex AI (ADC)](#vertex-ai-adc)
|
|
40
|
+
- [CLI Login](#cli-login)
|
|
41
|
+
- [Programmatic OAuth](#programmatic-oauth)
|
|
42
|
+
- [Login Flow Example](#login-flow-example)
|
|
43
|
+
- [Using OAuth Tokens](#using-oauth-tokens)
|
|
44
|
+
- [Provider Notes](#provider-notes)
|
|
45
|
+
- [License](#license)
|
|
46
|
+
|
|
7
47
|
## Supported Providers
|
|
8
48
|
|
|
9
49
|
- **OpenAI**
|
|
50
|
+
- **OpenAI Codex** (ChatGPT Plus/Pro subscription, requires OAuth, see below)
|
|
10
51
|
- **Anthropic**
|
|
11
52
|
- **Google**
|
|
53
|
+
- **Vertex AI** (Gemini via Vertex AI)
|
|
12
54
|
- **Mistral**
|
|
13
55
|
- **Groq**
|
|
14
56
|
- **Cerebras**
|
|
15
57
|
- **xAI**
|
|
16
58
|
- **OpenRouter**
|
|
17
59
|
- **GitHub Copilot** (requires OAuth, see below)
|
|
60
|
+
- **Google Gemini CLI** (requires OAuth, see below)
|
|
61
|
+
- **Antigravity** (requires OAuth, see below)
|
|
18
62
|
- **Any OpenAI-compatible API**: Ollama, vLLM, LM Studio, etc.
|
|
19
63
|
|
|
20
64
|
## Installation
|
|
@@ -121,6 +165,7 @@ for (const call of toolCalls) {
|
|
|
121
165
|
toolCallId: call.id,
|
|
122
166
|
toolName: call.name,
|
|
123
167
|
content: [{ type: "text", text: result }],
|
|
168
|
+
isError: false,
|
|
124
169
|
timestamp: Date.now(),
|
|
125
170
|
});
|
|
126
171
|
}
|
|
@@ -208,6 +253,7 @@ for (const block of response.content) {
|
|
|
208
253
|
toolCallId: block.id,
|
|
209
254
|
toolName: block.name,
|
|
210
255
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
256
|
+
isError: false,
|
|
211
257
|
timestamp: Date.now(),
|
|
212
258
|
});
|
|
213
259
|
}
|
|
@@ -223,6 +269,7 @@ context.messages.push({
|
|
|
223
269
|
{ type: "text", text: "Generated chart showing temperature trends" },
|
|
224
270
|
{ type: "image", data: imageBuffer.toString("base64"), mimeType: "image/png" },
|
|
225
271
|
],
|
|
272
|
+
isError: false,
|
|
226
273
|
timestamp: Date.now(),
|
|
227
274
|
});
|
|
228
275
|
```
|
|
@@ -819,17 +866,19 @@ const response = await complete(
|
|
|
819
866
|
|
|
820
867
|
In Node.js environments, you can set environment variables to avoid passing API keys:
|
|
821
868
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
869
|
+
| Provider | Environment Variable(s) |
|
|
870
|
+
| -------------- | ---------------------------------------------------------------------------- |
|
|
871
|
+
| OpenAI | `OPENAI_API_KEY` |
|
|
872
|
+
| Anthropic | `ANTHROPIC_API_KEY` or `ANTHROPIC_OAUTH_TOKEN` |
|
|
873
|
+
| Google | `GEMINI_API_KEY` |
|
|
874
|
+
| Vertex AI | `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) + `GOOGLE_CLOUD_LOCATION` + ADC |
|
|
875
|
+
| Mistral | `MISTRAL_API_KEY` |
|
|
876
|
+
| Groq | `GROQ_API_KEY` |
|
|
877
|
+
| Cerebras | `CEREBRAS_API_KEY` |
|
|
878
|
+
| xAI | `XAI_API_KEY` |
|
|
879
|
+
| OpenRouter | `OPENROUTER_API_KEY` |
|
|
880
|
+
| zAI | `ZAI_API_KEY` |
|
|
881
|
+
| GitHub Copilot | `COPILOT_GITHUB_TOKEN` or `GH_TOKEN` or `GITHUB_TOKEN` |
|
|
833
882
|
|
|
834
883
|
When set, the library automatically uses these keys:
|
|
835
884
|
|
|
@@ -858,10 +907,15 @@ const key = getEnvApiKey("openai"); // checks OPENAI_API_KEY
|
|
|
858
907
|
Several providers require OAuth authentication instead of static API keys:
|
|
859
908
|
|
|
860
909
|
- **Anthropic** (Claude Pro/Max subscription)
|
|
910
|
+
- **OpenAI Codex** (ChatGPT Plus/Pro subscription, access to GPT-5.x Codex models)
|
|
861
911
|
- **GitHub Copilot** (Copilot subscription)
|
|
862
912
|
- **Google Gemini CLI** (Free Gemini 2.0/2.5 via Google Cloud Code Assist)
|
|
863
913
|
- **Antigravity** (Free Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
864
914
|
|
|
915
|
+
### Vertex AI (ADC)
|
|
916
|
+
|
|
917
|
+
Vertex AI models use Application Default Credentials. Run `gcloud auth application-default login`, set `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`), and `GOOGLE_CLOUD_LOCATION`. You can also pass `project`/`location` in the call options.
|
|
918
|
+
|
|
865
919
|
### CLI Login
|
|
866
920
|
|
|
867
921
|
The quickest way to authenticate:
|
|
@@ -882,6 +936,7 @@ The library provides login and token refresh functions. Credential storage is th
|
|
|
882
936
|
import {
|
|
883
937
|
// Login functions (return credentials, do not store)
|
|
884
938
|
loginAnthropic,
|
|
939
|
+
loginOpenAICodex,
|
|
885
940
|
loginGitHubCopilot,
|
|
886
941
|
loginGeminiCli,
|
|
887
942
|
loginAntigravity,
|
|
@@ -891,7 +946,7 @@ import {
|
|
|
891
946
|
getOAuthApiKey, // (provider, credentialsMap) => { newCredentials, apiKey } | null
|
|
892
947
|
|
|
893
948
|
// Types
|
|
894
|
-
type OAuthProvider, // 'anthropic' | 'github-copilot' | 'google-gemini-cli' | 'google-antigravity'
|
|
949
|
+
type OAuthProvider, // 'anthropic' | 'openai-codex' | 'github-copilot' | 'google-gemini-cli' | 'google-antigravity'
|
|
895
950
|
type OAuthCredentials,
|
|
896
951
|
} from "@oh-my-pi/pi-ai";
|
|
897
952
|
```
|
|
@@ -950,6 +1005,8 @@ const response = await complete(
|
|
|
950
1005
|
|
|
951
1006
|
### Provider Notes
|
|
952
1007
|
|
|
1008
|
+
**OpenAI Codex**: Requires a ChatGPT Plus or Pro subscription. Provides access to GPT-5.x Codex models with extended context windows and reasoning capabilities. The library automatically handles session-based prompt caching when `sessionId` is provided in stream options.
|
|
1009
|
+
|
|
953
1010
|
**GitHub Copilot**: If you get "The requested model is not supported" error, enable the model manually in VS Code: open Copilot Chat, click the model selector, select the model (warning icon), and click "Enable".
|
|
954
1011
|
|
|
955
1012
|
**Google Gemini CLI / Antigravity**: These use Google Cloud OAuth. The `apiKey` returned by `getOAuthApiKey()` is a JSON string containing both the token and project ID, which the library handles automatically.
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.34.0",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"types": "./src/index.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": "./src/index.ts",
|
|
10
|
-
"./utils/json-parse": "./src/utils/json-parse.ts"
|
|
11
|
-
},
|
|
12
8
|
"bin": {
|
|
13
9
|
"pi-ai": "./src/cli.ts"
|
|
14
10
|
},
|
|
@@ -18,8 +14,7 @@
|
|
|
18
14
|
],
|
|
19
15
|
"scripts": {
|
|
20
16
|
"generate-models": "bun scripts/generate-models.ts",
|
|
21
|
-
"test": "
|
|
22
|
-
"prepublishOnly": "bun run generate-models"
|
|
17
|
+
"test": "bun test"
|
|
23
18
|
},
|
|
24
19
|
"dependencies": {
|
|
25
20
|
"@anthropic-ai/sdk": "0.71.2",
|
|
@@ -53,8 +48,6 @@
|
|
|
53
48
|
"bun": ">=1.0.0"
|
|
54
49
|
},
|
|
55
50
|
"devDependencies": {
|
|
56
|
-
"@types/node": "^24.3.0"
|
|
57
|
-
"canvas": "^3.2.0",
|
|
58
|
-
"vitest": "^3.2.4"
|
|
51
|
+
"@types/node": "^24.3.0"
|
|
59
52
|
}
|
|
60
53
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { createInterface } from "readline";
|
|
4
5
|
import { loginAnthropic } from "./utils/oauth/anthropic";
|
|
5
6
|
import { loginGitHubCopilot } from "./utils/oauth/github-copilot";
|
|
6
7
|
import { loginAntigravity } from "./utils/oauth/google-antigravity";
|
|
@@ -12,104 +13,101 @@ import type { OAuthCredentials, OAuthProvider } from "./utils/oauth/types";
|
|
|
12
13
|
const AUTH_FILE = "auth.json";
|
|
13
14
|
const PROVIDERS = getOAuthProviders();
|
|
14
15
|
|
|
15
|
-
function prompt(question: string): Promise<string> {
|
|
16
|
-
return new Promise((resolve) =>
|
|
17
|
-
process.stdout.write(question);
|
|
18
|
-
process.stdin.resume();
|
|
19
|
-
process.stdin.once("data", (data) => {
|
|
20
|
-
process.stdin.pause();
|
|
21
|
-
resolve(data.toString().trim());
|
|
22
|
-
});
|
|
23
|
-
});
|
|
16
|
+
function prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
|
|
17
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
24
18
|
}
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!(await file.exists())) return {};
|
|
20
|
+
function loadAuth(): Record<string, { type: "oauth" } & OAuthCredentials> {
|
|
21
|
+
if (!existsSync(AUTH_FILE)) return {};
|
|
29
22
|
try {
|
|
30
|
-
return
|
|
31
|
-
} catch
|
|
32
|
-
logger.debug("Failed to parse config file", { error: String(err) });
|
|
23
|
+
return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
|
|
24
|
+
} catch {
|
|
33
25
|
return {};
|
|
34
26
|
}
|
|
35
27
|
}
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
function saveAuth(auth: Record<string, { type: "oauth" } & OAuthCredentials>): void {
|
|
30
|
+
writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2), "utf-8");
|
|
39
31
|
}
|
|
40
32
|
|
|
41
33
|
async function login(provider: OAuthProvider): Promise<void> {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
34
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
|
|
36
|
+
const promptFn = (msg: string) => prompt(rl, `${msg} `);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
let credentials: OAuthCredentials;
|
|
40
|
+
|
|
41
|
+
switch (provider) {
|
|
42
|
+
case "anthropic":
|
|
43
|
+
credentials = await loginAnthropic(
|
|
44
|
+
(url) => {
|
|
45
|
+
console.log(`\nOpen this URL in your browser:\n${url}\n`);
|
|
46
|
+
},
|
|
47
|
+
async () => {
|
|
48
|
+
return await promptFn("Paste the authorization code:");
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
break;
|
|
52
|
+
|
|
53
|
+
case "github-copilot":
|
|
54
|
+
credentials = await loginGitHubCopilot({
|
|
55
|
+
onAuth: (url, instructions) => {
|
|
56
|
+
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
57
|
+
if (instructions) console.log(instructions);
|
|
58
|
+
console.log();
|
|
59
|
+
},
|
|
60
|
+
onPrompt: async (p) => {
|
|
61
|
+
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
62
|
+
},
|
|
63
|
+
onProgress: (msg) => console.log(msg),
|
|
64
|
+
});
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case "google-gemini-cli":
|
|
68
|
+
credentials = await loginGeminiCli(
|
|
69
|
+
(info) => {
|
|
70
|
+
console.log(`\nOpen this URL in your browser:\n${info.url}`);
|
|
71
|
+
if (info.instructions) console.log(info.instructions);
|
|
72
|
+
console.log();
|
|
73
|
+
},
|
|
74
|
+
(msg) => console.log(msg),
|
|
75
|
+
);
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case "google-antigravity":
|
|
79
|
+
credentials = await loginAntigravity(
|
|
80
|
+
(info) => {
|
|
81
|
+
console.log(`\nOpen this URL in your browser:\n${info.url}`);
|
|
82
|
+
if (info.instructions) console.log(info.instructions);
|
|
83
|
+
console.log();
|
|
84
|
+
},
|
|
85
|
+
(msg) => console.log(msg),
|
|
86
|
+
);
|
|
87
|
+
break;
|
|
88
|
+
case "openai-codex":
|
|
89
|
+
credentials = await loginOpenAICodex({
|
|
90
|
+
onAuth: (info) => {
|
|
91
|
+
console.log(`\nOpen this URL in your browser:\n${info.url}`);
|
|
92
|
+
if (info.instructions) console.log(info.instructions);
|
|
93
|
+
console.log();
|
|
94
|
+
},
|
|
95
|
+
onPrompt: async (p) => {
|
|
96
|
+
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
97
|
+
},
|
|
98
|
+
onProgress: (msg) => console.log(msg),
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
const auth = loadAuth();
|
|
104
|
+
auth[provider] = { type: "oauth", ...credentials };
|
|
105
|
+
saveAuth(auth);
|
|
111
106
|
|
|
112
|
-
|
|
107
|
+
console.log(`\nCredentials saved to ${AUTH_FILE}`);
|
|
108
|
+
} finally {
|
|
109
|
+
rl.close();
|
|
110
|
+
}
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
async function main(): Promise<void> {
|
|
@@ -150,13 +148,15 @@ Examples:
|
|
|
150
148
|
let provider = args[1] as OAuthProvider | undefined;
|
|
151
149
|
|
|
152
150
|
if (!provider) {
|
|
151
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
153
152
|
console.log("Select a provider:\n");
|
|
154
153
|
for (let i = 0; i < PROVIDERS.length; i++) {
|
|
155
154
|
console.log(` ${i + 1}. ${PROVIDERS[i].name}`);
|
|
156
155
|
}
|
|
157
156
|
console.log();
|
|
158
157
|
|
|
159
|
-
const choice = await prompt(`Enter number (1-${PROVIDERS.length}): `);
|
|
158
|
+
const choice = await prompt(rl, `Enter number (1-${PROVIDERS.length}): `);
|
|
159
|
+
rl.close();
|
|
160
160
|
|
|
161
161
|
const index = parseInt(choice, 10) - 1;
|
|
162
162
|
if (index < 0 || index >= PROVIDERS.length) {
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/// <reference path="./bun-imports.d.ts" />
|
|
2
|
-
|
|
3
1
|
export * from "./models";
|
|
4
2
|
export * from "./providers/anthropic";
|
|
5
3
|
export * from "./providers/google";
|
|
6
4
|
export * from "./providers/google-gemini-cli";
|
|
5
|
+
export * from "./providers/google-vertex";
|
|
6
|
+
export * from "./providers/openai-codex/index";
|
|
7
7
|
export * from "./providers/openai-completions";
|
|
8
8
|
export * from "./providers/openai-responses";
|
|
9
9
|
export * from "./stream";
|