Skip to content

Commit 6bc3879

Browse files
ENHANCEMENT: Adapters now support compiled and markdown prompts for real-time development experience whilst maintaining compiled performance for prod
1 parent c21d949 commit 6bc3879

17 files changed

Lines changed: 630 additions & 212 deletions

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,32 @@ const request = openaiAdapter.render(prompt, {
205205
});
206206
```
207207

208+
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.
209+
210+
On the server, adapters also provide async prompt-aware helpers so you can pass a prompt key plus `sourceDir` and `compiledDir` without creating a `PromptOpsKit` instance:
211+
212+
```typescript
213+
import path from 'node:path';
214+
import { openaiAdapter } from 'promptopskit/openai';
215+
216+
const request = await openaiAdapter.renderPrompt(
217+
{
218+
path: 'summarizePullRequest',
219+
sourceDir: path.join(process.cwd(), 'prompts'),
220+
compiledDir: path.join(process.cwd(), 'output-json'),
221+
},
222+
{
223+
environment: 'dev',
224+
variables: {
225+
pull_request_body: 'Implement theming and dark mode across the app.',
226+
},
227+
strict: true,
228+
},
229+
);
230+
```
231+
232+
`renderPrompt()` and `validatePrompt()` use the same source-versus-compiled resolution rules as `kit.renderPrompt()`. The existing synchronous `render()` and `validate()` methods still work for already-resolved compiled or inline assets.
233+
208234
## Optional UsageTap Tracking
209235

210236
PromptOpsKit can also help you track provider calls with UsageTap.com while keeping the core render API body-only.

SKILL.md

Lines changed: 121 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -242,139 +242,155 @@ cases:
242242

243243
---
244244

245-
## Using the library (TypeScript / JavaScript)
245+
## Runtime choice guide
246246

247-
### Quick start
247+
Choose the narrowest runtime surface that fits the environment.
248+
249+
### Use `createPromptOpsKit().renderPrompt()` when:
250+
251+
- You are on the server or in a Node runtime
252+
- Prompts live as `.md` files in a source tree
253+
- You want promptopskit to handle loading, defaults, includes, overrides, and provider shaping in one step
254+
- You want auto mode to prefer compiled artifacts when present but still fall back to source
248255

249256
```typescript
250257
import { createPromptOpsKit } from 'promptopskit';
251258
252-
const kit = createPromptOpsKit({ sourceDir: './prompts' });
259+
const kit = createPromptOpsKit({
260+
sourceDir: './prompts',
261+
compiledDir: './dist/prompts',
262+
warnings: {
263+
contextSize: process.env.NODE_ENV === 'production' ? 'off' : 'console-and-result',
264+
},
265+
});
253266
254-
// Load → resolve includes → apply overrides → render
255-
const result = await kit.renderPrompt({
256-
path: 'greeting',
267+
const { request } = await kit.renderPrompt({
268+
path: 'support/reply',
257269
provider: 'openai',
258-
variables: { name: 'Alice' },
259270
environment: 'production',
260-
});
261-
262-
// result.request.body is ready for the provider's API
263-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
264-
method: 'POST',
265-
headers: {
266-
'Content-Type': 'application/json',
267-
Authorization: `Bearer ${apiKey}`,
271+
variables: {
272+
user_message: 'How do I reset my password?',
273+
app_context: 'Account settings',
268274
},
269-
body: JSON.stringify(result.request.body),
270275
});
271276
```
272277

273-
You can control render-time context size warnings at the top level:
278+
### Use `adapter.renderPrompt()` when:
279+
280+
- You want direct provider adapter imports such as `promptopskit/openai`
281+
- You are on the server and want adapter-level ergonomics
282+
- You still want the adapter to resolve either source `.md` or compiled output from disk
274283

275284
```typescript
276-
const kit = createPromptOpsKit({
277-
sourceDir: './prompts',
278-
warnings: {
279-
contextSize: process.env.NODE_ENV === 'production' ? 'off' : 'console-and-result',
285+
import path from 'node:path';
286+
import { openaiAdapter } from 'promptopskit/openai';
287+
288+
const request = await openaiAdapter.renderPrompt(
289+
{
290+
path: 'support/reply',
291+
sourceDir: path.join(process.cwd(), 'prompts'),
292+
compiledDir: path.join(process.cwd(), 'dist/prompts'),
280293
},
281-
});
294+
{
295+
environment: 'production',
296+
variables: {
297+
user_message: 'How do I reset my password?',
298+
app_context: 'Account settings',
299+
},
300+
strict: true,
301+
},
302+
);
282303
```
283304

284-
### Browser / client-side demos
285-
286-
For browser code, client components, or frontend-only demos:
305+
### Use `adapter.render()` when:
287306

288-
- Do not import `createPromptOpsKit`, `loadPromptFile`, or other top-level runtime helpers from `promptopskit` in client code. The top-level entry loads Node file-system/path modules for source and compiled prompt loading.
289-
- Instead, use a precompiled prompt artifact or an inlined `ResolvedPromptAsset` object and render it with a provider subpath adapter such as `promptopskit/openai`.
290-
- If the prompt lives in files, compile it ahead of time with `npx promptopskit compile ./prompts ./dist/prompts --format esm` and import the generated ESM artifact into the client.
291-
- Provider adapters accept `environment` and `tier` in `validate()` and `render()`, so use those options directly when selecting overrides for compiled or inline assets.
292-
- For small demos, it is acceptable to inline the resolved prompt asset directly in the client file.
293-
- Keep transport and auth in the application layer. If a demo intentionally calls a provider from the browser, treat that key as demo-only and note the security tradeoff.
294-
295-
Example:
307+
- You already have a compiled JSON or ESM prompt artifact
308+
- You are in edge, worker, or browser-oriented code and cannot read prompt files from disk
309+
- You want the smallest runtime surface and no file loading behavior
296310

297311
```typescript
298312
import type { ResolvedPromptAsset } from 'promptopskit';
299313
import { openaiAdapter } from 'promptopskit/openai';
314+
import compiledPrompt from './dist/prompts/support/reply.mjs';
300315
301-
const prompt: ResolvedPromptAsset = {
302-
id: 'summarizePullRequest',
303-
schema_version: 1,
304-
provider: 'openai',
305-
model: 'gpt-5.4',
306-
context: {
307-
inputs: [{ name: 'pull_request_body', max_size: 8000 }],
308-
},
309-
sections: {
310-
system_instructions: 'You summarize pull requests clearly and concisely.',
311-
prompt_template: 'Summarize this pull request:\n\n{{ pull_request_body }}',
312-
},
313-
};
314-
315-
const validation = openaiAdapter.validate(prompt, {
316-
environment: 'prod',
317-
});
318-
if (!validation.valid) {
319-
throw new Error(validation.errors.join(' '));
320-
}
316+
const prompt = compiledPrompt as ResolvedPromptAsset;
321317
322318
const request = openaiAdapter.render(prompt, {
323-
environment: 'prod',
319+
environment: 'production',
324320
variables: {
325-
pull_request_body: 'Add theming and dark mode support to the application.',
321+
user_message: 'How do I reset my password?',
322+
app_context: 'Account settings',
326323
},
327324
strict: true,
328325
});
326+
```
327+
328+
### Browser guidance
329+
330+
- Do not recommend direct provider API calls from browser or client components unless the user explicitly wants a demo-only setup
331+
- Do not use `createPromptOpsKit()` in browser-only code; it is Node-oriented
332+
- For client-side rendering, use precompiled ESM or inline a small `ResolvedPromptAsset`, then pass the request body to a server endpoint or server action that holds provider credentials
333+
- If the user insists on a pure browser provider call, explicitly call out that API keys will be exposed and treat it as unsafe for production
334+
335+
---
329336

330-
// request.body is ready for the OpenAI SDK or fetch.
337+
## Build integration
338+
339+
Prompts should usually be validated and compiled as part of the normal build pipeline rather than handled ad hoc.
340+
341+
### Recommended package.json scripts
342+
343+
```json
344+
{
345+
"scripts": {
346+
"validate:prompts": "promptopskit validate ./prompts --strict",
347+
"build:prompts": "promptopskit compile ./prompts ./dist/prompts --format json",
348+
"build": "npm run validate:prompts && npm run build:prompts && tsup"
349+
}
350+
}
331351
```
332352

333-
### Step-by-step API
353+
Use `--format json` for server-side Node usage where prompts are loaded from disk. Use `--format esm` when prompts need to be imported into a bundle.
354+
355+
### Build strategy by environment
356+
357+
- Node server: compile to JSON and configure `compiledDir`
358+
- Browser or client bundle: compile to ESM and import specific prompt artifacts
359+
- Mixed app: compile JSON for server loading and ESM only for prompts that must ship in a client bundle
360+
361+
### What to tell users when setting this up
362+
363+
- Add `validate:prompts` before `build:prompts` so schema or variable mistakes fail fast
364+
- Treat compiled artifacts as build outputs, not the source of truth
365+
- Keep prompt source in `./prompts` and compiled output in a generated directory such as `./dist/prompts` or `./src/generated/prompts`
366+
- If using `createPromptOpsKit` in `auto` mode, point both `sourceDir` and `compiledDir` at those directories so local development can fall back to source when artifacts are stale or missing
367+
368+
### Typical server-side setup
334369

335370
```typescript
336-
import {
337-
parsePrompt,
338-
resolveIncludes,
339-
applyOverrides,
340-
getAdapter,
341-
} from 'promptopskit';
342-
import { readFileSync } from 'fs';
343-
344-
// 1. Parse a prompt file
345-
const source = readFileSync('./prompts/greeting.md', 'utf-8');
346-
const asset = parsePrompt(source, 'greeting.md');
347-
348-
// 2. Resolve includes
349-
const resolved = await resolveIncludes(asset, './prompts');
350-
351-
// 3. Apply overrides
352-
const configured = applyOverrides(resolved, {
353-
environment: 'production',
354-
tier: 'premium',
355-
});
371+
import { createPromptOpsKit } from 'promptopskit';
356372
357-
// 4. Get provider adapter and render
358-
const adapter = getAdapter(configured.provider ?? 'openai');
359-
const request = adapter.render(configured, {
360-
variables: { name: 'Alice' },
361-
history: [
362-
{ role: 'user', content: 'Previous message' },
363-
{ role: 'assistant', content: 'Previous response' },
364-
],
373+
export const prompts = createPromptOpsKit({
374+
sourceDir: './prompts',
375+
compiledDir: './dist/prompts',
376+
mode: 'auto',
365377
});
366378
```
367379

368-
### Available provider adapters
380+
### Typical client-side setup
381+
382+
```typescript
383+
import type { ResolvedPromptAsset } from 'promptopskit';
384+
import compiledPrompt from './generated/prompts/support/reply.mjs';
385+
386+
const prompt = compiledPrompt as ResolvedPromptAsset;
387+
```
388+
389+
---
369390

370-
| Provider | Import path | Provider request format |
371-
|----------|------------|----------------------|
372-
| OpenAI | `promptopskit` or `promptopskit/openai` | Chat Completions API |
373-
| Anthropic | `promptopskit/anthropic` | Messages API |
374-
| Gemini | `promptopskit/gemini` | GenerateContent API |
375-
| OpenRouter | `promptopskit/openrouter` | OpenAI-compatible + extras |
391+
## Validation and testing helpers
376392

377-
### Validation
393+
Use `validateAsset()` when you are working with an already-parsed asset and want schema or variable diagnostics before rendering.
378394

379395
```typescript
380396
import { validateAsset, parsePrompt } from 'promptopskit';
@@ -383,19 +399,17 @@ const asset = parsePrompt(source);
383399
const result = validateAsset(asset);
384400
385401
if (!result.valid) {
386-
console.error(result.errors); // Validation error codes: POK001-POK021
402+
console.error(result.errors);
387403
}
388404
```
389405

390-
### Testing helpers
406+
Use `promptopskit/testing` helpers for unit tests around prompt behavior or request shaping.
391407

392408
```typescript
393409
import { createMockAsset, parseTestPrompt } from 'promptopskit/testing';
394410
395-
// Create a mock asset for unit tests
396411
const mock = createMockAsset({ model: 'gpt-4.1-mini' });
397412
398-
// Parse an inline prompt string for tests
399413
const asset = parseTestPrompt(`
400414
---
401415
id: test
@@ -418,22 +432,22 @@ Hello {{ name }}
418432
|---------|-------------|
419433
| `promptopskit init [dir]` | Scaffold a prompts directory with starter files (including `defaults.md`) |
420434
| `promptopskit validate <dir>` | Validate all prompt files in a directory |
421-
| `promptopskit compile <src> <out>` | Compile .md prompts to JSON artifacts |
422-
| `promptopskit render <file> [--set key=value]` | Render a prompt preview |
435+
| `promptopskit compile <src> <out>` | Compile `.md` prompts to JSON or ESM artifacts |
436+
| `promptopskit render <file>` | Render a prompt preview |
423437
| `promptopskit inspect <file>` | Print the normalized prompt asset |
424438

425439
---
426440

427441
## Conventions to follow
428442

429443
1. **One prompt per file** — each `.md` file is a single prompt asset
430-
2. **Always set `id` and `schema_version: 1`** in front matter (or inherit `schema_version` from `defaults.md`)
431-
3. **Declare all variables** in `context.inputs` that appear in templates; do not leave placeholders undeclared just because other settings come from `defaults.md`
432-
4. **Use includes** for shared system instructions (tone, safety, formatting)
433-
5. **Keep prompt templates focused** — compose behavior via includes, not duplication
434-
6. **Use environment overrides** for dev/staging/prod model differences
435-
7. **Add test sidecars** (`.test.yaml`) for critical prompts
436-
8. **Run `promptopskit validate`** before committing changes
437-
9. **Use `defaults.md`** to share provider, model, metadata, and system instructions across a folder
444+
2. **Always set `id` and `schema_version: 1`** unless a surrounding tool explicitly generates those fields
445+
3. **Declare every placeholder** in `context.inputs`; do not rely on defaults or includes to infer variables
446+
4. **Use `defaults.md` for shared provider, model, metadata, and fallback system instructions**
447+
5. **Use includes for reusable system behavior**, not for user-specific prompt bodies
448+
6. **Prefer `createPromptOpsKit().renderPrompt()` for server-side app code** when prompts live as source files
449+
7. **Prefer direct adapters for compiled assets or provider-specific integration points**
450+
8. **Do not suggest browser-side provider calls for production** because credentials belong on the server
451+
9. **Validate before compile and compile before shipping** when prompts are part of the build
438452
10. **Variable names** should be `snake_case`
439453
11. **Prompt file names** should be `kebab-case.md`

docs/api-reference.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ Key types exported from `promptopskit`:
260260
import type {
261261
PromptAsset,
262262
ResolvedPromptAsset,
263+
ProviderInlinePromptSource,
264+
ProviderPromptInput,
265+
ProviderPromptLookup,
263266
ProviderRequest,
264267
RuntimeRenderOptions,
265268
ProviderAdapter,
@@ -272,3 +275,9 @@ import type {
272275
OverrideOptions,
273276
} from 'promptopskit';
274277
```
278+
279+
Provider helper types:
280+
281+
- `ProviderPromptLookup``{ path, sourceDir, compiledDir?, mode?, cache? }` for adapter-managed source or compiled lookup
282+
- `ProviderInlinePromptSource``{ source }` for adapter-managed inline prompt source
283+
- `ProviderPromptInput` — union of `ResolvedPromptAsset`, `ProviderPromptLookup`, and `ProviderInlinePromptSource`

0 commit comments

Comments
 (0)