@phi-code-admin/phi-code 0.76.11 → 0.76.12
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/init.ts +26 -10
- package/extensions/phi/orchestrator.ts +21 -4
- package/extensions/phi/setup.ts +23 -15
- package/package.json +1 -1
package/extensions/phi/init.ts
CHANGED
|
@@ -196,13 +196,22 @@ async function detectLocalProviders(providers: DetectedProvider[]): Promise<void
|
|
|
196
196
|
);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
function getAllAvailableModels(providers: DetectedProvider[]): string
|
|
200
|
-
const
|
|
199
|
+
function getAllAvailableModels(providers: DetectedProvider[]): Array<{ ref: string; display: string }> {
|
|
200
|
+
const out: Array<{ ref: string; display: string }> = [];
|
|
201
|
+
const seen = new Set<string>();
|
|
201
202
|
for (const p of providers) {
|
|
202
203
|
if (!p.available) continue;
|
|
203
|
-
for (const id of p.models)
|
|
204
|
+
for (const id of p.models) {
|
|
205
|
+
// Provider-qualified reference ("provider/id") so the same model id
|
|
206
|
+
// offered by several providers stays distinct, and the provider is
|
|
207
|
+
// visible at selection. No cross-provider dedup (only exact dupes).
|
|
208
|
+
const ref = `${p.id}/${id}`;
|
|
209
|
+
if (seen.has(ref)) continue;
|
|
210
|
+
seen.add(ref);
|
|
211
|
+
out.push({ ref, display: `${id} [${p.id}]` });
|
|
212
|
+
}
|
|
204
213
|
}
|
|
205
|
-
return
|
|
214
|
+
return out;
|
|
206
215
|
}
|
|
207
216
|
|
|
208
217
|
// ─── Routing Presets ─────────────────────────────────────────────────────
|
|
@@ -387,25 +396,32 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
387
396
|
// the user's `/model` choice on every routing decision.
|
|
388
397
|
|
|
389
398
|
async function manualMode(
|
|
390
|
-
availableModels: string
|
|
399
|
+
availableModels: Array<{ ref: string; display: string }>,
|
|
391
400
|
ctx: any,
|
|
392
401
|
): Promise<Record<string, { preferred: string; fallback: string }>> {
|
|
393
402
|
ctx.ui.notify(
|
|
394
403
|
"Assign a model to each orchestration role.\n" +
|
|
395
404
|
"These models are used by `/plan` and the orchestrator — NOT by normal chat.\n" +
|
|
396
|
-
"The chat default model is controlled via `/model` (and stays sticky across prompts).\n"
|
|
405
|
+
"The chat default model is controlled via `/model` (and stays sticky across prompts).\n" +
|
|
406
|
+
"Each option shows its provider as `model-id [provider]`, so the same model from\n" +
|
|
407
|
+
"different providers stays distinct.\n",
|
|
397
408
|
"info",
|
|
398
409
|
);
|
|
399
|
-
|
|
410
|
+
// Display strings carry the provider badge; map them back to the canonical
|
|
411
|
+
// "provider/id" reference that gets persisted into routing.json.
|
|
412
|
+
const DEFAULT_OPTION = "default (use current chat model)";
|
|
413
|
+
const refByDisplay = new Map<string, string>([[DEFAULT_OPTION, "default"]]);
|
|
414
|
+
for (const m of availableModels) refByDisplay.set(m.display, m.ref);
|
|
415
|
+
const modelOptions = [DEFAULT_OPTION, ...availableModels.map((m) => m.display)];
|
|
400
416
|
const assignments: Record<string, { preferred: string; fallback: string }> = {};
|
|
401
417
|
|
|
402
418
|
for (const role of TASK_ROLES) {
|
|
403
419
|
const chosen = await ctx.ui.select(`${role.label} — ${role.desc}`, modelOptions);
|
|
404
|
-
const preferredModel = chosen
|
|
420
|
+
const preferredModel = refByDisplay.get(chosen) ?? "default";
|
|
405
421
|
|
|
406
422
|
const fallbackOptions = modelOptions.filter((m) => m !== chosen);
|
|
407
423
|
const fallbackChoice = await ctx.ui.select(`Fallback for ${role.label}`, fallbackOptions);
|
|
408
|
-
const fallback = fallbackChoice
|
|
424
|
+
const fallback = refByDisplay.get(fallbackChoice) ?? "default";
|
|
409
425
|
|
|
410
426
|
assignments[role.key] = { preferred: preferredModel, fallback };
|
|
411
427
|
ctx.ui.notify(` ${role.label}: ${preferredModel} (fallback: ${fallback})`, "info");
|
|
@@ -415,7 +431,7 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
415
431
|
// This is NOT the chat default — `/model` controls that.
|
|
416
432
|
assignments["default"] = {
|
|
417
433
|
preferred: "default",
|
|
418
|
-
fallback: availableModels[0] || "default",
|
|
434
|
+
fallback: availableModels[0]?.ref || "default",
|
|
419
435
|
};
|
|
420
436
|
return assignments;
|
|
421
437
|
}
|
|
@@ -561,16 +561,33 @@ Tag the note with relevant keywords for vector search.
|
|
|
561
561
|
];
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
/**
|
|
565
|
+
* Resolve a routing.json model reference to an available model.
|
|
566
|
+
* Accepts a provider-qualified "provider/id" reference (so the same model id
|
|
567
|
+
* offered by several providers can be disambiguated) and falls back to a bare
|
|
568
|
+
* "id" for legacy configs. Splits on the FIRST slash only, since some model
|
|
569
|
+
* ids themselves contain slashes (e.g. OpenRouter "anthropic/claude-...").
|
|
570
|
+
*/
|
|
571
|
+
function resolveModelRef(available: any[], ref: string): any | undefined {
|
|
572
|
+
if (!ref) return undefined;
|
|
573
|
+
const slash = ref.indexOf("/");
|
|
574
|
+
if (slash > 0) {
|
|
575
|
+
const provider = ref.slice(0, slash);
|
|
576
|
+
const id = ref.slice(slash + 1);
|
|
577
|
+
const qualified = available.find((m: any) => m.provider === provider && m.id === id);
|
|
578
|
+
if (qualified) return qualified;
|
|
579
|
+
}
|
|
580
|
+
return available.find((m: any) => m.id === ref);
|
|
581
|
+
}
|
|
582
|
+
|
|
564
583
|
/**
|
|
565
584
|
* Switch model for the current phase.
|
|
566
585
|
*/
|
|
567
586
|
async function switchModelForPhase(phase: OrchestratorPhase, ctx: any): Promise<string> {
|
|
568
587
|
const available = ctx.modelRegistry?.getAvailable?.() || [];
|
|
569
|
-
const
|
|
570
|
-
const fallback = available.find((m: any) => m.id === phase.fallback);
|
|
571
|
-
const target = preferred || fallback;
|
|
588
|
+
const target = resolveModelRef(available, phase.model) || resolveModelRef(available, phase.fallback);
|
|
572
589
|
|
|
573
|
-
if (target && target.id !== ctx.model?.id) {
|
|
590
|
+
if (target && (target.id !== ctx.model?.id || target.provider !== ctx.model?.provider)) {
|
|
574
591
|
const switched = await pi.setModel(target);
|
|
575
592
|
if (switched) return target.id;
|
|
576
593
|
}
|
package/extensions/phi/setup.ts
CHANGED
|
@@ -516,23 +516,25 @@ async function configureLocal(
|
|
|
516
516
|
async function pickModelFromCatalog(
|
|
517
517
|
ui: ExtensionUIContext,
|
|
518
518
|
prompt: string,
|
|
519
|
-
|
|
519
|
+
models: Array<{ ref: string; display: string }>,
|
|
520
520
|
): Promise<string | undefined> {
|
|
521
|
-
if (
|
|
521
|
+
if (models.length === 0) {
|
|
522
522
|
ui.notify("No models available. Configure a provider first.", "warning");
|
|
523
523
|
return undefined;
|
|
524
524
|
}
|
|
525
|
-
const
|
|
525
|
+
const KEEP = "(keep default)";
|
|
526
|
+
const options = [KEEP, ...models.map((m) => m.display)];
|
|
526
527
|
const choice = await ui.select(prompt, options);
|
|
527
|
-
if (!choice || choice ===
|
|
528
|
-
|
|
528
|
+
if (!choice || choice === KEEP) return undefined;
|
|
529
|
+
// Map the displayed "id [provider]" back to the canonical "provider/id" ref.
|
|
530
|
+
return models.find((m) => m.display === choice)?.ref;
|
|
529
531
|
}
|
|
530
532
|
|
|
531
533
|
async function configureAssignments(
|
|
532
534
|
ui: ExtensionUIContext,
|
|
533
|
-
|
|
535
|
+
models: Array<{ ref: string; display: string }>,
|
|
534
536
|
): Promise<{ defaultModel: string; orchestration: Record<string, RouteAssignment> }> {
|
|
535
|
-
if (
|
|
537
|
+
if (models.length === 0) {
|
|
536
538
|
ui.notify("No models available for assignment. Configure a provider first.", "warning");
|
|
537
539
|
return { defaultModel: "default", orchestration: {} };
|
|
538
540
|
}
|
|
@@ -551,11 +553,11 @@ async function configureAssignments(
|
|
|
551
553
|
const orchestration: Record<string, RouteAssignment> = {};
|
|
552
554
|
for (const role of ORCHESTRATION_ROLES) {
|
|
553
555
|
const preferred =
|
|
554
|
-
(await pickModelFromCatalog(ui, `${role.label} - preferred model (${role.desc})`,
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
const fallback =
|
|
558
|
-
? (await pickModelFromCatalog(ui, `${role.label} - fallback model`,
|
|
556
|
+
(await pickModelFromCatalog(ui, `${role.label} - preferred model (${role.desc})`, models)) ??
|
|
557
|
+
models[0].ref;
|
|
558
|
+
const fallbackModels = models.filter((m) => m.ref !== preferred);
|
|
559
|
+
const fallback = fallbackModels.length > 0
|
|
560
|
+
? (await pickModelFromCatalog(ui, `${role.label} - fallback model`, fallbackModels)) ?? preferred
|
|
559
561
|
: preferred;
|
|
560
562
|
orchestration[role.key] = { preferred, fallback };
|
|
561
563
|
ui.notify(` ${role.label}: ${preferred} / ${fallback}`, "info");
|
|
@@ -675,17 +677,23 @@ export default function setupExtension(pi: ExtensionAPI) {
|
|
|
675
677
|
}
|
|
676
678
|
|
|
677
679
|
if (action === "Assign models to agent roles") {
|
|
678
|
-
const
|
|
680
|
+
const allModels: Array<{ ref: string; display: string }> = [];
|
|
681
|
+
const seenRefs = new Set<string>();
|
|
679
682
|
for (const p of catalog) {
|
|
680
683
|
const stored = store.getProvider(p.id);
|
|
681
684
|
if (!stored) continue;
|
|
682
685
|
const models = Array.isArray(stored.models) ? stored.models : [];
|
|
683
686
|
for (const m of models) {
|
|
684
687
|
const id = (m as { id?: string }).id;
|
|
685
|
-
if (typeof id
|
|
688
|
+
if (typeof id !== "string") continue;
|
|
689
|
+
// Provider-qualified ref so the same id from different providers stays distinct.
|
|
690
|
+
const ref = `${p.id}/${id}`;
|
|
691
|
+
if (seenRefs.has(ref)) continue;
|
|
692
|
+
seenRefs.add(ref);
|
|
693
|
+
allModels.push({ ref, display: `${id} [${p.id}]` });
|
|
686
694
|
}
|
|
687
695
|
}
|
|
688
|
-
const { defaultModel, orchestration } = await configureAssignments(ui,
|
|
696
|
+
const { defaultModel, orchestration } = await configureAssignments(ui, allModels);
|
|
689
697
|
assignments.default = defaultModel;
|
|
690
698
|
assignments.orchestration = orchestration;
|
|
691
699
|
refreshAvailable();
|