@alexkroman1/aai 0.7.11 → 0.8.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 (92) hide show
  1. package/README.md +1 -1
  2. package/dist/aai.js +1 -1
  3. package/dist/cli.js +693 -349
  4. package/dist/sdk/_internal_types.d.ts +0 -1
  5. package/dist/sdk/_internal_types.d.ts.map +1 -1
  6. package/dist/sdk/_internal_types.js.map +1 -1
  7. package/dist/sdk/_render_check.d.ts +7 -0
  8. package/dist/sdk/_render_check.d.ts.map +1 -0
  9. package/dist/sdk/_render_check.js +38 -0
  10. package/dist/sdk/_render_check.js.map +1 -0
  11. package/dist/sdk/builtin_tools.d.ts.map +1 -1
  12. package/dist/sdk/builtin_tools.js +21 -0
  13. package/dist/sdk/builtin_tools.js.map +1 -1
  14. package/dist/sdk/define_agent.d.ts.map +1 -1
  15. package/dist/sdk/define_agent.js +0 -1
  16. package/dist/sdk/define_agent.js.map +1 -1
  17. package/dist/sdk/direct_executor.d.ts.map +1 -1
  18. package/dist/sdk/direct_executor.js +0 -1
  19. package/dist/sdk/direct_executor.js.map +1 -1
  20. package/dist/sdk/memory_tools.d.ts +38 -0
  21. package/dist/sdk/memory_tools.d.ts.map +1 -0
  22. package/dist/sdk/memory_tools.js +77 -0
  23. package/dist/sdk/memory_tools.js.map +1 -0
  24. package/dist/sdk/mod.d.ts +3 -1
  25. package/dist/sdk/mod.d.ts.map +1 -1
  26. package/dist/sdk/mod.js +2 -0
  27. package/dist/sdk/mod.js.map +1 -1
  28. package/dist/sdk/protocol.d.ts +9 -3
  29. package/dist/sdk/protocol.d.ts.map +1 -1
  30. package/dist/sdk/protocol.js +2 -1
  31. package/dist/sdk/protocol.js.map +1 -1
  32. package/dist/sdk/s2s.d.ts.map +1 -1
  33. package/dist/sdk/s2s.js +9 -3
  34. package/dist/sdk/s2s.js.map +1 -1
  35. package/dist/sdk/session.d.ts.map +1 -1
  36. package/dist/sdk/session.js +5 -0
  37. package/dist/sdk/session.js.map +1 -1
  38. package/dist/sdk/types.d.ts +23 -14
  39. package/dist/sdk/types.d.ts.map +1 -1
  40. package/dist/sdk/types.js +29 -0
  41. package/dist/sdk/types.js.map +1 -1
  42. package/dist/ui/_components/app.d.ts.map +1 -1
  43. package/dist/ui/_components/app.js +6 -7
  44. package/dist/ui/_components/app.js.map +1 -1
  45. package/dist/ui/_components/message_list.d.ts.map +1 -1
  46. package/dist/ui/_components/message_list.js +2 -1
  47. package/dist/ui/_components/message_list.js.map +1 -1
  48. package/dist/ui/_components/sidebar_layout.d.ts +19 -0
  49. package/dist/ui/_components/sidebar_layout.d.ts.map +1 -0
  50. package/dist/ui/_components/sidebar_layout.js +21 -0
  51. package/dist/ui/_components/sidebar_layout.js.map +1 -0
  52. package/dist/ui/_components/start_screen.d.ts +24 -0
  53. package/dist/ui/_components/start_screen.d.ts.map +1 -0
  54. package/dist/ui/_components/start_screen.js +25 -0
  55. package/dist/ui/_components/start_screen.js.map +1 -0
  56. package/dist/ui/_hooks.d.ts +21 -0
  57. package/dist/ui/_hooks.d.ts.map +1 -0
  58. package/dist/ui/_hooks.js +35 -0
  59. package/dist/ui/_hooks.js.map +1 -0
  60. package/dist/ui/components.d.ts +20 -0
  61. package/dist/ui/components.d.ts.map +1 -1
  62. package/dist/ui/components.js +12 -0
  63. package/dist/ui/components.js.map +1 -1
  64. package/dist/ui/components_mod.d.ts +3 -2
  65. package/dist/ui/components_mod.d.ts.map +1 -1
  66. package/dist/ui/components_mod.js +3 -2
  67. package/dist/ui/components_mod.js.map +1 -1
  68. package/dist/ui/mod.d.ts +4 -3
  69. package/dist/ui/mod.d.ts.map +1 -1
  70. package/dist/ui/mod.js +3 -2
  71. package/dist/ui/mod.js.map +1 -1
  72. package/dist/ui/session.d.ts +7 -0
  73. package/dist/ui/session.d.ts.map +1 -1
  74. package/dist/ui/session.js +21 -3
  75. package/dist/ui/session.js.map +1 -1
  76. package/dist/ui/signals.d.ts +14 -0
  77. package/dist/ui/signals.d.ts.map +1 -1
  78. package/dist/ui/signals.js +55 -3
  79. package/dist/ui/signals.js.map +1 -1
  80. package/package.json +19 -2
  81. package/templates/_shared/CLAUDE.md +305 -116
  82. package/templates/_shared/package.json +4 -1
  83. package/templates/dispatch-center/agent.ts +43 -72
  84. package/templates/embedded-assets/agent.ts +4 -5
  85. package/templates/health-assistant/agent.ts +7 -7
  86. package/templates/infocom-adventure/agent.ts +20 -20
  87. package/templates/memory-agent/agent.ts +1 -55
  88. package/templates/night-owl/agent.ts +4 -4
  89. package/templates/night-owl/client.tsx +6 -23
  90. package/templates/pizza-ordering/agent.ts +214 -0
  91. package/templates/pizza-ordering/client.tsx +264 -0
  92. package/templates/smart-research/agent.ts +10 -10
