@alexkroman1/aai 0.3.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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli.js +3436 -0
  3. package/package.json +78 -0
  4. package/sdk/_internal_types.ts +89 -0
  5. package/sdk/_mock_ws.ts +172 -0
  6. package/sdk/_timeout.ts +24 -0
  7. package/sdk/builtin_tools.ts +309 -0
  8. package/sdk/capnweb.ts +341 -0
  9. package/sdk/define_agent.ts +70 -0
  10. package/sdk/direct_executor.ts +195 -0
  11. package/sdk/kv.ts +183 -0
  12. package/sdk/mod.ts +35 -0
  13. package/sdk/protocol.ts +313 -0
  14. package/sdk/runtime.ts +65 -0
  15. package/sdk/s2s.ts +271 -0
  16. package/sdk/server.ts +198 -0
  17. package/sdk/session.ts +438 -0
  18. package/sdk/system_prompt.ts +47 -0
  19. package/sdk/types.ts +406 -0
  20. package/sdk/vector.ts +133 -0
  21. package/sdk/winterc_server.ts +141 -0
  22. package/sdk/worker_entry.ts +99 -0
  23. package/sdk/worker_shim.ts +170 -0
  24. package/sdk/ws_handler.ts +190 -0
  25. package/templates/_shared/.env.example +5 -0
  26. package/templates/_shared/package.json +17 -0
  27. package/templates/code-interpreter/agent.ts +27 -0
  28. package/templates/code-interpreter/client.tsx +2 -0
  29. package/templates/dispatch-center/agent.ts +1536 -0
  30. package/templates/dispatch-center/client.tsx +504 -0
  31. package/templates/embedded-assets/agent.ts +49 -0
  32. package/templates/embedded-assets/client.tsx +2 -0
  33. package/templates/embedded-assets/knowledge.json +20 -0
  34. package/templates/health-assistant/agent.ts +160 -0
  35. package/templates/health-assistant/client.tsx +2 -0
  36. package/templates/infocom-adventure/agent.ts +164 -0
  37. package/templates/infocom-adventure/client.tsx +299 -0
  38. package/templates/math-buddy/agent.ts +21 -0
  39. package/templates/math-buddy/client.tsx +2 -0
  40. package/templates/memory-agent/agent.ts +74 -0
  41. package/templates/memory-agent/client.tsx +2 -0
  42. package/templates/night-owl/agent.ts +98 -0
  43. package/templates/night-owl/client.tsx +28 -0
  44. package/templates/personal-finance/agent.ts +26 -0
  45. package/templates/personal-finance/client.tsx +2 -0
  46. package/templates/simple/agent.ts +6 -0
  47. package/templates/simple/client.tsx +2 -0
  48. package/templates/smart-research/agent.ts +164 -0
  49. package/templates/smart-research/client.tsx +2 -0
  50. package/templates/support/README.md +62 -0
  51. package/templates/support/agent.ts +19 -0
  52. package/templates/support/client.tsx +2 -0
  53. package/templates/travel-concierge/agent.ts +29 -0
  54. package/templates/travel-concierge/client.tsx +2 -0
  55. package/templates/web-researcher/agent.ts +17 -0
  56. package/templates/web-researcher/client.tsx +2 -0
  57. package/ui/_components/app.tsx +37 -0
  58. package/ui/_components/chat_view.tsx +36 -0
  59. package/ui/_components/controls.tsx +32 -0
  60. package/ui/_components/error_banner.tsx +18 -0
  61. package/ui/_components/message_bubble.tsx +21 -0
  62. package/ui/_components/message_list.tsx +61 -0
  63. package/ui/_components/state_indicator.tsx +17 -0
  64. package/ui/_components/thinking_indicator.tsx +19 -0
  65. package/ui/_components/tool_call_block.tsx +110 -0
  66. package/ui/_components/tool_icons.tsx +101 -0
  67. package/ui/_components/transcript.tsx +20 -0
  68. package/ui/audio.ts +170 -0
  69. package/ui/components.ts +49 -0
  70. package/ui/components_mod.ts +37 -0
  71. package/ui/mod.ts +48 -0
  72. package/ui/mount.tsx +112 -0
  73. package/ui/mount_context.ts +19 -0
  74. package/ui/session.ts +456 -0
  75. package/ui/session_mod.ts +27 -0
  76. package/ui/signals.ts +111 -0
  77. package/ui/types.ts +50 -0
  78. package/ui/worklets/capture-processor.js +62 -0
  79. package/ui/worklets/playback-processor.js +110 -0
