@mariozechner/pi-coding-agent 0.7.25 → 0.7.27
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 +39 -0
- package/dist/model-config.d.ts.map +1 -1
- package/dist/model-config.js +5 -0
- package/dist/model-config.js.map +1 -1
- package/dist/tui/custom-editor.d.ts +1 -0
- package/dist/tui/custom-editor.d.ts.map +1 -1
- package/dist/tui/custom-editor.js +6 -0
- package/dist/tui/custom-editor.js.map +1 -1
- package/dist/tui/tool-execution.d.ts +2 -0
- package/dist/tui/tool-execution.d.ts.map +1 -1
- package/dist/tui/tool-execution.js +8 -3
- package/dist/tui/tool-execution.js.map +1 -1
- package/dist/tui/tui-renderer.d.ts +2 -0
- package/dist/tui/tui-renderer.d.ts.map +1 -1
- package/dist/tui/tui-renderer.js +18 -0
- package/dist/tui/tui-renderer.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.27] - 2025-11-20
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Slash Command Submission**: Fixed issue where slash commands required two Enter presses to execute. Now pressing Enter on a slash command autocomplete suggestion immediately submits the command, while Tab still applies the completion for adding arguments. ([#30](https://github.com/badlogic/pi-mono/issues/30))
|
|
10
|
+
- **Slash Command Autocomplete**: Fixed issue where typing a typo then correcting it would not show autocomplete suggestions. Autocomplete now re-triggers when typing or backspacing in a slash command context. ([#29](https://github.com/badlogic/pi-mono/issues/29))
|
|
11
|
+
|
|
12
|
+
## [0.7.26] - 2025-11-20
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Tool Output Expansion**: Press `Ctrl+O` to toggle between collapsed and expanded tool output display. Expands all tool call outputs (bash, read, write, etc.) to show full content instead of truncated previews. ([#31](https://github.com/badlogic/pi-mono/issues/31))
|
|
17
|
+
- **Custom Headers**: Added support for custom HTTP headers in `models.json` configuration. Headers can be specified at both provider and model level, with model-level headers overriding provider-level ones. This enables bypassing Cloudflare bot detection and other proxy requirements. ([#39](https://github.com/badlogic/pi-mono/issues/39))
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Chutes AI Provider**: Fixed 400 errors when using Chutes AI provider. Added compatibility fixes for `store` field exclusion, `max_tokens` parameter usage, and system prompt role handling. ([#42](https://github.com/badlogic/pi-mono/pull/42) by [@butelo](https://github.com/butelo))
|
|
22
|
+
- **Mistral/Chutes Syntax Error**: Fixed syntax error in merged PR that used `iif` instead of `if`.
|
|
23
|
+
- **Anthropic OAuth Bug**: Fixed bug where `process.env.ANTHROPIC_API_KEY = undefined` set the env var to string "undefined" instead of deleting it. Now uses `delete` operator.
|
|
24
|
+
|
|
5
25
|
## [0.7.25] - 2025-11-20
|
|
6
26
|
|
|
7
27
|
### Added
|
package/README.md
CHANGED
|
@@ -206,6 +206,44 @@ This allows both secure env var usage and literal keys for local servers.
|
|
|
206
206
|
|
|
207
207
|
This is useful when a provider supports multiple API standards through the same base URL.
|
|
208
208
|
|
|
209
|
+
### Custom Headers
|
|
210
|
+
|
|
211
|
+
You can add custom HTTP headers to bypass Cloudflare bot detection, add authentication tokens, or meet other proxy requirements:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"providers": {
|
|
216
|
+
"custom-proxy": {
|
|
217
|
+
"baseUrl": "https://proxy.example.com/v1",
|
|
218
|
+
"apiKey": "YOUR_API_KEY",
|
|
219
|
+
"api": "anthropic-messages",
|
|
220
|
+
"headers": {
|
|
221
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
|
|
222
|
+
"X-Custom-Auth": "bearer-token-here"
|
|
223
|
+
},
|
|
224
|
+
"models": [
|
|
225
|
+
{
|
|
226
|
+
"id": "claude-sonnet-4",
|
|
227
|
+
"name": "Claude Sonnet 4 (Proxied)",
|
|
228
|
+
"reasoning": true,
|
|
229
|
+
"input": ["text", "image"],
|
|
230
|
+
"cost": {"input": 3, "output": 15, "cacheRead": 0.3, "cacheWrite": 3.75},
|
|
231
|
+
"contextWindow": 200000,
|
|
232
|
+
"maxTokens": 8192,
|
|
233
|
+
"headers": {
|
|
234
|
+
"X-Model-Specific-Header": "value"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- **Provider-level `headers`**: Applied to all requests for models in that provider
|
|
244
|
+
- **Model-level `headers`**: Additional headers for specific models (merged with provider headers)
|
|
245
|
+
- Model headers override provider headers when keys conflict
|
|
246
|
+
|
|
209
247
|
### Model Selection Priority
|
|
210
248
|
|
|
211
249
|
When starting `pi`, models are selected in this order:
|
|
@@ -358,6 +396,7 @@ Paste multiple lines of text (e.g., code snippets, logs) and they'll be automati
|
|
|
358
396
|
- **Tab**: Path completion
|
|
359
397
|
- **Shift+Tab**: Cycle thinking level (for reasoning-capable models)
|
|
360
398
|
- **Ctrl+P**: Cycle models (use `--models` to scope)
|
|
399
|
+
- **Ctrl+O**: Toggle tool output expansion (collapsed ↔ full output)
|
|
361
400
|
- **Enter**: Send message
|
|
362
401
|
- **Shift+Enter**: Insert new line (multi-line input)
|
|
363
402
|
- **Backspace**: Delete character backwards
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-config.d.ts","sourceRoot":"","sources":["../src/model-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAA0D,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AA4DnH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOnE;AA2HD;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI;IAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAmBnF;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0BtF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAgBlG;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAS/G","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"]}
|
|
1
|
+
{"version":3,"file":"model-config.d.ts","sourceRoot":"","sources":["../src/model-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAA0D,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AA8DnH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOnE;AAgID;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI;IAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAmBnF;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0BtF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAgBlG;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAS/G","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\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\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\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\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\t// Merge headers: provider headers are base, model headers override\n\t\t\tconst headers =\n\t\t\t\tproviderConfig.headers || modelDef.headers ? { ...providerConfig.headers, ...modelDef.headers } : undefined;\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\theaders,\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"]}
|
package/dist/model-config.js
CHANGED
|
@@ -27,6 +27,7 @@ const ModelDefinitionSchema = Type.Object({
|
|
|
27
27
|
}),
|
|
28
28
|
contextWindow: Type.Number(),
|
|
29
29
|
maxTokens: Type.Number(),
|
|
30
|
+
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
30
31
|
});
|
|
31
32
|
const ProviderConfigSchema = Type.Object({
|
|
32
33
|
baseUrl: Type.String({ minLength: 1 }),
|
|
@@ -37,6 +38,7 @@ const ProviderConfigSchema = Type.Object({
|
|
|
37
38
|
Type.Literal("anthropic-messages"),
|
|
38
39
|
Type.Literal("google-generative-ai"),
|
|
39
40
|
])),
|
|
41
|
+
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
40
42
|
models: Type.Array(ModelDefinitionSchema),
|
|
41
43
|
});
|
|
42
44
|
const ModelsConfigSchema = Type.Object({
|
|
@@ -146,6 +148,8 @@ function parseModels(config) {
|
|
|
146
148
|
// This should have been caught by validateConfig, but be safe
|
|
147
149
|
continue;
|
|
148
150
|
}
|
|
151
|
+
// Merge headers: provider headers are base, model headers override
|
|
152
|
+
const headers = providerConfig.headers || modelDef.headers ? { ...providerConfig.headers, ...modelDef.headers } : undefined;
|
|
149
153
|
models.push({
|
|
150
154
|
id: modelDef.id,
|
|
151
155
|
name: modelDef.name,
|
|
@@ -157,6 +161,7 @@ function parseModels(config) {
|
|
|
157
161
|
cost: modelDef.cost,
|
|
158
162
|
contextWindow: modelDef.contextWindow,
|
|
159
163
|
maxTokens: modelDef.maxTokens,
|
|
164
|
+
headers,
|
|
160
165
|
});
|
|
161
166
|
}
|
|
162
167
|
}
|
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\";\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"]}
|
|
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\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\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\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\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\t// Merge headers: provider headers are base, model headers override\n\t\t\tconst headers =\n\t\t\t\tproviderConfig.headers || modelDef.headers ? { ...providerConfig.headers, ...modelDef.headers } : undefined;\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\theaders,\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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../src/tui/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../src/tui/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAkC9B;CACD","sourcesContent":["import { Editor } from \"@mariozechner/pi-tui\";\n\n/**\n * Custom editor that handles Escape and Ctrl+C keys for coding-agent\n */\nexport class CustomEditor extends Editor {\n\tpublic onEscape?: () => void;\n\tpublic onCtrlC?: () => void;\n\tpublic onShiftTab?: () => void;\n\tpublic onCtrlP?: () => void;\n\tpublic onCtrlO?: () => void;\n\n\thandleInput(data: string): void {\n\t\t// Intercept Ctrl+O for tool output expansion\n\t\tif (data === \"\\x0f\" && this.onCtrlO) {\n\t\t\tthis.onCtrlO();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Ctrl+P for model cycling\n\t\tif (data === \"\\x10\" && this.onCtrlP) {\n\t\t\tthis.onCtrlP();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Shift+Tab for thinking level cycling\n\t\tif (data === \"\\x1b[Z\" && this.onShiftTab) {\n\t\t\tthis.onShiftTab();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Escape key - but only if autocomplete is NOT active\n\t\t// (let parent handle escape for autocomplete cancellation)\n\t\tif (data === \"\\x1b\" && this.onEscape && !this.isShowingAutocomplete()) {\n\t\t\tthis.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Ctrl+C\n\t\tif (data === \"\\x03\" && this.onCtrlC) {\n\t\t\tthis.onCtrlC();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to parent for normal handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
@@ -7,7 +7,13 @@ export class CustomEditor extends Editor {
|
|
|
7
7
|
onCtrlC;
|
|
8
8
|
onShiftTab;
|
|
9
9
|
onCtrlP;
|
|
10
|
+
onCtrlO;
|
|
10
11
|
handleInput(data) {
|
|
12
|
+
// Intercept Ctrl+O for tool output expansion
|
|
13
|
+
if (data === "\x0f" && this.onCtrlO) {
|
|
14
|
+
this.onCtrlO();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
11
17
|
// Intercept Ctrl+P for model cycling
|
|
12
18
|
if (data === "\x10" && this.onCtrlP) {
|
|
13
19
|
this.onCtrlP();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../src/tui/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,MAAM;IAChC,QAAQ,CAAc;IACtB,OAAO,CAAc;IACrB,UAAU,CAAc;IACxB,OAAO,CAAc;IAE5B,WAAW,CAAC,IAAY,EAAQ;QAC/B,qCAAqC;QACrC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO;QACR,CAAC;QAED,iDAAiD;QACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC1C,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO;QACR,CAAC;QAED,gEAAgE;QAChE,2DAA2D;QAC3D,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;YACvE,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,OAAO;QACR,CAAC;QAED,mBAAmB;QACnB,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO;QACR,CAAC;QAED,qCAAqC;QACrC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAAA,CACxB;CACD","sourcesContent":["import { Editor } from \"@mariozechner/pi-tui\";\n\n/**\n * Custom editor that handles Escape and Ctrl+C keys for coding-agent\n */\nexport class CustomEditor extends Editor {\n\tpublic onEscape?: () => void;\n\tpublic onCtrlC?: () => void;\n\tpublic onShiftTab?: () => void;\n\tpublic onCtrlP?: () => void;\n\n\thandleInput(data: string): void {\n\t\t// Intercept Ctrl+P for model cycling\n\t\tif (data === \"\\x10\" && this.onCtrlP) {\n\t\t\tthis.onCtrlP();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Shift+Tab for thinking level cycling\n\t\tif (data === \"\\x1b[Z\" && this.onShiftTab) {\n\t\t\tthis.onShiftTab();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Escape key - but only if autocomplete is NOT active\n\t\t// (let parent handle escape for autocomplete cancellation)\n\t\tif (data === \"\\x1b\" && this.onEscape && !this.isShowingAutocomplete()) {\n\t\t\tthis.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Ctrl+C\n\t\tif (data === \"\\x03\" && this.onCtrlC) {\n\t\t\tthis.onCtrlC();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to parent for normal handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../src/tui/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,MAAM;IAChC,QAAQ,CAAc;IACtB,OAAO,CAAc;IACrB,UAAU,CAAc;IACxB,OAAO,CAAc;IACrB,OAAO,CAAc;IAE5B,WAAW,CAAC,IAAY,EAAQ;QAC/B,6CAA6C;QAC7C,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO;QACR,CAAC;QAED,qCAAqC;QACrC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO;QACR,CAAC;QAED,iDAAiD;QACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC1C,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO;QACR,CAAC;QAED,gEAAgE;QAChE,2DAA2D;QAC3D,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;YACvE,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,OAAO;QACR,CAAC;QAED,mBAAmB;QACnB,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO;QACR,CAAC;QAED,qCAAqC;QACrC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAAA,CACxB;CACD","sourcesContent":["import { Editor } from \"@mariozechner/pi-tui\";\n\n/**\n * Custom editor that handles Escape and Ctrl+C keys for coding-agent\n */\nexport class CustomEditor extends Editor {\n\tpublic onEscape?: () => void;\n\tpublic onCtrlC?: () => void;\n\tpublic onShiftTab?: () => void;\n\tpublic onCtrlP?: () => void;\n\tpublic onCtrlO?: () => void;\n\n\thandleInput(data: string): void {\n\t\t// Intercept Ctrl+O for tool output expansion\n\t\tif (data === \"\\x0f\" && this.onCtrlO) {\n\t\t\tthis.onCtrlO();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Ctrl+P for model cycling\n\t\tif (data === \"\\x10\" && this.onCtrlP) {\n\t\t\tthis.onCtrlP();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Shift+Tab for thinking level cycling\n\t\tif (data === \"\\x1b[Z\" && this.onShiftTab) {\n\t\t\tthis.onShiftTab();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Escape key - but only if autocomplete is NOT active\n\t\t// (let parent handle escape for autocomplete cancellation)\n\t\tif (data === \"\\x1b\" && this.onEscape && !this.isShowingAutocomplete()) {\n\t\t\tthis.onEscape();\n\t\t\treturn;\n\t\t}\n\n\t\t// Intercept Ctrl+C\n\t\tif (data === \"\\x03\" && this.onCtrlC) {\n\t\t\tthis.onCtrlC();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to parent for normal handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
@@ -6,6 +6,7 @@ export declare class ToolExecutionComponent extends Container {
|
|
|
6
6
|
private contentText;
|
|
7
7
|
private toolName;
|
|
8
8
|
private args;
|
|
9
|
+
private expanded;
|
|
9
10
|
private result?;
|
|
10
11
|
constructor(toolName: string, args: any);
|
|
11
12
|
updateArgs(args: any): void;
|
|
@@ -19,6 +20,7 @@ export declare class ToolExecutionComponent extends Container {
|
|
|
19
20
|
details?: any;
|
|
20
21
|
isError: boolean;
|
|
21
22
|
}): void;
|
|
23
|
+
setExpanded(expanded: boolean): void;
|
|
22
24
|
private updateDisplay;
|
|
23
25
|
private getTextOutput;
|
|
24
26
|
private formatToolExecution;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-execution.d.ts","sourceRoot":"","sources":["../../src/tui/tool-execution.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAgB,MAAM,sBAAsB,CAAC;AAyH/D;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,WAAW,CAAO;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,MAAM,CAAC,CAIb;IAEF,YAAY,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAStC;IAED,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAG1B;IAED,YAAY,CAAC,MAAM,EAAE;QACpB,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClF,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;KACjB,GAAG,IAAI,CAGP;IAED,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,mBAAmB;CAqG3B","sourcesContent":["import * as os from \"node:os\";\nimport { Container, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport * as Diff from \"diff\";\nimport stripAnsi from \"strip-ansi\";\n\n/**\n * Convert absolute path to tilde notation if it's in home directory\n */\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with spaces for consistent rendering\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Generate a unified diff with line numbers and context\n */\nfunction generateDiff(oldStr: string, newStr: string): string {\n\tconst parts = Diff.diffLines(oldStr, newStr);\n\tconst output: string[] = [];\n\n\t// Calculate max line number for padding\n\tconst oldLines = oldStr.split(\"\\n\");\n\tconst newLines = newStr.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tconst CONTEXT_LINES = 2; // Show 2 lines of context around changes\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.green(`${lineNum} ${line}`));\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.red(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst isFirstPart = i === 0;\n\t\t\tconst isLastPart = i === parts.length - 1;\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange || isFirstPart || isLastPart) {\n\t\t\t\t// Show context\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!isFirstPart && !lastWasChange) {\n\t\t\t\t\t// Show only last N lines as leading context\n\t\t\t\t\tskipStart = Math.max(0, raw.length - CONTEXT_LINES);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!isLastPart && !nextPartIsChange && linesToShow.length > CONTEXT_LINES) {\n\t\t\t\t\t// Show only first N lines as trailing context\n\t\t\t\t\tskipEnd = linesToShow.length - CONTEXT_LINES;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, CONTEXT_LINES);\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at start\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.dim(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at end\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\t// Update line numbers for skipped lines\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\n/**\n * Component that renders a tool call with its result (updateable)\n */\nexport class ToolExecutionComponent extends Container {\n\tprivate contentText: Text;\n\tprivate toolName: string;\n\tprivate args: any;\n\tprivate result?: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tisError: boolean;\n\t\tdetails?: any;\n\t};\n\n\tconstructor(toolName: string, args: any) {\n\t\tsuper();\n\t\tthis.toolName = toolName;\n\t\tthis.args = args;\n\t\tthis.addChild(new Spacer(1));\n\t\t// Content with colored background and padding\n\t\tthis.contentText = new Text(\"\", 1, 1, { r: 40, g: 40, b: 50 });\n\t\tthis.addChild(this.contentText);\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateArgs(args: any): void {\n\t\tthis.args = args;\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateResult(result: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: any;\n\t\tisError: boolean;\n\t}): void {\n\t\tthis.result = result;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst bgColor = this.result\n\t\t\t? this.result.isError\n\t\t\t\t? { r: 60, g: 40, b: 40 }\n\t\t\t\t: { r: 40, g: 50, b: 40 }\n\t\t\t: { r: 40, g: 40, b: 50 };\n\n\t\tthis.contentText.setCustomBgRgb(bgColor);\n\t\tthis.contentText.setText(this.formatToolExecution());\n\t}\n\n\tprivate getTextOutput(): string {\n\t\tif (!this.result) return \"\";\n\n\t\t// Extract text from content blocks\n\t\tconst textBlocks = this.result.content?.filter((c: any) => c.type === \"text\") || [];\n\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\n\t\t// Strip ANSI codes from raw output (bash may emit colors/formatting)\n\t\tlet output = textBlocks.map((c: any) => stripAnsi(c.text || \"\")).join(\"\\n\");\n\n\t\t// Add indicator for images\n\t\tif (imageBlocks.length > 0) {\n\t\t\tconst imageIndicators = imageBlocks.map((img: any) => `[Image: ${img.mimeType}]`).join(\"\\n\");\n\t\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tprivate formatToolExecution(): string {\n\t\tlet text = \"\";\n\n\t\t// Format based on tool type\n\t\tif (this.toolName === \"bash\") {\n\t\t\tconst command = this.args?.command || \"\";\n\t\t\ttext = chalk.bold(`$ ${command || chalk.dim(\"...\")}`);\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show output without code fences - more minimal\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = 5;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"read\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"read\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"write\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst fileContent = this.args?.content || \"\";\n\t\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\t\tconst totalLines = lines.length;\n\n\t\t\ttext = chalk.bold(\"write\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\t\t\tif (totalLines > 10) {\n\t\t\t\ttext += ` (${totalLines} lines)`;\n\t\t\t}\n\n\t\t\t// Show first 10 lines of content if available\n\t\t\tif (fileContent) {\n\t\t\t\tconst maxLines = 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"edit\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"edit\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show error message if it's an error\n\t\t\t\tif (this.result.isError) {\n\t\t\t\t\tconst errorText = this.getTextOutput();\n\t\t\t\t\tif (errorText) {\n\t\t\t\t\t\ttext += \"\\n\\n\" + chalk.red(errorText);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.result.details?.diff) {\n\t\t\t\t\t// Show diff if available\n\t\t\t\t\tconst diffLines = this.result.details.diff.split(\"\\n\");\n\t\t\t\t\tconst coloredLines = diffLines.map((line: string) => {\n\t\t\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\t\t\treturn chalk.green(line);\n\t\t\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\t\t\treturn chalk.red(line);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn chalk.dim(line);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\ttext += \"\\n\\n\" + coloredLines.join(\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tool\n\t\t\ttext = chalk.bold(this.toolName);\n\n\t\t\tconst content = JSON.stringify(this.args, null, 2);\n\t\t\ttext += \"\\n\\n\" + content;\n\t\t\tconst output = this.getTextOutput();\n\t\t\tif (output) {\n\t\t\t\ttext += \"\\n\" + output;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tool-execution.d.ts","sourceRoot":"","sources":["../../src/tui/tool-execution.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAgB,MAAM,sBAAsB,CAAC;AAyH/D;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,WAAW,CAAO;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAC,CAIb;IAEF,YAAY,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAStC;IAED,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAG1B;IAED,YAAY,CAAC,MAAM,EAAE;QACpB,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClF,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;KACjB,GAAG,IAAI,CAGP;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAED,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,mBAAmB;CAqG3B","sourcesContent":["import * as os from \"node:os\";\nimport { Container, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport * as Diff from \"diff\";\nimport stripAnsi from \"strip-ansi\";\n\n/**\n * Convert absolute path to tilde notation if it's in home directory\n */\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with spaces for consistent rendering\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Generate a unified diff with line numbers and context\n */\nfunction generateDiff(oldStr: string, newStr: string): string {\n\tconst parts = Diff.diffLines(oldStr, newStr);\n\tconst output: string[] = [];\n\n\t// Calculate max line number for padding\n\tconst oldLines = oldStr.split(\"\\n\");\n\tconst newLines = newStr.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tconst CONTEXT_LINES = 2; // Show 2 lines of context around changes\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.green(`${lineNum} ${line}`));\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.red(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst isFirstPart = i === 0;\n\t\t\tconst isLastPart = i === parts.length - 1;\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange || isFirstPart || isLastPart) {\n\t\t\t\t// Show context\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!isFirstPart && !lastWasChange) {\n\t\t\t\t\t// Show only last N lines as leading context\n\t\t\t\t\tskipStart = Math.max(0, raw.length - CONTEXT_LINES);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!isLastPart && !nextPartIsChange && linesToShow.length > CONTEXT_LINES) {\n\t\t\t\t\t// Show only first N lines as trailing context\n\t\t\t\t\tskipEnd = linesToShow.length - CONTEXT_LINES;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, CONTEXT_LINES);\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at start\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.dim(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at end\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\t// Update line numbers for skipped lines\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\n/**\n * Component that renders a tool call with its result (updateable)\n */\nexport class ToolExecutionComponent extends Container {\n\tprivate contentText: Text;\n\tprivate toolName: string;\n\tprivate args: any;\n\tprivate expanded = false;\n\tprivate result?: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tisError: boolean;\n\t\tdetails?: any;\n\t};\n\n\tconstructor(toolName: string, args: any) {\n\t\tsuper();\n\t\tthis.toolName = toolName;\n\t\tthis.args = args;\n\t\tthis.addChild(new Spacer(1));\n\t\t// Content with colored background and padding\n\t\tthis.contentText = new Text(\"\", 1, 1, { r: 40, g: 40, b: 50 });\n\t\tthis.addChild(this.contentText);\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateArgs(args: any): void {\n\t\tthis.args = args;\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateResult(result: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: any;\n\t\tisError: boolean;\n\t}): void {\n\t\tthis.result = result;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst bgColor = this.result\n\t\t\t? this.result.isError\n\t\t\t\t? { r: 60, g: 40, b: 40 }\n\t\t\t\t: { r: 40, g: 50, b: 40 }\n\t\t\t: { r: 40, g: 40, b: 50 };\n\n\t\tthis.contentText.setCustomBgRgb(bgColor);\n\t\tthis.contentText.setText(this.formatToolExecution());\n\t}\n\n\tprivate getTextOutput(): string {\n\t\tif (!this.result) return \"\";\n\n\t\t// Extract text from content blocks\n\t\tconst textBlocks = this.result.content?.filter((c: any) => c.type === \"text\") || [];\n\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\n\t\t// Strip ANSI codes from raw output (bash may emit colors/formatting)\n\t\tlet output = textBlocks.map((c: any) => stripAnsi(c.text || \"\")).join(\"\\n\");\n\n\t\t// Add indicator for images\n\t\tif (imageBlocks.length > 0) {\n\t\t\tconst imageIndicators = imageBlocks.map((img: any) => `[Image: ${img.mimeType}]`).join(\"\\n\");\n\t\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tprivate formatToolExecution(): string {\n\t\tlet text = \"\";\n\n\t\t// Format based on tool type\n\t\tif (this.toolName === \"bash\") {\n\t\t\tconst command = this.args?.command || \"\";\n\t\t\ttext = chalk.bold(`$ ${command || chalk.dim(\"...\")}`);\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show output without code fences - more minimal\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 5;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"read\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"read\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = this.expanded ? lines.length : 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"write\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst fileContent = this.args?.content || \"\";\n\t\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\t\tconst totalLines = lines.length;\n\n\t\t\ttext = chalk.bold(\"write\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\t\t\tif (totalLines > 10) {\n\t\t\t\ttext += ` (${totalLines} lines)`;\n\t\t\t}\n\n\t\t\t// Show first 10 lines of content if available\n\t\t\tif (fileContent) {\n\t\t\t\tconst maxLines = this.expanded ? lines.length : 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"edit\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"edit\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show error message if it's an error\n\t\t\t\tif (this.result.isError) {\n\t\t\t\t\tconst errorText = this.getTextOutput();\n\t\t\t\t\tif (errorText) {\n\t\t\t\t\t\ttext += \"\\n\\n\" + chalk.red(errorText);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.result.details?.diff) {\n\t\t\t\t\t// Show diff if available\n\t\t\t\t\tconst diffLines = this.result.details.diff.split(\"\\n\");\n\t\t\t\t\tconst coloredLines = diffLines.map((line: string) => {\n\t\t\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\t\t\treturn chalk.green(line);\n\t\t\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\t\t\treturn chalk.red(line);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn chalk.dim(line);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\ttext += \"\\n\\n\" + coloredLines.join(\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tool\n\t\t\ttext = chalk.bold(this.toolName);\n\n\t\t\tconst content = JSON.stringify(this.args, null, 2);\n\t\t\ttext += \"\\n\\n\" + content;\n\t\t\tconst output = this.getTextOutput();\n\t\t\tif (output) {\n\t\t\t\ttext += \"\\n\" + output;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
|
|
@@ -112,6 +112,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
112
112
|
contentText;
|
|
113
113
|
toolName;
|
|
114
114
|
args;
|
|
115
|
+
expanded = false;
|
|
115
116
|
result;
|
|
116
117
|
constructor(toolName, args) {
|
|
117
118
|
super();
|
|
@@ -131,6 +132,10 @@ export class ToolExecutionComponent extends Container {
|
|
|
131
132
|
this.result = result;
|
|
132
133
|
this.updateDisplay();
|
|
133
134
|
}
|
|
135
|
+
setExpanded(expanded) {
|
|
136
|
+
this.expanded = expanded;
|
|
137
|
+
this.updateDisplay();
|
|
138
|
+
}
|
|
134
139
|
updateDisplay() {
|
|
135
140
|
const bgColor = this.result
|
|
136
141
|
? this.result.isError
|
|
@@ -166,7 +171,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
166
171
|
const output = this.getTextOutput().trim();
|
|
167
172
|
if (output) {
|
|
168
173
|
const lines = output.split("\n");
|
|
169
|
-
const maxLines = 5;
|
|
174
|
+
const maxLines = this.expanded ? lines.length : 5;
|
|
170
175
|
const displayLines = lines.slice(0, maxLines);
|
|
171
176
|
const remaining = lines.length - maxLines;
|
|
172
177
|
text += "\n\n" + displayLines.map((line) => chalk.dim(line)).join("\n");
|
|
@@ -182,7 +187,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
182
187
|
if (this.result) {
|
|
183
188
|
const output = this.getTextOutput();
|
|
184
189
|
const lines = output.split("\n");
|
|
185
|
-
const maxLines = 10;
|
|
190
|
+
const maxLines = this.expanded ? lines.length : 10;
|
|
186
191
|
const displayLines = lines.slice(0, maxLines);
|
|
187
192
|
const remaining = lines.length - maxLines;
|
|
188
193
|
text += "\n\n" + displayLines.map((line) => chalk.dim(replaceTabs(line))).join("\n");
|
|
@@ -202,7 +207,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
202
207
|
}
|
|
203
208
|
// Show first 10 lines of content if available
|
|
204
209
|
if (fileContent) {
|
|
205
|
-
const maxLines = 10;
|
|
210
|
+
const maxLines = this.expanded ? lines.length : 10;
|
|
206
211
|
const displayLines = lines.slice(0, maxLines);
|
|
207
212
|
const remaining = lines.length - maxLines;
|
|
208
213
|
text += "\n\n" + displayLines.map((line) => chalk.dim(replaceTabs(line))).join("\n");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-execution.js","sourceRoot":"","sources":["../../src/tui/tool-execution.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport { Container, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport * as Diff from \"diff\";\nimport stripAnsi from \"strip-ansi\";\n\n/**\n * Convert absolute path to tilde notation if it's in home directory\n */\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with spaces for consistent rendering\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Generate a unified diff with line numbers and context\n */\nfunction generateDiff(oldStr: string, newStr: string): string {\n\tconst parts = Diff.diffLines(oldStr, newStr);\n\tconst output: string[] = [];\n\n\t// Calculate max line number for padding\n\tconst oldLines = oldStr.split(\"\\n\");\n\tconst newLines = newStr.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tconst CONTEXT_LINES = 2; // Show 2 lines of context around changes\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.green(`${lineNum} ${line}`));\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.red(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst isFirstPart = i === 0;\n\t\t\tconst isLastPart = i === parts.length - 1;\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange || isFirstPart || isLastPart) {\n\t\t\t\t// Show context\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!isFirstPart && !lastWasChange) {\n\t\t\t\t\t// Show only last N lines as leading context\n\t\t\t\t\tskipStart = Math.max(0, raw.length - CONTEXT_LINES);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!isLastPart && !nextPartIsChange && linesToShow.length > CONTEXT_LINES) {\n\t\t\t\t\t// Show only first N lines as trailing context\n\t\t\t\t\tskipEnd = linesToShow.length - CONTEXT_LINES;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, CONTEXT_LINES);\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at start\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.dim(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at end\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\t// Update line numbers for skipped lines\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\n/**\n * Component that renders a tool call with its result (updateable)\n */\nexport class ToolExecutionComponent extends Container {\n\tprivate contentText: Text;\n\tprivate toolName: string;\n\tprivate args: any;\n\tprivate result?: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tisError: boolean;\n\t\tdetails?: any;\n\t};\n\n\tconstructor(toolName: string, args: any) {\n\t\tsuper();\n\t\tthis.toolName = toolName;\n\t\tthis.args = args;\n\t\tthis.addChild(new Spacer(1));\n\t\t// Content with colored background and padding\n\t\tthis.contentText = new Text(\"\", 1, 1, { r: 40, g: 40, b: 50 });\n\t\tthis.addChild(this.contentText);\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateArgs(args: any): void {\n\t\tthis.args = args;\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateResult(result: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: any;\n\t\tisError: boolean;\n\t}): void {\n\t\tthis.result = result;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst bgColor = this.result\n\t\t\t? this.result.isError\n\t\t\t\t? { r: 60, g: 40, b: 40 }\n\t\t\t\t: { r: 40, g: 50, b: 40 }\n\t\t\t: { r: 40, g: 40, b: 50 };\n\n\t\tthis.contentText.setCustomBgRgb(bgColor);\n\t\tthis.contentText.setText(this.formatToolExecution());\n\t}\n\n\tprivate getTextOutput(): string {\n\t\tif (!this.result) return \"\";\n\n\t\t// Extract text from content blocks\n\t\tconst textBlocks = this.result.content?.filter((c: any) => c.type === \"text\") || [];\n\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\n\t\t// Strip ANSI codes from raw output (bash may emit colors/formatting)\n\t\tlet output = textBlocks.map((c: any) => stripAnsi(c.text || \"\")).join(\"\\n\");\n\n\t\t// Add indicator for images\n\t\tif (imageBlocks.length > 0) {\n\t\t\tconst imageIndicators = imageBlocks.map((img: any) => `[Image: ${img.mimeType}]`).join(\"\\n\");\n\t\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tprivate formatToolExecution(): string {\n\t\tlet text = \"\";\n\n\t\t// Format based on tool type\n\t\tif (this.toolName === \"bash\") {\n\t\t\tconst command = this.args?.command || \"\";\n\t\t\ttext = chalk.bold(`$ ${command || chalk.dim(\"...\")}`);\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show output without code fences - more minimal\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = 5;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"read\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"read\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"write\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst fileContent = this.args?.content || \"\";\n\t\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\t\tconst totalLines = lines.length;\n\n\t\t\ttext = chalk.bold(\"write\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\t\t\tif (totalLines > 10) {\n\t\t\t\ttext += ` (${totalLines} lines)`;\n\t\t\t}\n\n\t\t\t// Show first 10 lines of content if available\n\t\t\tif (fileContent) {\n\t\t\t\tconst maxLines = 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"edit\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"edit\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show error message if it's an error\n\t\t\t\tif (this.result.isError) {\n\t\t\t\t\tconst errorText = this.getTextOutput();\n\t\t\t\t\tif (errorText) {\n\t\t\t\t\t\ttext += \"\\n\\n\" + chalk.red(errorText);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.result.details?.diff) {\n\t\t\t\t\t// Show diff if available\n\t\t\t\t\tconst diffLines = this.result.details.diff.split(\"\\n\");\n\t\t\t\t\tconst coloredLines = diffLines.map((line: string) => {\n\t\t\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\t\t\treturn chalk.green(line);\n\t\t\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\t\t\treturn chalk.red(line);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn chalk.dim(line);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\ttext += \"\\n\\n\" + coloredLines.join(\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tool\n\t\t\ttext = chalk.bold(this.toolName);\n\n\t\t\tconst content = JSON.stringify(this.args, null, 2);\n\t\t\ttext += \"\\n\\n\" + content;\n\t\t\tconst output = this.getTextOutput();\n\t\t\tif (output) {\n\t\t\t\ttext += \"\\n\" + output;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tool-execution.js","sourceRoot":"","sources":["../../src/tui/tool-execution.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport { Container, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport * as Diff from \"diff\";\nimport stripAnsi from \"strip-ansi\";\n\n/**\n * Convert absolute path to tilde notation if it's in home directory\n */\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with spaces for consistent rendering\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Generate a unified diff with line numbers and context\n */\nfunction generateDiff(oldStr: string, newStr: string): string {\n\tconst parts = Diff.diffLines(oldStr, newStr);\n\tconst output: string[] = [];\n\n\t// Calculate max line number for padding\n\tconst oldLines = oldStr.split(\"\\n\");\n\tconst newLines = newStr.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tconst CONTEXT_LINES = 2; // Show 2 lines of context around changes\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.green(`${lineNum} ${line}`));\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.red(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst isFirstPart = i === 0;\n\t\t\tconst isLastPart = i === parts.length - 1;\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange || isFirstPart || isLastPart) {\n\t\t\t\t// Show context\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!isFirstPart && !lastWasChange) {\n\t\t\t\t\t// Show only last N lines as leading context\n\t\t\t\t\tskipStart = Math.max(0, raw.length - CONTEXT_LINES);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!isLastPart && !nextPartIsChange && linesToShow.length > CONTEXT_LINES) {\n\t\t\t\t\t// Show only first N lines as trailing context\n\t\t\t\t\tskipEnd = linesToShow.length - CONTEXT_LINES;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, CONTEXT_LINES);\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at start\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.dim(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at end\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\t// Update line numbers for skipped lines\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\n/**\n * Component that renders a tool call with its result (updateable)\n */\nexport class ToolExecutionComponent extends Container {\n\tprivate contentText: Text;\n\tprivate toolName: string;\n\tprivate args: any;\n\tprivate expanded = false;\n\tprivate result?: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tisError: boolean;\n\t\tdetails?: any;\n\t};\n\n\tconstructor(toolName: string, args: any) {\n\t\tsuper();\n\t\tthis.toolName = toolName;\n\t\tthis.args = args;\n\t\tthis.addChild(new Spacer(1));\n\t\t// Content with colored background and padding\n\t\tthis.contentText = new Text(\"\", 1, 1, { r: 40, g: 40, b: 50 });\n\t\tthis.addChild(this.contentText);\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateArgs(args: any): void {\n\t\tthis.args = args;\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateResult(result: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: any;\n\t\tisError: boolean;\n\t}): void {\n\t\tthis.result = result;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst bgColor = this.result\n\t\t\t? this.result.isError\n\t\t\t\t? { r: 60, g: 40, b: 40 }\n\t\t\t\t: { r: 40, g: 50, b: 40 }\n\t\t\t: { r: 40, g: 40, b: 50 };\n\n\t\tthis.contentText.setCustomBgRgb(bgColor);\n\t\tthis.contentText.setText(this.formatToolExecution());\n\t}\n\n\tprivate getTextOutput(): string {\n\t\tif (!this.result) return \"\";\n\n\t\t// Extract text from content blocks\n\t\tconst textBlocks = this.result.content?.filter((c: any) => c.type === \"text\") || [];\n\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\n\t\t// Strip ANSI codes from raw output (bash may emit colors/formatting)\n\t\tlet output = textBlocks.map((c: any) => stripAnsi(c.text || \"\")).join(\"\\n\");\n\n\t\t// Add indicator for images\n\t\tif (imageBlocks.length > 0) {\n\t\t\tconst imageIndicators = imageBlocks.map((img: any) => `[Image: ${img.mimeType}]`).join(\"\\n\");\n\t\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tprivate formatToolExecution(): string {\n\t\tlet text = \"\";\n\n\t\t// Format based on tool type\n\t\tif (this.toolName === \"bash\") {\n\t\t\tconst command = this.args?.command || \"\";\n\t\t\ttext = chalk.bold(`$ ${command || chalk.dim(\"...\")}`);\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show output without code fences - more minimal\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 5;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"read\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"read\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = this.expanded ? lines.length : 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"write\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst fileContent = this.args?.content || \"\";\n\t\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\t\tconst totalLines = lines.length;\n\n\t\t\ttext = chalk.bold(\"write\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\t\t\tif (totalLines > 10) {\n\t\t\t\ttext += ` (${totalLines} lines)`;\n\t\t\t}\n\n\t\t\t// Show first 10 lines of content if available\n\t\t\tif (fileContent) {\n\t\t\t\tconst maxLines = this.expanded ? lines.length : 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"edit\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"edit\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show error message if it's an error\n\t\t\t\tif (this.result.isError) {\n\t\t\t\t\tconst errorText = this.getTextOutput();\n\t\t\t\t\tif (errorText) {\n\t\t\t\t\t\ttext += \"\\n\\n\" + chalk.red(errorText);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.result.details?.diff) {\n\t\t\t\t\t// Show diff if available\n\t\t\t\t\tconst diffLines = this.result.details.diff.split(\"\\n\");\n\t\t\t\t\tconst coloredLines = diffLines.map((line: string) => {\n\t\t\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\t\t\treturn chalk.green(line);\n\t\t\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\t\t\treturn chalk.red(line);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn chalk.dim(line);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\ttext += \"\\n\\n\" + coloredLines.join(\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tool\n\t\t\ttext = chalk.bold(this.toolName);\n\n\t\t\tconst content = JSON.stringify(this.args, null, 2);\n\t\t\ttext += \"\\n\\n\" + content;\n\t\t\tconst output = this.getTextOutput();\n\t\t\tif (output) {\n\t\t\t\ttext += \"\\n\" + output;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
|
|
@@ -31,6 +31,7 @@ export declare class TuiRenderer {
|
|
|
31
31
|
private oauthSelector;
|
|
32
32
|
private isFirstUserMessage;
|
|
33
33
|
private scopedModels;
|
|
34
|
+
private toolOutputExpanded;
|
|
34
35
|
constructor(agent: Agent, sessionManager: SessionManager, settingsManager: SettingsManager, version: string, changelogMarkdown?: string | null, newVersion?: string | null, scopedModels?: Model<any>[]);
|
|
35
36
|
init(): Promise<void>;
|
|
36
37
|
handleEvent(event: AgentEvent, state: AgentState): Promise<void>;
|
|
@@ -43,6 +44,7 @@ export declare class TuiRenderer {
|
|
|
43
44
|
private updateEditorBorderColor;
|
|
44
45
|
private cycleThinkingLevel;
|
|
45
46
|
private cycleModel;
|
|
47
|
+
private toggleToolOutputExpansion;
|
|
46
48
|
clearEditor(): void;
|
|
47
49
|
showError(errorMessage: string): void;
|
|
48
50
|
showWarning(warningMessage: string): void;
|