@oh-my-pi/pi-ai 3.20.1 → 3.35.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 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
- ```bash
823
- OPENAI_API_KEY=sk-...
824
- ANTHROPIC_API_KEY=sk-ant-...
825
- GEMINI_API_KEY=...
826
- MISTRAL_API_KEY=...
827
- GROQ_API_KEY=gsk_...
828
- CEREBRAS_API_KEY=csk-...
829
- XAI_API_KEY=xai-...
830
- ZAI_API_KEY=...
831
- OPENROUTER_API_KEY=sk-or-...
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.20.1",
3
+ "version": "3.35.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": "vitest --run",
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 bun
1
+ #!/usr/bin/env node
2
2
 
3
- import { logger } from "@oh-my-pi/pi-coding-agent";
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
- async function loadAuth(): Promise<Record<string, { type: "oauth" } & OAuthCredentials>> {
27
- const file = Bun.file(AUTH_FILE);
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 await file.json();
31
- } catch (err) {
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
- async function saveAuth(auth: Record<string, { type: "oauth" } & OAuthCredentials>): Promise<void> {
38
- await Bun.write(AUTH_FILE, JSON.stringify(auth, null, 2));
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 promptFn = (msg: string) => prompt(`${msg} `);
43
-
44
- let credentials: OAuthCredentials;
45
-
46
- switch (provider) {
47
- case "anthropic":
48
- credentials = await loginAnthropic(
49
- (url) => {
50
- console.log(`\nOpen this URL in your browser:\n${url}\n`);
51
- },
52
- async () => {
53
- return await promptFn("Paste the authorization code:");
54
- },
55
- );
56
- break;
57
-
58
- case "github-copilot":
59
- credentials = await loginGitHubCopilot({
60
- onAuth: (url, instructions) => {
61
- console.log(`\nOpen this URL in your browser:\n${url}`);
62
- if (instructions) console.log(instructions);
63
- console.log();
64
- },
65
- onPrompt: async (p) => {
66
- return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
67
- },
68
- onProgress: (msg) => console.log(msg),
69
- });
70
- break;
71
-
72
- case "google-gemini-cli":
73
- credentials = await loginGeminiCli(
74
- (info) => {
75
- console.log(`\nOpen this URL in your browser:\n${info.url}`);
76
- if (info.instructions) console.log(info.instructions);
77
- console.log();
78
- },
79
- (msg) => console.log(msg),
80
- );
81
- break;
82
-
83
- case "google-antigravity":
84
- credentials = await loginAntigravity(
85
- (info) => {
86
- console.log(`\nOpen this URL in your browser:\n${info.url}`);
87
- if (info.instructions) console.log(info.instructions);
88
- console.log();
89
- },
90
- (msg) => console.log(msg),
91
- );
92
- break;
93
- case "openai-codex":
94
- credentials = await loginOpenAICodex({
95
- onAuth: (info) => {
96
- console.log(`\nOpen this URL in your browser:\n${info.url}`);
97
- if (info.instructions) console.log(info.instructions);
98
- console.log();
99
- },
100
- onPrompt: async (p) => {
101
- return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
102
- },
103
- onProgress: (msg) => console.log(msg),
104
- });
105
- break;
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
- const auth = await loadAuth();
109
- auth[provider] = { type: "oauth", ...credentials };
110
- await saveAuth(auth);
103
+ const auth = loadAuth();
104
+ auth[provider] = { type: "oauth", ...credentials };
105
+ saveAuth(auth);
111
106
 
112
- console.log(`\nCredentials saved to ${AUTH_FILE}`);
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";