@@ -0,0 +1,74 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+ import { z } from "zod";
3
+
4
+ export default defineAgent({
5
+ name: "Memory Agent",
6
+ instructions: `You are a helpful assistant with persistent memory. You can \
7
+ remember facts, preferences, and notes across conversations.
8
+
9
+ When the user tells you something worth remembering, save it with a descriptive \
10
+ key. When they ask about something you might have saved, look it up.
11
+
12
+ Use save_memory for storing information and recall_memory for retrieving it. \
13
+ Use list_memories to see what you have stored. Be proactive about saving \
14
+ important details the user shares — names, preferences, ongoing projects, etc.
15
+
16
+ Keep responses concise and conversational. Never say "I saved that to my \
17
+ database" — just confirm naturally, like "Got it, I'll remember that."`,
18
+ greeting:
19
+ "Hey there. I'm an assistant with a long-term memory. Tell me things you want me to remember, and I'll recall them in future conversations.",
20
+ builtinTools: ["web_search"],
21
+ tools: {
22
+ save_memory: {
23
+ description:
24
+ "Save a piece of information to persistent memory. Use a descriptive key like 'user:name' or 'project:status'.",
25
+ parameters: z.object({
26
+ key: z.string().describe(
27
+ "A descriptive key for this memory (e.g. 'user:name', 'preference:color')",
28
+ ),
29
+ value: z.string().describe("The information to remember"),
30
+ }),
31
+ execute: async (
32
+ { key, value }: { key: string; value: string },
33
+ ctx,
34
+ ) => {
35
+ await ctx.kv.set(key, value);
36
+ return { saved: key };
37
+ },
38
+ },
39
+ recall_memory: {
40
+ description: "Retrieve a previously saved memory by its key.",
41
+ parameters: z.object({
42
+ key: z.string().describe("The key to look up"),
43
+ }),
44
+ execute: async ({ key }: { key: string }, ctx) => {
45
+ const value = await ctx.kv.get(key);
46
+ if (value === null) return { found: false, key };
47
+ return { found: true, key, value };
48
+ },
49
+ },
50
+ list_memories: {
51
+ description:
52
+ "List all saved memory keys, optionally filtered by a prefix (e.g. 'user:').",
53
+ parameters: z.object({
54
+ prefix: z.string().describe(
55
+ "Prefix to filter keys (e.g. 'user:'). Use empty string for all.",
56
+ ).optional(),
57
+ }),
58
+ execute: async ({ prefix }: { prefix?: string }, ctx) => {
59
+ const entries = await ctx.kv.list(prefix ?? "");
60
+ return { count: entries.length, keys: entries.map((e) => e.key) };
61
+ },
62
+ },
63
+ forget_memory: {
64
+ description: "Delete a previously saved memory by its key.",
65
+ parameters: z.object({
66
+ key: z.string().describe("The key to delete"),
67
+ }),
68
+ execute: async ({ key }: { key: string }, ctx) => {
69
+ await ctx.kv.delete(key);
70
+ return { deleted: key };
71
+ },
72
+ },
73
+ },
74
+ });
@@ -0,0 +1,2 @@
1
+ import { App, mount } from "@alexkroman1/aai/ui";
2
+ mount(App);
@@ -0,0 +1,98 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+ import { z } from "zod";
3
+
4
+ const PICKS: Record<string, Record<string, string[]>> = {
5
+ movie: {
6
+ chill: ["Lost in Translation", "The Grand Budapest Hotel", "Amelie"],
7
+ intense: ["Inception", "Interstellar", "The Dark Knight"],
8
+ cozy: ["When Harry Met Sally", "The Holiday", "Paddington 2"],
9
+ spooky: ["The Shining", "Get Out", "Hereditary"],
10
+ funny: ["The Big Lebowski", "Airplane!", "Superbad"],
11
+ },
12
+ music: {
13
+ chill: [
14
+ "Khruangbin — Con Todo El Mundo",
15
+ "Tycho — Dive",
16
+ "Bonobo — Migration",
17
+ ],
18
+ intense: [
19
+ "Radiohead — OK Computer",
20
+ "Tool — Lateralus",
21
+ "Deftones — White Pony",
22
+ ],
23
+ cozy: [
24
+ "Norah Jones — Come Away with Me",
25
+ "Iron & Wine — Our Endless Numbered Days",
26
+ "Bon Iver — For Emma, Forever Ago",
27
+ ],
28
+ spooky: [
29
+ "Portishead — Dummy",
30
+ "Massive Attack — Mezzanine",
31
+ "Boards of Canada — Music Has the Right to Children",
32
+ ],
33
+ funny: [
34
+ "Weird Al — Running with Scissors",
35
+ "Flight of the Conchords — S/T",
36
+ "Tenacious D — S/T",
37
+ ],
38
+ },
39
+ book: {
40
+ chill: [
41
+ "Norwegian Wood — Murakami",
42
+ "The Alchemist — Coelho",
43
+ "Siddhartha — Hesse",
44
+ ],
45
+ intense: [
46
+ "Blood Meridian — McCarthy",
47
+ "House of Leaves — Danielewski",
48
+ "Neuromancer — Gibson",
49
+ ],
50
+ cozy: [
51
+ "The House in the Cerulean Sea — Klune",
52
+ "A Man Called Ove — Backman",
53
+ "Anxious People — Backman",
54
+ ],
55
+ spooky: [
56
+ "The Haunting of Hill House — Jackson",
57
+ "Mexican Gothic — Moreno-Garcia",
58
+ "The Turn of the Screw — James",
59
+ ],
60
+ funny: [
61
+ "Good Omens — Pratchett & Gaiman",
62
+ "Hitchhiker's Guide — Adams",
63
+ "Catch-22 — Heller",
64
+ ],
65
+ },
66
+ };
67
+
68
+ export default defineAgent({
69
+ name: "Night Owl",
70
+ instructions:
71
+ `You are Night Owl, a cozy evening companion. You help people wind down, recommend entertainment, and share interesting facts about the night sky. Keep your tone warm and relaxed. Use short, conversational responses.
72
+
73
+ Use run_code for sleep calculations:
74
+ - Each sleep cycle is 90 minutes, plus 15 minutes to fall asleep
75
+ - Bedtime = wake_time - (cycles * 90 + 15) minutes
76
+ - If result is negative, add 1440 (24 hours in minutes)
77
+ - Format as HH:MM`,
78
+ greeting:
79
+ "Hey there, night owl. Try asking me for a cozy movie recommendation, or tell me what time you need to wake up and I'll calculate the best time to fall asleep.",
80
+ builtinTools: ["run_code"],
81
+ tools: {
82
+ recommend: {
83
+ description:
84
+ "Get recommendations for movies, music, or books based on mood.",
85
+ parameters: z.object({
86
+ category: z.enum(["movie", "music", "book"]),
87
+ mood: z.enum(["chill", "intense", "cozy", "spooky", "funny"]),
88
+ }),
89
+ execute: ({ category, mood }) => {
90
+ return {
91
+ category,
92
+ mood,
93
+ picks: PICKS[category]?.[mood] ?? [],
94
+ };
95
+ },
96
+ },
97
+ },
98
+ });
@@ -0,0 +1,28 @@
1
+ import { ChatView, mount, useSession } from "@alexkroman1/aai/ui";
2
+
3
+ function NightOwl() {
4
+ const { started, start } = useSession();
5
+
6
+ if (!started.value) {
7
+ return (
8
+ <div class="flex items-center justify-center h-screen bg-aai-bg font-aai">
9
+ <div class="flex flex-col items-center gap-6 bg-aai-surface border border-aai-border rounded-lg px-12 py-10">
10
+ <div class="text-5xl">&#x1F989;</div>
11
+ <h1 class="text-xl font-semibold text-aai-text m-0">Night Owl</h1>
12
+ <p class="text-sm text-aai-text-muted m-0">your evening companion</p>
13
+ <button
14
+ type="button"
15
+ class="mt-2 px-8 py-3 rounded-aai text-sm font-medium cursor-pointer bg-aai-primary text-white border-none"
16
+ onClick={start}
17
+ >
18
+ Start Conversation
19
+ </button>
20
+ </div>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ return <ChatView />;
26
+ }
27
+
28
+ mount(NightOwl);
@@ -0,0 +1,26 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+
3
+ export default defineAgent({
4
+ name: "Penny",
5
+ greeting:
6
+ "Hey, I'm Penny, your personal finance helper. Try asking me something like, what's 100 dollars in euros, what's the price of bitcoin, or help me split a 120 dollar bill four ways with 20 percent tip.",
7
+ instructions:
8
+ `You are Penny, a friendly personal finance assistant. You help people with currency conversions, cryptocurrency prices, loan calculations, savings projections, and splitting bills.
9
+
10
+ Rules:
11
+ - Always show your math clearly when explaining calculations
12
+ - When discussing investments or crypto, remind users that prices fluctuate and this is not financial advice
13
+ - Be encouraging about savings goals
14
+ - Keep responses concise — this is a voice conversation
15
+ - Round dollar amounts to two decimal places for clarity
16
+
17
+ API endpoints (use fetch_json):
18
+ - Currency rates: https://open.er-api.com/v6/latest/{CODE} — returns { rates: { USD: 1.0, EUR: 0.85, ... } }
19
+ - Crypto prices: https://api.coingecko.com/api/v3/simple/price?ids={coin}&vs_currencies={cur}&include_24hr_change=true&include_market_cap=true
20
+
21
+ Math calculations (use run_code):
22
+ - Compound interest: FV = principal * (1 + rate/n)^(n*years) + monthly * ((1 + rate/n)^(n*years) - 1) / (rate/n)
23
+ - Loan payment: M = P * (r(1+r)^n) / ((1+r)^n - 1) where r = annual_rate/12, n = years*12
24
+ - Tip calculator: tip = bill * percent/100, per_person = (bill + tip) / people`,
25
+ builtinTools: ["run_code", "fetch_json"],
26
+ });
@@ -0,0 +1,2 @@
1
+ import { App, mount } from "@alexkroman1/aai/ui";
2
+ mount(App);
@@ -0,0 +1,6 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+
3
+ export default defineAgent({
4
+ name: "Simple Assistant",
5
+ // builtinTools: ["web_search", "visit_webpage", "fetch_json", "run_code"],
6
+ });
@@ -0,0 +1,2 @@
1
+ import { App, mount } from "@alexkroman1/aai/ui";
2
+ mount(App);
@@ -0,0 +1,164 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+ import { z } from "zod";
3
+ import type { HookContext, StepInfo } from "@alexkroman1/aai/types";
4
+
5
+ /**
6
+ * Smart Research Agent — demonstrates all 5 advanced features:
7
+ * 1. toolChoice: "required" — forces the LLM to use tools every step
8
+ * 2. ctx.messages — tools can read conversation history
9
+ * 3. onStep — logs each step's tool calls
10
+ * 4. onBeforeStep — restricts available tools based on research phase
11
+ * 5. maxSteps as function — adapts max steps based on session complexity
12
+ */
13
+
14
+ type ResearchState = {
15
+ phase: "gather" | "analyze" | "respond";
16
+ sources: string[];
17
+ stepCount: number;
18
+ complexity: "simple" | "deep";
19
+ };
20
+
21
+ export default defineAgent({
22
+ name: "Smart Research Agent",
23
+ instructions: `You are a research assistant that gathers information, \
24
+ analyzes it, then responds. You work in three phases:
25
+ 1. Gather: Use search and fetch tools to collect information.
26
+ 2. Analyze: Use the analyze tool to synthesize your findings.
27
+ 3. Respond: Deliver your final answer.
28
+
29
+ Always search first, then analyze, then answer. Be thorough but concise.`,
30
+ greeting:
31
+ "I'm your research assistant. Ask me anything and I'll dig into it.",
32
+ builtinTools: ["web_search"],
33
+
34
+ // Feature 1: toolChoice — force the LLM to always use a tool
35
+ toolChoice: "required",
36
+
37
+ // Feature 5: maxSteps as function — more steps for complex research
38
+ maxSteps: (ctx: HookContext<ResearchState>) => {
39
+ const state = ctx.state;
40
+ return state.complexity === "deep" ? 10 : 5;
41
+ },
42
+
43
+ state: (): ResearchState => ({
44
+ phase: "gather",
45
+ sources: [],
46
+ stepCount: 0,
47
+ complexity: "simple",
48
+ }),
49
+
50
+ // Feature 3: onStep — track what tools were called each step
51
+ onStep: (step: StepInfo, ctx: HookContext<ResearchState>) => {
52
+ const state = ctx.state;
53
+ state.stepCount++;
54
+ for (const tc of step.toolCalls) {
55
+ console.log(
56
+ `[step ${step.stepNumber}] ${tc.toolName} (phase: ${state.phase})`,
57
+ );
58
+ }
59
+ },
60
+
61
+ // Feature 4: onBeforeStep — restrict tools per research phase
62
+ onBeforeStep: (
63
+ _stepNumber: number,
64
+ ctx: HookContext<ResearchState>,
65
+ ) => {
66
+ const state = ctx.state;
67
+ if (state.phase === "gather") {
68
+ return {
69
+ activeTools: [
70
+ "web_search",
71
+ "save_source",
72
+ "mark_complex",
73
+ "advance_phase",
74
+ ],
75
+ };
76
+ }
77
+ if (state.phase === "analyze") {
78
+ return {
79
+ activeTools: [
80
+ "analyze",
81
+ "conversation_summary",
82
+ "advance_phase",
83
+ ],
84
+ };
85
+ }
86
+ // respond phase: no tools needed, LLM responds with text directly
87
+ return { activeTools: [] };
88
+ },
89
+
90
+ tools: {
91
+ save_source: {
92
+ description: "Save a source URL found during research for later analysis",
93
+ parameters: z.object({
94
+ url: z.string().describe("The source URL"),
95
+ title: z.string().describe("Brief title or description"),
96
+ }),
97
+ execute: (args, ctx) => {
98
+ const state = ctx.state;
99
+ state.sources.push(`${args.title}: ${args.url}`);
100
+ return { saved: true, totalSources: state.sources.length };
101
+ },
102
+ },
103
+
104
+ mark_complex: {
105
+ description:
106
+ "Mark this research query as complex, allowing more search steps",
107
+ execute: (_args, ctx) => {
108
+ const state = ctx.state;
109
+ state.complexity = "deep";
110
+ return { complexity: "deep", maxSteps: 10 };
111
+ },
112
+ },
113
+
114
+ advance_phase: {
115
+ description:
116
+ "Move to the next research phase (gather -> analyze -> respond)",
117
+ execute: (_args, ctx) => {
118
+ const state = ctx.state;
119
+ if (state.phase === "gather") {
120
+ state.phase = "analyze";
121
+ } else if (state.phase === "analyze") {
122
+ state.phase = "respond";
123
+ }
124
+ return { phase: state.phase };
125
+ },
126
+ },
127
+
128
+ // Feature 2: ctx.messages — access conversation history in tools
129
+ analyze: {
130
+ description:
131
+ "Analyze all gathered sources and conversation context to form a conclusion",
132
+ parameters: z.object({
133
+ focus: z.string().describe("What aspect to focus the analysis on"),
134
+ }),
135
+ execute: (args, ctx) => {
136
+ const state = ctx.state;
137
+ // Use ctx.messages to see what's been discussed
138
+ const userMessages = ctx.messages.filter((m) => m.role === "user");
139
+ return {
140
+ focus: args.focus,
141
+ sources: state.sources,
142
+ conversationTurns: userMessages.length,
143
+ totalMessages: ctx.messages.length,
144
+ phase: state.phase,
145
+ };
146
+ },
147
+ },
148
+
149
+ conversation_summary: {
150
+ description: "Get a summary of the conversation so far",
151
+ execute: (_args, ctx) => {
152
+ const msgs = ctx.messages;
153
+ return {
154
+ totalMessages: msgs.length,
155
+ byRole: {
156
+ user: msgs.filter((m) => m.role === "user").length,
157
+ assistant: msgs.filter((m) => m.role === "assistant").length,
158
+ tool: msgs.filter((m) => m.role === "tool").length,
159
+ },
160
+ };
161
+ },
162
+ },
163
+ },
164
+ });
@@ -0,0 +1,2 @@
1
+ import { App, mount } from "@alexkroman1/aai/ui";
2
+ mount(App);
@@ -0,0 +1,62 @@
1
+ # AssemblyAI Support Agent
2
+
3
+ A voice-powered support agent for [AssemblyAI](https://assemblyai.com), built
4
+ with [aai](https://github.com/anthropics/aai).
5
+
6
+ ## Getting started
7
+
8
+ ```sh
9
+ aai deploy # Bundle and deploy
10
+ aai deploy -y # Deploy without prompts
11
+ ```
12
+
13
+ ## Ingesting documentation into the vector store
14
+
15
+ This agent uses `vector_search` to answer questions from AssemblyAI's docs.
16
+ Before it can answer anything, you need to ingest the documentation:
17
+
18
+ ```sh
19
+ aai rag https://assemblyai.com/docs/llms-full.txt
20
+ ```
21
+
22
+ This fetches AssemblyAI's `llms-full.txt` (a single file containing all their
23
+ documentation), chunks it, and upserts the chunks into the vector store.
24
+
25
+ ### Options
26
+
27
+ ```sh
28
+ # Custom chunk size (default: 512 tokens)
29
+ aai rag https://assemblyai.com/docs/llms-full.txt --chunk-size 256
30
+
31
+ # Target a specific server
32
+ aai rag https://assemblyai.com/docs/llms-full.txt --server http://localhost:3100
33
+ ```
34
+
35
+ ### How it works
36
+
37
+ 1. `aai rag` fetches the `llms-full.txt` file from the URL
38
+ 2. It splits the content into chunks (~512 tokens each by default)
39
+ 3. Each chunk is upserted into the vector store, scoped to this agent
40
+ 4. At runtime, the `vector_search` builtin tool queries the vector store to find
41
+ relevant documentation chunks for each user question
42
+
43
+ ### Re-ingesting
44
+
45
+ Run the same `aai rag` command again to update the vector store with fresh
46
+ documentation. Chunks are keyed by content, so unchanged pages won't be
47
+ duplicated.
48
+
49
+ ## Environment variables
50
+
51
+ ```sh
52
+ aai env add MY_KEY # Set a secret (prompts for value)
53
+ aai env ls # List secret names
54
+ aai env pull # Pull names into .env for reference
55
+ aai env rm MY_KEY # Remove a secret
56
+ ```
57
+
58
+ Access secrets in your agent via `ctx.env.MY_KEY`.
59
+
60
+ ## Learn more
61
+
62
+ See `CLAUDE.md` for the full agent API reference.
@@ -0,0 +1,19 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+
3
+ export default defineAgent({
4
+ name: "AssemblyAI Support",
5
+ instructions:
6
+ `You are a friendly support agent for AssemblyAI. Help users with questions \
7
+ about AssemblyAI's speech-to-text API, audio intelligence features, and integrations.
8
+
9
+ - Always use vector_search to find relevant documentation before answering.
10
+ - Base your answers strictly on the retrieved documentation — don't guess.
11
+ - If search results aren't relevant to the question, say the docs don't cover that topic \
12
+ and suggest visiting assemblyai.com or contacting support@assemblyai.com.
13
+ - Be concise — this is a voice conversation.
14
+ - When explaining API usage, mention endpoint names and key parameters.
15
+ - If a question is ambiguous, ask the user to clarify which product or feature they mean.`,
16
+ greeting:
17
+ "Hi! I'm the AssemblyAI support assistant. I can help you with questions about our speech-to-text API, audio intelligence features, LLM gateway, and more. What can I help you with?",
18
+ builtinTools: ["vector_search"],
19
+ });
@@ -0,0 +1,2 @@
1
+ import { App, mount } from "@alexkroman1/aai/ui";
2
+ mount(App);
@@ -0,0 +1,29 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+
3
+ export default defineAgent({
4
+ name: "Aria",
5
+ instructions:
6
+ `You are Aria, a luxury travel concierge. You help customers plan trips,
7
+ find flights and hotels, check weather at destinations, and convert currencies.
8
+
9
+ Rules:
10
+ - Always check weather before recommending activities
11
+ - When discussing costs, convert to the customer's preferred currency
12
+ - Suggest specific restaurants, landmarks, and experiences
13
+ - Be warm and enthusiastic but concise — this is a voice conversation
14
+ - If the customer hasn't specified dates, ask for them before searching flights
15
+ - Use web_search to find current flight and hotel options, then visit_webpage for details
16
+
17
+ API endpoints (use fetch_json):
18
+ - Geocoding: https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1&language=en
19
+ Returns { results: [{ name, country, latitude, longitude }] }
20
+ - Weather forecast: https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,weathercode&timezone=auto&forecast_days=7
21
+ Returns { daily: { time, temperature_2m_max, temperature_2m_min, precipitation_sum, weathercode } }
22
+ Weather codes: 0=Clear, 1=Mainly clear, 2=Partly cloudy, 3=Overcast, 45/48=Fog, 51/53/55=Drizzle, 61/63/65=Rain, 71/73/75=Snow, 80-82=Rain showers, 85/86=Snow showers, 95/96/99=Thunderstorm
23
+ Convert C to F: F = C * 9/5 + 32
24
+ - Currency rates: https://open.er-api.com/v6/latest/{CODE}
25
+ Returns { rates: { USD: 1.0, EUR: 0.85, ... } }`,
26
+ greeting:
27
+ "Hey, I'm Aria, your travel concierge. Try asking me something like, what's the weather in Tokyo this week, or help me plan a long weekend in Barcelona.",
28
+ builtinTools: ["web_search", "visit_webpage", "fetch_json"],
29
+ });
@@ -0,0 +1,2 @@
1
+ import { App, mount } from "@alexkroman1/aai/ui";
2
+ mount(App);
@@ -0,0 +1,17 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+
3
+ export default defineAgent({
4
+ name: "Scout",
5
+ instructions:
6
+ `You are Scout, a research assistant who finds answers by searching the web.
7
+
8
+ - Search first. Never guess or rely on memory for factual questions.
9
+ - Use visit_webpage when search snippets aren't detailed enough.
10
+ - For complex questions, search multiple times with different queries.
11
+ - Cite sources by website name.
12
+ - Be concise — this is a voice conversation.
13
+ - If results are unclear or contradictory, say so.`,
14
+ greeting:
15
+ "Hey, I'm Scout. I search the web for answers. Try asking me something like, what happened in tech news today, or who won the last World Cup.",
16
+ builtinTools: ["web_search", "visit_webpage"],
17
+ });
@@ -0,0 +1,2 @@
1
+ import { App, mount } from "@alexkroman1/aai/ui";
2
+ mount(App);
@@ -0,0 +1,37 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+ import type * as preact from "preact";
3
+ import { useMountConfig } from "../mount_context.ts";
4
+ import { useSession } from "../signals.ts";
5
+ import { ChatView } from "./chat_view.tsx";
6
+
7
+ const AAI_LOGO = `▄▀█ ▄▀█ █\n█▀█ █▀█ █`;
8
+
9
+ export function App(): preact.JSX.Element {
10
+ const { started, start } = useSession();
11
+ const { title } = useMountConfig();
12
+
13
+ if (!started.value) {
14
+ return (
15
+ <div class="flex items-center justify-center h-screen bg-aai-bg font-aai">
16
+ <div class="flex flex-col items-center gap-6 bg-aai-surface border border-aai-border rounded-lg px-12 py-10">
17
+ {title ? (
18
+ <span class="text-lg font-semibold text-aai-primary">{title}</span>
19
+ ) : (
20
+ <span class="font-aai-mono text-lg leading-[1.1] font-bold text-aai-primary whitespace-pre">
21
+ {AAI_LOGO}
22
+ </span>
23
+ )}
24
+ <button
25
+ type="button"
26
+ class="h-8 px-4 py-1.5 rounded-aai text-sm font-medium leading-[130%] cursor-pointer bg-aai-surface-hover text-aai-text-secondary border border-aai-border outline-none"
27
+ onClick={start}
28
+ >
29
+ Start
30
+ </button>
31
+ </div>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ return <ChatView />;
37
+ }
@@ -0,0 +1,36 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+ import type * as preact from "preact";
3
+ import { useMountConfig } from "../mount_context.ts";
4
+ import { useSession } from "../signals.ts";
5
+ import { Controls } from "./controls.tsx";
6
+ import { ErrorBanner } from "./error_banner.tsx";
7
+ import { MessageList } from "./message_list.tsx";
8
+ import { StateIndicator } from "./state_indicator.tsx";
9
+
10
+ const AAI_LOGO = `▄▀█ ▄▀█ █\n█▀█ █▀█ █`;
11
+
12
+ export function ChatView(): preact.JSX.Element {
13
+ const { session } = useSession();
14
+ const { title } = useMountConfig();
15
+
16
+ return (
17
+ <div class="flex flex-col h-screen max-w-130 mx-auto bg-aai-bg text-aai-text font-aai text-sm">
18
+ {/* Header */}
19
+ <div class="flex items-center gap-3 px-4 py-3 border-b border-aai-border shrink-0">
20
+ {title ? (
21
+ <span class="text-sm font-semibold text-aai-primary">{title}</span>
22
+ ) : (
23
+ <span class="font-aai-mono text-[10px] leading-[1.1] font-bold text-aai-primary whitespace-pre">
24
+ {AAI_LOGO}
25
+ </span>
26
+ )}
27
+ <div class="ml-auto">
28
+ <StateIndicator state={session.state} />
29
+ </div>
30
+ </div>
31
+ <ErrorBanner error={session.error} />
32
+ <MessageList />
33
+ <Controls />
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,32 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+ import { useSession } from "../signals.ts";
3
+
4
+ export function Controls() {
5
+ const { running, toggle, reset } = useSession();
6
+
7
+ const btnBase =
8
+ "h-8 px-3 py-1.5 rounded-aai text-sm font-medium leading-[130%] cursor-pointer border border-transparent outline-none";
9
+
10
+ return (
11
+ <div class="flex gap-2 px-4 py-3 border-t border-aai-border shrink-0">
12
+ <button
13
+ type="button"
14
+ class={`${btnBase} ${
15
+ running.value
16
+ ? "bg-aai-surface-hover text-aai-text-secondary border-aai-border"
17
+ : "bg-aai-surface-hover text-aai-text-secondary border-aai-border"
18
+ }`}
19
+ onClick={toggle}
20
+ >
21
+ {running.value ? "Stop" : "Resume"}
22
+ </button>
23
+ <button
24
+ type="button"
25
+ class={`${btnBase} bg-transparent text-aai-text-secondary border-aai-border`}
26
+ onClick={reset}
27
+ >
28
+ New Conversation
29
+ </button>
30
+ </div>
31
+ );
32
+ }