omni-pi 0.8.1 → 0.8.3
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 +35 -0
- package/PROVIDERS.md +3 -1
- package/README.md +4 -3
- package/bin/omni.js +57 -1
- package/extensions/omni-providers/index.ts +2 -2
- package/package.json +10 -5
- package/src/brain.ts +1 -1
- package/src/config.ts +4 -36
- package/src/contracts.ts +6 -26
- package/src/doctor.ts +6 -9
- package/src/model-command.ts +32 -4
- package/src/model-refresh-state.ts +39 -0
- package/src/model-setup.ts +83 -42
- package/src/planning.ts +1 -8
- package/src/status.ts +0 -44
- package/src/tasks.ts +0 -1
- package/src/work.ts +28 -87
- package/src/workflow.ts +3 -3
- package/src/subagents.ts +0 -1262
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.3 - 2026-04-17
|
|
4
|
+
|
|
5
|
+
### Model refresh flow
|
|
6
|
+
|
|
7
|
+
- added a custom model refresh flow so newly released provider models can be picked up without repeating setup from scratch
|
|
8
|
+
- stored refresh state separately so daily checks can detect when a refresh is needed and avoid redundant prompts
|
|
9
|
+
- added coverage for the refresh command, state handling, and daily refresh behavior
|
|
10
|
+
|
|
11
|
+
## 0.8.2 - 2026-04-07
|
|
12
|
+
|
|
13
|
+
### Single-brain runtime
|
|
14
|
+
|
|
15
|
+
- removed the legacy multi-agent execution path and deleted the dormant subagent runtime
|
|
16
|
+
- simplified task execution to a single-brain retry flow with recovery notes instead of worker/expert escalation
|
|
17
|
+
- removed outdated worker, expert, and planner role/config concepts so runtime, config, and tests now match the current brain-only architecture
|
|
18
|
+
|
|
19
|
+
### Startup
|
|
20
|
+
|
|
21
|
+
- defaulted first-run `omni` launcher installs to quiet startup so package resource listings do not appear unless the user opts in
|
|
22
|
+
|
|
23
|
+
### Model setup and config
|
|
24
|
+
|
|
25
|
+
- simplified Omni model configuration to a single `brain` model assignment
|
|
26
|
+
- stopped offering automatic model discovery for `google-generative-ai`, which was not implemented
|
|
27
|
+
- stopped persisting fake placeholder API keys for local or unauthenticated custom providers
|
|
28
|
+
|
|
29
|
+
### Dependencies
|
|
30
|
+
|
|
31
|
+
- removed the unused `pi-subagents` dependency
|
|
32
|
+
- added npm overrides for `@mozilla/readability`, `brace-expansion`, `picomatch`, and `vite`
|
|
33
|
+
|
|
34
|
+
## 0.8.1 - 2026-04-07
|
|
35
|
+
|
|
36
|
+
- blocked Anthropic oAuth login to avoid bans from recent policy changes on the Claude Code subscription ToS
|
|
37
|
+
|
|
3
38
|
## 0.8.0 - 2026-04-06
|
|
4
39
|
|
|
5
40
|
### Runtime and UX
|
package/PROVIDERS.md
CHANGED
|
@@ -62,7 +62,9 @@ The bundled-provider list below is expected to stay in sync with the exported pr
|
|
|
62
62
|
|
|
63
63
|
For custom providers that expose a compatible model listing endpoint, Omni-Pi can fetch models for you after you add the provider details and credentials.
|
|
64
64
|
|
|
65
|
-
On launch, Omni-Pi
|
|
65
|
+
On launch, Omni-Pi refreshes authenticated, discoverable custom providers that are already configured in `models.json` at most once per day.
|
|
66
|
+
|
|
67
|
+
You can also run `/model-setup refresh` to re-discover those custom provider models on demand.
|
|
66
68
|
|
|
67
69
|
## When To Use Which Path
|
|
68
70
|
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ cd your-project
|
|
|
29
29
|
omni
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Custom provider setup and bundled provider behavior are documented in [PROVIDERS.md](PROVIDERS.md).
|
|
32
|
+
Custom provider setup, refresh behavior, and bundled provider behavior are documented in [PROVIDERS.md](PROVIDERS.md).
|
|
33
33
|
|
|
34
34
|
## Features
|
|
35
35
|
|
|
@@ -66,7 +66,7 @@ Omni-Pi now bundles [Glimpse](https://github.com/HazAT/glimpse) for native micro
|
|
|
66
66
|
|
|
67
67
|
| Command | Description |
|
|
68
68
|
|---------|-------------|
|
|
69
|
-
| `/model-setup` | Add
|
|
69
|
+
| `/model-setup` | Add, refresh, or remove custom provider/model entries |
|
|
70
70
|
| `/manage-providers` | Remove stored auth for bundled providers |
|
|
71
71
|
| `/omni-mode` | Toggle persistent Omni mode on or off for this project |
|
|
72
72
|
| `/companion` | Toggle the Glimpse floating companion widget |
|
|
@@ -87,12 +87,13 @@ Omni-Pi checks for new versions on startup (cached, re-checks every 4 hours). Wh
|
|
|
87
87
|
|
|
88
88
|
`/model-setup` is for custom providers and custom model entries only.
|
|
89
89
|
|
|
90
|
-
Use
|
|
90
|
+
Use `/model-setup` when you want to configure:
|
|
91
91
|
|
|
92
92
|
- a custom provider id
|
|
93
93
|
- an API type and base URL
|
|
94
94
|
- an API key for that custom provider
|
|
95
95
|
- discovered models or manual model entries
|
|
96
|
+
- a manual refresh of already configured custom providers
|
|
96
97
|
|
|
97
98
|
Use `/manage-providers` to remove stored auth for bundled Pi providers.
|
|
98
99
|
|
package/bin/omni.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { realpathSync } from "node:fs";
|
|
4
|
+
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
@@ -26,6 +27,60 @@ export function buildOmniEnvironment(baseEnv = process.env) {
|
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
function resolveAgentDir(baseEnv = process.env) {
|
|
31
|
+
const envDir = baseEnv.PI_CODING_AGENT_DIR;
|
|
32
|
+
if (!envDir) {
|
|
33
|
+
return path.join(os.homedir(), ".pi", "agent");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (envDir === "~") {
|
|
37
|
+
return os.homedir();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (envDir.startsWith("~/")) {
|
|
41
|
+
return path.join(os.homedir(), envDir.slice(2));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return envDir;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ensureQuietStartupDefault(baseEnv = process.env) {
|
|
48
|
+
const agentDir = resolveAgentDir(baseEnv);
|
|
49
|
+
const settingsFile = path.join(agentDir, "settings.json");
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const raw = readFileSync(settingsFile, "utf8");
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
if (
|
|
55
|
+
parsed &&
|
|
56
|
+
typeof parsed === "object" &&
|
|
57
|
+
parsed.quietStartup === undefined
|
|
58
|
+
) {
|
|
59
|
+
writeFileSync(
|
|
60
|
+
settingsFile,
|
|
61
|
+
`${JSON.stringify({ ...parsed, quietStartup: true }, null, 2)}\n`,
|
|
62
|
+
"utf8",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const code =
|
|
67
|
+
error && typeof error === "object" && "code" in error
|
|
68
|
+
? error.code
|
|
69
|
+
: undefined;
|
|
70
|
+
|
|
71
|
+
if (code !== "ENOENT") {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
mkdirSync(agentDir, { recursive: true });
|
|
76
|
+
writeFileSync(
|
|
77
|
+
settingsFile,
|
|
78
|
+
`${JSON.stringify({ quietStartup: true }, null, 2)}\n`,
|
|
79
|
+
"utf8",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
29
84
|
export function buildPiProcessSpec(
|
|
30
85
|
argv = process.argv.slice(2),
|
|
31
86
|
baseEnv = process.env,
|
|
@@ -38,6 +93,7 @@ export function buildPiProcessSpec(
|
|
|
38
93
|
}
|
|
39
94
|
|
|
40
95
|
export async function runOmni(argv = process.argv.slice(2), options = {}) {
|
|
96
|
+
ensureQuietStartupDefault(options.env);
|
|
41
97
|
const spec = buildPiProcessSpec(argv, options.env);
|
|
42
98
|
|
|
43
99
|
await new Promise((resolve, reject) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import { disableAnthropicOAuth } from "../../src/anthropic-auth-guard.js";
|
|
4
|
-
import {
|
|
4
|
+
import { refreshAuthenticatedProviderModelsWithDailyGuard } from "../../src/model-setup.js";
|
|
5
5
|
import { registerOmniProviders } from "../../src/providers.js";
|
|
6
6
|
|
|
7
7
|
export default async function omniProvidersExtension(
|
|
@@ -11,6 +11,6 @@ export default async function omniProvidersExtension(
|
|
|
11
11
|
|
|
12
12
|
api.on("session_start", async (_event, ctx) => {
|
|
13
13
|
disableAnthropicOAuth(ctx.modelRegistry);
|
|
14
|
-
await
|
|
14
|
+
await refreshAuthenticatedProviderModelsWithDailyGuard(ctx.modelRegistry);
|
|
15
15
|
});
|
|
16
16
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-pi",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Single-agent Pi package that interviews the user, documents the spec, and implements work in bounded slices.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Eduard-David Gyarmati",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/
|
|
10
|
+
"url": "https://github.com/edgyarmati/Omni-Pi"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://github.com/
|
|
12
|
+
"homepage": "https://github.com/edgyarmati/Omni-Pi#readme",
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/edgyarmati/Omni-Pi/issues"
|
|
15
15
|
},
|
|
16
16
|
"engines": {
|
|
17
17
|
"node": ">=22"
|
|
@@ -83,8 +83,13 @@
|
|
|
83
83
|
"@mariozechner/pi-coding-agent": "^0.65.2",
|
|
84
84
|
"glimpseui": "^0.6.2",
|
|
85
85
|
"pi-interview": "^0.5.5",
|
|
86
|
-
"pi-subagents": "^0.11.11",
|
|
87
86
|
"pi-web-access": "^0.10.3",
|
|
88
87
|
"zod": "^4.3.6"
|
|
88
|
+
},
|
|
89
|
+
"overrides": {
|
|
90
|
+
"@mozilla/readability": "0.6.0",
|
|
91
|
+
"brace-expansion": "5.0.5",
|
|
92
|
+
"picomatch": "4.0.4",
|
|
93
|
+
"vite": "7.3.2"
|
|
89
94
|
}
|
|
90
95
|
}
|
package/src/brain.ts
CHANGED
|
@@ -33,7 +33,7 @@ Your workflow is mandatory:
|
|
|
33
33
|
|
|
34
34
|
Behavior rules:
|
|
35
35
|
- Stay friendly, plain-spoken, direct, and efficient with tokens/context.
|
|
36
|
-
- Do not expose
|
|
36
|
+
- Do not expose internal handoffs or legacy role concepts. Everything happens behind the scenes.
|
|
37
37
|
- If the request is not fully clear enough to implement safely without guessing, use the interview tool to ask targeted clarification questions instead of asking them in chat.
|
|
38
38
|
- Do not start editing code until the spec is explicit enough to avoid guessing.
|
|
39
39
|
- In this repo, treat direct user instructions as requested Omni app/product behavior by default unless the user explicitly marks them as meta instructions for the agent/session.
|
package/src/config.ts
CHANGED
|
@@ -6,13 +6,8 @@ import { AVAILABLE_MODELS } from "./providers.js";
|
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_CONFIG: OmniConfig = {
|
|
8
8
|
models: {
|
|
9
|
-
worker: "anthropic/claude-sonnet-4-6",
|
|
10
|
-
expert: "openai/gpt-5.4",
|
|
11
|
-
planner: "openai/gpt-5.4",
|
|
12
9
|
brain: "anthropic/claude-opus-4-6",
|
|
13
10
|
},
|
|
14
|
-
retryLimit: 2,
|
|
15
|
-
chainEnabled: false,
|
|
16
11
|
cleanupCompletedPlans: false,
|
|
17
12
|
};
|
|
18
13
|
|
|
@@ -38,24 +33,14 @@ function parseModelTable(
|
|
|
38
33
|
if (rowMatch) {
|
|
39
34
|
const agent = rowMatch[1].trim().toLowerCase();
|
|
40
35
|
const model = rowMatch[2].trim();
|
|
41
|
-
|
|
36
|
+
if (model.length > 0) {
|
|
37
|
+
models[agent] = model;
|
|
38
|
+
}
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
41
|
return models;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
function parseRetryLimit(content: string): number {
|
|
48
|
-
const match = content.match(
|
|
49
|
-
/(?:Implementation retries before the plan must be tightened|Worker retries before expert takeover):\s*(\d+)/u,
|
|
50
|
-
);
|
|
51
|
-
return match ? Number.parseInt(match[1], 10) : DEFAULT_CONFIG.retryLimit;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function parseChainEnabled(content: string): boolean {
|
|
55
|
-
const match = content.match(/Chain execution enabled:\s*(true|false)/u);
|
|
56
|
-
return match ? match[1] === "true" : DEFAULT_CONFIG.chainEnabled;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
44
|
function parseCleanupCompletedPlans(content: string): boolean {
|
|
60
45
|
const match = content.match(/Delete completed plan files:\s*(true|false)/u);
|
|
61
46
|
return match ? match[1] === "true" : DEFAULT_CONFIG.cleanupCompletedPlans;
|
|
@@ -66,17 +51,11 @@ export async function readConfig(rootDir: string): Promise<OmniConfig> {
|
|
|
66
51
|
try {
|
|
67
52
|
const content = await readFile(configPath, "utf8");
|
|
68
53
|
const models = parseModelTable(content, "## Models");
|
|
69
|
-
const retryLimit = parseRetryLimit(content);
|
|
70
54
|
|
|
71
55
|
return {
|
|
72
56
|
models: {
|
|
73
|
-
worker: models.worker ?? DEFAULT_CONFIG.models.worker,
|
|
74
|
-
expert: models.expert ?? DEFAULT_CONFIG.models.expert,
|
|
75
|
-
planner: models.planner ?? DEFAULT_CONFIG.models.planner,
|
|
76
57
|
brain: models.brain ?? DEFAULT_CONFIG.models.brain,
|
|
77
58
|
},
|
|
78
|
-
retryLimit,
|
|
79
|
-
chainEnabled: parseChainEnabled(content),
|
|
80
59
|
cleanupCompletedPlans: parseCleanupCompletedPlans(content),
|
|
81
60
|
};
|
|
82
61
|
} catch {
|
|
@@ -91,19 +70,8 @@ function renderConfigContent(config: OmniConfig): string {
|
|
|
91
70
|
|
|
92
71
|
| Agent | Model |
|
|
93
72
|
|-------|-------|
|
|
94
|
-
| worker | ${config.models.worker} |
|
|
95
|
-
| expert | ${config.models.expert} |
|
|
96
|
-
| planner | ${config.models.planner} |
|
|
97
73
|
| brain | ${config.models.brain} |
|
|
98
74
|
|
|
99
|
-
## Retry Policy
|
|
100
|
-
|
|
101
|
-
Implementation retries before the plan must be tightened: ${config.retryLimit}
|
|
102
|
-
|
|
103
|
-
## Execution
|
|
104
|
-
|
|
105
|
-
Chain execution enabled: ${config.chainEnabled}
|
|
106
|
-
|
|
107
75
|
## Memory
|
|
108
76
|
|
|
109
77
|
Delete completed plan files: ${config.cleanupCompletedPlans}
|
|
@@ -125,7 +93,7 @@ export async function updateModelConfig(
|
|
|
125
93
|
model: string,
|
|
126
94
|
): Promise<OmniConfig> {
|
|
127
95
|
const config = await readConfig(rootDir);
|
|
128
|
-
const validAgents = ["
|
|
96
|
+
const validAgents = ["brain"] as const;
|
|
129
97
|
const normalizedAgent = agent.toLowerCase() as (typeof validAgents)[number];
|
|
130
98
|
|
|
131
99
|
if (!validAgents.includes(normalizedAgent)) {
|
package/src/contracts.ts
CHANGED
|
@@ -32,7 +32,6 @@ export interface TaskBrief {
|
|
|
32
32
|
contextFiles: string[];
|
|
33
33
|
skills: string[];
|
|
34
34
|
doneCriteria: string[];
|
|
35
|
-
role: "worker" | "expert";
|
|
36
35
|
status: TaskStatus;
|
|
37
36
|
dependsOn: string[];
|
|
38
37
|
}
|
|
@@ -51,20 +50,6 @@ export interface TaskAttemptResult {
|
|
|
51
50
|
modifiedFiles?: string[];
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
export interface EscalationBrief {
|
|
55
|
-
taskId: string;
|
|
56
|
-
priorAttempts: number;
|
|
57
|
-
failureLogs: string[];
|
|
58
|
-
expertObjective: string;
|
|
59
|
-
verificationResults?: Array<{
|
|
60
|
-
command: string;
|
|
61
|
-
passed: boolean;
|
|
62
|
-
stdout: string;
|
|
63
|
-
stderr: string;
|
|
64
|
-
}>;
|
|
65
|
-
modifiedFiles?: string[];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
53
|
export interface SkillCandidate {
|
|
69
54
|
name: string;
|
|
70
55
|
reason: string;
|
|
@@ -94,7 +79,7 @@ export interface PresetConfig {
|
|
|
94
79
|
maxTasks: number;
|
|
95
80
|
skipInterview: boolean;
|
|
96
81
|
requireVerification: boolean;
|
|
97
|
-
|
|
82
|
+
executionHint: string;
|
|
98
83
|
}
|
|
99
84
|
|
|
100
85
|
export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
@@ -105,7 +90,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
105
90
|
maxTasks: 2,
|
|
106
91
|
skipInterview: true,
|
|
107
92
|
requireVerification: true,
|
|
108
|
-
|
|
93
|
+
executionHint:
|
|
109
94
|
"Focus on the root cause. Write a regression test before fixing.",
|
|
110
95
|
},
|
|
111
96
|
feature: {
|
|
@@ -115,7 +100,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
115
100
|
maxTasks: 8,
|
|
116
101
|
skipInterview: false,
|
|
117
102
|
requireVerification: true,
|
|
118
|
-
|
|
103
|
+
executionHint: "Follow the spec. Keep tasks bounded and verifiable.",
|
|
119
104
|
},
|
|
120
105
|
refactor: {
|
|
121
106
|
name: "refactor",
|
|
@@ -123,7 +108,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
123
108
|
maxTasks: 5,
|
|
124
109
|
skipInterview: true,
|
|
125
110
|
requireVerification: true,
|
|
126
|
-
|
|
111
|
+
executionHint:
|
|
127
112
|
"Preserve all existing behavior. Run the full test suite after each change.",
|
|
128
113
|
},
|
|
129
114
|
spike: {
|
|
@@ -133,7 +118,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
133
118
|
maxTasks: 1,
|
|
134
119
|
skipInterview: true,
|
|
135
120
|
requireVerification: false,
|
|
136
|
-
|
|
121
|
+
executionHint: "Explore freely. Document findings in .omni/research/.",
|
|
137
122
|
},
|
|
138
123
|
"security-audit": {
|
|
139
124
|
name: "security-audit",
|
|
@@ -141,7 +126,7 @@ export const WORKFLOW_PRESETS: Record<WorkflowPreset, PresetConfig> = {
|
|
|
141
126
|
maxTasks: 3,
|
|
142
127
|
skipInterview: true,
|
|
143
128
|
requireVerification: false,
|
|
144
|
-
|
|
129
|
+
executionHint:
|
|
145
130
|
"Analyze for OWASP Top 10, secrets in code, dependency vulnerabilities. Do not modify source code.",
|
|
146
131
|
},
|
|
147
132
|
};
|
|
@@ -162,13 +147,8 @@ export function detectPreset(
|
|
|
162
147
|
|
|
163
148
|
export interface OmniConfig {
|
|
164
149
|
models: {
|
|
165
|
-
worker: string;
|
|
166
|
-
expert: string;
|
|
167
|
-
planner: string;
|
|
168
150
|
brain: string;
|
|
169
151
|
};
|
|
170
|
-
retryLimit: number;
|
|
171
|
-
chainEnabled: boolean;
|
|
172
152
|
cleanupCompletedPlans: boolean;
|
|
173
153
|
}
|
|
174
154
|
|
package/src/doctor.ts
CHANGED
|
@@ -50,15 +50,12 @@ async function checkConfigParseable(
|
|
|
50
50
|
): Promise<DiagnosticResult> {
|
|
51
51
|
try {
|
|
52
52
|
const config = await readConfig(rootDir);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
message: `Model for ${role} is empty in CONFIG.md.`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
53
|
+
if (!config.models.brain) {
|
|
54
|
+
return {
|
|
55
|
+
name: "config",
|
|
56
|
+
level: "yellow",
|
|
57
|
+
message: "Model for brain is empty in CONFIG.md.",
|
|
58
|
+
};
|
|
62
59
|
}
|
|
63
60
|
return { name: "config", level: "green", message: "Config is valid." };
|
|
64
61
|
} catch {
|
package/src/model-command.ts
CHANGED
|
@@ -7,7 +7,10 @@ import type {
|
|
|
7
7
|
} from "@mariozechner/pi-coding-agent";
|
|
8
8
|
import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
refreshAuthenticatedProviderModelsWithDailyGuard,
|
|
12
|
+
runModelSetupWizard,
|
|
13
|
+
} from "./model-setup.js";
|
|
11
14
|
import { searchableSelect } from "./searchable-select.js";
|
|
12
15
|
|
|
13
16
|
interface ModelsJsonModel {
|
|
@@ -101,6 +104,23 @@ async function handleAdd(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
101
104
|
ctx.ui.notify(result.summary, "info");
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
async function handleRefresh(ctx: ExtensionCommandContext): Promise<void> {
|
|
108
|
+
const result = await refreshAuthenticatedProviderModelsWithDailyGuard(
|
|
109
|
+
ctx.modelRegistry,
|
|
110
|
+
{ force: true },
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (result.refreshedProviders.length === 0) {
|
|
114
|
+
ctx.ui.notify("No eligible custom providers were refreshed.", "info");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ctx.ui.notify(
|
|
119
|
+
`Refreshed custom providers: ${result.refreshedProviders.join(", ")}.`,
|
|
120
|
+
"info",
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
104
124
|
async function handleList(ctx: ExtensionCommandContext): Promise<void> {
|
|
105
125
|
const config = await readModelsJson();
|
|
106
126
|
const custom = getCustomModels(config);
|
|
@@ -149,21 +169,28 @@ async function handleList(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
149
169
|
|
|
150
170
|
export function registerModelCommand(api: ExtensionAPI): void {
|
|
151
171
|
api.registerCommand("model-setup", {
|
|
152
|
-
description:
|
|
172
|
+
description:
|
|
173
|
+
"Add custom providers/models, refresh discovered models, or remove custom model entries",
|
|
153
174
|
async handler(args: string, ctx: ExtensionCommandContext) {
|
|
154
175
|
const sub = args.trim().toLowerCase();
|
|
155
176
|
|
|
156
177
|
if (sub === "add") return handleAdd(ctx);
|
|
178
|
+
if (sub === "refresh") return handleRefresh(ctx);
|
|
157
179
|
if (sub === "list") return handleList(ctx);
|
|
158
180
|
|
|
159
181
|
const choice = await searchableSelect(ctx.ui, "Model setup:", [
|
|
160
182
|
{
|
|
161
|
-
label: "add
|
|
183
|
+
label: "add — Add a custom provider or model",
|
|
162
184
|
value: "add",
|
|
163
185
|
searchText: "add custom provider model",
|
|
164
186
|
},
|
|
165
187
|
{
|
|
166
|
-
label: "
|
|
188
|
+
label: "refresh — Re-discover models for configured custom providers",
|
|
189
|
+
value: "refresh",
|
|
190
|
+
searchText: "refresh rediscover custom provider models",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
label: "list — Show custom models / remove model entries",
|
|
167
194
|
value: "list",
|
|
168
195
|
searchText: "list remove custom models",
|
|
169
196
|
},
|
|
@@ -171,6 +198,7 @@ export function registerModelCommand(api: ExtensionAPI): void {
|
|
|
171
198
|
if (!choice) return;
|
|
172
199
|
|
|
173
200
|
if (choice === "add") return handleAdd(ctx);
|
|
201
|
+
if (choice === "refresh") return handleRefresh(ctx);
|
|
174
202
|
if (choice === "list") return handleList(ctx);
|
|
175
203
|
},
|
|
176
204
|
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
export interface ModelRefreshState {
|
|
7
|
+
lastSuccessfulRefreshDate?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getModelRefreshStatePath(agentDir = getAgentDir()): string {
|
|
11
|
+
return path.join(agentDir, "model-refresh-state.json");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getLocalDateStamp(date = new Date()): string {
|
|
15
|
+
const year = date.getFullYear();
|
|
16
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
17
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
18
|
+
return `${year}-${month}-${day}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function readModelRefreshState(
|
|
22
|
+
statePath = getModelRefreshStatePath(),
|
|
23
|
+
): Promise<ModelRefreshState> {
|
|
24
|
+
try {
|
|
25
|
+
const content = await readFile(statePath, "utf8");
|
|
26
|
+
const parsed = JSON.parse(content) as ModelRefreshState;
|
|
27
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
28
|
+
} catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function writeModelRefreshState(
|
|
34
|
+
state: ModelRefreshState,
|
|
35
|
+
statePath = getModelRefreshStatePath(),
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
38
|
+
await writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
39
|
+
}
|