WIP: Remove global state from pi-ai OAuth/API key handling
- Remove setApiKey, resolveApiKey, and global apiKeys Map from stream.ts - Rename getApiKey to getApiKeyFromEnv (only checks env vars) - Remove OAuth storage layer (storage.ts deleted) - OAuth login/refresh functions now return credentials instead of saving - getOAuthApiKey/refreshOAuthToken now take credentials as params - Add test/oauth.ts helper for ai package tests - Simplify root npm run check (single biome + tsgo pass) - Remove redundant check scripts from most packages - Add web-ui and coding-agent examples to biome/tsgo includes coding-agent still has compile errors - needs refactoring for new API
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, resolveApiKey, stream } from "../src/stream.js";
|
||||
import { complete, stream } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi } from "../src/types.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const geminiCliToken = await resolveApiKey("google-gemini-cli");
|
||||
|
||||
@@ -3,7 +3,6 @@ import { agentLoop, agentLoopContinue } from "../src/agent/agent-loop.js";
|
||||
import { calculateTool } from "../src/agent/tools/calculate.js";
|
||||
import type { AgentContext, AgentEvent, AgentLoopConfig } from "../src/agent/types.js";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { resolveApiKey } from "../src/stream.js";
|
||||
import type {
|
||||
Api,
|
||||
AssistantMessage,
|
||||
@@ -13,6 +12,7 @@ import type {
|
||||
ToolResultMessage,
|
||||
UserMessage,
|
||||
} from "../src/types.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const oauthTokens = await Promise.all([
|
||||
|
||||
@@ -15,9 +15,10 @@ import type { ChildProcess } from "child_process";
|
||||
import { execSync, spawn } from "child_process";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, resolveApiKey } from "../src/stream.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { AssistantMessage, Context, Model, Usage } from "../src/types.js";
|
||||
import { isContextOverflow } from "../src/utils/overflow.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const oauthTokens = await Promise.all([
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, resolveApiKey } from "../src/stream.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Api, AssistantMessage, Context, Model, OptionsForApi, UserMessage } from "../src/types.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const oauthTokens = await Promise.all([
|
||||
|
||||
@@ -3,8 +3,9 @@ import { join } from "node:path";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Api, Context, Model, Tool, ToolResultMessage } from "../src/index.js";
|
||||
import { complete, getModel, resolveApiKey } from "../src/index.js";
|
||||
import { complete, getModel } from "../src/index.js";
|
||||
import type { OptionsForApi } from "../src/types.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const oauthTokens = await Promise.all([
|
||||
|
||||
89
packages/ai/test/oauth.ts
Normal file
89
packages/ai/test/oauth.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Test helper for resolving API keys from ~/.pi/agent/auth.json
|
||||
*
|
||||
* Supports both API key and OAuth credentials.
|
||||
* OAuth tokens are automatically refreshed if expired and saved back to auth.json.
|
||||
*/
|
||||
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { dirname, join } from "path";
|
||||
import { getOAuthApiKey } from "../src/utils/oauth/index.js";
|
||||
import type { OAuthCredentials, OAuthProvider } from "../src/utils/oauth/types.js";
|
||||
|
||||
const AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
|
||||
|
||||
type ApiKeyCredential = {
|
||||
type: "api_key";
|
||||
key: string;
|
||||
};
|
||||
|
||||
type OAuthCredentialEntry = {
|
||||
type: "oauth";
|
||||
} & OAuthCredentials;
|
||||
|
||||
type AuthCredential = ApiKeyCredential | OAuthCredentialEntry;
|
||||
|
||||
type AuthStorage = Record<string, AuthCredential>;
|
||||
|
||||
function loadAuthStorage(): AuthStorage {
|
||||
if (!existsSync(AUTH_PATH)) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const content = readFileSync(AUTH_PATH, "utf-8");
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function saveAuthStorage(storage: AuthStorage): void {
|
||||
const configDir = dirname(AUTH_PATH);
|
||||
if (!existsSync(configDir)) {
|
||||
mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
writeFileSync(AUTH_PATH, JSON.stringify(storage, null, 2), "utf-8");
|
||||
chmodSync(AUTH_PATH, 0o600);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve API key for a provider from ~/.pi/agent/auth.json
|
||||
*
|
||||
* For API key credentials, returns the key directly.
|
||||
* For OAuth credentials, returns the access token (refreshing if expired and saving back).
|
||||
*
|
||||
* For google-gemini-cli and google-antigravity, returns JSON-encoded { token, projectId }
|
||||
*/
|
||||
export async function resolveApiKey(provider: string): Promise<string | undefined> {
|
||||
const storage = loadAuthStorage();
|
||||
const entry = storage[provider];
|
||||
|
||||
if (!entry) return undefined;
|
||||
|
||||
if (entry.type === "api_key") {
|
||||
return entry.key;
|
||||
}
|
||||
|
||||
if (entry.type === "oauth") {
|
||||
// Build OAuthCredentials record for getOAuthApiKey
|
||||
const oauthCredentials: Record<string, OAuthCredentials> = {};
|
||||
for (const [key, value] of Object.entries(storage)) {
|
||||
if (value.type === "oauth") {
|
||||
const { type: _, ...creds } = value;
|
||||
oauthCredentials[key] = creds;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await getOAuthApiKey(provider as OAuthProvider, oauthCredentials);
|
||||
if (!result) return undefined;
|
||||
|
||||
// Save refreshed credentials back to auth.json
|
||||
storage[provider] = { type: "oauth", ...result.newCredentials };
|
||||
saveAuthStorage(storage);
|
||||
|
||||
return result.apiKey;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, resolveApiKey, stream } from "../src/stream.js";
|
||||
import { complete, stream } from "../src/stream.js";
|
||||
import type { Api, Context, ImageContent, Model, OptionsForApi, Tool, ToolResultMessage } from "../src/types.js";
|
||||
import { StringEnum } from "../src/utils/typebox-helpers.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { resolveApiKey, stream } from "../src/stream.js";
|
||||
import { stream } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi } from "../src/types.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const oauthTokens = await Promise.all([
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, resolveApiKey } from "../src/stream.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi, Tool } from "../src/types.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const oauthTokens = await Promise.all([
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, resolveApiKey } from "../src/stream.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi, Usage } from "../src/types.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const oauthTokens = await Promise.all([
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, resolveApiKey } from "../src/stream.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi, ToolResultMessage } from "../src/types.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Empty schema for test tools - must be proper OBJECT type for Cloud Code Assist
|
||||
const emptySchema = Type.Object({});
|
||||
|
||||
Reference in New Issue
Block a user