@mariozechner/pi-coding-agent 0.7.16 → 0.7.18
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 +20 -0
- package/README.md +45 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -5
- package/dist/main.js.map +1 -1
- package/dist/model-config.d.ts +4 -3
- package/dist/model-config.d.ts.map +1 -1
- package/dist/model-config.js +22 -10
- package/dist/model-config.js.map +1 -1
- package/dist/oauth/anthropic.d.ts +10 -0
- package/dist/oauth/anthropic.d.ts.map +1 -0
- package/dist/oauth/anthropic.js +100 -0
- package/dist/oauth/anthropic.js.map +1 -0
- package/dist/oauth/index.d.ts +29 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +87 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/storage.d.ts +23 -0
- package/dist/oauth/storage.d.ts.map +1 -0
- package/dist/oauth/storage.js +77 -0
- package/dist/oauth/storage.js.map +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +3 -6
- package/dist/tools/bash.js.map +1 -1
- package/dist/tui/assistant-message.d.ts.map +1 -1
- package/dist/tui/assistant-message.js +6 -4
- package/dist/tui/assistant-message.js.map +1 -1
- package/dist/tui/model-selector.d.ts +3 -2
- package/dist/tui/model-selector.d.ts.map +1 -1
- package/dist/tui/model-selector.js +11 -7
- package/dist/tui/model-selector.js.map +1 -1
- package/dist/tui/oauth-selector.d.ts +17 -0
- package/dist/tui/oauth-selector.d.ts.map +1 -0
- package/dist/tui/oauth-selector.js +91 -0
- package/dist/tui/oauth-selector.js.map +1 -0
- package/dist/tui/tui-renderer.d.ts +3 -0
- package/dist/tui/tui-renderer.d.ts.map +1 -1
- package/dist/tui/tui-renderer.js +135 -7
- package/dist/tui/tui-renderer.js.map +1 -1
- package/dist/tui/user-message.d.ts.map +1 -1
- package/dist/tui/user-message.js +1 -1
- package/dist/tui/user-message.js.map +1 -1
- package/package.json +3 -3
package/dist/model-config.js
CHANGED
|
@@ -4,6 +4,7 @@ import AjvModule from "ajv";
|
|
|
4
4
|
import { existsSync, readFileSync } from "fs";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
import { join } from "path";
|
|
7
|
+
import { getOAuthToken } from "./oauth/index.js";
|
|
7
8
|
// Handle both default and named exports
|
|
8
9
|
const Ajv = AjvModule.default || AjvModule;
|
|
9
10
|
// Schema for custom model definition
|
|
@@ -183,19 +184,27 @@ export function loadAndMergeModels() {
|
|
|
183
184
|
}
|
|
184
185
|
/**
|
|
185
186
|
* Get API key for a model (checks custom providers first, then built-in)
|
|
187
|
+
* Now async to support OAuth token refresh
|
|
186
188
|
*/
|
|
187
|
-
export function getApiKeyForModel(model) {
|
|
189
|
+
export async function getApiKeyForModel(model) {
|
|
188
190
|
// For custom providers, check their apiKey config
|
|
189
191
|
const customKeyConfig = customProviderApiKeys.get(model.provider);
|
|
190
192
|
if (customKeyConfig) {
|
|
191
193
|
return resolveApiKey(customKeyConfig);
|
|
192
194
|
}
|
|
193
|
-
// For Anthropic, check
|
|
195
|
+
// For Anthropic, check OAuth first
|
|
194
196
|
if (model.provider === "anthropic") {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
// 1. Check OAuth storage (auto-refresh if needed)
|
|
198
|
+
const oauthToken = await getOAuthToken("anthropic");
|
|
199
|
+
if (oauthToken) {
|
|
200
|
+
return oauthToken;
|
|
198
201
|
}
|
|
202
|
+
// 2. Check ANTHROPIC_OAUTH_TOKEN env var (manual OAuth token)
|
|
203
|
+
const oauthEnv = process.env.ANTHROPIC_OAUTH_TOKEN;
|
|
204
|
+
if (oauthEnv) {
|
|
205
|
+
return oauthEnv;
|
|
206
|
+
}
|
|
207
|
+
// 3. Fall back to ANTHROPIC_API_KEY env var
|
|
199
208
|
}
|
|
200
209
|
// For built-in providers, use getApiKey from @mariozechner/pi-ai
|
|
201
210
|
return getApiKey(model.provider);
|
|
@@ -204,15 +213,18 @@ export function getApiKeyForModel(model) {
|
|
|
204
213
|
* Get only models that have valid API keys available
|
|
205
214
|
* Returns { models, error } - either models array or error message
|
|
206
215
|
*/
|
|
207
|
-
export function getAvailableModels() {
|
|
216
|
+
export async function getAvailableModels() {
|
|
208
217
|
const { models: allModels, error } = loadAndMergeModels();
|
|
209
218
|
if (error) {
|
|
210
219
|
return { models: [], error };
|
|
211
220
|
}
|
|
212
|
-
const availableModels =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
221
|
+
const availableModels = [];
|
|
222
|
+
for (const model of allModels) {
|
|
223
|
+
const apiKey = await getApiKeyForModel(model);
|
|
224
|
+
if (apiKey) {
|
|
225
|
+
availableModels.push(model);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
216
228
|
return { models: availableModels, error: null };
|
|
217
229
|
}
|
|
218
230
|
/**
|
package/dist/model-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-config.js","sourceRoot":"","sources":["../src/model-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Api, getApiKey, getModels, getProviders, type KnownProvider, type Model } from \"@mariozechner/pi-ai\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport AjvModule from \"ajv\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\n// Handle both default and named exports\nconst Ajv = (AjvModule as any).default || AjvModule;\n\n// Schema for custom model definition\nconst ModelDefinitionSchema = Type.Object({\n\tid: Type.String({ minLength: 1 }),\n\tname: Type.String({ minLength: 1 }),\n\tapi: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai-completions\"),\n\t\t\tType.Literal(\"openai-responses\"),\n\t\t\tType.Literal(\"anthropic-messages\"),\n\t\t\tType.Literal(\"google-generative-ai\"),\n\t\t]),\n\t),\n\treasoning: Type.Boolean(),\n\tinput: Type.Array(Type.Union([Type.Literal(\"text\"), Type.Literal(\"image\")])),\n\tcost: Type.Object({\n\t\tinput: Type.Number(),\n\t\toutput: Type.Number(),\n\t\tcacheRead: Type.Number(),\n\t\tcacheWrite: Type.Number(),\n\t}),\n\tcontextWindow: Type.Number(),\n\tmaxTokens: Type.Number(),\n});\n\nconst ProviderConfigSchema = Type.Object({\n\tbaseUrl: Type.String({ minLength: 1 }),\n\tapiKey: Type.String({ minLength: 1 }),\n\tapi: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai-completions\"),\n\t\t\tType.Literal(\"openai-responses\"),\n\t\t\tType.Literal(\"anthropic-messages\"),\n\t\t\tType.Literal(\"google-generative-ai\"),\n\t\t]),\n\t),\n\tmodels: Type.Array(ModelDefinitionSchema),\n});\n\nconst ModelsConfigSchema = Type.Object({\n\tproviders: Type.Record(Type.String(), ProviderConfigSchema),\n});\n\ntype ModelsConfig = Static<typeof ModelsConfigSchema>;\ntype ProviderConfig = Static<typeof ProviderConfigSchema>;\ntype ModelDefinition = Static<typeof ModelDefinitionSchema>;\n\n// Custom provider API key mappings (provider name -> apiKey config)\nconst customProviderApiKeys: Map<string, string> = new Map();\n\n/**\n * Resolve an API key config value to an actual key.\n * First checks if it's an environment variable, then treats as literal.\n */\nexport function resolveApiKey(keyConfig: string): string | undefined {\n\t// First check if it's an env var name\n\tconst envValue = process.env[keyConfig];\n\tif (envValue) return envValue;\n\n\t// Otherwise treat as literal API key\n\treturn keyConfig;\n}\n\n/**\n * Load custom models from ~/.pi/agent/models.json\n * Returns { models, error } - either models array or error message\n */\nfunction loadCustomModels(): { models: Model<Api>[]; error: string | null } {\n\tconst configPath = join(homedir(), \".pi\", \"agent\", \"models.json\");\n\tif (!existsSync(configPath)) {\n\t\treturn { models: [], error: null };\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(configPath, \"utf-8\");\n\t\tconst config: ModelsConfig = JSON.parse(content);\n\n\t\t// Validate schema\n\t\tconst ajv = new Ajv();\n\t\tconst validate = ajv.compile(ModelsConfigSchema);\n\t\tif (!validate(config)) {\n\t\t\tconst errors =\n\t\t\t\tvalidate.errors?.map((e: any) => ` - ${e.instancePath || \"root\"}: ${e.message}`).join(\"\\n\") ||\n\t\t\t\t\"Unknown schema error\";\n\t\t\treturn {\n\t\t\t\tmodels: [],\n\t\t\t\terror: `Invalid models.json schema:\\n${errors}\\n\\nFile: ${configPath}`,\n\t\t\t};\n\t\t}\n\n\t\t// Additional validation\n\t\ttry {\n\t\t\tvalidateConfig(config);\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tmodels: [],\n\t\t\t\terror: `Invalid models.json: ${error instanceof Error ? error.message : error}\\n\\nFile: ${configPath}`,\n\t\t\t};\n\t\t}\n\n\t\t// Parse models\n\t\treturn { models: parseModels(config), error: null };\n\t} catch (error) {\n\t\tif (error instanceof SyntaxError) {\n\t\t\treturn {\n\t\t\t\tmodels: [],\n\t\t\t\terror: `Failed to parse models.json: ${error.message}\\n\\nFile: ${configPath}`,\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tmodels: [],\n\t\t\terror: `Failed to load models.json: ${error instanceof Error ? error.message : error}\\n\\nFile: ${configPath}`,\n\t\t};\n\t}\n}\n\n/**\n * Validate config structure and requirements\n */\nfunction validateConfig(config: ModelsConfig): void {\n\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\tconst hasProviderApi = !!providerConfig.api;\n\n\t\tfor (const modelDef of providerConfig.models) {\n\t\t\tconst hasModelApi = !!modelDef.api;\n\n\t\t\tif (!hasProviderApi && !hasModelApi) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified. ` +\n\t\t\t\t\t\t`Set at provider or model level.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Validate required fields\n\t\t\tif (!modelDef.id) throw new Error(`Provider ${providerName}: model missing \"id\"`);\n\t\t\tif (!modelDef.name) throw new Error(`Provider ${providerName}: model missing \"name\"`);\n\t\t\tif (modelDef.contextWindow <= 0)\n\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);\n\t\t\tif (modelDef.maxTokens <= 0)\n\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);\n\t\t}\n\t}\n}\n\n/**\n * Parse config into Model objects\n */\nfunction parseModels(config: ModelsConfig): Model<Api>[] {\n\tconst models: Model<Api>[] = [];\n\n\t// Clear and rebuild custom provider API key mappings\n\tcustomProviderApiKeys.clear();\n\n\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t// Store API key config for this provider\n\t\tcustomProviderApiKeys.set(providerName, providerConfig.apiKey);\n\n\t\tfor (const modelDef of providerConfig.models) {\n\t\t\t// Model-level api overrides provider-level api\n\t\t\tconst api = modelDef.api || providerConfig.api;\n\n\t\t\tif (!api) {\n\t\t\t\t// This should have been caught by validateConfig, but be safe\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmodels.push({\n\t\t\t\tid: modelDef.id,\n\t\t\t\tname: modelDef.name,\n\t\t\t\tapi: api as Api,\n\t\t\t\tprovider: providerName,\n\t\t\t\tbaseUrl: providerConfig.baseUrl,\n\t\t\t\treasoning: modelDef.reasoning,\n\t\t\t\tinput: modelDef.input as (\"text\" | \"image\")[],\n\t\t\t\tcost: modelDef.cost,\n\t\t\t\tcontextWindow: modelDef.contextWindow,\n\t\t\t\tmaxTokens: modelDef.maxTokens,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn models;\n}\n\n/**\n * Get all models (built-in + custom), freshly loaded\n * Returns { models, error } - either models array or error message\n */\nexport function loadAndMergeModels(): { models: Model<Api>[]; error: string | null } {\n\tconst builtInModels: Model<Api>[] = [];\n\tconst providers = getProviders();\n\n\t// Load all built-in models\n\tfor (const provider of providers) {\n\t\tconst providerModels = getModels(provider as KnownProvider);\n\t\tbuiltInModels.push(...(providerModels as Model<Api>[]));\n\t}\n\n\t// Load custom models\n\tconst { models: customModels, error } = loadCustomModels();\n\n\tif (error) {\n\t\treturn { models: [], error };\n\t}\n\n\t// Merge: custom models come after built-in\n\treturn { models: [...builtInModels, ...customModels], error: null };\n}\n\n/**\n * Get API key for a model (checks custom providers first, then built-in)\n */\nexport function getApiKeyForModel(model: Model<Api>): string | undefined {\n\t// For custom providers, check their apiKey config\n\tconst customKeyConfig = customProviderApiKeys.get(model.provider);\n\tif (customKeyConfig) {\n\t\treturn resolveApiKey(customKeyConfig);\n\t}\n\n\t// For Anthropic, check ANTHROPIC_OAUTH_KEY first\n\tif (model.provider === \"anthropic\") {\n\t\tconst oauthKey = process.env.ANTHROPIC_OAUTH_TOKEN;\n\t\tif (oauthKey) {\n\t\t\treturn oauthKey;\n\t\t}\n\t}\n\n\t// For built-in providers, use getApiKey from @mariozechner/pi-ai\n\treturn getApiKey(model.provider as KnownProvider);\n}\n\n/**\n * Get only models that have valid API keys available\n * Returns { models, error } - either models array or error message\n */\nexport function getAvailableModels(): { models: Model<Api>[]; error: string | null } {\n\tconst { models: allModels, error } = loadAndMergeModels();\n\n\tif (error) {\n\t\treturn { models: [], error };\n\t}\n\n\tconst availableModels = allModels.filter((model) => {\n\t\tconst apiKey = getApiKeyForModel(model);\n\t\treturn !!apiKey;\n\t});\n\n\treturn { models: availableModels, error: null };\n}\n\n/**\n * Find a specific model by provider and ID\n * Returns { model, error } - either model or error message\n */\nexport function findModel(provider: string, modelId: string): { model: Model<Api> | null; error: string | null } {\n\tconst { models: allModels, error } = loadAndMergeModels();\n\n\tif (error) {\n\t\treturn { model: null, error };\n\t}\n\n\tconst model = allModels.find((m) => m.provider === provider && m.id === modelId) || null;\n\treturn { model, error: null };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"model-config.js","sourceRoot":"","sources":["../src/model-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Api, getApiKey, getModels, getProviders, type KnownProvider, type Model } from \"@mariozechner/pi-ai\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport AjvModule from \"ajv\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { getOAuthToken } from \"./oauth/index.js\";\n\n// Handle both default and named exports\nconst Ajv = (AjvModule as any).default || AjvModule;\n\n// Schema for custom model definition\nconst ModelDefinitionSchema = Type.Object({\n\tid: Type.String({ minLength: 1 }),\n\tname: Type.String({ minLength: 1 }),\n\tapi: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai-completions\"),\n\t\t\tType.Literal(\"openai-responses\"),\n\t\t\tType.Literal(\"anthropic-messages\"),\n\t\t\tType.Literal(\"google-generative-ai\"),\n\t\t]),\n\t),\n\treasoning: Type.Boolean(),\n\tinput: Type.Array(Type.Union([Type.Literal(\"text\"), Type.Literal(\"image\")])),\n\tcost: Type.Object({\n\t\tinput: Type.Number(),\n\t\toutput: Type.Number(),\n\t\tcacheRead: Type.Number(),\n\t\tcacheWrite: Type.Number(),\n\t}),\n\tcontextWindow: Type.Number(),\n\tmaxTokens: Type.Number(),\n});\n\nconst ProviderConfigSchema = Type.Object({\n\tbaseUrl: Type.String({ minLength: 1 }),\n\tapiKey: Type.String({ minLength: 1 }),\n\tapi: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai-completions\"),\n\t\t\tType.Literal(\"openai-responses\"),\n\t\t\tType.Literal(\"anthropic-messages\"),\n\t\t\tType.Literal(\"google-generative-ai\"),\n\t\t]),\n\t),\n\tmodels: Type.Array(ModelDefinitionSchema),\n});\n\nconst ModelsConfigSchema = Type.Object({\n\tproviders: Type.Record(Type.String(), ProviderConfigSchema),\n});\n\ntype ModelsConfig = Static<typeof ModelsConfigSchema>;\ntype ProviderConfig = Static<typeof ProviderConfigSchema>;\ntype ModelDefinition = Static<typeof ModelDefinitionSchema>;\n\n// Custom provider API key mappings (provider name -> apiKey config)\nconst customProviderApiKeys: Map<string, string> = new Map();\n\n/**\n * Resolve an API key config value to an actual key.\n * First checks if it's an environment variable, then treats as literal.\n */\nexport function resolveApiKey(keyConfig: string): string | undefined {\n\t// First check if it's an env var name\n\tconst envValue = process.env[keyConfig];\n\tif (envValue) return envValue;\n\n\t// Otherwise treat as literal API key\n\treturn keyConfig;\n}\n\n/**\n * Load custom models from ~/.pi/agent/models.json\n * Returns { models, error } - either models array or error message\n */\nfunction loadCustomModels(): { models: Model<Api>[]; error: string | null } {\n\tconst configPath = join(homedir(), \".pi\", \"agent\", \"models.json\");\n\tif (!existsSync(configPath)) {\n\t\treturn { models: [], error: null };\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(configPath, \"utf-8\");\n\t\tconst config: ModelsConfig = JSON.parse(content);\n\n\t\t// Validate schema\n\t\tconst ajv = new Ajv();\n\t\tconst validate = ajv.compile(ModelsConfigSchema);\n\t\tif (!validate(config)) {\n\t\t\tconst errors =\n\t\t\t\tvalidate.errors?.map((e: any) => ` - ${e.instancePath || \"root\"}: ${e.message}`).join(\"\\n\") ||\n\t\t\t\t\"Unknown schema error\";\n\t\t\treturn {\n\t\t\t\tmodels: [],\n\t\t\t\terror: `Invalid models.json schema:\\n${errors}\\n\\nFile: ${configPath}`,\n\t\t\t};\n\t\t}\n\n\t\t// Additional validation\n\t\ttry {\n\t\t\tvalidateConfig(config);\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tmodels: [],\n\t\t\t\terror: `Invalid models.json: ${error instanceof Error ? error.message : error}\\n\\nFile: ${configPath}`,\n\t\t\t};\n\t\t}\n\n\t\t// Parse models\n\t\treturn { models: parseModels(config), error: null };\n\t} catch (error) {\n\t\tif (error instanceof SyntaxError) {\n\t\t\treturn {\n\t\t\t\tmodels: [],\n\t\t\t\terror: `Failed to parse models.json: ${error.message}\\n\\nFile: ${configPath}`,\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tmodels: [],\n\t\t\terror: `Failed to load models.json: ${error instanceof Error ? error.message : error}\\n\\nFile: ${configPath}`,\n\t\t};\n\t}\n}\n\n/**\n * Validate config structure and requirements\n */\nfunction validateConfig(config: ModelsConfig): void {\n\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\tconst hasProviderApi = !!providerConfig.api;\n\n\t\tfor (const modelDef of providerConfig.models) {\n\t\t\tconst hasModelApi = !!modelDef.api;\n\n\t\t\tif (!hasProviderApi && !hasModelApi) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified. ` +\n\t\t\t\t\t\t`Set at provider or model level.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Validate required fields\n\t\t\tif (!modelDef.id) throw new Error(`Provider ${providerName}: model missing \"id\"`);\n\t\t\tif (!modelDef.name) throw new Error(`Provider ${providerName}: model missing \"name\"`);\n\t\t\tif (modelDef.contextWindow <= 0)\n\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);\n\t\t\tif (modelDef.maxTokens <= 0)\n\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);\n\t\t}\n\t}\n}\n\n/**\n * Parse config into Model objects\n */\nfunction parseModels(config: ModelsConfig): Model<Api>[] {\n\tconst models: Model<Api>[] = [];\n\n\t// Clear and rebuild custom provider API key mappings\n\tcustomProviderApiKeys.clear();\n\n\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t// Store API key config for this provider\n\t\tcustomProviderApiKeys.set(providerName, providerConfig.apiKey);\n\n\t\tfor (const modelDef of providerConfig.models) {\n\t\t\t// Model-level api overrides provider-level api\n\t\t\tconst api = modelDef.api || providerConfig.api;\n\n\t\t\tif (!api) {\n\t\t\t\t// This should have been caught by validateConfig, but be safe\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmodels.push({\n\t\t\t\tid: modelDef.id,\n\t\t\t\tname: modelDef.name,\n\t\t\t\tapi: api as Api,\n\t\t\t\tprovider: providerName,\n\t\t\t\tbaseUrl: providerConfig.baseUrl,\n\t\t\t\treasoning: modelDef.reasoning,\n\t\t\t\tinput: modelDef.input as (\"text\" | \"image\")[],\n\t\t\t\tcost: modelDef.cost,\n\t\t\t\tcontextWindow: modelDef.contextWindow,\n\t\t\t\tmaxTokens: modelDef.maxTokens,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn models;\n}\n\n/**\n * Get all models (built-in + custom), freshly loaded\n * Returns { models, error } - either models array or error message\n */\nexport function loadAndMergeModels(): { models: Model<Api>[]; error: string | null } {\n\tconst builtInModels: Model<Api>[] = [];\n\tconst providers = getProviders();\n\n\t// Load all built-in models\n\tfor (const provider of providers) {\n\t\tconst providerModels = getModels(provider as KnownProvider);\n\t\tbuiltInModels.push(...(providerModels as Model<Api>[]));\n\t}\n\n\t// Load custom models\n\tconst { models: customModels, error } = loadCustomModels();\n\n\tif (error) {\n\t\treturn { models: [], error };\n\t}\n\n\t// Merge: custom models come after built-in\n\treturn { models: [...builtInModels, ...customModels], error: null };\n}\n\n/**\n * Get API key for a model (checks custom providers first, then built-in)\n * Now async to support OAuth token refresh\n */\nexport async function getApiKeyForModel(model: Model<Api>): Promise<string | undefined> {\n\t// For custom providers, check their apiKey config\n\tconst customKeyConfig = customProviderApiKeys.get(model.provider);\n\tif (customKeyConfig) {\n\t\treturn resolveApiKey(customKeyConfig);\n\t}\n\n\t// For Anthropic, check OAuth first\n\tif (model.provider === \"anthropic\") {\n\t\t// 1. Check OAuth storage (auto-refresh if needed)\n\t\tconst oauthToken = await getOAuthToken(\"anthropic\");\n\t\tif (oauthToken) {\n\t\t\treturn oauthToken;\n\t\t}\n\n\t\t// 2. Check ANTHROPIC_OAUTH_TOKEN env var (manual OAuth token)\n\t\tconst oauthEnv = process.env.ANTHROPIC_OAUTH_TOKEN;\n\t\tif (oauthEnv) {\n\t\t\treturn oauthEnv;\n\t\t}\n\n\t\t// 3. Fall back to ANTHROPIC_API_KEY env var\n\t}\n\n\t// For built-in providers, use getApiKey from @mariozechner/pi-ai\n\treturn getApiKey(model.provider as KnownProvider);\n}\n\n/**\n * Get only models that have valid API keys available\n * Returns { models, error } - either models array or error message\n */\nexport async function getAvailableModels(): Promise<{ models: Model<Api>[]; error: string | null }> {\n\tconst { models: allModels, error } = loadAndMergeModels();\n\n\tif (error) {\n\t\treturn { models: [], error };\n\t}\n\n\tconst availableModels: Model<Api>[] = [];\n\tfor (const model of allModels) {\n\t\tconst apiKey = await getApiKeyForModel(model);\n\t\tif (apiKey) {\n\t\t\tavailableModels.push(model);\n\t\t}\n\t}\n\n\treturn { models: availableModels, error: null };\n}\n\n/**\n * Find a specific model by provider and ID\n * Returns { model, error } - either model or error message\n */\nexport function findModel(provider: string, modelId: string): { model: Model<Api> | null; error: string | null } {\n\tconst { models: allModels, error } = loadAndMergeModels();\n\n\tif (error) {\n\t\treturn { model: null, error };\n\t}\n\n\tconst model = allModels.find((m) => m.provider === provider && m.id === modelId) || null;\n\treturn { model, error: null };\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type OAuthCredentials } from "./storage.js";
|
|
2
|
+
/**
|
|
3
|
+
* Login with Anthropic OAuth (device code flow)
|
|
4
|
+
*/
|
|
5
|
+
export declare function loginAnthropic(onAuthUrl: (url: string) => void, onPromptCode: () => Promise<string>): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Refresh Anthropic OAuth token using refresh token
|
|
8
|
+
*/
|
|
9
|
+
export declare function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials>;
|
|
10
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/oauth/anthropic.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,gBAAgB,EAAwB,MAAM,cAAc,CAAC;AAiB3E;;GAEG;AACH,wBAAsB,cAAc,CACnC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAiEf;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiC3F","sourcesContent":["import { createHash, randomBytes } from \"crypto\";\nimport { type OAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\";\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://console.anthropic.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://console.anthropic.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\n/**\n * Generate PKCE code verifier and challenge\n */\nfunction generatePKCE(): { verifier: string; challenge: string } {\n\tconst verifier = randomBytes(32).toString(\"base64url\");\n\tconst challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\treturn { verifier, challenge };\n}\n\n/**\n * Login with Anthropic OAuth (device code flow)\n */\nexport async function loginAnthropic(\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tconst { verifier, challenge } = generatePKCE();\n\n\t// Build authorization URL\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\n\tconst authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;\n\n\t// Notify caller with URL to open\n\tonAuthUrl(authUrl);\n\n\t// Wait for user to paste authorization code (format: code#state)\n\tconst authCode = await onPromptCode();\n\tconst splits = authCode.split(\"#\");\n\tconst code = splits[0];\n\tconst state = splits[1];\n\n\t// Exchange code for tokens\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode: code,\n\t\t\tstate: state,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t// Save credentials\n\tconst credentials: OAuthCredentials = {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n\n\tsaveOAuthCredentials(\"anthropic\", credentials);\n}\n\n/**\n * Refresh Anthropic OAuth token using refresh token\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token refresh failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "crypto";
|
|
2
|
+
import { saveOAuthCredentials } from "./storage.js";
|
|
3
|
+
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
4
|
+
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
5
|
+
const TOKEN_URL = "https://console.anthropic.com/v1/oauth/token";
|
|
6
|
+
const REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
|
|
7
|
+
const SCOPES = "org:create_api_key user:profile user:inference";
|
|
8
|
+
/**
|
|
9
|
+
* Generate PKCE code verifier and challenge
|
|
10
|
+
*/
|
|
11
|
+
function generatePKCE() {
|
|
12
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
13
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
14
|
+
return { verifier, challenge };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Login with Anthropic OAuth (device code flow)
|
|
18
|
+
*/
|
|
19
|
+
export async function loginAnthropic(onAuthUrl, onPromptCode) {
|
|
20
|
+
const { verifier, challenge } = generatePKCE();
|
|
21
|
+
// Build authorization URL
|
|
22
|
+
const authParams = new URLSearchParams({
|
|
23
|
+
code: "true",
|
|
24
|
+
client_id: CLIENT_ID,
|
|
25
|
+
response_type: "code",
|
|
26
|
+
redirect_uri: REDIRECT_URI,
|
|
27
|
+
scope: SCOPES,
|
|
28
|
+
code_challenge: challenge,
|
|
29
|
+
code_challenge_method: "S256",
|
|
30
|
+
state: verifier,
|
|
31
|
+
});
|
|
32
|
+
const authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;
|
|
33
|
+
// Notify caller with URL to open
|
|
34
|
+
onAuthUrl(authUrl);
|
|
35
|
+
// Wait for user to paste authorization code (format: code#state)
|
|
36
|
+
const authCode = await onPromptCode();
|
|
37
|
+
const splits = authCode.split("#");
|
|
38
|
+
const code = splits[0];
|
|
39
|
+
const state = splits[1];
|
|
40
|
+
// Exchange code for tokens
|
|
41
|
+
const tokenResponse = await fetch(TOKEN_URL, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: {
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
grant_type: "authorization_code",
|
|
48
|
+
client_id: CLIENT_ID,
|
|
49
|
+
code: code,
|
|
50
|
+
state: state,
|
|
51
|
+
redirect_uri: REDIRECT_URI,
|
|
52
|
+
code_verifier: verifier,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
if (!tokenResponse.ok) {
|
|
56
|
+
const error = await tokenResponse.text();
|
|
57
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
58
|
+
}
|
|
59
|
+
const tokenData = (await tokenResponse.json());
|
|
60
|
+
// Calculate expiry time (current time + expires_in seconds - 5 min buffer)
|
|
61
|
+
const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
|
|
62
|
+
// Save credentials
|
|
63
|
+
const credentials = {
|
|
64
|
+
type: "oauth",
|
|
65
|
+
refresh: tokenData.refresh_token,
|
|
66
|
+
access: tokenData.access_token,
|
|
67
|
+
expires: expiresAt,
|
|
68
|
+
};
|
|
69
|
+
saveOAuthCredentials("anthropic", credentials);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Refresh Anthropic OAuth token using refresh token
|
|
73
|
+
*/
|
|
74
|
+
export async function refreshAnthropicToken(refreshToken) {
|
|
75
|
+
const tokenResponse = await fetch(TOKEN_URL, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
grant_type: "refresh_token",
|
|
82
|
+
client_id: CLIENT_ID,
|
|
83
|
+
refresh_token: refreshToken,
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
if (!tokenResponse.ok) {
|
|
87
|
+
const error = await tokenResponse.text();
|
|
88
|
+
throw new Error(`Token refresh failed: ${error}`);
|
|
89
|
+
}
|
|
90
|
+
const tokenData = (await tokenResponse.json());
|
|
91
|
+
// Calculate expiry time (current time + expires_in seconds - 5 min buffer)
|
|
92
|
+
const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
|
|
93
|
+
return {
|
|
94
|
+
type: "oauth",
|
|
95
|
+
refresh: tokenData.refresh_token,
|
|
96
|
+
access: tokenData.access_token,
|
|
97
|
+
expires: expiresAt,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/oauth/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from \"crypto\";\nimport { type OAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\";\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://console.anthropic.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://console.anthropic.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\n/**\n * Generate PKCE code verifier and challenge\n */\nfunction generatePKCE(): { verifier: string; challenge: string } {\n\tconst verifier = randomBytes(32).toString(\"base64url\");\n\tconst challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\treturn { verifier, challenge };\n}\n\n/**\n * Login with Anthropic OAuth (device code flow)\n */\nexport async function loginAnthropic(\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tconst { verifier, challenge } = generatePKCE();\n\n\t// Build authorization URL\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\n\tconst authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;\n\n\t// Notify caller with URL to open\n\tonAuthUrl(authUrl);\n\n\t// Wait for user to paste authorization code (format: code#state)\n\tconst authCode = await onPromptCode();\n\tconst splits = authCode.split(\"#\");\n\tconst code = splits[0];\n\tconst state = splits[1];\n\n\t// Exchange code for tokens\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode: code,\n\t\t\tstate: state,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t// Save credentials\n\tconst credentials: OAuthCredentials = {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n\n\tsaveOAuthCredentials(\"anthropic\", credentials);\n}\n\n/**\n * Refresh Anthropic OAuth token using refresh token\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token refresh failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { listOAuthProviders as listOAuthProvidersFromStorage } from "./storage.js";
|
|
2
|
+
export { listOAuthProvidersFromStorage as listOAuthProviders };
|
|
3
|
+
export type SupportedOAuthProvider = "anthropic" | "github-copilot";
|
|
4
|
+
export interface OAuthProviderInfo {
|
|
5
|
+
id: SupportedOAuthProvider;
|
|
6
|
+
name: string;
|
|
7
|
+
available: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get list of OAuth providers
|
|
11
|
+
*/
|
|
12
|
+
export declare function getOAuthProviders(): OAuthProviderInfo[];
|
|
13
|
+
/**
|
|
14
|
+
* Login with OAuth provider
|
|
15
|
+
*/
|
|
16
|
+
export declare function login(provider: SupportedOAuthProvider, onAuthUrl: (url: string) => void, onPromptCode: () => Promise<string>): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Logout from OAuth provider
|
|
19
|
+
*/
|
|
20
|
+
export declare function logout(provider: SupportedOAuthProvider): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Refresh OAuth token for provider
|
|
23
|
+
*/
|
|
24
|
+
export declare function refreshToken(provider: SupportedOAuthProvider): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Get OAuth token for provider (auto-refreshes if expired)
|
|
27
|
+
*/
|
|
28
|
+
export declare function getOAuthToken(provider: SupportedOAuthProvider): Promise<string | null>;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,kBAAkB,IAAI,6BAA6B,EAKnD,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,6BAA6B,IAAI,kBAAkB,EAAE,CAAC;AAE/D,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,sBAAsB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,EAAE,CAavD;AAED;;GAEG;AACH,wBAAsB,KAAK,CAC1B,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBpF;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoB5F","sourcesContent":["import { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\nimport {\n\tlistOAuthProviders as listOAuthProvidersFromStorage,\n\tloadOAuthCredentials,\n\ttype OAuthCredentials,\n\tremoveOAuthCredentials,\n\tsaveOAuthCredentials,\n} from \"./storage.js\";\n\n// Re-export for convenience\nexport { listOAuthProvidersFromStorage as listOAuthProviders };\n\nexport type SupportedOAuthProvider = \"anthropic\" | \"github-copilot\";\n\nexport interface OAuthProviderInfo {\n\tid: SupportedOAuthProvider;\n\tname: string;\n\tavailable: boolean;\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot (coming soon)\",\n\t\t\tavailable: false,\n\t\t},\n\t];\n}\n\n/**\n * Login with OAuth provider\n */\nexport async function login(\n\tprovider: SupportedOAuthProvider,\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tawait loginAnthropic(onAuthUrl, onPromptCode);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tthrow new Error(\"GitHub Copilot OAuth is not yet implemented\");\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n}\n\n/**\n * Logout from OAuth provider\n */\nexport async function logout(provider: SupportedOAuthProvider): Promise<void> {\n\tremoveOAuthCredentials(provider);\n}\n\n/**\n * Refresh OAuth token for provider\n */\nexport async function refreshToken(provider: SupportedOAuthProvider): Promise<string> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tthrow new Error(\"GitHub Copilot OAuth is not yet implemented\");\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\t// Save new credentials\n\tsaveOAuthCredentials(provider, newCredentials);\n\n\treturn newCredentials.access;\n}\n\n/**\n * Get OAuth token for provider (auto-refreshes if expired)\n */\nexport async function getOAuthToken(provider: SupportedOAuthProvider): Promise<string | null> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\treturn null;\n\t}\n\n\t// Check if token is expired (with 5 min buffer already applied)\n\tif (Date.now() >= credentials.expires) {\n\t\t// Token expired - refresh it\n\t\ttry {\n\t\t\treturn await refreshToken(provider);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to refresh OAuth token for ${provider}:`, error);\n\t\t\t// Remove invalid credentials\n\t\t\tremoveOAuthCredentials(provider);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\treturn credentials.access;\n}\n"]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
|
|
2
|
+
import { listOAuthProviders as listOAuthProvidersFromStorage, loadOAuthCredentials, removeOAuthCredentials, saveOAuthCredentials, } from "./storage.js";
|
|
3
|
+
// Re-export for convenience
|
|
4
|
+
export { listOAuthProvidersFromStorage as listOAuthProviders };
|
|
5
|
+
/**
|
|
6
|
+
* Get list of OAuth providers
|
|
7
|
+
*/
|
|
8
|
+
export function getOAuthProviders() {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
id: "anthropic",
|
|
12
|
+
name: "Anthropic (Claude Pro/Max)",
|
|
13
|
+
available: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "github-copilot",
|
|
17
|
+
name: "GitHub Copilot (coming soon)",
|
|
18
|
+
available: false,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Login with OAuth provider
|
|
24
|
+
*/
|
|
25
|
+
export async function login(provider, onAuthUrl, onPromptCode) {
|
|
26
|
+
switch (provider) {
|
|
27
|
+
case "anthropic":
|
|
28
|
+
await loginAnthropic(onAuthUrl, onPromptCode);
|
|
29
|
+
break;
|
|
30
|
+
case "github-copilot":
|
|
31
|
+
throw new Error("GitHub Copilot OAuth is not yet implemented");
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Logout from OAuth provider
|
|
38
|
+
*/
|
|
39
|
+
export async function logout(provider) {
|
|
40
|
+
removeOAuthCredentials(provider);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Refresh OAuth token for provider
|
|
44
|
+
*/
|
|
45
|
+
export async function refreshToken(provider) {
|
|
46
|
+
const credentials = loadOAuthCredentials(provider);
|
|
47
|
+
if (!credentials) {
|
|
48
|
+
throw new Error(`No OAuth credentials found for ${provider}`);
|
|
49
|
+
}
|
|
50
|
+
let newCredentials;
|
|
51
|
+
switch (provider) {
|
|
52
|
+
case "anthropic":
|
|
53
|
+
newCredentials = await refreshAnthropicToken(credentials.refresh);
|
|
54
|
+
break;
|
|
55
|
+
case "github-copilot":
|
|
56
|
+
throw new Error("GitHub Copilot OAuth is not yet implemented");
|
|
57
|
+
default:
|
|
58
|
+
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
59
|
+
}
|
|
60
|
+
// Save new credentials
|
|
61
|
+
saveOAuthCredentials(provider, newCredentials);
|
|
62
|
+
return newCredentials.access;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get OAuth token for provider (auto-refreshes if expired)
|
|
66
|
+
*/
|
|
67
|
+
export async function getOAuthToken(provider) {
|
|
68
|
+
const credentials = loadOAuthCredentials(provider);
|
|
69
|
+
if (!credentials) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// Check if token is expired (with 5 min buffer already applied)
|
|
73
|
+
if (Date.now() >= credentials.expires) {
|
|
74
|
+
// Token expired - refresh it
|
|
75
|
+
try {
|
|
76
|
+
return await refreshToken(provider);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error(`Failed to refresh OAuth token for ${provider}:`, error);
|
|
80
|
+
// Remove invalid credentials
|
|
81
|
+
removeOAuthCredentials(provider);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return credentials.access;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EACN,kBAAkB,IAAI,6BAA6B,EACnD,oBAAoB,EAEpB,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,cAAc,CAAC;AAEtB,4BAA4B;AAC5B,OAAO,EAAE,6BAA6B,IAAI,kBAAkB,EAAE,CAAC;AAU/D;;GAEG;AACH,MAAM,UAAU,iBAAiB,GAAwB;IACxD,OAAO;QACN;YACC,EAAE,EAAE,WAAW;YACf,IAAI,EAAE,4BAA4B;YAClC,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,gBAAgB;YACpB,IAAI,EAAE,8BAA8B;YACpC,SAAS,EAAE,KAAK;SAChB;KACD,CAAC;AAAA,CACF;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAC1B,QAAgC,EAChC,SAAgC,EAChC,YAAmC,EACnB;IAChB,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,WAAW;YACf,MAAM,cAAc,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;YAC9C,MAAM;QACP,KAAK,gBAAgB;YACpB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAChE;YACC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;AAAA,CACD;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,QAAgC,EAAiB;IAC7E,sBAAsB,CAAC,QAAQ,CAAC,CAAC;AAAA,CACjC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgC,EAAmB;IACrF,MAAM,WAAW,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,cAAgC,CAAC;IAErC,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,WAAW;YACf,cAAc,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClE,MAAM;QACP,KAAK,gBAAgB;YACpB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAChE;YACC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,uBAAuB;IACvB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAE/C,OAAO,cAAc,CAAC,MAAM,CAAC;AAAA,CAC7B;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgC,EAA0B;IAC7F,MAAM,WAAW,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,gEAAgE;IAChE,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QACvC,6BAA6B;QAC7B,IAAI,CAAC;YACJ,OAAO,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,qCAAqC,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;YACvE,6BAA6B;YAC7B,sBAAsB,CAAC,QAAQ,CAAC,CAAC;YACjC,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,OAAO,WAAW,CAAC,MAAM,CAAC;AAAA,CAC1B","sourcesContent":["import { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\nimport {\n\tlistOAuthProviders as listOAuthProvidersFromStorage,\n\tloadOAuthCredentials,\n\ttype OAuthCredentials,\n\tremoveOAuthCredentials,\n\tsaveOAuthCredentials,\n} from \"./storage.js\";\n\n// Re-export for convenience\nexport { listOAuthProvidersFromStorage as listOAuthProviders };\n\nexport type SupportedOAuthProvider = \"anthropic\" | \"github-copilot\";\n\nexport interface OAuthProviderInfo {\n\tid: SupportedOAuthProvider;\n\tname: string;\n\tavailable: boolean;\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot (coming soon)\",\n\t\t\tavailable: false,\n\t\t},\n\t];\n}\n\n/**\n * Login with OAuth provider\n */\nexport async function login(\n\tprovider: SupportedOAuthProvider,\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tawait loginAnthropic(onAuthUrl, onPromptCode);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tthrow new Error(\"GitHub Copilot OAuth is not yet implemented\");\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n}\n\n/**\n * Logout from OAuth provider\n */\nexport async function logout(provider: SupportedOAuthProvider): Promise<void> {\n\tremoveOAuthCredentials(provider);\n}\n\n/**\n * Refresh OAuth token for provider\n */\nexport async function refreshToken(provider: SupportedOAuthProvider): Promise<string> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tthrow new Error(\"GitHub Copilot OAuth is not yet implemented\");\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\t// Save new credentials\n\tsaveOAuthCredentials(provider, newCredentials);\n\n\treturn newCredentials.access;\n}\n\n/**\n * Get OAuth token for provider (auto-refreshes if expired)\n */\nexport async function getOAuthToken(provider: SupportedOAuthProvider): Promise<string | null> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\treturn null;\n\t}\n\n\t// Check if token is expired (with 5 min buffer already applied)\n\tif (Date.now() >= credentials.expires) {\n\t\t// Token expired - refresh it\n\t\ttry {\n\t\t\treturn await refreshToken(provider);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to refresh OAuth token for ${provider}:`, error);\n\t\t\t// Remove invalid credentials\n\t\t\tremoveOAuthCredentials(provider);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\treturn credentials.access;\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface OAuthCredentials {
|
|
2
|
+
type: "oauth";
|
|
3
|
+
refresh: string;
|
|
4
|
+
access: string;
|
|
5
|
+
expires: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Load OAuth credentials for a specific provider
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadOAuthCredentials(provider: string): OAuthCredentials | null;
|
|
11
|
+
/**
|
|
12
|
+
* Save OAuth credentials for a specific provider
|
|
13
|
+
*/
|
|
14
|
+
export declare function saveOAuthCredentials(provider: string, creds: OAuthCredentials): void;
|
|
15
|
+
/**
|
|
16
|
+
* Remove OAuth credentials for a specific provider
|
|
17
|
+
*/
|
|
18
|
+
export declare function removeOAuthCredentials(provider: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* List all providers with OAuth credentials
|
|
21
|
+
*/
|
|
22
|
+
export declare function listOAuthProviders(): string[];
|
|
23
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/oauth/storage.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB;AAqDD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAG9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAIpF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAI7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAG7C","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface OAuthCredentials {\n\ttype: \"oauth\";\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n}\n\ninterface OAuthStorageFormat {\n\t[provider: string]: OAuthCredentials;\n}\n\n/**\n * Get path to oauth.json\n */\nfunction getOAuthFilePath(): string {\n\tconst configDir = join(homedir(), \".pi\", \"agent\");\n\treturn join(configDir, \"oauth.json\");\n}\n\n/**\n * Ensure the config directory exists\n */\nfunction ensureConfigDir(): void {\n\tconst configDir = join(homedir(), \".pi\", \"agent\");\n\tif (!existsSync(configDir)) {\n\t\tmkdirSync(configDir, { recursive: true, mode: 0o700 });\n\t}\n}\n\n/**\n * Load all OAuth credentials from oauth.json\n */\nfunction loadStorage(): OAuthStorageFormat {\n\tconst filePath = getOAuthFilePath();\n\tif (!existsSync(filePath)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\treturn JSON.parse(content);\n\t} catch (error) {\n\t\tconsole.error(`Warning: Failed to load OAuth credentials: ${error}`);\n\t\treturn {};\n\t}\n}\n\n/**\n * Save all OAuth credentials to oauth.json\n */\nfunction saveStorage(storage: OAuthStorageFormat): void {\n\tensureConfigDir();\n\tconst filePath = getOAuthFilePath();\n\twriteFileSync(filePath, JSON.stringify(storage, null, 2), \"utf-8\");\n\t// Set permissions to owner read/write only\n\tchmodSync(filePath, 0o600);\n}\n\n/**\n * Load OAuth credentials for a specific provider\n */\nexport function loadOAuthCredentials(provider: string): OAuthCredentials | null {\n\tconst storage = loadStorage();\n\treturn storage[provider] || null;\n}\n\n/**\n * Save OAuth credentials for a specific provider\n */\nexport function saveOAuthCredentials(provider: string, creds: OAuthCredentials): void {\n\tconst storage = loadStorage();\n\tstorage[provider] = creds;\n\tsaveStorage(storage);\n}\n\n/**\n * Remove OAuth credentials for a specific provider\n */\nexport function removeOAuthCredentials(provider: string): void {\n\tconst storage = loadStorage();\n\tdelete storage[provider];\n\tsaveStorage(storage);\n}\n\n/**\n * List all providers with OAuth credentials\n */\nexport function listOAuthProviders(): string[] {\n\tconst storage = loadStorage();\n\treturn Object.keys(storage);\n}\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
/**
|
|
5
|
+
* Get path to oauth.json
|
|
6
|
+
*/
|
|
7
|
+
function getOAuthFilePath() {
|
|
8
|
+
const configDir = join(homedir(), ".pi", "agent");
|
|
9
|
+
return join(configDir, "oauth.json");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Ensure the config directory exists
|
|
13
|
+
*/
|
|
14
|
+
function ensureConfigDir() {
|
|
15
|
+
const configDir = join(homedir(), ".pi", "agent");
|
|
16
|
+
if (!existsSync(configDir)) {
|
|
17
|
+
mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Load all OAuth credentials from oauth.json
|
|
22
|
+
*/
|
|
23
|
+
function loadStorage() {
|
|
24
|
+
const filePath = getOAuthFilePath();
|
|
25
|
+
if (!existsSync(filePath)) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(filePath, "utf-8");
|
|
30
|
+
return JSON.parse(content);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error(`Warning: Failed to load OAuth credentials: ${error}`);
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Save all OAuth credentials to oauth.json
|
|
39
|
+
*/
|
|
40
|
+
function saveStorage(storage) {
|
|
41
|
+
ensureConfigDir();
|
|
42
|
+
const filePath = getOAuthFilePath();
|
|
43
|
+
writeFileSync(filePath, JSON.stringify(storage, null, 2), "utf-8");
|
|
44
|
+
// Set permissions to owner read/write only
|
|
45
|
+
chmodSync(filePath, 0o600);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Load OAuth credentials for a specific provider
|
|
49
|
+
*/
|
|
50
|
+
export function loadOAuthCredentials(provider) {
|
|
51
|
+
const storage = loadStorage();
|
|
52
|
+
return storage[provider] || null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Save OAuth credentials for a specific provider
|
|
56
|
+
*/
|
|
57
|
+
export function saveOAuthCredentials(provider, creds) {
|
|
58
|
+
const storage = loadStorage();
|
|
59
|
+
storage[provider] = creds;
|
|
60
|
+
saveStorage(storage);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Remove OAuth credentials for a specific provider
|
|
64
|
+
*/
|
|
65
|
+
export function removeOAuthCredentials(provider) {
|
|
66
|
+
const storage = loadStorage();
|
|
67
|
+
delete storage[provider];
|
|
68
|
+
saveStorage(storage);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* List all providers with OAuth credentials
|
|
72
|
+
*/
|
|
73
|
+
export function listOAuthProviders() {
|
|
74
|
+
const storage = loadStorage();
|
|
75
|
+
return Object.keys(storage);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/oauth/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACnF,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAa5B;;GAEG;AACH,SAAS,gBAAgB,GAAW;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AAAA,CACrC;AAED;;GAEG;AACH,SAAS,eAAe,GAAS;IAChC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,CAAC;AAAA,CACD;AAED;;GAEG;AACH,SAAS,WAAW,GAAuB;IAC1C,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,8CAA8C,KAAK,EAAE,CAAC,CAAC;QACrE,OAAO,EAAE,CAAC;IACX,CAAC;AAAA,CACD;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,OAA2B,EAAQ;IACvD,eAAe,EAAE,CAAC;IAClB,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACnE,2CAA2C;IAC3C,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAAA,CAC3B;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAA2B;IAC/E,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;AAAA,CACjC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,KAAuB,EAAQ;IACrF,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;IAC1B,WAAW,CAAC,OAAO,CAAC,CAAC;AAAA,CACrB;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB,EAAQ;IAC9D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzB,WAAW,CAAC,OAAO,CAAC,CAAC;AAAA,CACrB;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,GAAa;IAC9C,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAAA,CAC5B","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nexport interface OAuthCredentials {\n\ttype: \"oauth\";\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n}\n\ninterface OAuthStorageFormat {\n\t[provider: string]: OAuthCredentials;\n}\n\n/**\n * Get path to oauth.json\n */\nfunction getOAuthFilePath(): string {\n\tconst configDir = join(homedir(), \".pi\", \"agent\");\n\treturn join(configDir, \"oauth.json\");\n}\n\n/**\n * Ensure the config directory exists\n */\nfunction ensureConfigDir(): void {\n\tconst configDir = join(homedir(), \".pi\", \"agent\");\n\tif (!existsSync(configDir)) {\n\t\tmkdirSync(configDir, { recursive: true, mode: 0o700 });\n\t}\n}\n\n/**\n * Load all OAuth credentials from oauth.json\n */\nfunction loadStorage(): OAuthStorageFormat {\n\tconst filePath = getOAuthFilePath();\n\tif (!existsSync(filePath)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\treturn JSON.parse(content);\n\t} catch (error) {\n\t\tconsole.error(`Warning: Failed to load OAuth credentials: ${error}`);\n\t\treturn {};\n\t}\n}\n\n/**\n * Save all OAuth credentials to oauth.json\n */\nfunction saveStorage(storage: OAuthStorageFormat): void {\n\tensureConfigDir();\n\tconst filePath = getOAuthFilePath();\n\twriteFileSync(filePath, JSON.stringify(storage, null, 2), \"utf-8\");\n\t// Set permissions to owner read/write only\n\tchmodSync(filePath, 0o600);\n}\n\n/**\n * Load OAuth credentials for a specific provider\n */\nexport function loadOAuthCredentials(provider: string): OAuthCredentials | null {\n\tconst storage = loadStorage();\n\treturn storage[provider] || null;\n}\n\n/**\n * Save OAuth credentials for a specific provider\n */\nexport function saveOAuthCredentials(provider: string, creds: OAuthCredentials): void {\n\tconst storage = loadStorage();\n\tstorage[provider] = creds;\n\tsaveStorage(storage);\n}\n\n/**\n * Remove OAuth credentials for a specific provider\n */\nexport function removeOAuthCredentials(provider: string): void {\n\tconst storage = loadStorage();\n\tdelete storage[provider];\n\tsaveStorage(storage);\n}\n\n/**\n * List all providers with OAuth credentials\n */\nexport function listOAuthProviders(): string[] {\n\tconst storage = loadStorage();\n\treturn Object.keys(storage);\n}\n"]}
|
package/dist/tools/bash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AA+DrD,QAAA,MAAM,UAAU;;;EAGd,CAAC;AAEH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AA+DrD,QAAA,MAAM,UAAU;;;EAGd,CAAC;AAEH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CAuHjD,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport { existsSync } from \"fs\";\n\n/**\n * Get shell configuration based on platform\n */\nfunction getShellConfig(): { shell: string; args: string[] } {\n\tif (process.platform === \"win32\") {\n\t\tconst paths: string[] = [];\n\t\tconst programFiles = process.env.ProgramFiles;\n\t\tif (programFiles) {\n\t\t\tpaths.push(`${programFiles}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\t\tconst programFilesX86 = process.env[\"ProgramFiles(x86)\"];\n\t\tif (programFilesX86) {\n\t\t\tpaths.push(`${programFilesX86}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\n\t\tfor (const path of paths) {\n\t\t\tif (existsSync(path)) {\n\t\t\t\treturn { shell: path, args: [\"-c\"] };\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Git Bash not found. Please install Git for Windows from https://git-scm.com/download/win\\n` +\n\t\t\t\t`Searched in:\\n${paths.map((p) => ` ${p}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\treturn { shell: \"sh\", args: [\"-c\"] };\n}\n\n/**\n * Kill a process and all its children\n */\nfunction killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use taskkill on Windows to kill process tree\n\t\ttry {\n\t\t\tspawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], {\n\t\t\t\tstdio: \"ignore\",\n\t\t\t\tdetached: true,\n\t\t\t});\n\t\t} catch (e) {\n\t\t\t// Ignore errors if taskkill fails\n\t\t}\n\t} else {\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch (e) {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch (e2) {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nexport const bashTool: AgentTool<typeof bashSchema> = {\n\tname: \"bash\",\n\tlabel: \"bash\",\n\tdescription:\n\t\t\"Execute a bash command in the current working directory. Returns stdout and stderr. Optionally provide a timeout in seconds.\",\n\tparameters: bashSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, _reject) => {\n\t\t\tconst { shell, args } = getShellConfig();\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tdetached: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\tlet stdout = \"\";\n\t\t\tlet stderr = \"\";\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout if provided\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (timeout !== undefined && timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tonAbort();\n\t\t\t\t}, timeout * 1000);\n\t\t\t}\n\n\t\t\t// Collect stdout\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", (data) => {\n\t\t\t\t\tstdout += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stdout.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstdout = stdout.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Collect stderr\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", (data) => {\n\t\t\t\t\tstderr += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stderr.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstderr = stderr.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tif (timeoutHandle) {\n\t\t\t\t\tclearTimeout(timeoutHandle);\n\t\t\t\t}\n\t\t\t\tif (signal) {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t}\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\t_reject(new Error(output));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += `Command timed out after ${timeout} seconds`;\n\t\t\t\t\t_reject(new Error(output));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet output = \"\";\n\t\t\t\tif (stdout) output += stdout;\n\t\t\t\tif (stderr) {\n\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\toutput += stderr;\n\t\t\t\t}\n\n\t\t\t\tif (code !== 0 && code !== null) {\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\t_reject(new Error(`${output}Command exited with code ${code}`));\n\t\t\t\t} else {\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: output || \"(no output)\" }], details: undefined });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\tkillProcessTree(child.pid);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n};\n"]}
|
package/dist/tools/bash.js
CHANGED
|
@@ -124,7 +124,7 @@ export const bashTool = {
|
|
|
124
124
|
if (output)
|
|
125
125
|
output += "\n\n";
|
|
126
126
|
output += "Command aborted";
|
|
127
|
-
|
|
127
|
+
_reject(new Error(output));
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
130
|
if (timedOut) {
|
|
@@ -139,7 +139,7 @@ export const bashTool = {
|
|
|
139
139
|
if (output)
|
|
140
140
|
output += "\n\n";
|
|
141
141
|
output += `Command timed out after ${timeout} seconds`;
|
|
142
|
-
|
|
142
|
+
_reject(new Error(output));
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
let output = "";
|
|
@@ -153,10 +153,7 @@ export const bashTool = {
|
|
|
153
153
|
if (code !== 0 && code !== null) {
|
|
154
154
|
if (output)
|
|
155
155
|
output += "\n\n";
|
|
156
|
-
|
|
157
|
-
content: [{ type: "text", text: `Command failed\n\n${output}Command exited with code ${code}` }],
|
|
158
|
-
details: undefined,
|
|
159
|
-
});
|
|
156
|
+
_reject(new Error(`${output}Command exited with code ${code}`));
|
|
160
157
|
}
|
|
161
158
|
else {
|
|
162
159
|
resolve({ content: [{ type: "text", text: output || "(no output)" }], details: undefined });
|