pi-prompt-template-model 0.3.0 → 0.4.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/CHANGELOG.md +42 -3
- package/README.md +72 -7
- package/args.ts +59 -0
- package/index.ts +209 -578
- package/model-selection.ts +99 -0
- package/notifications.ts +31 -0
- package/package.json +19 -1
- package/prompt-execution.ts +42 -0
- package/prompt-loader.ts +447 -0
- package/skill-loaded-renderer.ts +46 -0
- package/template-conditionals.ts +294 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [0.4.0] - 2026-03-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Inline `<if-model is="...">...</if-model>` blocks with optional `<else>` branches inside prompt bodies.
|
|
8
|
+
- Provider wildcard matching in conditionals with syntax like `anthropic/*`.
|
|
9
|
+
- Conditional rendering now happens after model fallback resolution for both single prompt commands and `/chain-prompts`.
|
|
10
|
+
- Prompt argument substitution now mirrors pi core more closely, including `${@:N}` and `${@:N:L}` slice syntax. See README for full placeholder reference.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Model fallback now preserves the currently active model whenever it matches any listed fallback candidate, including ambiguous bare model IDs that would otherwise resolve through provider preference, instead of switching to an earlier candidate unnecessarily.
|
|
15
|
+
- Prompt templates that collide with reserved slash commands, including built-ins like `/model` and the extension’s own `/chain-prompts`, are now skipped with a warning instead of being silently shadowed.
|
|
16
|
+
- Prompt discovery is now deterministic in a locale-independent way, and duplicate model-enabled prompt names within the same source layer are skipped with a warning instead of silently depending on traversal order.
|
|
17
|
+
- Invalid `model` frontmatter declarations are now rejected during prompt loading with diagnostics instead of failing later at execution time.
|
|
18
|
+
- Literal tags like `<elsewhere>` and `</if-modeling>` no longer get misparsed as malformed conditional directives.
|
|
19
|
+
- Non-interactive notifications now go to stderr so print-mode stdout stays clean.
|
|
20
|
+
- Bare model IDs with multiple providers can now still resolve through OAuth-backed auth checks even when fast availability checks alone are inconclusive.
|
|
21
|
+
- Optional string frontmatter fields are now trimmed so quoted values like `thinking: " high "` and `skill: " tmux "` behave as expected.
|
|
22
|
+
- Existing prompt commands now refresh prompt files before execution, so edits made during a session take effect on the next run instead of waiting for a new session.
|
|
23
|
+
- Skill-loaded custom messages now fail safe if their details payload is missing instead of crashing the renderer.
|
|
24
|
+
- Frontmatter `model` specs and inline conditional `is` specs now reject internal whitespace like `anthropic /model` or `anthropic /*` instead of silently registering values that can never match.
|
|
25
|
+
- Recursive prompt discovery now detects already-visited directories and skips symlink loops instead of risking infinite recursion or duplicate traversal.
|
|
26
|
+
- Bare model IDs now honor provider priority across all auth-capable candidates, including OAuth-backed providers, instead of incorrectly favoring a lower-priority provider just because it appeared in the fast-available set.
|
|
27
|
+
- Prompt loading now rejects non-object YAML frontmatter roots, like lists, with a diagnostic instead of silently treating them as missing `model` fields.
|
|
28
|
+
- `/chain-prompts` now only restores model and thinking when they actually changed, avoiding redundant state writes and noisy restore notifications on no-op chains.
|
|
29
|
+
- `/chain-prompts` now tracks thinking changes caused by model switches even when a step does not set `thinking`, so final restoration stays correct when the runtime clamps or resets thinking during a model change.
|
|
30
|
+
- `/chain-prompts` now rejects empty or quote-only step segments explicitly instead of treating them as blank template names.
|
|
31
|
+
- Single-command auto-restore now also skips no-op thinking restores and notifications when the runtime is already back on the original thinking level.
|
|
32
|
+
- Removed unnecessary exports: `modelSpecMatches` from model-selection.ts and `VALID_THINKING_LEVELS` from prompt-loader.ts are now internal implementation details.
|
|
33
|
+
- `/chain-prompts` now correctly ignores ` -- ` and `->` inside quoted per-step arguments instead of misinterpreting them as separators.
|
|
34
|
+
- `</else>` is now explicitly rejected with a helpful error message explaining that `<else>` is a separator, not a container.
|
|
35
|
+
- Fast-path optimization now correctly includes `</else>` check so standalone invalid tags are caught.
|
|
36
|
+
- Empty prompt abort in single-command mode now notifies as "error" instead of "warning" for consistency with chain mode.
|
|
37
|
+
|
|
38
|
+
## [0.3.1] - 2026-02-08
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Prompts map now initialized at extension load instead of waiting for `session_start`. Commands invoked before the first session event no longer fail with stale empty state.
|
|
4
43
|
|
|
5
44
|
## [0.3.0] - 2026-02-08
|
|
6
45
|
|
|
7
46
|
### Added
|
|
8
47
|
|
|
9
|
-
- **Chain command**: `/chain` orchestrates multiple prompt templates sequentially, each with its own model, skill, and thinking level. Conversation context flows between steps naturally.
|
|
10
|
-
- Per-step args override shared args: `/chain analyze "error handling" -> fix-plan "focus on perf" -> summarize -- src/main.ts`
|
|
48
|
+
- **Chain command**: `/chain-prompts` orchestrates multiple prompt templates sequentially, each with its own model, skill, and thinking level. Conversation context flows between steps naturally.
|
|
49
|
+
- Per-step args override shared args: `/chain-prompts analyze "error handling" -> fix-plan "focus on perf" -> summarize -- src/main.ts`
|
|
11
50
|
- Mid-chain failure rolls back to the original model and thinking level
|
|
12
51
|
- Step progress notifications show which step is running
|
|
13
52
|
- State isolation: chain uses local variables, never interferes with single-command restore behavior
|
package/README.md
CHANGED
|
@@ -131,6 +131,72 @@ Here the extension tries Haiku on Anthropic first, then Haiku on OpenRouter, the
|
|
|
131
131
|
|
|
132
132
|
When all candidates fail, a single error notification lists everything that was tried.
|
|
133
133
|
|
|
134
|
+
## Inline Model Conditionals
|
|
135
|
+
|
|
136
|
+
Prompt bodies can embed model-specific instructions directly in the markdown:
|
|
137
|
+
|
|
138
|
+
```markdown
|
|
139
|
+
---
|
|
140
|
+
description: Cross-model code review
|
|
141
|
+
model: claude-haiku-4-5, claude-sonnet-4-20250514
|
|
142
|
+
---
|
|
143
|
+
Summarize the change first.
|
|
144
|
+
|
|
145
|
+
<if-model is="claude-haiku-4-5">
|
|
146
|
+
Keep the answer brief and cost-conscious.
|
|
147
|
+
<else>
|
|
148
|
+
Do a deeper pass and call out subtle risks.
|
|
149
|
+
</if-model>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Conditionals are evaluated against the model that actually runs the command after fallback resolution. That means the same template can render differently depending on which candidate was selected.
|
|
153
|
+
|
|
154
|
+
Supported matches inside `is="..."`:
|
|
155
|
+
|
|
156
|
+
- Exact `provider/model-id`
|
|
157
|
+
- Exact bare `model-id`
|
|
158
|
+
- Provider wildcard like `anthropic/*`
|
|
159
|
+
- Comma-separated lists combining any of the above
|
|
160
|
+
|
|
161
|
+
Examples:
|
|
162
|
+
|
|
163
|
+
```markdown
|
|
164
|
+
<if-model is="anthropic/claude-sonnet-4-20250514">...</if-model>
|
|
165
|
+
<if-model is="claude-sonnet-4-20250514">...</if-model>
|
|
166
|
+
<if-model is="anthropic/*">...</if-model>
|
|
167
|
+
<if-model is="openai/gpt-5.2, anthropic/*">...</if-model>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`<else>` is the fallback branch for the current `<if-model>` block. Nested blocks are supported.
|
|
171
|
+
|
|
172
|
+
Conditionals are a raw text preprocessing step, not markdown-aware syntax. If you want to show the directive literally inside a prompt, escape it in the source text, for example with `<if-model is="anthropic/*">`.
|
|
173
|
+
|
|
174
|
+
## Argument Substitution
|
|
175
|
+
|
|
176
|
+
Prompt bodies support argument placeholders that expand to command arguments:
|
|
177
|
+
|
|
178
|
+
| Placeholder | Description |
|
|
179
|
+
|-------------|-------------|
|
|
180
|
+
| `$1`, `$2`, ... | Positional argument (1-indexed) |
|
|
181
|
+
| `$@` | All arguments joined with spaces |
|
|
182
|
+
| `$ARGUMENTS` | Same as `$@` |
|
|
183
|
+
| `${@:N}` | All arguments from position N onward |
|
|
184
|
+
| `${@:N:L}` | L arguments starting from position N |
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
|
|
188
|
+
```markdown
|
|
189
|
+
---
|
|
190
|
+
model: claude-sonnet-4-20250514
|
|
191
|
+
---
|
|
192
|
+
Analyze $1 focusing on $2. Additional context: ${@:3}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Running `/analyze src/main.ts performance edge cases error handling` expands to:
|
|
196
|
+
- `$1` → `src/main.ts`
|
|
197
|
+
- `$2` → `performance`
|
|
198
|
+
- `${@:3}` → `edge cases error handling`
|
|
199
|
+
|
|
134
200
|
## Skill Resolution
|
|
135
201
|
|
|
136
202
|
The `skill` field matches the skill's directory name:
|
|
@@ -158,7 +224,7 @@ Organize prompts in subdirectories for namespacing:
|
|
|
158
224
|
└── hook.md → /hook (user:frontend)
|
|
159
225
|
```
|
|
160
226
|
|
|
161
|
-
The subdirectory shows in autocomplete as the source label.
|
|
227
|
+
The subdirectory shows in autocomplete as the source label. Command names are based on filename only. If duplicates exist within the same source layer, the first one found after lexical sorting wins and later duplicates are skipped with a warning. Reserved command names like `model`, `reload`, and `chain-prompts` are also skipped with a warning.
|
|
162
228
|
|
|
163
229
|
## Examples
|
|
164
230
|
|
|
@@ -240,10 +306,10 @@ Switched to Haiku. How can I help?
|
|
|
240
306
|
|
|
241
307
|
## Chaining Templates
|
|
242
308
|
|
|
243
|
-
The `/chain` command runs multiple templates sequentially. Each step switches to its own model, injects its own skill, and the conversation context carries forward between steps.
|
|
309
|
+
The `/chain-prompts` command runs multiple templates sequentially. Each step switches to its own model, renders any inline model conditionals against that step’s resolved model, injects its own skill, and the conversation context carries forward between steps.
|
|
244
310
|
|
|
245
311
|
```
|
|
246
|
-
/chain analyze-code -> fix-plan -> summarize -- src/main.ts
|
|
312
|
+
/chain-prompts analyze-code -> fix-plan -> summarize -- src/main.ts
|
|
247
313
|
```
|
|
248
314
|
|
|
249
315
|
This runs `analyze-code` first, then `fix-plan` (which sees the analysis in conversation context), then `summarize`. The `-- src/main.ts` provides shared args substituted into every template's `$@`.
|
|
@@ -251,7 +317,7 @@ This runs `analyze-code` first, then `fix-plan` (which sees the analysis in conv
|
|
|
251
317
|
Each step can also receive its own args, overriding the shared args for that step:
|
|
252
318
|
|
|
253
319
|
```
|
|
254
|
-
/chain analyze-code "look at error handling" -> fix-plan "focus on perf" -> summarize
|
|
320
|
+
/chain-prompts analyze-code "look at error handling" -> fix-plan "focus on perf" -> summarize
|
|
255
321
|
```
|
|
256
322
|
|
|
257
323
|
Here `analyze-code` gets `$@ = "look at error handling"`, `fix-plan` gets `$@ = "focus on perf"`, and `summarize` has no per-step args so it falls back to the shared args (empty in this case, but conversation context from prior steps is usually enough).
|
|
@@ -259,7 +325,7 @@ Here `analyze-code` gets `$@ = "look at error handling"`, `fix-plan` gets `$@ =
|
|
|
259
325
|
You can mix both:
|
|
260
326
|
|
|
261
327
|
```
|
|
262
|
-
/chain analyze-code "error handling" -> fix-plan -> summarize -- src/main.ts
|
|
328
|
+
/chain-prompts analyze-code "error handling" -> fix-plan -> summarize -- src/main.ts
|
|
263
329
|
```
|
|
264
330
|
|
|
265
331
|
Step 1 uses its per-step args (`"error handling"`), steps 2 and 3 fall back to the shared args (`"src/main.ts"`).
|
|
@@ -290,7 +356,6 @@ The model switches, skill injects, agent responds, and output prints to stdout.
|
|
|
290
356
|
|
|
291
357
|
## Limitations
|
|
292
358
|
|
|
293
|
-
-
|
|
359
|
+
- Prompt files are reloaded on session start and whenever an extension-owned prompt command runs. If you add a brand-new prompt file while already inside a session, run another extension-owned command such as `/chain-prompts`, start a new session, or reload pi so the new slash command is registered.
|
|
294
360
|
- Model restore state is in-memory. Closing pi mid-response loses restore state.
|
|
295
361
|
- Only templates with a `model` field can be chained. Templates without `model` are handled by pi core and invisible to this extension.
|
|
296
|
-
- Per-step args containing a literal `->` will be misinterpreted as a step separator. Use shared `--` args or a template file instead.
|
package/args.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function parseCommandArgs(argsString: string): string[] {
|
|
2
|
+
const args: string[] = [];
|
|
3
|
+
let current = "";
|
|
4
|
+
let inQuote: string | null = null;
|
|
5
|
+
|
|
6
|
+
for (let i = 0; i < argsString.length; i++) {
|
|
7
|
+
const char = argsString[i];
|
|
8
|
+
|
|
9
|
+
if (inQuote) {
|
|
10
|
+
if (char === inQuote) {
|
|
11
|
+
inQuote = null;
|
|
12
|
+
} else {
|
|
13
|
+
current += char;
|
|
14
|
+
}
|
|
15
|
+
} else if (char === '"' || char === "'") {
|
|
16
|
+
inQuote = char;
|
|
17
|
+
} else if (char === " " || char === "\t") {
|
|
18
|
+
if (current) {
|
|
19
|
+
args.push(current);
|
|
20
|
+
current = "";
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
current += char;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (current) {
|
|
28
|
+
args.push(current);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return args;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function substituteArgs(content: string, args: string[]): string {
|
|
35
|
+
let result = content;
|
|
36
|
+
|
|
37
|
+
result = result.replace(/\$(\d+)/g, (_, num) => {
|
|
38
|
+
const index = parseInt(num, 10) - 1;
|
|
39
|
+
return args[index] ?? "";
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
result = result.replace(/\$\{@:(\d+)(?::(\d+))?\}/g, (_, startStr, lengthStr) => {
|
|
43
|
+
let start = parseInt(startStr, 10) - 1;
|
|
44
|
+
if (start < 0) start = 0;
|
|
45
|
+
|
|
46
|
+
if (lengthStr) {
|
|
47
|
+
const length = parseInt(lengthStr, 10);
|
|
48
|
+
return args.slice(start, start + length).join(" ");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return args.slice(start).join(" ");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const allArgs = args.join(" ");
|
|
55
|
+
result = result.replace(/\$ARGUMENTS/g, allArgs);
|
|
56
|
+
result = result.replace(/\$@/g, allArgs);
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
}
|