@@ -0,0 +1,214 @@
1
+ import { defineAgent, tool } from "@alexkroman1/aai";
2
+ import { z } from "zod";
3
+
4
+ interface Pizza {
5
+ id: number;
6
+ size: "small" | "medium" | "large";
7
+ crust: "thin" | "regular" | "thick" | "stuffed";
8
+ toppings: string[];
9
+ quantity: number;
10
+ }
11
+
12
+ interface OrderState {
13
+ pizzas: Pizza[];
14
+ nextId: number;
15
+ customerName: string;
16
+ orderPlaced: boolean;
17
+ }
18
+
19
+ const MENU = {
20
+ sizes: { small: 8.99, medium: 11.99, large: 14.99 },
21
+ crusts: { thin: 0, regular: 0, thick: 1.0, stuffed: 2.0 },
22
+ toppings: {
23
+ pepperoni: 1.5,
24
+ sausage: 1.5,
25
+ mushrooms: 1.0,
26
+ onions: 1.0,
27
+ green_peppers: 1.0,
28
+ black_olives: 1.0,
29
+ bacon: 2.0,
30
+ ham: 1.5,
31
+ pineapple: 1.0,
32
+ jalapenos: 1.0,
33
+ extra_cheese: 1.5,
34
+ spinach: 1.0,
35
+ tomatoes: 1.0,
36
+ anchovies: 1.5,
37
+ chicken: 2.0,
38
+ },
39
+ } as const;
40
+
41
+ function calculateTotal(pizzas: Pizza[]): number {
42
+ return pizzas.reduce((total, pizza) => {
43
+ const base = MENU.sizes[pizza.size];
44
+ const crust = MENU.crusts[pizza.crust];
45
+ const toppingsPrice = pizza.toppings.reduce(
46
+ (sum, t) =>
47
+ sum + (MENU.toppings[t as keyof typeof MENU.toppings] ?? 1.0),
48
+ 0,
49
+ );
50
+ return total + (base + crust + toppingsPrice) * pizza.quantity;
51
+ }, 0);
52
+ }
53
+
54
+ export default defineAgent({
55
+ name: "Pizza Palace",
56
+ greeting:
57
+ "Welcome to Pizza Palace. I can help you build your perfect pizza. What would you like to order?",
58
+ instructions: `You are a friendly pizza order-taker at Pizza Palace. Keep responses short and conversational, optimized for voice.
59
+
60
+ Your job is to help customers build their pizza order step by step. Guide them through size, crust, and toppings.
61
+
62
+ Menu info:
63
+ - Sizes: small ($8.99), medium ($11.99), large ($14.99)
64
+ - Crusts: thin (free), regular (free), thick (+$1), stuffed (+$2)
65
+ - Toppings: pepperoni ($1.50), sausage ($1.50), mushrooms ($1), onions ($1), green peppers ($1), black olives ($1), bacon ($2), ham ($1.50), pineapple ($1), jalapenos ($1), extra cheese ($1.50), spinach ($1), tomatoes ($1), anchovies ($1.50), chicken ($2)
66
+
67
+ Behavior:
68
+ - When a customer wants a pizza, collect size, crust, and toppings, then use add_pizza to add it.
69
+ - If they just say something like "pepperoni pizza", assume medium, regular crust, and confirm before adding.
70
+ - Always confirm what you added after using add_pizza.
71
+ - Use view_order when the customer asks to review their order.
72
+ - Use update_pizza if they want to change an existing pizza.
73
+ - Use remove_pizza if they want to remove one.
74
+ - When they say they are done ordering, use place_order.
75
+ - Suggest popular combos if they seem unsure. For example, "Our most popular is a large pepperoni with extra cheese."
76
+ - Always mention the running total after changes.
77
+ - Be warm but efficient. No long monologues.`,
78
+
79
+ state: (): OrderState => ({
80
+ pizzas: [],
81
+ nextId: 1,
82
+ customerName: "",
83
+ orderPlaced: false,
84
+ }),
85
+
86
+ tools: {
87
+ add_pizza: tool({
88
+ description:
89
+ "Add a pizza to the order. Use when the customer has decided on a pizza.",
90
+ parameters: z.object({
91
+ size: z.enum(["small", "medium", "large"]),
92
+ crust: z.enum(["thin", "regular", "thick", "stuffed"]),
93
+ toppings: z
94
+ .array(z.string())
95
+ .describe("List of topping names, e.g. ['pepperoni', 'mushrooms']"),
96
+ quantity: z.number().default(1),
97
+ }),
98
+ execute: (args, ctx) => {
99
+ const state = ctx.state as OrderState;
100
+ const pizza: Pizza = {
101
+ id: state.nextId++,
102
+ size: args.size,
103
+ crust: args.crust,
104
+ toppings: args.toppings,
105
+ quantity: args.quantity,
106
+ };
107
+ state.pizzas.push(pizza);
108
+ const total = calculateTotal(state.pizzas);
109
+ return {
110
+ added: pizza,
111
+ orderTotal: `$${total.toFixed(2)}`,
112
+ itemCount: state.pizzas.length,
113
+ };
114
+ },
115
+ }),
116
+
117
+ remove_pizza: tool({
118
+ description: "Remove a pizza from the order by its ID.",
119
+ parameters: z.object({
120
+ pizza_id: z.number().describe("The pizza ID to remove"),
121
+ }),
122
+ execute: (args, ctx) => {
123
+ const state = ctx.state as OrderState;
124
+ const idx = state.pizzas.findIndex((p) => p.id === args.pizza_id);
125
+ if (idx === -1) return { error: "Pizza not found in the order." };
126
+ const removed = state.pizzas.splice(idx, 1)[0];
127
+ const total = calculateTotal(state.pizzas);
128
+ return {
129
+ removed,
130
+ orderTotal: `$${total.toFixed(2)}`,
131
+ itemCount: state.pizzas.length,
132
+ };
133
+ },
134
+ }),
135
+
136
+ update_pizza: tool({
137
+ description:
138
+ "Update an existing pizza in the order. Only provided fields are changed.",
139
+ parameters: z.object({
140
+ pizza_id: z.number(),
141
+ size: z.enum(["small", "medium", "large"]).optional(),
142
+ crust: z.enum(["thin", "regular", "thick", "stuffed"]).optional(),
143
+ toppings: z.array(z.string()).optional(),
144
+ quantity: z.number().optional(),
145
+ }),
146
+ execute: (args, ctx) => {
147
+ const state = ctx.state as OrderState;
148
+ const pizza = state.pizzas.find((p) => p.id === args.pizza_id);
149
+ if (!pizza) return { error: "Pizza not found in the order." };
150
+ if (args.size) pizza.size = args.size;
151
+ if (args.crust) pizza.crust = args.crust;
152
+ if (args.toppings) pizza.toppings = args.toppings;
153
+ if (args.quantity) pizza.quantity = args.quantity;
154
+ const total = calculateTotal(state.pizzas);
155
+ return {
156
+ updated: pizza,
157
+ orderTotal: `$${total.toFixed(2)}`,
158
+ };
159
+ },
160
+ }),
161
+
162
+ view_order: {
163
+ description:
164
+ "View the current order summary with all pizzas and total price.",
165
+ execute: (_args, ctx) => {
166
+ const state = ctx.state as OrderState;
167
+ if (state.pizzas.length === 0)
168
+ return { message: "The order is empty." };
169
+ const total = calculateTotal(state.pizzas);
170
+ return {
171
+ pizzas: state.pizzas.map((p) => ({
172
+ id: p.id,
173
+ description: `${p.quantity}x ${p.size} ${p.crust} crust with ${p.toppings.length > 0 ? p.toppings.join(", ") : "cheese only"}`,
174
+ size: p.size,
175
+ crust: p.crust,
176
+ toppings: p.toppings,
177
+ quantity: p.quantity,
178
+ })),
179
+ orderTotal: `$${total.toFixed(2)}`,
180
+ };
181
+ },
182
+ },
183
+
184
+ set_customer_name: tool({
185
+ description: "Set the customer name for the order.",
186
+ parameters: z.object({ name: z.string() }),
187
+ execute: ({ name }, ctx) => {
188
+ const state = ctx.state as OrderState;
189
+ state.customerName = name;
190
+ return { name };
191
+ },
192
+ }),
193
+
194
+ place_order: {
195
+ description:
196
+ "Place the final order. Use when the customer confirms they are done and ready to order.",
197
+ execute: (_args, ctx) => {
198
+ const state = ctx.state as OrderState;
199
+ if (state.pizzas.length === 0)
200
+ return { error: "Cannot place an empty order." };
201
+ state.orderPlaced = true;
202
+ const total = calculateTotal(state.pizzas);
203
+ const orderNumber = Math.floor(1000 + Math.random() * 9000);
204
+ return {
205
+ orderNumber,
206
+ customerName: state.customerName || "Guest",
207
+ pizzas: state.pizzas.length,
208
+ total: `$${total.toFixed(2)}`,
209
+ estimatedMinutes: 15 + state.pizzas.length * 5,
210
+ };
211
+ },
212
+ },
213
+ },
214
+ });
@@ -0,0 +1,264 @@
1
+ import "@alexkroman1/aai/ui/styles.css";
2
+ import { useState } from "preact/hooks";
3
+ import { ChatView, SidebarLayout, StartScreen, mount, useSession, useToolResult } from "@alexkroman1/aai/ui";
4
+
5
+ interface Pizza {
6
+ id: number;
7
+ size: string;
8
+ crust: string;
9
+ toppings: string[];
10
+ quantity: number;
11
+ }
12
+
13
+ interface OrderInfo {
14
+ pizzas: Pizza[];
15
+ total: string;
16
+ orderPlaced: boolean;
17
+ orderNumber?: number;
18
+ estimatedMinutes?: number;
19
+ }
20
+
21
+ const SIZE_PRICES: Record<string, number> = {
22
+ small: 8.99,
23
+ medium: 11.99,
24
+ large: 14.99,
25
+ };
26
+ const CRUST_PRICES: Record<string, number> = {
27
+ thin: 0,
28
+ regular: 0,
29
+ thick: 1.0,
30
+ stuffed: 2.0,
31
+ };
32
+ const TOPPING_PRICES: Record<string, number> = {
33
+ pepperoni: 1.5,
34
+ sausage: 1.5,
35
+ mushrooms: 1.0,
36
+ onions: 1.0,
37
+ green_peppers: 1.0,
38
+ black_olives: 1.0,
39
+ bacon: 2.0,
40
+ ham: 1.5,
41
+ pineapple: 1.0,
42
+ jalapenos: 1.0,
43
+ extra_cheese: 1.5,
44
+ spinach: 1.0,
45
+ tomatoes: 1.0,
46
+ anchovies: 1.5,
47
+ chicken: 2.0,
48
+ };
49
+
50
+ function pizzaPrice(p: Pizza): number {
51
+ const base = SIZE_PRICES[p.size] ?? 11.99;
52
+ const crust = CRUST_PRICES[p.crust] ?? 0;
53
+ const toppings = p.toppings.reduce(
54
+ (s, t) => s + (TOPPING_PRICES[t] ?? 1.0),
55
+ 0,
56
+ );
57
+ return (base + crust + toppings) * p.quantity;
58
+ }
59
+
60
+ function PizzaIcon({ size }: { size: string }) {
61
+ const dim = size === "small" ? 36 : size === "large" ? 52 : 44;
62
+ return (
63
+ <svg
64
+ width={dim}
65
+ height={dim}
66
+ viewBox="0 0 100 100"
67
+ class="shrink-0"
68
+ >
69
+ <circle cx="50" cy="50" r="48" fill="#F4C542" stroke="#D4A017" stroke-width="3" />
70
+ <circle cx="50" cy="50" r="42" fill="#E8A025" />
71
+ <circle cx="35" cy="35" r="7" fill="#C0392B" opacity="0.9" />
72
+ <circle cx="60" cy="30" r="6" fill="#C0392B" opacity="0.9" />
73
+ <circle cx="55" cy="55" r="7" fill="#C0392B" opacity="0.9" />
74
+ <circle cx="30" cy="58" r="6" fill="#C0392B" opacity="0.9" />
75
+ <circle cx="65" cy="65" r="5" fill="#C0392B" opacity="0.9" />
76
+ <circle cx="45" cy="68" r="4" fill="#27AE60" opacity="0.7" />
77
+ <circle cx="70" cy="42" r="4" fill="#27AE60" opacity="0.7" />
78
+ </svg>
79
+ );
80
+ }
81
+
82
+ function OrderPanel({ order }: { order: OrderInfo }) {
83
+ if (order.orderPlaced) {
84
+ return (
85
+ <div class="flex flex-col items-center gap-4 p-6 text-center">
86
+ <div class="text-5xl">&#10003;</div>
87
+ <h2 class="text-lg font-bold text-aai-text">Order Placed</h2>
88
+ {order.orderNumber && (
89
+ <p class="text-aai-text opacity-70">
90
+ Order #{order.orderNumber}
91
+ </p>
92
+ )}
93
+ <p class="text-aai-primary font-bold text-xl">{order.total}</p>
94
+ {order.estimatedMinutes && (
95
+ <p class="text-aai-text opacity-60 text-sm">
96
+ Ready in ~{order.estimatedMinutes} minutes
97
+ </p>
98
+ )}
99
+ </div>
100
+ );
101
+ }
102
+
103
+ if (order.pizzas.length === 0) {
104
+ return (
105
+ <div class="flex flex-col items-center gap-3 p-6 text-center opacity-50">
106
+ <PizzaIcon size="large" />
107
+ <p class="text-aai-text text-sm">
108
+ Your order is empty. Tell me what you'd like.
109
+ </p>
110
+ </div>
111
+ );
112
+ }
113
+
114
+ return (
115
+ <div class="flex flex-col gap-3 p-4">
116
+ <h3 class="text-sm font-bold text-aai-text opacity-60 uppercase tracking-wide">
117
+ Your Order
118
+ </h3>
119
+ {order.pizzas.map((p) => (
120
+ <div
121
+ key={p.id}
122
+ class="flex items-center gap-3 p-3 rounded-lg bg-aai-surface"
123
+ >
124
+ <PizzaIcon size={p.size} />
125
+ <div class="flex-1 min-w-0">
126
+ <p class="text-aai-text text-sm font-medium">
127
+ {p.quantity > 1 ? `${p.quantity}x ` : ""}
128
+ {p.size.charAt(0).toUpperCase() + p.size.slice(1)}{" "}
129
+ {p.crust} crust
130
+ </p>
131
+ <p class="text-aai-text opacity-60 text-xs truncate">
132
+ {p.toppings.length > 0
133
+ ? p.toppings.map((t) => t.replace("_", " ")).join(", ")
134
+ : "cheese only"}
135
+ </p>
136
+ </div>
137
+ <p class="text-aai-primary text-sm font-bold whitespace-nowrap">
138
+ ${pizzaPrice(p).toFixed(2)}
139
+ </p>
140
+ </div>
141
+ ))}
142
+ <div class="flex justify-between items-center pt-3 mt-1 border-t border-aai-border">
143
+ <span class="text-aai-text font-bold">Total</span>
144
+ <span class="text-aai-primary font-bold text-lg">{order.total}</span>
145
+ </div>
146
+ </div>
147
+ );
148
+ }
149
+
150
+ function PizzaAgent() {
151
+ const { running, toggle, reset } = useSession();
152
+ const [order, setOrder] = useState<OrderInfo>({
153
+ pizzas: [],
154
+ total: "$0.00",
155
+ orderPlaced: false,
156
+ });
157
+
158
+ useToolResult((toolName, result: any) => {
159
+ switch (toolName) {
160
+ case "add_pizza":
161
+ if (result.added) {
162
+ setOrder((prev) => ({
163
+ ...prev,
164
+ pizzas: [...prev.pizzas, result.added],
165
+ total: result.orderTotal,
166
+ }));
167
+ }
168
+ break;
169
+ case "remove_pizza":
170
+ if (result.removed) {
171
+ setOrder((prev) => ({
172
+ ...prev,
173
+ pizzas: prev.pizzas.filter(
174
+ (p: Pizza) => p.id !== result.removed.id,
175
+ ),
176
+ total: result.orderTotal,
177
+ }));
178
+ }
179
+ break;
180
+ case "update_pizza":
181
+ if (result.updated) {
182
+ setOrder((prev) => ({
183
+ ...prev,
184
+ pizzas: prev.pizzas.map((p: Pizza) =>
185
+ p.id === result.updated.id ? result.updated : p,
186
+ ),
187
+ total: result.orderTotal,
188
+ }));
189
+ }
190
+ break;
191
+ case "view_order":
192
+ if (result.pizzas) {
193
+ const total =
194
+ result.orderTotal ||
195
+ `$${result.pizzas.reduce((s: number, p: Pizza) => s + pizzaPrice(p), 0).toFixed(2)}`;
196
+ setOrder((prev) => ({ ...prev, total }));
197
+ }
198
+ break;
199
+ case "place_order":
200
+ if (result.orderNumber) {
201
+ setOrder((prev) => ({
202
+ ...prev,
203
+ orderPlaced: true,
204
+ orderNumber: result.orderNumber,
205
+ total: result.total,
206
+ estimatedMinutes: result.estimatedMinutes,
207
+ }));
208
+ }
209
+ break;
210
+ }
211
+ });
212
+
213
+ const sidebar = (
214
+ <>
215
+ <div class="p-4 flex items-center gap-3 border-b border-aai-border">
216
+ <PizzaIcon size="small" />
217
+ <h2 class="text-base font-bold text-aai-text">Pizza Palace</h2>
218
+ </div>
219
+ <div class="flex-1">
220
+ <OrderPanel order={order} />
221
+ </div>
222
+ <div class="p-3 flex gap-2 border-t border-aai-border">
223
+ <button
224
+ class={`flex-1 py-2 rounded-lg text-sm border-none cursor-pointer text-white ${running.value ? "bg-aai-error" : "bg-aai-primary"}`}
225
+ onClick={toggle}
226
+ >
227
+ {running.value ? "Pause" : "Resume"}
228
+ </button>
229
+ <button
230
+ class="py-2 px-4 rounded-lg text-sm cursor-pointer text-aai-text bg-aai-surface border border-aai-border"
231
+ onClick={() => {
232
+ reset();
233
+ setOrder({
234
+ pizzas: [],
235
+ total: "$0.00",
236
+ orderPlaced: false,
237
+ });
238
+ }}
239
+ >
240
+ New Order
241
+ </button>
242
+ </div>
243
+ </>
244
+ );
245
+
246
+ return (
247
+ <StartScreen icon={<PizzaIcon size="large" />} title="Pizza Palace" subtitle="Voice-powered pizza ordering" buttonText="Start Ordering">
248
+ <SidebarLayout sidebar={sidebar}>
249
+ <ChatView />
250
+ </SidebarLayout>
251
+ </StartScreen>
252
+ );
253
+ }
254
+
255
+ mount(PizzaAgent, {
256
+ title: "Pizza Palace",
257
+ theme: {
258
+ bg: "#1a1008",
259
+ primary: "#E8A025",
260
+ text: "#f5f0e8",
261
+ surface: "#2a1f10",
262
+ border: "#3d2e18",
263
+ },
264
+ });
@@ -1,6 +1,6 @@
1
- import { defineAgent } from "@alexkroman1/aai";
1
+ import { defineAgent, tool } from "@alexkroman1/aai";
2
+ import type { HookContext, StepInfo } from "@alexkroman1/aai";
2
3
  import { z } from "zod";
