@phi-code-admin/phi-code 0.84.1 → 0.85.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/CHANGELOG.md +71 -0
- package/README.md +3 -3
- package/agents/code.md +1 -0
- package/agents/explore.md +11 -5
- package/agents/plan.md +18 -3
- package/agents/review.md +18 -7
- package/agents/test.md +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +2 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +30 -28
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/version-check.d.ts +5 -5
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +13 -8
- package/dist/utils/version-check.js.map +1 -1
- package/docs/settings.md +2 -2
- package/docs/usage.md +1 -1
- package/examples/sdk/12-full-control.ts +1 -1
- package/extensions/phi/mcp/index.ts +10 -4
- package/extensions/phi/models.ts +170 -49
- package/extensions/phi/orchestrator.ts +51 -12
- package/extensions/phi/providers/live-models.ts +31 -1
- package/extensions/phi/providers/opencode-go.ts +7 -3
- package/extensions/phi/setup.ts +9 -0
- package/extensions/phi/skill-loader.ts +46 -25
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface LatestRelease {
|
|
2
2
|
version: string;
|
|
3
3
|
packageName?: string;
|
|
4
4
|
}
|
|
5
5
|
export declare function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined;
|
|
6
6
|
export declare function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean;
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function getLatestRelease(currentVersion: string, options?: {
|
|
8
8
|
timeoutMs?: number;
|
|
9
|
-
}): Promise<
|
|
10
|
-
export declare function
|
|
9
|
+
}): Promise<LatestRelease | undefined>;
|
|
10
|
+
export declare function getLatestVersion(currentVersion: string, options?: {
|
|
11
11
|
timeoutMs?: number;
|
|
12
12
|
}): Promise<string | undefined>;
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function checkForNewVersion(currentVersion: string): Promise<string | undefined>;
|
|
14
14
|
//# sourceMappingURL=version-check.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAsBD,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcpG;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAM/F;AAED,wBAAsB,gBAAgB,CACrC,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAkBpC;AAED,wBAAsB,gBAAgB,CACrC,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU5F","sourcesContent":["import { PACKAGE_NAME } from \"../config.js\";\nimport { getPiUserAgent } from \"./pi-user-agent.js\";\n\n// Version checks query the npm registry for the published phi-code package.\n// (The inherited flow queried pi.dev, the upstream Pi endpoint, which reported\n// upstream Pi releases and made \"Update Available\" fire for versions of the\n// wrong product.)\nconst NPM_REGISTRY_URL = \"https://registry.npmjs.org\";\nconst DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;\n\nexport interface LatestRelease {\n\tversion: string;\n\tpackageName?: string;\n}\n\ninterface ParsedVersion {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\tprerelease?: string;\n}\n\nfunction parsePackageVersion(version: string): ParsedVersion | undefined {\n\tconst match = version.trim().match(/^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+.*)?$/);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tmajor: Number.parseInt(match[1], 10),\n\t\tminor: Number.parseInt(match[2], 10),\n\t\tpatch: Number.parseInt(match[3], 10),\n\t\tprerelease: match[4],\n\t};\n}\n\nexport function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined {\n\tconst left = parsePackageVersion(leftVersion);\n\tconst right = parsePackageVersion(rightVersion);\n\tif (!left || !right) {\n\t\treturn undefined;\n\t}\n\n\tif (left.major !== right.major) return left.major - right.major;\n\tif (left.minor !== right.minor) return left.minor - right.minor;\n\tif (left.patch !== right.patch) return left.patch - right.patch;\n\tif (left.prerelease === right.prerelease) return 0;\n\tif (!left.prerelease) return 1;\n\tif (!right.prerelease) return -1;\n\treturn left.prerelease.localeCompare(right.prerelease);\n}\n\nexport function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean {\n\tconst comparison = comparePackageVersions(candidateVersion, currentVersion);\n\tif (comparison !== undefined) {\n\t\treturn comparison > 0;\n\t}\n\treturn candidateVersion.trim() !== currentVersion.trim();\n}\n\nexport async function getLatestRelease(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<LatestRelease | undefined> {\n\tif (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE) return undefined;\n\n\tconst response = await fetch(`${NPM_REGISTRY_URL}/${PACKAGE_NAME}/latest`, {\n\t\theaders: {\n\t\t\t\"User-Agent\": getPiUserAgent(currentVersion),\n\t\t\taccept: \"application/json\",\n\t\t},\n\t\tsignal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS),\n\t});\n\tif (!response.ok) return undefined;\n\n\tconst data = (await response.json()) as { name?: unknown; version?: unknown };\n\tif (typeof data.version !== \"string\" || !data.version.trim()) {\n\t\treturn undefined;\n\t}\n\tconst packageName = typeof data.name === \"string\" && data.name.trim() ? data.name.trim() : undefined;\n\treturn { version: data.version.trim(), packageName };\n}\n\nexport async function getLatestVersion(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<string | undefined> {\n\treturn (await getLatestRelease(currentVersion, options))?.version;\n}\n\nexport async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {\n\ttry {\n\t\tconst latestVersion = await getLatestVersion(currentVersion);\n\t\tif (latestVersion && isNewerPackageVersion(latestVersion, currentVersion)) {\n\t\t\treturn latestVersion;\n\t\t}\n\t\treturn undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { PACKAGE_NAME } from "../config.js";
|
|
1
2
|
import { getPiUserAgent } from "./pi-user-agent.js";
|
|
2
|
-
|
|
3
|
+
// Version checks query the npm registry for the published phi-code package.
|
|
4
|
+
// (The inherited flow queried pi.dev, the upstream Pi endpoint, which reported
|
|
5
|
+
// upstream Pi releases and made "Update Available" fire for versions of the
|
|
6
|
+
// wrong product.)
|
|
7
|
+
const NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
3
8
|
const DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;
|
|
4
9
|
function parsePackageVersion(version) {
|
|
5
10
|
const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
@@ -40,10 +45,10 @@ export function isNewerPackageVersion(candidateVersion, currentVersion) {
|
|
|
40
45
|
}
|
|
41
46
|
return candidateVersion.trim() !== currentVersion.trim();
|
|
42
47
|
}
|
|
43
|
-
export async function
|
|
48
|
+
export async function getLatestRelease(currentVersion, options = {}) {
|
|
44
49
|
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
|
|
45
50
|
return undefined;
|
|
46
|
-
const response = await fetch(
|
|
51
|
+
const response = await fetch(`${NPM_REGISTRY_URL}/${PACKAGE_NAME}/latest`, {
|
|
47
52
|
headers: {
|
|
48
53
|
"User-Agent": getPiUserAgent(currentVersion),
|
|
49
54
|
accept: "application/json",
|
|
@@ -56,15 +61,15 @@ export async function getLatestPiRelease(currentVersion, options = {}) {
|
|
|
56
61
|
if (typeof data.version !== "string" || !data.version.trim()) {
|
|
57
62
|
return undefined;
|
|
58
63
|
}
|
|
59
|
-
const packageName = typeof data.
|
|
64
|
+
const packageName = typeof data.name === "string" && data.name.trim() ? data.name.trim() : undefined;
|
|
60
65
|
return { version: data.version.trim(), packageName };
|
|
61
66
|
}
|
|
62
|
-
export async function
|
|
63
|
-
return (await
|
|
67
|
+
export async function getLatestVersion(currentVersion, options = {}) {
|
|
68
|
+
return (await getLatestRelease(currentVersion, options))?.version;
|
|
64
69
|
}
|
|
65
|
-
export async function
|
|
70
|
+
export async function checkForNewVersion(currentVersion) {
|
|
66
71
|
try {
|
|
67
|
-
const latestVersion = await
|
|
72
|
+
const latestVersion = await getLatestVersion(currentVersion);
|
|
68
73
|
if (latestVersion && isNewerPackageVersion(latestVersion, currentVersion)) {
|
|
69
74
|
return latestVersion;
|
|
70
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,
|
|
1
|
+
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"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","sourcesContent":["import { PACKAGE_NAME } from \"../config.js\";\nimport { getPiUserAgent } from \"./pi-user-agent.js\";\n\n// Version checks query the npm registry for the published phi-code package.\n// (The inherited flow queried pi.dev, the upstream Pi endpoint, which reported\n// upstream Pi releases and made \"Update Available\" fire for versions of the\n// wrong product.)\nconst NPM_REGISTRY_URL = \"https://registry.npmjs.org\";\nconst DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;\n\nexport interface LatestRelease {\n\tversion: string;\n\tpackageName?: string;\n}\n\ninterface ParsedVersion {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\tprerelease?: string;\n}\n\nfunction parsePackageVersion(version: string): ParsedVersion | undefined {\n\tconst match = version.trim().match(/^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+.*)?$/);\n\tif (!match) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tmajor: Number.parseInt(match[1], 10),\n\t\tminor: Number.parseInt(match[2], 10),\n\t\tpatch: Number.parseInt(match[3], 10),\n\t\tprerelease: match[4],\n\t};\n}\n\nexport function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined {\n\tconst left = parsePackageVersion(leftVersion);\n\tconst right = parsePackageVersion(rightVersion);\n\tif (!left || !right) {\n\t\treturn undefined;\n\t}\n\n\tif (left.major !== right.major) return left.major - right.major;\n\tif (left.minor !== right.minor) return left.minor - right.minor;\n\tif (left.patch !== right.patch) return left.patch - right.patch;\n\tif (left.prerelease === right.prerelease) return 0;\n\tif (!left.prerelease) return 1;\n\tif (!right.prerelease) return -1;\n\treturn left.prerelease.localeCompare(right.prerelease);\n}\n\nexport function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean {\n\tconst comparison = comparePackageVersions(candidateVersion, currentVersion);\n\tif (comparison !== undefined) {\n\t\treturn comparison > 0;\n\t}\n\treturn candidateVersion.trim() !== currentVersion.trim();\n}\n\nexport async function getLatestRelease(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<LatestRelease | undefined> {\n\tif (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE) return undefined;\n\n\tconst response = await fetch(`${NPM_REGISTRY_URL}/${PACKAGE_NAME}/latest`, {\n\t\theaders: {\n\t\t\t\"User-Agent\": getPiUserAgent(currentVersion),\n\t\t\taccept: \"application/json\",\n\t\t},\n\t\tsignal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS),\n\t});\n\tif (!response.ok) return undefined;\n\n\tconst data = (await response.json()) as { name?: unknown; version?: unknown };\n\tif (typeof data.version !== \"string\" || !data.version.trim()) {\n\t\treturn undefined;\n\t}\n\tconst packageName = typeof data.name === \"string\" && data.name.trim() ? data.name.trim() : undefined;\n\treturn { version: data.version.trim(), packageName };\n}\n\nexport async function getLatestVersion(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<string | undefined> {\n\treturn (await getLatestRelease(currentVersion, options))?.version;\n}\n\nexport async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {\n\ttry {\n\t\tconst latestVersion = await getLatestVersion(currentVersion);\n\t\tif (latestVersion && isNewerPackageVersion(latestVersion, currentVersion)) {\n\t\t\treturn latestVersion;\n\t\t}\n\t\treturn undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
|
package/docs/settings.md
CHANGED
|
@@ -50,9 +50,9 @@ Edit directly or use `/settings` for common options.
|
|
|
50
50
|
|
|
51
51
|
### Telemetry and update checks
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
phi-code sends no install/update telemetry (the inherited upstream ping to `pi.dev` was removed; `enableInstallTelemetry` is kept for settings compatibility but has no effect). The update check fetches `https://registry.npmjs.org/@phi-code-admin/phi-code/latest` to look for a newer phi-code release.
|
|
54
54
|
|
|
55
|
-
Set `PI_SKIP_VERSION_CHECK=1` to disable the
|
|
55
|
+
Set `PI_SKIP_VERSION_CHECK=1` to disable the phi-code version update check. Use `--offline` or `PI_OFFLINE=1` to disable all startup network operations described here, including update checks and package update checks.
|
|
56
56
|
|
|
57
57
|
### Warnings
|
|
58
58
|
|
package/docs/usage.md
CHANGED
|
@@ -263,7 +263,7 @@ pi --tools read,grep,find,ls -p "Review the code"
|
|
|
263
263
|
| `PI_CODING_AGENT_SESSION_DIR` | Override session storage directory; overridden by `--session-dir` |
|
|
264
264
|
| `PI_PACKAGE_DIR` | Override package directory, useful for Nix/Guix store paths |
|
|
265
265
|
| `PI_OFFLINE` | Disable startup network operations, including update checks, package update checks, and install/update telemetry |
|
|
266
|
-
| `PI_SKIP_VERSION_CHECK` | Skip the
|
|
266
|
+
| `PI_SKIP_VERSION_CHECK` | Skip the phi-code version update check at startup. This prevents the npm registry latest-version request |
|
|
267
267
|
| `PI_TELEMETRY` | Override install/update telemetry: `1`/`true`/`yes` or `0`/`false`/`no`. This does not disable update checks |
|
|
268
268
|
| `PI_CACHE_RETENTION` | Set to `long` for extended prompt cache where supported |
|
|
269
269
|
| `VISUAL`, `EDITOR` | External editor for Ctrl+G |
|
|
@@ -26,7 +26,7 @@ if (process.env.MY_ANTHROPIC_KEY) {
|
|
|
26
26
|
// Model registry with no custom models.json
|
|
27
27
|
const modelRegistry = ModelRegistry.inMemory(authStorage);
|
|
28
28
|
|
|
29
|
-
const model = getModel("anthropic", "claude-sonnet-4-
|
|
29
|
+
const model = getModel("anthropic", "claude-sonnet-4-5");
|
|
30
30
|
if (!model) throw new Error("Model not found");
|
|
31
31
|
|
|
32
32
|
// In-memory settings with overrides
|
|
@@ -69,10 +69,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
72
|
+
// Even with zero configured servers we proceed so the bundled `/mcp` command
|
|
73
|
+
// is always available (discoverability); it then guides the user to create an
|
|
74
|
+
// mcp.json. Servers are still only connected when one is actually configured.
|
|
76
75
|
|
|
77
76
|
// ── 2. Initialize bridge components ──────────────────────────────────────
|
|
78
77
|
// Auth callbacks — opens browser and notifies user when OAuth is needed
|
|
@@ -172,6 +171,13 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
172
171
|
.filter(Boolean)
|
|
173
172
|
.join("\n");
|
|
174
173
|
ctx.ui.notify(detail, "info");
|
|
174
|
+
} else if (manager.getAllServers().length === 0) {
|
|
175
|
+
// No servers configured yet: guide the user instead of showing nothing.
|
|
176
|
+
ctx.ui.notify(
|
|
177
|
+
'No MCP servers configured. Create ~/.phi/agent/mcp.json (global) or .phi/mcp.json (project) with an "mcpServers" block, e.g.:\n' +
|
|
178
|
+
'{ "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."], "lifecycle": "eager" } } }',
|
|
179
|
+
"info",
|
|
180
|
+
);
|
|
175
181
|
} else {
|
|
176
182
|
// Summary view: all servers
|
|
177
183
|
ctx.ui.notify(manager.getStatusSummary(), "info");
|
package/extensions/phi/models.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { ApiKeyStore, type ConfigWatcher, type ExtensionAPI, getApiKeyStore, getConfigWatcher } from "phi-code";
|
|
19
|
+
import { getModels } from "phi-code-ai";
|
|
19
20
|
import {
|
|
20
21
|
buildOpenCodeGoAnthropicProviderConfig,
|
|
21
22
|
buildOpenCodeGoProviderConfig,
|
|
@@ -25,7 +26,9 @@ import { formatWindow, inferContextWindow, parseContextWindow } from "./provider
|
|
|
25
26
|
import { fetchLiveModels, peekCache, resetLiveModelsCache, toPersistedModel } from "./providers/live-models.js";
|
|
26
27
|
|
|
27
28
|
const PROVIDER_DISPLAY: Record<string, string> = {
|
|
29
|
+
opencode: "OpenCode Zen",
|
|
28
30
|
"opencode-go": "OpenCode Go",
|
|
31
|
+
"opencode-go-anthropic": "OpenCode Go (Anthropic-compat)",
|
|
29
32
|
"alibaba-codingplan": "Alibaba Coding Plan (OpenAI-compat)",
|
|
30
33
|
"alibaba-codingplan-anthropic": "Alibaba Coding Plan (Anthropic-compat)",
|
|
31
34
|
openai: "OpenAI",
|
|
@@ -37,10 +40,62 @@ const PROVIDER_DISPLAY: Record<string, string> = {
|
|
|
37
40
|
"lm-studio": "LM Studio (local)",
|
|
38
41
|
};
|
|
39
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Providers the live-models dispatcher can actually re-fetch (see
|
|
45
|
+
* live-models.ts dispatchFetch + refreshOpenCodeGo). Used to extend the
|
|
46
|
+
* startup/manual refresh to providers that are authenticated via auth.json or
|
|
47
|
+
* env vars but have no models.json entry yet — without it, a user who set a
|
|
48
|
+
* key with /auth (and never ran /setup) would never see new upstream models.
|
|
49
|
+
*/
|
|
50
|
+
const REFRESHABLE_PROVIDERS: ReadonlySet<string> = new Set([
|
|
51
|
+
"opencode",
|
|
52
|
+
"opencode-go",
|
|
53
|
+
"opencode-go-anthropic",
|
|
54
|
+
"alibaba-codingplan",
|
|
55
|
+
"alibaba-codingplan-anthropic",
|
|
56
|
+
"openai",
|
|
57
|
+
"anthropic",
|
|
58
|
+
"google",
|
|
59
|
+
"openrouter",
|
|
60
|
+
"groq",
|
|
61
|
+
"ollama",
|
|
62
|
+
"lm-studio",
|
|
63
|
+
]);
|
|
64
|
+
|
|
40
65
|
function displayName(id: string): string {
|
|
41
66
|
return PROVIDER_DISPLAY[id] ?? id;
|
|
42
67
|
}
|
|
43
68
|
|
|
69
|
+
/** Default discovery base URLs for providers whose models.json entry is created by a refresh. */
|
|
70
|
+
const DEFAULT_BASE_URLS: Record<string, string> = {
|
|
71
|
+
opencode: "https://opencode.ai/zen/v1",
|
|
72
|
+
"opencode-go": "https://opencode.ai/zen/go/v1",
|
|
73
|
+
"alibaba-codingplan": "https://coding-intl.dashscope.aliyuncs.com/v1",
|
|
74
|
+
"alibaba-codingplan-anthropic": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
|
|
75
|
+
openai: "https://api.openai.com/v1",
|
|
76
|
+
anthropic: "https://api.anthropic.com/v1",
|
|
77
|
+
google: "https://generativelanguage.googleapis.com/v1beta",
|
|
78
|
+
openrouter: "https://openrouter.ai/api/v1",
|
|
79
|
+
groq: "https://api.groq.com/openai/v1",
|
|
80
|
+
ollama: "http://localhost:11434/v1",
|
|
81
|
+
"lm-studio": "http://localhost:1234/v1",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Built-in (models.generated.ts) model ids for a provider. Persisting only the
|
|
86
|
+
* models NOT in this set keeps the rich built-in definitions (costs, image
|
|
87
|
+
* input, thinking-level maps) authoritative — models.json carries just the
|
|
88
|
+
* delta the static catalog does not know about yet.
|
|
89
|
+
*/
|
|
90
|
+
function builtinModelIds(providerId: string): Set<string> {
|
|
91
|
+
try {
|
|
92
|
+
const models = getModels(providerId as Parameters<typeof getModels>[0]) as Array<{ id: string }>;
|
|
93
|
+
return new Set(models.map((m) => m.id));
|
|
94
|
+
} catch {
|
|
95
|
+
return new Set();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
44
99
|
interface RefreshOutcome {
|
|
45
100
|
provider: string;
|
|
46
101
|
source: "live" | "cache" | "fallback" | "unsupported" | "skipped";
|
|
@@ -68,7 +123,21 @@ async function refreshOpenCodeGo(
|
|
|
68
123
|
? buildOpenCodeGoAnthropicProviderConfig(keyForBuild, models)
|
|
69
124
|
: buildOpenCodeGoProviderConfig(keyForBuild, models);
|
|
70
125
|
|
|
71
|
-
|
|
126
|
+
// Persist only models the built-in catalog does not know yet; built-ins stay
|
|
127
|
+
// authoritative (costs, image input) and models.json carries the delta.
|
|
128
|
+
const builtin = builtinModelIds(providerId);
|
|
129
|
+
const newModels = config.models.filter((m) => !builtin.has(m.id));
|
|
130
|
+
|
|
131
|
+
if (newModels.length === 0) {
|
|
132
|
+
if (stored && Array.isArray(stored.models) && stored.models.length > 0) {
|
|
133
|
+
// Clean up previously persisted models that have since become built-in.
|
|
134
|
+
watcher.muteForWrite("models_json_changed");
|
|
135
|
+
store.setKey(providerId, stored.apiKey ?? apiKey ?? "local", {
|
|
136
|
+
baseUrl: stored.baseUrl ?? config.baseUrl,
|
|
137
|
+
api: stored.api ?? config.api,
|
|
138
|
+
models: [],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
72
141
|
return { provider: providerId, source: source === "fallback" ? "fallback" : "skipped", count: 0 };
|
|
73
142
|
}
|
|
74
143
|
|
|
@@ -76,22 +145,26 @@ async function refreshOpenCodeGo(
|
|
|
76
145
|
store.setKey(providerId, stored?.apiKey ?? apiKey ?? "local", {
|
|
77
146
|
baseUrl: stored?.baseUrl ?? config.baseUrl,
|
|
78
147
|
api: stored?.api ?? config.api,
|
|
79
|
-
models:
|
|
148
|
+
models: newModels,
|
|
80
149
|
});
|
|
81
150
|
|
|
82
151
|
const outcomeSource = source === "live" ? "live" : source === "cache" ? "cache" : "fallback";
|
|
83
|
-
return { provider: providerId, source: outcomeSource, count:
|
|
152
|
+
return { provider: providerId, source: outcomeSource, count: newModels.length };
|
|
84
153
|
}
|
|
85
154
|
|
|
86
155
|
async function refreshOne(
|
|
87
156
|
store: ApiKeyStore,
|
|
88
157
|
watcher: ConfigWatcher,
|
|
89
158
|
providerId: string,
|
|
159
|
+
resolvedApiKey?: string,
|
|
90
160
|
): Promise<RefreshOutcome> {
|
|
91
161
|
const stored = store.getProvider(providerId);
|
|
92
|
-
const
|
|
162
|
+
const storedKey = stored?.apiKey && !stored.apiKey.startsWith("$") && stored.apiKey !== "local"
|
|
93
163
|
? stored.apiKey
|
|
94
164
|
: undefined;
|
|
165
|
+
// Prefer the key stored in models.json, else the one resolved from
|
|
166
|
+
// auth.json/env by the model registry (providers set up via /auth only).
|
|
167
|
+
const apiKey = storedKey ?? resolvedApiKey;
|
|
95
168
|
|
|
96
169
|
// OpenCode Go is a provider pair the generic fetchLiveModels path can't express
|
|
97
170
|
// (and never handled the Anthropic side), so refresh it from the shared catalog.
|
|
@@ -110,40 +183,30 @@ async function refreshOne(
|
|
|
110
183
|
return { provider: providerId, source: "skipped", count: 0, error: result.error };
|
|
111
184
|
}
|
|
112
185
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
186
|
+
// Persist only the delta the built-in catalog does not know yet (see
|
|
187
|
+
// builtinModelIds). Built-in definitions keep their costs/capabilities.
|
|
188
|
+
const builtin = builtinModelIds(providerId);
|
|
189
|
+
const persisted = result.models.map(toPersistedModel).filter((m) => !builtin.has(m.id));
|
|
117
190
|
|
|
118
191
|
// Preserve baseUrl/api/apiKey/headers from existing config; only models change.
|
|
119
|
-
const baseUrl =
|
|
120
|
-
stored?.baseUrl ??
|
|
121
|
-
(providerId === "opencode-go"
|
|
122
|
-
? "https://opencode.ai/zen/go/v1"
|
|
123
|
-
: providerId === "alibaba-codingplan"
|
|
124
|
-
? "https://coding-intl.dashscope.aliyuncs.com/v1"
|
|
125
|
-
: providerId === "alibaba-codingplan-anthropic"
|
|
126
|
-
? "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"
|
|
127
|
-
: providerId === "openai"
|
|
128
|
-
? "https://api.openai.com/v1"
|
|
129
|
-
: providerId === "anthropic"
|
|
130
|
-
? "https://api.anthropic.com/v1"
|
|
131
|
-
: providerId === "google"
|
|
132
|
-
? "https://generativelanguage.googleapis.com/v1beta"
|
|
133
|
-
: providerId === "openrouter"
|
|
134
|
-
? "https://openrouter.ai/api/v1"
|
|
135
|
-
: providerId === "groq"
|
|
136
|
-
? "https://api.groq.com/openai/v1"
|
|
137
|
-
: providerId === "ollama"
|
|
138
|
-
? "http://localhost:11434/v1"
|
|
139
|
-
: providerId === "lm-studio"
|
|
140
|
-
? "http://localhost:1234/v1"
|
|
141
|
-
: undefined);
|
|
142
|
-
|
|
192
|
+
const baseUrl = stored?.baseUrl ?? DEFAULT_BASE_URLS[providerId];
|
|
143
193
|
if (!baseUrl) {
|
|
144
194
|
return { provider: providerId, source: "skipped", count: 0, error: "unknown baseUrl" };
|
|
145
195
|
}
|
|
146
196
|
|
|
197
|
+
if (persisted.length === 0) {
|
|
198
|
+
if (stored && Array.isArray(stored.models) && stored.models.length > 0) {
|
|
199
|
+
// Clean up previously persisted models that have since become built-in.
|
|
200
|
+
watcher.muteForWrite("models_json_changed");
|
|
201
|
+
store.setKey(providerId, stored.apiKey ?? "local", {
|
|
202
|
+
baseUrl,
|
|
203
|
+
api: stored.api,
|
|
204
|
+
models: [],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return { provider: providerId, source: result.source, count: 0, error: result.error };
|
|
208
|
+
}
|
|
209
|
+
|
|
147
210
|
// Mute the config watcher so it does not echo this programmatic write back
|
|
148
211
|
// as a models_json_changed event (which would trigger a spurious reload +
|
|
149
212
|
// "Keys reloaded" notification). Mute per-write because refresh loops can
|
|
@@ -311,29 +374,73 @@ export default function modelsExtension(pi: ExtensionAPI) {
|
|
|
311
374
|
ctx.ui.notify(out, "info");
|
|
312
375
|
}
|
|
313
376
|
|
|
377
|
+
interface RefreshTarget {
|
|
378
|
+
id: string;
|
|
379
|
+
resolvedApiKey?: string;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Providers to refresh: every provider persisted in models.json, plus every
|
|
384
|
+
* refreshable provider that is authenticated (auth.json / env vars) but has
|
|
385
|
+
* no models.json entry yet. API keys are resolved through the registry so
|
|
386
|
+
* providers configured via /auth alone still get authenticated listings.
|
|
387
|
+
*/
|
|
388
|
+
async function resolveRefreshTargets(registry: {
|
|
389
|
+
getAvailable(): Array<{ provider: string }>;
|
|
390
|
+
getApiKeyForProvider(provider: string): Promise<string | undefined>;
|
|
391
|
+
}): Promise<RefreshTarget[]> {
|
|
392
|
+
const targets = new Map<string, RefreshTarget>();
|
|
393
|
+
for (const id of store.listProviders()) {
|
|
394
|
+
targets.set(id, { id });
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
for (const model of registry.getAvailable()) {
|
|
398
|
+
const id = model.provider;
|
|
399
|
+
if (!targets.has(id) && REFRESHABLE_PROVIDERS.has(id)) {
|
|
400
|
+
targets.set(id, { id });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
// registry unavailable — fall back to models.json providers only
|
|
405
|
+
}
|
|
406
|
+
for (const target of targets.values()) {
|
|
407
|
+
try {
|
|
408
|
+
target.resolvedApiKey = await registry.getApiKeyForProvider(target.id);
|
|
409
|
+
} catch {
|
|
410
|
+
// no resolvable key — refreshOne will try the stored/keyless path
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return [...targets.values()];
|
|
414
|
+
}
|
|
415
|
+
|
|
314
416
|
// Background refresh on session_start so every new Phi Code session reflects
|
|
315
417
|
// the latest provider catalogs without the user typing `/models refresh`.
|
|
316
418
|
// Failures are silent — startup must never be blocked by upstream API hiccups.
|
|
317
419
|
pi.on("session_start", async (_event, ctx) => {
|
|
420
|
+
if (process.env.PI_OFFLINE) return;
|
|
318
421
|
try {
|
|
319
422
|
store.load();
|
|
320
423
|
} catch {
|
|
321
424
|
// no models.json yet
|
|
322
425
|
}
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
426
|
+
const targets = await resolveRefreshTargets(ctx.modelRegistry);
|
|
427
|
+
if (targets.length === 0) return;
|
|
325
428
|
|
|
326
429
|
// Fire-and-forget. Hot-reload via models_json_changed event surfaces results.
|
|
327
430
|
void (async () => {
|
|
328
|
-
let
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
431
|
+
let discovered = 0;
|
|
432
|
+
let changedProviders = 0;
|
|
433
|
+
for (const target of targets) {
|
|
434
|
+
const outcome = await refreshOne(store, watcher, target.id, target.resolvedApiKey).catch(() => undefined);
|
|
435
|
+
if (outcome && outcome.source === "live" && outcome.count > 0) {
|
|
436
|
+
changedProviders++;
|
|
437
|
+
discovered += outcome.count;
|
|
438
|
+
}
|
|
332
439
|
}
|
|
333
|
-
if (
|
|
440
|
+
if (changedProviders > 0) {
|
|
334
441
|
try {
|
|
335
442
|
ctx.ui.notify(
|
|
336
|
-
`
|
|
443
|
+
`Discovered ${discovered} new model(s) across ${changedProviders} provider(s). See /model.`,
|
|
337
444
|
"info",
|
|
338
445
|
);
|
|
339
446
|
} catch {
|
|
@@ -346,20 +453,33 @@ export default function modelsExtension(pi: ExtensionAPI) {
|
|
|
346
453
|
|
|
347
454
|
async function refreshCommand(
|
|
348
455
|
target: string | undefined,
|
|
349
|
-
ctx: {
|
|
456
|
+
ctx: {
|
|
457
|
+
ui: { notify: (m: string, t?: "info" | "warning" | "error") => void; setStatus?: (k: string, v?: string) => void };
|
|
458
|
+
modelRegistry: {
|
|
459
|
+
getAvailable(): Array<{ provider: string }>;
|
|
460
|
+
getApiKeyForProvider(provider: string): Promise<string | undefined>;
|
|
461
|
+
};
|
|
462
|
+
},
|
|
350
463
|
): Promise<void> {
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
464
|
+
const targets = target ? [{ id: target } as RefreshTarget] : await resolveRefreshTargets(ctx.modelRegistry);
|
|
465
|
+
if (target) {
|
|
466
|
+
try {
|
|
467
|
+
targets[0].resolvedApiKey = await ctx.modelRegistry.getApiKeyForProvider(target);
|
|
468
|
+
} catch {
|
|
469
|
+
// keep undefined
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (targets.length === 0) {
|
|
353
473
|
ctx.ui.notify("No providers configured.", "warning");
|
|
354
474
|
return;
|
|
355
475
|
}
|
|
356
|
-
ctx.ui.notify(`Refreshing ${
|
|
476
|
+
ctx.ui.notify(`Refreshing ${targets.length} provider(s)...`, "info");
|
|
357
477
|
ctx.ui.setStatus?.("models-refresh", "Fetching live model catalogs...");
|
|
358
478
|
|
|
359
479
|
const outcomes: RefreshOutcome[] = [];
|
|
360
|
-
for (const
|
|
361
|
-
const outcome = await refreshOne(store, watcher, id).catch((err) => ({
|
|
362
|
-
provider: id,
|
|
480
|
+
for (const t of targets) {
|
|
481
|
+
const outcome = await refreshOne(store, watcher, t.id, t.resolvedApiKey).catch((err) => ({
|
|
482
|
+
provider: t.id,
|
|
363
483
|
source: "skipped" as const,
|
|
364
484
|
count: 0,
|
|
365
485
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -371,9 +491,10 @@ export default function modelsExtension(pi: ExtensionAPI) {
|
|
|
371
491
|
let out = "**Refresh report:**\n";
|
|
372
492
|
for (const o of outcomes) {
|
|
373
493
|
const icon = o.source === "live" ? "[ok]" : o.source === "fallback" ? "[fb]" : o.source === "cache" ? "[c]" : "[--]";
|
|
374
|
-
out += ` ${icon} ${displayName(o.provider)} \`${o.provider}\` — ${o.count} model(s) (${o.source}${o.error ? `, ${o.error}` : ""})\n`;
|
|
494
|
+
out += ` ${icon} ${displayName(o.provider)} \`${o.provider}\` — ${o.count} new model(s) (${o.source}${o.error ? `, ${o.error}` : ""})\n`;
|
|
375
495
|
}
|
|
376
|
-
out += `\
|
|
496
|
+
out += `\nOnly models missing from the built-in catalog are persisted to \`${store.configPath}\`;\n`;
|
|
497
|
+
out += `built-in models stay available either way. \`/model\` picker reflects the merged catalog.`;
|
|
377
498
|
ctx.ui.notify(out, "info");
|
|
378
499
|
pi.events.emit("models_json_changed", { source: "models-refresh" });
|
|
379
500
|
}
|
|
@@ -359,6 +359,28 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
359
359
|
return null;
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Load a skill body (SKILL.md) so a phase instruction can embed it verbatim.
|
|
364
|
+
* Search order mirrors loadAgentDef: project .phi/skills first, then the
|
|
365
|
+
* global ~/.phi/agent/skills (where postinstall copies the bundled skills).
|
|
366
|
+
* YAML frontmatter is stripped. Returns null when the skill is not installed.
|
|
367
|
+
*/
|
|
368
|
+
function loadSkillContent(name: string): string | null {
|
|
369
|
+
const dirs = [
|
|
370
|
+
join(process.cwd(), ".phi", "skills"),
|
|
371
|
+
join(homedir(), ".phi", "agent", "skills"),
|
|
372
|
+
];
|
|
373
|
+
for (const dir of dirs) {
|
|
374
|
+
try {
|
|
375
|
+
const content = readFileSync(join(dir, name, "SKILL.md"), "utf-8");
|
|
376
|
+
const fmMatch = content.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)$/);
|
|
377
|
+
const body = (fmMatch ? fmMatch[1] : content).trim();
|
|
378
|
+
if (body) return body;
|
|
379
|
+
} catch { /* try next dir */ }
|
|
380
|
+
}
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
|
|
362
384
|
/**
|
|
363
385
|
* Load routing config and build phase queue with model assignments + agent definitions.
|
|
364
386
|
* Each phase now reads outputs from previous phases and writes structured outputs.
|
|
@@ -392,6 +414,15 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
392
414
|
: '';
|
|
393
415
|
const runtimeInfo = `\n\nRuntime: ${process.platform} (${process.arch})${shellNote}`;
|
|
394
416
|
|
|
417
|
+
// Embed the prompt-architect skill so the PLAN phase applies it to every
|
|
418
|
+
// task it writes. The skill ships bundled and is copied to
|
|
419
|
+
// ~/.phi/agent/skills by postinstall; when missing, the inline task format
|
|
420
|
+
// still enforces the [CONTEXT]/[TASK]/[FORMAT]/[CONSTRAINTS] structure.
|
|
421
|
+
const promptArchitectSkill = loadSkillContent("prompt-architect");
|
|
422
|
+
const promptArchitectSection = promptArchitectSkill
|
|
423
|
+
? `\n**Prompt-architect skill (apply it to every task you write):**\n\n${promptArchitectSkill}\n`
|
|
424
|
+
: "";
|
|
425
|
+
|
|
395
426
|
const phases: OrchestratorPhase[] = [
|
|
396
427
|
{
|
|
397
428
|
key: "explore", label: "🔍 Phase 1 — EXPLORE", model: explore.preferred, fallback: explore.fallback,
|
|
@@ -466,29 +497,37 @@ After your analysis, use \`ontology_add\` to save key project entities AND their
|
|
|
466
497
|
**Step 1:** Read \`.phi/plans/brief-*.md\` (created by the explore phase)
|
|
467
498
|
**Step 2:** Read \`.phi/plans/explore-*.md\` to understand the codebase analysis
|
|
468
499
|
**Step 3:** Design the architecture based on findings
|
|
469
|
-
**Step 4:** Create a DETAILED TODO LIST in \`.phi/plans/todo-${ts}.md
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
-
|
|
473
|
-
-
|
|
474
|
-
-
|
|
475
|
-
-
|
|
500
|
+
**Step 4:** Create a DETAILED TODO LIST in \`.phi/plans/todo-${ts}.md\`.
|
|
501
|
+
Every task is a PROMPT for the CODE agent, which starts with ZERO memory of
|
|
502
|
+
this conversation. Write each task with the prompt-architect structure:
|
|
503
|
+
- **[CONTEXT]** what exists and why this task (real paths from the explore phase)
|
|
504
|
+
- **[TASK]** exactly what to do — files to create/modify with full paths
|
|
505
|
+
- **[FORMAT]** expected deliverable — exports, signatures, style, tests
|
|
506
|
+
- **[CONSTRAINTS]** what NOT to break, patterns to respect
|
|
507
|
+
Plus: task number + title, agent assignment (code/test), dependencies.
|
|
476
508
|
|
|
477
509
|
**Format for the todo list:**
|
|
478
510
|
\`\`\`markdown
|
|
479
511
|
# TODO: Project Tasks
|
|
480
512
|
|
|
481
513
|
## Task 1: [Task Title] [agent-type]
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
- [ ]
|
|
514
|
+
**[CONTEXT]** [What exists now, why this task, real paths (src/x.ts:42)]
|
|
515
|
+
**[TASK]**
|
|
516
|
+
- [ ] Create path/to/file.ext with [specific behavior]
|
|
517
|
+
- [ ] Modify path/to/other.ext: [exact change]
|
|
518
|
+
**[FORMAT]** [Expected exports/signatures/tests]
|
|
519
|
+
**[CONSTRAINTS]** [What not to break, patterns to follow]
|
|
485
520
|
- Dependencies: None
|
|
486
521
|
|
|
487
522
|
## Task 2: [Task Title] [agent-type]
|
|
488
|
-
|
|
523
|
+
**[CONTEXT]** ...
|
|
524
|
+
**[TASK]**
|
|
525
|
+
- [ ] ...
|
|
526
|
+
**[FORMAT]** ...
|
|
527
|
+
**[CONSTRAINTS]** ...
|
|
489
528
|
- Dependencies: Task 1
|
|
490
529
|
\`\`\`
|
|
491
|
-
|
|
530
|
+
${promptArchitectSection}
|
|
492
531
|
Before finishing, use \`memory_write\` to save your plan summary with relevant tags for future reference.` + runtimeInfo + COMMON_PHASE_RULES,
|
|
493
532
|
},
|
|
494
533
|
{
|