@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.
@@ -27,7 +27,7 @@ import {
27
27
  import { ALIBABA_MODELS, ALIBABA_PROVIDERS, pingAlibaba } from "./alibaba.js";
28
28
  import { inferContextWindow } from "./context-window.js";
29
29
 
30
- export const LAST_VERIFIED = "2026-05-15";
30
+ export const LAST_VERIFIED = "2026-07-10";
31
31
 
32
32
  export interface LiveModel {
33
33
  id: string;
@@ -203,8 +203,27 @@ const STATIC_OPENROUTER: LiveModel[] = [
203
203
  { id: "minimax/MiniMax-M2.7", name: "MiniMax M2.7", reasoning: true },
204
204
  ];
205
205
 
206
+ // OpenCode Zen (https://opencode.ai/zen) — the non-Go catalog. The models
207
+ // endpoint is reachable without a key (like OpenRouter); inference needs one.
208
+ const STATIC_OPENCODE_ZEN: LiveModel[] = [
209
+ { id: "claude-fable-5", name: "Claude Fable 5", contextWindow: 200_000, reasoning: true },
210
+ { id: "claude-opus-4-8", name: "Claude Opus 4.8", contextWindow: 200_000, reasoning: true },
211
+ { id: "claude-sonnet-5", name: "Claude Sonnet 5", contextWindow: 200_000, reasoning: true },
212
+ { id: "claude-haiku-4-5", name: "Claude Haiku 4.5", contextWindow: 200_000, reasoning: true },
213
+ { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash", contextWindow: 1_000_000, reasoning: true },
214
+ { id: "gemini-3.1-pro", name: "Gemini 3.1 Pro", contextWindow: 1_000_000, reasoning: true },
215
+ { id: "gpt-5.5", name: "GPT-5.5", contextWindow: 400_000, reasoning: true },
216
+ { id: "gpt-5.4", name: "GPT-5.4", contextWindow: 400_000, reasoning: true },
217
+ { id: "glm-5.2", name: "GLM 5.2", contextWindow: 200_000, reasoning: true },
218
+ { id: "glm-5.1", name: "GLM 5.1", contextWindow: 200_000, reasoning: true },
219
+ { id: "kimi-k2.6", name: "Kimi K2.6", contextWindow: 256_000, reasoning: true },
220
+ { id: "minimax-m2.7", name: "MiniMax M2.7", contextWindow: 1_000_000, reasoning: true },
221
+ ];
222
+
206
223
  function staticFallbackFor(providerId: string): LiveModel[] {
207
224
  switch (providerId) {
225
+ case "opencode":
226
+ return STATIC_OPENCODE_ZEN;
208
227
  case "opencode-go":
209
228
  return OPENCODE_GO_FALLBACK_MODELS.map((m) => ({
210
229
  id: m.id,
@@ -274,6 +293,15 @@ async function fetchOpenRouter(apiKey: string | undefined, timeoutMs: number): P
274
293
  return mapOpenAIModels(raw);
275
294
  }
276
295
 
296
+ async function fetchOpenCodeZen(apiKey: string | undefined, timeoutMs: number): Promise<LiveModel[]> {
297
+ const headers: Record<string, string> = { Accept: "application/json" };
298
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
299
+ const raw = (await fetchJson("https://opencode.ai/zen/v1/models", headers, timeoutMs)) as OpenAIModelsResponse;
300
+ // The Zen endpoint reports neither context windows nor reasoning support;
301
+ // frontier catalog models all expose reasoning, so default to true.
302
+ return mapOpenAIModels(raw).map((m) => ({ ...m, reasoning: true }));
303
+ }
304
+
277
305
  async function fetchGroq(apiKey: string, timeoutMs: number): Promise<LiveModel[]> {
278
306
  const raw = (await fetchJson(
279
307
  "https://api.groq.com/openai/v1/models",
@@ -351,6 +379,8 @@ async function dispatchFetch(providerId: string, options: FetchOptions): Promise
351
379
  return await fetchGoogle(apiKey, timeoutMs);
352
380
  case "openrouter":
353
381
  return await fetchOpenRouter(apiKey, timeoutMs);
382
+ case "opencode":
383
+ return await fetchOpenCodeZen(apiKey, timeoutMs);
354
384
  case "groq":
355
385
  if (!apiKey) throw new Error("Groq requires an API key for live listing");
356
386
  return await fetchGroq(apiKey, timeoutMs);
@@ -49,27 +49,31 @@ interface OpenCodeGoModelsResponse {
49
49
 
50
50
  /**
51
51
  * Fallback static list of OpenCode Go models.
52
- * Last verified: 2026-05-15 (15 models, sourced from the live /v1/models endpoint).
52
+ * Last verified: 2026-07-10 (20 models, sourced from the live /v1/models endpoint).
53
53
  * Used when network unreachable or auth fails before configuration.
54
54
  *
55
55
  * Refresh with: `curl -s https://opencode.ai/zen/go/v1/models | jq '.data[].id'`
56
56
  */
57
57
  export const OPENCODE_GO_FALLBACK_MODELS: readonly OpenCodeGoModel[] = [
58
+ { id: "minimax-m3", name: "MiniMax M3", contextWindow: 1_000_000, maxTokens: 16_384 },
58
59
  { id: "minimax-m2.7", name: "MiniMax M2.7", contextWindow: 1_000_000, maxTokens: 16_384 },
59
60
  { id: "minimax-m2.5", name: "MiniMax M2.5", contextWindow: 1_000_000, maxTokens: 16_384 },
61
+ { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", contextWindow: 256_000, maxTokens: 16_384 },
60
62
  { id: "kimi-k2.6", name: "Kimi K2.6", contextWindow: 256_000, maxTokens: 16_384 },
61
63
  { id: "kimi-k2.5", name: "Kimi K2.5", contextWindow: 256_000, maxTokens: 16_384 },
64
+ { id: "glm-5.2", name: "GLM 5.2", contextWindow: 200_000, maxTokens: 128_000 },
62
65
  { id: "glm-5.1", name: "GLM 5.1", contextWindow: 200_000, maxTokens: 128_000 },
63
66
  { id: "glm-5", name: "GLM 5", contextWindow: 200_000, maxTokens: 128_000 },
64
67
  { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", contextWindow: 128_000, maxTokens: 8_192 },
65
68
  { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", contextWindow: 128_000, maxTokens: 8_192 },
69
+ { id: "qwen3.7-max", name: "Qwen 3.7 Max", contextWindow: 1_000_000, maxTokens: 16_384 },
66
70
  { id: "qwen3.7-plus", name: "Qwen 3.7 Plus", contextWindow: 1_000_000, maxTokens: 16_384 },
67
71
  { id: "qwen3.6-plus", name: "Qwen 3.6 Plus", contextWindow: 1_000_000, maxTokens: 16_384 },
68
72
  { id: "qwen3.5-plus", name: "Qwen 3.5 Plus", contextWindow: 1_000_000, maxTokens: 16_384 },
69
- { id: "mimo-v2-pro", name: "MiMo V2 Pro", contextWindow: 200_000, maxTokens: 16_384 },
70
- { id: "mimo-v2-omni", name: "MiMo V2 Omni", contextWindow: 200_000, maxTokens: 16_384 },
71
73
  { id: "mimo-v2.5-pro", name: "MiMo V2.5 Pro", contextWindow: 200_000, maxTokens: 16_384 },
72
74
  { id: "mimo-v2.5", name: "MiMo V2.5", contextWindow: 200_000, maxTokens: 16_384 },
75
+ { id: "mimo-v2-pro", name: "MiMo V2 Pro", contextWindow: 200_000, maxTokens: 16_384 },
76
+ { id: "mimo-v2-omni", name: "MiMo V2 Omni", contextWindow: 200_000, maxTokens: 16_384 },
73
77
  { id: "hy3-preview", name: "Hy3 Preview", contextWindow: 128_000, maxTokens: 16_384 },
74
78
  ] as const;
75
79
 
@@ -109,6 +109,15 @@ function getProviderCatalog(): ProviderEntry[] {
109
109
  staticModels: [],
110
110
  docUrl: OPENCODE_GO_AUTH_URL,
111
111
  },
112
+ {
113
+ id: "opencode",
114
+ displayName: "OpenCode Zen",
115
+ envVar: "OPENCODE_API_KEY",
116
+ baseUrl: "https://opencode.ai/zen/v1",
117
+ api: "openai-completions",
118
+ staticModels: [],
119
+ docUrl: OPENCODE_GO_AUTH_URL,
120
+ },
112
121
  {
113
122
  id: "openai",
114
123
  displayName: "OpenAI",
@@ -3,8 +3,10 @@
3
3
  *
4
4
  * Uses sigma-skills SkillScanner and SkillLoader for skill discovery and matching.
5
5
  * Skills are folders containing SKILL.md files with specialized knowledge.
6
- * When skill-related keywords are detected in user input, the model is notified
7
- * and can load the full skill content via the `read` tool.
6
+ * When skill-related keywords are detected in user input, a compact skill hint
7
+ * (name, description, SKILL.md path) is appended to the message so the model
8
+ * actually SEES it and can load the full content via the `read` tool. A UI
9
+ * notification alone never reaches the model.
8
10
  *
9
11
  * Discovery locations (in priority order):
10
12
  * 1. .phi/skills/ (project-local, highest priority)
@@ -13,52 +15,78 @@
13
15
  *
14
16
  * Features:
15
17
  * - Automatic skill discovery at startup
16
- * - Keyword-based skill detection and loading
18
+ * - Keyword-based skill detection with an injection threshold
19
+ * - Auto-injection of skill hints into the model context (autoInject)
17
20
  * - /skills command to list available skills
18
- * - Contextual skill notification via ui.notify
19
21
  */
20
22
 
21
- import type { ExtensionAPI, ExtensionContext } from "phi-code";
23
+ import type { ExtensionAPI } from "phi-code";
22
24
  import { SkillScanner, SkillLoader } from "sigma-skills";
23
25
  import type { SkillsConfig } from "sigma-skills";
26
+ import { existsSync } from "node:fs";
24
27
  import { join } from "node:path";
25
28
  import { homedir } from "node:os";
26
29
 
27
30
  export default function skillLoaderExtension(pi: ExtensionAPI) {
31
+ // Bundled skills live inside the package in the repo layout; postinstall
32
+ // copies them to ~/.phi/agent/skills (== globalDir) in the installed layout,
33
+ // where the relative hop from ~/.phi/agent/extensions/ would point at the
34
+ // non-existent ~/skills. Probe both (the scanner dedupes by skill name).
35
+ const bundledCandidates = [
36
+ join(__dirname, "..", "..", "..", "skills"),
37
+ join(homedir(), ".phi", "agent", "skills"),
38
+ ];
28
39
  const config: SkillsConfig = {
29
40
  globalDir: join(homedir(), ".phi", "agent", "skills"),
30
41
  projectDir: join(process.cwd(), ".phi", "skills"),
31
- bundledDir: join(__dirname, "..", "..", "..", "skills"),
42
+ bundledDir: bundledCandidates.find((dir) => existsSync(dir)) ?? bundledCandidates[0],
32
43
  autoInject: true,
33
44
  };
34
45
 
35
46
  const scanner = new SkillScanner(config);
36
47
  const loader = new SkillLoader(scanner);
37
48
 
49
+ // Skills already hinted this session — hint each skill once, not on every
50
+ // message of a long conversation about the same topic.
51
+ const hintedSkills = new Set<string>();
52
+
38
53
  // ─── Input Event: Match skills to user input ─────────────────────
39
54
 
40
55
  pi.on("input", async (event, ctx) => {
41
56
  if (event.source === "extension") {
42
57
  return { action: "continue" };
43
58
  }
59
+ // Slash commands and skill blocks are not prose — nothing to match.
60
+ if (event.text.trimStart().startsWith("/")) {
61
+ return { action: "continue" };
62
+ }
44
63
 
45
64
  const matches = loader.findRelevantSkills(event.text);
65
+ // findRelevantSkills scores any shared word; require a clear signal
66
+ // (name match or several keywords) before surfacing a skill.
67
+ const strong = matches.filter((m) => m.score >= 3 && !hintedSkills.has(m.skill.name)).slice(0, 2);
68
+ if (strong.length === 0) {
69
+ return { action: "continue" };
70
+ }
71
+ for (const match of strong) {
72
+ hintedSkills.add(match.skill.name);
73
+ }
46
74
 
47
- if (matches.length > 0) {
48
- // Notify about top matches (max 3)
49
- const topMatches = matches.slice(0, 3);
50
- for (const match of topMatches) {
51
- ctx.ui.notify(
52
- `📚 Relevant skill: **${match.skill.name}** — ${match.skill.description}\nUse \`read ${match.skill.path}/SKILL.md\` for full content.`,
53
- "info"
54
- );
55
- }
75
+ const names = strong.map((m) => m.skill.name).join(", ");
76
+ ctx.ui.notify(`📚 Skill hint: ${names} (injected into context)`, "info");
56
77
 
57
- const skillNames = topMatches.map(m => m.skill.name).join(", ");
58
- ctx.ui.notify(`🧠 Loaded ${topMatches.length} skill(s): ${skillNames}`, "info");
78
+ if (!config.autoInject) {
79
+ return { action: "continue" };
59
80
  }
60
81
 
61
- return { action: "continue" };
82
+ const hints = strong
83
+ .map((m) => `- ${m.skill.name}: ${m.skill.description} — full content: read ${join(m.skill.path, "SKILL.md")}`)
84
+ .join("\n");
85
+ return {
86
+ action: "transform",
87
+ text: `${event.text}\n\n[Skill hints — relevant local skills detected for this request. Read the SKILL.md before applying:\n${hints}]`,
88
+ images: event.images,
89
+ };
62
90
  });
63
91
 
64
92
  // ─── /skills Command ─────────────────────────────────────────────
@@ -108,11 +136,4 @@ export default function skillLoaderExtension(pi: ExtensionAPI) {
108
136
  }
109
137
  },
110
138
  });
111
-
112
- // ─── Session Start: Load skills ──────────────────────────────────
113
-
114
- pi.on("session_start", async (_event, _ctx) => {
115
- const skills = loader.listSkills();
116
- console.log(`[skill-loader] Loaded ${skills.length} skills from 3 locations`);
117
- });
118
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.84.1",
3
+ "version": "0.85.0",
4
4
  "description": "Coding agent CLI with persistent memory, sub-agents, intelligent routing, and orchestration",
5
5
  "type": "module",
6
6
  "piConfig": {