@cr1ms0n/pi-subagent 0.8.2 → 0.8.4
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 +18 -0
- package/README.md +21 -3
- package/package.json +1 -1
- package/skills/subagent/SKILL.md +8 -4
- package/src/agents.ts +2 -5
- package/src/backend.ts +3 -0
- package/src/config.ts +3 -5
- package/src/extension.ts +1 -1
- package/src/model-policy.ts +19 -7
- package/src/persistence.ts +4 -3
- package/src/policy.ts +7 -2
- package/src/schema.ts +6 -9
- package/src/thinking.ts +18 -0
- package/src/types.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.4 — 2026-09-10
|
|
4
|
+
|
|
5
|
+
### Pi-owned thinking levels
|
|
6
|
+
|
|
7
|
+
- Treat route and task `thinking` values as opaque Pi strings instead of a
|
|
8
|
+
hard-coded local enum, preserving model-specific values such as `max` and
|
|
9
|
+
passing them to Pi unchanged. Pi remains responsible for model capability
|
|
10
|
+
validation and mapping.
|
|
11
|
+
|
|
12
|
+
## 0.8.3 — 2026-09-10
|
|
13
|
+
|
|
14
|
+
### Model-policy thinking defaults
|
|
15
|
+
|
|
16
|
+
- Allow `modelPolicy.default` and named-agent routes in `~/.pi/subagent.json`
|
|
17
|
+
to specify an optional `thinking` default (`off`, `minimal`, `low`, `medium`,
|
|
18
|
+
`high`, or `xhigh`). Explicit task, agent, and profile defaults still win;
|
|
19
|
+
unsupported backends reject resolved thinking instead of silently ignoring it.
|
|
20
|
+
|
|
3
21
|
## 0.8.1 — @cr1ms0n community fork (2026-09-08)
|
|
4
22
|
|
|
5
23
|
- Require an explicit model matching user-owned default/agent model policy.
|
package/README.md
CHANGED
|
@@ -238,14 +238,31 @@ fallback lists are ignored for model selection. The minimal template is:
|
|
|
238
238
|
```json
|
|
239
239
|
{
|
|
240
240
|
"modelPolicy": {
|
|
241
|
-
"default": {
|
|
241
|
+
"default": {
|
|
242
|
+
"model": "<provider/model-id>",
|
|
243
|
+
"fallbackModels": [],
|
|
244
|
+
"thinking": "medium"
|
|
245
|
+
},
|
|
242
246
|
"agents": {
|
|
243
|
-
"<agent-name>": {
|
|
247
|
+
"<agent-name>": {
|
|
248
|
+
"model": "<provider/model-id>",
|
|
249
|
+
"fallbackModels": [],
|
|
250
|
+
"thinking": "high"
|
|
251
|
+
}
|
|
244
252
|
}
|
|
245
253
|
}
|
|
246
254
|
}
|
|
247
255
|
```
|
|
248
256
|
|
|
257
|
+
`thinking` is optional and is an opaque Pi thinking-level string. Common values
|
|
258
|
+
include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`, but the
|
|
259
|
+
package does not remap or restrict model-specific values. Pi receives the value
|
|
260
|
+
unchanged and decides whether the active model supports it. It is a route default,
|
|
261
|
+
not a strict policy value. Resolution order is: explicit task `thinking` > agent
|
|
262
|
+
frontmatter `thinking` > profile `taskDefaults.<profile>.thinking` > the selected
|
|
263
|
+
`modelPolicy` route's `thinking` > the parent session's thinking level. Omitting it
|
|
264
|
+
preserves the existing behavior.
|
|
265
|
+
|
|
249
266
|
Every new `task`/`tasks[]` item must pass the exact mapped `model`. Omit
|
|
250
267
|
`fallback_models` to use the configured list; when supplied it must match the
|
|
251
268
|
configured order exactly. Management actions remain available when this file is
|
|
@@ -319,7 +336,8 @@ and file changes are picked up within seconds — no restart needed.
|
|
|
319
336
|
`taskDefaults` in `~/.pi/subagent.json` remains available for non-model
|
|
320
337
|
fields such as thinking, budgets, and retry counts. Its legacy `model` and
|
|
321
338
|
`fallbackModels` fields are ignored; model routing belongs only to
|
|
322
|
-
`modelPolicy`.
|
|
339
|
+
`modelPolicy`. A profile `thinking` value overrides the selected route default.
|
|
340
|
+
Invalid fields are dropped field-by-field.
|
|
323
341
|
|
|
324
342
|
Notes on behavior:
|
|
325
343
|
|
package/package.json
CHANGED
package/skills/subagent/SKILL.md
CHANGED
|
@@ -97,7 +97,11 @@ Every new task must pass a `model` that exactly matches the current
|
|
|
97
97
|
`taskDefaults.model`, and parent-session model inheritance are ignored. An
|
|
98
98
|
agent route replaces the default route, and configured fallback order is
|
|
99
99
|
immutable. Omit `fallback_models` to use the route; if supplied, it must match
|
|
100
|
-
exactly.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
exactly. An optional route `thinking` value is an opaque Pi thinking-level
|
|
101
|
+
string; common values include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`,
|
|
102
|
+
and `max`, but model-specific values are passed through unchanged. It is a
|
|
103
|
+
default; explicit task, agent, and profile `taskDefaults.thinking` values
|
|
104
|
+
override it. Management actions do not require model. The extension re-reads
|
|
105
|
+
this policy on each dispatch and injects it into
|
|
106
|
+
the parent prompt. If the policy is missing or invalid, management remains
|
|
107
|
+
available but new spawns and synthesis are rejected.
|
package/src/agents.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { TaskProfile } from "./types.js";
|
|
5
|
-
import type
|
|
5
|
+
import { isThinkingLevel, type ThinkingLevel } from "./thinking.js";
|
|
6
6
|
import { isPlausibleSchema } from "./structured.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -23,7 +23,6 @@ import { isPlausibleSchema } from "./structured.js";
|
|
|
23
23
|
* model policy is the only source used for new spawns.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
27
26
|
const PROFILES = ["explore", "review", "general"] as const;
|
|
28
27
|
|
|
29
28
|
export interface AgentDefinition {
|
|
@@ -178,9 +177,7 @@ export function parseAgentFile(name: string, raw: string, source: string, scope:
|
|
|
178
177
|
}
|
|
179
178
|
}
|
|
180
179
|
|
|
181
|
-
const thinking = frontmatter.thinking
|
|
182
|
-
? (frontmatter.thinking as ThinkingLevel)
|
|
183
|
-
: undefined;
|
|
180
|
+
const thinking = isThinkingLevel(frontmatter.thinking) ? frontmatter.thinking : undefined;
|
|
184
181
|
const profile = frontmatter.profile && (PROFILES as readonly string[]).includes(frontmatter.profile)
|
|
185
182
|
? (frontmatter.profile as TaskProfile)
|
|
186
183
|
: undefined;
|
package/src/backend.ts
CHANGED
|
@@ -139,6 +139,9 @@ export function checkCapabilities(
|
|
|
139
139
|
`backend '${backend}' cannot restrict the child's tools, so profile '${spec.profile ?? "explore"}' cannot be enforced; use profile:'general' with an explicitly writable backend, or the pi backend`,
|
|
140
140
|
);
|
|
141
141
|
}
|
|
142
|
+
if (spec.thinking !== undefined && !capabilities.thinking) {
|
|
143
|
+
problems.push(`backend '${backend}' does not support thinking level '${spec.thinking}'; omit thinking or use the pi backend`);
|
|
144
|
+
}
|
|
142
145
|
if (spec.outputSchema && !capabilities.outputSchema) {
|
|
143
146
|
problems.push(`backend '${backend}' does not support output_schema; drop it`);
|
|
144
147
|
}
|
package/src/config.ts
CHANGED
|
@@ -3,9 +3,9 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { TaskProfile } from "./types.js";
|
|
5
5
|
import { MODEL_POLICY_CONFIG_FILE, parseModelPolicy, type ModelPolicySnapshot } from "./model-policy.js";
|
|
6
|
+
import { isThinkingLevel, type ThinkingLevel } from "./thinking.js";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
8
|
+
export type { ThinkingLevel } from "./thinking.js";
|
|
9
9
|
|
|
10
10
|
const WIDGET_MODES = ["background", "off"] as const;
|
|
11
11
|
export type WidgetMode = (typeof WIDGET_MODES)[number];
|
|
@@ -139,9 +139,7 @@ function prune<T extends object>(value: Partial<T>): Partial<T> {
|
|
|
139
139
|
function sanitizeTaskDefaults(raw: unknown): TaskDefaults | undefined {
|
|
140
140
|
if (!raw || typeof raw !== "object") return undefined;
|
|
141
141
|
const value = raw as Record<string, unknown>;
|
|
142
|
-
const thinking =
|
|
143
|
-
? (value.thinking as ThinkingLevel)
|
|
144
|
-
: undefined;
|
|
142
|
+
const thinking = isThinkingLevel(value.thinking) ? value.thinking : undefined;
|
|
145
143
|
const fallbackModels = Array.isArray(value.fallbackModels)
|
|
146
144
|
? value.fallbackModels.map(nonEmptyString).filter((model): model is string => !!model)
|
|
147
145
|
: undefined;
|
package/src/extension.ts
CHANGED
|
@@ -566,7 +566,7 @@ async function runSynthesis(
|
|
|
566
566
|
tools: ["read"],
|
|
567
567
|
model: approved.route.model,
|
|
568
568
|
fallbackModels: [...approved.route.fallbackModels],
|
|
569
|
-
thinking: "low",
|
|
569
|
+
thinking: approved.route.thinking ?? "low",
|
|
570
570
|
timeoutMs: Math.min(runtime.config.defaultTimeoutMs, 5 * 60_000),
|
|
571
571
|
maxTurns: 8,
|
|
572
572
|
},
|
package/src/model-policy.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { isThinkingLevel, type ThinkingLevel } from "./thinking.js";
|
|
4
5
|
|
|
5
6
|
export const MODEL_POLICY_CONFIG_FILE = path.join(os.homedir(), ".pi", "subagent.json");
|
|
6
7
|
|
|
@@ -8,6 +9,8 @@ export interface ModelRoute {
|
|
|
8
9
|
model: string;
|
|
9
10
|
/** Immutable order owned by the user configuration. */
|
|
10
11
|
fallbackModels: readonly string[];
|
|
12
|
+
/** Optional route default; callers may override it with task/agent/profile settings. */
|
|
13
|
+
thinking?: ThinkingLevel;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export interface ModelPolicySnapshot {
|
|
@@ -42,7 +45,7 @@ function route(value: unknown, pathName: string, source: string): ModelRoute {
|
|
|
42
45
|
invalid(source, `${pathName} must be an object`);
|
|
43
46
|
}
|
|
44
47
|
const record = value as Record<string, unknown>;
|
|
45
|
-
const unknown = Object.keys(record).filter((key) => key !== "model" && key !== "fallbackModels");
|
|
48
|
+
const unknown = Object.keys(record).filter((key) => key !== "model" && key !== "fallbackModels" && key !== "thinking");
|
|
46
49
|
if (unknown.length) invalid(source, `${pathName} has unknown field(s): ${unknown.join(", ")}`);
|
|
47
50
|
const model = modelId(record.model, pathName, source);
|
|
48
51
|
const rawFallbacks = record.fallbackModels === undefined ? [] : record.fallbackModels;
|
|
@@ -50,7 +53,15 @@ function route(value: unknown, pathName: string, source: string): ModelRoute {
|
|
|
50
53
|
const fallbackModels = rawFallbacks.map((value, index) => modelId(value, `${pathName}.fallbackModels[${index}]`, source));
|
|
51
54
|
if (new Set(fallbackModels).size !== fallbackModels.length) invalid(source, `${pathName}.fallbackModels must not contain duplicates`);
|
|
52
55
|
if (fallbackModels.includes(model)) invalid(source, `${pathName}.fallbackModels must not repeat the primary model`);
|
|
53
|
-
|
|
56
|
+
const thinking = record.thinking;
|
|
57
|
+
if (thinking !== undefined && !isThinkingLevel(thinking)) {
|
|
58
|
+
invalid(source, `${pathName}.thinking must be a non-empty Pi thinking level string without whitespace or control characters`);
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
model,
|
|
62
|
+
fallbackModels: Object.freeze([...fallbackModels]),
|
|
63
|
+
...(thinking === undefined ? {} : { thinking }),
|
|
64
|
+
});
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
/** Parse only the modelPolicy subtree; no provider catalog or credentials are read. */
|
|
@@ -131,14 +142,15 @@ export function formatModelPolicyPrompt(policy: ModelPolicySnapshot | undefined,
|
|
|
131
142
|
const lines = [
|
|
132
143
|
"## Subagent model policy",
|
|
133
144
|
"Every new task/tasks[] spawn must pass model explicitly and it must exactly match this mapping. Management actions do not need model.",
|
|
134
|
-
`default (agentless and unmapped agents): model=${policy.default.model}; fallback_models=[${policy.default.fallbackModels.join(", ")}]`,
|
|
145
|
+
`default (agentless and unmapped agents): model=${policy.default.model}; fallback_models=[${policy.default.fallbackModels.join(", ")}]; thinking=${policy.default.thinking ?? "(unset)"}`,
|
|
135
146
|
];
|
|
136
147
|
for (const [agent, route] of [...policy.agents.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
137
|
-
lines.push(`agent:${agent}: model=${route.model}; fallback_models=[${route.fallbackModels.join(", ")}]`);
|
|
148
|
+
lines.push(`agent:${agent}: model=${route.model}; fallback_models=[${route.fallbackModels.join(", ")}]; thinking=${route.thinking ?? "(unset)"}`);
|
|
138
149
|
}
|
|
139
150
|
lines.push(
|
|
140
151
|
"If fallback_models is omitted, the configured list is used. If it is supplied, it must match the configured list exactly, including order.",
|
|
141
|
-
"
|
|
152
|
+
"thinking is a route default: explicit task thinking, agent thinking, and profile taskDefaults.thinking override it; parent thinking is used only when the route is unset.",
|
|
153
|
+
"Do not use model/fallback_models from agent frontmatter, taskDefaults, or the parent session; those legacy model fields are ignored.",
|
|
142
154
|
);
|
|
143
155
|
return lines.join("\n");
|
|
144
156
|
}
|
|
@@ -146,8 +158,8 @@ export function formatModelPolicyPrompt(policy: ModelPolicySnapshot | undefined,
|
|
|
146
158
|
export function modelPolicyTemplate(): string {
|
|
147
159
|
return JSON.stringify({
|
|
148
160
|
modelPolicy: {
|
|
149
|
-
default: { model: "<provider/model-id>", fallbackModels: [] },
|
|
150
|
-
agents: { "<agent-name>": { model: "<provider/model-id>", fallbackModels: [] } },
|
|
161
|
+
default: { model: "<provider/model-id>", fallbackModels: [], thinking: "medium" },
|
|
162
|
+
agents: { "<agent-name>": { model: "<provider/model-id>", fallbackModels: [], thinking: "high" } },
|
|
151
163
|
},
|
|
152
164
|
}, null, 2);
|
|
153
165
|
}
|
package/src/persistence.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import type { SubagentConfig } from "./config.js";
|
|
3
|
-
import type { ChildProcessIdentity, RunMode, RunSnapshot, RunState, TaskProfile, TimeoutPhase, UsageStats } from "./types.js";
|
|
3
|
+
import type { ChildProcessIdentity, RunMode, RunSnapshot, RunState, TaskProfile, TaskSpec, TimeoutPhase, UsageStats } from "./types.js";
|
|
4
4
|
import { emptyUsage } from "./types.js";
|
|
5
|
+
import { isThinkingLevel } from "./thinking.js";
|
|
5
6
|
|
|
6
7
|
export const RUN_ENTRY_TYPE = "subagent-run-v1";
|
|
7
8
|
|
|
@@ -23,7 +24,7 @@ export interface PersistedResult {
|
|
|
23
24
|
errorMessage?: string;
|
|
24
25
|
usage: UsageStats;
|
|
25
26
|
model?: string;
|
|
26
|
-
thinking?: "
|
|
27
|
+
thinking?: TaskSpec["thinking"];
|
|
27
28
|
profile?: TaskProfile;
|
|
28
29
|
canWrite?: boolean;
|
|
29
30
|
outputFile?: string;
|
|
@@ -141,7 +142,7 @@ function normalizeResult(value: unknown): PersistedResult | undefined {
|
|
|
141
142
|
errorMessage: typeof r.errorMessage === "string" ? utf8Prefix(r.errorMessage, 2_000) : undefined,
|
|
142
143
|
usage: normalizeUsage(r.usage),
|
|
143
144
|
model: typeof r.model === "string" ? r.model : undefined,
|
|
144
|
-
thinking:
|
|
145
|
+
thinking: isThinkingLevel(r.thinking) ? r.thinking : undefined,
|
|
145
146
|
profile: ["explore", "review", "general"].includes(String(r.profile)) ? r.profile : undefined,
|
|
146
147
|
canWrite: typeof r.canWrite === "boolean" ? r.canWrite : undefined,
|
|
147
148
|
outputFile: typeof r.outputFile === "string" ? r.outputFile : undefined,
|
package/src/policy.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { ParallelTaskInput, SubagentParams } from "./schema.js";
|
|
|
8
8
|
import { BACKEND_NAMES, checkCapabilities, type BackendName } from "./backend.js";
|
|
9
9
|
import { resolveBackend } from "./backends/index.js";
|
|
10
10
|
import { validateModelRequest, type ModelPolicySnapshot } from "./model-policy.js";
|
|
11
|
+
import { isThinkingLevel } from "./thinking.js";
|
|
11
12
|
|
|
12
13
|
export const DEPTH_ENV_VAR = "PI_SUBAGENT_DEPTH";
|
|
13
14
|
export const SPAWNS_ENV_VAR = "PI_SUBAGENT_SPAWNS";
|
|
@@ -199,6 +200,10 @@ function normalizeTask(
|
|
|
199
200
|
// file > per-profile config defaults > parent inheritance. Model routing was
|
|
200
201
|
// validated above and is exclusively owned by modelPolicy.
|
|
201
202
|
const profileDefaults: TaskDefaults = defaults.taskDefaults?.[profile] ?? {};
|
|
203
|
+
const effectiveThinking = item.thinking ?? agent?.thinking ?? profileDefaults.thinking ?? modelRoute.thinking ?? parent.thinking;
|
|
204
|
+
if (effectiveThinking !== undefined && !isThinkingLevel(effectiveThinking)) {
|
|
205
|
+
return { error: `Task ${index + 1}: thinking must be a non-empty Pi thinking level string without whitespace or control characters` };
|
|
206
|
+
}
|
|
202
207
|
const label = item.description?.trim()
|
|
203
208
|
? item.description.trim().slice(0, 60)
|
|
204
209
|
: agent
|
|
@@ -223,7 +228,7 @@ function normalizeTask(
|
|
|
223
228
|
// restricts: profile 'general' inherits the parent set and does not
|
|
224
229
|
// promise a read-only sandbox.
|
|
225
230
|
tools: profile === "general" ? undefined : resolved.tools,
|
|
226
|
-
thinking:
|
|
231
|
+
thinking: effectiveThinking,
|
|
227
232
|
outputSchema: item.output_schema ?? agent?.outputSchema,
|
|
228
233
|
profile,
|
|
229
234
|
canWrite: resolved.canWrite,
|
|
@@ -244,7 +249,7 @@ function normalizeTask(
|
|
|
244
249
|
// Model routing is exclusively owned by modelPolicy. Legacy agent,
|
|
245
250
|
// taskDefaults, and parent-session model fields never participate.
|
|
246
251
|
model: item.model!.trim(),
|
|
247
|
-
thinking:
|
|
252
|
+
thinking: effectiveThinking,
|
|
248
253
|
tools: resolved.tools,
|
|
249
254
|
profile,
|
|
250
255
|
cwd,
|
package/src/schema.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { Type, type Static } from "typebox";
|
|
2
2
|
|
|
3
|
-
const ThinkingLevel = Type.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Type.Literal("high"),
|
|
9
|
-
Type.Literal("xhigh"),
|
|
10
|
-
]);
|
|
3
|
+
const ThinkingLevel = Type.String({
|
|
4
|
+
minLength: 1,
|
|
5
|
+
maxLength: 64,
|
|
6
|
+
description: "Opaque Pi thinking level passed through unchanged. Pi/model-specific values such as max are allowed; the active Pi process decides support.",
|
|
7
|
+
});
|
|
11
8
|
|
|
12
9
|
const OutputMode = Type.Union([Type.Literal("inline"), Type.Literal("file-only")]);
|
|
13
10
|
const Profile = Type.Union([Type.Literal("explore"), Type.Literal("review"), Type.Literal("general")]);
|
|
@@ -31,7 +28,7 @@ export const TaskFields = {
|
|
|
31
28
|
description: Type.Optional(Type.String({ description: "Short human label (3-5 words) shown in UIs and result indexes." })),
|
|
32
29
|
system_prompt: Type.Optional(Type.String({ description: "Extra system prompt appended to the child's prompt (does not replace it)." })),
|
|
33
30
|
model: Type.Optional(Type.String({ description: "**REQUIRED for every spawn call (task/tasks).** Exact model id from modelPolicy, in provider/model-id form. Calls without an explicit model are rejected; agent-file model, taskDefaults, and parent-session inheritance are ignored. Management actions (status/wait/cancel/steer/diff/apply/discard) do not need it." })),
|
|
34
|
-
thinking: Type.Optional({ ...ThinkingLevel, description: "
|
|
31
|
+
thinking: Type.Optional({ ...ThinkingLevel, description: "Opaque Pi thinking level for the child. Values such as max are passed through unchanged; Pi/model support decides validity. Defaults to agent thinking, profile taskDefaults.thinking, modelPolicy route thinking, then the parent's level." }),
|
|
35
32
|
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional tool allowlist. explore/review profiles reject write-capable tools." })),
|
|
36
33
|
profile: Type.Optional({ ...Profile, description: "Capability profile: explore/review are strictly read-only; general inherits the parent's active tools and may write." }),
|
|
37
34
|
cwd: Type.Optional(Type.String({ description: "Working directory for the child process." })),
|
package/src/thinking.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque thinking-level value understood by the active Pi/model.
|
|
3
|
+
*
|
|
4
|
+
* Pi owns the vocabulary and model-specific mappings (for example `max` may
|
|
5
|
+
* be supported by one model while another exposes a different set). The
|
|
6
|
+
* subagent package must therefore validate only the transport-safe shape and
|
|
7
|
+
* pass the value through unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export type ThinkingLevel = string;
|
|
10
|
+
|
|
11
|
+
const MAX_THINKING_LEVEL_LENGTH = 64;
|
|
12
|
+
|
|
13
|
+
export function isThinkingLevel(value: unknown): value is ThinkingLevel {
|
|
14
|
+
return typeof value === "string"
|
|
15
|
+
&& value.length > 0
|
|
16
|
+
&& value.length <= MAX_THINKING_LEVEL_LENGTH
|
|
17
|
+
&& !/[\s\u0000-\u001f\u007f]/u.test(value);
|
|
18
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Message } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ThinkingLevel } from "./thinking.js";
|
|
2
3
|
|
|
3
4
|
export type RunMode = "single" | "parallel";
|
|
4
5
|
export type RunState = "queued" | "running" | "completed" | "partial" | "failed" | "cancelled" | "lost" | "timeout";
|
|
@@ -44,7 +45,7 @@ export interface TaskSpec {
|
|
|
44
45
|
label?: string;
|
|
45
46
|
systemPrompt?: string;
|
|
46
47
|
model?: string;
|
|
47
|
-
thinking?:
|
|
48
|
+
thinking?: ThinkingLevel;
|
|
48
49
|
tools?: string[];
|
|
49
50
|
profile: TaskProfile;
|
|
50
51
|
canWrite?: boolean;
|