@phi-code-admin/phi-code 0.90.0 → 0.92.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 +54 -0
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +3 -0
- package/dist/core/provider-display-names.js.map +1 -1
- package/extensions/phi/keys.ts +36 -6
- package/extensions/phi/orchestrator.ts +59 -4
- package/extensions/phi/providers/debug-build-commands.ts +3 -1
- package/extensions/phi/providers/provider-bootstrap.ts +98 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.92.0] - 2026-07-11
|
|
4
|
+
|
|
5
|
+
### Fixed — the two defects the n=13 SWE-bench measurement exposed
|
|
6
|
+
|
|
7
|
+
The measured head-to-head (baseline 7/13 vs /debug 6/13, official harness) traced
|
|
8
|
+
/debug's two lost points to concrete, reparable defects — both fixed here.
|
|
9
|
+
|
|
10
|
+
- **Hasty BLOCKED (sympy-11897, halted at 51s).** The generic /debug//build
|
|
11
|
+
driver now routes phase completion through the same pure decision machine as
|
|
12
|
+
/plan (`decidePhaseTransition`), gaining what it measurably lacked: a fatal
|
|
13
|
+
401 stops the run, a transient provider error (timeout / 5xx / 429 / broken
|
|
14
|
+
stream) retries the SAME phase once on the fallback model instead of being
|
|
15
|
+
swallowed, and — new — a BLOCKED verdict gets ONE second chance on the
|
|
16
|
+
fallback model (a different family re-checks the reproduction) before the
|
|
17
|
+
halt. A confirmed second BLOCKED still halts honestly: no fabricated fix.
|
|
18
|
+
REPRODUCE's instructions now state BLOCKED is a last resort — adjust and
|
|
19
|
+
retry once before concluding.
|
|
20
|
+
- **Timeout-blind VERIFY (sympy-11870, fix passed its repro but hung the real
|
|
21
|
+
suite for 900s).** VERIFY's instructions now treat a TIMEOUT of the
|
|
22
|
+
reproduction or the suite as a FAILURE of the candidate — a massive slowdown
|
|
23
|
+
or infinite loop IS a regression — with verdict BLOCKED, never FIXED. A
|
|
24
|
+
dramatic duration increase below the timeout is flagged as a red flag too.
|
|
25
|
+
|
|
26
|
+
8 integration tests cover the new behaviours (BLOCKED retry → honest halt,
|
|
27
|
+
BLOCKED retry → proceed on success, transient-error retry); full suite green
|
|
28
|
+
(1439).
|
|
29
|
+
|
|
30
|
+
## [0.91.0] - 2026-07-11
|
|
31
|
+
|
|
32
|
+
### Added — Alibaba Cloud Coding Plan, first-class and one-command
|
|
33
|
+
|
|
34
|
+
The Alibaba Coding Plan support (endpoints, live catalog, ping, wizard) already
|
|
35
|
+
existed but was only reachable through the interactive `/setup` wizard — a user
|
|
36
|
+
could not simply add the provider. Verified against the official docs
|
|
37
|
+
(https://www.alibabacloud.com/help/en/model-studio/coding-plan) and a live API
|
|
38
|
+
round-trip (catalog listing + a real `qwen3.7-plus` completion).
|
|
39
|
+
|
|
40
|
+
- **`/keys set` now bootstraps known providers.** `providers/provider-bootstrap.ts`
|
|
41
|
+
(pure, 11 tests) builds a complete first-time models.json entry — baseUrl, api
|
|
42
|
+
protocol, bundled model list — from a single API key, with each provider's own
|
|
43
|
+
key-format validation applied up front (`sk-sp-` prefix for Alibaba). One
|
|
44
|
+
command configures the provider end to end:
|
|
45
|
+
`/keys set alibaba-codingplan sk-sp-…` — then the session-start refresh
|
|
46
|
+
replaces the bundled list with the live catalog. Bootstrappable ids:
|
|
47
|
+
`alibaba-codingplan`, `alibaba-codingplan-anthropic`, `opencode-go`,
|
|
48
|
+
`opencode-go-anthropic`. Unknown providers keep the previous /setup guidance.
|
|
49
|
+
- **`/keys test` now actually works.** `registerProviderPing` existed but nothing
|
|
50
|
+
ever called it; keys.ts now registers the built-in pings (Alibaba, OpenCode Go)
|
|
51
|
+
at load, so `/keys test alibaba-codingplan` validates the key against the real
|
|
52
|
+
`/v1/models` endpoint.
|
|
53
|
+
- **Provider display names.** `alibaba-codingplan`, `alibaba-codingplan-anthropic`
|
|
54
|
+
and `opencode-go-anthropic` now render with proper names in the /model picker
|
|
55
|
+
(they fell back to raw ids).
|
|
56
|
+
|
|
3
57
|
## [0.90.0] - 2026-07-11
|
|
4
58
|
|
|
5
59
|
### Added — the execution sandbox (the guaranteed oracle)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-display-names.d.ts","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"provider-display-names.d.ts","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkClE,CAAC","sourcesContent":["export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n\t\"alibaba-codingplan\": \"Alibaba Coding Plan\",\n\t\"alibaba-codingplan-anthropic\": \"Alibaba Coding Plan (Anthropic-compat)\",\n\tanthropic: \"Anthropic\",\n\t\"amazon-bedrock\": \"Amazon Bedrock\",\n\t\"azure-openai-responses\": \"Azure OpenAI Responses\",\n\tcerebras: \"Cerebras\",\n\t\"cloudflare-ai-gateway\": \"Cloudflare AI Gateway\",\n\t\"cloudflare-workers-ai\": \"Cloudflare Workers AI\",\n\tdeepseek: \"DeepSeek\",\n\tfireworks: \"Fireworks\",\n\tgoogle: \"Google Gemini\",\n\t\"google-vertex\": \"Google Vertex AI\",\n\tgroq: \"Groq\",\n\thuggingface: \"Hugging Face\",\n\t\"kimi-coding\": \"Kimi For Coding\",\n\tmistral: \"Mistral\",\n\tminimax: \"MiniMax\",\n\t\"minimax-cn\": \"MiniMax (China)\",\n\tmoonshotai: \"Moonshot AI\",\n\t\"moonshotai-cn\": \"Moonshot AI (China)\",\n\topencode: \"OpenCode Zen\",\n\t\"opencode-go\": \"OpenCode Go\",\n\t\"opencode-go-anthropic\": \"OpenCode Go (Anthropic-compat)\",\n\topenai: \"OpenAI\",\n\topenrouter: \"OpenRouter\",\n\ttogether: \"Together AI\",\n\t\"vercel-ai-gateway\": \"Vercel AI Gateway\",\n\txai: \"xAI\",\n\tzai: \"ZAI\",\n\txiaomi: \"Xiaomi MiMo\",\n\t\"xiaomi-token-plan-cn\": \"Xiaomi MiMo Token Plan (China)\",\n\t\"xiaomi-token-plan-ams\": \"Xiaomi MiMo Token Plan (Amsterdam)\",\n\t\"xiaomi-token-plan-sgp\": \"Xiaomi MiMo Token Plan (Singapore)\",\n};\n"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export const BUILT_IN_PROVIDER_DISPLAY_NAMES = {
|
|
2
|
+
"alibaba-codingplan": "Alibaba Coding Plan",
|
|
3
|
+
"alibaba-codingplan-anthropic": "Alibaba Coding Plan (Anthropic-compat)",
|
|
2
4
|
anthropic: "Anthropic",
|
|
3
5
|
"amazon-bedrock": "Amazon Bedrock",
|
|
4
6
|
"azure-openai-responses": "Azure OpenAI Responses",
|
|
@@ -19,6 +21,7 @@ export const BUILT_IN_PROVIDER_DISPLAY_NAMES = {
|
|
|
19
21
|
"moonshotai-cn": "Moonshot AI (China)",
|
|
20
22
|
opencode: "OpenCode Zen",
|
|
21
23
|
"opencode-go": "OpenCode Go",
|
|
24
|
+
"opencode-go-anthropic": "OpenCode Go (Anthropic-compat)",
|
|
22
25
|
openai: "OpenAI",
|
|
23
26
|
openrouter: "OpenRouter",
|
|
24
27
|
together: "Together AI",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-display-names.js","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,+BAA+B,GAA2B;IACtE,SAAS,EAAE,WAAW;IACtB,gBAAgB,EAAE,gBAAgB;IAClC,wBAAwB,EAAE,wBAAwB;IAClD,QAAQ,EAAE,UAAU;IACpB,uBAAuB,EAAE,uBAAuB;IAChD,uBAAuB,EAAE,uBAAuB;IAChD,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,eAAe;IACvB,eAAe,EAAE,kBAAkB;IACnC,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,cAAc;IAC3B,aAAa,EAAE,iBAAiB;IAChC,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,aAAa;IACzB,eAAe,EAAE,qBAAqB;IACtC,QAAQ,EAAE,cAAc;IACxB,aAAa,EAAE,aAAa;IAC5B,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,aAAa;IACvB,mBAAmB,EAAE,mBAAmB;IACxC,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,aAAa;IACrB,sBAAsB,EAAE,gCAAgC;IACxD,uBAAuB,EAAE,oCAAoC;IAC7D,uBAAuB,EAAE,oCAAoC;CAC7D,CAAC","sourcesContent":["export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n\tanthropic: \"Anthropic\",\n\t\"amazon-bedrock\": \"Amazon Bedrock\",\n\t\"azure-openai-responses\": \"Azure OpenAI Responses\",\n\tcerebras: \"Cerebras\",\n\t\"cloudflare-ai-gateway\": \"Cloudflare AI Gateway\",\n\t\"cloudflare-workers-ai\": \"Cloudflare Workers AI\",\n\tdeepseek: \"DeepSeek\",\n\tfireworks: \"Fireworks\",\n\tgoogle: \"Google Gemini\",\n\t\"google-vertex\": \"Google Vertex AI\",\n\tgroq: \"Groq\",\n\thuggingface: \"Hugging Face\",\n\t\"kimi-coding\": \"Kimi For Coding\",\n\tmistral: \"Mistral\",\n\tminimax: \"MiniMax\",\n\t\"minimax-cn\": \"MiniMax (China)\",\n\tmoonshotai: \"Moonshot AI\",\n\t\"moonshotai-cn\": \"Moonshot AI (China)\",\n\topencode: \"OpenCode Zen\",\n\t\"opencode-go\": \"OpenCode Go\",\n\topenai: \"OpenAI\",\n\topenrouter: \"OpenRouter\",\n\ttogether: \"Together AI\",\n\t\"vercel-ai-gateway\": \"Vercel AI Gateway\",\n\txai: \"xAI\",\n\tzai: \"ZAI\",\n\txiaomi: \"Xiaomi MiMo\",\n\t\"xiaomi-token-plan-cn\": \"Xiaomi MiMo Token Plan (China)\",\n\t\"xiaomi-token-plan-ams\": \"Xiaomi MiMo Token Plan (Amsterdam)\",\n\t\"xiaomi-token-plan-sgp\": \"Xiaomi MiMo Token Plan (Singapore)\",\n};\n"]}
|
|
1
|
+
{"version":3,"file":"provider-display-names.js","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,+BAA+B,GAA2B;IACtE,oBAAoB,EAAE,qBAAqB;IAC3C,8BAA8B,EAAE,wCAAwC;IACxE,SAAS,EAAE,WAAW;IACtB,gBAAgB,EAAE,gBAAgB;IAClC,wBAAwB,EAAE,wBAAwB;IAClD,QAAQ,EAAE,UAAU;IACpB,uBAAuB,EAAE,uBAAuB;IAChD,uBAAuB,EAAE,uBAAuB;IAChD,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,eAAe;IACvB,eAAe,EAAE,kBAAkB;IACnC,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,cAAc;IAC3B,aAAa,EAAE,iBAAiB;IAChC,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,aAAa;IACzB,eAAe,EAAE,qBAAqB;IACtC,QAAQ,EAAE,cAAc;IACxB,aAAa,EAAE,aAAa;IAC5B,uBAAuB,EAAE,gCAAgC;IACzD,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,aAAa;IACvB,mBAAmB,EAAE,mBAAmB;IACxC,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,aAAa;IACrB,sBAAsB,EAAE,gCAAgC;IACxD,uBAAuB,EAAE,oCAAoC;IAC7D,uBAAuB,EAAE,oCAAoC;CAC7D,CAAC","sourcesContent":["export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n\t\"alibaba-codingplan\": \"Alibaba Coding Plan\",\n\t\"alibaba-codingplan-anthropic\": \"Alibaba Coding Plan (Anthropic-compat)\",\n\tanthropic: \"Anthropic\",\n\t\"amazon-bedrock\": \"Amazon Bedrock\",\n\t\"azure-openai-responses\": \"Azure OpenAI Responses\",\n\tcerebras: \"Cerebras\",\n\t\"cloudflare-ai-gateway\": \"Cloudflare AI Gateway\",\n\t\"cloudflare-workers-ai\": \"Cloudflare Workers AI\",\n\tdeepseek: \"DeepSeek\",\n\tfireworks: \"Fireworks\",\n\tgoogle: \"Google Gemini\",\n\t\"google-vertex\": \"Google Vertex AI\",\n\tgroq: \"Groq\",\n\thuggingface: \"Hugging Face\",\n\t\"kimi-coding\": \"Kimi For Coding\",\n\tmistral: \"Mistral\",\n\tminimax: \"MiniMax\",\n\t\"minimax-cn\": \"MiniMax (China)\",\n\tmoonshotai: \"Moonshot AI\",\n\t\"moonshotai-cn\": \"Moonshot AI (China)\",\n\topencode: \"OpenCode Zen\",\n\t\"opencode-go\": \"OpenCode Go\",\n\t\"opencode-go-anthropic\": \"OpenCode Go (Anthropic-compat)\",\n\topenai: \"OpenAI\",\n\topenrouter: \"OpenRouter\",\n\ttogether: \"Together AI\",\n\t\"vercel-ai-gateway\": \"Vercel AI Gateway\",\n\txai: \"xAI\",\n\tzai: \"ZAI\",\n\txiaomi: \"Xiaomi MiMo\",\n\t\"xiaomi-token-plan-cn\": \"Xiaomi MiMo Token Plan (China)\",\n\t\"xiaomi-token-plan-ams\": \"Xiaomi MiMo Token Plan (Amsterdam)\",\n\t\"xiaomi-token-plan-sgp\": \"Xiaomi MiMo Token Plan (Singapore)\",\n};\n"]}
|
package/extensions/phi/keys.ts
CHANGED
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { ApiKeyStore, type ExtensionAPI, getApiKeyStore, getConfigWatcher } from "phi-code";
|
|
24
|
+
import { pingAlibaba } from "./providers/alibaba.js";
|
|
25
|
+
import { pingOpenCodeGo } from "./providers/opencode-go.js";
|
|
26
|
+
import { bootstrapProviderConfig, isBootstrappableProvider } from "./providers/provider-bootstrap.js";
|
|
24
27
|
|
|
25
28
|
type ProviderPingFn = (key: string, timeoutMs?: number) => Promise<{ ok: boolean; error?: string }>;
|
|
26
29
|
|
|
@@ -38,6 +41,13 @@ export default function keysExtension(pi: ExtensionAPI) {
|
|
|
38
41
|
const store = getApiKeyStore();
|
|
39
42
|
const watcher = getConfigWatcher();
|
|
40
43
|
|
|
44
|
+
// Built-in pings so `/keys test <id>` works out of the box for the providers
|
|
45
|
+
// phi ships builders for (previously nothing ever registered a ping).
|
|
46
|
+
registerProviderPing("alibaba-codingplan", pingAlibaba);
|
|
47
|
+
registerProviderPing("alibaba-codingplan-anthropic", pingAlibaba);
|
|
48
|
+
registerProviderPing("opencode-go", pingOpenCodeGo);
|
|
49
|
+
registerProviderPing("opencode-go-anthropic", pingOpenCodeGo);
|
|
50
|
+
|
|
41
51
|
pi.registerCommand("keys", {
|
|
42
52
|
description: "Manage API keys (list / set / remove / test / reload)",
|
|
43
53
|
handler: async (args, ctx) => {
|
|
@@ -75,15 +85,35 @@ export default function keysExtension(pi: ExtensionAPI) {
|
|
|
75
85
|
return;
|
|
76
86
|
}
|
|
77
87
|
// A bare `set` only updates the key; it cannot supply baseUrl/api/models.
|
|
78
|
-
// For a never-configured id
|
|
79
|
-
//
|
|
88
|
+
// For a never-configured id: bootstrap the full entry when phi ships a
|
|
89
|
+
// builder for it (Alibaba Coding Plan, OpenCode Go); otherwise keep the
|
|
90
|
+
// old behaviour and point at /setup.
|
|
80
91
|
const existing = store.getProvider(id);
|
|
81
92
|
if (!existing?.baseUrl) {
|
|
93
|
+
if (!isBootstrappableProvider(id)) {
|
|
94
|
+
ctx.ui.notify(
|
|
95
|
+
`\`${id}\` is not a configured provider (no baseUrl on file). ` +
|
|
96
|
+
`Run \`/setup\` to add it with an endpoint and models first; ` +
|
|
97
|
+
`\`/keys set\` only updates the key of an already-configured provider.`,
|
|
98
|
+
"warning",
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const boot = bootstrapProviderConfig(id, key);
|
|
103
|
+
if (!boot.ok) {
|
|
104
|
+
ctx.ui.notify(`Cannot configure \`${id}\`: ${boot.error}`, "warning");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
watcher.muteForWrite("models_json_changed");
|
|
108
|
+
store.setKey(id, key, {
|
|
109
|
+
baseUrl: boot.config.baseUrl,
|
|
110
|
+
api: boot.config.api,
|
|
111
|
+
models: boot.config.models,
|
|
112
|
+
});
|
|
82
113
|
ctx.ui.notify(
|
|
83
|
-
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
"warning",
|
|
114
|
+
`Configured \`${id}\` (\`${ApiKeyStore.maskKey(key)}\`) — ${boot.note}\n` +
|
|
115
|
+
`Stored in ${store.configPath}. Use \`/keys test ${id}\` to validate, then pick a model with \`/model\`.`,
|
|
116
|
+
"info",
|
|
87
117
|
);
|
|
88
118
|
return;
|
|
89
119
|
}
|
|
@@ -284,6 +284,11 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
284
284
|
// Set true once this phase has been retried once (transient proxy failure /
|
|
285
285
|
// timeout / 0-tool-call). Hard cap of one retry per phase.
|
|
286
286
|
retried?: boolean;
|
|
287
|
+
// Generic driver (/debug, /build) only: set true once this phase was re-run
|
|
288
|
+
// after a BLOCKED verdict. One second chance on the fallback model — a hasty
|
|
289
|
+
// BLOCKED (measured: 51s on sympy-11897) is cheaper to re-check than to
|
|
290
|
+
// trust — then the halt is honest.
|
|
291
|
+
blockedRetried?: boolean;
|
|
287
292
|
// When true, switchModelForPhase resolves the fallback model first (used on retry
|
|
288
293
|
// to swap to a different model family rather than re-hit the one that just failed).
|
|
289
294
|
useFallback?: boolean;
|
|
@@ -1323,13 +1328,63 @@ Tag the note with relevant keywords for vector search.
|
|
|
1323
1328
|
phaseTimeoutId = null;
|
|
1324
1329
|
}
|
|
1325
1330
|
|
|
1326
|
-
//
|
|
1327
|
-
//
|
|
1331
|
+
// Route through the SAME pure decision machine as /plan so the generic
|
|
1332
|
+
// driver gets the protections it was measured to lack (sympy-11897, a 51s
|
|
1333
|
+
// hasty halt): fatal 401 stop, one retry on a transient provider error.
|
|
1328
1334
|
const structuredForThisPhase = currentPhaseResultKey === (currentPhase?.key ?? null) ? currentPhaseResult : null;
|
|
1329
1335
|
const outcome = resolvePhaseOutcome(structuredForThisPhase, null);
|
|
1330
|
-
|
|
1336
|
+
const decision = decidePhaseTransition({
|
|
1337
|
+
analysis,
|
|
1338
|
+
phase: currentPhase ? { key: currentPhase.key, retried: currentPhase.retried } : null,
|
|
1339
|
+
verdict: outcome.verdict,
|
|
1340
|
+
// The generic driver has no review-fix cycle; pass the cap as spent.
|
|
1341
|
+
reviewFixRounds: 1,
|
|
1342
|
+
maxToolCallsPerPhase: MAX_TOOL_CALLS_PER_PHASE,
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
if (decision.action === "stop") {
|
|
1346
|
+
ctx.ui.notify(
|
|
1347
|
+
`\n❌ **/${orchestrationMode} aborted:** API authentication error (401). Check your API key.`,
|
|
1348
|
+
"error",
|
|
1349
|
+
);
|
|
1350
|
+
finishGenericOrchestration(ctx, `❌ **/${orchestrationMode} aborted (auth error).**`);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if (decision.action === "retry-fallback" && currentPhase) {
|
|
1355
|
+
// Transient provider failure (timeout / 5xx / 429 / broken stream): retry
|
|
1356
|
+
// the SAME phase once on the fallback model before treating any of its
|
|
1357
|
+
// output as a real outcome.
|
|
1358
|
+
currentPhase.retried = true;
|
|
1359
|
+
currentPhase.useFallback = true;
|
|
1360
|
+
phaseQueue.unshift(currentPhase);
|
|
1361
|
+
phasePending = false;
|
|
1362
|
+
ctx.ui.notify(
|
|
1363
|
+
`\n🔁 **Transient provider error** in ${currentPhase.label} — retrying once on the fallback model.`,
|
|
1364
|
+
"warning",
|
|
1365
|
+
);
|
|
1366
|
+
sendNextGenericPhase(ctx);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// BLOCKED (REPRODUCE cannot reproduce / VERIFY could not confirm): give the
|
|
1371
|
+
// phase ONE second chance on the fallback model — a hasty BLOCKED is cheaper
|
|
1372
|
+
// to re-check than to trust — then halt honestly. No fabricated success.
|
|
1373
|
+
if (decision.action === "pause-blocked" && currentPhase) {
|
|
1374
|
+
if (!currentPhase.blockedRetried) {
|
|
1375
|
+
currentPhase.blockedRetried = true;
|
|
1376
|
+
currentPhase.useFallback = true;
|
|
1377
|
+
phaseQueue.unshift(currentPhase);
|
|
1378
|
+
phasePending = false;
|
|
1379
|
+
ctx.ui.notify(
|
|
1380
|
+
`\n🔁 **${currentPhase.label} reported BLOCKED on the first attempt** — retrying once on the fallback model before halting. ${outcome.handoff || ""}`,
|
|
1381
|
+
"warning",
|
|
1382
|
+
);
|
|
1383
|
+
sendNextGenericPhase(ctx);
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1331
1386
|
ctx.ui.notify(
|
|
1332
|
-
`\n⏸️ **${currentPhase.label} reported BLOCKED.** ${outcome.handoff || "No reproducible/verifiable result."}`,
|
|
1387
|
+
`\n⏸️ **${currentPhase.label} reported BLOCKED (confirmed on retry).** ${outcome.handoff || "No reproducible/verifiable result."}`,
|
|
1333
1388
|
"warning",
|
|
1334
1389
|
);
|
|
1335
1390
|
finishGenericOrchestration(ctx, `⏸️ **/${orchestrationMode} stopped: BLOCKED at ${currentPhase.label}.**`);
|
|
@@ -59,7 +59,8 @@ ${failing}
|
|
|
59
59
|
- If it PASSES → STOP. Write \`BLOCKED: cannot reproduce — passes on current code\` with the run pasted. Do not invent a bug.
|
|
60
60
|
- If \`sandbox_run\` reports \`SANDBOX UNAVAILABLE\` → STOP. Write \`BLOCKED: no executable environment\`.
|
|
61
61
|
4. Do NOT edit any source yet. This phase only observes.
|
|
62
|
-
5. **
|
|
62
|
+
5. **BLOCKED is a last resort, not a first reaction.** If a run fails for an infrastructure-looking reason (tool error, missing file you guessed wrong, transient failure), adjust and retry at least once — e.g. locate the real test paths, try an alternative reproduction — before concluding. Only report BLOCKED after a genuine attempt showed the state is not reproducible or not runnable.
|
|
63
|
+
6. **Last action:** call \`phase_result\` — \`verdict: PASS\` if it reproduced (proceed to LOCALIZE), or \`verdict: BLOCKED\` with the reason if you stopped.` +
|
|
63
64
|
DEBUG_RULES,
|
|
64
65
|
|
|
65
66
|
localize: `You are the LOCALIZE agent (phase 2 of /debug). Drive from the REAL symptom the previous phase captured.
|
|
@@ -96,6 +97,7 @@ ${failing}
|
|
|
96
97
|
3. Verdict (from what \`sandbox_run\` returned, not from inspection):
|
|
97
98
|
- Both green → \`FIXED\`, and paste the before(fail)/after(pass) reproduction runs and the green suite as evidence.
|
|
98
99
|
- Reproduction still fails, or the suite regresses → \`BLOCKED\` with the closest diagnostic. Do NOT ship the least-bad patch; a wrong fix is worse than an honest BLOCKED.
|
|
100
|
+
- **A TIMEOUT counts as a FAILURE.** If the reproduction or the suite TIMES OUT with the fix applied (\`sandbox_run\` verdict TIMEOUT), the fix has introduced a massive slowdown or an infinite loop — that IS a regression (measured: a fix that passed its repro but made the real test suite hang for 900s). Verdict \`BLOCKED\`, never \`FIXED\`. Compare rough durations before/after: a check that got dramatically slower is a red flag even below the timeout.
|
|
99
101
|
4. Write the final verdict block, then call \`phase_result\` with \`verdict: PASS\` (FIXED) or \`verdict: BLOCKED\`, plus a one-line handoff:
|
|
100
102
|
\`\`\`
|
|
101
103
|
VERDICT: FIXED | BLOCKED
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider bootstrap — first-time, non-interactive provider configuration.
|
|
3
|
+
*
|
|
4
|
+
* `/keys set <id> <key>` used to refuse a provider that had no models.json
|
|
5
|
+
* entry yet ("run /setup first"), which made adding a known provider (e.g. the
|
|
6
|
+
* Alibaba Coding Plan) impossible outside the interactive wizard. This module
|
|
7
|
+
* closes that gap: for providers phi ships a config builder for, it produces a
|
|
8
|
+
* complete models.json entry (baseUrl + api + model list) from a single API
|
|
9
|
+
* key, with the provider's own key-format validation applied first.
|
|
10
|
+
*
|
|
11
|
+
* Pure (no fs, no network) so every branch is unit-tested; keys.ts is the only
|
|
12
|
+
* call site.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { ALIBABA_PROVIDERS, buildAlibabaProviderConfig, validateAlibabaApiKey } from "./alibaba.js";
|
|
16
|
+
import {
|
|
17
|
+
buildOpenCodeGoAnthropicProviderConfig,
|
|
18
|
+
buildOpenCodeGoProviderConfig,
|
|
19
|
+
OPENCODE_GO_FALLBACK_MODELS,
|
|
20
|
+
type OpenCodeGoModel,
|
|
21
|
+
validateOpenCodeGoApiKey,
|
|
22
|
+
} from "./opencode-go.js";
|
|
23
|
+
|
|
24
|
+
export interface BootstrapModel {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
reasoning: boolean;
|
|
28
|
+
input: readonly ("text" | "image")[];
|
|
29
|
+
contextWindow: number;
|
|
30
|
+
maxTokens: number;
|
|
31
|
+
compat?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BootstrapConfig {
|
|
35
|
+
baseUrl: string;
|
|
36
|
+
api: string;
|
|
37
|
+
apiKey: string;
|
|
38
|
+
models: BootstrapModel[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type BootstrapResult = { ok: true; config: BootstrapConfig; note: string } | { ok: false; error: string };
|
|
42
|
+
|
|
43
|
+
/** Provider ids `/keys set` can fully configure from just an API key. */
|
|
44
|
+
export const BOOTSTRAPPABLE_PROVIDERS = [
|
|
45
|
+
"alibaba-codingplan",
|
|
46
|
+
"alibaba-codingplan-anthropic",
|
|
47
|
+
"opencode-go",
|
|
48
|
+
"opencode-go-anthropic",
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
export function isBootstrappableProvider(id: string): boolean {
|
|
52
|
+
return (BOOTSTRAPPABLE_PROVIDERS as readonly string[]).includes(id);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a complete first-time provider config from an API key. Validates the
|
|
57
|
+
* key FORMAT (prefix/length) per provider; liveness is `/keys test`'s job.
|
|
58
|
+
* The bundled model list seeds the entry — the session-start refresh and
|
|
59
|
+
* `/models refresh` replace it with the live catalog afterwards.
|
|
60
|
+
*/
|
|
61
|
+
export function bootstrapProviderConfig(id: string, apiKey: string): BootstrapResult {
|
|
62
|
+
const key = apiKey.trim();
|
|
63
|
+
|
|
64
|
+
switch (id) {
|
|
65
|
+
case "alibaba-codingplan":
|
|
66
|
+
case "alibaba-codingplan-anthropic": {
|
|
67
|
+
const invalid = validateAlibabaApiKey(key);
|
|
68
|
+
if (invalid) return { ok: false, error: invalid };
|
|
69
|
+
const variant = id === "alibaba-codingplan-anthropic" ? "anthropic" : "openai";
|
|
70
|
+
const config: BootstrapConfig = buildAlibabaProviderConfig(variant, key);
|
|
71
|
+
return {
|
|
72
|
+
ok: true,
|
|
73
|
+
config,
|
|
74
|
+
note: `${ALIBABA_PROVIDERS[variant].displayName} — ${config.models.length} bundled model(s); a live refresh follows on next session start (or run /models refresh ${id}).`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
case "opencode-go":
|
|
78
|
+
case "opencode-go-anthropic": {
|
|
79
|
+
const invalid = validateOpenCodeGoApiKey(key);
|
|
80
|
+
if (invalid) return { ok: false, error: invalid };
|
|
81
|
+
const models: OpenCodeGoModel[] = [...OPENCODE_GO_FALLBACK_MODELS];
|
|
82
|
+
const config: BootstrapConfig =
|
|
83
|
+
id === "opencode-go-anthropic"
|
|
84
|
+
? buildOpenCodeGoAnthropicProviderConfig(key, models)
|
|
85
|
+
: buildOpenCodeGoProviderConfig(key, models);
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
config,
|
|
89
|
+
note: `OpenCode Go — ${config.models.length} bundled model(s); a live refresh follows on next session start (or run /models refresh ${id}).`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: `\`${id}\` has no bootstrap builder. Run \`/setup\` to configure it interactively, or add it to ~/.phi/agent/models.json. Bootstrappable ids: ${BOOTSTRAPPABLE_PROVIDERS.join(", ")}.`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|