@aparte/provider-scenario 0.16.6 → 0.16.7
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import type { AparteAIModel, AparteAIProvider, AparteChatRequest, AparteStreamEvent, AparteUsage } from '@aparte/core';
|
|
26
26
|
/** One thing the scripted model does, in order. */
|
|
27
27
|
export type ScenarioStep =
|
|
28
|
-
/** Stream this text, chunk by chunk at the configured pace.
|
|
28
|
+
/** Stream this text, chunk by chunk at the configured pace. Rendered as markdown once a markdown plugin is installed (plain text otherwise — core ships no markdown renderer); a tagged block (`<artifact>`…) becomes its segment once a grammar for it is registered (`@aparte/plugin-artifacts` does). */
|
|
29
29
|
{
|
|
30
30
|
text: string;
|
|
31
31
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EACR,aAAa,EACb,gBAAgB,EAEhB,iBAAiB,EAEjB,iBAAiB,EACjB,WAAW,EACd,MAAM,cAAc,CAAC;AAItB,mDAAmD;AACnD,MAAM,MAAM,YAAY;AACpB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EACR,aAAa,EACb,gBAAgB,EAEhB,iBAAiB,EAEjB,iBAAiB,EACjB,WAAW,EACd,MAAM,cAAc,CAAC;AAItB,mDAAmD;AACnD,MAAM,MAAM,YAAY;AACpB,4SAA4S;AAC1S;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,qDAAqD;GACnD;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE;AACtB,wGAAwG;GACtG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE;AAChE,oFAAoF;GAClF;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE;AACnB,0DAA0D;GACxD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,yEAAyE;GACvE;IAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;CAAE,CAAC;AAEtC,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,EAAE,CAAC;AAEnD,yDAAyD;AACzD,MAAM,WAAW,QAAQ;IACrB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;CACtB;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC3B,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACpC,+FAA+F;IAC/F,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,YAAY,CAAC,CAAC;IACpD,mGAAmG;IACnG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;IAChG,iFAAiF;IACjF,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,6FAA6F;IAC7F,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AA8BD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,MAAM,GAAG,SAAS,CAehH;AAkBD;;;;GAIG;AACH,wBAAgB,QAAQ,CACpB,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,GAAE,cAAc,GAAG,SAAc,EACvC,MAAM,CAAC,EAAE,WAAW,GACrB,cAAc,CAAC,iBAAiB,CAAC,CAoDnC;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CAoF9F;AAED,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -203,6 +203,26 @@ function playTurn(turn, request, pacing = {}, signal) {
|
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
205
|
function createScenarioProvider(options = {}) {
|
|
206
|
+
if (options.scenarios && !options.match) {
|
|
207
|
+
const values = Object.values(options.scenarios);
|
|
208
|
+
const asScenario = (v) => typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
209
|
+
const routed = new Set(values.map((v) => asScenario(v)?.after).filter(Boolean));
|
|
210
|
+
const orphans = /* @__PURE__ */ new Set();
|
|
211
|
+
for (const v of values) {
|
|
212
|
+
const turn = asScenario(v)?.turn ?? v;
|
|
213
|
+
const steps = Array.isArray(turn) ? turn : [];
|
|
214
|
+
for (const step of steps) {
|
|
215
|
+
if (typeof step === "object" && step !== null && "tool" in step && typeof step.tool === "string" && !routed.has(step.tool)) {
|
|
216
|
+
orphans.add(step.tool);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (orphans.size > 0) {
|
|
221
|
+
console.warn(
|
|
222
|
+
`[scenario] No \`after\` route for: ${[...orphans].map((t) => `\`${t}\``).join(", ")}. A scenario calls the tool, but nothing answers its result — the default match sends it back through the same \`when\`, and the conversation loops until the client's maxTurns stops it. Declare a scenario with \`after: '<tool>'\` for each.`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
206
226
|
const id = options.id ?? "scenario";
|
|
207
227
|
const name = options.name ?? "Scripted model";
|
|
208
228
|
const models = options.models ?? [{
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/presets.ts","../src/index.ts"],"sourcesContent":["import type { Scenario } from './index.js';\n\n/**\n * A ready-made set of scenarios that shows the whole surface of a chat: markdown, a\n * code block, reasoning, a tool round-trip, a typed question, an artifact, an error.\n * The docs' live frames and a demo page can register it as is:\n *\n * ```ts\n * aparteGlobalConfig.registerAIProvider(createScenarioProvider({ scenarios: showcase }));\n * ```\n *\n * The tool names it calls — `get_weather`, `ask_user` — only do something if the app\n * registered a tool of that name; `@aparte/plugin-ask-user` provides the second.\n */\nexport const showcase: Record<string, Scenario> = {\n default: {\n turn: 'Hi — I am a **scripted** model: no key, no network, the same answer every time.\\n\\nAsk me for a *haiku*, a *table*, some *code*, the *weather*, or to *ask you a question*.',\n },\n haiku: {\n when: /haiku/i,\n turn: 'Web components hum — \\nno framework in the wind, \\njust the page, alive.',\n },\n table: {\n when: /table/i,\n turn: 'Two ways to reach a model:\\n\\n| Transport | Where the key lives | Use it for |\\n|---|---|---|\\n| `AparteDirectTransport` | in the browser | prototypes, local models |\\n| `AparteBackendTransport` | on your server | production |\\n\\nThe component does not change between the two.',\n },\n code: {\n when: /code|example|snippet/i,\n turn: [\n { thinking: 'The shortest useful wiring is three lines: register a provider, set a transport, start the client.' },\n { text: 'The minimal wiring:\\n\\n```ts\\nimport { AparteClient, aparteGlobalConfig, AparteDirectTransport } from \\'@aparte/core\\';\\nimport { createScenarioProvider } from \\'@aparte/provider-scenario\\';\\n\\naparteGlobalConfig.registerAIProvider(createScenarioProvider({ turns: [\\'Hello!\\'] }));\\naparteGlobalConfig.setTransport(new AparteDirectTransport({ byok: true }));\\nnew AparteClient().start();\\n```\\n\\nThree lines that matter.' },\n ],\n },\n weather: {\n when: /weather|forecast/i,\n turn: [\n { text: 'Let me check that for you.' },\n { tool: 'get_weather', input: { city: 'Lille' } },\n ],\n },\n forecast: {\n after: 'get_weather',\n turn: 'Cloudy, 14 °C, a little wind from the west — bring a jacket.',\n },\n // Several questions in one call: the panel becomes a stepper (1 2 …) with a\n // \"Skip\" per step. Nothing in the repository showed that mode until a consumer\n // reported clipped borders on it — a state no example renders is a state\n // nobody looks at. BEFORE `question`: scenarios are matched in order, and\n // \"two questions\" contains the word that one listens for.\n survey: {\n when: /survey|two questions|a few questions/i,\n turn: [\n { text: 'Two quick ones, then I can tailor it.' },\n {\n tool: 'ask_user',\n input: {\n questions: [\n {\n question: 'Which colour do you favour?',\n header: 'Colour',\n options: [\n { title: 'Blue', description: 'calm, trusted' },\n { title: 'Green', description: 'fresh, natural' },\n { title: 'Yellow', description: 'warm, loud' },\n ],\n },\n {\n question: 'How dense should the layout be?',\n header: 'Density',\n options: [\n { title: 'Airy', description: 'more room, fewer items per screen', recommended: true },\n { title: 'Compact', description: 'more on screen, tighter spacing' },\n ],\n },\n ],\n },\n },\n ],\n },\n question: {\n when: /question|ask me|colou?r scheme/i,\n turn: [\n { text: 'One thing first.' },\n {\n tool: 'ask_user',\n input: {\n questions: [{\n question: 'Which mood for your landing page?',\n header: 'Mood',\n options: [\n { title: 'Dark and quiet', description: 'ink, brass accents', recommended: true },\n { title: 'Light and warm', description: 'cream, soft contrast' },\n { title: 'Maximum contrast', description: 'strict black and white' },\n ],\n }],\n },\n },\n ],\n },\n answered: {\n after: 'ask_user',\n turn: 'Noted — I will go with that.',\n },\n artifact: {\n when: /artifact|document|write me/i,\n turn: 'Here is a first draft:\\n\\n<artifact type=\"text/markdown\" title=\"Launch note\">## aparté\\n\\nA chat in Web Components, with the agent loop inside.\\n\\n- any framework\\n- zero third-party dependencies\\n</artifact>\\n\\nTell me what to change.',\n },\n slow: {\n when: /slow|think hard|take your time/i,\n turn: [\n { thinking: 'This deserves a moment.' },\n { wait: 2500 },\n { text: 'Done thinking. The answer is: it depends on the transport.' },\n ],\n },\n failure: {\n when: /fail|error|crash/i,\n turn: [{ error: 'The scripted model exploded, as requested.' }],\n },\n};\n","/**\n * @aparte/provider-scenario — a scripted model.\n *\n * An `AparteAIProvider` that REPLAYS turns you wrote instead of calling a model: text at\n * a typing pace, thinking, tool calls (the real loop runs your handler and calls back\n * with the result), errors, pauses. No key, no network, no randomness.\n *\n * Three things in this repository had written it by hand before this package existed:\n * the e2e mock of the wire, the screenshot harness of the UI audit, and the docs frames\n * that seed content. It is also the piece nobody ships for CONSUMERS: a deterministic\n * model for their own tests, and a demo that streams without a backend.\n *\n * It owns its I/O — `chat()` — so `AparteDirectTransport` hands the request straight to\n * it; there is no wire format here to adapt. (This repository's own e2e keeps its\n * network mock on purpose: that suite tests the real wire path, which this provider\n * bypasses by construction.)\n *\n * ```ts\n * import { createScenarioProvider } from '@aparte/provider-scenario';\n * aparteGlobalConfig.registerAIProvider(createScenarioProvider({\n * turns: ['Hello! Ask me anything.', [{ thinking: 'Let me see…' }, { text: 'Here is my answer.' }]],\n * }));\n * ```\n */\n\nimport type {\n AparteAIModel,\n AparteAIProvider,\n AparteChatMessage,\n AparteChatRequest,\n AparteChatResponse,\n AparteStreamEvent,\n AparteUsage,\n} from '@aparte/core';\n\n// ─── What a turn is made of ──────────────────────────────────────────────────\n\n/** One thing the scripted model does, in order. */\nexport type ScenarioStep =\n /** Stream this text, chunk by chunk at the configured pace. Markdown is parsed by core; a tagged block (`<artifact>`…) is too, once a grammar for it is registered (`@aparte/plugin-artifacts` does). */\n | { text: string }\n /** Stream this as reasoning (the thinking block). */\n | { thinking: string }\n /** Call a tool. The loop runs the registered handler, then calls the provider again with the result. */\n | { tool: string; input?: Record<string, unknown>; id?: string }\n /** Fail the turn with this message — what a provider error looks like to the UI. */\n | { error: string }\n /** Pause, in milliseconds — a slow model, a long tool. */\n | { wait: number }\n /** Override the usage reported at the end (merged over the estimate). */\n | { usage: Partial<AparteUsage> };\n\n/** What the model does for ONE call. A bare string is one text step. */\nexport type ScenarioTurn = string | ScenarioStep[];\n\n/** A turn with the conditions under which it answers. */\nexport interface Scenario {\n /**\n * Answers when the last user message matches: a string is a case-insensitive\n * substring, a RegExp is tested as is.\n */\n when?: string | RegExp;\n /** Answers the call that follows this tool's result — the second half of a tool round-trip. */\n after?: string;\n turn: ScenarioTurn;\n}\n\n/** How fast text streams. */\nexport interface ScenarioPacing {\n /** Characters per chunk. Default 12. */\n chunk?: number;\n /** Milliseconds between chunks. Default 24. */\n delay?: number;\n}\n\nexport interface ScenarioProviderOptions {\n /** Provider id, as seen by core (key resolution, model picker, events). Default `scenario`. */\n id?: string;\n /** Display name. Default `Scripted model`. */\n name?: string;\n /**\n * Calls answered in order; the last one repeats. The simplest form — a demo that\n * always goes the same way, a test that needs three replies. Every call to the\n * model advances, a tool round-trip included.\n */\n turns?: ScenarioTurn[];\n /**\n * Named scenarios, one of which answers each call — picked by `match`, or by the\n * default: the `after` of a tool result, else the first `when` that matches the\n * last user message, else `default`, else the first one. A bare turn value is\n * `{ turn }`.\n */\n scenarios?: Record<string, Scenario | ScenarioTurn>;\n /** Picks the scenario for a call, by name. Return `undefined` to fall back to the default rule. */\n match?: (request: AparteChatRequest, scenarios: Record<string, Scenario>) => string | undefined;\n /** Typing pace, or `'instant'` for a test that wants the whole reply at once. */\n pacing?: ScenarioPacing | 'instant';\n /** The models the picker offers. Default: one, `scripted`, declaring streaming and tools. */\n models?: AparteAIModel[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst textOf = (message: AparteChatMessage | undefined): string => {\n if (!message) return '';\n const { content } = message;\n if (typeof content === 'string') return content;\n return content.map((part) => (part.type === 'text' ? part.text : '')).join(' ');\n};\n\nconst stepsOf = (turn: ScenarioTurn): ScenarioStep[] => (typeof turn === 'string' ? [{ text: turn }] : turn);\n\nconst isTurn = (value: Scenario | ScenarioTurn): value is ScenarioTurn => typeof value === 'string' || Array.isArray(value);\n\nconst normalize = (scenarios: Record<string, Scenario | ScenarioTurn>): Record<string, Scenario> => {\n const out: Record<string, Scenario> = {};\n for (const [key, value] of Object.entries(scenarios)) out[key] = isTurn(value) ? { turn: value } : value;\n return out;\n};\n\n/** The name of the tool a `tool_result` answers: the call with that id, in the turn before. */\nconst toolNameOf = (messages: AparteChatMessage[], toolCallId: string | undefined): string | undefined => {\n for (let i = messages.length - 1; i >= 0; i--) {\n const call = messages[i]!.toolCalls?.find((tc) => tc.id === toolCallId);\n if (call) return call.name;\n }\n return undefined;\n};\n\n/**\n * The default rule: a tool result goes to the scenario that declared `after` for that\n * tool; otherwise the first scenario whose `when` matches the last user message;\n * otherwise `default`; otherwise the first scenario declared.\n */\nexport function defaultMatch(request: AparteChatRequest, scenarios: Record<string, Scenario>): string | undefined {\n const entries = Object.entries(scenarios);\n const last = request.messages[request.messages.length - 1];\n if (last?.role === 'tool_result') {\n const tool = toolNameOf(request.messages, last.toolCallId);\n const hit = entries.find(([, s]) => s.after !== undefined && s.after === tool);\n if (hit) return hit[0];\n }\n const text = textOf(last);\n for (const [key, s] of entries) {\n if (s.when === undefined) continue;\n if (typeof s.when === 'string' ? text.toLowerCase().includes(s.when.toLowerCase()) : s.when.test(text)) return key;\n }\n if ('default' in scenarios) return 'default';\n return entries[0]?.[0];\n}\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> => new Promise((resolve) => {\n if (ms <= 0 || signal?.aborted) return resolve();\n const timer = setTimeout(done, ms);\n function done(): void {\n clearTimeout(timer);\n signal?.removeEventListener('abort', done);\n resolve();\n }\n signal?.addEventListener('abort', done, { once: true });\n});\n\n/** Four characters per token — the usual rough cut, enough for a gauge to move. */\nconst estimateTokens = (chars: number): number => Math.ceil(chars / 4);\n\nlet sequence = 0;\n\n/**\n * Play one turn as the event stream the loop reads.\n *\n * Exported for a test or a tool that wants the events without a provider around them.\n */\nexport function playTurn(\n turn: ScenarioTurn,\n request: AparteChatRequest,\n pacing: ScenarioPacing | 'instant' = {},\n signal?: AbortSignal,\n): ReadableStream<AparteStreamEvent> {\n const chunk = pacing === 'instant' ? Number.POSITIVE_INFINITY : Math.max(1, pacing.chunk ?? 12);\n const delay = pacing === 'instant' ? 0 : Math.max(0, pacing.delay ?? 24);\n const steps = stepsOf(turn);\n const inputChars = request.messages.reduce((n, m) => n + textOf(m).length, 0);\n\n return new ReadableStream<AparteStreamEvent>({\n async start(controller) {\n let outputChars = 0;\n let usage: Partial<AparteUsage> = {};\n // Not `startedAt`: that name is a segment stamp core writes in one place, and\n // `check:segment-stamp` reads any write to it as a third stamping site.\n const turnBegan = Date.now();\n try {\n for (const step of steps) {\n if (signal?.aborted) break;\n if ('wait' in step) {\n await sleep(step.wait, signal);\n } else if ('text' in step || 'thinking' in step) {\n const type = 'text' in step ? 'text' : 'thinking';\n const body = 'text' in step ? step.text : step.thinking;\n for (let i = 0; i < body.length; i += chunk) {\n if (signal?.aborted) break;\n const delta = body.slice(i, i + chunk);\n controller.enqueue({ type, delta });\n outputChars += delta.length;\n if (delay > 0 && i + chunk < body.length) await sleep(delay, signal);\n }\n } else if ('tool' in step) {\n controller.enqueue({ type: 'tool_use', id: step.id ?? `scenario_${++sequence}`, name: step.tool, input: step.input ?? {} });\n } else if ('error' in step) {\n controller.enqueue({ type: 'error', message: step.error });\n controller.close();\n return;\n } else if ('usage' in step) {\n usage = { ...usage, ...step.usage };\n }\n }\n if (!signal?.aborted) {\n const outputTokens = estimateTokens(outputChars);\n const inputTokens = estimateTokens(inputChars);\n controller.enqueue({\n type: 'done',\n usage: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens, durationMs: Date.now() - turnBegan, ...usage },\n });\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n },\n });\n}\n\n// ─── The provider ────────────────────────────────────────────────────────────\n\n/**\n * Build the scripted provider. Register it like any other:\n *\n * ```ts\n * aparteGlobalConfig.registerAIProvider(createScenarioProvider({\n * scenarios: {\n * default: 'Ask me for a haiku, a table, or the weather.',\n * haiku: { when: /haiku/i, turn: 'Web components hum — \\nno framework in the wind, \\njust the page, alive.' },\n * weather: { when: 'weather', turn: [{ text: 'Let me check.' }, { tool: 'get_weather', input: { city: 'Lille' } }] },\n * forecast: { after: 'get_weather', turn: 'Cloudy, 14 °C. Bring a jacket.' },\n * },\n * }));\n * ```\n */\nexport function createScenarioProvider(options: ScenarioProviderOptions = {}): AparteAIProvider {\n const id = options.id ?? 'scenario';\n const name = options.name ?? 'Scripted model';\n const models: AparteAIModel[] = options.models ?? [{\n id: 'scripted',\n name,\n description: 'Replays scripted turns — no key, no network.',\n capabilities: ['streaming', 'function_calling'],\n }];\n const scenarios = normalize(options.scenarios ?? {});\n const match = options.match ?? defaultMatch;\n let calls = 0;\n\n const pick = (request: AparteChatRequest): ScenarioTurn => {\n if (options.turns?.length) {\n const turn = options.turns[Math.min(calls, options.turns.length - 1)]!;\n calls++;\n return turn;\n }\n const key = match(request, scenarios) ?? defaultMatch(request, scenarios);\n const scenario = key !== undefined ? scenarios[key] : undefined;\n if (!scenario) {\n // Said, because the alternative was \"Typing…\" forever with an empty reply\n // and nothing in the console (issue #29). The usual cause: `match()`\n // returned the scenario OBJECT — the second argument hands you the objects,\n // so it is an easy slip — where the loop needs its KEY.\n const got = key === undefined ? 'nothing' : typeof key === 'string' ? `\"${key}\"` : `a ${typeof key}`;\n console.warn(\n `[provider-scenario] no scenario for this call: match() returned ${got}, and the keys are `\n + `${Object.keys(scenarios).map((k) => `\"${k}\"`).join(', ') || '(none)'}. Return a key, or `\n + '`undefined` to fall back to the default rule. Streaming an empty turn.',\n );\n }\n return scenario?.turn ?? '';\n };\n\n return {\n id,\n getMetadata: () => ({\n id,\n name,\n description: 'Replays scripted turns — no key, no network.',\n isLocal: true,\n configSchema: { fields: [] },\n }),\n getModels: () => models,\n fetchModels: async () => models,\n async chat(request: AparteChatRequest, _config?: unknown, ctx?: { providerId: string; signal?: AbortSignal }): Promise<AparteChatResponse> {\n return playTurn(pick(request), request, options.pacing, ctx?.signal);\n },\n };\n}\n\nexport { showcase } from './presets.js';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/presets.ts","../src/index.ts"],"sourcesContent":["import type { Scenario } from './index.js';\n\n/**\n * A ready-made set of scenarios that shows the whole surface of a chat: markdown, a\n * code block, reasoning, a tool round-trip, a typed question, an artifact, an error.\n * The docs' live frames and a demo page can register it as is:\n *\n * ```ts\n * aparteGlobalConfig.registerAIProvider(createScenarioProvider({ scenarios: showcase }));\n * ```\n *\n * The tool names it calls — `get_weather`, `ask_user` — only do something if the app\n * registered a tool of that name; `@aparte/plugin-ask-user` provides the second.\n */\nexport const showcase: Record<string, Scenario> = {\n default: {\n turn: 'Hi — I am a **scripted** model: no key, no network, the same answer every time.\\n\\nAsk me for a *haiku*, a *table*, some *code*, the *weather*, or to *ask you a question*.',\n },\n haiku: {\n when: /haiku/i,\n turn: 'Web components hum — \\nno framework in the wind, \\njust the page, alive.',\n },\n table: {\n when: /table/i,\n turn: 'Two ways to reach a model:\\n\\n| Transport | Where the key lives | Use it for |\\n|---|---|---|\\n| `AparteDirectTransport` | in the browser | prototypes, local models |\\n| `AparteBackendTransport` | on your server | production |\\n\\nThe component does not change between the two.',\n },\n code: {\n when: /code|example|snippet/i,\n turn: [\n { thinking: 'The shortest useful wiring is three lines: register a provider, set a transport, start the client.' },\n { text: 'The minimal wiring:\\n\\n```ts\\nimport { AparteClient, aparteGlobalConfig, AparteDirectTransport } from \\'@aparte/core\\';\\nimport { createScenarioProvider } from \\'@aparte/provider-scenario\\';\\n\\naparteGlobalConfig.registerAIProvider(createScenarioProvider({ turns: [\\'Hello!\\'] }));\\naparteGlobalConfig.setTransport(new AparteDirectTransport({ byok: true }));\\nnew AparteClient().start();\\n```\\n\\nThree lines that matter.' },\n ],\n },\n weather: {\n when: /weather|forecast/i,\n turn: [\n { text: 'Let me check that for you.' },\n { tool: 'get_weather', input: { city: 'Lille' } },\n ],\n },\n forecast: {\n after: 'get_weather',\n turn: 'Cloudy, 14 °C, a little wind from the west — bring a jacket.',\n },\n // Several questions in one call: the panel becomes a stepper (1 2 …) with a\n // \"Skip\" per step. Nothing in the repository showed that mode until a consumer\n // reported clipped borders on it — a state no example renders is a state\n // nobody looks at. BEFORE `question`: scenarios are matched in order, and\n // \"two questions\" contains the word that one listens for.\n survey: {\n when: /survey|two questions|a few questions/i,\n turn: [\n { text: 'Two quick ones, then I can tailor it.' },\n {\n tool: 'ask_user',\n input: {\n questions: [\n {\n question: 'Which colour do you favour?',\n header: 'Colour',\n options: [\n { title: 'Blue', description: 'calm, trusted' },\n { title: 'Green', description: 'fresh, natural' },\n { title: 'Yellow', description: 'warm, loud' },\n ],\n },\n {\n question: 'How dense should the layout be?',\n header: 'Density',\n options: [\n { title: 'Airy', description: 'more room, fewer items per screen', recommended: true },\n { title: 'Compact', description: 'more on screen, tighter spacing' },\n ],\n },\n ],\n },\n },\n ],\n },\n question: {\n when: /question|ask me|colou?r scheme/i,\n turn: [\n { text: 'One thing first.' },\n {\n tool: 'ask_user',\n input: {\n questions: [{\n question: 'Which mood for your landing page?',\n header: 'Mood',\n options: [\n { title: 'Dark and quiet', description: 'ink, brass accents', recommended: true },\n { title: 'Light and warm', description: 'cream, soft contrast' },\n { title: 'Maximum contrast', description: 'strict black and white' },\n ],\n }],\n },\n },\n ],\n },\n answered: {\n after: 'ask_user',\n turn: 'Noted — I will go with that.',\n },\n artifact: {\n when: /artifact|document|write me/i,\n turn: 'Here is a first draft:\\n\\n<artifact type=\"text/markdown\" title=\"Launch note\">## aparté\\n\\nA chat in Web Components, with the agent loop inside.\\n\\n- any framework\\n- zero third-party dependencies\\n</artifact>\\n\\nTell me what to change.',\n },\n slow: {\n when: /slow|think hard|take your time/i,\n turn: [\n { thinking: 'This deserves a moment.' },\n { wait: 2500 },\n { text: 'Done thinking. The answer is: it depends on the transport.' },\n ],\n },\n failure: {\n when: /fail|error|crash/i,\n turn: [{ error: 'The scripted model exploded, as requested.' }],\n },\n};\n","/**\n * @aparte/provider-scenario — a scripted model.\n *\n * An `AparteAIProvider` that REPLAYS turns you wrote instead of calling a model: text at\n * a typing pace, thinking, tool calls (the real loop runs your handler and calls back\n * with the result), errors, pauses. No key, no network, no randomness.\n *\n * Three things in this repository had written it by hand before this package existed:\n * the e2e mock of the wire, the screenshot harness of the UI audit, and the docs frames\n * that seed content. It is also the piece nobody ships for CONSUMERS: a deterministic\n * model for their own tests, and a demo that streams without a backend.\n *\n * It owns its I/O — `chat()` — so `AparteDirectTransport` hands the request straight to\n * it; there is no wire format here to adapt. (This repository's own e2e keeps its\n * network mock on purpose: that suite tests the real wire path, which this provider\n * bypasses by construction.)\n *\n * ```ts\n * import { createScenarioProvider } from '@aparte/provider-scenario';\n * aparteGlobalConfig.registerAIProvider(createScenarioProvider({\n * turns: ['Hello! Ask me anything.', [{ thinking: 'Let me see…' }, { text: 'Here is my answer.' }]],\n * }));\n * ```\n */\n\nimport type {\n AparteAIModel,\n AparteAIProvider,\n AparteChatMessage,\n AparteChatRequest,\n AparteChatResponse,\n AparteStreamEvent,\n AparteUsage,\n} from '@aparte/core';\n\n// ─── What a turn is made of ──────────────────────────────────────────────────\n\n/** One thing the scripted model does, in order. */\nexport type ScenarioStep =\n /** Stream this text, chunk by chunk at the configured pace. Rendered as markdown once a markdown plugin is installed (plain text otherwise — core ships no markdown renderer); a tagged block (`<artifact>`…) becomes its segment once a grammar for it is registered (`@aparte/plugin-artifacts` does). */\n | { text: string }\n /** Stream this as reasoning (the thinking block). */\n | { thinking: string }\n /** Call a tool. The loop runs the registered handler, then calls the provider again with the result. */\n | { tool: string; input?: Record<string, unknown>; id?: string }\n /** Fail the turn with this message — what a provider error looks like to the UI. */\n | { error: string }\n /** Pause, in milliseconds — a slow model, a long tool. */\n | { wait: number }\n /** Override the usage reported at the end (merged over the estimate). */\n | { usage: Partial<AparteUsage> };\n\n/** What the model does for ONE call. A bare string is one text step. */\nexport type ScenarioTurn = string | ScenarioStep[];\n\n/** A turn with the conditions under which it answers. */\nexport interface Scenario {\n /**\n * Answers when the last user message matches: a string is a case-insensitive\n * substring, a RegExp is tested as is.\n */\n when?: string | RegExp;\n /** Answers the call that follows this tool's result — the second half of a tool round-trip. */\n after?: string;\n turn: ScenarioTurn;\n}\n\n/** How fast text streams. */\nexport interface ScenarioPacing {\n /** Characters per chunk. Default 12. */\n chunk?: number;\n /** Milliseconds between chunks. Default 24. */\n delay?: number;\n}\n\nexport interface ScenarioProviderOptions {\n /** Provider id, as seen by core (key resolution, model picker, events). Default `scenario`. */\n id?: string;\n /** Display name. Default `Scripted model`. */\n name?: string;\n /**\n * Calls answered in order; the last one repeats. The simplest form — a demo that\n * always goes the same way, a test that needs three replies. Every call to the\n * model advances, a tool round-trip included.\n */\n turns?: ScenarioTurn[];\n /**\n * Named scenarios, one of which answers each call — picked by `match`, or by the\n * default: the `after` of a tool result, else the first `when` that matches the\n * last user message, else `default`, else the first one. A bare turn value is\n * `{ turn }`.\n */\n scenarios?: Record<string, Scenario | ScenarioTurn>;\n /** Picks the scenario for a call, by name. Return `undefined` to fall back to the default rule. */\n match?: (request: AparteChatRequest, scenarios: Record<string, Scenario>) => string | undefined;\n /** Typing pace, or `'instant'` for a test that wants the whole reply at once. */\n pacing?: ScenarioPacing | 'instant';\n /** The models the picker offers. Default: one, `scripted`, declaring streaming and tools. */\n models?: AparteAIModel[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst textOf = (message: AparteChatMessage | undefined): string => {\n if (!message) return '';\n const { content } = message;\n if (typeof content === 'string') return content;\n return content.map((part) => (part.type === 'text' ? part.text : '')).join(' ');\n};\n\nconst stepsOf = (turn: ScenarioTurn): ScenarioStep[] => (typeof turn === 'string' ? [{ text: turn }] : turn);\n\nconst isTurn = (value: Scenario | ScenarioTurn): value is ScenarioTurn => typeof value === 'string' || Array.isArray(value);\n\nconst normalize = (scenarios: Record<string, Scenario | ScenarioTurn>): Record<string, Scenario> => {\n const out: Record<string, Scenario> = {};\n for (const [key, value] of Object.entries(scenarios)) out[key] = isTurn(value) ? { turn: value } : value;\n return out;\n};\n\n/** The name of the tool a `tool_result` answers: the call with that id, in the turn before. */\nconst toolNameOf = (messages: AparteChatMessage[], toolCallId: string | undefined): string | undefined => {\n for (let i = messages.length - 1; i >= 0; i--) {\n const call = messages[i]!.toolCalls?.find((tc) => tc.id === toolCallId);\n if (call) return call.name;\n }\n return undefined;\n};\n\n/**\n * The default rule: a tool result goes to the scenario that declared `after` for that\n * tool; otherwise the first scenario whose `when` matches the last user message;\n * otherwise `default`; otherwise the first scenario declared.\n */\nexport function defaultMatch(request: AparteChatRequest, scenarios: Record<string, Scenario>): string | undefined {\n const entries = Object.entries(scenarios);\n const last = request.messages[request.messages.length - 1];\n if (last?.role === 'tool_result') {\n const tool = toolNameOf(request.messages, last.toolCallId);\n const hit = entries.find(([, s]) => s.after !== undefined && s.after === tool);\n if (hit) return hit[0];\n }\n const text = textOf(last);\n for (const [key, s] of entries) {\n if (s.when === undefined) continue;\n if (typeof s.when === 'string' ? text.toLowerCase().includes(s.when.toLowerCase()) : s.when.test(text)) return key;\n }\n if ('default' in scenarios) return 'default';\n return entries[0]?.[0];\n}\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> => new Promise((resolve) => {\n if (ms <= 0 || signal?.aborted) return resolve();\n const timer = setTimeout(done, ms);\n function done(): void {\n clearTimeout(timer);\n signal?.removeEventListener('abort', done);\n resolve();\n }\n signal?.addEventListener('abort', done, { once: true });\n});\n\n/** Four characters per token — the usual rough cut, enough for a gauge to move. */\nconst estimateTokens = (chars: number): number => Math.ceil(chars / 4);\n\nlet sequence = 0;\n\n/**\n * Play one turn as the event stream the loop reads.\n *\n * Exported for a test or a tool that wants the events without a provider around them.\n */\nexport function playTurn(\n turn: ScenarioTurn,\n request: AparteChatRequest,\n pacing: ScenarioPacing | 'instant' = {},\n signal?: AbortSignal,\n): ReadableStream<AparteStreamEvent> {\n const chunk = pacing === 'instant' ? Number.POSITIVE_INFINITY : Math.max(1, pacing.chunk ?? 12);\n const delay = pacing === 'instant' ? 0 : Math.max(0, pacing.delay ?? 24);\n const steps = stepsOf(turn);\n const inputChars = request.messages.reduce((n, m) => n + textOf(m).length, 0);\n\n return new ReadableStream<AparteStreamEvent>({\n async start(controller) {\n let outputChars = 0;\n let usage: Partial<AparteUsage> = {};\n // Not `startedAt`: that name is a segment stamp core writes in one place, and\n // `check:segment-stamp` reads any write to it as a third stamping site.\n const turnBegan = Date.now();\n try {\n for (const step of steps) {\n if (signal?.aborted) break;\n if ('wait' in step) {\n await sleep(step.wait, signal);\n } else if ('text' in step || 'thinking' in step) {\n const type = 'text' in step ? 'text' : 'thinking';\n const body = 'text' in step ? step.text : step.thinking;\n for (let i = 0; i < body.length; i += chunk) {\n if (signal?.aborted) break;\n const delta = body.slice(i, i + chunk);\n controller.enqueue({ type, delta });\n outputChars += delta.length;\n if (delay > 0 && i + chunk < body.length) await sleep(delay, signal);\n }\n } else if ('tool' in step) {\n controller.enqueue({ type: 'tool_use', id: step.id ?? `scenario_${++sequence}`, name: step.tool, input: step.input ?? {} });\n } else if ('error' in step) {\n controller.enqueue({ type: 'error', message: step.error });\n controller.close();\n return;\n } else if ('usage' in step) {\n usage = { ...usage, ...step.usage };\n }\n }\n if (!signal?.aborted) {\n const outputTokens = estimateTokens(outputChars);\n const inputTokens = estimateTokens(inputChars);\n controller.enqueue({\n type: 'done',\n usage: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens, durationMs: Date.now() - turnBegan, ...usage },\n });\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n },\n });\n}\n\n// ─── The provider ────────────────────────────────────────────────────────────\n\n/**\n * Build the scripted provider. Register it like any other:\n *\n * ```ts\n * aparteGlobalConfig.registerAIProvider(createScenarioProvider({\n * scenarios: {\n * default: 'Ask me for a haiku, a table, or the weather.',\n * haiku: { when: /haiku/i, turn: 'Web components hum — \\nno framework in the wind, \\njust the page, alive.' },\n * weather: { when: 'weather', turn: [{ text: 'Let me check.' }, { tool: 'get_weather', input: { city: 'Lille' } }] },\n * forecast: { after: 'get_weather', turn: 'Cloudy, 14 °C. Bring a jacket.' },\n * },\n * }));\n * ```\n */\nexport function createScenarioProvider(options: ScenarioProviderOptions = {}): AparteAIProvider {\n // A `when` scenario that calls a tool needs its `after` counterpart, or the\n // default match routes the tool RESULT back through the same `when` and the\n // conversation eats its own tail — identical rounds until the client's\n // maxTurns error. Plausible to write, silent to read; the hole is visible at\n // creation, so it is said at creation. Ordered `turns` advance on their own,\n // and a custom `match` replaces the default rule — both exempt.\n if (options.scenarios && !options.match) {\n // A record value is a Scenario OR a bare turn (string / steps) — a tool in a\n // BARE turn loops the same way, through the default/first-entry fallback.\n const values = Object.values(options.scenarios);\n const asScenario = (v: Scenario | ScenarioTurn): Scenario | null =>\n typeof v === 'object' && !Array.isArray(v) ? v : null;\n const routed = new Set(values.map((v) => asScenario(v)?.after).filter(Boolean));\n const orphans = new Set<string>();\n for (const v of values) {\n const turn = asScenario(v)?.turn ?? (v as ScenarioTurn);\n const steps = Array.isArray(turn) ? turn : [];\n for (const step of steps) {\n if (typeof step === 'object' && step !== null && 'tool' in step\n && typeof step.tool === 'string' && !routed.has(step.tool)) {\n orphans.add(step.tool);\n }\n }\n }\n if (orphans.size > 0) {\n console.warn(\n `[scenario] No \\`after\\` route for: ${[...orphans].map((t) => `\\`${t}\\``).join(', ')}. `\n + 'A scenario calls the tool, but nothing answers its result — the default match '\n + 'sends it back through the same `when`, and the conversation loops until the '\n + \"client's maxTurns stops it. Declare a scenario with `after: '<tool>'` for each.\",\n );\n }\n }\n const id = options.id ?? 'scenario';\n const name = options.name ?? 'Scripted model';\n const models: AparteAIModel[] = options.models ?? [{\n id: 'scripted',\n name,\n description: 'Replays scripted turns — no key, no network.',\n capabilities: ['streaming', 'function_calling'],\n }];\n const scenarios = normalize(options.scenarios ?? {});\n const match = options.match ?? defaultMatch;\n let calls = 0;\n\n const pick = (request: AparteChatRequest): ScenarioTurn => {\n if (options.turns?.length) {\n const turn = options.turns[Math.min(calls, options.turns.length - 1)]!;\n calls++;\n return turn;\n }\n const key = match(request, scenarios) ?? defaultMatch(request, scenarios);\n const scenario = key !== undefined ? scenarios[key] : undefined;\n if (!scenario) {\n // Said, because the alternative was \"Typing…\" forever with an empty reply\n // and nothing in the console (issue #29). The usual cause: `match()`\n // returned the scenario OBJECT — the second argument hands you the objects,\n // so it is an easy slip — where the loop needs its KEY.\n const got = key === undefined ? 'nothing' : typeof key === 'string' ? `\"${key}\"` : `a ${typeof key}`;\n console.warn(\n `[provider-scenario] no scenario for this call: match() returned ${got}, and the keys are `\n + `${Object.keys(scenarios).map((k) => `\"${k}\"`).join(', ') || '(none)'}. Return a key, or `\n + '`undefined` to fall back to the default rule. Streaming an empty turn.',\n );\n }\n return scenario?.turn ?? '';\n };\n\n return {\n id,\n getMetadata: () => ({\n id,\n name,\n description: 'Replays scripted turns — no key, no network.',\n isLocal: true,\n configSchema: { fields: [] },\n }),\n getModels: () => models,\n fetchModels: async () => models,\n async chat(request: AparteChatRequest, _config?: unknown, ctx?: { providerId: string; signal?: AbortSignal }): Promise<AparteChatResponse> {\n return playTurn(pick(request), request, options.pacing, ctx?.signal);\n },\n };\n}\n\nexport { showcase } from './presets.js';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aparte/provider-scenario",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.7",
|
|
4
|
+
"homepage": "https://apartejs.dev/providers/ai/scenario/",
|
|
4
5
|
"description": "A scripted model for aparté — replays turns you wrote (text at a typing pace, thinking, tool calls, errors) so demos, docs and your own tests stream real-looking conversations with no key and no network.",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"sideEffects": false,
|
|
@@ -24,12 +25,12 @@
|
|
|
24
25
|
"node": ">=18"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
|
-
"@aparte/core": ">=0.16.
|
|
28
|
+
"@aparte/core": ">=0.16.7 <1.0.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"typescript": "^5.4.0",
|
|
31
32
|
"vite": "^6.0.0",
|
|
32
|
-
"@aparte/core": "0.16.
|
|
33
|
+
"@aparte/core": "0.16.7"
|
|
33
34
|
},
|
|
34
35
|
"keywords": [
|
|
35
36
|
"aparte",
|