@henryqw/pi-task-models 3.0.2 → 4.0.1
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/README.md +20 -3
- package/dist/index.d.ts +5 -4
- package/dist/index.js +27 -37
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Choose shared `fast`, `balanced`, `frontier`, and `fav` model profiles for consumer-owned Model Tasks.
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
## Why
|
|
6
8
|
|
|
7
9
|
- **Created for**: Remove duplicated model pickers and catalogs that extensions once owned separately.
|
|
@@ -48,7 +50,7 @@ Fallback choices exclude the selected primary. BTW selects the first authenticat
|
|
|
48
50
|
|
|
49
51
|
## Config
|
|
50
52
|
|
|
51
|
-
The
|
|
53
|
+
The shared JSON file is at `~/.pi/agent/config/pi-task-models/config.json`. Consumers use `loadTaskModelsConfig()` for validated values. They never read or write this file. Only explicit `/task-models` actions save it.
|
|
52
54
|
|
|
53
55
|
```json
|
|
54
56
|
{
|
|
@@ -88,12 +90,27 @@ Task defaults live only in consumer declarations. Existing explicit assignments,
|
|
|
88
90
|
|
|
89
91
|
Model references use canonical `provider/model`. Numbered Codex account aliases (`openai-codex-N`) resolve through Pi's registry and store canonically as `openai-codex/<model>`.
|
|
90
92
|
|
|
91
|
-
Reads are strict.
|
|
93
|
+
Reads are strict. `loadTaskModelsConfig()` returns `{ source: "missing", value: { "profiles": {}, "tasks": {} } }` for a missing file. It does not create a file.
|
|
94
|
+
|
|
95
|
+
At session start, task-models warns when `~/.pi/agent/config/pi-task-models/config.json` is missing; run `/task-models` to configure task routes.
|
|
92
96
|
|
|
93
|
-
Malformed JSON, unknown keys, invalid task IDs, unknown profiles, or invalid profile or route values fail visibly with `/task-models` guidance. The file is
|
|
97
|
+
Malformed JSON, unknown keys, invalid task IDs, unknown profiles, or invalid profile or route values fail visibly with `/task-models` guidance. The malformed file is preserved.
|
|
94
98
|
|
|
95
99
|
## For extension authors
|
|
96
100
|
|
|
97
101
|
A `ModelTask` is a consumer-owned independently executed model operation. Consumers define a `ModelTask` and call `registerModelTask(pi, task)` at extension load.
|
|
98
102
|
|
|
103
|
+
Use `loadTaskModelsConfig()` to get validated config without reading a file. Its `source` is `"file"` or `"missing"`, so consumers can warn when defaults are in use.
|
|
104
|
+
|
|
99
105
|
Use `resolveConfiguredTaskRoute(ctx, task)` or `resolveConfiguredTaskRoutes(ctx, task)` to resolve routes. Profile thinking is authoritative for task routes. Resolution uses `config.tasks[task.id] ?? task.defaultProfile`.
|
|
106
|
+
|
|
107
|
+
Resolution errors are `TaskRouteError` values. Check `taskRouteCode`:
|
|
108
|
+
|
|
109
|
+
| Code | Meaning |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `config-missing` | The optional shared config file is absent. |
|
|
112
|
+
| `config-read` | A present shared config file cannot be read or validated. |
|
|
113
|
+
| `profile-missing` | The selected profile is not configured. |
|
|
114
|
+
| `no-route` | The selected profile has no available route. |
|
|
115
|
+
|
|
116
|
+
Every error directs users to `/task-models`. A consumer may silence only `config-missing` when it has a safe current-session fallback.
|
package/dist/index.d.ts
CHANGED
|
@@ -28,9 +28,10 @@ export type ResolvedTaskRoute = {
|
|
|
28
28
|
model: AvailableModel;
|
|
29
29
|
thinkingLevel: ThinkingLevel;
|
|
30
30
|
};
|
|
31
|
-
export declare
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
export declare function loadTaskModelsConfig(agentDir?: string): {
|
|
32
|
+
source: "file" | "missing";
|
|
33
|
+
value: TaskModelsConfig;
|
|
34
|
+
};
|
|
34
35
|
export declare function canonicalModelReference(model: {
|
|
35
36
|
provider: string;
|
|
36
37
|
id: string;
|
|
@@ -45,7 +46,7 @@ export declare function availableTaskModels(ctx: ExtensionContext): AvailableMod
|
|
|
45
46
|
export declare function taskThinkingLevels(ctx: ExtensionContext, model: AvailableModel): ThinkingLevel[];
|
|
46
47
|
export declare function resolveTaskModelRoute(ctx: ExtensionContext, route: TaskModelRoute, agentDir?: string, thinking?: ThinkingLevel): ResolvedTaskRoute | undefined;
|
|
47
48
|
export declare function resolveConfiguredTaskRoutes(ctx: ExtensionContext, task: ModelTask, agentDir?: string, thinking?: ThinkingLevel): ResolvedTaskRoute[];
|
|
48
|
-
export type TaskRouteErrorCode = "config-read" | "profile-missing" | "no-route";
|
|
49
|
+
export type TaskRouteErrorCode = "config-missing" | "config-read" | "profile-missing" | "no-route";
|
|
49
50
|
export type TaskRouteError = Error & {
|
|
50
51
|
taskRouteCode: TaskRouteErrorCode;
|
|
51
52
|
profileName?: ProfileName;
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
1
|
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
4
2
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { createConfigStore } from "@henryqw/pi-config-store";
|
|
5
4
|
export const PROFILE_NAMES = ["fast", "balanced", "frontier", "fav"];
|
|
6
5
|
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
7
6
|
const CODEX_ALIAS = /^openai-codex-(?:[2-9]|[1-9]\d+)$/;
|
|
8
|
-
const CONFIG_FILE = "pi-task-models.json";
|
|
9
7
|
const MODEL_TASK_REQUEST_EVENT = "@henryqw/pi-task-models:model-task-request";
|
|
10
8
|
const MODEL_TASK_RESPONSE_EVENT = "@henryqw/pi-task-models:model-task-response";
|
|
11
9
|
const registeredModelTasks = new WeakMap();
|
|
12
10
|
let modelTaskRequestNumber = 0;
|
|
13
|
-
export const configPath = (agentDir = getAgentDir()) => join(agentDir, "config", CONFIG_FILE);
|
|
14
11
|
function isCodexProvider(provider) {
|
|
15
12
|
return provider === "openai-codex" || Boolean(provider && CODEX_ALIAS.test(provider));
|
|
16
13
|
}
|
|
@@ -89,15 +86,6 @@ function isTaskProfile(value) {
|
|
|
89
86
|
function normalizeRoute(route) {
|
|
90
87
|
return { model: canonicalModelReference(route.model), thinkingLevel: route.thinkingLevel };
|
|
91
88
|
}
|
|
92
|
-
function normalizeConfig(config) {
|
|
93
|
-
return {
|
|
94
|
-
profiles: Object.fromEntries(Object.entries(config.profiles).map(([name, profile]) => [name, {
|
|
95
|
-
primary: normalizeRoute(profile.primary),
|
|
96
|
-
...(profile.fallback ? { fallback: normalizeRoute(profile.fallback) } : {}),
|
|
97
|
-
}])),
|
|
98
|
-
tasks: { ...config.tasks },
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
89
|
function parseConfig(value) {
|
|
102
90
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
103
91
|
throw new Error("Config must be an object.");
|
|
@@ -136,24 +124,16 @@ function parseConfig(value) {
|
|
|
136
124
|
}
|
|
137
125
|
return { profiles, tasks };
|
|
138
126
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return { profiles: {}, tasks: {} };
|
|
147
|
-
}
|
|
148
|
-
throw error;
|
|
149
|
-
}
|
|
127
|
+
function taskModelsConfigStore(agentDir = getAgentDir()) {
|
|
128
|
+
return createConfigStore({
|
|
129
|
+
extensionId: "pi-task-models",
|
|
130
|
+
agentDir,
|
|
131
|
+
defaults: () => ({ profiles: {}, tasks: {} }),
|
|
132
|
+
parse: parseConfig,
|
|
133
|
+
});
|
|
150
134
|
}
|
|
151
|
-
export function
|
|
152
|
-
|
|
153
|
-
throw new Error("fav profile has no fallback.");
|
|
154
|
-
const file = configPath(agentDir);
|
|
155
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
156
|
-
writeFileSync(file, `${JSON.stringify(normalizeConfig(config), null, 2)}\n`);
|
|
135
|
+
export function loadTaskModelsConfig(agentDir = getAgentDir()) {
|
|
136
|
+
return taskModelsConfigStore(agentDir).loadSync();
|
|
157
137
|
}
|
|
158
138
|
export function canonicalModelReference(model) {
|
|
159
139
|
const reference = typeof model === "string" ? model : `${model.provider}/${model.id}`;
|
|
@@ -237,13 +217,17 @@ export function resolveTaskModelRoute(ctx, route, agentDir = getAgentDir(), thin
|
|
|
237
217
|
}
|
|
238
218
|
export function resolveConfiguredTaskRoutes(ctx, task, agentDir = getAgentDir(), thinking) {
|
|
239
219
|
const declaration = validatedModelTask(task);
|
|
240
|
-
let
|
|
220
|
+
let loadedConfig;
|
|
241
221
|
try {
|
|
242
|
-
|
|
222
|
+
loadedConfig = loadTaskModelsConfig(agentDir);
|
|
243
223
|
}
|
|
244
224
|
catch {
|
|
245
225
|
throw taskRouteError("config-read", "Couldn't read task model config. Run /task-models.");
|
|
246
226
|
}
|
|
227
|
+
if (loadedConfig.source === "missing") {
|
|
228
|
+
throw taskRouteError("config-missing", "Task model config is missing. Run /task-models to configure task routes.");
|
|
229
|
+
}
|
|
230
|
+
const config = loadedConfig.value;
|
|
247
231
|
const profileName = config.tasks[declaration.id] ?? declaration.defaultProfile;
|
|
248
232
|
const profile = config.profiles[profileName];
|
|
249
233
|
if (!profile)
|
|
@@ -351,12 +335,18 @@ function discoverModelTasks(pi) {
|
|
|
351
335
|
}
|
|
352
336
|
export function createTaskModelsExtension(pi, options) {
|
|
353
337
|
const agentDir = options?.agentDir ?? getAgentDir();
|
|
338
|
+
const configStore = taskModelsConfigStore(agentDir);
|
|
339
|
+
pi.on("session_start", (_event, ctx) => {
|
|
340
|
+
if (configStore.loadSync().source === "missing") {
|
|
341
|
+
ctx.ui.notify(`Task model config is missing at ${configStore.path}; run /task-models to configure task routes.`, "warning");
|
|
342
|
+
}
|
|
343
|
+
});
|
|
354
344
|
pi.registerCommand("task-models", {
|
|
355
345
|
description: "configure shared task model profiles",
|
|
356
346
|
handler: async (_args, ctx) => {
|
|
357
347
|
let config;
|
|
358
348
|
try {
|
|
359
|
-
config =
|
|
349
|
+
config = configStore.loadSync().value;
|
|
360
350
|
}
|
|
361
351
|
catch {
|
|
362
352
|
ctx.ui.notify("Couldn't read task model config.", "error");
|
|
@@ -371,9 +361,9 @@ export function createTaskModelsExtension(pi, options) {
|
|
|
371
361
|
: `${name} · not configured`,
|
|
372
362
|
};
|
|
373
363
|
});
|
|
374
|
-
const save = () => {
|
|
364
|
+
const save = async () => {
|
|
375
365
|
try {
|
|
376
|
-
|
|
366
|
+
await configStore.save(config);
|
|
377
367
|
return true;
|
|
378
368
|
}
|
|
379
369
|
catch {
|
|
@@ -400,7 +390,7 @@ export function createTaskModelsExtension(pi, options) {
|
|
|
400
390
|
delete config.tasks[task.id];
|
|
401
391
|
else
|
|
402
392
|
config.tasks[task.id] = profile;
|
|
403
|
-
if (!save())
|
|
393
|
+
if (!await save())
|
|
404
394
|
return;
|
|
405
395
|
ctx.ui.notify(`${task.id} assigned to ${profile}.`, "info");
|
|
406
396
|
return;
|
|
@@ -434,7 +424,7 @@ export function createTaskModelsExtension(pi, options) {
|
|
|
434
424
|
return;
|
|
435
425
|
config.profiles[profile] = { primary, ...(fallback ? { fallback } : {}) };
|
|
436
426
|
}
|
|
437
|
-
if (!save())
|
|
427
|
+
if (!await save())
|
|
438
428
|
return;
|
|
439
429
|
ctx.ui.notify(`${profile} profile saved.`, "info");
|
|
440
430
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-task-models",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "Shared task model profiles and routing for HenryQW Pi extensions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|
|
43
|
-
"url": "git+https://github.com/HenryQW/pi-
|
|
44
|
-
"directory": "
|
|
43
|
+
"url": "git+https://github.com/HenryQW/pi-harness.git",
|
|
44
|
+
"directory": "extensions/pi-task-models"
|
|
45
45
|
},
|
|
46
46
|
"bugs": {
|
|
47
|
-
"url": "https://github.com/HenryQW/pi-
|
|
47
|
+
"url": "https://github.com/HenryQW/pi-harness/issues"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
"pi": {
|
|
53
53
|
"extensions": [
|
|
54
54
|
"./extensions/task-models.ts"
|
|
55
|
-
]
|
|
55
|
+
],
|
|
56
|
+
"image": "https://raw.githubusercontent.com/HenryQW/pi-harness/main/extensions/pi-task-models/example.png"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@henryqw/pi-config-store": "^0.1.0"
|
|
56
60
|
}
|
|
57
61
|
}
|