3
- import type { HookContext, StepInfo } from "@alexkroman1/aai/types";
4
4
 
5
5
  /**
6
6
  * Smart Research Agent — demonstrates all 5 advanced features:
@@ -88,18 +88,18 @@ Always search first, then analyze, then answer. Be thorough but concise.`,
88
88
  },
89
89
 
90
90
  tools: {
91
- save_source: {
91
+ save_source: tool({
92
92
  description: "Save a source URL found during research for later analysis",
93
93
  parameters: z.object({
94
94
  url: z.string().describe("The source URL"),
95
95
  title: z.string().describe("Brief title or description"),
96
96
  }),
97
- execute: (args: { url: string; title: string }, ctx) => {
97
+ execute: ({ url, title }, ctx) => {
98
98
  const state = ctx.state;
99
- state.sources.push(`${args.title}: ${args.url}`);
99
+ state.sources.push(`${title}: ${url}`);
100
100
  return { saved: true, totalSources: state.sources.length };
101
101
  },
102
- },
102
+ }),
103
103
 
104
104
  mark_complex: {
105
105
  description:
@@ -126,25 +126,25 @@ Always search first, then analyze, then answer. Be thorough but concise.`,
126
126
  },
127
127
 
128
128
  // Feature 2: ctx.messages — access conversation history in tools
129
- analyze: {
129
+ analyze: tool({
130
130
  description:
131
131
  "Analyze all gathered sources and conversation context to form a conclusion",
132
132
  parameters: z.object({
133
133
  focus: z.string().describe("What aspect to focus the analysis on"),
134
134
  }),
135
- execute: (args: { focus: string }, ctx) => {
135
+ execute: ({ focus }, ctx) => {
136
136
  const state = ctx.state;
137
137
  // Use ctx.messages to see what's been discussed
138
138
  const userMessages = ctx.messages.filter((m) => m.role === "user");
139
139
  return {
140
- focus: args.focus,
140
+ focus,
141
141
  sources: state.sources,
142
142
  conversationTurns: userMessages.length,
143
143
  totalMessages: ctx.messages.length,
144
144
  phase: state.phase,
145
145
  };
146
146
  },
147
- },
147
+ }),
148
148
 
149
149
  conversation_summary: {
150
150
  description: "Get a summary of the conversation so far",