@henryqw/pi-task-models 0.1.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/CONTEXT.md ADDED
@@ -0,0 +1,17 @@
1
+ # Pi Task Models
2
+
3
+ Pi Task Models stores shared task profiles and task-to-profile assignments for HenryQW extensions.
4
+
5
+ ## Language
6
+
7
+ **Task Profile**:
8
+ Named shared route set (`fast`, `balanced`, or `frontier`) with a required primary route and optional fallback route.
9
+ _Avoid_: package-owned model picker, per-extension model catalog
10
+
11
+ **Task Route**:
12
+ One model reference plus thinking level chosen from current Pi model scope. Empty scope means every available registry model; pinned scope thinking remains binding.
13
+ _Avoid_: free-form provider path, copied model metadata
14
+
15
+ **Active Task Package**:
16
+ Installed HenryQW package discovered from Pi command/tool sourceInfo.
17
+ _Avoid_: filesystem scan, settings.json scan
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Henry Wang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # `@henryqw/pi-task-models`
2
+
3
+ Shared `fast`, `balanced`, and `frontier` model profiles for HenryQW Pi extensions.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pi install npm:@henryqw/pi-task-models
9
+ ```
10
+
11
+ Run `/task-models`. Top menu shows all profiles plus configured task assignments whose `@henryqw` package is active in Pi's effective command/tool registry. Selecting a profile chooses primary model, primary thinking, fallback model or `None`, then fallback thinking when needed; one completed flow writes whole profile once. Selecting a task changes its profile assignment.
12
+
13
+ Menus and runtime resolution use current session's `ctx.scopedModels`, including pinned thinking levels. Empty scope falls back to Pi's full available model registry. Numbered Codex account aliases are deduplicated, and fallback choices exclude selected primary model. Hidden task assignments remain stored when package is disabled or removed.
14
+
15
+ ## Config
16
+
17
+ Config lives at `getAgentDir()/config/pi-task-models.json`, normally `~/.pi/agent/config/pi-task-models.json`:
18
+
19
+ ```json
20
+ {
21
+ "profiles": {
22
+ "fast": {
23
+ "primary": { "model": "openai-codex/gpt-fast", "thinkingLevel": "low" },
24
+ "fallback": { "model": "other-provider/fast-model", "thinkingLevel": "low" }
25
+ },
26
+ "balanced": {
27
+ "primary": { "model": "openai-codex/gpt-balanced", "thinkingLevel": "high" }
28
+ },
29
+ "frontier": {
30
+ "primary": { "model": "openai-codex/gpt-frontier", "thinkingLevel": "max" }
31
+ }
32
+ },
33
+ "tasks": {
34
+ "pi-herdr-rename/rename": "fast",
35
+ "pi-auto-compact/autoCompact": "balanced"
36
+ }
37
+ }
38
+ ```
39
+
40
+ Each configured profile requires one primary model and thinking level. Fallback is optional. Model references use canonical `provider/model`; numbered `openai-codex-N` account routes are stored as `openai-codex/model` and resolve onto active matching account when available.
41
+
42
+ Config reads are strict. Malformed or unknown values fail visibly and never rewrite file. Only explicit `/task-models` actions write config.
43
+
44
+ ## Consumers
45
+
46
+ - `pi-herdr-rename`: task assignment defaults to `fast`; retries configured fallback after primary route failure.
47
+ - `pi-auto-compact`: task assignment defaults to `balanced`; retries fallback, then uses current session model so compaction still runs.
48
+ - `pi-subagent`: caller chooses `fast`, `balanced`, or `frontier`; fallback is selected only before child starts. Started child is never retried because tools may already have side effects.
49
+ - `pi-herdr-subagents`: intentionally independent.
50
+
51
+ Primary and fallback routes outside current model scope, with unavailable models, or with unsupported or scope-pinned-different thinking levels are skipped. Consumers define final failure behavior above.
52
+
53
+ ## Library API
54
+
55
+ Package exports config, canonical model reference, model deduplication, supported-thinking, route resolution, and active-task discovery helpers. Pi model registry and extension `sourceInfo` remain runtime authority.
56
+
57
+ ## Development
58
+
59
+ ```bash
60
+ npm test --workspace @henryqw/pi-task-models
61
+ npm run typecheck --workspace @henryqw/pi-task-models
62
+ npm run pack:check --workspace @henryqw/pi-task-models
63
+ ```
@@ -0,0 +1,53 @@
1
+ import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ export declare const PROFILE_NAMES: readonly ["fast", "balanced", "frontier"];
3
+ export type ProfileName = (typeof PROFILE_NAMES)[number];
4
+ export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
5
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
6
+ export declare const DEFAULT_TASK_ASSIGNMENTS: {
7
+ readonly "pi-herdr-rename/rename": "fast";
8
+ readonly "pi-auto-compact/autoCompact": "balanced";
9
+ };
10
+ export type TaskModelRoute = {
11
+ model: string;
12
+ thinkingLevel: ThinkingLevel;
13
+ };
14
+ export type TaskModelProfile = {
15
+ primary: TaskModelRoute;
16
+ fallback?: TaskModelRoute;
17
+ };
18
+ export type TaskModelsConfig = {
19
+ profiles: Partial<Record<ProfileName, TaskModelProfile>>;
20
+ tasks: Record<string, ProfileName>;
21
+ };
22
+ export type AvailableModel = ReturnType<ExtensionContext["modelRegistry"]["getAvailable"]>[number];
23
+ export type ResolvedTaskRoute = {
24
+ model: AvailableModel;
25
+ thinkingLevel: ThinkingLevel;
26
+ };
27
+ export type ActiveTaskPackage = {
28
+ packageName: string;
29
+ task: string;
30
+ };
31
+ export declare const configPath: (agentDir?: string) => string;
32
+ export declare function readTaskModelsConfig(agentDir?: string): TaskModelsConfig;
33
+ export declare function writeTaskModelsConfig(config: TaskModelsConfig, agentDir?: string): void;
34
+ export declare function canonicalModelReference(model: {
35
+ provider: string;
36
+ id: string;
37
+ } | string): string;
38
+ export declare function modelReference(model: {
39
+ provider: string;
40
+ id: string;
41
+ }): string;
42
+ export declare function dedupeAvailableModels(models: readonly AvailableModel[], preferredProvider?: string): AvailableModel[];
43
+ export declare function resolveAvailableModel(models: readonly AvailableModel[], reference: string, preferredProvider?: string): AvailableModel | undefined;
44
+ export declare function supportedThinkingLevels(model: AvailableModel): ThinkingLevel[];
45
+ export declare function availableTaskModels(ctx: ExtensionContext): AvailableModel[];
46
+ export declare function taskThinkingLevels(ctx: ExtensionContext, model: AvailableModel): ThinkingLevel[];
47
+ export declare function resolveTaskModelRoute(ctx: ExtensionContext, route: TaskModelRoute): ResolvedTaskRoute | undefined;
48
+ export declare function orderedProfileRoutes(profile: TaskModelProfile): TaskModelRoute[];
49
+ export declare function activeTaskPackages(pi: Pick<ExtensionAPI, "getCommands" | "getAllTools">, tasks?: Readonly<Record<string, ProfileName>>): ActiveTaskPackage[];
50
+ export declare function createTaskModelsExtension(pi: ExtensionAPI, options?: {
51
+ agentDir?: string;
52
+ }): void;
53
+ export default function taskModelsExtension(pi: ExtensionAPI): void;
package/dist/index.js ADDED
@@ -0,0 +1,319 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
4
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
+ export const PROFILE_NAMES = ["fast", "balanced", "frontier"];
6
+ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
7
+ export const DEFAULT_TASK_ASSIGNMENTS = {
8
+ "pi-herdr-rename/rename": "fast",
9
+ "pi-auto-compact/autoCompact": "balanced",
10
+ };
11
+ const CODEX_ALIAS = /^openai-codex-(?:[2-9]|[1-9]\d+)$/;
12
+ const CONFIG_FILE = "pi-task-models.json";
13
+ const defaultTaskAssignments = () => ({ ...DEFAULT_TASK_ASSIGNMENTS });
14
+ export const configPath = (agentDir = getAgentDir()) => join(agentDir, "config", CONFIG_FILE);
15
+ function isCodexProvider(provider) {
16
+ return provider === "openai-codex" || Boolean(provider && CODEX_ALIAS.test(provider));
17
+ }
18
+ function isProfileName(value) {
19
+ return typeof value === "string" && PROFILE_NAMES.includes(value);
20
+ }
21
+ function isThinkingLevel(value) {
22
+ return typeof value === "string" && THINKING_LEVELS.includes(value);
23
+ }
24
+ function hasOnlyKeys(value, keys) {
25
+ return Object.keys(value).every((key) => keys.includes(key));
26
+ }
27
+ function isModelReference(value) {
28
+ return typeof value === "string"
29
+ && value === value.trim()
30
+ && !value.includes("\0")
31
+ && /^[^\s/]+\/\S+$/.test(value);
32
+ }
33
+ function isTaskId(value) {
34
+ return /^[a-z0-9][a-z0-9-]*\/[A-Za-z0-9][A-Za-z0-9-]*$/.test(value);
35
+ }
36
+ function isTaskRoute(value) {
37
+ if (!value || typeof value !== "object" || Array.isArray(value))
38
+ return false;
39
+ const route = value;
40
+ return hasOnlyKeys(route, ["model", "thinkingLevel"])
41
+ && isModelReference(route.model)
42
+ && isThinkingLevel(route.thinkingLevel);
43
+ }
44
+ function isTaskProfile(value) {
45
+ if (!value || typeof value !== "object" || Array.isArray(value))
46
+ return false;
47
+ const profile = value;
48
+ return hasOnlyKeys(profile, ["primary", "fallback"])
49
+ && isTaskRoute(profile.primary)
50
+ && (profile.fallback === undefined || isTaskRoute(profile.fallback));
51
+ }
52
+ function normalizeRoute(route) {
53
+ return { model: canonicalModelReference(route.model), thinkingLevel: route.thinkingLevel };
54
+ }
55
+ function normalizeConfig(config) {
56
+ return {
57
+ profiles: Object.fromEntries(Object.entries(config.profiles).map(([name, profile]) => [name, {
58
+ primary: normalizeRoute(profile.primary),
59
+ ...(profile.fallback ? { fallback: normalizeRoute(profile.fallback) } : {}),
60
+ }])),
61
+ tasks: { ...config.tasks },
62
+ };
63
+ }
64
+ function parseConfig(value) {
65
+ if (!value || typeof value !== "object" || Array.isArray(value))
66
+ throw new Error("Config must be an object.");
67
+ const record = value;
68
+ if (!hasOnlyKeys(record, ["profiles", "tasks"]))
69
+ throw new Error("Config contains unknown settings.");
70
+ const profiles = {};
71
+ const tasks = defaultTaskAssignments();
72
+ if (record.profiles !== undefined) {
73
+ if (!record.profiles || typeof record.profiles !== "object" || Array.isArray(record.profiles)) {
74
+ throw new Error("profiles must be an object.");
75
+ }
76
+ for (const [name, profile] of Object.entries(record.profiles)) {
77
+ if (!PROFILE_NAMES.includes(name))
78
+ throw new Error(`Unknown profile: ${name}.`);
79
+ if (!isTaskProfile(profile))
80
+ throw new Error(`${name} profile is invalid.`);
81
+ const profileName = name;
82
+ profiles[profileName] = {
83
+ primary: normalizeRoute(profile.primary),
84
+ ...(profile.fallback ? { fallback: normalizeRoute(profile.fallback) } : {}),
85
+ };
86
+ }
87
+ }
88
+ if (record.tasks !== undefined) {
89
+ if (!record.tasks || typeof record.tasks !== "object" || Array.isArray(record.tasks)) {
90
+ throw new Error("tasks must be an object.");
91
+ }
92
+ for (const [task, profile] of Object.entries(record.tasks)) {
93
+ if (!isTaskId(task) || !isProfileName(profile))
94
+ throw new Error(`Invalid profile assignment for ${task}.`);
95
+ tasks[task] = profile;
96
+ }
97
+ }
98
+ return { profiles, tasks };
99
+ }
100
+ export function readTaskModelsConfig(agentDir = getAgentDir()) {
101
+ try {
102
+ const value = JSON.parse(readFileSync(configPath(agentDir), "utf8"));
103
+ return parseConfig(value);
104
+ }
105
+ catch (error) {
106
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
107
+ return { profiles: {}, tasks: defaultTaskAssignments() };
108
+ }
109
+ throw error;
110
+ }
111
+ }
112
+ export function writeTaskModelsConfig(config, agentDir = getAgentDir()) {
113
+ const file = configPath(agentDir);
114
+ mkdirSync(dirname(file), { recursive: true });
115
+ writeFileSync(file, `${JSON.stringify(normalizeConfig(config), null, 2)}\n`);
116
+ }
117
+ export function canonicalModelReference(model) {
118
+ const reference = typeof model === "string" ? model : `${model.provider}/${model.id}`;
119
+ if (!isModelReference(reference))
120
+ throw new Error("Model reference must be provider/model without whitespace.");
121
+ const separator = reference.indexOf("/");
122
+ const provider = reference.slice(0, separator);
123
+ const id = reference.slice(separator + 1);
124
+ return `${isCodexProvider(provider) ? "openai-codex" : provider}/${id}`;
125
+ }
126
+ export function modelReference(model) {
127
+ return `${model.provider}/${model.id}`;
128
+ }
129
+ export function dedupeAvailableModels(models, preferredProvider) {
130
+ const deduped = [];
131
+ const indexes = new Map();
132
+ for (const model of models) {
133
+ const key = canonicalModelReference(model);
134
+ const index = indexes.get(key);
135
+ if (index === undefined) {
136
+ indexes.set(key, deduped.length);
137
+ deduped.push(model);
138
+ continue;
139
+ }
140
+ const current = deduped[index];
141
+ if (shouldPreferModel(model, current, preferredProvider))
142
+ deduped[index] = model;
143
+ }
144
+ return deduped;
145
+ }
146
+ function shouldPreferModel(candidate, current, preferredProvider) {
147
+ if (preferredProvider && candidate.provider === preferredProvider && current.provider !== preferredProvider)
148
+ return true;
149
+ if (preferredProvider && current.provider === preferredProvider && candidate.provider !== preferredProvider)
150
+ return false;
151
+ const candidateAlias = CODEX_ALIAS.test(candidate.provider);
152
+ const currentAlias = CODEX_ALIAS.test(current.provider);
153
+ if (candidateAlias !== currentAlias)
154
+ return candidateAlias;
155
+ return false;
156
+ }
157
+ export function resolveAvailableModel(models, reference, preferredProvider) {
158
+ const canonical = canonicalModelReference(reference);
159
+ const separator = canonical.indexOf("/");
160
+ const provider = canonical.slice(0, separator);
161
+ const id = canonical.slice(separator + 1);
162
+ if (provider !== "openai-codex") {
163
+ return models.find((model) => canonicalModelReference(model) === canonical);
164
+ }
165
+ return (preferredProvider && isCodexProvider(preferredProvider)
166
+ ? models.find((model) => model.provider === preferredProvider && model.id === id)
167
+ : undefined)
168
+ ?? models.find((model) => CODEX_ALIAS.test(model.provider) && model.id === id)
169
+ ?? models.find((model) => model.provider === "openai-codex" && model.id === id);
170
+ }
171
+ export function supportedThinkingLevels(model) {
172
+ return getSupportedThinkingLevels(model);
173
+ }
174
+ export function availableTaskModels(ctx) {
175
+ const scopedModels = ctx.scopedModels ?? [];
176
+ return dedupeAvailableModels((scopedModels.length ? scopedModels.map(({ model }) => model) : ctx.modelRegistry.getAvailable())
177
+ .filter((model) => model.input.includes("text")), ctx.model?.provider);
178
+ }
179
+ export function taskThinkingLevels(ctx, model) {
180
+ const supported = supportedThinkingLevels(model);
181
+ const pinned = (ctx.scopedModels ?? []).find(({ model: scoped }) => scoped.provider === model.provider && scoped.id === model.id)?.thinkingLevel;
182
+ if (!pinned)
183
+ return supported;
184
+ return supported.includes(pinned) ? [pinned] : [];
185
+ }
186
+ export function resolveTaskModelRoute(ctx, route) {
187
+ const model = resolveAvailableModel(availableTaskModels(ctx), route.model, ctx.model?.provider);
188
+ return model && taskThinkingLevels(ctx, model).includes(route.thinkingLevel)
189
+ ? { model, thinkingLevel: route.thinkingLevel }
190
+ : undefined;
191
+ }
192
+ export function orderedProfileRoutes(profile) {
193
+ return profile.fallback ? [profile.primary, profile.fallback] : [profile.primary];
194
+ }
195
+ export function activeTaskPackages(pi, tasks = DEFAULT_TASK_ASSIGNMENTS) {
196
+ const sources = [
197
+ ...pi.getCommands().map((command) => command.sourceInfo),
198
+ ...pi.getAllTools().map((tool) => tool.sourceInfo),
199
+ ];
200
+ return Object.keys(tasks).flatMap((task) => {
201
+ if (!isTaskId(task))
202
+ return [];
203
+ const packageName = `@henryqw/${task.slice(0, task.indexOf("/"))}`;
204
+ return sources.some((source) => sourceMatchesPackage(source, packageName))
205
+ ? [{ packageName, task }]
206
+ : [];
207
+ });
208
+ }
209
+ function sourceMatchesPackage(sourceInfo, packageName) {
210
+ const npmSource = `npm:${packageName}`;
211
+ if (sourceInfo.source === packageName || sourceInfo.source === npmSource || sourceInfo.source.startsWith(`${npmSource}@`))
212
+ return true;
213
+ const path = sourceInfo.path.replaceAll("\\", "/");
214
+ const shortName = packageName.split("/").pop();
215
+ return path.includes(`/node_modules/${packageName}/`)
216
+ || Boolean(shortName && path.includes(`/packages/${shortName}/`));
217
+ }
218
+ export function createTaskModelsExtension(pi, options) {
219
+ const agentDir = options?.agentDir ?? getAgentDir();
220
+ pi.registerCommand("task-models", {
221
+ description: "configure shared task model profiles",
222
+ handler: async (_args, ctx) => {
223
+ let config;
224
+ try {
225
+ config = readTaskModelsConfig(agentDir);
226
+ }
227
+ catch {
228
+ ctx.ui.notify("Couldn't read task model config.", "error");
229
+ return;
230
+ }
231
+ const profileOptions = PROFILE_NAMES.map((name) => {
232
+ const configured = config.profiles[name];
233
+ return {
234
+ name,
235
+ label: configured
236
+ ? `${name} · ${routeLabel(configured.primary)} → ${configured.fallback ? routeLabel(configured.fallback) : "none"}`
237
+ : `${name} · not configured`,
238
+ };
239
+ });
240
+ const save = () => {
241
+ try {
242
+ writeTaskModelsConfig(config, agentDir);
243
+ return true;
244
+ }
245
+ catch {
246
+ ctx.ui.notify("Couldn't save task model config.", "error");
247
+ return false;
248
+ }
249
+ };
250
+ const taskOptions = activeTaskPackages(pi, config.tasks).map((entry) => ({
251
+ entry,
252
+ label: `${entry.task} · ${config.tasks[entry.task]}`,
253
+ }));
254
+ const selected = await ctx.ui.select("Task models", [
255
+ ...profileOptions.map(({ label }) => label),
256
+ ...taskOptions.map(({ label }) => label),
257
+ ]);
258
+ if (!selected)
259
+ return;
260
+ const task = taskOptions.find(({ label }) => label === selected)?.entry;
261
+ if (task) {
262
+ const profile = await ctx.ui.select(`${task.task} profile`, [...PROFILE_NAMES]);
263
+ if (!isProfileName(profile))
264
+ return;
265
+ config.tasks[task.task] = profile;
266
+ if (!save())
267
+ return;
268
+ ctx.ui.notify(`${task.task} assigned to ${profile}.`, "info");
269
+ return;
270
+ }
271
+ const profile = profileOptions.find(({ label }) => label === selected)?.name;
272
+ if (!profile)
273
+ return;
274
+ const models = availableTaskModels(ctx);
275
+ if (!models.length) {
276
+ ctx.ui.notify("No text models are available.", "error");
277
+ return;
278
+ }
279
+ const primary = await selectRoute(ctx, `Profile ${profile} primary`, models);
280
+ if (!primary)
281
+ return;
282
+ const fallbackModels = models.filter((model) => canonicalModelReference(model) !== primary.model);
283
+ const fallbackModel = await ctx.ui.select(`Profile ${profile} fallback`, [
284
+ "None",
285
+ ...fallbackModels.map((model) => modelReference(model)),
286
+ ]);
287
+ if (!fallbackModel)
288
+ return;
289
+ const fallback = fallbackModel === "None"
290
+ ? undefined
291
+ : await selectThinkingLevel(ctx, `Profile ${profile} fallback`, fallbackModels, fallbackModel);
292
+ if (fallbackModel !== "None" && !fallback)
293
+ return;
294
+ config.profiles[profile] = { primary, ...(fallback ? { fallback } : {}) };
295
+ if (!save())
296
+ return;
297
+ ctx.ui.notify(`${profile} profile saved.`, "info");
298
+ },
299
+ });
300
+ }
301
+ function routeLabel(route) {
302
+ return `${route.model} (${route.thinkingLevel})`;
303
+ }
304
+ async function selectRoute(ctx, title, models) {
305
+ const selectedModel = await ctx.ui.select(title, models.map((model) => modelReference(model)));
306
+ return selectedModel ? selectThinkingLevel(ctx, title, models, selectedModel) : undefined;
307
+ }
308
+ async function selectThinkingLevel(ctx, title, models, selectedModel) {
309
+ const model = models.find((candidate) => modelReference(candidate) === selectedModel);
310
+ if (!model)
311
+ return;
312
+ const thinkingLevel = await ctx.ui.select(`${title} thinking`, taskThinkingLevels(ctx, model));
313
+ if (!isThinkingLevel(thinkingLevel))
314
+ return;
315
+ return { model: canonicalModelReference(model), thinkingLevel };
316
+ }
317
+ export default function taskModelsExtension(pi) {
318
+ createTaskModelsExtension(pi);
319
+ }
@@ -0,0 +1,10 @@
1
+ import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { createTaskModelsExtension } from "@henryqw/pi-task-models";
3
+
4
+ export function registerTaskModelsExtension(pi: ExtensionAPI, options?: { agentDir?: string }): void {
5
+ createTaskModelsExtension(pi, options);
6
+ }
7
+
8
+ export default function taskModelsExtension(pi: ExtensionAPI): void {
9
+ registerTaskModelsExtension(pi);
10
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@henryqw/pi-task-models",
3
+ "version": "0.1.0",
4
+ "description": "Shared task model profiles and routing for HenryQW Pi extensions.",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi",
8
+ "model",
9
+ "settings"
10
+ ],
11
+ "type": "module",
12
+ "engines": {
13
+ "node": ">=22.19.0"
14
+ },
15
+ "license": "MIT",
16
+ "files": [
17
+ "dist",
18
+ "extensions",
19
+ "README.md",
20
+ "CONTEXT.md",
21
+ "LICENSE"
22
+ ],
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build": "tsc --project tsconfig.build.json",
32
+ "test": "npm run build && node --test test/*.test.ts",
33
+ "typecheck": "tsc --noEmit --allowImportingTsExtensions --target ES2022 --module NodeNext --moduleResolution NodeNext --types node --skipLibCheck src/*.ts extensions/*.ts test/*.ts",
34
+ "prepack": "npm run build",
35
+ "pack:check": "npm pack --dry-run"
36
+ },
37
+ "peerDependencies": {
38
+ "@earendil-works/pi-ai": "^0.84.2",
39
+ "@earendil-works/pi-coding-agent": "^0.84.2"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/HenryQW/pi-packages.git",
44
+ "directory": "packages/pi-task-models"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/HenryQW/pi-packages/issues"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "pi": {
53
+ "extensions": [
54
+ "./extensions/task-models.ts"
55
+ ]
56
+ }
57
+ }