@aparte/plugin-artifacts 0.16.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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/binary-file.d.ts +7 -0
- package/dist/binary-file.d.ts.map +1 -0
- package/dist/card.d.ts +6 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/custom-elements.json +1184 -0
- package/dist/highlight.d.ts +17 -0
- package/dist/highlight.d.ts.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +675 -0
- package/dist/index.js.map +1 -0
- package/dist/index.node.d.ts +32 -0
- package/dist/index.node.d.ts.map +1 -0
- package/dist/index.node.js +30 -0
- package/dist/index.node.js.map +1 -0
- package/dist/kinds.d.ts +19 -0
- package/dist/kinds.d.ts.map +1 -0
- package/dist/options.d.ts +72 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/preview-document-xP-Ne-F0.js +177 -0
- package/dist/preview-document-xP-Ne-F0.js.map +1 -0
- package/dist/preview-document.d.ts +27 -0
- package/dist/preview-document.d.ts.map +1 -0
- package/dist/segment.d.ts +66 -0
- package/dist/segment.d.ts.map +1 -0
- package/dist/shared.d.ts +29 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/tool.d.ts +31 -0
- package/dist/tool.d.ts.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-document-xP-Ne-F0.js","sources":["../src/tool.ts","../src/kinds.ts","../src/segment.ts","../src/preview-document.ts"],"sourcesContent":["/**\n * The `create_artifact` tool: a real `AparteTool` the app registers, with a handler that\n * returns the document as its structured result.\n *\n * It used to be a string compared in the engine (`event.name === 'create_artifact'`)\n * with no tool behind it: no app declared it to the model, no approval policy could\n * rule on it, and its result skipped the generic path. As a tool it goes through\n * everything a tool goes through — the schema the model sees, the approval gate (a\n * policy may well class writing a document as a `write`), the handler, the envelope —\n * and the card is a tool renderer on its result.\n */\nimport type { AparteTool, AparteToolCall, AparteToolResult } from '@aparte/core';\nimport type { ArtifactInput } from './segment.js';\n\n/** What the model is told, when the app does not say otherwise. */\nexport const ARTIFACT_SYSTEM_PROMPT =\n 'When the user asks for a document, a page, a component, a diagram or a file — anything '\n + 'they will keep, edit or run rather than read once — produce it with the create_artifact '\n + 'tool, with a MIME type that names what it is, rather than pasting it into your reply. '\n + 'Keep your reply short: say what you made, not what it contains.';\n\nexport interface ArtifactToolOptions {\n /** The tool's name as the model sees it. Default `create_artifact`. */\n name?: string;\n /**\n * The system prompt the tool carries — how the model learns when to produce a\n * document. A string of your own replaces the default; `false` sends none.\n */\n systemPrompt?: string | false;\n}\n\n/** The tool definition — name, description, input schema, system prompt. */\nexport function createArtifactTool(options: ArtifactToolOptions = {}): AparteTool {\n const tool: AparteTool = {\n name: options.name ?? 'create_artifact',\n description: 'Create a self-contained document the user can keep, edit, run or download: a page, a '\n + 'component, a script, a stylesheet, an SVG, a JSON document, a Markdown note, a CSV. '\n + 'Give it a MIME type (text/html, application/vnd.ant.react, image/svg+xml, text/markdown, '\n + 'application/json, text/csv, application/javascript, text/css) and a short title.',\n inputSchema: {\n type: 'object',\n properties: {\n mimeType: { type: 'string', description: 'The MIME type of the document, e.g. text/html or application/vnd.ant.react.' },\n title: { type: 'string', description: 'A short human title for the document.' },\n content: { type: 'string', description: 'The complete document.' },\n },\n required: ['mimeType', 'content'],\n },\n };\n if (options.systemPrompt !== false) tool.systemPrompt = options.systemPrompt ?? ARTIFACT_SYSTEM_PROMPT;\n return tool;\n}\n\n/**\n * The handler: the document IS the result. The prose tells the model what happened;\n * the structured twin carries the artifact for the renderer, verbatim.\n */\nexport async function artifactHandler(call: AparteToolCall): Promise<AparteToolResult> {\n const input = (call.input ?? {}) as Partial<ArtifactInput>;\n const mimeType = typeof input.mimeType === 'string' && input.mimeType.trim() ? input.mimeType.trim() : 'text/plain';\n const title = typeof input.title === 'string' ? input.title.trim() : '';\n const content = typeof input.content === 'string' ? input.content : '';\n const structured: ArtifactInput = { mimeType, content, ...(title ? { title } : {}) };\n return {\n toolCallId: call.id,\n content: `Artifact created${title ? `: ${title}` : ''} (${mimeType}, ${content.length} characters). It is shown to the user; do not repeat its content.`,\n structuredContent: structured,\n };\n}\n","/**\n * Map an artifact MIME type to a renderer kind — `'react'`, `'html'`, `'js'`, `'css'`,\n * `'svg'`, `'json'`, `'markdown'`, `'csv'`, `'text'` — or `fallback` (default\n * `'unknown'`) for anything else. Anthropic's `application/vnd.ant.*` namespace maps to\n * its suffix; exact standard MIMEs are matched first, then a substring rescue for\n * parameterised or vendor variants (`text/html; charset=utf-8`, `application/ld+json`).\n *\n * THE implementation, and the plugin's: it used to live in `@aparte/engine` and be\n * re-exported by `@aparte/core`, because the engine's built-in `create_artifact` and\n * core's parser both needed it. Neither does any more — an artifact is a plugin's\n * convention, so the function that names its kinds is the plugin's too.\n *\n * @example\n * deriveArtifactKind('application/vnd.ant.react') // 'react'\n * deriveArtifactKind('text/html; charset=utf-8') // 'html'\n * deriveArtifactKind('font/woff2', 'text') // 'text' (fallback)\n */\nexport function deriveArtifactKind(mimeType: string, fallback = 'unknown'): string {\n const m = (mimeType || '').toLowerCase().trim();\n const ant = m.match(/^application\\/vnd\\.ant\\.([a-z0-9-]+)/);\n if (ant) return ant[1]!;\n if (m === 'text/html' || m === 'application/xhtml+xml') return 'html';\n if (m === 'application/javascript' || m === 'text/javascript') return 'js';\n if (m === 'text/css') return 'css';\n if (m === 'image/svg+xml') return 'svg';\n if (m === 'application/json') return 'json';\n if (m === 'text/markdown') return 'markdown';\n if (m === 'text/csv') return 'csv';\n if (m === 'text/plain') return 'text';\n // The binary kinds the file card knows, by their standard names — a model calling\n // the tool with a real MIME type must land on the same path as `vnd.ant.pdf`.\n if (m === 'application/pdf') return 'pdf';\n if (m === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') return 'xlsx';\n if (m === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') return 'docx';\n if (m.includes('react')) return 'react';\n if (m.includes('html')) return 'html';\n if (m.includes('javascript')) return 'js';\n if (m.includes('css')) return 'css';\n if (m.includes('svg')) return 'svg';\n if (m.includes('json')) return 'json';\n if (m.includes('csv')) return 'csv';\n if (m.includes('markdown')) return 'markdown';\n return fallback;\n}\n","/**\n * The artifact segment, and the two ways one is produced.\n *\n * An artifact is not something a model does by nature — it is a convention an app\n * teaches it, and there are exactly two ways the convention arrives in a stream:\n *\n * 1. **A tool call.** The app registers `create_artifact` (`./tool.ts`); the model\n * calls it with `{ mimeType, title, content }`; the call's structured result is\n * the artifact, and the tool renderer draws the card from it.\n * 2. **A tag in the prose.** A model with no tools, or one prompted that way, writes\n * `<artifact type=\"…\" title=\"…\">…</artifact>` inline; the block grammar below turns\n * it into a segment of this type, streamed delta by delta by core's parser.\n *\n * Both end in the same card, which is what makes them one feature and not two.\n */\nimport type { AparteSegmentBase, AparteStreamBlock, AparteToolCallSegment } from '@aparte/core';\nimport { deriveArtifactKind } from './kinds.js';\n\n/** Segment type discriminator. Registered by `setupArtifacts()`, not built into core. */\nexport const ARTIFACT_SEGMENT_TYPE = 'artifact' as const;\n\n/** The default tag the grammar recognises: `<artifact …>…</artifact>`. */\nexport const ARTIFACT_TAG = 'artifact';\n\n/**\n * An artifact — a document the model produced, embedded in its reply.\n *\n * `mimeType` follows standard MIME conventions, with Anthropic's vendor namespace for\n * framework-specific kinds (`application/vnd.ant.react`); `artifactType` is the short\n * kind derived from it (`'react'`, `'html'`, `'svg'`, `'pdf'`…), which is what the card\n * switches on. `content` accumulates while a tagged block streams and is complete at\n * once for a tool call.\n */\nexport interface ArtifactSegment extends AparteSegmentBase {\n type: typeof ARTIFACT_SEGMENT_TYPE;\n /** Standard MIME type, verbatim from the tool input or the tag's `type` / `mimeType` attribute. */\n mimeType: string;\n /** Short kind derived from `mimeType` — see {@link deriveArtifactKind}. */\n artifactType: string;\n /** Optional human title. */\n title?: string;\n /** The document. Accumulates while a tagged block streams. */\n content: string;\n}\n\n/**\n * What the model passes to the tool — and what the tool returns as its structured\n * result, so the card reads one shape on both paths.\n */\nexport interface ArtifactInput {\n mimeType: string;\n title?: string;\n content: string;\n}\n\n/** Build the segment for a document, from either path. */\nexport function artifactSegment(id: string, input: Partial<ArtifactInput>): ArtifactSegment {\n const mimeType = typeof input.mimeType === 'string' && input.mimeType.trim() ? input.mimeType.trim() : 'text/plain';\n return {\n id,\n type: ARTIFACT_SEGMENT_TYPE,\n mimeType,\n artifactType: deriveArtifactKind(mimeType),\n title: typeof input.title === 'string' && input.title.trim() ? input.title.trim() : undefined,\n content: typeof input.content === 'string' ? input.content : '',\n };\n}\n\n/**\n * The block grammar for `<artifact …>…</artifact>` in the prose.\n *\n * Both attribute spellings are read — `type=` (Anthropic's) and `mimeType=` — and\n * `mimeType` wins when both are present; `title=` is optional. Everything the parser\n * does with it (chunk boundaries, the partial closing tag, `finalize()`) is core's.\n */\nexport function artifactBlock(tag: string = ARTIFACT_TAG): AparteStreamBlock {\n return {\n tag,\n toSegment: ({ attrs, id }) => artifactSegment(id, {\n mimeType: attrs['mimeType'] ?? attrs['mimetype'] ?? attrs['type'],\n title: attrs['title'],\n content: '',\n }),\n };\n}\n\n/**\n * The artifact a tool-call segment carries: its structured result once the tool ran,\n * else the model's input while the call is pending — so the card can be drawn before\n * the handler returns, which is instantaneous anyway.\n */\nexport function artifactFromToolCall(segment: AparteToolCallSegment): ArtifactSegment {\n const structured = segment.structuredResult as Partial<ArtifactInput> | undefined;\n const source = structured && typeof structured === 'object' ? structured : (segment.toolCall?.input ?? {}) as Partial<ArtifactInput>;\n const art = artifactSegment(segment.id, source);\n // A call whose input is still arriving would stream; a tool call's input arrives\n // whole, so the card is never in its streaming state here.\n art.isStreaming = false;\n return art;\n}\n","/**\n * The artifact preview DOCUMENT — the security boundary of the artifact card.\n *\n * A previewable artifact is model-authored code rendered in a sandboxed iframe. What\n * that iframe is allowed to reach, and how the model's body is wrapped before it gets\n * there, is decided entirely here. Until now it decided it from line 1806 of a\n * 1900-line file, next to nine unrelated renderers.\n *\n * It is also a documented public override point:\n * `contextConfig().getArtifactPreviewBuilder() ?? buildSafePreviewDocument` means a\n * consumer can replace this whole document — so `buildSafePreviewDocument` is the\n * reference implementation their replacement is measured against, and it deserves to\n * be findable.\n *\n * Everything here is pure: `(kind, body, title) => string`. No config read, no DOM,\n * no state — which is why it can be tested directly and why moving it changes nothing.\n *\n * Two things travelled deliberately and must not be separated again:\n * • `PREVIEW_CSP` is used twice — as the iframe's `csp` attribute and as the\n * document's own `<meta http-equiv>`. Belt and braces: the attribute is not\n * honoured by Firefox or Safari, so the meta tag is the one that actually applies\n * there.\n * • `escapeClosingScriptTag` is listed by NAME in `scripts/escaping-names.mjs`, so\n * both escaping guards keep recognising it from its new home.\n */\nimport { escapeHtml, escapeAttr } from '@aparte/core';\n\n/**\n * What a previewed artifact is allowed to FETCH. Inline script and style are\n * permitted (that IS the preview); no fetch, no XHR, no websocket, no remote image,\n * no font. Without this the sandbox still lets injected code beacon out to any\n * origin, so the policy does real work — measured blocked in Chromium, Firefox and\n * WebKit.\n *\n * WHAT IT DOES NOT STOP, stated because the sentence here used to claim otherwise:\n * the frame NAVIGATING ITSELF. Assigning `location.href` is a navigation, not a\n * fetch, and no directive in this policy governs it — CSP's `navigate-to` was\n * removed from the spec and never shipped, and the parent page's `frame-src` does\n * not apply to a navigation the frame initiates. Reproduced in all three engines;\n * in Firefox and WebKit the frame then RENDERS the attacker's page inside the card,\n * which is a phishing surface as well as an exfiltration channel.\n *\n * What still holds, and is why this is serious rather than fatal: the sandbox has\n * no `allow-same-origin`, so the frame's origin is opaque — it cannot read the host\n * page, its storage, or the API key — and no `allow-forms` or\n * `allow-top-navigation`, so it can neither POST nor move the tab.\n *\n * The honest summary for a consumer: a previewed artifact cannot reach your page or\n * your data, and can phone home once. Previewing model-authored HTML is running\n * untrusted content in a box, not running nothing.\n */\nexport const PREVIEW_CSP = \"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:\";\n\n/**\n * The portable half of the preview containment.\n *\n * The iframe's `csp` attribute is Chromium-only, so the same policy also goes\n * INSIDE the document as a `<meta http-equiv>`, which every engine honours. Both\n * are declared; whichever the browser understands applies.\n *\n * A model-authored full document gets one too, which it previously did not.\n *\n * This used to insert the meta only after an existing `<head>` and skip the\n * document otherwise, justified as \"a `<meta>` cannot be prepended without\n * breaking the doctype\". That is not true — `<html>` may be followed by a `<head>`\n * we open ourselves — and it left the ONE case that needs the policy most (a\n * `<!doctype html>` document written entirely by the model, which is passed\n * through verbatim) with no `<meta>` at all. On Firefox and Safari, where the\n * `csp` attribute does nothing, that frame ran `allow-scripts` under no policy: it\n * could beacon out, and load remote script. So a `<head>` is now created when the\n * document has none.\n *\n * Containment that held regardless: `sandbox=\"allow-scripts\"` without\n * `allow-same-origin` gives an opaque origin, so the frame never reached the host\n * DOM, storage, or the API key.\n */\nfunction withMetaCsp(doc: string): string { // safe-text: doc is the model-authored artifact HTML this function INJECTS a CSP into — escaping it would destroy the document it is protecting\n const meta = `<meta http-equiv=\"Content-Security-Policy\" content=\"${escapeAttr(PREVIEW_CSP)}\">`;\n\n // FIRST, always — after the doctype when there is one, otherwise at the very\n // front. Not \"after the model's `<head>`\", which is what this used to do.\n //\n // A `<meta http-equiv>` policy governs only what FOLLOWS it, and the model\n // writes this document: it can put a `<script>` before its own `<head>`, or\n // before `<html>`. The parser hoists that script into an implicitly-opened\n // head, where it sits EARLIER in document order than a meta inserted after a\n // tag the model declared later — so the script ran outside the policy.\n // Reproduced end to end in Firefox, WebKit, and Chromium with the `csp`\n // attribute absent: the beacon fired. And since that attribute is\n // Chromium-only, on Firefox and Safari this meta is the frame's ONLY policy.\n //\n // Prepending is safe in every shape, which the previously-correct third branch\n // already demonstrated across all three engines: content before `<html>` is\n // hoisted into an implicit head, the model's own `<html>`/`<head>` then merge\n // into the one already open. One branch replaces three, and it is the branch\n // that was right.\n const doctype = doc.match(/^\\s*<!doctype[^>]*>/i);\n const at = doctype ? doctype[0].length : 0;\n return `${doc.slice(0, at)}<head>${meta}</head>${doc.slice(at)}`;\n}\n\nexport function buildSafePreviewDocument(kind: string, body: string, title: string): string {\n return withMetaCsp(buildPreviewBody(kind, body, title));\n}\n\nfunction buildPreviewBody(\n kind: string,\n body: string, // safe-text: the model-authored artifact SOURCE, wrapped for a sandboxed CSP-constrained iframe — escaping it would render the code as text, which is the bug the sandbox exists to avoid\n title: string,\n): string {\n switch (kind) {\n case 'html': {\n if (startsWithDoctype(body)) return body;\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/><title>${escapeAttr(title)}</title></head><body>${body}</body></html>`;\n }\n // An SVG with only a `viewBox` — the recommended, responsive form, and the one\n // a model writes most often — has NO intrinsic size. As a flex item its cross\n // size then collapses to zero and the frame is blank: the preview worked for an\n // SVG that declared `width`/`height` and silently showed nothing for the\n // idiomatic one. Reported from the landing the moment its demo payload became a\n // real file instead of a hand-written chart with dimensions on it.\n //\n // The rule is narrowed by attribute selector rather than applied to every SVG,\n // so an SVG that DOES state its size keeps the size it asked for.\n case 'svg':\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeAttr(title)}</title>\n<style>html,body{margin:0;height:100%;display:flex;align-items:center;justify-content:center;background:#fff}svg{max-width:90%;max-height:90%}svg:not([width]):not([height]){width:90%;height:90%}</style>\n</head><body>${body}</body></html>`;\n case 'js': {\n const safeBody = escapeClosingScriptTag(body);\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeAttr(title)}</title>\n<style>body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;padding:1rem;background:#fff;color:#0f172a}</style>\n</head><body><div id=\"root\"></div><script>\ntry { ${safeBody}\n} catch (e) { document.getElementById('root').innerHTML = '<pre style=\"color:#b91c1c\">' + (e && e.stack || e) + '</pre>'; }\n</script></body></html>`;\n }\n case 'css':\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeAttr(title)}</title>\n<style>${body}</style></head><body>\n<div class=\"demo\">\n <h1>Heading</h1>\n <p>Paragraph with a <a href=\"#\">link</a> and <strong>strong</strong> text.</p>\n <button>Button</button>\n <input placeholder=\"Input\"/>\n <ul><li>One</li><li>Two</li><li>Three</li></ul>\n</div></body></html>`;\n default:\n // react / unknown — no live preview offline; show the code read-only.\n return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeAttr(title)}</title>\n<style>body{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;padding:1rem;background:#fff;color:#0f172a}pre{white-space:pre-wrap;word-break:break-word;margin:0}</style>\n</head><body><pre>${escapeHtml(body)}</pre></body></html>`;\n }\n}\n\n/** Char-based check for `<!doctype` (case-insensitive) at start of string,\n * ignoring leading whitespace. */\nfunction startsWithDoctype(s: string): boolean {\n let i = 0;\n while (i < s.length && (s[i] === ' ' || s[i] === '\\t' || s[i] === '\\n' || s[i] === '\\r')) i++;\n const probe = s.slice(i, i + 9).toLowerCase();\n return probe === '<!doctype';\n}\n\n/** Escape any literal `</script` inside the body so it cannot terminate the\n * outer <script> tag we wrap user code in. The HTML spec closes a script on\n * `</script` followed by whitespace, `/` or `>` (not only the exact `</script>`),\n * so match the 8-char prefix + a terminator. Char-based. */\nfunction escapeClosingScriptTag(body: string): string {\n let out = '';\n let i = 0;\n while (i < body.length) {\n if (body[i] === '<' && body.slice(i, i + 8).toLowerCase() === '</script') {\n const next = body[i + 8];\n // A real closing tag needs a terminator after `</script` (space/tab/\n // newline/form-feed, `/`, `>`) or end-of-input.\n if (next === undefined || next === '/' || next === '>' || /\\s/.test(next)) {\n out += '<\\\\/script'; // neutralise the `<` so the browser sees no tag\n i += 8;\n continue;\n }\n }\n out += body[i];\n i++;\n }\n return out;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a previewed artifact is allowed to FETCH. Inline script and style are
|
|
3
|
+
* permitted (that IS the preview); no fetch, no XHR, no websocket, no remote image,
|
|
4
|
+
* no font. Without this the sandbox still lets injected code beacon out to any
|
|
5
|
+
* origin, so the policy does real work — measured blocked in Chromium, Firefox and
|
|
6
|
+
* WebKit.
|
|
7
|
+
*
|
|
8
|
+
* WHAT IT DOES NOT STOP, stated because the sentence here used to claim otherwise:
|
|
9
|
+
* the frame NAVIGATING ITSELF. Assigning `location.href` is a navigation, not a
|
|
10
|
+
* fetch, and no directive in this policy governs it — CSP's `navigate-to` was
|
|
11
|
+
* removed from the spec and never shipped, and the parent page's `frame-src` does
|
|
12
|
+
* not apply to a navigation the frame initiates. Reproduced in all three engines;
|
|
13
|
+
* in Firefox and WebKit the frame then RENDERS the attacker's page inside the card,
|
|
14
|
+
* which is a phishing surface as well as an exfiltration channel.
|
|
15
|
+
*
|
|
16
|
+
* What still holds, and is why this is serious rather than fatal: the sandbox has
|
|
17
|
+
* no `allow-same-origin`, so the frame's origin is opaque — it cannot read the host
|
|
18
|
+
* page, its storage, or the API key — and no `allow-forms` or
|
|
19
|
+
* `allow-top-navigation`, so it can neither POST nor move the tab.
|
|
20
|
+
*
|
|
21
|
+
* The honest summary for a consumer: a previewed artifact cannot reach your page or
|
|
22
|
+
* your data, and can phone home once. Previewing model-authored HTML is running
|
|
23
|
+
* untrusted content in a box, not running nothing.
|
|
24
|
+
*/
|
|
25
|
+
export declare const PREVIEW_CSP = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:";
|
|
26
|
+
export declare function buildSafePreviewDocument(kind: string, body: string, title: string): string;
|
|
27
|
+
//# sourceMappingURL=preview-document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-document.d.ts","sourceRoot":"","sources":["../src/preview-document.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,WAAW,6GAA6G,CAAC;AAkDtI,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1F"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The artifact segment, and the two ways one is produced.
|
|
3
|
+
*
|
|
4
|
+
* An artifact is not something a model does by nature — it is a convention an app
|
|
5
|
+
* teaches it, and there are exactly two ways the convention arrives in a stream:
|
|
6
|
+
*
|
|
7
|
+
* 1. **A tool call.** The app registers `create_artifact` (`./tool.ts`); the model
|
|
8
|
+
* calls it with `{ mimeType, title, content }`; the call's structured result is
|
|
9
|
+
* the artifact, and the tool renderer draws the card from it.
|
|
10
|
+
* 2. **A tag in the prose.** A model with no tools, or one prompted that way, writes
|
|
11
|
+
* `<artifact type="…" title="…">…</artifact>` inline; the block grammar below turns
|
|
12
|
+
* it into a segment of this type, streamed delta by delta by core's parser.
|
|
13
|
+
*
|
|
14
|
+
* Both end in the same card, which is what makes them one feature and not two.
|
|
15
|
+
*/
|
|
16
|
+
import type { AparteSegmentBase, AparteStreamBlock, AparteToolCallSegment } from '@aparte/core';
|
|
17
|
+
/** Segment type discriminator. Registered by `setupArtifacts()`, not built into core. */
|
|
18
|
+
export declare const ARTIFACT_SEGMENT_TYPE: "artifact";
|
|
19
|
+
/** The default tag the grammar recognises: `<artifact …>…</artifact>`. */
|
|
20
|
+
export declare const ARTIFACT_TAG = "artifact";
|
|
21
|
+
/**
|
|
22
|
+
* An artifact — a document the model produced, embedded in its reply.
|
|
23
|
+
*
|
|
24
|
+
* `mimeType` follows standard MIME conventions, with Anthropic's vendor namespace for
|
|
25
|
+
* framework-specific kinds (`application/vnd.ant.react`); `artifactType` is the short
|
|
26
|
+
* kind derived from it (`'react'`, `'html'`, `'svg'`, `'pdf'`…), which is what the card
|
|
27
|
+
* switches on. `content` accumulates while a tagged block streams and is complete at
|
|
28
|
+
* once for a tool call.
|
|
29
|
+
*/
|
|
30
|
+
export interface ArtifactSegment extends AparteSegmentBase {
|
|
31
|
+
type: typeof ARTIFACT_SEGMENT_TYPE;
|
|
32
|
+
/** Standard MIME type, verbatim from the tool input or the tag's `type` / `mimeType` attribute. */
|
|
33
|
+
mimeType: string;
|
|
34
|
+
/** Short kind derived from `mimeType` — see {@link deriveArtifactKind}. */
|
|
35
|
+
artifactType: string;
|
|
36
|
+
/** Optional human title. */
|
|
37
|
+
title?: string;
|
|
38
|
+
/** The document. Accumulates while a tagged block streams. */
|
|
39
|
+
content: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* What the model passes to the tool — and what the tool returns as its structured
|
|
43
|
+
* result, so the card reads one shape on both paths.
|
|
44
|
+
*/
|
|
45
|
+
export interface ArtifactInput {
|
|
46
|
+
mimeType: string;
|
|
47
|
+
title?: string;
|
|
48
|
+
content: string;
|
|
49
|
+
}
|
|
50
|
+
/** Build the segment for a document, from either path. */
|
|
51
|
+
export declare function artifactSegment(id: string, input: Partial<ArtifactInput>): ArtifactSegment;
|
|
52
|
+
/**
|
|
53
|
+
* The block grammar for `<artifact …>…</artifact>` in the prose.
|
|
54
|
+
*
|
|
55
|
+
* Both attribute spellings are read — `type=` (Anthropic's) and `mimeType=` — and
|
|
56
|
+
* `mimeType` wins when both are present; `title=` is optional. Everything the parser
|
|
57
|
+
* does with it (chunk boundaries, the partial closing tag, `finalize()`) is core's.
|
|
58
|
+
*/
|
|
59
|
+
export declare function artifactBlock(tag?: string): AparteStreamBlock;
|
|
60
|
+
/**
|
|
61
|
+
* The artifact a tool-call segment carries: its structured result once the tool ran,
|
|
62
|
+
* else the model's input while the call is pending — so the card can be drawn before
|
|
63
|
+
* the handler returns, which is instantaneous anyway.
|
|
64
|
+
*/
|
|
65
|
+
export declare function artifactFromToolCall(segment: AparteToolCallSegment): ArtifactSegment;
|
|
66
|
+
//# sourceMappingURL=segment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../src/segment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAGhG,yFAAyF;AACzF,eAAO,MAAM,qBAAqB,EAAG,UAAmB,CAAC;AAEzD,0EAA0E;AAC1E,eAAO,MAAM,YAAY,aAAa,CAAC;AAEvC;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACtD,IAAI,EAAE,OAAO,qBAAqB,CAAC;IACnC,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,eAAe,CAU1F;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAqB,GAAG,iBAAiB,CAS3E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,eAAe,CAQpF"}
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The leaves the artifact card and the binary-file path BOTH use.
|
|
3
|
+
*
|
|
4
|
+
* Scoped by a call-site census, not by intuition. The obvious-looking candidates are
|
|
5
|
+
* not here: `BINARY_FILE_KINDS` reads as shared and is used only by the card (which
|
|
6
|
+
* consults it to delegate), `PREVIEWABLE_KINDS`, `languageForKind` and
|
|
7
|
+
* `downloadTextArtifact` are card-only, and `FILE_ICON_LABEL` and `formatBytes` are
|
|
8
|
+
* binary-only. A first draft of this module took all of them and would have been a
|
|
9
|
+
* grab-bag named for one of its three jobs.
|
|
10
|
+
*
|
|
11
|
+
* What is genuinely shared is small: fence stripping (which the plain code renderer
|
|
12
|
+
* needs too), the kind→label map, and the throttle pair behind the debounced
|
|
13
|
+
* re-highlight.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Strip leading/trailing markdown code fences (``` or ~~~, optional lang tag).
|
|
17
|
+
* Also strips any content that appears after the closing fence (small models
|
|
18
|
+
* frequently duplicate lines after the closing ``` block).
|
|
19
|
+
*
|
|
20
|
+
* Char-based scanner — no regex.
|
|
21
|
+
*/
|
|
22
|
+
export declare function stripCodeFences(content: string): string;
|
|
23
|
+
export declare function labelForKind(kind: string): string;
|
|
24
|
+
/** Cap a segmentId→timestamp throttle map so a long session can't grow it without
|
|
25
|
+
* bound (one entry per streamed segment). Values are timestamps (tiny), so the cap
|
|
26
|
+
* is generous; delete-then-set refreshes recency, evict the oldest key when full.
|
|
27
|
+
* Evicting a stale entry costs at most one extra highlight/dispatch — never
|
|
28
|
+
* incorrect, since both debounce windows are far shorter than the cap horizon. */
|
|
29
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsBvD;AAsCD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBjD;AAED;;;;mFAImF"}
|
package/dist/tool.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `create_artifact` tool: a real `AparteTool` the app registers, with a handler that
|
|
3
|
+
* returns the document as its structured result.
|
|
4
|
+
*
|
|
5
|
+
* It used to be a string compared in the engine (`event.name === 'create_artifact'`)
|
|
6
|
+
* with no tool behind it: no app declared it to the model, no approval policy could
|
|
7
|
+
* rule on it, and its result skipped the generic path. As a tool it goes through
|
|
8
|
+
* everything a tool goes through — the schema the model sees, the approval gate (a
|
|
9
|
+
* policy may well class writing a document as a `write`), the handler, the envelope —
|
|
10
|
+
* and the card is a tool renderer on its result.
|
|
11
|
+
*/
|
|
12
|
+
import type { AparteTool, AparteToolCall, AparteToolResult } from '@aparte/core';
|
|
13
|
+
/** What the model is told, when the app does not say otherwise. */
|
|
14
|
+
export declare const ARTIFACT_SYSTEM_PROMPT: string;
|
|
15
|
+
export interface ArtifactToolOptions {
|
|
16
|
+
/** The tool's name as the model sees it. Default `create_artifact`. */
|
|
17
|
+
name?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The system prompt the tool carries — how the model learns when to produce a
|
|
20
|
+
* document. A string of your own replaces the default; `false` sends none.
|
|
21
|
+
*/
|
|
22
|
+
systemPrompt?: string | false;
|
|
23
|
+
}
|
|
24
|
+
/** The tool definition — name, description, input schema, system prompt. */
|
|
25
|
+
export declare function createArtifactTool(options?: ArtifactToolOptions): AparteTool;
|
|
26
|
+
/**
|
|
27
|
+
* The handler: the document IS the result. The prose tells the model what happened;
|
|
28
|
+
* the structured twin carries the artifact for the renderer, verbatim.
|
|
29
|
+
*/
|
|
30
|
+
export declare function artifactHandler(call: AparteToolCall): Promise<AparteToolResult>;
|
|
31
|
+
//# sourceMappingURL=tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGjF,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,QAIoC,CAAC;AAExE,MAAM,WAAW,mBAAmB;IAChC,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACjC;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,UAAU,CAmBhF;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAWrF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aparte/plugin-artifacts",
|
|
3
|
+
"version": "0.16.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Artifacts for aparté — a create_artifact tool the model calls, a rich Code/Preview card that renders its result, and the <artifact> tag grammar for a model that writes one in its prose.",
|
|
8
|
+
"customElements": "dist/custom-elements.json",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"@aparte-workspace/source": "./src/index.ts",
|
|
17
|
+
"node": {
|
|
18
|
+
"types": "./dist/index.node.d.ts",
|
|
19
|
+
"default": "./dist/index.node.js"
|
|
20
|
+
},
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@aparte/core": ">=0.16.0 <1.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@custom-elements-manifest/analyzer": "^0.11.0",
|
|
39
|
+
"typescript": "^5.4.0",
|
|
40
|
+
"vite": "^6.0.0",
|
|
41
|
+
"@aparte/core": "0.16.0"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"aparte",
|
|
45
|
+
"plugin",
|
|
46
|
+
"tool",
|
|
47
|
+
"artifact",
|
|
48
|
+
"artifacts",
|
|
49
|
+
"preview"
|
|
50
|
+
],
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/apartejs/aparte.git",
|
|
55
|
+
"directory": "packages/plugins/artifacts"
|
|
56
|
+
},
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/apartejs/aparte/issues"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"dev": "vite",
|
|
62
|
+
"build": "vite build && tsc -b --emitDeclarationOnly --force && pnpm run analyze",
|
|
63
|
+
"analyze": "cem analyze",
|
|
64
|
+
"preview": "vite preview",
|
|
65
|
+
"test": "vitest",
|
|
66
|
+
"test:run": "vitest run",
|
|
67
|
+
"test:coverage": "vitest run --coverage"
|
|
68
|
+
}
|
|
69
|
+
}
|