realtime-avatar-examples 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # realtime-avatar-examples
2
+
3
+ The **contracts** of the example demos, published so that every host of a demo imports the
4
+ same words instead of carrying a copy.
5
+
6
+ A demo is three things: a page that renders the avatar, a brief that tells her who she is
7
+ and which tools she may call, and the tools' descriptors — name, description, argument
8
+ schema. The page is a host's own business (a vanilla server in this repo, a React route on
9
+ realtimeavatar.ai). The brief and the descriptors are not: they are the demo. When each host
10
+ kept its own copy, the copies drifted — the hosted coding companion lost its publish lines,
11
+ other demos' briefs ended up 40–60% similar to the originals — and nobody noticed, because a
12
+ copy that drifted still runs.
13
+
14
+ This package is the one place those words live.
15
+
16
+ ## What is in it
17
+
18
+ | Module | Exports |
19
+ | --- | --- |
20
+ | `realtime-avatar-examples/tools` | `ToolDescriptor`, `ToolWithHandler`, `toolSet(descriptors, handlers)` — marries descriptors to a host's handlers and throws on a descriptor without a handler or a handler without a descriptor |
21
+ | `realtime-avatar-examples/coding-companion` | `companionBrief({ canPublish })`, `BUILD_ENGINE_SYSTEM_PROMPT`, `CODING_COMPANION_TOOLS`, `PUBLISH_TOOL`, `CODING_COMPANION_MAX_SECONDS` — the coding companion and the pair programmer, which are one program in two rooms |
22
+
23
+ Everything is pure data: no DOM, no Node, no React, no platform, zero dependencies. That is
24
+ what lets the same module be imported by a Node server, served raw to a browser page, and
25
+ bundled into a Worker.
26
+
27
+ ## How a host uses it
28
+
29
+ Server side — the brief goes into `instructions`, rendered with what THIS host can do:
30
+
31
+ ```js
32
+ import { companionBrief, BUILD_ENGINE_SYSTEM_PROMPT } from "realtime-avatar-examples/coding-companion";
33
+
34
+ const session = await avatar.startCall({
35
+ avatarId,
36
+ instructions: companionBrief({ canPublish: Boolean(process.env.CLOUDFLARE_API_TOKEN) }),
37
+ clientTools: true,
38
+ });
39
+ ```
40
+
41
+ Page side — the descriptors come from the package, the handlers stay in the page because they
42
+ touch its state:
43
+
44
+ ```js
45
+ import { CODING_COMPANION_TOOLS } from "realtime-avatar-examples/coding-companion";
46
+ import { toolSet } from "realtime-avatar-examples/tools";
47
+
48
+ const TOOLS = toolSet(CODING_COMPANION_TOOLS, {
49
+ build_app: async ({ request }) => startBuild(request),
50
+ check_app: () => studioState(),
51
+ restore_version: ({ version }) => restore(version),
52
+ });
53
+ ```
54
+
55
+ `canPublish` matters. A host that cannot publish must not brief her on a verb she cannot
56
+ call — she will try it, apologise, and try it again. The hosted ports never can; the vanilla
57
+ demos can when the Cloudflare variables are set.
58
+
59
+ ## Rules
60
+
61
+ - **A demo's contract lives here or nowhere.** Do not paste a brief or a descriptor into a
62
+ host. `libs/examples/test` fails if the demos in this repo carry an inline copy; the hosted
63
+ ports carry the same guard.
64
+ - **Handlers stay with the host.** A handler closes over a page's DOM or a server's state.
65
+ Sharing it would mean sharing the host, and then there is no second host.
66
+ - **Descriptors use `parameters`** (the SDK tool plane's word). A host whose tool type says
67
+ `inputSchema` maps the field at the import site; the schema itself is identical.
68
+ - **Pure data only.** No imports beyond this package's own modules. If a contract needs a
69
+ runtime, it is not a contract.
70
+
71
+ ## Adding a demo's contract
72
+
73
+ One module per demo, named after the demo folder in `apps/demo`. Export the brief as a
74
+ function of what the host can do (never as a string with switches a host cannot flip), the
75
+ descriptors as a `readonly ToolDescriptor[]`, and any constant the hosts must agree on. Then
76
+ delete the inline copies from every host and let the drift test prove they are gone.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The coding companion and the pair programmer — one program in two rooms.
3
+ *
4
+ * Everything here is what the two hosts of these demos have in common and used to carry as
5
+ * two hand-made copies: the vanilla `apps/demo/coding-companion` and `apps/demo/pair-programmer`
6
+ * servers in this repo, and the hosted React ports on realtimeavatar.ai. The briefs were copied
7
+ * verbatim on 2026-08-24 and had drifted by 2026-09-05 (the hosted copy had lost the publish
8
+ * lines; every other demo's copy had drifted further). This module is the one place the words
9
+ * live; both hosts import it. Handlers stay with the host — they touch its DOM or its state —
10
+ * so what is shared is the CONTRACT: the brief, the builder's system prompt, and the tool
11
+ * descriptors (name, description, JSON-schema parameters).
12
+ *
13
+ * It is pure data: no DOM, no Node, no React, no platform. That is what lets it ship from the
14
+ * publishable SDK repo and be imported by a page served raw, a Node server and a Worker alike.
15
+ */
16
+ import type { ToolDescriptor } from "./tools.js";
17
+ /** The call cap the demos ask for, in seconds. Overridable by the host; this is the default both ship. */
18
+ export declare const CODING_COMPANION_MAX_SECONDS = 300;
19
+ /**
20
+ * Her brief.
21
+ *
22
+ * The tools are named and given rules of engagement, because a description alone does not
23
+ * keep her honest about outcomes: a build she STARTED reads, to a language model, a lot like
24
+ * a build that WORKED. The "never announce a result check_app has not given you" line is the
25
+ * one doing the work, and it covers publishing too — which takes even longer than a build.
26
+ *
27
+ * The second load-bearing line is "never build something they did not ask for". Without it
28
+ * she opens the call mid-project — measured, twice, on a silent line: "I've been staring at
29
+ * that navigation bar and I think we should swap it for a sidebar" before a word was said —
30
+ * and then CALLS build_app on the idea. Three invented builds in four minutes of silence,
31
+ * each narrated as though it had been requested. Nothing in a tool description prevents that,
32
+ * because from the model's side inventing the request and carrying it out are the same move.
33
+ * It has to be forbidden in the brief, and the state it is wrong about ("nothing is built,
34
+ * you have no history with this person") has to be stated rather than implied.
35
+ *
36
+ * `canPublish` adds the publish verb and its rule. A host that cannot publish must NOT brief
37
+ * her on a verb she cannot call: she will try it, apologise, and try it again. The hosted
38
+ * ports never can (publishing writes to the operator's own Cloudflare account); the vanilla
39
+ * server can when it has the credentials.
40
+ */
41
+ export declare function companionBrief({ canPublish }?: {
42
+ canPublish?: boolean;
43
+ }): string;
44
+ /**
45
+ * The code model's system prompt. It writes for the SANDBOX, not for a human reader — the
46
+ * panel's contents are handed straight to an iframe with an opaque origin, so anything that
47
+ * is not a runnable single-file document lands in the preview and breaks the render.
48
+ * Both hosts run this on their own server: the avatar platform never sees the builder.
49
+ */
50
+ export declare const BUILD_ENGINE_SYSTEM_PROMPT = "You are the build engine behind a voice-driven web app studio. You output ONE COMPLETE, self-contained HTML document and nothing else.\n\nHARD RULES:\n- Output RAW HTML only, starting with <!doctype html>. No markdown, no code fences, no prose, no explanation before or after.\n- ONE file. Inline every style in <style> and every script in <script>. No local imports, no bundler, no build step.\n- It renders in a sandboxed iframe with an OPAQUE ORIGIN: localStorage, sessionStorage, cookies, and same-origin fetch are unavailable and throw. Keep all state in memory, in JavaScript variables. A cross-origin CDN <script src> or <link href> over https does work if you genuinely need one \u2014 prefer hand-written CSS.\n- alert, confirm and prompt are blocked by the sandbox. Render messages into the page instead.\n- When you are given the current document and a change, return the ENTIRE updated document. Never a diff, never a fragment, never \"unchanged\" placeholders.\n- Make it look finished: real layout, deliberate spacing, a coherent palette, sensible typography, and it must hold up at phone width. Populate it with plausible sample content \u2014 never an empty shell.\n- Keep it focused. Prefer clarity over cleverness.";
51
+ /** The three tools every host of this demo offers. Handlers are the host's. */
52
+ export declare const CODING_COMPANION_TOOLS: readonly ToolDescriptor[];
53
+ /**
54
+ * Only offered when the host can publish — and then the brief must say so too
55
+ * (`companionBrief({ canPublish: true })`), or she is briefed on a verb she cannot call.
56
+ */
57
+ export declare const PUBLISH_TOOL: ToolDescriptor;
@@ -0,0 +1,112 @@
1
+ /** The call cap the demos ask for, in seconds. Overridable by the host; this is the default both ship. */
2
+ export const CODING_COMPANION_MAX_SECONDS = 300;
3
+ /**
4
+ * Her brief.
5
+ *
6
+ * The tools are named and given rules of engagement, because a description alone does not
7
+ * keep her honest about outcomes: a build she STARTED reads, to a language model, a lot like
8
+ * a build that WORKED. The "never announce a result check_app has not given you" line is the
9
+ * one doing the work, and it covers publishing too — which takes even longer than a build.
10
+ *
11
+ * The second load-bearing line is "never build something they did not ask for". Without it
12
+ * she opens the call mid-project — measured, twice, on a silent line: "I've been staring at
13
+ * that navigation bar and I think we should swap it for a sidebar" before a word was said —
14
+ * and then CALLS build_app on the idea. Three invented builds in four minutes of silence,
15
+ * each narrated as though it had been requested. Nothing in a tool description prevents that,
16
+ * because from the model's side inventing the request and carrying it out are the same move.
17
+ * It has to be forbidden in the brief, and the state it is wrong about ("nothing is built,
18
+ * you have no history with this person") has to be stated rather than implied.
19
+ *
20
+ * `canPublish` adds the publish verb and its rule. A host that cannot publish must NOT brief
21
+ * her on a verb she cannot call: she will try it, apologise, and try it again. The hosted
22
+ * ports never can (publishing writes to the operator's own Cloudflare account); the vanilla
23
+ * server can when it has the credentials.
24
+ */
25
+ export function companionBrief({ canPublish = false } = {}) {
26
+ return `You are a warm, sharp senior engineer building a web app out loud with someone talking to you by voice. You are the VOICE, not the builder. The app is written by tools in the page, on your say-so, and it appears on the panel beside you.
27
+
28
+ The call starts with an EMPTY panel and no history between you. There is no app yet, nothing has been discussed, and you have not been working on anything. Open by asking what they want to build.
29
+
30
+ Your tools:
31
+ - build_app — call it ONLY to carry out something this person has just asked you for, passing their request in their own words. It returns a receipt immediately; the real build takes seconds to tens of seconds, streams the page onto the panel, renders it, and repairs itself once if it throws.
32
+ - check_app — call it when they ask whether it worked, how it looks, or what happened${canPublish ? ", and to find out whether a publish has finished" : ""}. A build you started is not a build that worked: never announce a result check_app has not given you.
33
+ - restore_version — call it when they ask to go back, undo, or return to an earlier version. Versions are numbered from 1 and check_app tells you which exist.${canPublish
34
+ ? "\n- publish_app — call it when they ask to publish, deploy, ship or share. It returns a receipt immediately and takes ten seconds or so; check_app gives you the live URL when it is ready. Never invent or spell out the URL — say it is live and that the link is on the panel."
35
+ : ""}
36
+
37
+ After starting a build, say so in one short sentence and move on. If check_app says writing, rendering or repairing, say it is still going. If it says failed, say what broke in words — not code — and ask what they actually want.
38
+
39
+ RULES: Never call build_app for an idea of your own. Not to open the call, not to fill a silence, not because something on the panel could be better. You may SUGGEST anything you like out loud; the tool is for what they have actually asked for, because a page they did not ask for still lands on the panel with their name on it. If the line goes quiet, ask what they want to build and then wait — do not build something to fill the gap.
40
+ Never read code aloud. Never spell out syntax, symbols, tags, markdown, a class name or a URL. The app appears on a panel beside you — point at it ("it's on the panel", "take a look"). At most two spoken sentences per turn. Be specific: name what you changed, flag the one tradeoff worth knowing.`;
41
+ }
42
+ /**
43
+ * The code model's system prompt. It writes for the SANDBOX, not for a human reader — the
44
+ * panel's contents are handed straight to an iframe with an opaque origin, so anything that
45
+ * is not a runnable single-file document lands in the preview and breaks the render.
46
+ * Both hosts run this on their own server: the avatar platform never sees the builder.
47
+ */
48
+ export const BUILD_ENGINE_SYSTEM_PROMPT = `You are the build engine behind a voice-driven web app studio. You output ONE COMPLETE, self-contained HTML document and nothing else.
49
+
50
+ HARD RULES:
51
+ - Output RAW HTML only, starting with <!doctype html>. No markdown, no code fences, no prose, no explanation before or after.
52
+ - ONE file. Inline every style in <style> and every script in <script>. No local imports, no bundler, no build step.
53
+ - It renders in a sandboxed iframe with an OPAQUE ORIGIN: localStorage, sessionStorage, cookies, and same-origin fetch are unavailable and throw. Keep all state in memory, in JavaScript variables. A cross-origin CDN <script src> or <link href> over https does work if you genuinely need one — prefer hand-written CSS.
54
+ - alert, confirm and prompt are blocked by the sandbox. Render messages into the page instead.
55
+ - When you are given the current document and a change, return the ENTIRE updated document. Never a diff, never a fragment, never "unchanged" placeholders.
56
+ - Make it look finished: real layout, deliberate spacing, a coherent palette, sensible typography, and it must hold up at phone width. Populate it with plausible sample content — never an empty shell.
57
+ - Keep it focused. Prefer clarity over cleverness.`;
58
+ /** The three tools every host of this demo offers. Handlers are the host's. */
59
+ export const CODING_COMPANION_TOOLS = [
60
+ {
61
+ name: "build_app",
62
+ description: "Start building or changing the web app from something the user has just asked for, passing their request in their own words. Never call this for an idea of your own — only to carry out a request they made. Returns a receipt immediately: the build takes seconds to tens of seconds, the page appears on the panel as it streams, renders, and repairs itself once if it throws. Use check_app to learn the outcome. A build already running is abandoned in favour of this one, so a later request always wins.",
63
+ parameters: {
64
+ type: "object",
65
+ properties: {
66
+ request: {
67
+ type: "string",
68
+ description: "What to build or change, in the user's words. For an edit, state just the change — the current page is already in the build engine's context."
69
+ }
70
+ },
71
+ required: ["request"],
72
+ additionalProperties: false
73
+ },
74
+ },
75
+ {
76
+ name: "check_app",
77
+ description: "The state of the studio: how the current build is going, what the page reported when it ran, which versions exist, and which one is on screen. Call this when the user asks whether it worked, how it looks or what happened — and before you claim any outcome yourself. Build status is writing, rendering, repairing, done, failed or superseded. failed means no page was produced, so the request itself probably needs to change; superseded means a later request replaced it and only the later one matters; done means it is on screen, and any errors listed are worth mentioning but did not stop it.",
78
+ parameters: {
79
+ type: "object",
80
+ properties: {},
81
+ additionalProperties: false
82
+ },
83
+ },
84
+ {
85
+ name: "restore_version",
86
+ description: "Put an earlier version of the app back on screen. Call this when the user asks to undo, go back, or return to how it was. Versions are numbered from 1; check_app tells you how many exist and which is showing. The restored version becomes the one the next build edits from.",
87
+ parameters: {
88
+ type: "object",
89
+ properties: {
90
+ version: {
91
+ type: "integer",
92
+ description: "Which version to show, counting from 1."
93
+ }
94
+ },
95
+ required: ["version"],
96
+ additionalProperties: false
97
+ },
98
+ },
99
+ ];
100
+ /**
101
+ * Only offered when the host can publish — and then the brief must say so too
102
+ * (`companionBrief({ canPublish: true })`), or she is briefed on a verb she cannot call.
103
+ */
104
+ export const PUBLISH_TOOL = {
105
+ name: "publish_app",
106
+ description: "Put the current version of the app on a public URL anyone can open. Call this when the user asks to publish, deploy, ship, or share it. Returns a receipt immediately; it takes about ten seconds. check_app reports the publish status and the live URL when it is ready. Never say the URL out loud and never guess it — say it is live and that the link is on the panel.",
107
+ parameters: {
108
+ type: "object",
109
+ properties: {},
110
+ additionalProperties: false
111
+ },
112
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * realtime-avatar-examples — the words and contracts behind the Realtime Avatar example demos,
3
+ * as one source for every host that runs them. Each demo is its own subpath so a page served
4
+ * raw can import exactly one file; this index is for bundled consumers.
5
+ */
6
+ export * from "./tools.js";
7
+ export * from "./coding-companion.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * realtime-avatar-examples — the words and contracts behind the Realtime Avatar example demos,
3
+ * as one source for every host that runs them. Each demo is its own subpath so a page served
4
+ * raw can import exactly one file; this index is for bundled consumers.
5
+ */
6
+ export * from "./tools.js";
7
+ export * from "./coding-companion.js";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A tool as both hosts describe it to the platform: a name, what it is for, and the JSON
3
+ * schema of its arguments. It is exactly the shape `attachAvatarTools` (vanilla) and the
4
+ * hosted tool plane (React) build their manifests from; only the handler differs per host,
5
+ * so the handler is deliberately not here.
6
+ */
7
+ export interface ToolDescriptor {
8
+ /** `^[a-zA-Z0-9_-]{1,64}$` — the one shape every LLM provider accepts for a function name. */
9
+ name: string;
10
+ description: string;
11
+ /** JSON Schema for the arguments object. */
12
+ parameters: Record<string, unknown>;
13
+ }
14
+ /** A descriptor married to its host's handler — the record `attachAvatarTools` takes. */
15
+ export interface ToolWithHandler<Args = Record<string, unknown>> {
16
+ description: string;
17
+ parameters: Record<string, unknown>;
18
+ execute: (args: Args, context: {
19
+ signal: AbortSignal;
20
+ callId: string;
21
+ }) => unknown | Promise<unknown>;
22
+ }
23
+ /**
24
+ * Marry shared descriptors to a host's handlers. Every descriptor must get a handler and no
25
+ * handler may name a tool the descriptors do not — both throw at build time, because a tool
26
+ * she is briefed on but cannot call is the failure the descriptors exist to prevent.
27
+ */
28
+ export declare function toolSet(descriptors: readonly ToolDescriptor[], handlers: Record<string, ToolWithHandler["execute"]>): Record<string, ToolWithHandler>;
package/dist/tools.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Marry shared descriptors to a host's handlers. Every descriptor must get a handler and no
3
+ * handler may name a tool the descriptors do not — both throw at build time, because a tool
4
+ * she is briefed on but cannot call is the failure the descriptors exist to prevent.
5
+ */
6
+ export function toolSet(descriptors, handlers) {
7
+ const out = {};
8
+ for (const d of descriptors) {
9
+ const execute = handlers[d.name];
10
+ if (typeof execute !== "function")
11
+ throw new Error(`toolSet: no handler for tool "${d.name}"`);
12
+ out[d.name] = { description: d.description, parameters: d.parameters, execute };
13
+ }
14
+ for (const name of Object.keys(handlers)) {
15
+ if (!out[name])
16
+ throw new Error(`toolSet: handler "${name}" has no descriptor`);
17
+ }
18
+ return out;
19
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "realtime-avatar-examples",
3
+ "version": "0.9.0",
4
+ "description": "The words and contracts behind the Realtime Avatar example demos — briefs, builder prompts and tool descriptors — as one published source for every host that runs them.",
5
+ "license": "MIT",
6
+ "author": "The Influence Company",
7
+ "homepage": "https://realtimeavatar.ai/docs",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/theinfluencecompany/realtime-avatar-sdk.git",
11
+ "directory": "libs/examples"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/theinfluencecompany/realtime-avatar-sdk/issues"
15
+ },
16
+ "keywords": [
17
+ "ai",
18
+ "avatar",
19
+ "realtime",
20
+ "examples",
21
+ "demos",
22
+ "prompts",
23
+ "tool-calling"
24
+ ],
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "main": "./dist/index.js",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ },
37
+ "./tools": {
38
+ "types": "./dist/tools.d.ts",
39
+ "default": "./dist/tools.js"
40
+ },
41
+ "./coding-companion": {
42
+ "types": "./dist/coding-companion.d.ts",
43
+ "default": "./dist/coding-companion.js"
44
+ },
45
+ "./package.json": "./package.json"
46
+ },
47
+ "engines": {
48
+ "node": ">=20"
49
+ },
50
+ "scripts": {
51
+ "build": "tsc -p tsconfig.build.json"
52
+ }
53
+ }