@andreprado/agentkit 0.2.0 → 0.3.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.
@@ -49,12 +49,12 @@ To use the Codex subscription login instead of an OpenAI API key:
49
49
  npm run agentkit -- provider login openai-codex
50
50
  ```
51
51
 
52
- Run this in an interactive terminal, open the printed URL, and sign in with ChatGPT. If the browser callback cannot reach the terminal, paste the redirect URL into the prompt. This uses Pi's Codex OAuth flow and creates an AgentKit session; it does not read or change the Codex app/CLI credential cache.
52
+ Run this in an interactive terminal, choose browser login (default) or device code login for headless machines, then sign in with ChatGPT. If the browser callback cannot reach the terminal, paste the redirect URL into the prompt. This uses Pi's Codex OAuth flow and creates an AgentKit session; it does not read or change the Codex app/CLI credential cache.
53
53
 
54
54
  Configure the capsule:
55
55
 
56
56
  ```ts
57
- provider: { name: "openai-codex", model: "gpt-5.4" },
57
+ provider: { name: "openai-codex", model: "gpt-5.5" },
58
58
  secrets: [], // Keep any secrets required by your tools or other services.
59
59
  ```
60
60
 
@@ -102,7 +102,7 @@ Anthropic:
102
102
  ```ts
103
103
  provider: {
104
104
  name: "anthropic",
105
- model: "claude-3-5-haiku-latest",
105
+ model: "claude-haiku-4-5",
106
106
  },
107
107
  secrets: ["ANTHROPIC_API_KEY"],
