pi-media-models 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -141
- package/package.json +1 -1
- package/skills/pi-media/SKILL.md +13 -19
- package/src/adapters/base.ts +1 -1
- package/src/adapters/google.ts +3 -1
- package/src/config.ts +14 -2
- package/src/router.ts +9 -3
package/README.md
CHANGED
|
@@ -3,109 +3,97 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/pi-media-models)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Provider-neutral multimodal generation extension for
|
|
6
|
+
**Provider-neutral multimodal generation extension for Pi Coding Agent.**
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
This extension seamlessly bridges Pi's reasoning capabilities with top-tier AI media generation platforms. It abstracts away complex multi-part uploads, background task polling, CDN hosting, and API differences, exposing exactly **6 unified tools** for the agent.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## ✨ Features
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
- **Unified Interface**: One request format (`provider`, `model`, `prompt`, `referenceImages`, etc.) maps automatically to the correct capability across providers.
|
|
13
|
+
- **Config-First Authentication**: Manage all API keys and credentials directly in a single `media-models.json` configuration file — no cluttered environment variables needed.
|
|
14
|
+
- **Auto-Download**: Output media (images, videos, audio) is automatically downloaded and saved to a local directory (`~/.pi/agent/media/outputs/`) using atomic `.part` renames. LLM context remains pristine and only receives local file paths.
|
|
15
|
+
- **Smart Input Resolution**: Pass local paths (`C:/...` or `/path/...`), file URIs (`file://`), standard URLs (`http(s)://`), or base64 (`data:...`). The router transparently handles multipart uploads, base64 encoding, or CDN pre-uploading (e.g., for `fal.ai`).
|
|
16
|
+
- **Resilient Polling**: Advanced `MediaJob` processing handles asynchronous Long-Running Operations (LROs), 429 rate limits (respecting `Retry-After`), and timeouts. Supports remote job cancellation where supported by the provider.
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
pi install npm:pi-media-models
|
|
16
|
-
```
|
|
18
|
+
## 📦 Installation
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
This extension is built for the **Pi Coding Agent**. It bundles both the executable extension logic and the LLM `SKILL.md` prompt context.
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
Install natively inside your Pi environment:
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
> ```
|
|
24
|
+
```bash
|
|
25
|
+
# Install via NPM (Recommended)
|
|
26
|
+
pi install npm:pi-media-models
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
# Or install directly from GitHub
|
|
29
|
+
pi install git:github.com/luffysolution-svg/pi-media-models
|
|
30
|
+
```
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
Once installed, restart Pi or type `/reload` in your active session.
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
## 🚀 Quick Usage
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
After installation, the extension and its companion skill are active immediately. You can talk to Pi naturally — Pi knows how to query available models and route tasks to the appropriate tool.
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
Generate an image of a misty mountain at sunrise using fal.ai
|
|
37
|
-
```
|
|
38
|
+
**Example Prompts:**
|
|
38
39
|
|
|
39
40
|
```
|
|
40
|
-
|
|
41
|
+
帮我用 fal.ai 画一张赛博朋克风格的雨夜街景,比例 16:9
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
```
|
|
44
|
-
|
|
45
|
+
Use xAI to generate a 5-second video of ocean waves with audio
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
```
|
|
48
|
-
|
|
49
|
+
使用 Atlas 编辑这张图片,把背景换成雪山:C:/assets/photo.jpg
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
Pi will automatically call the right tool, handle uploads and polling, and save the result to your local disk. It reports the saved file path when done.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## API Key Setup
|
|
56
|
-
|
|
57
|
-
The extension reads keys from environment variables. Set whichever providers you want to use:
|
|
58
|
-
|
|
59
|
-
| Provider | Environment Variable |
|
|
60
|
-
|---|---|
|
|
61
|
-
| fal.ai | `FAL_KEY` |
|
|
62
|
-
| OpenAI | `OPENAI_API_KEY` |
|
|
63
|
-
| xAI (Grok) | `XAI_API_KEY` |
|
|
64
|
-
| Google Gemini | `GEMINI_API_KEY` |
|
|
65
|
-
| DashScope / QwenCloud | `DASHSCOPE_API_KEY` |
|
|
66
|
-
| OpenRouter | `OPENROUTER_API_KEY` |
|
|
67
|
-
| Atlas | `ATLAS_API_KEY` |
|
|
68
|
-
| Vertex AI | `GOOGLE_APPLICATION_CREDENTIALS` (ADC) |
|
|
69
|
-
|
|
70
|
-
**macOS / Linux** — add to `~/.zshrc` or `~/.bashrc`:
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
export FAL_KEY="your-key"
|
|
74
|
-
export XAI_API_KEY="your-key"
|
|
75
52
|
```
|
|
76
|
-
|
|
77
|
-
**Windows** — run in PowerShell (persists across reboots):
|
|
78
|
-
|
|
79
|
-
```powershell
|
|
80
|
-
[System.Environment]::SetEnvironmentVariable("FAL_KEY", "your-key", "User")
|
|
81
|
-
[System.Environment]::SetEnvironmentVariable("XAI_API_KEY", "your-key", "User")
|
|
53
|
+
帮我查一下当前已配置好可用的多模态模型有哪些?
|
|
82
54
|
```
|
|
83
55
|
|
|
84
|
-
|
|
56
|
+
Pi 会自动调取对应工具、完成排队轮询与文件下载,并直接返回本地媒体文件的保存路径。
|
|
85
57
|
|
|
86
|
-
|
|
58
|
+
## ⚙️ Configuration
|
|
87
59
|
|
|
88
|
-
|
|
60
|
+
All API keys, custom endpoints, and output paths are configured directly in a single JSON file.
|
|
89
61
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"fal": { "apiKey": "your-fal-key" },
|
|
94
|
-
"xai": { "apiKey": "xai-your-key" },
|
|
95
|
-
"openai": { "apiKey": "sk-your-key" }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
```
|
|
62
|
+
**Configuration File Location:**
|
|
63
|
+
- **Global (Recommended):** `~/.pi/agent/media-models.json`
|
|
64
|
+
- **Project-Specific:** `<project_root>/.pi/media-models.json`
|
|
99
65
|
|
|
100
|
-
|
|
66
|
+
### Example `media-models.json`
|
|
101
67
|
|
|
102
|
-
|
|
68
|
+
Create or edit `~/.pi/agent/media-models.json`:
|
|
103
69
|
|
|
104
70
|
```json
|
|
105
71
|
{
|
|
72
|
+
"outputDir": "~/.pi/agent/media/outputs",
|
|
106
73
|
"providerOptions": {
|
|
74
|
+
"fal": {
|
|
75
|
+
"apiKey": "fal-xxxxxxxxxxxxxxxxxxxx"
|
|
76
|
+
},
|
|
77
|
+
"xai": {
|
|
78
|
+
"apiKey": "xai-xxxxxxxxxxxxxxxxxxxx"
|
|
79
|
+
},
|
|
80
|
+
"atlas": {
|
|
81
|
+
"apiKey": "sk-xxxxxxxxxxxxxxxxxxxx"
|
|
82
|
+
},
|
|
83
|
+
"dashscope": {
|
|
84
|
+
"apiKey": "sk-xxxxxxxxxxxxxxxxxxxx"
|
|
85
|
+
},
|
|
86
|
+
"openai": {
|
|
87
|
+
"apiKey": "sk-xxxxxxxxxxxxxxxxxxxx"
|
|
88
|
+
},
|
|
89
|
+
"gemini": {
|
|
90
|
+
"apiKey": "AIzaxxxxxxxxxxxxxxxxxxxx"
|
|
91
|
+
},
|
|
92
|
+
"openrouter": {
|
|
93
|
+
"apiKey": "sk-or-xxxxxxxxxxxxxxxxxxxx"
|
|
94
|
+
},
|
|
107
95
|
"vertex": {
|
|
108
|
-
"credentialsFile": "/path/to/service-account.json",
|
|
96
|
+
"credentialsFile": "/path/to/vertex-service-account.json",
|
|
109
97
|
"project": "my-gcp-project",
|
|
110
98
|
"location": "us-central1"
|
|
111
99
|
}
|
|
@@ -113,83 +101,27 @@ Keys in the config file take precedence over environment variables for that prov
|
|
|
113
101
|
}
|
|
114
102
|
```
|
|
115
103
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
## Output Files
|
|
119
|
-
|
|
120
|
-
All generated media is automatically downloaded and saved to:
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
~/.pi/agent/media/outputs/
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
To use a different directory, add `outputDir` to the config file:
|
|
127
|
-
|
|
128
|
-
```json
|
|
129
|
-
{
|
|
130
|
-
"outputDir": "/Users/alice/Pictures/ai-output"
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## Supported Providers
|
|
137
|
-
|
|
138
|
-
| Provider | Capabilities | Env Var |
|
|
139
|
-
|---|---|---|
|
|
140
|
-
| **fal.ai** | Images, Video, Audio, TTS, STT | `FAL_KEY` |
|
|
141
|
-
| **xAI (Grok Imagine)** | Image gen/edit, T2V, I2V, video extend | `XAI_API_KEY` |
|
|
142
|
-
| **OpenAI** | Image gen/edit (DALL-E), TTS, STT (Whisper) | `OPENAI_API_KEY` |
|
|
143
|
-
| **Google Gemini** | Imagen, Gemini image, Veo video, Lyria audio, TTS | `GEMINI_API_KEY` |
|
|
144
|
-
| **Google Vertex AI** | Same as Gemini, enterprise ADC auth | ADC JSON |
|
|
145
|
-
| **DashScope / 百炼** | Qwen image, Wan video, music, TTS/STT | `DASHSCOPE_API_KEY` |
|
|
146
|
-
| **QwenCloud** | Same as DashScope (international endpoints) | `DASHSCOPE_API_KEY` |
|
|
147
|
-
| **OpenRouter** | Image gen, async video, TTS | `OPENROUTER_API_KEY` |
|
|
148
|
-
| **Atlas** | Image gen/edit, video (sync + async) | `ATLAS_API_KEY` |
|
|
149
|
-
|
|
150
|
-
To see which providers are active in your current setup, ask Pi:
|
|
151
|
-
|
|
152
|
-
```
|
|
153
|
-
What media providers do I have configured?
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## Available Tools
|
|
159
|
-
|
|
160
|
-
The extension registers exactly 6 tools. Pi selects the correct one automatically based on your request.
|
|
161
|
-
|
|
162
|
-
| Tool | Purpose |
|
|
163
|
-
|---|---|
|
|
164
|
-
| `media_models` | List providers, models, and capabilities |
|
|
165
|
-
| `image_generate` | Generate images from text or reference images |
|
|
166
|
-
| `image_edit` | Edit an existing image (inpainting, style, background) |
|
|
167
|
-
| `video_generate` | Text-to-video, image-to-video, video extend/edit |
|
|
168
|
-
| `audio_generate` | Generate music or raw audio |
|
|
169
|
-
| `speech_generate` | TTS (text → audio file) or STT (audio → transcript) |
|
|
170
|
-
|
|
171
|
-
---
|
|
104
|
+
> **Note**: You only need to fill in the providers you plan to use. Unused providers can simply be omitted.
|
|
172
105
|
|
|
173
|
-
|
|
106
|
+
### Custom OpenAI-Compatible Providers
|
|
174
107
|
|
|
175
|
-
You can
|
|
108
|
+
You can connect any third-party or internal OpenAI-compatible media gateway by declaring it in `customProviders`:
|
|
176
109
|
|
|
177
110
|
```json
|
|
178
111
|
{
|
|
179
112
|
"customProviders": [
|
|
180
113
|
{
|
|
181
|
-
"id": "my-
|
|
114
|
+
"id": "my-custom-ai",
|
|
182
115
|
"name": "Internal AI Gateway",
|
|
183
|
-
"baseUrl": "https://api.internal.
|
|
184
|
-
"apiKeyEnv": "MY_INTERNAL_KEY",
|
|
116
|
+
"baseUrl": "https://api.internal.com/v1",
|
|
185
117
|
"auth": "bearer",
|
|
186
118
|
"models": [
|
|
187
119
|
{
|
|
188
|
-
"id": "internal-
|
|
120
|
+
"id": "internal-video-pro",
|
|
189
121
|
"vendor": "internal",
|
|
190
|
-
"capabilities": ["
|
|
122
|
+
"capabilities": ["video.text_to_video"],
|
|
191
123
|
"endpoints": {
|
|
192
|
-
"
|
|
124
|
+
"video.text_to_video": "/videos/generations"
|
|
193
125
|
}
|
|
194
126
|
}
|
|
195
127
|
]
|
|
@@ -198,24 +130,45 @@ You can add any OpenAI-compatible API as a custom provider in the config file. C
|
|
|
198
130
|
}
|
|
199
131
|
```
|
|
200
132
|
|
|
201
|
-
|
|
133
|
+
## 🔌 Supported Providers
|
|
134
|
+
|
|
135
|
+
| Provider | Provider ID | Supported Capabilities | Config Option |
|
|
136
|
+
|---|---|---|---|
|
|
137
|
+
| **fal.ai** | `fal` | Images, Video, Audio, TTS, STT (Queue/CDN integration) | `providerOptions.fal.apiKey` |
|
|
138
|
+
| **xAI (Grok Imagine)** | `xai` | Image Gen/Edit, T2V/I2V, Reference-to-Video, Video Extend | `providerOptions.xai.apiKey` |
|
|
139
|
+
| **Atlas API** | `atlas` | Sync/Async Image Gen & Edit, Video LROs | `providerOptions.atlas.apiKey` |
|
|
140
|
+
| **DashScope / 百炼** | `dashscope` | Qwen/Wan Images, Wan Video, Fun-Music, TTS/STT | `providerOptions.dashscope.apiKey` |
|
|
141
|
+
| **QwenCloud** | `qwencloud` | Same as DashScope (International endpoints) | `providerOptions.qwencloud.apiKey` |
|
|
142
|
+
| **OpenAI API** | `openai` | Image Gen/Edit (DALL-E), TTS, STT (Whisper) | `providerOptions.openai.apiKey` |
|
|
143
|
+
| **Google Gemini API** | `gemini` | Gemini/Imagen, Veo, Lyria, TTS, STT | `providerOptions.gemini.apiKey` |
|
|
144
|
+
| **Google Vertex AI** | `vertex` | ADC, Imagen/Gemini, Veo, Lyria, TTS, STT | `providerOptions.vertex.credentialsFile` |
|
|
145
|
+
| **OpenRouter** | `openrouter` | Image Generation, Async Video, TTS | `providerOptions.openrouter.apiKey` |
|
|
146
|
+
|
|
147
|
+
## 🛠️ Exposed Tools
|
|
148
|
+
|
|
149
|
+
The extension registers exactly 6 unified tools for the reasoning agent:
|
|
202
150
|
|
|
203
|
-
|
|
151
|
+
1. `media_models`: Lists providers, configured models, and supported capabilities.
|
|
152
|
+
2. `image_generate`: Generate images from text, image, or multiple reference inputs.
|
|
153
|
+
3. `image_edit`: Edit existing images (supports masks and multiple references).
|
|
154
|
+
4. `video_generate`: Generates, edits, or extends videos. Automatically maps inputs (`referenceImages`, `inputVideo`, `duration`, `generateAudio`, etc.) to the provider's exact capability.
|
|
155
|
+
5. `audio_generate`: Generate music or raw audio (separate from TTS).
|
|
156
|
+
6. `speech_generate`: Handle TTS (Text-to-Speech) and STT (Speech-to-Text).
|
|
204
157
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
- No
|
|
158
|
+
## 🔒 Security & Privacy
|
|
159
|
+
|
|
160
|
+
- **No Key Logging**: API keys and Bearer tokens are redacted (`[REDACTED]`) from all error logs and HTTP outputs before being returned to the LLM.
|
|
161
|
+
- **Local Downloads**: Media assets are fetched directly by your local client into your configured `outputDir` without third-party proxies.
|
|
162
|
+
- **Git Ignored**: `media-models.json` is automatically ignored from git repositories to prevent accidental credential commits.
|
|
208
163
|
|
|
209
164
|
---
|
|
210
165
|
|
|
211
|
-
## Updating
|
|
166
|
+
## 🔄 Updating
|
|
212
167
|
|
|
213
168
|
```bash
|
|
214
169
|
pi update npm:pi-media-models
|
|
215
170
|
```
|
|
216
171
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
## License
|
|
172
|
+
## 📄 License
|
|
220
173
|
|
|
221
174
|
MIT
|
package/package.json
CHANGED
package/skills/pi-media/SKILL.md
CHANGED
|
@@ -82,34 +82,28 @@ All generated media is automatically downloaded to `~/.pi/agent/media/outputs/`.
|
|
|
82
82
|
|
|
83
83
|
## Key Configuration
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
**Environment variables** (any shell or system env):
|
|
88
|
-
|
|
89
|
-
| Provider | Variable |
|
|
90
|
-
|---|---|
|
|
91
|
-
| OpenAI | `OPENAI_API_KEY` |
|
|
92
|
-
| fal.ai | `FAL_KEY` |
|
|
93
|
-
| Gemini | `GEMINI_API_KEY` |
|
|
94
|
-
| Vertex AI | `GOOGLE_APPLICATION_CREDENTIALS` (ADC) |
|
|
95
|
-
| xAI | `XAI_API_KEY` |
|
|
96
|
-
| DashScope / QwenCloud | `DASHSCOPE_API_KEY` |
|
|
97
|
-
| OpenRouter | `OPENROUTER_API_KEY` |
|
|
98
|
-
| Atlas | `ATLAS_API_KEY` |
|
|
99
|
-
|
|
100
|
-
**Config file** (`~/.pi/agent/media-models.json` or `.pi/media-models.json` in the project root):
|
|
85
|
+
All API keys and provider options are configured in `~/.pi/agent/media-models.json` (or `.pi/media-models.json` in the workspace root):
|
|
101
86
|
|
|
102
87
|
```json
|
|
103
88
|
{
|
|
104
|
-
"outputDir": "/
|
|
89
|
+
"outputDir": "~/.pi/agent/media/outputs",
|
|
105
90
|
"providerOptions": {
|
|
91
|
+
"fal": { "apiKey": "fal-..." },
|
|
106
92
|
"xai": { "apiKey": "xai-..." },
|
|
93
|
+
"atlas": { "apiKey": "sk-..." },
|
|
94
|
+
"dashscope": { "apiKey": "sk-..." },
|
|
95
|
+
"openai": { "apiKey": "sk-..." },
|
|
96
|
+
"gemini": { "apiKey": "AIza..." },
|
|
97
|
+
"openrouter": { "apiKey": "sk-or-..." },
|
|
107
98
|
"vertex": {
|
|
108
99
|
"credentialsFile": "/path/to/service-account.json",
|
|
109
|
-
"project": "my-gcp-project"
|
|
100
|
+
"project": "my-gcp-project",
|
|
101
|
+
"location": "us-central1"
|
|
110
102
|
}
|
|
111
103
|
}
|
|
112
104
|
}
|
|
113
105
|
```
|
|
114
106
|
|
|
115
|
-
|
|
107
|
+
Tell the user to edit `~/.pi/agent/media-models.json` directly to add or update API keys.
|
|
108
|
+
|
|
109
|
+
*(Environment variables such as `FAL_KEY`, `OPENAI_API_KEY`, `XAI_API_KEY` are also checked as a fallback).*
|
package/src/adapters/base.ts
CHANGED
|
@@ -30,7 +30,7 @@ export abstract class BaseAdapter implements ProviderAdapter {
|
|
|
30
30
|
|
|
31
31
|
protected key(request: MediaRequest): string {
|
|
32
32
|
const configKey = request.providerOptions?.apiKey;
|
|
33
|
-
const value = (typeof configKey === "string" ? configKey : undefined) ?? (this.envKey ? this.env[this.envKey] : undefined)
|
|
33
|
+
const value = (typeof configKey === "string" && configKey.trim() ? configKey.trim() : undefined) ?? (this.envKey ? this.env[this.envKey] : undefined)
|
|
34
34
|
if (!value) throw new MediaError('AUTH', `${this.envKey ?? `${this.id} API key`} is not set in environment or config`, { provider: this.id })
|
|
35
35
|
return value
|
|
36
36
|
}
|
package/src/adapters/google.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
3
3
|
import { MediaError } from '../errors.js'
|
|
4
4
|
import { MediaJob } from '../media-job.js'
|
|
5
5
|
import { BaseAdapter, artifactsOrThrow, makeModel } from './base.js'
|
|
6
|
+
import { expandHomePath } from '../config.js'
|
|
6
7
|
import type { AdapterContext, AdapterResult, Capability, JobStatus, JsonObject, MediaRequest, ModelDescriptor } from '../types.js'
|
|
7
8
|
import type { AdapterDependencies } from './base.js'
|
|
8
9
|
|
|
@@ -164,7 +165,8 @@ export class GoogleMediaAdapter extends BaseAdapter {
|
|
|
164
165
|
const options = request.providerOptions;
|
|
165
166
|
const configuredFile = typeof options?.credentialsFile === "string" ? options.credentialsFile : undefined
|
|
166
167
|
const rawKeyFilename = configuredFile ?? this.env.VERTEX_CREDENTIALS_FILE ?? this.env.GOOGLE_APPLICATION_CREDENTIALS
|
|
167
|
-
const
|
|
168
|
+
const expandedKeyFile = expandHomePath(rawKeyFilename)
|
|
169
|
+
const keyFilename = expandedKeyFile?.startsWith('file://') ? fileURLToPath(expandedKeyFile) : expandedKeyFile
|
|
168
170
|
const auth = new GoogleAuth({
|
|
169
171
|
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
|
|
170
172
|
...(keyFilename ? { keyFilename } : {}),
|
package/src/config.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
|
-
import { join } from 'node:path'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
4
|
import { CAPABILITIES, type Capability, type JsonObject } from './types.js'
|
|
5
5
|
import { MediaError } from './errors.js'
|
|
6
6
|
|
|
7
|
+
export function expandHomePath(rawPath: string | undefined): string | undefined {
|
|
8
|
+
if (!rawPath) return undefined
|
|
9
|
+
if (rawPath === '~') return homedir()
|
|
10
|
+
if (rawPath.startsWith('~/') || rawPath.startsWith('~\\')) {
|
|
11
|
+
return join(homedir(), rawPath.slice(2))
|
|
12
|
+
}
|
|
13
|
+
return rawPath
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export interface CustomAsyncConfig {
|
|
8
17
|
idPath: string
|
|
9
18
|
statusPath: string
|
|
@@ -61,8 +70,11 @@ export async function loadMediaConfig(cwd: string, allowProjectConfig: boolean):
|
|
|
61
70
|
const globalPath = join(homedir(), '.pi', 'agent', 'media-models.json')
|
|
62
71
|
const global = await parseFile(globalPath) ?? EMPTY_CONFIG
|
|
63
72
|
const project = allowProjectConfig ? await parseFile(join(cwd, '.pi', 'media-models.json')) : undefined
|
|
73
|
+
const rawOutputDir = project?.outputDir ?? global.outputDir
|
|
74
|
+
const expandedOutputDir = expandHomePath(rawOutputDir)
|
|
75
|
+
const outputDir = expandedOutputDir ? resolve(cwd, expandedOutputDir) : undefined
|
|
64
76
|
const merged: MediaConfig = {
|
|
65
|
-
outputDir
|
|
77
|
+
outputDir,
|
|
66
78
|
customProviders: project?.customProviders ?? global.customProviders ?? [],
|
|
67
79
|
providerOptions: { ...(global.providerOptions ?? {}), ...(project?.providerOptions ?? {}) },
|
|
68
80
|
}
|
package/src/router.ts
CHANGED
|
@@ -49,9 +49,15 @@ export class CapabilityRouter {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
private isConfigured(adapter: ProviderAdapter): boolean {
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
if (adapter.id === 'vertex') {
|
|
53
|
+
const vertexOpts = this.providerDefaults['vertex']
|
|
54
|
+
if (typeof vertexOpts?.credentialsFile === 'string' && vertexOpts.credentialsFile.trim()) return true
|
|
55
|
+
if (this.env.GOOGLE_APPLICATION_CREDENTIALS || this.env.VERTEX_CREDENTIALS_FILE) return true
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
const configKey = this.providerDefaults[adapter.id]?.apiKey
|
|
59
|
+
if (typeof configKey === 'string' && configKey.trim()) return true
|
|
60
|
+
if (adapter.envKey && this.env[adapter.envKey]) return true
|
|
55
61
|
return false
|
|
56
62
|
}
|
|
57
63
|
|