@phi-code-admin/phi-code 0.56.6 → 0.57.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/extensions/phi/README.md +34 -214
- package/extensions/phi/agents.ts +202 -0
- package/extensions/phi/benchmark.ts +611 -398
- package/extensions/phi/init.ts +447 -312
- package/extensions/phi/memory.ts +36 -18
- package/package.json +1 -1
package/extensions/phi/init.ts
CHANGED
|
@@ -1,331 +1,466 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Phi Init Extension - Interactive setup wizard for Phi Code
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - Extension activation
|
|
4
|
+
* Three modes:
|
|
5
|
+
* - auto: Use Alibaba Coding Plan defaults (instant, recommended)
|
|
6
|
+
* - benchmark: Test available models with /benchmark, then assign (10-15 min)
|
|
7
|
+
* - manual: User assigns each model role interactively
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
* - /phi-init command for setup wizard
|
|
12
|
-
* - Auto-detects available API keys
|
|
13
|
-
* - Three setup modes: auto, benchmark, manual
|
|
14
|
-
* - Creates ~/.phi/agent/ configuration structure
|
|
15
|
-
* - Activates Phi Code extensions
|
|
16
|
-
*
|
|
17
|
-
* Usage:
|
|
18
|
-
* 1. Run /phi-init to start the wizard
|
|
19
|
-
* 2. Follow interactive prompts
|
|
20
|
-
* 3. Configuration saved to ~/.phi/agent/
|
|
9
|
+
* Creates ~/.phi/agent/ structure with routing, agents, and memory.
|
|
21
10
|
*/
|
|
22
11
|
|
|
23
12
|
import type { ExtensionAPI } from "phi-code";
|
|
24
|
-
import { writeFile, mkdir,
|
|
25
|
-
import { join } from "node:path";
|
|
13
|
+
import { writeFile, mkdir, copyFile, readdir, access } from "node:fs/promises";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
26
15
|
import { homedir } from "node:os";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
|
|
18
|
+
// ─── Types ───────────────────────────────────────────────────────────────
|
|
27
19
|
|
|
28
20
|
interface DetectedProvider {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
name: string;
|
|
22
|
+
envVar: string;
|
|
23
|
+
baseUrl: string;
|
|
24
|
+
models: string[];
|
|
25
|
+
available: boolean;
|
|
33
26
|
}
|
|
34
27
|
|
|
35
|
-
interface
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
interface RoutingConfig {
|
|
29
|
+
routes: Record<string, {
|
|
30
|
+
description: string;
|
|
31
|
+
keywords: string[];
|
|
32
|
+
preferredModel: string;
|
|
33
|
+
fallback: string;
|
|
34
|
+
agent: string;
|
|
35
|
+
}>;
|
|
36
|
+
default: { model: string; agent: string | null };
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
// ─── Provider Detection ──────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function detectProviders(): DetectedProvider[] {
|
|
42
|
+
const providers: DetectedProvider[] = [
|
|
43
|
+
{
|
|
44
|
+
name: "Alibaba Coding Plan",
|
|
45
|
+
envVar: "ALIBABA_CODING_PLAN_KEY",
|
|
46
|
+
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1",
|
|
47
|
+
models: ["qwen3.5-plus", "qwen3-max-2026-01-23", "qwen3-coder-plus", "qwen3-coder-next", "kimi-k2.5", "glm-5", "glm-4.7", "MiniMax-M2.5"],
|
|
48
|
+
available: false,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "OpenAI",
|
|
52
|
+
envVar: "OPENAI_API_KEY",
|
|
53
|
+
baseUrl: "https://api.openai.com/v1",
|
|
54
|
+
models: ["gpt-4o", "gpt-4o-mini", "o1", "o3-mini"],
|
|
55
|
+
available: false,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "Anthropic",
|
|
59
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
60
|
+
baseUrl: "https://api.anthropic.com/v1",
|
|
61
|
+
models: ["claude-sonnet-4-20250514", "claude-3-5-haiku-20241022"],
|
|
62
|
+
available: false,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "Google",
|
|
66
|
+
envVar: "GOOGLE_API_KEY",
|
|
67
|
+
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
68
|
+
models: ["gemini-2.5-pro", "gemini-2.5-flash"],
|
|
69
|
+
available: false,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "OpenRouter",
|
|
73
|
+
envVar: "OPENROUTER_API_KEY",
|
|
74
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
75
|
+
models: [],
|
|
76
|
+
available: false,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "Groq",
|
|
80
|
+
envVar: "GROQ_API_KEY",
|
|
81
|
+
baseUrl: "https://api.groq.com/openai/v1",
|
|
82
|
+
models: ["llama-3.3-70b-versatile", "mixtral-8x7b-32768"],
|
|
83
|
+
available: false,
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const p of providers) {
|
|
88
|
+
p.available = !!process.env[p.envVar];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return providers;
|
|
50
92
|
}
|
|
51
93
|
|
|
94
|
+
function getAllAvailableModels(providers: DetectedProvider[]): string[] {
|
|
95
|
+
return providers.filter(p => p.available).flatMap(p => p.models);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── Routing Presets ─────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
const TASK_ROLES = [
|
|
101
|
+
{ key: "code", label: "Code Generation", desc: "Writing and modifying code", agent: "code", defaultModel: "qwen3-coder-plus" },
|
|
102
|
+
{ key: "debug", label: "Debugging", desc: "Finding and fixing bugs", agent: "code", defaultModel: "qwen3-max-2026-01-23" },
|
|
103
|
+
{ key: "plan", label: "Planning", desc: "Architecture and design", agent: "plan", defaultModel: "qwen3-max-2026-01-23" },
|
|
104
|
+
{ key: "explore", label: "Exploration", desc: "Code reading and analysis", agent: "explore", defaultModel: "kimi-k2.5" },
|
|
105
|
+
{ key: "test", label: "Testing", desc: "Running and writing tests", agent: "test", defaultModel: "kimi-k2.5" },
|
|
106
|
+
{ key: "review", label: "Code Review", desc: "Quality and security review", agent: "review", defaultModel: "qwen3.5-plus" },
|
|
107
|
+
] as const;
|
|
108
|
+
|
|
109
|
+
const KEYWORDS: Record<string, string[]> = {
|
|
110
|
+
code: ["implement", "create", "build", "refactor", "write", "add", "modify", "update", "generate"],
|
|
111
|
+
debug: ["fix", "bug", "error", "debug", "crash", "broken", "failing", "issue", "troubleshoot"],
|
|
112
|
+
explore: ["read", "analyze", "explain", "understand", "find", "search", "look", "show", "what", "how"],
|
|
113
|
+
plan: ["plan", "design", "architect", "spec", "structure", "organize", "strategy", "approach"],
|
|
114
|
+
test: ["test", "verify", "validate", "check", "assert", "coverage"],
|
|
115
|
+
review: ["review", "audit", "quality", "security", "improve", "optimize"],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function createRouting(assignments: Record<string, { preferred: string; fallback: string }>): RoutingConfig {
|
|
119
|
+
const routes: RoutingConfig["routes"] = {};
|
|
120
|
+
for (const role of TASK_ROLES) {
|
|
121
|
+
const assignment = assignments[role.key];
|
|
122
|
+
routes[role.key] = {
|
|
123
|
+
description: role.desc,
|
|
124
|
+
keywords: KEYWORDS[role.key] || [],
|
|
125
|
+
preferredModel: assignment?.preferred || role.defaultModel,
|
|
126
|
+
fallback: assignment?.fallback || "qwen3.5-plus",
|
|
127
|
+
agent: role.agent,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
routes,
|
|
132
|
+
default: { model: assignments["default"]?.preferred || "qwen3.5-plus", agent: null },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Extension ───────────────────────────────────────────────────────────
|
|
137
|
+
|
|
52
138
|
export default function initExtension(pi: ExtensionAPI) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
primaryProvider = provider;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!primaryProvider) {
|
|
125
|
-
throw new Error("Aucun provider disponible détecté");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const models: ModelConfig[] = primaryProvider.models.map(modelId => ({
|
|
129
|
-
id: modelId,
|
|
130
|
-
provider: primaryProvider!.name,
|
|
131
|
-
apiKey: process.env[primaryProvider!.envVar]
|
|
132
|
-
}));
|
|
133
|
-
|
|
134
|
-
const routing: RoutingConfigData = {
|
|
135
|
-
routes: {
|
|
136
|
-
code: {
|
|
137
|
-
preferredModel: primaryProvider.models.find(m => m.includes("coder")) || primaryProvider.models[0],
|
|
138
|
-
fallback: primaryProvider.models[0],
|
|
139
|
-
agent: null,
|
|
140
|
-
keywords: ["code", "implement", "write", "create", "build", "develop", "function", "class"]
|
|
141
|
-
},
|
|
142
|
-
debug: {
|
|
143
|
-
preferredModel: primaryProvider.models[0],
|
|
144
|
-
fallback: primaryProvider.models[1] || primaryProvider.models[0],
|
|
145
|
-
agent: null,
|
|
146
|
-
keywords: ["debug", "fix", "error", "bug", "broken", "issue", "problem", "repair"]
|
|
147
|
-
},
|
|
148
|
-
plan: {
|
|
149
|
-
preferredModel: primaryProvider.models.find(m => m.includes("max")) || primaryProvider.models[0],
|
|
150
|
-
fallback: primaryProvider.models[0],
|
|
151
|
-
agent: null,
|
|
152
|
-
keywords: ["plan", "design", "architecture", "strategy", "structure", "organize"]
|
|
153
|
-
},
|
|
154
|
-
review: {
|
|
155
|
-
preferredModel: primaryProvider.models[0],
|
|
156
|
-
fallback: primaryProvider.models[1] || primaryProvider.models[0],
|
|
157
|
-
agent: null,
|
|
158
|
-
keywords: ["review", "audit", "check", "validate", "quality", "improve", "optimize"]
|
|
159
|
-
},
|
|
160
|
-
test: {
|
|
161
|
-
preferredModel: primaryProvider.models.find(m => m.includes("fast")) || primaryProvider.models[0],
|
|
162
|
-
fallback: primaryProvider.models[0],
|
|
163
|
-
agent: null,
|
|
164
|
-
keywords: ["test", "testing", "unit", "integration", "verify", "validate"]
|
|
165
|
-
},
|
|
166
|
-
explore: {
|
|
167
|
-
preferredModel: primaryProvider.models.find(m => m.includes("fast")) || primaryProvider.models[0],
|
|
168
|
-
fallback: primaryProvider.models[0],
|
|
169
|
-
agent: null,
|
|
170
|
-
keywords: ["explore", "understand", "analyze", "examine", "investigate"]
|
|
171
|
-
},
|
|
172
|
-
general: {
|
|
173
|
-
preferredModel: primaryProvider.models[0],
|
|
174
|
-
fallback: primaryProvider.models[1] || primaryProvider.models[0],
|
|
175
|
-
agent: null,
|
|
176
|
-
keywords: ["help", "explain", "what", "how", "why", "question"]
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
default: {
|
|
180
|
-
model: primaryProvider.models[0],
|
|
181
|
-
agent: null
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
return { routing, models };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Crée les répertoires nécessaires
|
|
190
|
-
*/
|
|
191
|
-
async function ensureDirectories(): Promise<void> {
|
|
192
|
-
await mkdir(phiAgentDir, { recursive: true });
|
|
193
|
-
await mkdir(agentsDir, { recursive: true });
|
|
194
|
-
await mkdir(extensionsDir, { recursive: true });
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Copie les définitions d'agents par défaut
|
|
199
|
-
*/
|
|
200
|
-
async function copyDefaultAgents(): Promise<void> {
|
|
201
|
-
// Pour l'instant, nous créons un agent par défaut simple
|
|
202
|
-
const defaultAgent = `---
|
|
203
|
-
name: general-assistant
|
|
204
|
-
description: General purpose assistant for various tasks
|
|
205
|
-
model: qwen3.5-plus
|
|
206
|
-
tools: [read, write, exec, web_search]
|
|
207
|
-
maxTokens: 4096
|
|
139
|
+
const phiDir = join(homedir(), ".phi");
|
|
140
|
+
const agentDir = join(phiDir, "agent");
|
|
141
|
+
const agentsDir = join(agentDir, "agents");
|
|
142
|
+
const memoryDir = join(phiDir, "memory");
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create all necessary directories
|
|
146
|
+
*/
|
|
147
|
+
async function ensureDirs() {
|
|
148
|
+
for (const dir of [agentDir, agentsDir, join(agentDir, "skills"), join(agentDir, "extensions"), memoryDir, join(memoryDir, "ontology")]) {
|
|
149
|
+
await mkdir(dir, { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Copy bundled agent definitions to user directory
|
|
155
|
+
*/
|
|
156
|
+
async function copyBundledAgents() {
|
|
157
|
+
const bundledDir = resolve(join(__dirname, "..", "..", "..", "agents"));
|
|
158
|
+
if (!existsSync(bundledDir)) return;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const files = await readdir(bundledDir);
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
if (file.endsWith(".md")) {
|
|
164
|
+
const dest = join(agentsDir, file);
|
|
165
|
+
if (!existsSync(dest)) {
|
|
166
|
+
await copyFile(join(bundledDir, file), dest);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
// bundled dir not available
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Create AGENTS.md template
|
|
177
|
+
*/
|
|
178
|
+
async function createAgentsTemplate() {
|
|
179
|
+
const agentsMdPath = join(memoryDir, "AGENTS.md");
|
|
180
|
+
if (existsSync(agentsMdPath)) return; // Don't overwrite
|
|
181
|
+
|
|
182
|
+
await writeFile(agentsMdPath, `# AGENTS.md — Persistent Instructions
|
|
183
|
+
|
|
184
|
+
This file is loaded at the start of every session. Use it to store:
|
|
185
|
+
- Project conventions and rules
|
|
186
|
+
- Recurring instructions
|
|
187
|
+
- Important context the agent should always know
|
|
188
|
+
|
|
189
|
+
## Project
|
|
190
|
+
|
|
191
|
+
- Name: (your project name)
|
|
192
|
+
- Language: TypeScript
|
|
193
|
+
- Framework: (your framework)
|
|
194
|
+
|
|
195
|
+
## Conventions
|
|
196
|
+
|
|
197
|
+
- (your coding conventions)
|
|
198
|
+
- (your naming rules)
|
|
199
|
+
- (your commit format)
|
|
200
|
+
|
|
201
|
+
## Important Notes
|
|
202
|
+
|
|
203
|
+
- (anything the agent should always remember)
|
|
204
|
+
|
|
208
205
|
---
|
|
209
206
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
207
|
+
_Edit this file to customize Phi Code's behavior for your project._
|
|
208
|
+
`, "utf-8");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── MODE: Auto ──────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
function autoMode(availableModels: string[]): Record<string, { preferred: string; fallback: string }> {
|
|
214
|
+
const assignments: Record<string, { preferred: string; fallback: string }> = {};
|
|
215
|
+
|
|
216
|
+
for (const role of TASK_ROLES) {
|
|
217
|
+
const preferred = availableModels.includes(role.defaultModel) ? role.defaultModel : availableModels[0];
|
|
218
|
+
const fallbackModel = availableModels.includes("qwen3.5-plus") ? "qwen3.5-plus" : availableModels[0];
|
|
219
|
+
assignments[role.key] = { preferred, fallback: fallbackModel };
|
|
220
|
+
}
|
|
221
|
+
assignments["default"] = {
|
|
222
|
+
preferred: availableModels.includes("qwen3.5-plus") ? "qwen3.5-plus" : availableModels[0],
|
|
223
|
+
fallback: availableModels[0],
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return assignments;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ─── MODE: Benchmark ─────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
async function benchmarkMode(availableModels: string[], ctx: any): Promise<Record<string, { preferred: string; fallback: string }>> {
|
|
232
|
+
ctx.ui.notify("🧪 Benchmark mode: running tests on available models...", "info");
|
|
233
|
+
ctx.ui.notify("This will test each model with 6 coding tasks. It may take 10-15 minutes.", "info");
|
|
234
|
+
ctx.ui.notify("💡 Tip: You can run `/benchmark all` separately and use `/benchmark results` to see rankings.\n", "info");
|
|
235
|
+
|
|
236
|
+
// Check if benchmark results already exist
|
|
237
|
+
const benchmarkPath = join(phiDir, "benchmark", "results.json");
|
|
238
|
+
let existingResults: any = null;
|
|
239
|
+
try {
|
|
240
|
+
await access(benchmarkPath);
|
|
241
|
+
const content = await (await import("node:fs/promises")).readFile(benchmarkPath, "utf-8");
|
|
242
|
+
existingResults = JSON.parse(content);
|
|
243
|
+
} catch {
|
|
244
|
+
// No existing results
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (existingResults?.results?.length > 0) {
|
|
248
|
+
const useExisting = await ctx.ui.confirm(
|
|
249
|
+
`Found ${existingResults.results.length} existing benchmark results. Use them instead of re-running?`
|
|
250
|
+
);
|
|
251
|
+
if (useExisting) {
|
|
252
|
+
return assignFromBenchmark(existingResults.results, availableModels);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Run benchmarks via the /benchmark command
|
|
257
|
+
ctx.ui.notify("Starting benchmarks... (this runs in the background, continue with /phi-init after /benchmark completes)\n", "info");
|
|
258
|
+
ctx.ui.notify("Run: `/benchmark all` then `/phi-init` again with mode=benchmark to use results.\n", "info");
|
|
259
|
+
|
|
260
|
+
// Fall back to auto for now
|
|
261
|
+
return autoMode(availableModels);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function assignFromBenchmark(results: any[], availableModels: string[]): Record<string, { preferred: string; fallback: string }> {
|
|
265
|
+
const assignments: Record<string, { preferred: string; fallback: string }> = {};
|
|
266
|
+
|
|
267
|
+
// Sort by total score
|
|
268
|
+
const sorted = [...results].sort((a: any, b: any) => (b.totalScore || 0) - (a.totalScore || 0));
|
|
269
|
+
const bestOverall = sorted[0]?.modelId || availableModels[0];
|
|
270
|
+
const secondBest = sorted[1]?.modelId || bestOverall;
|
|
271
|
+
|
|
272
|
+
// Find best per category
|
|
273
|
+
function bestForCategory(category: string): string {
|
|
274
|
+
let best = { id: bestOverall, score: 0 };
|
|
275
|
+
for (const r of results) {
|
|
276
|
+
const catScore = r.categories?.[category]?.score ?? 0;
|
|
277
|
+
if (catScore > best.score) {
|
|
278
|
+
best = { id: r.modelId, score: catScore };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return best.id;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
assignments["code"] = { preferred: bestForCategory("code-gen"), fallback: secondBest };
|
|
285
|
+
assignments["debug"] = { preferred: bestForCategory("debug"), fallback: secondBest };
|
|
286
|
+
assignments["plan"] = { preferred: bestForCategory("planning"), fallback: secondBest };
|
|
287
|
+
assignments["explore"] = { preferred: bestForCategory("speed"), fallback: secondBest };
|
|
288
|
+
assignments["test"] = { preferred: bestForCategory("speed"), fallback: secondBest };
|
|
289
|
+
assignments["review"] = { preferred: bestForCategory("orchestration"), fallback: secondBest };
|
|
290
|
+
assignments["default"] = { preferred: bestOverall, fallback: secondBest };
|
|
291
|
+
|
|
292
|
+
return assignments;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ─── MODE: Manual ────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
async function manualMode(availableModels: string[], ctx: any): Promise<Record<string, { preferred: string; fallback: string }>> {
|
|
298
|
+
ctx.ui.notify("🎛️ Manual mode: assign a model to each task category.\n", "info");
|
|
299
|
+
|
|
300
|
+
const modelList = availableModels.map((m, i) => ` ${i + 1}. ${m}`).join("\n");
|
|
301
|
+
const assignments: Record<string, { preferred: string; fallback: string }> = {};
|
|
302
|
+
|
|
303
|
+
for (const role of TASK_ROLES) {
|
|
304
|
+
const input = await ctx.ui.input(
|
|
305
|
+
`**${role.label}** — ${role.desc}\nDefault: ${role.defaultModel}\n\nAvailable models:\n${modelList}\n\nEnter model name or number (Enter for default):`
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
let chosen = role.defaultModel;
|
|
309
|
+
const trimmed = input.trim();
|
|
310
|
+
|
|
311
|
+
if (trimmed) {
|
|
312
|
+
// Try as number
|
|
313
|
+
const num = parseInt(trimmed);
|
|
314
|
+
if (num >= 1 && num <= availableModels.length) {
|
|
315
|
+
chosen = availableModels[num - 1];
|
|
316
|
+
} else {
|
|
317
|
+
// Try as model name (partial match)
|
|
318
|
+
const match = availableModels.find(m => m.toLowerCase().includes(trimmed.toLowerCase()));
|
|
319
|
+
if (match) chosen = match;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Fallback selection
|
|
324
|
+
const fallbackDefault = availableModels.find(m => m !== chosen) || chosen;
|
|
325
|
+
const fallbackInput = await ctx.ui.input(
|
|
326
|
+
`Fallback model for ${role.label}? (Enter for ${fallbackDefault}):`
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
let fallback = fallbackDefault;
|
|
330
|
+
if (fallbackInput.trim()) {
|
|
331
|
+
const num = parseInt(fallbackInput.trim());
|
|
332
|
+
if (num >= 1 && num <= availableModels.length) {
|
|
333
|
+
fallback = availableModels[num - 1];
|
|
334
|
+
} else {
|
|
335
|
+
const match = availableModels.find(m => m.toLowerCase().includes(fallbackInput.trim().toLowerCase()));
|
|
336
|
+
if (match) fallback = match;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
assignments[role.key] = { preferred: chosen, fallback };
|
|
341
|
+
ctx.ui.notify(` ✅ ${role.label}: ${chosen} (fallback: ${fallback})`, "info");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Default model
|
|
345
|
+
const defaultInput = await ctx.ui.input(
|
|
346
|
+
`Default model for general tasks?\nAvailable:\n${modelList}\n\nEnter model name or number (Enter for ${availableModels[0]}):`
|
|
347
|
+
);
|
|
348
|
+
let defaultModel = availableModels[0];
|
|
349
|
+
if (defaultInput.trim()) {
|
|
350
|
+
const num = parseInt(defaultInput.trim());
|
|
351
|
+
if (num >= 1 && num <= availableModels.length) {
|
|
352
|
+
defaultModel = availableModels[num - 1];
|
|
353
|
+
} else {
|
|
354
|
+
const match = availableModels.find(m => m.toLowerCase().includes(defaultInput.trim().toLowerCase()));
|
|
355
|
+
if (match) defaultModel = match;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
assignments["default"] = { preferred: defaultModel, fallback: availableModels[0] };
|
|
359
|
+
|
|
360
|
+
return assignments;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ─── Command ─────────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
pi.registerCommand("phi-init", {
|
|
366
|
+
description: "Initialize Phi Code — interactive setup wizard (3 modes: auto, benchmark, manual)",
|
|
367
|
+
handler: async (args, ctx) => {
|
|
368
|
+
try {
|
|
369
|
+
ctx.ui.notify("╔══════════════════════════════════════╗", "info");
|
|
370
|
+
ctx.ui.notify("║ Φ Phi Code Setup Wizard ║", "info");
|
|
371
|
+
ctx.ui.notify("╚══════════════════════════════════════╝\n", "info");
|
|
372
|
+
|
|
373
|
+
// 1. Detect API keys
|
|
374
|
+
ctx.ui.notify("🔍 Detecting API keys...", "info");
|
|
375
|
+
const providers = detectProviders();
|
|
376
|
+
const available = providers.filter(p => p.available);
|
|
377
|
+
|
|
378
|
+
if (available.length === 0) {
|
|
379
|
+
ctx.ui.notify("❌ No API keys found. Set at least one:\n" +
|
|
380
|
+
providers.map(p => ` export ${p.envVar}="your-key" # ${p.name}`).join("\n") +
|
|
381
|
+
"\n\n💡 Free option: Get an Alibaba Coding Plan key at https://help.aliyun.com/zh/model-studio/", "error");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
ctx.ui.notify(`✅ Found ${available.length} provider(s):`, "info");
|
|
386
|
+
for (const p of available) {
|
|
387
|
+
ctx.ui.notify(` • ${p.name} — ${p.models.length} models`, "info");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const allModels = getAllAvailableModels(providers);
|
|
391
|
+
ctx.ui.notify(` Total: ${allModels.length} models available\n`, "info");
|
|
392
|
+
|
|
393
|
+
// 2. Choose mode
|
|
394
|
+
const modeInput = await ctx.ui.input(
|
|
395
|
+
"Choose setup mode:\n\n" +
|
|
396
|
+
" 1. auto — Use optimal defaults from public rankings (instant)\n" +
|
|
397
|
+
" 2. benchmark — Test models with coding tasks, assign by results (10-15 min)\n" +
|
|
398
|
+
" 3. manual — Choose each model assignment yourself\n\n" +
|
|
399
|
+
"Enter 1, 2, or 3:"
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
const mode = modeInput.trim().startsWith("2") || modeInput.trim().toLowerCase().startsWith("b") ? "benchmark"
|
|
403
|
+
: modeInput.trim().startsWith("3") || modeInput.trim().toLowerCase().startsWith("m") ? "manual"
|
|
404
|
+
: "auto";
|
|
405
|
+
|
|
406
|
+
ctx.ui.notify(`\n📋 Mode: **${mode}**\n`, "info");
|
|
407
|
+
|
|
408
|
+
// 3. Get assignments based on mode
|
|
409
|
+
let assignments: Record<string, { preferred: string; fallback: string }>;
|
|
410
|
+
|
|
411
|
+
if (mode === "auto") {
|
|
412
|
+
assignments = autoMode(allModels);
|
|
413
|
+
ctx.ui.notify("⚡ Auto-assigned models based on public rankings and model specializations.", "info");
|
|
414
|
+
} else if (mode === "benchmark") {
|
|
415
|
+
assignments = await benchmarkMode(allModels, ctx);
|
|
416
|
+
} else {
|
|
417
|
+
assignments = await manualMode(allModels, ctx);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 4. Create directory structure
|
|
421
|
+
ctx.ui.notify("\n📁 Creating directories...", "info");
|
|
422
|
+
await ensureDirs();
|
|
423
|
+
|
|
424
|
+
// 5. Write routing config
|
|
425
|
+
ctx.ui.notify("🔀 Writing routing configuration...", "info");
|
|
426
|
+
const routing = createRouting(assignments);
|
|
427
|
+
await writeFile(join(agentDir, "routing.json"), JSON.stringify(routing, null, 2), "utf-8");
|
|
428
|
+
|
|
429
|
+
// 6. Copy bundled agents
|
|
430
|
+
ctx.ui.notify("🤖 Setting up sub-agents...", "info");
|
|
431
|
+
await copyBundledAgents();
|
|
432
|
+
|
|
433
|
+
// 7. Create AGENTS.md template
|
|
434
|
+
ctx.ui.notify("📝 Creating memory template...", "info");
|
|
435
|
+
await createAgentsTemplate();
|
|
436
|
+
|
|
437
|
+
// 8. Summary
|
|
438
|
+
ctx.ui.notify("\n╔══════════════════════════════════════╗", "info");
|
|
439
|
+
ctx.ui.notify("║ ✅ Setup Complete! ║", "info");
|
|
440
|
+
ctx.ui.notify("╚══════════════════════════════════════╝\n", "info");
|
|
441
|
+
|
|
442
|
+
ctx.ui.notify("**Configuration:**", "info");
|
|
443
|
+
ctx.ui.notify(` 📁 Config: ${agentDir}`, "info");
|
|
444
|
+
ctx.ui.notify(` 📁 Memory: ${memoryDir}`, "info");
|
|
445
|
+
ctx.ui.notify(` 🤖 Agents: ${agentsDir}`, "info");
|
|
446
|
+
|
|
447
|
+
ctx.ui.notify("\n**Model Assignments:**", "info");
|
|
448
|
+
for (const role of TASK_ROLES) {
|
|
449
|
+
const a = assignments[role.key];
|
|
450
|
+
ctx.ui.notify(` ${role.label}: \`${a.preferred}\` (fallback: \`${a.fallback}\`)`, "info");
|
|
451
|
+
}
|
|
452
|
+
ctx.ui.notify(` Default: \`${assignments["default"].preferred}\``, "info");
|
|
453
|
+
|
|
454
|
+
ctx.ui.notify("\n**Next steps:**", "info");
|
|
455
|
+
ctx.ui.notify(" • Edit `~/.phi/memory/AGENTS.md` with your project instructions", "info");
|
|
456
|
+
ctx.ui.notify(" • Run `/agents` to see available sub-agents", "info");
|
|
457
|
+
ctx.ui.notify(" • Run `/skills` to see available skills", "info");
|
|
458
|
+
ctx.ui.notify(" • Run `/benchmark all` to test model performance", "info");
|
|
459
|
+
ctx.ui.notify(" • Start coding! 🚀\n", "info");
|
|
460
|
+
|
|
461
|
+
} catch (error) {
|
|
462
|
+
ctx.ui.notify(`❌ Setup failed: ${error}`, "error");
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
}
|