108
108
  ```
@@ -193,6 +193,8 @@ npm run agentkit -- inspect
193
193
 
194
194
  For OpenAI, Anthropic, OpenCode Zen, or OpenCode Go, use a model id known to the installed Pi SDK for that provider. For OpenRouter, prefer a known Pi alias when possible; otherwise a raw OpenRouter model id is passed through and any remaining model error comes from OpenRouter.
195
195
 
196
+ After upgrading to AgentKit 0.3.0 (Pi 0.99), `openai-codex` no longer offers `gpt-5.4` or `gpt-5.4-mini`, and `anthropic` no longer offers `claude-3-5-haiku-latest`. Switch to a listed model such as `gpt-5.5` or `claude-haiku-4-5`; the error message lists examples.
197
+
196
198
  Provider returns auth failure:
197
199
 
198
200
  Check the key value, provider account status, and model access.
@@ -217,7 +217,7 @@ secrets: [],
217
217
 
218
218
  Before claiming real conversation behavior has been tested, ask the owner which provider to use: OpenRouter, OpenAI, Anthropic, OpenCode Zen, OpenCode Go, or another supported provider. Do not choose for them. After the owner chooses, update `agentkit.config.ts`, `.env.schema`, local secrets, then rerun chat/UI checks.
219
219
 
220
- ChatGPT/Codex subscription login (local only): run `agentkit provider login openai-codex` in an interactive terminal, sign in through the printed URL, and set `provider: { name: "openai-codex", model: "gpt-5.4" }`. No `OPENAI_API_KEY` is required for this provider. Keep secrets needed by tools or other services. The AgentKit session is stored outside capsules in `~/.agentkit/providers/openai-codex.json`, renewed through Pi, and never copied from or into the Codex app cache. `agentkit provider status openai-codex` reports local status without tokens; `agentkit provider logout openai-codex` removes the local session without revoking it at OpenAI. See `docs/guides/use-provider.md`.
220
+ ChatGPT/Codex subscription login (local only): run `agentkit provider login openai-codex` in an interactive terminal, sign in through the printed URL, and set `provider: { name: "openai-codex", model: "gpt-5.5" }`. No `OPENAI_API_KEY` is required for this provider. Keep secrets needed by tools or other services. The AgentKit session is stored outside capsules in `~/.agentkit/providers/openai-codex.json`, renewed through Pi, and never copied from or into the Codex app cache. `agentkit provider status openai-codex` reports local status without tokens; `agentkit provider logout openai-codex` removes the local session without revoking it at OpenAI. See `docs/guides/use-provider.md`.
221
221
 
222
222
  OpenAI example:
223
223
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andreprado/agentkit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "agentkit": "bin/agentkit.mjs"
30
30
  },
31
31
  "dependencies": {
32
- "@earendil-works/pi-ai": "^0.75.5",
32
+ "@earendil-works/pi-ai": "^0.99.2",
33
33
  "@libsql/client": "^0.15.15",
34
34
  "tsx": "^4.23.12",
35
35
  "typebox": "^1.1.38"
@@ -1,8 +1,8 @@
1
- import { createInterface } from "node:readline/promises";
1
+ import { createInterface, type Interface } from "node:readline/promises";
2
2
 
3
- import { loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
3
+ import type { AuthEvent, AuthPrompt } from "@earendil-works/pi-ai";
4
4
 
5
- import { clearCodexLogin, codexLoginStatus, saveCodexLogin } from "../../providers/codex-auth";
5
+ import { clearCodexLogin, codexLoginStatus, codexOAuth, saveCodexLogin } from "../../providers/codex-auth";
6
6
  import { AgentKitError } from "../../runtime/errors";
7
7
  import type { ParsedArgs } from "../args";
8
8
 
@@ -28,20 +28,38 @@ export async function handleProviderCommand(args: ParsedArgs): Promise<void> {
28
28
  const abort = new AbortController();
29
29
  readline.on("SIGINT", () => abort.abort());
30
30
  try {
31
- const credentials = await loginOpenAICodex({
32
- onAuth({ url, instructions }) {
33
- console.log(`Open this URL to sign in with ChatGPT:\n${url}`);
34
- if (instructions) console.log(instructions);
35
- },
36
- onPrompt: ({ message }) => readline.question(`${message} `, { signal: abort.signal }),
37
- onManualCodeInput: () => readline.question("After signing in, paste the redirect URL here if the browser callback cannot reach this terminal: ", { signal: abort.signal }),
31
+ const credentials = await codexOAuth().login({
32
+ signal: abort.signal,
33
+ notify: printAuthEvent,
34
+ prompt: (prompt) => askAuthPrompt(readline, prompt, abort.signal),
38
35
  }).catch(() => {
39
36
  throw new AgentKitError("provider_auth_failed", "Codex login failed or was cancelled. Run agentkit provider login openai-codex again.");
40
37
  });
41
38
  await saveCodexLogin(credentials);
42
- console.log('Signed in. Set provider: { name: "openai-codex", model: "gpt-5.4" } in agentkit.config.ts. No OPENAI_API_KEY is required.');
39
+ console.log('Signed in. Set provider: { name: "openai-codex", model: "gpt-5.5" } in agentkit.config.ts. No OPENAI_API_KEY is required.');
43
40
  } finally {
44
41
  abort.abort();
45
42
  readline.close();
46
43
  }
47
44
  }
45
+
46
+ function printAuthEvent(event: AuthEvent): void {
47
+ if (event.type === "auth_url") {
48
+ console.log(`Open this URL to sign in with ChatGPT:\n${event.url}`);
49
+ if (event.instructions) console.log(event.instructions);
50
+ } else if (event.type === "device_code") {
51
+ console.log(`Open ${event.verificationUri} and enter code ${event.userCode}`);
52
+ } else {
53
+ console.log(event.message);
54
+ }
55
+ }
56
+
57
+ async function askAuthPrompt(readline: Interface, prompt: AuthPrompt, loginSignal: AbortSignal): Promise<string> {
58
+ const signal = prompt.signal ? AbortSignal.any([loginSignal, prompt.signal]) : loginSignal;
59
+ if (prompt.type !== "select") return readline.question(`${prompt.message} `, { signal });
60
+ prompt.options.forEach((option, index) => console.log(` ${index + 1}. ${option.label}`));
61
+ const answer = (await readline.question(`${prompt.message} [1] `, { signal })).trim() || "1";
62
+ const option = prompt.options[Number(answer) - 1];
63
+ if (!option) throw new Error("Unknown login option.");
64
+ return option.id;
65
+ }
@@ -4,10 +4,24 @@ import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { setTimeout as delay } from "node:timers/promises";
6
6
 
7
- import { refreshOpenAICodexToken, type OAuthCredentials } from "@earendil-works/pi-ai/oauth";
7
+ import type { OAuthAuth } from "@earendil-works/pi-ai";
8
+ import type { OAuthCredentials } from "@earendil-works/pi-ai/oauth";
9
+ import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
8
10
 
9
11
  import { AgentKitError } from "../runtime/errors";
10
12
 
13
+ const REFRESH_TIMEOUT_MS = 15_000;
14
+
15
+ export function codexOAuth(): OAuthAuth {
16
+ const oauth = openaiCodexProvider().auth.oauth;
17
+ if (!oauth) throw new AgentKitError("provider_not_supported", "The installed Pi SDK no longer supports OpenAI Codex sign-in.");
18
+ return oauth;
19
+ }
20
+
21
+ function refreshCodexToken(refresh: string): Promise<OAuthCredentials> {
22
+ return codexOAuth().refresh({ type: "oauth", access: "", refresh, expires: 0 }, AbortSignal.timeout(REFRESH_TIMEOUT_MS));
23
+ }
24
+
11
25
  export function codexAuthPath(): string {
12
26
  return join(homedir(), ".agentkit", "providers", "openai-codex.json");
13
27
  }
@@ -90,7 +104,7 @@ export async function codexLoginStatus(path = codexAuthPath()) {
90
104
 
91
105
  export async function getCodexApiKey(
92
106
  path = codexAuthPath(),
93
- refresh = refreshOpenAICodexToken,
107
+ refresh = refreshCodexToken,
94
108
  ): Promise<string> {
95
109
  return withAuthLock(path, async () => {
96
110
  const credentials = await readCredentials(path);
@@ -1,28 +1,51 @@
1
1
  import {
2
- findEnvKeys,
3
- getModel,
4
- getModels,
5
- stream,
2
+ createModels,
3
+ envApiKeyAuth,
6
4
  type AssistantMessage,
7
5
  type AssistantMessageEvent,
6
+ type Context,
8
7
  type Message,
9
8
  type Model,
10
- type Provider,
9
+ type StreamOptions,
11
10
  type Tool as PiTool,
12
11
  type ToolCall,
13
12
  type Usage,
14
13
  } from "@earendil-works/pi-ai";
14
+ import { anthropicProvider } from "@earendil-works/pi-ai/providers/anthropic";
15
+ import { openaiProvider } from "@earendil-works/pi-ai/providers/openai";
16
+ import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
17
+ import { opencodeProvider } from "@earendil-works/pi-ai/providers/opencode";
18
+ import { opencodeGoProvider } from "@earendil-works/pi-ai/providers/opencode-go";
19
+ import { openrouterProvider } from "@earendil-works/pi-ai/providers/openrouter";
15
20
 
16
21
  import type { AgentProviderName, AgentTool } from "../index";
17
22
  import { AgentKitError } from "../runtime/errors";
18
23
  import type { ProviderAdapter, ProviderRunInput, ProviderRunResult } from "./types";
19
24
 
20
25
  const PI_PROVIDER_NAMES = ["openai", "openai-codex", "anthropic", "openrouter", "opencode", "opencode-go"] as const satisfies AgentProviderName[];
26
+ type Provider = (typeof PI_PROVIDER_NAMES)[number];
27
+ const PROVIDER_SECRET_NAMES: Record<Exclude<Provider, "openai-codex">, string[]> = {
28
+ openai: ["OPENAI_API_KEY"],
29
+ anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
30
+ openrouter: ["OPENROUTER_API_KEY"],
31
+ opencode: ["OPENCODE_API_KEY"],
32
+ "opencode-go": ["OPENCODE_API_KEY"],
33
+ };
21
34
  const MAX_TOOL_TURNS = 8;
22
35
  const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
23
36
  const UNKNOWN_OPENROUTER_CONTEXT_WINDOW = 128000;
24
37
  const UNKNOWN_OPENROUTER_MAX_TOKENS = 8192;
25
38
 
39
+ // AgentKit resolves credentials itself (.env secrets, the local Codex login) and passes
40
+ // them per request, so Pi never reads ambient env vars or its own credential store.
41
+ export const piModels = createModels();
42
+ for (const provider of [openaiProvider(), anthropicProvider(), openrouterProvider(), opencodeProvider(), opencodeGoProvider()]) {
43
+ piModels.setProvider(provider);
44
+ }
45
+ const codexProvider = openaiCodexProvider();
46
+ // Pi's Codex provider only accepts its own OAuth store; accept the explicit access token instead.
47
+ piModels.setProvider({ ...codexProvider, auth: { ...codexProvider.auth, apiKey: envApiKeyAuth("OpenAI Codex access token", []) } });
48
+
26
49
  export const piProviderAdapters: ProviderAdapter[] = PI_PROVIDER_NAMES.map((name) => ({
27
50
  name,
28
51
  run(input) {
@@ -129,11 +152,11 @@ export function providerToolResultText(result: {
129
152
 
130
153
  async function runPiTurn(
131
154
  model: Model<any>,
132
- context: Parameters<typeof stream>[1],
133
- options: Parameters<typeof stream>[2],
155
+ context: Context,
156
+ options: StreamOptions,
134
157
  rawEvents: AssistantMessageEvent[],
135
158
  ): Promise<AssistantMessage> {
136
- const eventStream = stream(model, context, options);
159
+ const eventStream = piModels.stream(model, context, options);
137
160
  let assistant: AssistantMessage | undefined;
138
161
  let failed: AssistantMessage | undefined;
139
162
 
@@ -180,7 +203,7 @@ function requireProviderApiKey(provider: Provider, env: Record<string, string |
180
203
  throw new AgentKitError("provider_auth_required", "Run agentkit provider login openai-codex, then use local chat, dev, or eval.");
181
204
  }
182
205
 
183
- const envNames = findEnvKeys(provider) ?? defaultProviderSecretNames(provider);
206
+ const envNames = PROVIDER_SECRET_NAMES[provider];
184
207
  const setName = envNames.find((name) => env[name]);
185
208
 
186
209
  if (!setName) {
@@ -193,14 +216,6 @@ function requireProviderApiKey(provider: Provider, env: Record<string, string |
193
216
  return env[setName] as string;
194
217
  }
195
218
 
196
- function defaultProviderSecretNames(provider: Provider): string[] {
197
- if (provider === "opencode" || provider === "opencode-go") {
198
- return ["OPENCODE_API_KEY"];
199
- }
200
-
201
- return [`${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`];
202
- }
203
-
204
219
  function formatSecretList(names: string[]): string {
205
220
  if (names.length === 1) {
206
221
  return names[0];
@@ -210,7 +225,7 @@ function formatSecretList(names: string[]): string {
210
225
  }
211
226
 
212
227
  export function resolveModel(provider: Provider, modelId: string): Model<any> {
213
- const model = getModel(provider as any, modelId as any) as Model<any> | undefined;
228
+ const model = piModels.getModel(provider, modelId);
214
229
 
215
230
  if (model) {
216
231
  return model;
@@ -250,7 +265,7 @@ function createOpenRouterPassThroughModel(modelId: string): Model<"openai-comple
250
265
  }
251
266
 
252
267
  function knownModelHint(provider: Provider): string {
253
- const examples = getModels(provider as any)
268
+ const examples = piModels.getModels(provider)
254
269
  .slice(0, 5)
255
270
  .map((model) => model.id);
256
271
 
@@ -34,7 +34,7 @@ secrets: ["OPENAI_API_KEY"],
34
34
  Anthropic:
35
35
 
36
36
  ```ts
37
- provider: { name: "anthropic", model: "claude-3-5-haiku-latest" },
37
+ provider: { name: "anthropic", model: "claude-haiku-4-5" },
38
38
  secrets: ["ANTHROPIC_API_KEY"],
39
39
  ```
40
40