promptopskit 0.3.9 → 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/README.md +91 -7
- package/SKILL.md +69 -6
- package/dist/{chunk-YNIAA4ZJ.js → chunk-F6UJ73Y6.js} +5 -3
- package/dist/chunk-F6UJ73Y6.js.map +1 -0
- package/dist/{chunk-O6RQZYS4.js → chunk-HLNVKKMS.js} +16 -5
- package/dist/chunk-HLNVKKMS.js.map +1 -0
- package/dist/{chunk-4LEPCCSA.js → chunk-JBLUPKY4.js} +144 -3
- package/dist/chunk-JBLUPKY4.js.map +1 -0
- package/dist/{chunk-LMURMZUA.js → chunk-L2V7LDKD.js} +5 -3
- package/dist/chunk-L2V7LDKD.js.map +1 -0
- package/dist/{chunk-AN3OR7YC.js → chunk-RY5DVTLV.js} +7 -5
- package/dist/chunk-RY5DVTLV.js.map +1 -0
- package/dist/chunk-UPTCSWK3.js +60 -0
- package/dist/chunk-UPTCSWK3.js.map +1 -0
- package/dist/{chunk-V7QY7MRM.js → chunk-XNNDGDPI.js} +50 -3
- package/dist/chunk-XNNDGDPI.js.map +1 -0
- package/dist/cli/index.js +356 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +398 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +162 -10
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.cjs +200 -5
- package/dist/providers/anthropic.cjs.map +1 -1
- package/dist/providers/anthropic.d.cts +2 -2
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +3 -3
- package/dist/providers/gemini.cjs +191 -5
- package/dist/providers/gemini.cjs.map +1 -1
- package/dist/providers/gemini.d.cts +2 -2
- package/dist/providers/gemini.d.ts +2 -2
- package/dist/providers/gemini.js +3 -3
- package/dist/providers/openai-responses.cjs +189 -3
- package/dist/providers/openai-responses.cjs.map +1 -1
- package/dist/providers/openai-responses.d.cts +2 -2
- package/dist/providers/openai-responses.d.ts +2 -2
- package/dist/providers/openai-responses.js +3 -3
- package/dist/providers/openai.cjs +189 -3
- package/dist/providers/openai.cjs.map +1 -1
- package/dist/providers/openai.d.cts +2 -2
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +3 -3
- package/dist/providers/openrouter.cjs +224 -6
- package/dist/providers/openrouter.cjs.map +1 -1
- package/dist/providers/openrouter.d.cts +2 -2
- package/dist/providers/openrouter.d.ts +2 -2
- package/dist/providers/openrouter.js +4 -4
- package/dist/{schema-BKkg93rL.d.cts → schema-B9A1zLRk.d.cts} +482 -2
- package/dist/{schema-BKkg93rL.d.ts → schema-B9A1zLRk.d.ts} +482 -2
- package/dist/testing.cjs +217 -4
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +59 -2
- package/dist/testing.d.ts +59 -2
- package/dist/testing.js +70 -2
- package/dist/testing.js.map +1 -1
- package/dist/{types-CsQ8tON_.d.ts → types-DjGAwJxC.d.ts} +1 -1
- package/dist/{types-7cC17GWp.d.cts → types-DpbzzZO2.d.cts} +1 -1
- package/dist/usagetap/index.d.cts +2 -2
- package/dist/usagetap/index.d.ts +2 -2
- package/package.json +2 -2
- package/dist/chunk-4LEPCCSA.js.map +0 -1
- package/dist/chunk-AN3OR7YC.js.map +0 -1
- package/dist/chunk-LMURMZUA.js.map +0 -1
- package/dist/chunk-O6RQZYS4.js.map +0 -1
- package/dist/chunk-V7QY7MRM.js.map +0 -1
- package/dist/chunk-YAD6COQX.js +0 -26
- package/dist/chunk-YAD6COQX.js.map +0 -1
- package/dist/chunk-YNIAA4ZJ.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,6 +11,31 @@ Your prompts are already in Git. PromptOpsKit makes them manageable. It replaces
|
|
|
11
11
|
|
|
12
12
|
Provider adapters for OpenAI, Anthropic, Gemini, and OpenRouter produce a ready-to-send **request body only** — no HTTP client, no auth, no headers. Your application owns transport, so PromptOpsKit slots into any stack without opinions about how you call the API.
|
|
13
13
|
|
|
14
|
+
## How is this different from GitHub Models?
|
|
15
|
+
|
|
16
|
+
GitHub Models is a great place to prototype prompts, compare models, and run evaluations inside GitHub.
|
|
17
|
+
|
|
18
|
+
PromptOpsKit is focused on the application runtime layer.
|
|
19
|
+
|
|
20
|
+
It helps you turn prompt behavior into repo-native assets that your application can load, validate, compose, compile, and render into provider-specific request bodies.
|
|
21
|
+
|
|
22
|
+
Use GitHub Models when you want:
|
|
23
|
+
|
|
24
|
+
- a GitHub-hosted prompt editor and playground
|
|
25
|
+
- side-by-side model comparisons
|
|
26
|
+
- structured evaluations inside GitHub
|
|
27
|
+
- `.prompt.yml` files for prompt experiments and evals
|
|
28
|
+
|
|
29
|
+
Use PromptOpsKit when you want:
|
|
30
|
+
|
|
31
|
+
- Markdown prompt assets with YAML metadata
|
|
32
|
+
- production input hardening and validation
|
|
33
|
+
- environment-specific model and parameter overrides
|
|
34
|
+
- reusable prompt composition
|
|
35
|
+
- compiled artifacts for deployment
|
|
36
|
+
- provider-specific request bodies for your own runtime code
|
|
37
|
+
- control over SDKs, auth, retries, routing, observability, and billing
|
|
38
|
+
|
|
14
39
|
### Why PromptOpsKit?
|
|
15
40
|
|
|
16
41
|
- **Centralized, not scattered** — each prompt is a single Markdown file that captures prompt text, model config, tool bindings, and context rules together.
|
|
@@ -44,9 +69,12 @@ This creates:
|
|
|
44
69
|
prompts/
|
|
45
70
|
├── defaults.md # Folder-level defaults (provider, model, metadata, system instructions)
|
|
46
71
|
├── hello.md # Sample prompt with variables
|
|
47
|
-
├── hello.test.yaml # Test sidecar with sample inputs
|
|
72
|
+
├── hello.test.yaml # Test sidecar with sample inputs and hardcoded responses
|
|
48
73
|
└── shared/
|
|
49
74
|
└── tone.md # Shared system instructions (included via composition)
|
|
75
|
+
|
|
76
|
+
tests/
|
|
77
|
+
└── hello.prompt.test.mjs # Executable starter test for the hello prompt
|
|
50
78
|
```
|
|
51
79
|
|
|
52
80
|
### 2. Write a prompt
|
|
@@ -69,9 +97,7 @@ context:
|
|
|
69
97
|
reject_secrets: true
|
|
70
98
|
- name: app_context
|
|
71
99
|
max_size: 2000
|
|
72
|
-
allow_regex:
|
|
73
|
-
pattern: "^[A-Za-z0-9 _-]+$"
|
|
74
|
-
flags: "i"
|
|
100
|
+
allow_regex: /^[A-Za-z0-9 _-]+$/i
|
|
75
101
|
includes:
|
|
76
102
|
- ./shared/tone.md
|
|
77
103
|
---
|
|
@@ -137,8 +163,9 @@ Supported values for `warnings.contextSize` are `auto`, `off`, `result-only`, `c
|
|
|
137
163
|
- **Overrides** — Environment and tier-based overrides (base → env → tier → runtime)
|
|
138
164
|
- **5 provider adapters** — OpenAI (Chat), OpenAI (Responses), Anthropic, Gemini, OpenRouter — body-only output
|
|
139
165
|
- **Provider-aware input caching controls** — optional `cache` front matter maps to OpenAI prompt cache hints, Anthropic `cache_control`, and Gemini `cachedContent`
|
|
166
|
+
- **Vendor escape hatch** — optional `raw.<provider>` blocks shallow-merge unmodeled request-body fields into the final provider payload
|
|
140
167
|
- **Validation** — Zod schema validation, Levenshtein-based "did you mean?" for typos, variable usage checks
|
|
141
|
-
- **Context hardening** —
|
|
168
|
+
- **Context hardening** — copyable `/pattern/i` regex literals, structured regexes with `return_message`, and built-in `non_empty` / `reject_secrets` validators
|
|
142
169
|
- **Optional short-circuit messages** — validators can return a structured `returnMessage` instead of throwing when configured
|
|
143
170
|
- **Context size guardrails** — optional per-input `max_size` metadata with non-blocking render-time warnings
|
|
144
171
|
- **Warning controls** — top-level config can suppress or emit context size warnings differently in dev and prod
|
|
@@ -225,6 +252,48 @@ const request = openaiAdapter.render(prompt, {
|
|
|
225
252
|
|
|
226
253
|
In browser or client-side code, keep provider credentials on the server. Use the rendered request body with your own server endpoint, server action, or edge function rather than calling a provider directly from the client.
|
|
227
254
|
|
|
255
|
+
### Provider-specific fields and raw passthrough
|
|
256
|
+
|
|
257
|
+
Use normalized fields first (`sampling`, `response`, `cache`, `tools`) so prompts stay portable. `response.schema` is the neutral JSON Schema path; adapters emit it as OpenAI/OpenRouter `response_format`, OpenAI Responses `text.format`, Anthropic `output_config.format`, and Gemini `generationConfig.responseJsonSchema`.
|
|
258
|
+
|
|
259
|
+
Use `provider_options` when PromptOpsKit has a known provider-specific mapping, such as Anthropic `top_k`, Gemini's native `response_schema`, or OpenRouter routing fields.
|
|
260
|
+
|
|
261
|
+
```yaml
|
|
262
|
+
response:
|
|
263
|
+
format: json
|
|
264
|
+
schema_name: support_reply
|
|
265
|
+
schema_description: Structured support reply
|
|
266
|
+
schema:
|
|
267
|
+
type: object
|
|
268
|
+
properties:
|
|
269
|
+
answer:
|
|
270
|
+
type: string
|
|
271
|
+
provider_options:
|
|
272
|
+
openrouter:
|
|
273
|
+
provider:
|
|
274
|
+
order: ["anthropic", "openai"]
|
|
275
|
+
transforms: ["middle-out"]
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
When a provider adds a body field PromptOpsKit does not model yet, use `raw`:
|
|
279
|
+
|
|
280
|
+
```yaml
|
|
281
|
+
raw:
|
|
282
|
+
openai:
|
|
283
|
+
service_tier: flex
|
|
284
|
+
anthropic:
|
|
285
|
+
service_tier: auto
|
|
286
|
+
gemini:
|
|
287
|
+
safetySettings:
|
|
288
|
+
- category: HARM_CATEGORY_DANGEROUS_CONTENT
|
|
289
|
+
threshold: BLOCK_ONLY_HIGH
|
|
290
|
+
openrouter:
|
|
291
|
+
usage:
|
|
292
|
+
include: true
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Each adapter reads only its matching raw block and shallow-merges it into the generated request body after normalized mappings. This is intentionally an escape hatch; prefer first-class fields when they exist.
|
|
296
|
+
|
|
228
297
|
On the server, adapters also provide async prompt-aware helpers so you can use the default `./prompts` and `./.generated-prompts/json` directories without creating a `PromptOpsKit` instance:
|
|
229
298
|
|
|
230
299
|
```typescript
|
|
@@ -474,11 +543,21 @@ Hello {{ name }}!`,
|
|
|
474
543
|
## Testing Helpers
|
|
475
544
|
|
|
476
545
|
```typescript
|
|
477
|
-
import {
|
|
546
|
+
import {
|
|
547
|
+
createHardcodedPromptResponder,
|
|
548
|
+
createMockAsset,
|
|
549
|
+
createMockResolvedAsset,
|
|
550
|
+
loadPromptTestSidecar,
|
|
551
|
+
parseTestPrompt,
|
|
552
|
+
} from 'promptopskit/testing';
|
|
478
553
|
|
|
479
554
|
const asset = createMockAsset({ model: 'gpt-5.4' });
|
|
480
555
|
const resolved = createMockResolvedAsset();
|
|
481
556
|
const parsed = parseTestPrompt('---\nid: test\nschema_version: 1\n---\n\nHello');
|
|
557
|
+
|
|
558
|
+
const sidecar = await loadPromptTestSidecar('./prompts/hello.test.yaml');
|
|
559
|
+
const respond = createHardcodedPromptResponder(sidecar);
|
|
560
|
+
const response = respond('basic-greeting');
|
|
482
561
|
```
|
|
483
562
|
|
|
484
563
|
## API Reference
|
|
@@ -536,8 +615,11 @@ Prompt files use YAML front matter with these fields:
|
|
|
536
615
|
| `fallback_models` | `string[]` | Fallback model list |
|
|
537
616
|
| `reasoning` | `object` | `{ effort, budget_tokens }` |
|
|
538
617
|
| `sampling` | `object` | `{ temperature, top_p, frequency_penalty, presence_penalty, stop, max_output_tokens }` |
|
|
539
|
-
| `response` | `object` | `{ format, stream }` |
|
|
618
|
+
| `response` | `object` | `{ format, stream, schema, schema_name, schema_description, schema_strict }` |
|
|
619
|
+
| `cache` | `object` | Provider-specific cache controls (`openai`, `anthropic`, `gemini`/`google`) |
|
|
540
620
|
| `tools` | `array` | Tool references (string names or inline definitions) |
|
|
621
|
+
| `provider_options` | `object` | Provider-specific non-portable options (`anthropic`, `gemini`, `openrouter`) |
|
|
622
|
+
| `raw` | `object` | Provider-scoped request-body passthrough (`openai`, `openai-responses`, `anthropic`, `gemini`/`google`, `openrouter`) |
|
|
541
623
|
| `mcp` | `object` | MCP server references |
|
|
542
624
|
| `context` | `object` | `{ inputs, history }` — declare expected variables, with optional per-input `max_size`, `trim`, structured or literal `allow_regex`/`deny_regex`, and built-in `non_empty` / `reject_secrets` validators |
|
|
543
625
|
| `includes` | `string[]` | Paths to included prompt files |
|
|
@@ -545,6 +627,8 @@ Prompt files use YAML front matter with these fields:
|
|
|
545
627
|
| `tiers` | `object` | Named tier overrides |
|
|
546
628
|
| `metadata` | `object` | `{ owner, tags, review_required, stable }` |
|
|
547
629
|
|
|
630
|
+
For `allow_regex` and `deny_regex`, prefer unquoted `/pattern/i` literal form so regex escapes such as `\s` and `\b` stay copyable from tools like regex101. If you use structured `pattern:` form, use single-quoted YAML strings or double each backslash in double-quoted strings.
|
|
631
|
+
|
|
548
632
|
## Website
|
|
549
633
|
|
|
550
634
|
The `website/` directory contains a standalone marketing website for PromptOpsKit.
|
package/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ description: Guidance for creating and editing promptopskit prompt files, defaul
|
|
|
8
8
|
This project uses **promptopskit** to manage LLM prompts as code.
|
|
9
9
|
Prompts live in markdown files with YAML front matter, are validated against
|
|
10
10
|
a schema, and render into provider-specific request bodies (OpenAI, Anthropic,
|
|
11
|
-
Gemini, OpenRouter). Follow these instructions when creating or editing prompts.
|
|
11
|
+
Gemini, OpenRouter, and OpenAI Responses). Follow these instructions when creating or editing prompts.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -58,13 +58,16 @@ the fields required by that specific file:
|
|
|
58
58
|
| `id` | string | **yes** | Unique identifier for the prompt |
|
|
59
59
|
| `schema_version` | number | yes | Always `1` |
|
|
60
60
|
| `description` | string | no | Human-readable description |
|
|
61
|
-
| `provider` | enum | no | `openai`, `anthropic`, `google`, `gemini`, `openrouter`, or `any` |
|
|
61
|
+
| `provider` | enum | no | `openai`, `openai-responses`, `anthropic`, `google`, `gemini`, `openrouter`, or `any` |
|
|
62
62
|
| `model` | string | no | Model identifier (e.g. `gpt-5.4`, `claude-sonnet-4-20250514`) |
|
|
63
63
|
| `fallback_models` | string[] | no | Ordered fallback model list |
|
|
64
64
|
| `reasoning` | object | no | `{ effort: low|medium|high, budget_tokens: number }` |
|
|
65
65
|
| `sampling` | object | no | `{ temperature, top_p, frequency_penalty, presence_penalty, stop, max_output_tokens }` |
|
|
66
|
-
| `response` | object | no | `{ format: text|json|markdown, stream: boolean }` |
|
|
66
|
+
| `response` | object | no | `{ format: text|json|markdown, stream: boolean, schema?: object, schema_name?: string, schema_description?: string, schema_strict?: boolean }` |
|
|
67
|
+
| `cache` | object | no | Provider-specific cache controls (`openai`, `anthropic`, `gemini`/`google`) |
|
|
67
68
|
| `tools` | array | no | Tool names (strings) or inline definitions with `{ name, description, input_schema }` |
|
|
69
|
+
| `provider_options` | object | no | Provider-specific advanced options (`anthropic`, `gemini`, `openrouter`) |
|
|
70
|
+
| `raw` | object | no | Provider-scoped request-body passthrough for unmodeled vendor fields |
|
|
68
71
|
| `mcp` | object | no | `{ servers: [string | { name, config }] }` |
|
|
69
72
|
| `context.inputs` | `Array<string | { name, max_size?, trim?, allow_regex?, deny_regex?, non_empty?, reject_secrets? }>` | no | Declared variable names used in templates, with optional size budgets and runtime hardening controls |
|
|
70
73
|
| `context.history` | object | no | `{ max_items: number }` |
|
|
@@ -102,7 +105,9 @@ Rules:
|
|
|
102
105
|
- Use object-form inputs with `max_size` when a variable is likely to grow large and should trigger early warnings
|
|
103
106
|
- Use `trim` to enforce byte budgets before interpolation when `max_size` is set
|
|
104
107
|
- Use `allow_regex` for allowlist checks and `deny_regex` for blocklist checks on risky inputs
|
|
105
|
-
- Prefer
|
|
108
|
+
- Prefer unquoted `/pattern/i` literals for regex validators so backslash escapes such as `\s` and `\b` stay copyable from regex tools
|
|
109
|
+
- Use structured regexes like `{ pattern, flags, return_message }` when the validator needs a fallback message or separate flags
|
|
110
|
+
- In structured `pattern:` YAML, use single quotes for patterns with backslashes or double each backslash in double-quoted strings
|
|
106
111
|
- Use `non_empty: true` for required user text and `reject_secrets: true` for common secret redaction checks
|
|
107
112
|
- When the caller should receive a structured fallback message instead of an exception, use object form with `return_message` on `allow_regex`, `deny_regex`, `non_empty`, or `reject_secrets`
|
|
108
113
|
- Escape literal braces with `\{{` and `\}}`
|
|
@@ -124,7 +129,7 @@ At render time, callers can also pass `onContextOverflow` to transform oversized
|
|
|
124
129
|
|
|
125
130
|
If a validator declares `return_message`, `renderPrompt()` returns that message in a structured result and omits the provider request instead of throwing for that validation failure. Invalid regex definitions still fail during `validate` and `compile` as `POK013` prompt-authoring errors.
|
|
126
131
|
|
|
127
|
-
Malformed `allow_regex` and `deny_regex` values fail during `validate` and `compile`, not just at render time. When regex compilation fails, the error includes the prompt id, variable name, field name, and raw configured value.
|
|
132
|
+
Malformed `allow_regex` and `deny_regex` values fail during `validate` and `compile`, not just at render time. When regex compilation fails, the error includes the prompt id, variable name, field name, and raw configured value. Double-quoted YAML regex strings with raw backslashes fail as `POK013`; use `/pattern/i`, single-quoted `pattern: '...'`, or doubled backslashes.
|
|
128
133
|
|
|
129
134
|
Example: this is the minimal valid shape for a prompt that references
|
|
130
135
|
`{{ pull_request }}` even when provider/model are inherited from defaults:
|
|
@@ -182,6 +187,7 @@ prompts/
|
|
|
182
187
|
Supported default fields:
|
|
183
188
|
- `provider` (front matter) — default provider for the folder
|
|
184
189
|
- `model` (front matter) — default model for the folder
|
|
190
|
+
- `cache` (front matter) — default provider-specific cache hints
|
|
185
191
|
- `metadata` (front matter) — merged with prompt-local metadata
|
|
186
192
|
- `# System instructions` (body section) — used when the prompt has none
|
|
187
193
|
|
|
@@ -229,12 +235,69 @@ tiers:
|
|
|
229
235
|
```
|
|
230
236
|
|
|
231
237
|
Overridable fields: `model`, `fallback_models`, `reasoning`, `sampling`,
|
|
232
|
-
`response`, `tools`.
|
|
238
|
+
`response`, `cache`, `raw`, `tools`, `provider_options`.
|
|
233
239
|
|
|
234
240
|
Override application order: **base → environment → tier → runtime**.
|
|
235
241
|
|
|
236
242
|
---
|
|
237
243
|
|
|
244
|
+
## Provider-specific fields
|
|
245
|
+
|
|
246
|
+
Prefer portable fields first:
|
|
247
|
+
|
|
248
|
+
- Use `sampling` for common sampling controls
|
|
249
|
+
- Use `response.schema`, `response.schema_name`, `response.schema_description`, and `response.schema_strict` for structured output when possible
|
|
250
|
+
- Use `cache` for provider cache hints
|
|
251
|
+
- Use `tools` for tool definitions
|
|
252
|
+
|
|
253
|
+
Treat `response.schema` as the provider-neutral JSON Schema contract. The adapters emit it through provider-specific request fields: OpenAI/OpenRouter `response_format`, OpenAI Responses `text.format`, Anthropic `output_config.format`, and Gemini `generationConfig.responseJsonSchema`.
|
|
254
|
+
|
|
255
|
+
Use `provider_options` for known non-portable mappings:
|
|
256
|
+
|
|
257
|
+
```yaml
|
|
258
|
+
provider_options:
|
|
259
|
+
anthropic:
|
|
260
|
+
top_k: 40
|
|
261
|
+
tool_choice:
|
|
262
|
+
type: auto
|
|
263
|
+
output_config:
|
|
264
|
+
format:
|
|
265
|
+
type: json_schema
|
|
266
|
+
schema:
|
|
267
|
+
type: object
|
|
268
|
+
gemini:
|
|
269
|
+
# Use only when Gemini's native schema dialect is required.
|
|
270
|
+
response_schema:
|
|
271
|
+
type: object
|
|
272
|
+
response_json_schema:
|
|
273
|
+
type: object
|
|
274
|
+
openrouter:
|
|
275
|
+
provider:
|
|
276
|
+
order: ["anthropic", "openai"]
|
|
277
|
+
transforms: ["middle-out"]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Use `raw` only when a vendor request-body field is important and PromptOpsKit does not model it yet:
|
|
281
|
+
|
|
282
|
+
```yaml
|
|
283
|
+
raw:
|
|
284
|
+
openai:
|
|
285
|
+
service_tier: flex
|
|
286
|
+
anthropic:
|
|
287
|
+
service_tier: auto
|
|
288
|
+
gemini:
|
|
289
|
+
safetySettings:
|
|
290
|
+
- category: HARM_CATEGORY_DANGEROUS_CONTENT
|
|
291
|
+
threshold: BLOCK_ONLY_HIGH
|
|
292
|
+
openrouter:
|
|
293
|
+
usage:
|
|
294
|
+
include: true
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Raw blocks are provider-scoped (`openai`, `openai-responses`/`openai_responses`, `anthropic`, `gemini`/`google`, `openrouter`) and are shallow-merged into the final request body after normalized fields. When adding `raw`, include a short note in `# Notes` explaining why a first-class field is not being used.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
238
301
|
## Test sidecars
|
|
239
302
|
|
|
240
303
|
Create a `.test.yaml` file alongside a prompt to define test cases:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
applyRawProviderBody,
|
|
2
3
|
renderSections,
|
|
3
4
|
resolveAssetForProvider,
|
|
4
5
|
withPromptInputSupport
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XNNDGDPI.js";
|
|
6
7
|
|
|
7
8
|
// src/providers/openai-responses.ts
|
|
8
9
|
var openaiResponsesAdapter = withPromptInputSupport({
|
|
@@ -64,6 +65,7 @@ var openaiResponsesAdapter = withPromptInputSupport({
|
|
|
64
65
|
format: {
|
|
65
66
|
type: "json_schema",
|
|
66
67
|
name: resolvedAsset.response.schema_name ?? `${resolvedAsset.id}_response`,
|
|
68
|
+
...resolvedAsset.response.schema_description ? { description: resolvedAsset.response.schema_description } : {},
|
|
67
69
|
schema: resolvedAsset.response.schema,
|
|
68
70
|
strict: resolvedAsset.response.schema_strict ?? true
|
|
69
71
|
}
|
|
@@ -98,7 +100,7 @@ var openaiResponsesAdapter = withPromptInputSupport({
|
|
|
98
100
|
if (responseOptions?.include !== void 0) body.include = responseOptions.include;
|
|
99
101
|
if (responseOptions?.background !== void 0) body.background = responseOptions.background;
|
|
100
102
|
return {
|
|
101
|
-
body,
|
|
103
|
+
body: applyRawProviderBody(body, resolvedAsset, "openai-responses"),
|
|
102
104
|
provider: "openai-responses",
|
|
103
105
|
model: resolvedAsset.model ?? "unknown"
|
|
104
106
|
};
|
|
@@ -108,4 +110,4 @@ var openaiResponsesAdapter = withPromptInputSupport({
|
|
|
108
110
|
export {
|
|
109
111
|
openaiResponsesAdapter
|
|
110
112
|
};
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
113
|
+
//# sourceMappingURL=chunk-F6UJ73Y6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/openai-responses.ts"],"sourcesContent":["import type { ResolvedPromptAsset } from '../schema/index.js';\nimport type {\n ProviderAdapter,\n ProviderRequest,\n ValidationResult,\n RuntimeRenderOptions,\n} from './types.js';\nimport { renderSections } from '../renderer/index.js';\nimport { resolveAssetForProvider } from './resolve-asset.js';\nimport { withPromptInputSupport } from './prompt-input.js';\nimport { applyRawProviderBody } from './raw.js';\n\n/**\n * OpenAI Responses provider adapter.\n * Produces request bodies compatible with the OpenAI Responses API.\n */\nexport const openaiResponsesAdapter: ProviderAdapter = withPromptInputSupport({\n name: 'openai-responses',\n\n validate(asset: ResolvedPromptAsset, runtime?: RuntimeRenderOptions): ValidationResult {\n const resolvedAsset = resolveAssetForProvider(asset, runtime);\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!resolvedAsset.model) {\n errors.push('OpenAI Responses adapter requires a model to be specified.');\n }\n\n if (resolvedAsset.reasoning?.budget_tokens !== undefined) {\n warnings.push('OpenAI Responses uses reasoning.effort, not budget_tokens. budget_tokens will be ignored.');\n }\n\n if (resolvedAsset.response?.schema !== undefined && resolvedAsset.response?.format !== 'json') {\n warnings.push('OpenAI Responses response.schema requires response.format: json. schema will still be applied as JSON schema output.');\n }\n\n if (runtime?.openaiResponses?.conversation !== undefined && runtime?.openaiResponses?.previous_response_id !== undefined) {\n errors.push('OpenAI Responses options \"conversation\" and \"previous_response_id\" cannot both be set.');\n }\n\n return { valid: errors.length === 0, errors, warnings };\n },\n\n render(asset: ResolvedPromptAsset, runtime: RuntimeRenderOptions): ProviderRequest {\n const resolvedAsset = resolveAssetForProvider(asset, runtime);\n const sections = renderSections(resolvedAsset, {\n variables: runtime.variables,\n strict: runtime.strict,\n });\n const responseOptions = runtime.openaiResponses;\n\n const input: Array<Record<string, unknown>> = [];\n\n if (runtime.history) {\n for (const msg of runtime.history) {\n input.push({ role: msg.role, content: msg.content });\n }\n }\n\n if (sections.prompt_template) {\n input.push({ role: 'user', content: sections.prompt_template });\n }\n\n const body: Record<string, unknown> = {\n model: resolvedAsset.model,\n input,\n };\n\n if (responseOptions?.instructions !== undefined) {\n body.instructions = responseOptions.instructions;\n } else if (sections.system_instructions) {\n body.instructions = sections.system_instructions;\n }\n\n // Sampling params\n if (resolvedAsset.sampling?.temperature !== undefined) body.temperature = resolvedAsset.sampling.temperature;\n if (resolvedAsset.sampling?.top_p !== undefined) body.top_p = resolvedAsset.sampling.top_p;\n if (resolvedAsset.sampling?.frequency_penalty !== undefined) body.frequency_penalty = resolvedAsset.sampling.frequency_penalty;\n if (resolvedAsset.sampling?.presence_penalty !== undefined) body.presence_penalty = resolvedAsset.sampling.presence_penalty;\n if (resolvedAsset.sampling?.stop !== undefined) body.stop = resolvedAsset.sampling.stop;\n if (resolvedAsset.sampling?.max_output_tokens !== undefined) body.max_output_tokens = resolvedAsset.sampling.max_output_tokens;\n\n // Reasoning\n if (resolvedAsset.reasoning?.effort) {\n body.reasoning = { effort: resolvedAsset.reasoning.effort };\n }\n\n // Structured output / response format\n if (resolvedAsset.response?.schema) {\n body.text = {\n format: {\n type: 'json_schema',\n name: resolvedAsset.response.schema_name ?? `${resolvedAsset.id}_response`,\n ...(resolvedAsset.response.schema_description ? { description: resolvedAsset.response.schema_description } : {}),\n schema: resolvedAsset.response.schema,\n strict: resolvedAsset.response.schema_strict ?? true,\n },\n };\n } else if (resolvedAsset.response?.format === 'json') {\n body.text = { format: { type: 'json_object' } };\n }\n\n // Streaming\n if (resolvedAsset.response?.stream !== undefined) {\n body.stream = resolvedAsset.response.stream;\n }\n\n // Tools\n if (resolvedAsset.tools && resolvedAsset.tools.length > 0) {\n body.tools = resolvedAsset.tools.map((tool) => {\n if (typeof tool === 'string') {\n const def = runtime.toolRegistry?.[tool];\n if (def) return def;\n return { type: 'function', name: tool };\n }\n return {\n type: 'function',\n name: tool.name,\n description: tool.description,\n parameters: tool.input_schema,\n };\n });\n }\n\n if (responseOptions?.previous_response_id !== undefined) body.previous_response_id = responseOptions.previous_response_id;\n if (responseOptions?.conversation !== undefined) body.conversation = responseOptions.conversation;\n if (responseOptions?.parallel_tool_calls !== undefined) body.parallel_tool_calls = responseOptions.parallel_tool_calls;\n if (responseOptions?.max_tool_calls !== undefined) body.max_tool_calls = responseOptions.max_tool_calls;\n if (responseOptions?.store !== undefined) body.store = responseOptions.store;\n if (responseOptions?.metadata !== undefined) body.metadata = responseOptions.metadata;\n if (responseOptions?.include !== undefined) body.include = responseOptions.include;\n if (responseOptions?.background !== undefined) body.background = responseOptions.background;\n\n return {\n body: applyRawProviderBody(body, resolvedAsset, 'openai-responses'),\n provider: 'openai-responses',\n model: resolvedAsset.model ?? 'unknown',\n };\n },\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
applyRawProviderBody,
|
|
2
3
|
renderSections,
|
|
3
4
|
resolveAssetForProvider,
|
|
4
5
|
withPromptInputSupport
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XNNDGDPI.js";
|
|
6
7
|
|
|
7
8
|
// src/providers/anthropic.ts
|
|
8
9
|
var anthropicAdapter = withPromptInputSupport({
|
|
@@ -23,8 +24,8 @@ var anthropicAdapter = withPromptInputSupport({
|
|
|
23
24
|
if (resolvedAsset.reasoning?.effort !== void 0) {
|
|
24
25
|
warnings.push("Anthropic uses budget_tokens for thinking, not effort. effort will be mapped approximately.");
|
|
25
26
|
}
|
|
26
|
-
if (resolvedAsset.response?.schema !== void 0) {
|
|
27
|
-
warnings.push("Anthropic
|
|
27
|
+
if (resolvedAsset.response?.schema !== void 0 && resolvedAsset.response?.format !== "json") {
|
|
28
|
+
warnings.push("Anthropic response.schema is mapped to output_config.format and should usually be paired with response.format: json.");
|
|
28
29
|
}
|
|
29
30
|
if (resolvedAsset.provider_options?.anthropic?.top_k !== void 0 && resolvedAsset.provider_options.anthropic.top_k < 0) {
|
|
30
31
|
errors.push("Anthropic provider_options.top_k must be >= 0.");
|
|
@@ -88,6 +89,16 @@ var anthropicAdapter = withPromptInputSupport({
|
|
|
88
89
|
if (resolvedAsset.provider_options?.anthropic?.top_k !== void 0) {
|
|
89
90
|
body.top_k = resolvedAsset.provider_options.anthropic.top_k;
|
|
90
91
|
}
|
|
92
|
+
if (resolvedAsset.provider_options?.anthropic?.output_config !== void 0) {
|
|
93
|
+
body.output_config = resolvedAsset.provider_options.anthropic.output_config;
|
|
94
|
+
} else if (resolvedAsset.response?.schema !== void 0) {
|
|
95
|
+
body.output_config = {
|
|
96
|
+
format: {
|
|
97
|
+
type: "json_schema",
|
|
98
|
+
schema: resolvedAsset.response.schema
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
91
102
|
if (resolvedAsset.response?.stream !== void 0) {
|
|
92
103
|
body.stream = resolvedAsset.response.stream;
|
|
93
104
|
}
|
|
@@ -121,7 +132,7 @@ var anthropicAdapter = withPromptInputSupport({
|
|
|
121
132
|
body.tool_choice = resolvedAsset.provider_options.anthropic.tool_choice;
|
|
122
133
|
}
|
|
123
134
|
return {
|
|
124
|
-
body,
|
|
135
|
+
body: applyRawProviderBody(body, resolvedAsset, "anthropic"),
|
|
125
136
|
provider: "anthropic",
|
|
126
137
|
model: resolvedAsset.model ?? "unknown"
|
|
127
138
|
};
|
|
@@ -131,4 +142,4 @@ var anthropicAdapter = withPromptInputSupport({
|
|
|
131
142
|
export {
|
|
132
143
|
anthropicAdapter
|
|
133
144
|
};
|
|
134
|
-
//# sourceMappingURL=chunk-
|
|
145
|
+
//# sourceMappingURL=chunk-HLNVKKMS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/anthropic.ts"],"sourcesContent":["import type { ResolvedPromptAsset } from '../schema/index.js';\nimport type {\n ProviderAdapter,\n ProviderRequest,\n ValidationResult,\n RuntimeRenderOptions,\n} from './types.js';\nimport { renderSections } from '../renderer/index.js';\nimport { resolveAssetForProvider } from './resolve-asset.js';\nimport { withPromptInputSupport } from './prompt-input.js';\nimport { applyRawProviderBody } from './raw.js';\n\n/**\n * Anthropic provider adapter.\n * Produces request bodies compatible with the Anthropic Messages API.\n */\nexport const anthropicAdapter: ProviderAdapter = withPromptInputSupport({\n name: 'anthropic',\n\n validate(asset: ResolvedPromptAsset, runtime?: RuntimeRenderOptions): ValidationResult {\n const resolvedAsset = resolveAssetForProvider(asset, runtime);\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!resolvedAsset.model) {\n errors.push('Anthropic adapter requires a model to be specified.');\n }\n\n if (resolvedAsset.sampling?.frequency_penalty !== undefined) {\n warnings.push('Anthropic does not support frequency_penalty. It will be ignored.');\n }\n if (resolvedAsset.sampling?.presence_penalty !== undefined) {\n warnings.push('Anthropic does not support presence_penalty. It will be ignored.');\n }\n if (resolvedAsset.reasoning?.effort !== undefined) {\n warnings.push('Anthropic uses budget_tokens for thinking, not effort. effort will be mapped approximately.');\n }\n if (resolvedAsset.response?.schema !== undefined && resolvedAsset.response?.format !== 'json') {\n warnings.push('Anthropic response.schema is mapped to output_config.format and should usually be paired with response.format: json.');\n }\n\n if (resolvedAsset.provider_options?.anthropic?.top_k !== undefined && resolvedAsset.provider_options.anthropic.top_k < 0) {\n errors.push('Anthropic provider_options.top_k must be >= 0.');\n }\n\n return { valid: errors.length === 0, errors, warnings };\n },\n\n render(asset: ResolvedPromptAsset, runtime: RuntimeRenderOptions): ProviderRequest {\n const resolvedAsset = resolveAssetForProvider(asset, runtime);\n const sections = renderSections(resolvedAsset, {\n variables: runtime.variables,\n strict: runtime.strict,\n });\n\n const messages: Array<Record<string, unknown>> = [];\n const anthropicCacheConfig = resolvedAsset.cache?.anthropic;\n const cacheType = anthropicCacheConfig?.type ?? 'ephemeral';\n const cacheControl = anthropicCacheConfig\n ? {\n type: cacheType,\n ...(anthropicCacheConfig.ttl ? { ttl: anthropicCacheConfig.ttl } : {}),\n }\n : undefined;\n const cacheMode = anthropicCacheConfig?.mode ?? 'automatic';\n\n // History\n if (runtime.history) {\n for (const msg of runtime.history) {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n\n // User message (prompt template)\n if (sections.prompt_template) {\n if (cacheControl && cacheMode === 'explicit' && anthropicCacheConfig?.cache_prompt_template) {\n messages.push({\n role: 'user',\n content: [{ type: 'text', text: sections.prompt_template, cache_control: cacheControl }],\n });\n } else {\n messages.push({ role: 'user', content: sections.prompt_template });\n }\n }\n\n const body: Record<string, unknown> = {\n model: resolvedAsset.model,\n messages,\n };\n\n // System goes as top-level field in Anthropic\n if (sections.system_instructions) {\n if (cacheControl && cacheMode === 'explicit' && anthropicCacheConfig?.cache_system_instructions !== false) {\n body.system = [{ type: 'text', text: sections.system_instructions, cache_control: cacheControl }];\n } else {\n body.system = sections.system_instructions;\n }\n }\n\n // Sampling params\n if (resolvedAsset.sampling?.temperature !== undefined) body.temperature = resolvedAsset.sampling.temperature;\n if (resolvedAsset.sampling?.top_p !== undefined) body.top_p = resolvedAsset.sampling.top_p;\n if (resolvedAsset.sampling?.stop !== undefined) body.stop_sequences = resolvedAsset.sampling.stop;\n if (resolvedAsset.sampling?.max_output_tokens !== undefined) {\n body.max_tokens = resolvedAsset.sampling.max_output_tokens;\n } else {\n // Anthropic requires max_tokens\n body.max_tokens = 4096;\n }\n\n // Thinking/reasoning\n if (resolvedAsset.reasoning?.budget_tokens) {\n body.thinking = {\n type: 'enabled',\n budget_tokens: resolvedAsset.reasoning.budget_tokens,\n };\n }\n\n // Provider-specific options\n if (resolvedAsset.provider_options?.anthropic?.top_k !== undefined) {\n body.top_k = resolvedAsset.provider_options.anthropic.top_k;\n }\n\n if (resolvedAsset.provider_options?.anthropic?.output_config !== undefined) {\n body.output_config = resolvedAsset.provider_options.anthropic.output_config;\n } else if (resolvedAsset.response?.schema !== undefined) {\n body.output_config = {\n format: {\n type: 'json_schema',\n schema: resolvedAsset.response.schema,\n },\n };\n }\n\n // Streaming\n if (resolvedAsset.response?.stream !== undefined) {\n body.stream = resolvedAsset.response.stream;\n }\n\n if (cacheControl && cacheMode === 'automatic') {\n body.cache_control = cacheControl;\n }\n\n // Tools\n if (resolvedAsset.tools && resolvedAsset.tools.length > 0) {\n body.tools = resolvedAsset.tools.map((tool) => {\n if (typeof tool === 'string') {\n const def = runtime.toolRegistry?.[tool];\n if (def) {\n if (cacheControl && cacheMode === 'explicit' && anthropicCacheConfig?.cache_tools) {\n return { ...(def as Record<string, unknown>), cache_control: cacheControl };\n }\n return def;\n }\n return {\n name: tool,\n ...(cacheControl && cacheMode === 'explicit' && anthropicCacheConfig?.cache_tools\n ? { cache_control: cacheControl }\n : {}),\n };\n }\n return {\n name: tool.name,\n description: tool.description,\n input_schema: tool.input_schema ?? { type: 'object', properties: {} },\n ...(cacheControl && cacheMode === 'explicit' && anthropicCacheConfig?.cache_tools\n ? { cache_control: cacheControl }\n : {}),\n };\n });\n }\n\n if (resolvedAsset.provider_options?.anthropic?.tool_choice !== undefined) {\n body.tool_choice = resolvedAsset.provider_options.anthropic.tool_choice;\n }\n\n return {\n body: applyRawProviderBody(body, resolvedAsset, 'anthropic'),\n provider: 'anthropic',\n model: resolvedAsset.model ?? 'unknown',\n };\n },\n});\n"],"mappings":"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","names":[]}
|
|
@@ -30,23 +30,42 @@ var ResponseSchema = z.object({
|
|
|
30
30
|
stream: z.boolean().optional(),
|
|
31
31
|
schema: z.record(z.unknown()).optional(),
|
|
32
32
|
schema_name: z.string().optional(),
|
|
33
|
+
schema_description: z.string().optional(),
|
|
33
34
|
schema_strict: z.boolean().optional()
|
|
34
35
|
});
|
|
35
36
|
var AnthropicProviderOptionsSchema = z.object({
|
|
36
37
|
top_k: z.number().int().min(0).optional(),
|
|
37
|
-
tool_choice: z.record(z.unknown()).optional()
|
|
38
|
+
tool_choice: z.record(z.unknown()).optional(),
|
|
39
|
+
output_config: z.record(z.unknown()).optional()
|
|
38
40
|
});
|
|
39
41
|
var GeminiProviderOptionsSchema = z.object({
|
|
40
42
|
candidate_count: z.number().int().positive().optional(),
|
|
41
43
|
top_k: z.number().int().min(0).optional(),
|
|
42
44
|
seed: z.number().int().optional(),
|
|
43
45
|
response_schema: z.record(z.unknown()).optional(),
|
|
46
|
+
response_json_schema: z.record(z.unknown()).optional(),
|
|
44
47
|
response_modalities: z.array(z.string()).optional(),
|
|
45
48
|
thinking_budget_tokens: z.number().int().positive().optional()
|
|
46
49
|
});
|
|
50
|
+
var OpenRouterProviderOptionsSchema = z.object({
|
|
51
|
+
provider: z.record(z.unknown()).optional(),
|
|
52
|
+
transforms: z.array(z.string()).optional(),
|
|
53
|
+
plugins: z.array(z.record(z.unknown())).optional(),
|
|
54
|
+
models: z.array(z.string()).optional()
|
|
55
|
+
});
|
|
47
56
|
var ProviderOptionsSchema = z.object({
|
|
48
57
|
anthropic: AnthropicProviderOptionsSchema.optional(),
|
|
49
|
-
gemini: GeminiProviderOptionsSchema.optional()
|
|
58
|
+
gemini: GeminiProviderOptionsSchema.optional(),
|
|
59
|
+
openrouter: OpenRouterProviderOptionsSchema.optional()
|
|
60
|
+
});
|
|
61
|
+
var RawProviderBodySchema = z.object({
|
|
62
|
+
openai: z.record(z.unknown()).optional(),
|
|
63
|
+
"openai-responses": z.record(z.unknown()).optional(),
|
|
64
|
+
openai_responses: z.record(z.unknown()).optional(),
|
|
65
|
+
anthropic: z.record(z.unknown()).optional(),
|
|
66
|
+
gemini: z.record(z.unknown()).optional(),
|
|
67
|
+
google: z.record(z.unknown()).optional(),
|
|
68
|
+
openrouter: z.record(z.unknown()).optional()
|
|
50
69
|
});
|
|
51
70
|
var OpenAICacheSchema = z.object({
|
|
52
71
|
prompt_cache_key: z.string().min(1).optional(),
|
|
@@ -119,6 +138,7 @@ var PromptAssetOverridesSchema = z.object({
|
|
|
119
138
|
sampling: SamplingSchema.optional(),
|
|
120
139
|
response: ResponseSchema.optional(),
|
|
121
140
|
cache: CacheSchema.optional(),
|
|
141
|
+
raw: RawProviderBodySchema.optional(),
|
|
122
142
|
tools: z.array(ToolRefSchema).optional(),
|
|
123
143
|
provider_options: ProviderOptionsSchema.optional()
|
|
124
144
|
});
|
|
@@ -151,6 +171,7 @@ var PromptAssetSchema = z.object({
|
|
|
151
171
|
sampling: SamplingSchema.optional(),
|
|
152
172
|
response: ResponseSchema.optional(),
|
|
153
173
|
cache: CacheSchema.optional(),
|
|
174
|
+
raw: RawProviderBodySchema.optional(),
|
|
154
175
|
tools: z.array(ToolRefSchema).optional(),
|
|
155
176
|
provider_options: ProviderOptionsSchema.optional(),
|
|
156
177
|
mcp: MCPSchema.optional(),
|
|
@@ -204,7 +225,127 @@ function extractSections(body) {
|
|
|
204
225
|
|
|
205
226
|
// src/parser/parser.ts
|
|
206
227
|
import matter from "gray-matter";
|
|
228
|
+
|
|
229
|
+
// src/parser/frontmatter-guard.ts
|
|
230
|
+
var FRONT_MATTER_DELIMITER = /^---[ \t]*$/;
|
|
231
|
+
var REGEX_FIELD = /^(?<indent>\s*)(?<field>allow_regex|deny_regex)\s*:\s*(?<value>.*)$/;
|
|
232
|
+
var PATTERN_FIELD = /^(?<indent>\s*)pattern\s*:\s*(?<value>.*)$/;
|
|
233
|
+
var INLINE_PATTERN_FIELD = /(?:^|[{,]\s*)pattern\s*:\s*(?<value>"(?:\\.|[^"\\])*")/;
|
|
234
|
+
function assertRegexFrontMatterQuoting(content, filePath) {
|
|
235
|
+
const frontMatter = extractFrontMatter(content);
|
|
236
|
+
if (!frontMatter) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
let pendingRegex;
|
|
240
|
+
for (const [index, line] of frontMatter.lines.entries()) {
|
|
241
|
+
const lineNumber = frontMatter.startLine + index;
|
|
242
|
+
const regexMatch = line.match(REGEX_FIELD);
|
|
243
|
+
if (regexMatch?.groups) {
|
|
244
|
+
const field = regexMatch.groups.field;
|
|
245
|
+
const indent2 = regexMatch.groups.indent.length;
|
|
246
|
+
const value = regexMatch.groups.value.trim();
|
|
247
|
+
pendingRegex = value === "" ? { field, indent: indent2 } : void 0;
|
|
248
|
+
assertNoUnescapedBackslashInDoubleQuotedRegex(value, field, filePath, lineNumber);
|
|
249
|
+
assertNoUnescapedBackslashInInlinePattern(value, field, filePath, lineNumber);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (!pendingRegex) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (line.trim() === "" || line.trimStart().startsWith("#")) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const indent = line.match(/^\s*/)?.[0].length ?? 0;
|
|
259
|
+
if (indent <= pendingRegex.indent) {
|
|
260
|
+
pendingRegex = void 0;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const patternMatch = line.match(PATTERN_FIELD);
|
|
264
|
+
if (!patternMatch?.groups) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
assertNoUnescapedBackslashInDoubleQuotedRegex(
|
|
268
|
+
patternMatch.groups.value.trim(),
|
|
269
|
+
`${pendingRegex.field}.pattern`,
|
|
270
|
+
filePath,
|
|
271
|
+
lineNumber
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function assertNoUnescapedBackslashInInlinePattern(value, field, filePath, lineNumber) {
|
|
276
|
+
const match = value.match(INLINE_PATTERN_FIELD);
|
|
277
|
+
const pattern = match?.groups?.value;
|
|
278
|
+
if (!pattern) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
assertNoUnescapedBackslashInDoubleQuotedRegex(pattern, `${field}.pattern`, filePath, lineNumber);
|
|
282
|
+
}
|
|
283
|
+
function extractFrontMatter(content) {
|
|
284
|
+
const lines = content.split(/\r?\n/);
|
|
285
|
+
if (!FRONT_MATTER_DELIMITER.test(lines[0] ?? "")) {
|
|
286
|
+
return void 0;
|
|
287
|
+
}
|
|
288
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
289
|
+
if (FRONT_MATTER_DELIMITER.test(lines[index] ?? "")) {
|
|
290
|
+
return {
|
|
291
|
+
lines: lines.slice(1, index),
|
|
292
|
+
startLine: 2
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return void 0;
|
|
297
|
+
}
|
|
298
|
+
function assertNoUnescapedBackslashInDoubleQuotedRegex(value, field, filePath, lineNumber) {
|
|
299
|
+
if (!value.startsWith('"')) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const quoted = readDoubleQuotedScalar(value);
|
|
303
|
+
if (!quoted) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (!hasUnescapedBackslash(quoted)) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const location = filePath ? `${filePath}:${lineNumber}` : `line ${lineNumber}`;
|
|
310
|
+
throw new Error(
|
|
311
|
+
`POK013: Invalid context regex YAML at ${location}, field "${field}": double-quoted regex strings treat backslashes as YAML escapes. Use unquoted /pattern/i literal form, single quotes, or double each backslash.`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
function readDoubleQuotedScalar(value) {
|
|
315
|
+
let result = "";
|
|
316
|
+
for (let index = 1; index < value.length; index += 1) {
|
|
317
|
+
const char = value[index];
|
|
318
|
+
if (char === '"') {
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
result += char;
|
|
322
|
+
if (char === "\\" && index + 1 < value.length) {
|
|
323
|
+
index += 1;
|
|
324
|
+
result += value[index];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return void 0;
|
|
328
|
+
}
|
|
329
|
+
function hasUnescapedBackslash(value) {
|
|
330
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
331
|
+
if (value[index] !== "\\") {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
let count = 1;
|
|
335
|
+
while (value[index + count] === "\\") {
|
|
336
|
+
count += 1;
|
|
337
|
+
}
|
|
338
|
+
if (count % 2 === 1) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
index += count - 1;
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/parser/parser.ts
|
|
207
347
|
function parsePrompt(content, filePath) {
|
|
348
|
+
assertRegexFrontMatterQuoting(content, filePath);
|
|
208
349
|
const { data: frontMatter, content: body } = matter(content);
|
|
209
350
|
const sections = extractSections(body);
|
|
210
351
|
const raw = {
|
|
@@ -322,4 +463,4 @@ export {
|
|
|
322
463
|
parsePrompt,
|
|
323
464
|
loadPromptFile
|
|
324
465
|
};
|
|
325
|
-
//# sourceMappingURL=chunk-
|
|
466
|
+
//# sourceMappingURL=chunk-JBLUPKY4.js.map
|