omni-pi 0.1.0 → 0.2.0
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 +66 -3
- package/extensions/omni-providers/index.ts +9 -0
- package/package.json +5 -2
- package/src/commands.ts +13 -1
- package/src/config.ts +2 -14
- package/src/providers.ts +682 -0
- package/src/subagents.ts +287 -53
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Omni-Pi: Guided software delivery for everyone.
|
|
4
4
|
|
|
5
|
-
Omni-Pi is an opinionated Pi package and branded launcher
|
|
5
|
+
Omni-Pi is an opinionated Pi package and branded launcher published on npm as `omni-pi`. It helps people move from a blank repo to a structured plan, implemented work, and explicit verification without having to assemble the workflow themselves.
|
|
6
6
|
|
|
7
7
|
Requires Node.js 22 or newer.
|
|
8
8
|
|
|
@@ -19,12 +19,73 @@ Requires Node.js 22 or newer.
|
|
|
19
19
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
22
|
+
Install Omni-Pi from npm, then run it in your project:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g omni-pi
|
|
26
|
+
cd your-project
|
|
27
|
+
omni
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
Install the published package globally with npm:
|
|
33
|
+
|
|
22
34
|
```bash
|
|
23
35
|
npm install -g omni-pi
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Confirm the launcher is available:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
omni --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then open any project directory and start Omni-Pi:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
24
47
|
cd your-project
|
|
25
48
|
omni
|
|
26
49
|
```
|
|
27
50
|
|
|
51
|
+
To upgrade later:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install -g omni-pi@latest
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Omni-Pi launches the bundled Pi runtime and loads the Omni-Pi package automatically, so you do not need to manually wire extensions, skills, or prompts after installing from npm.
|
|
58
|
+
|
|
59
|
+
## Model Providers
|
|
60
|
+
|
|
61
|
+
Omni-Pi now ships the upstream provider mix needed for practical multi-provider use on top of Pi.
|
|
62
|
+
|
|
63
|
+
- Built into the underlying Pi runtime: `anthropic`, `openai`, `openai-codex`, `google`, `google-vertex`, `amazon-bedrock`, `azure-openai-responses`, `openrouter`, `xai`, `zai`, `mistral`, `groq`, `cerebras`, `huggingface`, `github-copilot`, `kimi-coding`, `minimax`, `minimax-cn`, `opencode`, `opencode-go`
|
|
64
|
+
- Added by Omni-Pi: `nvidia`, `together`, `synthetic`, `nanogpt`, `xiaomi`, `moonshot`, `venice`, `kilo`, `gitlab-duo`, `qwen-portal`, `qianfan`, `cloudflare-ai-gateway`
|
|
65
|
+
- Auto-discovered when running locally: `ollama`, `lm-studio`, `llama.cpp`, `litellm`, `vllm`
|
|
66
|
+
|
|
67
|
+
For users who do not want to rely on Anthropic OAuth inside Pi, Omni-Pi also exposes opt-in Claude Agent SDK model aliases:
|
|
68
|
+
|
|
69
|
+
- `claude-agent/claude-sonnet-4-6`
|
|
70
|
+
- `claude-agent/claude-opus-4-6`
|
|
71
|
+
|
|
72
|
+
These are intended for Omni-Pi's worker and expert subagents. Configure a role with `/omni-model` and Omni-Pi will run that subagent through the Claude Agent SDK instead of Pi's normal Anthropic provider path.
|
|
73
|
+
|
|
74
|
+
Common provider env vars:
|
|
75
|
+
|
|
76
|
+
- `NVIDIA_API_KEY`, `TOGETHER_API_KEY`, `SYNTHETIC_API_KEY`, `NANO_GPT_API_KEY`
|
|
77
|
+
- `XIAOMI_API_KEY`, `MOONSHOT_API_KEY`, `VENICE_API_KEY`, `KILO_API_KEY`
|
|
78
|
+
- `GITLAB_TOKEN`, `QWEN_OAUTH_TOKEN` or `QWEN_PORTAL_API_KEY`, `QIANFAN_API_KEY`
|
|
79
|
+
- `CLOUDFLARE_AI_GATEWAY_API_KEY` and `CLOUDFLARE_AI_GATEWAY_BASE_URL`
|
|
80
|
+
|
|
81
|
+
For local providers, Omni-Pi registers models only when the endpoint is reachable:
|
|
82
|
+
|
|
83
|
+
- `OLLAMA_BASE_URL` / `OLLAMA_API_KEY`
|
|
84
|
+
- `LM_STUDIO_BASE_URL` / `LM_STUDIO_API_KEY`
|
|
85
|
+
- `LLAMA_CPP_BASE_URL` / `LLAMA_CPP_API_KEY`
|
|
86
|
+
- `LITELLM_BASE_URL` / `LITELLM_API_KEY`
|
|
87
|
+
- `VLLM_BASE_URL` / `VLLM_API_KEY`
|
|
88
|
+
|
|
28
89
|
## Commands
|
|
29
90
|
|
|
30
91
|
| Command | Description |
|
|
@@ -36,7 +97,7 @@ omni
|
|
|
36
97
|
| `/omni-sync` | Update durable memory files from recent progress |
|
|
37
98
|
| `/omni-skills` | Inspect installed, recommended, deferred, and rejected skills |
|
|
38
99
|
| `/omni-explain` | Explain what Omni-Pi is doing in simple language |
|
|
39
|
-
| `/omni-model` | Interactively select the model for a specific agent role |
|
|
100
|
+
| `/omni-model` | Interactively select the model for a specific agent role, or enter any canonical `provider/model` reference |
|
|
40
101
|
| `/omni-commit` | Create a branch and commit for the last completed task |
|
|
41
102
|
| `/omni-doctor` | Run diagnostic health checks and detect stuck tasks |
|
|
42
103
|
|
|
@@ -46,6 +107,8 @@ Omni-Pi follows a simple agent pipeline: Brain, Planner, Worker, Expert. The Bra
|
|
|
46
107
|
|
|
47
108
|
When the Worker gets stuck or verification fails repeatedly, the Expert role steps in to recover the task, adapt the approach, or surface the blocker clearly instead of letting the session stall.
|
|
48
109
|
|
|
110
|
+
On first use inside a project, Omni-Pi creates and updates `.omni/` state so plans, task progress, verification steps, and recovery context persist across sessions.
|
|
111
|
+
|
|
49
112
|
## Features
|
|
50
113
|
|
|
51
114
|
- Core workflow with durable `.omni/` project memory, typed planning and execution contracts, filesystem-backed init/planning/status, and retry-aware task execution.
|
|
@@ -61,7 +124,7 @@ When the Worker gets stuck or verification fails repeatedly, the Expert role ste
|
|
|
61
124
|
|
|
62
125
|
## Development
|
|
63
126
|
|
|
64
|
-
For
|
|
127
|
+
For local checkout development, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
65
128
|
|
|
66
129
|
```bash
|
|
67
130
|
git clone https://github.com/EdGy2k/Omni-Pi.git
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { registerOmniProviders } from "../../src/providers.js";
|
|
4
|
+
|
|
5
|
+
export default async function omniProvidersExtension(
|
|
6
|
+
api: ExtensionAPI,
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
await registerOmniProviders(api);
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Opinionated Pi package for guided, beginner-friendly planning and implementation workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"extensions": [
|
|
57
57
|
"./node_modules/pi-subagents/index.ts",
|
|
58
58
|
"./node_modules/pi-subagents/notify.ts",
|
|
59
|
+
"./extensions/omni-providers/index.ts",
|
|
59
60
|
"./extensions/omni-core/index.ts",
|
|
60
61
|
"./extensions/omni-memory/index.ts",
|
|
61
62
|
"./extensions/omni-skills/index.ts",
|
|
@@ -69,7 +70,9 @@
|
|
|
69
70
|
]
|
|
70
71
|
},
|
|
71
72
|
"dependencies": {
|
|
73
|
+
"@anthropic-ai/claude-agent-sdk": "0.2.84",
|
|
72
74
|
"@mariozechner/pi-coding-agent": "^0.62.0",
|
|
73
|
-
"pi-subagents": "^0.11.11"
|
|
75
|
+
"pi-subagents": "^0.11.11",
|
|
76
|
+
"zod": "^4.3.6"
|
|
74
77
|
}
|
|
75
78
|
}
|
package/src/commands.ts
CHANGED
|
@@ -449,6 +449,7 @@ export function createOmniCommands(): AppCommandDefinition[] {
|
|
|
449
449
|
const modelOptions = AVAILABLE_MODELS.map((model) =>
|
|
450
450
|
model === currentModel ? `${model} (current)` : model,
|
|
451
451
|
);
|
|
452
|
+
modelOptions.push("Enter custom provider/model");
|
|
452
453
|
|
|
453
454
|
const selectedModelDisplay = await ui.select(
|
|
454
455
|
`Select model for ${selectedAgent}:`,
|
|
@@ -458,7 +459,18 @@ export function createOmniCommands(): AppCommandDefinition[] {
|
|
|
458
459
|
return "Model selection cancelled.";
|
|
459
460
|
}
|
|
460
461
|
|
|
461
|
-
|
|
462
|
+
let selectedModel = selectedModelDisplay.replace(" (current)", "");
|
|
463
|
+
if (selectedModel === "Enter custom provider/model") {
|
|
464
|
+
const customModel = await ui.input(
|
|
465
|
+
"Enter model as provider/model",
|
|
466
|
+
"e.g., openrouter/anthropic/claude-sonnet-4",
|
|
467
|
+
);
|
|
468
|
+
if (!customModel?.includes("/")) {
|
|
469
|
+
return "Custom model cancelled. Use the canonical provider/model format.";
|
|
470
|
+
}
|
|
471
|
+
selectedModel = customModel.trim();
|
|
472
|
+
}
|
|
473
|
+
|
|
462
474
|
await updateModelConfig(cwd, selectedAgent, selectedModel);
|
|
463
475
|
|
|
464
476
|
return `Updated ${selectedAgent} model to ${selectedModel}. Configuration saved to .omni/CONFIG.md`;
|
package/src/config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import type { OmniConfig } from "./contracts.js";
|
|
5
|
+
import { AVAILABLE_MODELS } from "./providers.js";
|
|
5
6
|
|
|
6
7
|
export const DEFAULT_CONFIG: OmniConfig = {
|
|
7
8
|
models: {
|
|
@@ -138,17 +139,4 @@ export async function updateModelConfig(
|
|
|
138
139
|
return config;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
export
|
|
142
|
-
"anthropic/claude-sonnet-4-6",
|
|
143
|
-
"anthropic/claude-opus-4-6",
|
|
144
|
-
"anthropic/claude-sonnet-4-5",
|
|
145
|
-
"anthropic/claude-opus-4-1",
|
|
146
|
-
"openai/gpt-5.4",
|
|
147
|
-
"openai/gpt-5",
|
|
148
|
-
"openai/gpt-4.1",
|
|
149
|
-
"openai/gpt-4o",
|
|
150
|
-
"openai/o3-mini",
|
|
151
|
-
"openai/o1",
|
|
152
|
-
"google/gemini-2.5-pro",
|
|
153
|
-
"google/gemini-2.5-flash",
|
|
154
|
-
];
|
|
142
|
+
export { AVAILABLE_MODELS };
|
package/src/providers.ts
ADDED
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type ModelApi =
|
|
4
|
+
| "anthropic-messages"
|
|
5
|
+
| "openai-completions"
|
|
6
|
+
| "openai-responses";
|
|
7
|
+
|
|
8
|
+
interface OmniProviderModel {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
api: ModelApi;
|
|
12
|
+
reasoning: boolean;
|
|
13
|
+
input: Array<"text" | "image">;
|
|
14
|
+
cost: {
|
|
15
|
+
input: number;
|
|
16
|
+
output: number;
|
|
17
|
+
cacheRead: number;
|
|
18
|
+
cacheWrite: number;
|
|
19
|
+
};
|
|
20
|
+
contextWindow: number;
|
|
21
|
+
maxTokens: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface StaticProviderDefinition {
|
|
25
|
+
name: string;
|
|
26
|
+
apiKey: string;
|
|
27
|
+
models: OmniProviderModel[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface LocalDiscoveryDefinition {
|
|
31
|
+
name: string;
|
|
32
|
+
api: ModelApi;
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
apiKeyEnv?: string;
|
|
35
|
+
discover: () => Promise<OmniProviderModel[]>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ZERO_COST = {
|
|
39
|
+
input: 0,
|
|
40
|
+
output: 0,
|
|
41
|
+
cacheRead: 0,
|
|
42
|
+
cacheWrite: 0,
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
45
|
+
function model(
|
|
46
|
+
id: string,
|
|
47
|
+
name: string,
|
|
48
|
+
api: ModelApi,
|
|
49
|
+
reasoning: boolean,
|
|
50
|
+
input: Array<"text" | "image">,
|
|
51
|
+
contextWindow: number,
|
|
52
|
+
maxTokens: number,
|
|
53
|
+
): OmniProviderModel {
|
|
54
|
+
return {
|
|
55
|
+
id,
|
|
56
|
+
name,
|
|
57
|
+
api,
|
|
58
|
+
reasoning,
|
|
59
|
+
input,
|
|
60
|
+
cost: ZERO_COST,
|
|
61
|
+
contextWindow,
|
|
62
|
+
maxTokens,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const STATIC_PROVIDERS: StaticProviderDefinition[] = [
|
|
67
|
+
{
|
|
68
|
+
name: "nvidia",
|
|
69
|
+
apiKey: "NVIDIA_API_KEY",
|
|
70
|
+
models: [
|
|
71
|
+
model(
|
|
72
|
+
"deepseek-ai/deepseek-v3.2",
|
|
73
|
+
"DeepSeek V3.2",
|
|
74
|
+
"openai-completions",
|
|
75
|
+
true,
|
|
76
|
+
["text"],
|
|
77
|
+
163840,
|
|
78
|
+
65536,
|
|
79
|
+
),
|
|
80
|
+
model(
|
|
81
|
+
"deepseek-ai/deepseek-r1-0528",
|
|
82
|
+
"DeepSeek R1 0528",
|
|
83
|
+
"openai-completions",
|
|
84
|
+
true,
|
|
85
|
+
["text"],
|
|
86
|
+
128000,
|
|
87
|
+
4096,
|
|
88
|
+
),
|
|
89
|
+
model(
|
|
90
|
+
"meta/llama-3.3-70b-instruct",
|
|
91
|
+
"Llama 3.3 70B Instruct",
|
|
92
|
+
"openai-completions",
|
|
93
|
+
false,
|
|
94
|
+
["text"],
|
|
95
|
+
128000,
|
|
96
|
+
4096,
|
|
97
|
+
),
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "together",
|
|
102
|
+
apiKey: "TOGETHER_API_KEY",
|
|
103
|
+
models: [
|
|
104
|
+
model(
|
|
105
|
+
"deepseek-ai/DeepSeek-R1",
|
|
106
|
+
"DeepSeek R1",
|
|
107
|
+
"openai-completions",
|
|
108
|
+
true,
|
|
109
|
+
["text"],
|
|
110
|
+
131072,
|
|
111
|
+
8192,
|
|
112
|
+
),
|
|
113
|
+
model(
|
|
114
|
+
"moonshotai/Kimi-K2.5",
|
|
115
|
+
"Kimi K2.5",
|
|
116
|
+
"openai-completions",
|
|
117
|
+
true,
|
|
118
|
+
["text", "image"],
|
|
119
|
+
262144,
|
|
120
|
+
32768,
|
|
121
|
+
),
|
|
122
|
+
model(
|
|
123
|
+
"meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
124
|
+
"Llama 3.3 70B Instruct Turbo",
|
|
125
|
+
"openai-completions",
|
|
126
|
+
false,
|
|
127
|
+
["text"],
|
|
128
|
+
131072,
|
|
129
|
+
8192,
|
|
130
|
+
),
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "synthetic",
|
|
135
|
+
apiKey: "SYNTHETIC_API_KEY",
|
|
136
|
+
models: [
|
|
137
|
+
model(
|
|
138
|
+
"hf:deepseek-ai/DeepSeek-V3.2",
|
|
139
|
+
"DeepSeek V3.2",
|
|
140
|
+
"openai-completions",
|
|
141
|
+
false,
|
|
142
|
+
["text"],
|
|
143
|
+
162816,
|
|
144
|
+
8192,
|
|
145
|
+
),
|
|
146
|
+
model(
|
|
147
|
+
"hf:moonshotai/Kimi-K2-Instruct-0905",
|
|
148
|
+
"Kimi K2 Instruct 0905",
|
|
149
|
+
"openai-completions",
|
|
150
|
+
false,
|
|
151
|
+
["text"],
|
|
152
|
+
262144,
|
|
153
|
+
8192,
|
|
154
|
+
),
|
|
155
|
+
model(
|
|
156
|
+
"hf:meta-llama/Llama-3.3-70B-Instruct",
|
|
157
|
+
"Llama 3.3 70B Instruct",
|
|
158
|
+
"openai-completions",
|
|
159
|
+
false,
|
|
160
|
+
["text"],
|
|
161
|
+
131072,
|
|
162
|
+
8192,
|
|
163
|
+
),
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: "nanogpt",
|
|
168
|
+
apiKey: "NANO_GPT_API_KEY",
|
|
169
|
+
models: [
|
|
170
|
+
model(
|
|
171
|
+
"anthropic/claude-sonnet-4.6",
|
|
172
|
+
"Claude Sonnet 4.6",
|
|
173
|
+
"openai-completions",
|
|
174
|
+
true,
|
|
175
|
+
["text"],
|
|
176
|
+
222222,
|
|
177
|
+
8888,
|
|
178
|
+
),
|
|
179
|
+
model(
|
|
180
|
+
"anthropic/claude-opus-4.6",
|
|
181
|
+
"Claude Opus 4.6",
|
|
182
|
+
"openai-completions",
|
|
183
|
+
true,
|
|
184
|
+
["text"],
|
|
185
|
+
222222,
|
|
186
|
+
8888,
|
|
187
|
+
),
|
|
188
|
+
model(
|
|
189
|
+
"baseten/Kimi-K2-Instruct-FP4",
|
|
190
|
+
"Kimi K2 Instruct FP4",
|
|
191
|
+
"openai-completions",
|
|
192
|
+
false,
|
|
193
|
+
["text"],
|
|
194
|
+
222222,
|
|
195
|
+
8888,
|
|
196
|
+
),
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "xiaomi",
|
|
201
|
+
apiKey: "XIAOMI_API_KEY",
|
|
202
|
+
models: [
|
|
203
|
+
model(
|
|
204
|
+
"mimo-v2-flash",
|
|
205
|
+
"MiMo-V2-Flash",
|
|
206
|
+
"anthropic-messages",
|
|
207
|
+
true,
|
|
208
|
+
["text"],
|
|
209
|
+
256000,
|
|
210
|
+
64000,
|
|
211
|
+
),
|
|
212
|
+
model(
|
|
213
|
+
"mimo-v2-omni",
|
|
214
|
+
"MiMo-V2-Omni",
|
|
215
|
+
"anthropic-messages",
|
|
216
|
+
true,
|
|
217
|
+
["text", "image"],
|
|
218
|
+
256000,
|
|
219
|
+
128000,
|
|
220
|
+
),
|
|
221
|
+
model(
|
|
222
|
+
"mimo-v2-pro",
|
|
223
|
+
"MiMo-V2-Pro",
|
|
224
|
+
"anthropic-messages",
|
|
225
|
+
true,
|
|
226
|
+
["text"],
|
|
227
|
+
1000000,
|
|
228
|
+
128000,
|
|
229
|
+
),
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "moonshot",
|
|
234
|
+
apiKey: "MOONSHOT_API_KEY",
|
|
235
|
+
models: [
|
|
236
|
+
model(
|
|
237
|
+
"kimi-k2.5",
|
|
238
|
+
"Kimi K2.5",
|
|
239
|
+
"openai-completions",
|
|
240
|
+
true,
|
|
241
|
+
["text", "image"],
|
|
242
|
+
262144,
|
|
243
|
+
65536,
|
|
244
|
+
),
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "venice",
|
|
249
|
+
apiKey: "VENICE_API_KEY",
|
|
250
|
+
models: [
|
|
251
|
+
model(
|
|
252
|
+
"claude-sonnet-4-6",
|
|
253
|
+
"Claude Sonnet 4.6",
|
|
254
|
+
"openai-completions",
|
|
255
|
+
true,
|
|
256
|
+
["text", "image"],
|
|
257
|
+
1000000,
|
|
258
|
+
64000,
|
|
259
|
+
),
|
|
260
|
+
model(
|
|
261
|
+
"claude-opus-4-6",
|
|
262
|
+
"Claude Opus 4.6",
|
|
263
|
+
"openai-completions",
|
|
264
|
+
true,
|
|
265
|
+
["text", "image"],
|
|
266
|
+
1000000,
|
|
267
|
+
128000,
|
|
268
|
+
),
|
|
269
|
+
model(
|
|
270
|
+
"deepseek-v3.2",
|
|
271
|
+
"DeepSeek V3.2",
|
|
272
|
+
"openai-completions",
|
|
273
|
+
true,
|
|
274
|
+
["text"],
|
|
275
|
+
160000,
|
|
276
|
+
8192,
|
|
277
|
+
),
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "kilo",
|
|
282
|
+
apiKey: "KILO_API_KEY",
|
|
283
|
+
models: [
|
|
284
|
+
model(
|
|
285
|
+
"anthropic/claude-sonnet-4.6",
|
|
286
|
+
"Claude Sonnet 4.6",
|
|
287
|
+
"openai-completions",
|
|
288
|
+
true,
|
|
289
|
+
["text"],
|
|
290
|
+
222222,
|
|
291
|
+
8888,
|
|
292
|
+
),
|
|
293
|
+
model(
|
|
294
|
+
"deepseek/deepseek-r1",
|
|
295
|
+
"DeepSeek R1",
|
|
296
|
+
"openai-completions",
|
|
297
|
+
false,
|
|
298
|
+
["text"],
|
|
299
|
+
222222,
|
|
300
|
+
8888,
|
|
301
|
+
),
|
|
302
|
+
model(
|
|
303
|
+
"arcee-ai/coder-large",
|
|
304
|
+
"Arcee Coder Large",
|
|
305
|
+
"openai-completions",
|
|
306
|
+
false,
|
|
307
|
+
["text"],
|
|
308
|
+
222222,
|
|
309
|
+
8888,
|
|
310
|
+
),
|
|
311
|
+
],
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "gitlab-duo",
|
|
315
|
+
apiKey: "GITLAB_TOKEN",
|
|
316
|
+
models: [
|
|
317
|
+
model(
|
|
318
|
+
"duo-chat-sonnet-4-6",
|
|
319
|
+
"Duo Chat Sonnet 4.6",
|
|
320
|
+
"anthropic-messages",
|
|
321
|
+
true,
|
|
322
|
+
["text", "image"],
|
|
323
|
+
200000,
|
|
324
|
+
64000,
|
|
325
|
+
),
|
|
326
|
+
model(
|
|
327
|
+
"duo-chat-opus-4-6",
|
|
328
|
+
"Duo Chat Opus 4.6",
|
|
329
|
+
"anthropic-messages",
|
|
330
|
+
true,
|
|
331
|
+
["text", "image"],
|
|
332
|
+
200000,
|
|
333
|
+
64000,
|
|
334
|
+
),
|
|
335
|
+
model(
|
|
336
|
+
"duo-chat-gpt-5-2-codex",
|
|
337
|
+
"Duo Chat GPT-5.2 Codex",
|
|
338
|
+
"openai-responses",
|
|
339
|
+
true,
|
|
340
|
+
["text", "image"],
|
|
341
|
+
272000,
|
|
342
|
+
128000,
|
|
343
|
+
),
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "qwen-portal",
|
|
348
|
+
apiKey: process.env.QWEN_OAUTH_TOKEN
|
|
349
|
+
? "QWEN_OAUTH_TOKEN"
|
|
350
|
+
: "QWEN_PORTAL_API_KEY",
|
|
351
|
+
models: [
|
|
352
|
+
model(
|
|
353
|
+
"coder-model",
|
|
354
|
+
"Qwen Coder",
|
|
355
|
+
"openai-completions",
|
|
356
|
+
false,
|
|
357
|
+
["text"],
|
|
358
|
+
128000,
|
|
359
|
+
8192,
|
|
360
|
+
),
|
|
361
|
+
model(
|
|
362
|
+
"vision-model",
|
|
363
|
+
"Qwen Vision",
|
|
364
|
+
"openai-completions",
|
|
365
|
+
false,
|
|
366
|
+
["text", "image"],
|
|
367
|
+
128000,
|
|
368
|
+
8192,
|
|
369
|
+
),
|
|
370
|
+
],
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "qianfan",
|
|
374
|
+
apiKey: "QIANFAN_API_KEY",
|
|
375
|
+
models: [
|
|
376
|
+
model(
|
|
377
|
+
"deepseek-v3.2",
|
|
378
|
+
"DeepSeek V3.2",
|
|
379
|
+
"openai-completions",
|
|
380
|
+
false,
|
|
381
|
+
["text"],
|
|
382
|
+
98304,
|
|
383
|
+
32768,
|
|
384
|
+
),
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "cloudflare-ai-gateway",
|
|
389
|
+
apiKey: "CLOUDFLARE_AI_GATEWAY_API_KEY",
|
|
390
|
+
models: [
|
|
391
|
+
model(
|
|
392
|
+
"anthropic/claude-sonnet-4-6",
|
|
393
|
+
"Claude Sonnet 4.6",
|
|
394
|
+
"anthropic-messages",
|
|
395
|
+
true,
|
|
396
|
+
["text", "image"],
|
|
397
|
+
200000,
|
|
398
|
+
64000,
|
|
399
|
+
),
|
|
400
|
+
model(
|
|
401
|
+
"anthropic/claude-opus-4-6",
|
|
402
|
+
"Claude Opus 4.6",
|
|
403
|
+
"anthropic-messages",
|
|
404
|
+
true,
|
|
405
|
+
["text", "image"],
|
|
406
|
+
200000,
|
|
407
|
+
32000,
|
|
408
|
+
),
|
|
409
|
+
model(
|
|
410
|
+
"openai/gpt-5.1",
|
|
411
|
+
"GPT-5.1",
|
|
412
|
+
"openai-completions",
|
|
413
|
+
true,
|
|
414
|
+
["text", "image"],
|
|
415
|
+
400000,
|
|
416
|
+
128000,
|
|
417
|
+
),
|
|
418
|
+
].map((entry) => ({
|
|
419
|
+
...entry,
|
|
420
|
+
// Cloudflare requires the Anthropic/OpenAI provider path in the base URL.
|
|
421
|
+
// The canonical endpoint must be supplied by environment in real usage.
|
|
422
|
+
})),
|
|
423
|
+
},
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
const LOCAL_PROVIDERS: LocalDiscoveryDefinition[] = [
|
|
427
|
+
{
|
|
428
|
+
name: "ollama",
|
|
429
|
+
api: "openai-completions",
|
|
430
|
+
baseUrl: withV1(process.env.OLLAMA_BASE_URL ?? "http://127.0.0.1:11434"),
|
|
431
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
432
|
+
discover: async () => discoverOllamaModels(),
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "lm-studio",
|
|
436
|
+
api: "openai-completions",
|
|
437
|
+
baseUrl: process.env.LM_STUDIO_BASE_URL ?? "http://127.0.0.1:1234/v1",
|
|
438
|
+
apiKeyEnv: "LM_STUDIO_API_KEY",
|
|
439
|
+
discover: async () =>
|
|
440
|
+
discoverOpenAICompatibleModels(
|
|
441
|
+
"lm-studio",
|
|
442
|
+
process.env.LM_STUDIO_BASE_URL ?? "http://127.0.0.1:1234/v1",
|
|
443
|
+
"openai-completions",
|
|
444
|
+
),
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "llama.cpp",
|
|
448
|
+
api: "openai-responses",
|
|
449
|
+
baseUrl: process.env.LLAMA_CPP_BASE_URL ?? "http://127.0.0.1:8080",
|
|
450
|
+
apiKeyEnv: "LLAMA_CPP_API_KEY",
|
|
451
|
+
discover: async () =>
|
|
452
|
+
discoverOpenAICompatibleModels(
|
|
453
|
+
"llama.cpp",
|
|
454
|
+
process.env.LLAMA_CPP_BASE_URL ?? "http://127.0.0.1:8080",
|
|
455
|
+
"openai-responses",
|
|
456
|
+
),
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: "litellm",
|
|
460
|
+
api: "openai-completions",
|
|
461
|
+
baseUrl: process.env.LITELLM_BASE_URL ?? "http://localhost:4000/v1",
|
|
462
|
+
apiKeyEnv: "LITELLM_API_KEY",
|
|
463
|
+
discover: async () =>
|
|
464
|
+
discoverOpenAICompatibleModels(
|
|
465
|
+
"litellm",
|
|
466
|
+
process.env.LITELLM_BASE_URL ?? "http://localhost:4000/v1",
|
|
467
|
+
"openai-completions",
|
|
468
|
+
),
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: "vllm",
|
|
472
|
+
api: "openai-completions",
|
|
473
|
+
baseUrl: process.env.VLLM_BASE_URL ?? "http://127.0.0.1:8000/v1",
|
|
474
|
+
apiKeyEnv: "VLLM_API_KEY",
|
|
475
|
+
discover: async () =>
|
|
476
|
+
discoverOpenAICompatibleModels(
|
|
477
|
+
"vllm",
|
|
478
|
+
process.env.VLLM_BASE_URL ?? "http://127.0.0.1:8000/v1",
|
|
479
|
+
"openai-completions",
|
|
480
|
+
),
|
|
481
|
+
},
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
export const AVAILABLE_MODELS = [
|
|
485
|
+
"claude-agent/claude-sonnet-4-6",
|
|
486
|
+
"claude-agent/claude-opus-4-6",
|
|
487
|
+
"anthropic/claude-sonnet-4-6",
|
|
488
|
+
"anthropic/claude-opus-4-6",
|
|
489
|
+
"anthropic/claude-sonnet-4-5",
|
|
490
|
+
"anthropic/claude-opus-4-1",
|
|
491
|
+
"openai/gpt-5.4",
|
|
492
|
+
"openai/gpt-5",
|
|
493
|
+
"openai/gpt-4.1",
|
|
494
|
+
"openai/gpt-4o",
|
|
495
|
+
"openai/o3-mini",
|
|
496
|
+
"openai/o1",
|
|
497
|
+
"google/gemini-2.5-pro",
|
|
498
|
+
"google/gemini-2.5-flash",
|
|
499
|
+
"amazon-bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0",
|
|
500
|
+
"azure-openai-responses/gpt-5.2",
|
|
501
|
+
"openrouter/anthropic/claude-sonnet-4",
|
|
502
|
+
"xai/grok-code-fast-1",
|
|
503
|
+
"zai/glm-4.6",
|
|
504
|
+
"openai-codex/gpt-5-codex",
|
|
505
|
+
"github-copilot/claude-sonnet-4",
|
|
506
|
+
"google-vertex/gemini-2.5-pro",
|
|
507
|
+
"together/moonshotai/Kimi-K2.5",
|
|
508
|
+
"moonshot/kimi-k2.5",
|
|
509
|
+
"nvidia/deepseek-ai/deepseek-v3.2",
|
|
510
|
+
"venice/claude-sonnet-4-6",
|
|
511
|
+
"qianfan/deepseek-v3.2",
|
|
512
|
+
"qwen-portal/coder-model",
|
|
513
|
+
"cloudflare-ai-gateway/anthropic/claude-sonnet-4-6",
|
|
514
|
+
"gitlab-duo/duo-chat-gpt-5-2-codex",
|
|
515
|
+
"xiaomi/mimo-v2-pro",
|
|
516
|
+
"synthetic/hf:deepseek-ai/DeepSeek-V3.2",
|
|
517
|
+
"nanogpt/anthropic/claude-sonnet-4.6",
|
|
518
|
+
"kilo/anthropic/claude-sonnet-4.6",
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
export async function registerOmniProviders(api: ExtensionAPI): Promise<void> {
|
|
522
|
+
for (const provider of STATIC_PROVIDERS) {
|
|
523
|
+
const baseUrl =
|
|
524
|
+
provider.name === "cloudflare-ai-gateway"
|
|
525
|
+
? (process.env.CLOUDFLARE_AI_GATEWAY_BASE_URL ??
|
|
526
|
+
"https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic")
|
|
527
|
+
: undefined;
|
|
528
|
+
|
|
529
|
+
api.registerProvider(provider.name, {
|
|
530
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
531
|
+
apiKey: provider.apiKey,
|
|
532
|
+
models: provider.models.map((entry) => ({
|
|
533
|
+
...entry,
|
|
534
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
535
|
+
})),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const discovered = await Promise.all(
|
|
540
|
+
LOCAL_PROVIDERS.map(async (provider) => {
|
|
541
|
+
const models = await provider.discover();
|
|
542
|
+
return { provider, models };
|
|
543
|
+
}),
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
for (const { provider, models } of discovered) {
|
|
547
|
+
if (models.length === 0) {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
api.registerProvider(provider.name, {
|
|
552
|
+
baseUrl: provider.baseUrl,
|
|
553
|
+
apiKey: provider.apiKeyEnv ?? "omni-local",
|
|
554
|
+
api: provider.api,
|
|
555
|
+
models,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function withV1(baseUrl: string): string {
|
|
561
|
+
const trimmed = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
562
|
+
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function withoutV1(baseUrl: string): string {
|
|
566
|
+
return baseUrl.endsWith("/v1") ? baseUrl.slice(0, -3) : baseUrl;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function fetchJson(
|
|
570
|
+
input: string,
|
|
571
|
+
init?: RequestInit,
|
|
572
|
+
): Promise<unknown | null> {
|
|
573
|
+
const controller = new AbortController();
|
|
574
|
+
const timeout = setTimeout(() => controller.abort(), 750);
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
const response = await fetch(input, {
|
|
578
|
+
...init,
|
|
579
|
+
signal: controller.signal,
|
|
580
|
+
headers: {
|
|
581
|
+
Accept: "application/json",
|
|
582
|
+
...(init?.headers ?? {}),
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
if (!response.ok) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return await response.json();
|
|
591
|
+
} catch {
|
|
592
|
+
return null;
|
|
593
|
+
} finally {
|
|
594
|
+
clearTimeout(timeout);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async function discoverOllamaModels(): Promise<OmniProviderModel[]> {
|
|
599
|
+
const baseUrl = withV1(
|
|
600
|
+
process.env.OLLAMA_BASE_URL ?? "http://127.0.0.1:11434",
|
|
601
|
+
);
|
|
602
|
+
const nativeBaseUrl = withoutV1(baseUrl);
|
|
603
|
+
const payload = (await fetchJson(`${nativeBaseUrl}/api/tags`)) as {
|
|
604
|
+
models?: Array<{ model?: string; name?: string }>;
|
|
605
|
+
} | null;
|
|
606
|
+
|
|
607
|
+
return (payload?.models ?? [])
|
|
608
|
+
.map((entry) => {
|
|
609
|
+
const id = entry.model ?? entry.name;
|
|
610
|
+
if (!id) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return model(
|
|
615
|
+
id,
|
|
616
|
+
entry.name ?? id,
|
|
617
|
+
"openai-completions",
|
|
618
|
+
inferReasoning(id),
|
|
619
|
+
inferInput(id),
|
|
620
|
+
128000,
|
|
621
|
+
8192,
|
|
622
|
+
);
|
|
623
|
+
})
|
|
624
|
+
.filter((entry): entry is OmniProviderModel => entry !== null)
|
|
625
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function discoverOpenAICompatibleModels(
|
|
629
|
+
provider: string,
|
|
630
|
+
baseUrl: string,
|
|
631
|
+
api: ModelApi,
|
|
632
|
+
): Promise<OmniProviderModel[]> {
|
|
633
|
+
const normalizedBaseUrl = baseUrl.endsWith("/")
|
|
634
|
+
? baseUrl.slice(0, -1)
|
|
635
|
+
: baseUrl;
|
|
636
|
+
const headerKey = apiKeyEnvForProvider(provider);
|
|
637
|
+
const headerValue = headerKey ? process.env[headerKey] : undefined;
|
|
638
|
+
const payload = (await fetchJson(`${normalizedBaseUrl}/models`, {
|
|
639
|
+
headers: headerValue
|
|
640
|
+
? {
|
|
641
|
+
Authorization: `Bearer ${headerValue}`,
|
|
642
|
+
}
|
|
643
|
+
: undefined,
|
|
644
|
+
})) as { data?: Array<{ id?: string }> } | Array<{ id?: string }> | null;
|
|
645
|
+
|
|
646
|
+
const entries = Array.isArray(payload) ? payload : (payload?.data ?? []);
|
|
647
|
+
|
|
648
|
+
return entries
|
|
649
|
+
.map((entry) => entry.id?.trim())
|
|
650
|
+
.filter((id): id is string => Boolean(id))
|
|
651
|
+
.map((id) =>
|
|
652
|
+
model(id, id, api, inferReasoning(id), inferInput(id), 128000, 8192),
|
|
653
|
+
)
|
|
654
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function inferReasoning(id: string): boolean {
|
|
658
|
+
return /(reason|thinking|r1|o1|o3|o4|qwq|gpt-oss|sonnet|opus|kimi-k2\.5)/iu.test(
|
|
659
|
+
id,
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function inferInput(id: string): Array<"text" | "image"> {
|
|
664
|
+
return /(vision|vl|omni|llava|gemma-3|mimo-v2-omni)/iu.test(id)
|
|
665
|
+
? ["text", "image"]
|
|
666
|
+
: ["text"];
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function apiKeyEnvForProvider(provider: string): string | undefined {
|
|
670
|
+
switch (provider) {
|
|
671
|
+
case "lm-studio":
|
|
672
|
+
return "LM_STUDIO_API_KEY";
|
|
673
|
+
case "llama.cpp":
|
|
674
|
+
return "LLAMA_CPP_API_KEY";
|
|
675
|
+
case "litellm":
|
|
676
|
+
return "LITELLM_API_KEY";
|
|
677
|
+
case "vllm":
|
|
678
|
+
return "VLLM_API_KEY";
|
|
679
|
+
default:
|
|
680
|
+
return undefined;
|
|
681
|
+
}
|
|
682
|
+
}
|
package/src/subagents.ts
CHANGED
|
@@ -69,6 +69,54 @@ interface SubagentDeps {
|
|
|
69
69
|
loadRunsForAgent?: (agent: string) => RunHistoryEntry[];
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
interface ClaudeAgentTextBlock {
|
|
73
|
+
type: string;
|
|
74
|
+
text?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface ClaudeAgentAssistantMessage {
|
|
78
|
+
type: "assistant";
|
|
79
|
+
message?: {
|
|
80
|
+
content?: ClaudeAgentTextBlock[];
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ClaudeAgentResultMessage {
|
|
85
|
+
type: "result";
|
|
86
|
+
result?: string;
|
|
87
|
+
subtype?: string;
|
|
88
|
+
errors?: string[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface ClaudeAgentProgressMessage {
|
|
92
|
+
type: "tool_progress" | "session_state_changed";
|
|
93
|
+
title?: string;
|
|
94
|
+
data?: {
|
|
95
|
+
toolName?: string;
|
|
96
|
+
status?: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type ClaudeAgentMessage =
|
|
101
|
+
| ClaudeAgentAssistantMessage
|
|
102
|
+
| ClaudeAgentResultMessage
|
|
103
|
+
| ClaudeAgentProgressMessage
|
|
104
|
+
| { type: string };
|
|
105
|
+
|
|
106
|
+
interface ClaudeAgentDeps {
|
|
107
|
+
query: (input: {
|
|
108
|
+
prompt: string;
|
|
109
|
+
options: {
|
|
110
|
+
cwd: string;
|
|
111
|
+
model: string;
|
|
112
|
+
permissionMode: "bypassPermissions";
|
|
113
|
+
allowDangerouslySkipPermissions: boolean;
|
|
114
|
+
canUseTool: () => Promise<{ behavior: "allow" }>;
|
|
115
|
+
env: Record<string, string | undefined>;
|
|
116
|
+
};
|
|
117
|
+
}) => AsyncIterable<ClaudeAgentMessage>;
|
|
118
|
+
}
|
|
119
|
+
|
|
72
120
|
export interface RunHistoryEntry {
|
|
73
121
|
agent: string;
|
|
74
122
|
task: string;
|
|
@@ -195,6 +243,13 @@ export async function loadSubagentDeps(
|
|
|
195
243
|
} as SubagentDeps;
|
|
196
244
|
}
|
|
197
245
|
|
|
246
|
+
export async function loadClaudeAgentDeps(): Promise<ClaudeAgentDeps> {
|
|
247
|
+
const sdkModule = await import("@anthropic-ai/claude-agent-sdk");
|
|
248
|
+
return {
|
|
249
|
+
query: sdkModule.query,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
198
253
|
export async function loadRunHistory(
|
|
199
254
|
packageDir = omniPackageDir(),
|
|
200
255
|
): Promise<{ loadRunsForAgent: (agent: string) => RunHistoryEntry[] } | null> {
|
|
@@ -711,6 +766,145 @@ function findAgent(
|
|
|
711
766
|
return fallback;
|
|
712
767
|
}
|
|
713
768
|
|
|
769
|
+
function getAgentConfig(
|
|
770
|
+
agents: SubagentConfig[],
|
|
771
|
+
preferred: string,
|
|
772
|
+
fallback: string,
|
|
773
|
+
): SubagentConfig | undefined {
|
|
774
|
+
return (
|
|
775
|
+
agents.find((agent) => agent.name === preferred) ??
|
|
776
|
+
agents.find((agent) => agent.name === fallback)
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function isClaudeAgentModel(model: string | undefined): boolean {
|
|
781
|
+
return model?.startsWith("claude-agent/") ?? false;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function stripClaudeAgentPrefix(model: string): string {
|
|
785
|
+
return model.replace(/^claude-agent\//u, "");
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function isClaudeAgentResultMessage(
|
|
789
|
+
message: ClaudeAgentMessage,
|
|
790
|
+
): message is ClaudeAgentResultMessage {
|
|
791
|
+
return message.type === "result";
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function isClaudeAgentAssistantMessage(
|
|
795
|
+
message: ClaudeAgentMessage,
|
|
796
|
+
): message is ClaudeAgentAssistantMessage {
|
|
797
|
+
return message.type === "assistant";
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function isClaudeAgentProgressMessage(
|
|
801
|
+
message: ClaudeAgentMessage,
|
|
802
|
+
): message is ClaudeAgentProgressMessage {
|
|
803
|
+
return (
|
|
804
|
+
message.type === "tool_progress" || message.type === "session_state_changed"
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function extractClaudeAgentRawOutput(messages: ClaudeAgentMessage[]): string {
|
|
809
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
810
|
+
const message = messages[index];
|
|
811
|
+
if (
|
|
812
|
+
isClaudeAgentResultMessage(message) &&
|
|
813
|
+
typeof message.result === "string" &&
|
|
814
|
+
message.result.trim().length > 0
|
|
815
|
+
) {
|
|
816
|
+
return message.result;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
821
|
+
const message = messages[index];
|
|
822
|
+
if (
|
|
823
|
+
isClaudeAgentResultMessage(message) &&
|
|
824
|
+
Array.isArray(message.errors) &&
|
|
825
|
+
message.errors.length > 0
|
|
826
|
+
) {
|
|
827
|
+
return message.errors.join("\n");
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const assistantText = messages
|
|
832
|
+
.flatMap((message) =>
|
|
833
|
+
isClaudeAgentAssistantMessage(message)
|
|
834
|
+
? (message.message?.content
|
|
835
|
+
?.filter(
|
|
836
|
+
(block): block is ClaudeAgentTextBlock & { text: string } =>
|
|
837
|
+
typeof block.text === "string" && block.text.trim().length > 0,
|
|
838
|
+
)
|
|
839
|
+
.map((block) => block.text) ?? [])
|
|
840
|
+
: [],
|
|
841
|
+
)
|
|
842
|
+
.join("\n\n")
|
|
843
|
+
.trim();
|
|
844
|
+
|
|
845
|
+
return assistantText;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async function runClaudeAgentTask(
|
|
849
|
+
rootDir: string,
|
|
850
|
+
ctx: ExtensionCommandContext,
|
|
851
|
+
claudeDeps: ClaudeAgentDeps,
|
|
852
|
+
agentName: string,
|
|
853
|
+
agentModel: string,
|
|
854
|
+
prompt: string,
|
|
855
|
+
): Promise<SubagentSingleResult> {
|
|
856
|
+
const messages: ClaudeAgentMessage[] = [];
|
|
857
|
+
|
|
858
|
+
try {
|
|
859
|
+
const query = claudeDeps.query({
|
|
860
|
+
prompt,
|
|
861
|
+
options: {
|
|
862
|
+
cwd: rootDir,
|
|
863
|
+
model: stripClaudeAgentPrefix(agentModel),
|
|
864
|
+
permissionMode: "bypassPermissions",
|
|
865
|
+
allowDangerouslySkipPermissions: true,
|
|
866
|
+
canUseTool: async () => ({ behavior: "allow" }),
|
|
867
|
+
env: {
|
|
868
|
+
...process.env,
|
|
869
|
+
CLAUDE_AGENT_SDK_CLIENT_APP: "omni-pi",
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
for await (const message of query) {
|
|
875
|
+
messages.push(message);
|
|
876
|
+
if (
|
|
877
|
+
isClaudeAgentProgressMessage(message) &&
|
|
878
|
+
message.type === "tool_progress"
|
|
879
|
+
) {
|
|
880
|
+
const toolName = message.data?.toolName ?? message.title ?? "working";
|
|
881
|
+
ctx.ui.setStatus("omni", `${agentName}: ${toolName}`);
|
|
882
|
+
} else if (
|
|
883
|
+
isClaudeAgentProgressMessage(message) &&
|
|
884
|
+
message.type === "session_state_changed"
|
|
885
|
+
) {
|
|
886
|
+
const status = message.data?.status;
|
|
887
|
+
if (status) {
|
|
888
|
+
ctx.ui.setStatus("omni", `${agentName}: ${status}`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return {
|
|
894
|
+
agent: agentName,
|
|
895
|
+
exitCode: 0,
|
|
896
|
+
messages,
|
|
897
|
+
};
|
|
898
|
+
} catch (error) {
|
|
899
|
+
return {
|
|
900
|
+
agent: agentName,
|
|
901
|
+
exitCode: 1,
|
|
902
|
+
messages,
|
|
903
|
+
error: error instanceof Error ? error.message : String(error),
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
714
908
|
const AGENT_ROLE_MAP: Record<string, keyof OmniConfig["models"]> = {
|
|
715
909
|
"omni-worker": "worker",
|
|
716
910
|
"omni-expert": "expert",
|
|
@@ -740,13 +934,25 @@ export async function createSubagentWorkEngine(
|
|
|
740
934
|
ctx: ExtensionCommandContext,
|
|
741
935
|
deps?: SubagentDeps,
|
|
742
936
|
verificationExecutor?: VerificationExecutor,
|
|
937
|
+
claudeDeps?: ClaudeAgentDeps,
|
|
743
938
|
): Promise<WorkEngine> {
|
|
744
939
|
const subagentDeps = deps ?? (await loadSubagentDeps());
|
|
940
|
+
const resolvedClaudeDeps = claudeDeps;
|
|
745
941
|
const config = await readConfig(rootDir);
|
|
746
942
|
const discovery = subagentDeps.discoverAgents(rootDir, "both");
|
|
747
943
|
const agentsWithOverrides = applyModelOverrides(discovery.agents, config);
|
|
748
944
|
const workerAgent = findAgent(agentsWithOverrides, "omni-worker", "worker");
|
|
749
945
|
const expertAgent = findAgent(agentsWithOverrides, "omni-expert", "reviewer");
|
|
946
|
+
const workerAgentConfig = getAgentConfig(
|
|
947
|
+
agentsWithOverrides,
|
|
948
|
+
"omni-worker",
|
|
949
|
+
"worker",
|
|
950
|
+
);
|
|
951
|
+
const expertAgentConfig = getAgentConfig(
|
|
952
|
+
agentsWithOverrides,
|
|
953
|
+
"omni-expert",
|
|
954
|
+
"reviewer",
|
|
955
|
+
);
|
|
750
956
|
const sessionDir = path.join(rootDir, ".omni", "subagent-sessions");
|
|
751
957
|
const packageDir = omniPackageDir();
|
|
752
958
|
const skillTriggers = await loadSkillTriggers(
|
|
@@ -768,37 +974,51 @@ export async function createSubagentWorkEngine(
|
|
|
768
974
|
runWorkerTask: async (task, attempt) => {
|
|
769
975
|
const verificationPlan = await readVerificationPlan(rootDir, task);
|
|
770
976
|
const preReadContext = await gatherTaskContext(rootDir, task, 4000);
|
|
977
|
+
const workerPrompt = buildWorkerPrompt(
|
|
978
|
+
task,
|
|
979
|
+
verificationPlan,
|
|
980
|
+
getSkillContext(task),
|
|
981
|
+
preReadContext,
|
|
982
|
+
);
|
|
771
983
|
ctx.ui.setStatus(
|
|
772
984
|
"omni",
|
|
773
985
|
`Worker ${workerAgent} is handling ${task.id} (attempt ${attempt})`,
|
|
774
986
|
);
|
|
775
987
|
const startTime = Date.now();
|
|
776
|
-
const result =
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
988
|
+
const result =
|
|
989
|
+
workerAgentConfig?.model && isClaudeAgentModel(workerAgentConfig.model)
|
|
990
|
+
? await runClaudeAgentTask(
|
|
991
|
+
rootDir,
|
|
992
|
+
ctx,
|
|
993
|
+
resolvedClaudeDeps ?? (await loadClaudeAgentDeps()),
|
|
994
|
+
workerAgent,
|
|
995
|
+
workerAgentConfig.model,
|
|
996
|
+
workerPrompt,
|
|
997
|
+
)
|
|
998
|
+
: await subagentDeps.runSync(
|
|
999
|
+
rootDir,
|
|
1000
|
+
agentsWithOverrides,
|
|
1001
|
+
workerAgent,
|
|
1002
|
+
workerPrompt,
|
|
1003
|
+
{
|
|
1004
|
+
cwd: rootDir,
|
|
1005
|
+
runId: randomUUID(),
|
|
1006
|
+
sessionDir,
|
|
1007
|
+
onUpdate: (update) => {
|
|
1008
|
+
const progress = update.details?.progress?.[0];
|
|
1009
|
+
if (progress) {
|
|
1010
|
+
ctx.ui.setStatus(
|
|
1011
|
+
"omni",
|
|
1012
|
+
`${progress.agent}: ${progress.currentTool ?? "working"}${progress.toolCount ? ` (${progress.toolCount} tools)` : ""}`,
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
},
|
|
1016
|
+
},
|
|
1017
|
+
);
|
|
1018
|
+
const raw =
|
|
1019
|
+
workerAgentConfig?.model && isClaudeAgentModel(workerAgentConfig.model)
|
|
1020
|
+
? extractClaudeAgentRawOutput(result.messages as ClaudeAgentMessage[])
|
|
1021
|
+
: subagentDeps.getFinalOutput(result.messages);
|
|
802
1022
|
const rawOutputPath = path.join(
|
|
803
1023
|
rootDir,
|
|
804
1024
|
".omni",
|
|
@@ -871,34 +1091,48 @@ export async function createSubagentWorkEngine(
|
|
|
871
1091
|
`Escalating ${task.id} to expert after ${escalation.priorAttempts} failed attempts. Failed checks: ${failedChecksSummary}`,
|
|
872
1092
|
);
|
|
873
1093
|
const preReadContext = await gatherTaskContext(rootDir, task, 6000);
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
task,
|
|
881
|
-
escalation,
|
|
882
|
-
verificationPlan,
|
|
883
|
-
getSkillContext(task),
|
|
884
|
-
preReadContext,
|
|
885
|
-
),
|
|
886
|
-
{
|
|
887
|
-
cwd: rootDir,
|
|
888
|
-
runId: randomUUID(),
|
|
889
|
-
sessionDir,
|
|
890
|
-
onUpdate: (update) => {
|
|
891
|
-
const progress = update.details?.progress?.[0];
|
|
892
|
-
if (progress) {
|
|
893
|
-
ctx.ui.setStatus(
|
|
894
|
-
"omni",
|
|
895
|
-
`${progress.agent}: ${progress.currentTool ?? "resolving"}${progress.toolCount ? ` (${progress.toolCount} tools)` : ""}`,
|
|
896
|
-
);
|
|
897
|
-
}
|
|
898
|
-
},
|
|
899
|
-
},
|
|
1094
|
+
const expertPrompt = buildExpertPrompt(
|
|
1095
|
+
task,
|
|
1096
|
+
escalation,
|
|
1097
|
+
verificationPlan,
|
|
1098
|
+
getSkillContext(task),
|
|
1099
|
+
preReadContext,
|
|
900
1100
|
);
|
|
901
|
-
const
|
|
1101
|
+
const expertStartTime = Date.now();
|
|
1102
|
+
const result =
|
|
1103
|
+
expertAgentConfig?.model && isClaudeAgentModel(expertAgentConfig.model)
|
|
1104
|
+
? await runClaudeAgentTask(
|
|
1105
|
+
rootDir,
|
|
1106
|
+
ctx,
|
|
1107
|
+
resolvedClaudeDeps ?? (await loadClaudeAgentDeps()),
|
|
1108
|
+
expertAgent,
|
|
1109
|
+
expertAgentConfig.model,
|
|
1110
|
+
expertPrompt,
|
|
1111
|
+
)
|
|
1112
|
+
: await subagentDeps.runSync(
|
|
1113
|
+
rootDir,
|
|
1114
|
+
agentsWithOverrides,
|
|
1115
|
+
expertAgent,
|
|
1116
|
+
expertPrompt,
|
|
1117
|
+
{
|
|
1118
|
+
cwd: rootDir,
|
|
1119
|
+
runId: randomUUID(),
|
|
1120
|
+
sessionDir,
|
|
1121
|
+
onUpdate: (update) => {
|
|
1122
|
+
const progress = update.details?.progress?.[0];
|
|
1123
|
+
if (progress) {
|
|
1124
|
+
ctx.ui.setStatus(
|
|
1125
|
+
"omni",
|
|
1126
|
+
`${progress.agent}: ${progress.currentTool ?? "resolving"}${progress.toolCount ? ` (${progress.toolCount} tools)` : ""}`,
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
},
|
|
1130
|
+
},
|
|
1131
|
+
);
|
|
1132
|
+
const raw =
|
|
1133
|
+
expertAgentConfig?.model && isClaudeAgentModel(expertAgentConfig.model)
|
|
1134
|
+
? extractClaudeAgentRawOutput(result.messages as ClaudeAgentMessage[])
|
|
1135
|
+
: subagentDeps.getFinalOutput(result.messages);
|
|
902
1136
|
const rawOutputPath = path.join(
|
|
903
1137
|
rootDir,
|
|
904
1138
|
".omni",
|