omni-pi 0.4.0 → 0.6.1
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 +42 -0
- package/CREDITS.md +11 -0
- package/PROVIDERS.md +76 -0
- package/README.md +75 -45
- package/extensions/omni-core/index.ts +7 -1
- package/extensions/omni-providers/index.ts +11 -2
- package/package.json +13 -4
- package/src/model-command.ts +146 -16
- package/src/model-setup.ts +346 -302
- package/src/provider-auth-command.ts +104 -0
- package/src/providers.ts +19 -2
- package/src/searchable-select.ts +198 -0
- package/src/todo-shortcut.ts +290 -0
- package/src/updater.ts +214 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.6.1 - 2026-03-29
|
|
4
|
+
|
|
5
|
+
### Provider management
|
|
6
|
+
|
|
7
|
+
- renamed bundled provider auth management from `/provider-auth` to `/manage-providers`
|
|
8
|
+
- limited `/model-setup list` to removing individual custom model entries instead of deleting whole custom providers
|
|
9
|
+
- documented the bundled provider list directly in `PROVIDERS.md`
|
|
10
|
+
|
|
11
|
+
### CI and documentation
|
|
12
|
+
|
|
13
|
+
- added a unified `npm run verify` gate for local development, CI, and publish checks
|
|
14
|
+
- added docs coverage tests so command docs and the bundled provider list fail CI when they drift from the code
|
|
15
|
+
- added a tag-triggered release workflow that re-verifies the repo, creates a GitHub release, and publishes to npm when credentials are configured
|
|
16
|
+
|
|
17
|
+
## 0.6.0 - 2026-03-27
|
|
18
|
+
|
|
19
|
+
### Provider management
|
|
20
|
+
|
|
21
|
+
- restricted `/model-setup` to custom providers and custom model entries stored in `models.json`
|
|
22
|
+
- added `/provider-auth` to remove stored auth for bundled Pi providers from the UI
|
|
23
|
+
- added whole-provider removal for custom providers, not just single-model removal
|
|
24
|
+
- fixed `/model-setup list` so it only shows custom providers/models instead of the full authenticated runtime catalog
|
|
25
|
+
|
|
26
|
+
### Provider discovery and refresh
|
|
27
|
+
|
|
28
|
+
- added startup refresh for authenticated, discoverable custom providers
|
|
29
|
+
- preserved dynamic headers and other existing model metadata when custom providers are edited or rediscovered
|
|
30
|
+
- improved custom-provider onboarding so users can add a provider first and discover models automatically
|
|
31
|
+
- stopped persisting invalid `contextWindow: 0` and `maxTokens: 0` values for discovered providers
|
|
32
|
+
|
|
33
|
+
### Selector and UX improvements
|
|
34
|
+
|
|
35
|
+
- aligned Omni-Pi setup selectors with Pi-style searchable selection behavior
|
|
36
|
+
- limited the custom searchable selector to 10 visible rows and only enabled search when more than 10 items are present
|
|
37
|
+
- improved bundled command descriptions and provider-management messaging
|
|
38
|
+
|
|
39
|
+
### Documentation
|
|
40
|
+
|
|
41
|
+
- documented the split between custom provider setup and bundled provider auth management in `README.md`
|
|
42
|
+
- added `PROVIDERS.md` with guidance for `/model-setup`, `/provider-auth`, and custom provider discovery behavior
|
package/CREDITS.md
CHANGED
|
@@ -9,6 +9,17 @@ Omni-Pi exists because of the Pi ecosystem and the work of earlier authors.
|
|
|
9
9
|
- [@mariozechner/pi-coding-agent](https://www.npmjs.com/package/@mariozechner/pi-coding-agent) by Mario Zechner
|
|
10
10
|
- Pi coding agent package (direct dependency)
|
|
11
11
|
|
|
12
|
+
## Bundled third-party extensions
|
|
13
|
+
|
|
14
|
+
- [pi-web-access](https://www.npmjs.com/package/pi-web-access)
|
|
15
|
+
- Web search and fetch tools for the agent
|
|
16
|
+
- [pi-interview](https://www.npmjs.com/package/pi-interview)
|
|
17
|
+
- Guided Q&A interface for user clarification
|
|
18
|
+
- [@juanibiapina/pi-powerbar](https://www.npmjs.com/package/@juanibiapina/pi-powerbar) by Juani Biapina
|
|
19
|
+
- Powerline-style status bar with extensible segments
|
|
20
|
+
- [@juanibiapina/pi-extension-settings](https://www.npmjs.com/package/@juanibiapina/pi-extension-settings) by Juani Biapina
|
|
21
|
+
- Settings persistence for Pi extensions
|
|
22
|
+
|
|
12
23
|
## Workflow and orchestration inspiration
|
|
13
24
|
|
|
14
25
|
- [can1357/oh-my-pi](https://github.com/can1357/oh-my-pi)
|
package/PROVIDERS.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Provider Setup
|
|
2
|
+
|
|
3
|
+
Omni-Pi separates bundled providers from custom providers.
|
|
4
|
+
|
|
5
|
+
## `/model-setup`
|
|
6
|
+
|
|
7
|
+
`/model-setup` is only for custom providers and custom model entries stored in `models.json`.
|
|
8
|
+
|
|
9
|
+
Use it when you want to configure:
|
|
10
|
+
|
|
11
|
+
- your own provider id
|
|
12
|
+
- an API type such as OpenAI-compatible or Anthropic-compatible
|
|
13
|
+
- a custom base URL
|
|
14
|
+
- an API key for that custom provider
|
|
15
|
+
- discovered models or manual model entries
|
|
16
|
+
|
|
17
|
+
`/model-setup list` only shows custom models from `models.json`, and it removes individual custom model entries only.
|
|
18
|
+
|
|
19
|
+
## Bundled Providers
|
|
20
|
+
|
|
21
|
+
Pi's bundled providers are still available through the runtime model registry, but Omni-Pi does not manage them through `/model-setup`.
|
|
22
|
+
|
|
23
|
+
That means:
|
|
24
|
+
|
|
25
|
+
- `/model-setup` does not add bundled providers
|
|
26
|
+
- `/model-setup` does not list bundled providers
|
|
27
|
+
- `/model-setup` does not remove bundled providers
|
|
28
|
+
|
|
29
|
+
If a bundled provider already has valid auth in the Pi runtime, Omni-Pi may use it through normal model selection, but its setup is outside the custom-provider flow documented here.
|
|
30
|
+
|
|
31
|
+
Use `/manage-providers` to list bundled providers that currently have stored auth and remove that auth when needed.
|
|
32
|
+
|
|
33
|
+
The bundled-provider list below is expected to stay in sync with the exported provider setup list in `src/model-setup.ts`. The test suite checks that this section matches the code list.
|
|
34
|
+
|
|
35
|
+
### Bundled provider list
|
|
36
|
+
|
|
37
|
+
- `anthropic` — API key
|
|
38
|
+
- `openai` — API key
|
|
39
|
+
- `openrouter` — API key
|
|
40
|
+
- `google` — API key
|
|
41
|
+
- `github-copilot` — OAuth
|
|
42
|
+
- `openai-codex` — OAuth
|
|
43
|
+
- `xai` — API key
|
|
44
|
+
- `zai` — API key
|
|
45
|
+
- `azure-openai-responses` — API key
|
|
46
|
+
- `nvidia` — API key
|
|
47
|
+
- `together` — API key
|
|
48
|
+
- `synthetic` — API key
|
|
49
|
+
- `nanogpt` — API key
|
|
50
|
+
- `xiaomi` — API key
|
|
51
|
+
- `moonshot` — API key
|
|
52
|
+
- `venice` — API key
|
|
53
|
+
- `kilo` — API key
|
|
54
|
+
- `gitlab-duo` — API key
|
|
55
|
+
- `qwen-portal` — API key
|
|
56
|
+
- `qianfan` — API key
|
|
57
|
+
- `cloudflare-ai-gateway` — API key
|
|
58
|
+
|
|
59
|
+
## Custom Provider Discovery
|
|
60
|
+
|
|
61
|
+
For custom providers that expose a compatible model listing endpoint, Omni-Pi can fetch models for you after you add the provider details and credentials.
|
|
62
|
+
|
|
63
|
+
On launch, Omni-Pi also refreshes authenticated, discoverable custom providers that are already configured in `models.json`.
|
|
64
|
+
|
|
65
|
+
## When To Use Which Path
|
|
66
|
+
|
|
67
|
+
Use `/model-setup` when:
|
|
68
|
+
|
|
69
|
+
- you are adding a non-bundled provider
|
|
70
|
+
- you need a custom base URL
|
|
71
|
+
- you want to manage your own discovered or manual model list
|
|
72
|
+
|
|
73
|
+
Do not use `/model-setup` when:
|
|
74
|
+
|
|
75
|
+
- you are trying to manage a built-in Pi provider
|
|
76
|
+
- you expect bundled providers to appear as removable custom entries
|
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Omni-Pi
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The goal is simple: talk to a helpful agent, let it interview you until the request is precise, have it write the spec and task breakdown into `.omni/`, then implement the work in bounded slices with explicit verification and progress notes.
|
|
3
|
+
A batteries-included [Pi](https://github.com/badlogic/pi-mono) package that interviews the user, documents the spec, and implements work in bounded slices.
|
|
6
4
|
|
|
7
5
|
Requires Node.js 22 or newer.
|
|
8
6
|
|
|
@@ -12,15 +10,13 @@ Requires Node.js 22 or newer.
|
|
|
12
10
|
|
|
13
11
|
## What It Does
|
|
14
12
|
|
|
15
|
-
- One
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
13
|
+
- One conversational brain interviews the user until the request is precise.
|
|
14
|
+
- Writes specs, tasks, and progress into `.omni/` as durable project memory.
|
|
15
|
+
- Breaks work into small, verifiable slices and implements them one at a time.
|
|
16
|
+
- Bundles web search, guided interviews, themed UI, a task viewer, a powerbar, custom provider/model management, and automatic updates out of the box.
|
|
19
17
|
|
|
20
18
|
## Install
|
|
21
19
|
|
|
22
|
-
Install the standalone executable:
|
|
23
|
-
|
|
24
20
|
```bash
|
|
25
21
|
npm install -g omni-pi
|
|
26
22
|
```
|
|
@@ -32,61 +28,95 @@ cd your-project
|
|
|
32
28
|
omni
|
|
33
29
|
```
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
Custom provider setup and bundled provider behavior are documented in [PROVIDERS.md](PROVIDERS.md).
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
git clone https://github.com/EdGy2k/Omni-Pi.git
|
|
39
|
-
cd Omni-Pi
|
|
40
|
-
npm install
|
|
41
|
-
npm run chat
|
|
42
|
-
```
|
|
33
|
+
## Features
|
|
43
34
|
|
|
44
|
-
|
|
35
|
+
### Bundled Extensions
|
|
45
36
|
|
|
46
|
-
|
|
37
|
+
| Extension | What it does |
|
|
38
|
+
|-----------|-------------|
|
|
39
|
+
| **omni-core** | Brain workflow, themed header, session init, system prompt injection |
|
|
40
|
+
| **omni-providers** | Model provider wiring |
|
|
41
|
+
| **omni-memory** | `.omni/` durable memory bootstrap |
|
|
42
|
+
| **pi-web-access** | Web search and fetch tools for the agent |
|
|
43
|
+
| **pi-interview** | Guided Q&A when the agent needs clarification |
|
|
44
|
+
| **pi-powerbar** | Powerline-style status bar with segments |
|
|
45
|
+
| **pi-extension-settings** | Settings persistence for extensions |
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
### Commands
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
| Command | Description |
|
|
50
|
+
|---------|-------------|
|
|
51
|
+
| `/model-setup` | Add custom providers/models or remove custom model entries |
|
|
52
|
+
| `/manage-providers` | Remove stored auth for bundled providers |
|
|
53
|
+
| `/theme` | Switch between color presets (lavender, ember, ocean, mint, rose, gold, arctic, neon, copper, slate) |
|
|
54
|
+
| `/update` | Check for Omni-Pi updates |
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
### Keyboard Shortcuts
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- It implements one bounded slice at a time and runs the planned checks.
|
|
58
|
+
| Shortcut | Description |
|
|
59
|
+
|----------|-------------|
|
|
60
|
+
| `Ctrl+Shift+T` | Toggle the task list widget (`.omni/TASKS.md` + `.omni/STATE.md`) |
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
### Auto-Updater
|
|
62
63
|
|
|
63
|
-
Omni-Pi
|
|
64
|
+
Omni-Pi checks for new versions on startup (cached, re-checks every 4 hours). When an update is available, it prompts to install and restart. Pi's own update notification is suppressed to avoid duplication.
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
- `SPEC.md` captures the exact requested behavior and implementation shape.
|
|
67
|
-
- `TASKS.md` breaks work into bounded slices.
|
|
68
|
-
- `TESTS.md` records the checks for the current slice.
|
|
69
|
-
- `STATE.md`, `SESSION-SUMMARY.md`, and `DECISIONS.md` keep progress and rationale durable across sessions.
|
|
66
|
+
## Provider Support
|
|
70
67
|
|
|
71
|
-
|
|
68
|
+
`/model-setup` is for custom providers and custom model entries only.
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
Use it when you want to configure:
|
|
74
71
|
|
|
75
|
-
|
|
72
|
+
- a custom provider id
|
|
73
|
+
- an API type and base URL
|
|
74
|
+
- an API key for that custom provider
|
|
75
|
+
- discovered models or manual model entries
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
Use `/manage-providers` to remove stored auth for bundled Pi providers.
|
|
78
|
+
|
|
79
|
+
See [PROVIDERS.md](PROVIDERS.md) for the current supported-provider list and auth-management split.
|
|
80
|
+
|
|
81
|
+
## Durable Memory
|
|
82
|
+
|
|
83
|
+
Omni-Pi keeps its working notes in `.omni/`:
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
| File | Purpose |
|
|
86
|
+
|------|---------|
|
|
87
|
+
| `PROJECT.md` | Problem, users, constraints, success criteria |
|
|
88
|
+
| `SPEC.md` | Exact requested behavior and implementation shape |
|
|
89
|
+
| `TASKS.md` | Work broken into bounded slices |
|
|
90
|
+
| `TESTS.md` | Checks for the current slice |
|
|
91
|
+
| `STATE.md` | Current phase, active task, blockers |
|
|
92
|
+
| `SESSION-SUMMARY.md` | Progress notes across sessions |
|
|
93
|
+
| `DECISIONS.md` | Rationale for key choices |
|
|
94
|
+
|
|
95
|
+
## Development
|
|
84
96
|
|
|
85
97
|
```bash
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
git clone https://github.com/EdGy2k/Omni-Pi.git
|
|
99
|
+
cd Omni-Pi
|
|
100
|
+
npm install
|
|
101
|
+
npm run chat # launch locally in dev mode
|
|
88
102
|
```
|
|
89
103
|
|
|
104
|
+
| Command | Purpose |
|
|
105
|
+
|---------|---------|
|
|
106
|
+
| `npm run chat` | Launch the local `omni` executable |
|
|
107
|
+
| `npm test` | Run the test suite (Vitest) |
|
|
108
|
+
| `npm run check` | TypeScript type-check |
|
|
109
|
+
| `npm run lint` | Biome lint + format check |
|
|
110
|
+
| `npm run verify` | Full local/CI gate: type-check, lint, test, and package dry-run |
|
|
111
|
+
| `npm run format` | Auto-fix lint and formatting |
|
|
112
|
+
| `npm install -g .` | Install globally from local checkout |
|
|
113
|
+
|
|
114
|
+
## CI/CD
|
|
115
|
+
|
|
116
|
+
- Pull requests and pushes to `main` run `npm run verify`.
|
|
117
|
+
- The docs are part of the test contract, including a sync check between `PROVIDERS.md` and the bundled-provider setup list in code.
|
|
118
|
+
- Pushing a `v*` tag runs the release workflow, verifies the repo again, creates a GitHub release, and publishes to npm when `NPM_TOKEN` is configured.
|
|
119
|
+
|
|
90
120
|
## Attribution
|
|
91
121
|
|
|
92
122
|
Omni-Pi builds on the Pi ecosystem. See [CREDITS.md](CREDITS.md).
|
|
@@ -7,20 +7,26 @@ import {
|
|
|
7
7
|
import { renderHeader } from "../../src/header.js";
|
|
8
8
|
import { registerModelCommand } from "../../src/model-command.js";
|
|
9
9
|
import { registerOmniMessageRenderer } from "../../src/pi.js";
|
|
10
|
+
import { registerProviderAuthCommand } from "../../src/provider-auth-command.js";
|
|
10
11
|
import { createOmniTheme } from "../../src/theme.js";
|
|
11
12
|
import { registerThemeCommand } from "../../src/theme-command.js";
|
|
13
|
+
import { registerTodoShortcut } from "../../src/todo-shortcut.js";
|
|
14
|
+
import { registerUpdater } from "../../src/updater.js";
|
|
12
15
|
|
|
13
16
|
export default function omniCoreExtension(api: ExtensionAPI): void {
|
|
14
17
|
registerOmniMessageRenderer(api);
|
|
15
18
|
registerModelCommand(api);
|
|
19
|
+
registerProviderAuthCommand(api);
|
|
16
20
|
registerThemeCommand(api);
|
|
21
|
+
registerTodoShortcut(api);
|
|
22
|
+
registerUpdater(api);
|
|
17
23
|
|
|
18
24
|
api.on("session_start", async (_event, ctx) => {
|
|
19
25
|
await ensureOmniInitialized(ctx.cwd);
|
|
20
26
|
ctx.ui.setTitle("Omni-Pi");
|
|
21
27
|
ctx.ui.setTheme(createOmniTheme());
|
|
22
28
|
ctx.ui.setHeader((_tui, theme) => renderHeader(theme));
|
|
23
|
-
ctx.ui.setStatus("omni",
|
|
29
|
+
ctx.ui.setStatus("omni", "\x1b[2mctrl+shift+t tasks\x1b[0m");
|
|
24
30
|
});
|
|
25
31
|
|
|
26
32
|
api.on("before_agent_start", async (event, ctx) => {
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { refreshAuthenticatedProviderModels } from "../../src/model-setup.js";
|
|
4
|
+
import { registerOmniProviders } from "../../src/providers.js";
|
|
5
|
+
|
|
1
6
|
export default async function omniProvidersExtension(
|
|
2
|
-
|
|
7
|
+
api: ExtensionAPI,
|
|
3
8
|
): Promise<void> {
|
|
4
|
-
|
|
9
|
+
await registerOmniProviders(api);
|
|
10
|
+
|
|
11
|
+
api.on("session_start", async (_event, ctx) => {
|
|
12
|
+
await refreshAuthenticatedProviderModels(ctx.modelRegistry);
|
|
13
|
+
});
|
|
5
14
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Single-agent Pi package that interviews the user, documents the spec, and implements work in bounded slices.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"bin",
|
|
32
32
|
"agents",
|
|
33
|
+
"CHANGELOG.md",
|
|
33
34
|
"extensions",
|
|
34
35
|
"prompts",
|
|
35
36
|
"skills",
|
|
36
37
|
"src",
|
|
37
38
|
"templates",
|
|
38
39
|
"README.md",
|
|
40
|
+
"PROVIDERS.md",
|
|
39
41
|
"CREDITS.md"
|
|
40
42
|
],
|
|
41
43
|
"scripts": {
|
|
@@ -44,8 +46,9 @@
|
|
|
44
46
|
"lint": "biome check .",
|
|
45
47
|
"format": "biome check --write .",
|
|
46
48
|
"test": "vitest run",
|
|
49
|
+
"verify": "npm run check && npm run lint && npm test && npm run pack:check",
|
|
47
50
|
"pack:check": "npm pack --dry-run",
|
|
48
|
-
"prepublishOnly": "npm run
|
|
51
|
+
"prepublishOnly": "npm run verify"
|
|
49
52
|
},
|
|
50
53
|
"devDependencies": {
|
|
51
54
|
"@biomejs/biome": "2.4.9",
|
|
@@ -58,7 +61,10 @@
|
|
|
58
61
|
"./extensions/omni-providers/index.ts",
|
|
59
62
|
"./extensions/omni-core/index.ts",
|
|
60
63
|
"./extensions/omni-memory/index.ts",
|
|
61
|
-
"./node_modules/pi-web-access/index.ts"
|
|
64
|
+
"./node_modules/pi-web-access/index.ts",
|
|
65
|
+
"./node_modules/pi-interview/index.ts",
|
|
66
|
+
"./node_modules/@juanibiapina/pi-extension-settings/dist/index.js",
|
|
67
|
+
"./node_modules/@juanibiapina/pi-powerbar/dist"
|
|
62
68
|
],
|
|
63
69
|
"skills": [
|
|
64
70
|
"./skills",
|
|
@@ -70,7 +76,10 @@
|
|
|
70
76
|
},
|
|
71
77
|
"dependencies": {
|
|
72
78
|
"@anthropic-ai/claude-agent-sdk": "0.2.84",
|
|
73
|
-
"@
|
|
79
|
+
"@juanibiapina/pi-extension-settings": "^0.6.0",
|
|
80
|
+
"@juanibiapina/pi-powerbar": "^0.7.1",
|
|
81
|
+
"@mariozechner/pi-coding-agent": "^0.63.0",
|
|
82
|
+
"pi-interview": "^0.5.5",
|
|
74
83
|
"pi-subagents": "^0.11.11",
|
|
75
84
|
"pi-web-access": "^0.10.3",
|
|
76
85
|
"zod": "^4.3.6"
|
package/src/model-command.ts
CHANGED
|
@@ -1,47 +1,177 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
1
4
|
import type {
|
|
2
5
|
ExtensionAPI,
|
|
3
6
|
ExtensionCommandContext,
|
|
4
7
|
} from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
import { runModelSetupWizard } from "./model-setup.js";
|
|
11
|
+
import { searchableSelect } from "./searchable-select.js";
|
|
12
|
+
|
|
13
|
+
interface ModelsJsonModel {
|
|
14
|
+
id: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ModelsJsonProvider {
|
|
19
|
+
models?: ModelsJsonModel[];
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ModelsJsonConfig {
|
|
24
|
+
providers?: Record<string, ModelsJsonProvider>;
|
|
25
|
+
}
|
|
5
26
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
27
|
+
type CustomModelEntry = { provider: string; modelId: string };
|
|
28
|
+
type ListOption = { provider: string; modelId: string; label: string };
|
|
29
|
+
|
|
30
|
+
function getModelsPath(): string {
|
|
31
|
+
return path.join(getAgentDir(), "models.json");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function readModelsJson(): Promise<ModelsJsonConfig> {
|
|
35
|
+
try {
|
|
36
|
+
const content = await readFile(getModelsPath(), "utf8");
|
|
37
|
+
const parsed = JSON.parse(content) as ModelsJsonConfig;
|
|
38
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
39
|
+
} catch {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function writeModelsJson(config: ModelsJsonConfig): Promise<void> {
|
|
45
|
+
const modelsPath = getModelsPath();
|
|
46
|
+
await mkdir(path.dirname(modelsPath), { recursive: true });
|
|
47
|
+
await writeFile(modelsPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getCustomModels(config: ModelsJsonConfig): CustomModelEntry[] {
|
|
51
|
+
const entries: CustomModelEntry[] = [];
|
|
52
|
+
for (const [provider, providerConfig] of Object.entries(
|
|
53
|
+
config.providers ?? {},
|
|
54
|
+
)) {
|
|
55
|
+
for (const model of providerConfig.models ?? []) {
|
|
56
|
+
entries.push({ provider, modelId: model.id });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return entries;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function removeCustomModelFromConfig(
|
|
63
|
+
config: ModelsJsonConfig,
|
|
64
|
+
provider: string,
|
|
65
|
+
modelId: string,
|
|
66
|
+
): ModelsJsonConfig {
|
|
67
|
+
const providers = { ...(config.providers ?? {}) };
|
|
68
|
+
const providerConfig = providers[provider];
|
|
69
|
+
if (!providerConfig?.models) {
|
|
70
|
+
return {
|
|
71
|
+
...config,
|
|
72
|
+
providers,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
providerConfig.models = providerConfig.models.filter(
|
|
77
|
+
(model) => model.id !== modelId,
|
|
78
|
+
);
|
|
79
|
+
if (providerConfig.models.length === 0) {
|
|
80
|
+
delete providers[provider];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...config,
|
|
85
|
+
providers,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildListOptions(customModels: CustomModelEntry[]): ListOption[] {
|
|
90
|
+
return customModels
|
|
91
|
+
.map(({ provider, modelId }) => ({
|
|
92
|
+
provider,
|
|
93
|
+
modelId,
|
|
94
|
+
label: `${provider}/${modelId} ✕`,
|
|
95
|
+
}))
|
|
96
|
+
.sort((left, right) => left.label.localeCompare(right.label));
|
|
97
|
+
}
|
|
10
98
|
|
|
11
99
|
async function handleAdd(ctx: ExtensionCommandContext): Promise<void> {
|
|
12
|
-
const result = await
|
|
100
|
+
const result = await runModelSetupWizard({ ctx });
|
|
13
101
|
ctx.ui.notify(result.summary, "info");
|
|
14
102
|
}
|
|
15
103
|
|
|
16
104
|
async function handleList(ctx: ExtensionCommandContext): Promise<void> {
|
|
17
|
-
const
|
|
105
|
+
const config = await readModelsJson();
|
|
106
|
+
const custom = getCustomModels(config);
|
|
107
|
+
const options = buildListOptions(custom);
|
|
18
108
|
|
|
19
|
-
if (
|
|
20
|
-
ctx.ui.notify("No models
|
|
109
|
+
if (options.length === 0) {
|
|
110
|
+
ctx.ui.notify("No custom models found.", "info");
|
|
21
111
|
return;
|
|
22
112
|
}
|
|
23
113
|
|
|
24
|
-
|
|
114
|
+
while (true) {
|
|
115
|
+
const selected = await searchableSelect(
|
|
116
|
+
ctx.ui,
|
|
117
|
+
"Custom models (✕ = remove):",
|
|
118
|
+
options.map((option) => ({
|
|
119
|
+
label: option.label,
|
|
120
|
+
value: option.label,
|
|
121
|
+
searchText: option.label.replace("✕", ""),
|
|
122
|
+
})),
|
|
123
|
+
);
|
|
124
|
+
if (selected === undefined) return;
|
|
125
|
+
const selectedOption = options.find((option) => option.label === selected);
|
|
126
|
+
if (!selectedOption) continue;
|
|
127
|
+
|
|
128
|
+
const modelRef = `${selectedOption.provider}/${selectedOption.modelId}`;
|
|
129
|
+
const confirmed = await ctx.ui.confirm(
|
|
130
|
+
"Remove model?",
|
|
131
|
+
`Remove ${modelRef} from models.json?`,
|
|
132
|
+
);
|
|
133
|
+
if (!confirmed) return;
|
|
134
|
+
|
|
135
|
+
const freshConfig = await readModelsJson();
|
|
136
|
+
await writeModelsJson(
|
|
137
|
+
removeCustomModelFromConfig(
|
|
138
|
+
freshConfig,
|
|
139
|
+
selectedOption.provider,
|
|
140
|
+
selectedOption.modelId,
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
ctx.modelRegistry.refresh();
|
|
145
|
+
ctx.ui.notify(`Removed ${modelRef}.`, "info");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
25
148
|
}
|
|
26
149
|
|
|
27
150
|
export function registerModelCommand(api: ExtensionAPI): void {
|
|
28
151
|
api.registerCommand("model-setup", {
|
|
29
|
-
description: "Add or
|
|
152
|
+
description: "Add custom providers/models or remove custom model entries",
|
|
30
153
|
async handler(args: string, ctx: ExtensionCommandContext) {
|
|
31
154
|
const sub = args.trim().toLowerCase();
|
|
32
155
|
|
|
33
156
|
if (sub === "add") return handleAdd(ctx);
|
|
34
157
|
if (sub === "list") return handleList(ctx);
|
|
35
158
|
|
|
36
|
-
const choice = await ctx.ui
|
|
37
|
-
|
|
38
|
-
|
|
159
|
+
const choice = await searchableSelect(ctx.ui, "Model setup:", [
|
|
160
|
+
{
|
|
161
|
+
label: "add — Add a custom provider or model",
|
|
162
|
+
value: "add",
|
|
163
|
+
searchText: "add custom provider model",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: "list — Show custom models / remove model entries",
|
|
167
|
+
value: "list",
|
|
168
|
+
searchText: "list remove custom models",
|
|
169
|
+
},
|
|
39
170
|
]);
|
|
40
171
|
if (!choice) return;
|
|
41
172
|
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
if (picked === "list") return handleList(ctx);
|
|
173
|
+
if (choice === "add") return handleAdd(ctx);
|
|
174
|
+
if (choice === "list") return handleList(ctx);
|
|
45
175
|
},
|
|
46
176
|
});
|
|
47
177
|
}
|