@alexkroman1/aai 0.8.2 → 0.8.4

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 (166) hide show
  1. package/dist/cli/tsconfig.tsbuildinfo +1 -0
  2. package/dist/cli.js +1368 -1904
  3. package/dist/sdk/_mock_ws.js +2 -2
  4. package/dist/sdk/_mock_ws.js.map +1 -1
  5. package/dist/sdk/_render_check.d.ts.map +1 -1
  6. package/dist/sdk/_render_check.js +30 -0
  7. package/dist/sdk/_render_check.js.map +1 -1
  8. package/dist/sdk/_utils.d.ts +4 -0
  9. package/dist/sdk/_utils.d.ts.map +1 -0
  10. package/dist/sdk/_utils.js +7 -0
  11. package/dist/sdk/_utils.js.map +1 -0
  12. package/dist/sdk/builtin_tools.d.ts +35 -11
  13. package/dist/sdk/builtin_tools.d.ts.map +1 -1
  14. package/dist/sdk/builtin_tools.js +118 -76
  15. package/dist/sdk/builtin_tools.js.map +1 -1
  16. package/dist/sdk/capnweb.d.ts +76 -47
  17. package/dist/sdk/capnweb.d.ts.map +1 -1
  18. package/dist/sdk/capnweb.js +99 -242
  19. package/dist/sdk/capnweb.js.map +1 -1
  20. package/dist/sdk/direct_executor.d.ts.map +1 -1
  21. package/dist/sdk/direct_executor.js +0 -2
  22. package/dist/sdk/direct_executor.js.map +1 -1
  23. package/dist/sdk/host.d.ts +59 -0
  24. package/dist/sdk/host.d.ts.map +1 -0
  25. package/dist/sdk/host.js +131 -0
  26. package/dist/sdk/host.js.map +1 -0
  27. package/dist/sdk/mod.d.ts +2 -4
  28. package/dist/sdk/mod.d.ts.map +1 -1
  29. package/dist/sdk/mod.js +2 -3
  30. package/dist/sdk/mod.js.map +1 -1
  31. package/dist/sdk/protocol.d.ts +33 -135
  32. package/dist/sdk/protocol.d.ts.map +1 -1
  33. package/dist/sdk/protocol.js +49 -51
  34. package/dist/sdk/protocol.js.map +1 -1
  35. package/dist/sdk/runtime.d.ts +0 -1
  36. package/dist/sdk/runtime.d.ts.map +1 -1
  37. package/dist/sdk/runtime.js +5 -24
  38. package/dist/sdk/runtime.js.map +1 -1
  39. package/dist/sdk/s2s.d.ts +14 -3
  40. package/dist/sdk/s2s.d.ts.map +1 -1
  41. package/dist/sdk/s2s.js +72 -113
  42. package/dist/sdk/s2s.js.map +1 -1
  43. package/dist/sdk/server.d.ts +1 -1
  44. package/dist/sdk/server.d.ts.map +1 -1
  45. package/dist/sdk/server.js +51 -92
  46. package/dist/sdk/server.js.map +1 -1
  47. package/dist/sdk/session.d.ts +5 -1
  48. package/dist/sdk/session.d.ts.map +1 -1
  49. package/dist/sdk/session.js +131 -137
  50. package/dist/sdk/session.js.map +1 -1
  51. package/dist/sdk/tsconfig.tsbuildinfo +1 -0
  52. package/dist/sdk/types.d.ts +30 -3
  53. package/dist/sdk/types.d.ts.map +1 -1
  54. package/dist/sdk/types.js +37 -0
  55. package/dist/sdk/types.js.map +1 -1
  56. package/dist/sdk/winterc_server.d.ts.map +1 -1
  57. package/dist/sdk/winterc_server.js +10 -15
  58. package/dist/sdk/winterc_server.js.map +1 -1
  59. package/dist/sdk/worker_entry.d.ts +3 -11
  60. package/dist/sdk/worker_entry.d.ts.map +1 -1
  61. package/dist/sdk/worker_entry.js +8 -18
  62. package/dist/sdk/worker_entry.js.map +1 -1
  63. package/dist/sdk/worker_shim.d.ts +5 -6
  64. package/dist/sdk/worker_shim.d.ts.map +1 -1
  65. package/dist/sdk/worker_shim.js +93 -136
  66. package/dist/sdk/worker_shim.js.map +1 -1
  67. package/dist/sdk/ws_handler.d.ts +1 -1
  68. package/dist/sdk/ws_handler.d.ts.map +1 -1
  69. package/dist/sdk/ws_handler.js +13 -22
  70. package/dist/sdk/ws_handler.js.map +1 -1
  71. package/dist/ui/_cn.d.ts +5 -0
  72. package/dist/ui/_cn.d.ts.map +1 -0
  73. package/dist/ui/_cn.js +22 -0
  74. package/dist/ui/_cn.js.map +1 -0
  75. package/dist/ui/_components/app.d.ts +3 -1
  76. package/dist/ui/_components/app.d.ts.map +1 -1
  77. package/dist/ui/_components/app.js +2 -2
  78. package/dist/ui/_components/app.js.map +1 -1
  79. package/dist/ui/_components/button.d.ts +11 -0
  80. package/dist/ui/_components/button.d.ts.map +1 -0
  81. package/dist/ui/_components/button.js +17 -0
  82. package/dist/ui/_components/button.js.map +1 -0
  83. package/dist/ui/_components/chat_view.d.ts +3 -1
  84. package/dist/ui/_components/chat_view.d.ts.map +1 -1
  85. package/dist/ui/_components/chat_view.js +4 -2
  86. package/dist/ui/_components/chat_view.js.map +1 -1
  87. package/dist/ui/_components/controls.d.ts +3 -1
  88. package/dist/ui/_components/controls.d.ts.map +1 -1
  89. package/dist/ui/_components/controls.js +4 -5
  90. package/dist/ui/_components/controls.js.map +1 -1
  91. package/dist/ui/_components/error_banner.d.ts +2 -1
  92. package/dist/ui/_components/error_banner.d.ts.map +1 -1
  93. package/dist/ui/_components/error_banner.js +3 -2
  94. package/dist/ui/_components/error_banner.js.map +1 -1
  95. package/dist/ui/_components/message_bubble.d.ts +2 -1
  96. package/dist/ui/_components/message_bubble.d.ts.map +1 -1
  97. package/dist/ui/_components/message_bubble.js +5 -3
  98. package/dist/ui/_components/message_bubble.js.map +1 -1
  99. package/dist/ui/_components/message_list.d.ts +3 -1
  100. package/dist/ui/_components/message_list.d.ts.map +1 -1
  101. package/dist/ui/_components/message_list.js +7 -15
  102. package/dist/ui/_components/message_list.js.map +1 -1
  103. package/dist/ui/_components/sidebar_layout.d.ts +2 -1
  104. package/dist/ui/_components/sidebar_layout.d.ts.map +1 -1
  105. package/dist/ui/_components/sidebar_layout.js +5 -7
  106. package/dist/ui/_components/sidebar_layout.js.map +1 -1
  107. package/dist/ui/_components/start_screen.d.ts +2 -1
  108. package/dist/ui/_components/start_screen.d.ts.map +1 -1
  109. package/dist/ui/_components/start_screen.js +5 -2
  110. package/dist/ui/_components/start_screen.js.map +1 -1
  111. package/dist/ui/_components/state_indicator.d.ts +2 -1
  112. package/dist/ui/_components/state_indicator.d.ts.map +1 -1
  113. package/dist/ui/_components/state_indicator.js +3 -2
  114. package/dist/ui/_components/state_indicator.js.map +1 -1
  115. package/dist/ui/_components/thinking_indicator.d.ts +3 -1
  116. package/dist/ui/_components/thinking_indicator.d.ts.map +1 -1
  117. package/dist/ui/_components/thinking_indicator.js +4 -2
  118. package/dist/ui/_components/thinking_indicator.js.map +1 -1
  119. package/dist/ui/_components/tool_call_block.d.ts +2 -1
  120. package/dist/ui/_components/tool_call_block.d.ts.map +1 -1
  121. package/dist/ui/_components/tool_call_block.js +13 -25
  122. package/dist/ui/_components/tool_call_block.js.map +1 -1
  123. package/dist/ui/_components/transcript.d.ts +2 -1
  124. package/dist/ui/_components/transcript.d.ts.map +1 -1
  125. package/dist/ui/_components/transcript.js +3 -2
  126. package/dist/ui/_components/transcript.js.map +1 -1
  127. package/dist/ui/_jsdom_setup.d.ts +1 -0
  128. package/dist/ui/_jsdom_setup.d.ts.map +1 -0
  129. package/dist/ui/_jsdom_setup.js +6 -0
  130. package/dist/ui/_jsdom_setup.js.map +1 -0
  131. package/dist/ui/audio.d.ts.map +1 -1
  132. package/dist/ui/audio.js +4 -4
  133. package/dist/ui/audio.js.map +1 -1
  134. package/dist/ui/components.d.ts +13 -55
  135. package/dist/ui/components.d.ts.map +1 -1
  136. package/dist/ui/components.js +13 -42
  137. package/dist/ui/components.js.map +1 -1
  138. package/dist/ui/components_mod.d.ts +14 -3
  139. package/dist/ui/components_mod.d.ts.map +1 -1
  140. package/dist/ui/components_mod.js +14 -3
  141. package/dist/ui/components_mod.js.map +1 -1
  142. package/dist/ui/mod.d.ts +1 -8
  143. package/dist/ui/mod.d.ts.map +1 -1
  144. package/dist/ui/mod.js +1 -5
  145. package/dist/ui/mod.js.map +1 -1
  146. package/dist/ui/mount.d.ts +1 -1
  147. package/dist/ui/mount.d.ts.map +1 -1
  148. package/dist/ui/mount.js +1 -0
  149. package/dist/ui/mount.js.map +1 -1
  150. package/dist/ui/session.d.ts +0 -2
  151. package/dist/ui/session.d.ts.map +1 -1
  152. package/dist/ui/session.js +9 -6
  153. package/dist/ui/session.js.map +1 -1
  154. package/dist/ui/signals.d.ts +8 -3
  155. package/dist/ui/signals.d.ts.map +1 -1
  156. package/dist/ui/signals.js +22 -11
  157. package/dist/ui/signals.js.map +1 -1
  158. package/dist/ui/tsconfig.tsbuildinfo +1 -0
  159. package/dist/ui/worklets/playback-processor.js +3 -3
  160. package/package.json +39 -16
  161. package/templates/_shared/CLAUDE.md +50 -30
  162. package/templates/_shared/global.d.ts +1 -0
  163. package/templates/_shared/package.json +2 -1
  164. package/templates/dispatch-center/agent.ts +85 -397
  165. package/templates/solo-rpg/agent.ts +1240 -0
  166. package/templates/solo-rpg/client.tsx +698 -0
package/dist/cli.js CHANGED
@@ -9,17 +9,143 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // cli/_colors.ts
12
+ // sdk/_utils.ts
13
+ function errorMessage(err) {
14
+ return err instanceof Error ? err.message : String(err);
15
+ }
16
+ var init_utils = __esm({
17
+ "sdk/_utils.ts"() {
18
+ "use strict";
19
+ }
20
+ });
21
+
22
+ // cli/_ink.tsx
23
+ import { Spinner, StatusMessage } from "@inkjs/ui";
13
24
  import chalk from "chalk";
25
+ import { Box, render, Static, Text, useApp } from "ink";
26
+ import React, { useRef, useState } from "react";
27
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
28
  function primary(s) {
15
29
  return chalk.hex(COLORS.primary)(s);
16
30
  }
17
31
  function interactive(s) {
18
32
  return chalk.hex(COLORS.interactive)(s);
19
33
  }
34
+ function StepBase({ action, msg, color }) {
35
+ return /* @__PURE__ */ jsxs(Text, { children: [
36
+ /* @__PURE__ */ jsx(Text, { bold: true, color, children: action }),
37
+ /* @__PURE__ */ jsxs(Text, { children: [
38
+ " ",
39
+ msg
40
+ ] })
41
+ ] });
42
+ }
43
+ function Step({ action, msg }) {
44
+ return /* @__PURE__ */ jsx(StepBase, { action, msg, color: COLORS.primary });
45
+ }
46
+ function StepInfo({ action, msg }) {
47
+ return /* @__PURE__ */ jsx(StepBase, { action, msg, color: COLORS.interactive });
48
+ }
49
+ function Info({ msg }) {
50
+ return /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
51
+ " ",
52
+ msg
53
+ ] });
54
+ }
55
+ function Detail({ msg }) {
56
+ return /* @__PURE__ */ jsxs(Text, { children: [
57
+ " ",
58
+ msg
59
+ ] });
60
+ }
61
+ function Warn({ msg }) {
62
+ return /* @__PURE__ */ jsx(StatusMessage, { variant: "warning", children: msg });
63
+ }
64
+ function ErrorLine({ msg }) {
65
+ return /* @__PURE__ */ jsx(StatusMessage, { variant: "error", children: msg });
66
+ }
67
+ function StepLog({ items }) {
68
+ return /* @__PURE__ */ jsx(Static, { items, children: (item) => /* @__PURE__ */ jsx(Box, { children: item.node }, item.id) });
69
+ }
70
+ function useStepLog() {
71
+ const [items, setItems] = useState([]);
72
+ const nextId = useRef(0);
73
+ const log = (node) => {
74
+ const id = nextId.current++;
75
+ setItems((prev) => [...prev, { id, node }]);
76
+ };
77
+ return { items, log };
78
+ }
79
+ function CommandRunner({
80
+ run,
81
+ onError
82
+ }) {
83
+ const { exit } = useApp();
84
+ const { items, log } = useStepLog();
85
+ const [spinning, setSpinning] = useState(true);
86
+ const [currentStep, setCurrentStep] = useState(null);
87
+ const [statusLine, setStatusLine] = useState(null);
88
+ const [err, setErr] = useState(null);
89
+ const currentStepRef = useRef(null);
90
+ const wrappedLog = (node) => {
91
+ if (currentStepRef.current) {
92
+ log(currentStepRef.current);
93
+ }
94
+ currentStepRef.current = node;
95
+ setCurrentStep(node);
96
+ };
97
+ const started = useRef(false);
98
+ React.useEffect(() => {
99
+ if (started.current) return;
100
+ started.current = true;
101
+ (async () => {
102
+ try {
103
+ await run({ log: wrappedLog, setStatus: setStatusLine });
104
+ } catch (e) {
105
+ const error = e instanceof Error ? e : new Error(String(e));
106
+ setErr(error.message);
107
+ onError?.(error);
108
+ }
109
+ if (currentStepRef.current) {
110
+ log(currentStepRef.current);
111
+ currentStepRef.current = null;
112
+ }
113
+ setCurrentStep(null);
114
+ setStatusLine(null);
115
+ setSpinning(false);
116
+ setTimeout(() => exit(), 0);
117
+ })();
118
+ });
119
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
120
+ /* @__PURE__ */ jsx(StepLog, { items }),
121
+ err && /* @__PURE__ */ jsx(ErrorLine, { msg: err }),
122
+ spinning && currentStep && /* @__PURE__ */ jsxs(Box, { children: [
123
+ /* @__PURE__ */ jsx(Spinner, {}),
124
+ /* @__PURE__ */ jsx(Text, { children: " " }),
125
+ currentStep
126
+ ] }),
127
+ spinning && statusLine && /* @__PURE__ */ jsx(Box, { children: statusLine })
128
+ ] });
129
+ }
130
+ async function runWithInk(fn) {
131
+ let thrownError;
132
+ const app = render(
133
+ /* @__PURE__ */ jsx(
134
+ CommandRunner,
135
+ {
136
+ onError: (e) => {
137
+ thrownError = e;
138
+ },
139
+ run: fn
140
+ }
141
+ )
142
+ );
143
+ await app.waitUntilExit();
144
+ if (thrownError) throw thrownError;
145
+ }
20
146
  var COLORS;
21
- var init_colors = __esm({
22
- "cli/_colors.ts"() {
147
+ var init_ink = __esm({
148
+ "cli/_ink.tsx"() {
23
149
  "use strict";
24
150
  if (chalk.level === 0 && !process.env.NO_COLOR) {
25
151
  const ct = process.env.COLORTERM;
@@ -43,200 +169,61 @@ var init_colors = __esm({
43
169
  }
44
170
  });
45
171
 
46
- // cli/_help.ts
47
- import chalk2 from "chalk";
48
- function rootHelp(version) {
49
- const lines = [];
50
- lines.push("");
51
- lines.push(
52
- ` ${primary(chalk2.bold(" \u2584\u2580\u2588 \u2584\u2580\u2588 \u2588"))} ${chalk2.dim("Voice agent development kit")}`
53
- );
54
- lines.push(` ${primary(chalk2.bold(" \u2588\u2580\u2588 \u2588\u2580\u2588 \u2588"))} ${primary(`v${version}`)}`);
55
- lines.push("");
56
- lines.push(
57
- ` ${chalk2.bold(interactive("Usage"))} ${primary("aai")} ${chalk2.dim("<command> [options]")}`
58
- );
59
- lines.push("");
60
- lines.push(` ${chalk2.bold(interactive("Commands"))}`);
61
- lines.push("");
62
- const cmds = [
63
- ["init", "[dir]", "Scaffold a new agent project"],
64
- ["dev", "", "Start a local development server"],
65
- ["build", "", "Bundle and validate (no server or deploy)"],
66
- ["deploy", "", "Bundle and deploy to production"],
67
- ["start", "", "Start production server from build"],
68
- ["secret", "<cmd>", "Manage secrets"],
69
- ["rag", "<url>", "Ingest a site into the vector store"]
70
- ];
71
- for (const [name, args, desc] of cmds) {
72
- const nameStr = interactive(name.padEnd(8));
73
- const argsStr = args ? primary(args.padEnd(6)) : " ";
74
- lines.push(` ${nameStr} ${argsStr} ${chalk2.dim(desc)}`);
75
- }
76
- lines.push("");
77
- lines.push(` ${chalk2.bold(interactive("Options"))}`);
78
- lines.push("");
79
- lines.push(
80
- ` ${interactive("-h")}${chalk2.dim(",")} ${interactive("--help")} ${chalk2.dim(
81
- "Show this help"
82
- )}`
83
- );
84
- lines.push(
85
- ` ${interactive("-V")}${chalk2.dim(",")} ${interactive("--version")} ${chalk2.dim(
86
- "Show the version number"
87
- )}`
88
- );
89
- lines.push("");
90
- lines.push(` ${chalk2.bold(interactive("Getting started"))}`);
91
- lines.push("");
92
- lines.push(` ${chalk2.dim("$")} ${primary("aai init")} ${interactive("my-agent")}`);
93
- lines.push(` ${chalk2.dim("$")} ${primary("cd")} ${interactive("my-agent")}`);
94
- lines.push(` ${chalk2.dim("$")} ${primary("aai dev")}`);
95
- lines.push("");
96
- return lines.join("\n");
97
- }
98
- function subcommandHelp(cmd, version) {
99
- const lines = [];
100
- lines.push("");
101
- lines.push(
102
- ` ${primary(chalk2.bold("aai"))} ${interactive(chalk2.bold(cmd.name))}${version ? chalk2.dim(` v${version}`) : ""}`
103
- );
104
- lines.push(` ${chalk2.dim(cmd.description)}`);
105
- lines.push("");
106
- if (cmd.args && cmd.args.length > 0) {
107
- lines.push(` ${chalk2.bold(interactive("Arguments"))}`);
108
- lines.push("");
109
- for (const arg of cmd.args) {
110
- const label = arg.optional ? primary(`[${arg.name}]`) : primary(`<${arg.name}>`);
111
- lines.push(` ${label}`);
112
- }
113
- lines.push("");
114
- }
115
- const visibleOptions = (cmd.options ?? []).filter((o) => !o.hidden);
116
- if (visibleOptions.length > 0) {
117
- lines.push(` ${chalk2.bold(interactive("Options"))}`);
118
- lines.push("");
119
- for (const opt of visibleOptions) {
120
- lines.push(` ${interactive(opt.flags)}`);
121
- lines.push(` ${chalk2.dim(opt.description)}`);
122
- }
123
- lines.push(` ${interactive("-h")}${chalk2.dim(",")} ${interactive("--help")}`);
124
- lines.push(` ${chalk2.dim("Show this help")}`);
125
- lines.push("");
126
- }
127
- return lines.join("\n");
128
- }
129
- var init_help = __esm({
130
- "cli/_help.ts"() {
131
- "use strict";
132
- init_colors();
133
- }
134
- });
135
-
136
172
  // cli/_prompts.tsx
137
173
  import { ConfirmInput, PasswordInput, Select, TextInput } from "@inkjs/ui";
138
- import { Box, render, Text } from "ink";
139
- import { jsx, jsxs } from "react/jsx-runtime";
140
- async function askPassword(message) {
141
- return new Promise((resolve) => {
142
- const app = render(
143
- /* @__PURE__ */ jsxs(Box, { children: [
144
- /* @__PURE__ */ jsxs(Text, { children: [
145
- message,
146
- ": "
147
- ] }),
148
- /* @__PURE__ */ jsx(
149
- PasswordInput,
150
- {
151
- onSubmit: (value) => {
152
- resolve(value);
153
- app.unmount();
154
- }
155
- }
156
- )
157
- ] })
158
- );
159
- });
160
- }
161
- async function askText(message, defaultValue) {
174
+ import { Box as Box2, render as render2, Text as Text2 } from "ink";
175
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
176
+ function inkPrompt(ui) {
162
177
  return new Promise((resolve) => {
163
- const app = render(
164
- /* @__PURE__ */ jsxs(Box, { children: [
165
- /* @__PURE__ */ jsxs(Text, { color: COLORS.interactive, children: [
166
- message,
167
- " \u203A "
168
- ] }),
169
- /* @__PURE__ */ jsx(
170
- TextInput,
171
- {
172
- placeholder: defaultValue,
173
- onSubmit: (value) => {
174
- resolve(value || defaultValue);
175
- app.unmount();
176
- app.clear();
177
- }
178
- }
179
- )
180
- ] })
178
+ const app = render2(
179
+ ui((value) => {
180
+ resolve(value);
181
+ app.unmount();
182
+ })
181
183
  );
182
184
  });
183
185
  }
184
- async function askEnter(message) {
185
- return new Promise((resolve) => {
186
- const app = render(
187
- /* @__PURE__ */ jsxs(Box, { children: [
188
- /* @__PURE__ */ jsx(Text, { color: COLORS.interactive, children: message }),
189
- /* @__PURE__ */ jsx(
190
- TextInput,
191
- {
192
- placeholder: "",
193
- onSubmit: () => {
194
- resolve();
195
- app.unmount();
196
- }
197
- }
198
- )
199
- ] })
200
- );
201
- });
186
+ function askPassword(message) {
187
+ return inkPrompt((done) => /* @__PURE__ */ jsxs2(Box2, { children: [
188
+ /* @__PURE__ */ jsxs2(Text2, { children: [
189
+ message,
190
+ ": "
191
+ ] }),
192
+ /* @__PURE__ */ jsx2(PasswordInput, { onSubmit: done })
193
+ ] }));
194
+ }
195
+ function askText(message, defaultValue) {
196
+ return inkPrompt((done) => /* @__PURE__ */ jsxs2(Box2, { children: [
197
+ /* @__PURE__ */ jsxs2(Text2, { color: COLORS.interactive, children: [
198
+ message,
199
+ " \u203A "
200
+ ] }),
201
+ /* @__PURE__ */ jsx2(TextInput, { placeholder: defaultValue, onSubmit: (value) => done(value || defaultValue) })
202
+ ] }));
203
+ }
204
+ function askEnter(message) {
205
+ return inkPrompt((done) => /* @__PURE__ */ jsxs2(Box2, { children: [
206
+ /* @__PURE__ */ jsx2(Text2, { color: COLORS.interactive, children: message }),
207
+ /* @__PURE__ */ jsx2(TextInput, { placeholder: "", onSubmit: () => done(void 0) })
208
+ ] }));
202
209
  }
203
210
  var init_prompts = __esm({
204
211
  "cli/_prompts.tsx"() {
205
212
  "use strict";
206
- init_colors();
213
+ init_ink();
207
214
  }
208
215
  });
209
216
 
210
217
  // cli/_discover.ts
211
- var discover_exports = {};
212
- __export(discover_exports, {
213
- DEFAULT_SERVER: () => DEFAULT_SERVER,
214
- fileExists: () => fileExists,
215
- generateSlug: () => generateSlug,
216
- getApiKey: () => getApiKey,
217
- isDevMode: () => isDevMode,
218
- loadAgent: () => loadAgent,
219
- readProjectConfig: () => readProjectConfig,
220
- writeProjectConfig: () => writeProjectConfig
221
- });
222
- import { accessSync } from "node:fs";
223
218
  import fs from "node:fs/promises";
224
219
  import path from "node:path";
225
220
  import { humanId } from "human-id";
226
- function isDevMode() {
227
- const script = process.argv[1] ?? "";
228
- if (script.endsWith(".ts") || script.endsWith(".tsx")) return true;
229
- if (script.includes("/dist/") && !script.includes("node_modules")) {
230
- const dir = path.dirname(path.dirname(script));
231
- try {
232
- accessSync(path.join(dir, "sdk"));
233
- accessSync(path.join(dir, "cli"));
234
- return true;
235
- } catch {
236
- return false;
237
- }
238
- }
239
- return false;
221
+ function resolveCwd() {
222
+ return process.env.INIT_CWD || process.cwd();
223
+ }
224
+ function isDevMode(script) {
225
+ script ??= process.argv[1] ?? "";
226
+ return script.endsWith(".ts") || script.endsWith(".tsx");
240
227
  }
241
228
  function generateSlug() {
242
229
  return humanId({ separator: "-", capitalize: false });
@@ -284,6 +271,18 @@ async function writeProjectConfig(agentDir, data) {
284
271
  await fs.writeFile(path.join(aaiDir, "project.json"), `${JSON.stringify(data, null, 2)}
285
272
  `);
286
273
  }
274
+ async function getServerInfo(cwd, explicitServer, explicitApiKey) {
275
+ const config = await readProjectConfig(cwd);
276
+ if (!config) {
277
+ throw new Error("No .aai/project.json found \u2014 deploy first with `aai deploy`");
278
+ }
279
+ const apiKey = explicitApiKey ?? await getApiKey();
280
+ const serverUrl = resolveServerUrl(explicitServer, config.serverUrl);
281
+ return { serverUrl, slug: config.slug, apiKey };
282
+ }
283
+ function resolveServerUrl(explicit, configUrl) {
284
+ return explicit || configUrl || (isDevMode() ? "http://localhost:3100" : DEFAULT_SERVER);
285
+ }
287
286
  async function fileExists(p) {
288
287
  try {
289
288
  await fs.access(p);
@@ -316,86 +315,173 @@ var init_discover = __esm({
316
315
  }
317
316
  });
318
317
 
319
- // cli/_bundler.ts
318
+ // cli/_init.ts
319
+ var init_exports = {};
320
+ __export(init_exports, {
321
+ listTemplates: () => listTemplates,
322
+ runInit: () => runInit
323
+ });
320
324
  import fs2 from "node:fs/promises";
321
325
  import path2 from "node:path";
326
+ async function listTemplates(dir) {
327
+ const templates = [];
328
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
329
+ for (const entry of entries) {
330
+ if (entry.isDirectory() && !entry.name.startsWith("_")) {
331
+ templates.push(entry.name);
332
+ }
333
+ }
334
+ return templates.sort();
335
+ }
336
+ async function copyDirNoOverwrite(src, dest) {
337
+ const entries = await fs2.readdir(src, { recursive: true, withFileTypes: true });
338
+ for (const entry of entries) {
339
+ if (!entry.isFile()) continue;
340
+ const rel = path2.relative(src, path2.join(entry.parentPath, entry.name));
341
+ const destPath = path2.join(dest, rel);
342
+ await fs2.mkdir(path2.dirname(destPath), { recursive: true });
343
+ try {
344
+ await fs2.copyFile(path2.join(src, rel), destPath, fs2.constants.COPYFILE_EXCL);
345
+ } catch (err) {
346
+ if (err.code !== "EEXIST") throw err;
347
+ }
348
+ }
349
+ }
350
+ async function runInit(opts) {
351
+ const { targetDir, template, templatesDir } = opts;
352
+ const available = await listTemplates(templatesDir);
353
+ if (!available.includes(template)) {
354
+ throw new Error(`unknown template '${template}' -- available: ${available.join(", ")}`);
355
+ }
356
+ await fs2.cp(path2.join(templatesDir, template), targetDir, { recursive: true, force: true });
357
+ await copyDirNoOverwrite(path2.join(templatesDir, "_shared"), targetDir);
358
+ try {
359
+ await fs2.copyFile(path2.join(targetDir, ".env.example"), path2.join(targetDir, ".env"));
360
+ } catch {
361
+ }
362
+ const readmePath = path2.join(targetDir, "README.md");
363
+ const slug = path2.basename(path2.resolve(targetDir));
364
+ const readme = `# ${slug}
365
+
366
+ A voice agent built with [aai](https://github.com/anthropics/aai).
367
+
368
+ ## Getting started
369
+
370
+ \`\`\`sh
371
+ npm install # Install dependencies
372
+ npm run dev # Run locally (opens browser)
373
+ npm run deploy # Deploy to production
374
+ \`\`\`
375
+
376
+ ## Environment variables
377
+
378
+ Secrets are managed on the server, not in local files:
379
+
380
+ \`\`\`sh
381
+ aai env add MY_KEY # Set a secret (prompts for value)
382
+ aai env ls # List secret names
383
+ aai env pull # Pull names into .env for reference
384
+ aai env rm MY_KEY # Remove a secret
385
+ \`\`\`
386
+
387
+ Access secrets in your agent via \`ctx.env.MY_KEY\`.
388
+
389
+ ## Learn more
390
+
391
+ See \`CLAUDE.md\` for the full agent API reference.
392
+ `;
393
+ try {
394
+ await fs2.writeFile(readmePath, readme, { flag: "wx" });
395
+ } catch (err) {
396
+ if (err.code !== "EEXIST") throw err;
397
+ }
398
+ return targetDir;
399
+ }
400
+ var init_init = __esm({
401
+ "cli/_init.ts"() {
402
+ "use strict";
403
+ }
404
+ });
405
+
406
+ // cli/_bundler.ts
407
+ import fs3 from "node:fs/promises";
408
+ import path3 from "node:path";
322
409
  import preact from "@preact/preset-vite";
323
410
  import tailwindcss from "@tailwindcss/vite";
324
411
  import { build } from "vite";
325
- async function readDirRecursive(dir, base = dir) {
326
- const files = {};
327
- let names;
412
+ function workerEntryPlugin() {
413
+ const virtualId = "virtual:worker-entry";
414
+ const resolvedId = `\0${virtualId}`;
415
+ return {
416
+ name: "aai-worker-entry",
417
+ resolveId(source) {
418
+ return source === virtualId ? resolvedId : null;
419
+ },
420
+ load(id) {
421
+ if (id !== resolvedId) return null;
422
+ return [
423
+ `import agent from "./agent.ts";`,
424
+ `import { initWorker } from "@alexkroman1/aai/worker-shim";`,
425
+ `initWorker(agent);`
426
+ ].join("\n");
427
+ }
428
+ };
429
+ }
430
+ async function readDirFiles(dir) {
431
+ let entries;
328
432
  try {
329
- names = await fs2.readdir(dir);
433
+ entries = await fs3.readdir(dir, { recursive: true, withFileTypes: true });
330
434
  } catch {
331
- return files;
332
- }
333
- for (const name of names) {
334
- const full = path2.join(dir, name);
335
- const stat = await fs2.stat(full);
336
- const entry = { name, isDirectory: () => stat.isDirectory() };
337
- if (entry.isDirectory()) {
338
- Object.assign(files, await readDirRecursive(full, base));
339
- } else {
340
- const rel = path2.relative(base, full);
341
- files[rel] = await fs2.readFile(full, "utf-8");
342
- }
435
+ return {};
343
436
  }
437
+ const files = {};
438
+ await Promise.all(
439
+ entries.filter((e) => e.isFile()).map(async (e) => {
440
+ const full = path3.join(e.parentPath, e.name);
441
+ files[path3.relative(dir, full)] = await fs3.readFile(full, "utf-8");
442
+ })
443
+ );
344
444
  return files;
345
445
  }
346
446
  async function bundleAgent(agent, opts) {
347
- const aaiDir = path2.join(agent.dir, ".aai");
348
- const buildDir = path2.join(aaiDir, "build");
349
- const clientDir = path2.join(aaiDir, "client");
350
- await fs2.mkdir(aaiDir, { recursive: true });
351
- const workerEntry = path2.join(aaiDir, "_worker_entry.ts");
352
- await fs2.writeFile(
353
- workerEntry,
354
- [
355
- `import agent from "../agent.ts";`,
356
- `import { initWorker } from "@alexkroman1/aai/worker-shim";`,
357
- `initWorker(agent);`
358
- ].join("\n")
359
- );
447
+ const aaiDir = path3.join(agent.dir, ".aai");
448
+ const buildDir = path3.join(aaiDir, "build");
449
+ const clientDir = path3.join(aaiDir, "client");
450
+ const devMode = isDevMode();
451
+ const devResolve = devMode ? { conditions: ["source"] } : {};
360
452
  try {
361
453
  await build({
362
454
  configFile: false,
363
455
  root: agent.dir,
364
456
  logLevel: "warn",
457
+ plugins: [workerEntryPlugin()],
458
+ resolve: devResolve,
365
459
  build: {
460
+ rollupOptions: {
461
+ input: "virtual:worker-entry",
462
+ output: { format: "es", entryFileNames: "worker.js" }
463
+ },
366
464
  outDir: buildDir,
367
465
  emptyOutDir: true,
368
466
  minify: true,
369
- target: "es2022",
370
- rollupOptions: {
371
- input: workerEntry,
372
- output: {
373
- format: "es",
374
- entryFileNames: "worker.js",
375
- inlineDynamicImports: true
376
- }
377
- }
467
+ target: "es2022"
378
468
  }
379
469
  });
380
470
  } catch (err) {
381
- throw new BundleError(err instanceof Error ? err.message : String(err));
471
+ throw new BundleError(errorMessage(err));
382
472
  }
383
473
  const skipClient = opts?.skipClient || !agent.clientEntry;
384
474
  if (!skipClient) {
385
- const devAlias = {};
386
- if (isDevMode()) {
387
- const monorepoRoot = path2.resolve(import.meta.dirname ?? __dirname, "..");
388
- devAlias["@alexkroman1/aai/ui/styles.css"] = path2.join(monorepoRoot, "ui/styles.css");
389
- devAlias["@alexkroman1/aai/ui"] = path2.join(monorepoRoot, "ui/mod.ts");
390
- devAlias["@alexkroman1/aai"] = path2.join(monorepoRoot, "sdk/mod.ts");
391
- }
392
475
  try {
393
476
  await build({
394
477
  root: agent.dir,
395
478
  base: "./",
396
479
  logLevel: "warn",
397
480
  plugins: [preact(), tailwindcss()],
398
- ...Object.keys(devAlias).length > 0 && { resolve: { alias: devAlias } },
481
+ resolve: {
482
+ ...devResolve,
483
+ ...devMode && { dedupe: ["preact", "@preact/signals"] }
484
+ },
399
485
  build: {
400
486
  outDir: clientDir,
401
487
  emptyOutDir: true,
@@ -404,11 +490,11 @@ async function bundleAgent(agent, opts) {
404
490
  }
405
491
  });
406
492
  } catch (err) {
407
- throw new BundleError(err instanceof Error ? err.message : String(err));
493
+ throw new BundleError(errorMessage(err));
408
494
  }
409
495
  }
410
- const worker = await fs2.readFile(path2.join(buildDir, "worker.js"), "utf-8");
411
- const clientFiles = await readDirRecursive(clientDir);
496
+ const worker = await fs3.readFile(path3.join(buildDir, "worker.js"), "utf-8");
497
+ const clientFiles = await readDirFiles(clientDir);
412
498
  return {
413
499
  worker,
414
500
  clientFiles,
@@ -420,6 +506,7 @@ var BundleError;
420
506
  var init_bundler = __esm({
421
507
  "cli/_bundler.ts"() {
422
508
  "use strict";
509
+ init_utils();
423
510
  init_discover();
424
511
  BundleError = class extends Error {
425
512
  constructor(message) {
@@ -430,150 +517,19 @@ var init_bundler = __esm({
430
517
  }
431
518
  });
432
519
 
433
- // cli/_ink.tsx
434
- import { Spinner } from "@inkjs/ui";
435
- import { Box as Box2, render as render2, Static, Text as Text2, useApp } from "ink";
436
- import React, { useRef, useState } from "react";
437
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
438
- function Step({ action, msg }) {
439
- return /* @__PURE__ */ jsxs2(Text2, { children: [
440
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: COLORS.primary, children: action }),
441
- /* @__PURE__ */ jsxs2(Text2, { children: [
442
- " ",
443
- msg
444
- ] })
445
- ] });
446
- }
447
- function StepInfo({ action, msg }) {
448
- return /* @__PURE__ */ jsxs2(Text2, { children: [
449
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: COLORS.interactive, children: action }),
450
- /* @__PURE__ */ jsxs2(Text2, { children: [
451
- " ",
452
- msg
453
- ] })
454
- ] });
455
- }
456
- function Info({ msg }) {
457
- return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
458
- " ",
459
- msg
460
- ] });
461
- }
462
- function Detail({ msg }) {
463
- return /* @__PURE__ */ jsxs2(Text2, { children: [
464
- " ",
465
- msg
466
- ] });
467
- }
468
- function Warn({ msg }) {
469
- return /* @__PURE__ */ jsxs2(Text2, { children: [
470
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: COLORS.warning, children: "\u25B2" }),
471
- /* @__PURE__ */ jsxs2(Text2, { children: [
472
- " ",
473
- msg
474
- ] })
475
- ] });
476
- }
477
- function ErrorLine({ msg }) {
478
- return /* @__PURE__ */ jsxs2(Text2, { children: [
479
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: COLORS.error, children: "\u2717" }),
480
- /* @__PURE__ */ jsxs2(Text2, { children: [
481
- " ",
482
- msg
483
- ] })
484
- ] });
485
- }
486
- function StepLog({ items }) {
487
- return /* @__PURE__ */ jsx2(Static, { items, children: (item) => /* @__PURE__ */ jsx2(Box2, { children: item.node }, item.id) });
488
- }
489
- function useStepLog() {
490
- const [items, setItems] = useState([]);
491
- const nextId = useRef(0);
492
- const log = (node) => {
493
- const id = nextId.current++;
494
- setItems((prev) => [...prev, { id, node }]);
495
- };
496
- return { items, log };
497
- }
498
- function CommandRunner({
499
- run,
500
- onError
501
- }) {
502
- const { exit } = useApp();
503
- const { items, log } = useStepLog();
504
- const [spinning, setSpinning] = useState(true);
505
- const [currentStep, setCurrentStep] = useState(null);
506
- const [err, setErr] = useState(null);
507
- const currentStepRef = useRef(null);
508
- const wrappedLog = (node) => {
509
- if (currentStepRef.current) {
510
- log(currentStepRef.current);
511
- }
512
- currentStepRef.current = node;
513
- setCurrentStep(node);
514
- };
515
- const started = useRef(false);
516
- React.useEffect(() => {
517
- if (started.current) return;
518
- started.current = true;
519
- (async () => {
520
- try {
521
- await run(wrappedLog);
522
- } catch (e) {
523
- const error = e instanceof Error ? e : new Error(String(e));
524
- setErr(error.message);
525
- onError?.(error);
526
- }
527
- if (currentStepRef.current) {
528
- log(currentStepRef.current);
529
- currentStepRef.current = null;
530
- }
531
- setCurrentStep(null);
532
- setSpinning(false);
533
- setTimeout(() => exit(), 0);
534
- })();
535
- });
536
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
537
- /* @__PURE__ */ jsx2(StepLog, { items }),
538
- err && /* @__PURE__ */ jsx2(ErrorLine, { msg: err }),
539
- spinning && currentStep && /* @__PURE__ */ jsxs2(Box2, { children: [
540
- /* @__PURE__ */ jsx2(Spinner, {}),
541
- /* @__PURE__ */ jsx2(Text2, { children: " " }),
542
- currentStep
543
- ] })
544
- ] });
545
- }
546
- async function runWithInk(fn) {
547
- let thrownError;
548
- const app = render2(
549
- /* @__PURE__ */ jsx2(
550
- CommandRunner,
551
- {
552
- onError: (e) => {
553
- thrownError = e;
554
- },
555
- run: fn
556
- }
557
- )
558
- );
559
- await app.waitUntilExit();
560
- if (thrownError) process.exit(1);
561
- }
562
- var init_ink = __esm({
563
- "cli/_ink.tsx"() {
564
- "use strict";
565
- init_colors();
566
- }
520
+ // cli/_build.tsx
521
+ var build_exports = {};
522
+ __export(build_exports, {
523
+ buildAgentBundle: () => buildAgentBundle,
524
+ runBuildCommand: () => runBuildCommand
567
525
  });
568
-
569
- // cli/_build.ts
570
- import React2 from "react";
571
- async function buildAgentBundle(cwd, log) {
526
+ import { jsx as jsx3 } from "react/jsx-runtime";
527
+ async function buildAgentBundle(cwd, log, opts) {
572
528
  const agent = await loadAgent(cwd);
573
529
  if (!agent) {
574
530
  throw new Error("No agent found \u2014 run `aai init` first");
575
531
  }
576
- log(React2.createElement(Step, { action: "Bundle", msg: agent.slug }));
532
+ log(/* @__PURE__ */ jsx3(Step, { action: "Bundle", msg: agent.slug }));
577
533
  let bundle;
578
534
  try {
579
535
  bundle = await bundleAgent(agent);
@@ -585,125 +541,44 @@ async function buildAgentBundle(cwd, log) {
585
541
  }
586
542
  const kb = (bundle.workerBytes / 1024).toFixed(1);
587
543
  const clientCount = Object.keys(bundle.clientFiles).length;
588
- log(React2.createElement(Info, { msg: `worker: ${kb} KB, client: ${clientCount} file(s)` }));
589
- if (agent.clientEntry) {
590
- try {
591
- const renderCheckPath = "../sdk/_render_check.ts";
592
- const { renderCheck } = await import(
593
- /* @vite-ignore */
594
- renderCheckPath
595
- );
596
- log(React2.createElement(Step, { action: "Render", msg: "check" }));
597
- await renderCheck(agent.clientEntry, cwd);
598
- } catch (err) {
599
- const msg = err instanceof Error ? err.message : String(err);
600
- if (msg.includes("linkedom") || msg.includes("_render_check")) return bundle;
601
- throw new Error(`Render check failed: ${msg}`);
544
+ log(/* @__PURE__ */ jsx3(Info, { msg: `worker: ${kb} KB, client: ${clientCount} file(s)` }));
545
+ if (agent.clientEntry && !opts?.skipRenderCheck) {
546
+ const renderCheckPath = "../sdk/_render_check.ts";
547
+ const mod = await import(
548
+ /* @vite-ignore */
549
+ renderCheckPath
550
+ ).catch(() => null);
551
+ if (mod) {
552
+ log(/* @__PURE__ */ jsx3(Step, { action: "Render", msg: "check" }));
553
+ try {
554
+ await mod.renderCheck(agent.clientEntry, cwd);
555
+ } catch (err) {
556
+ throw new Error(`Render check failed: ${errorMessage(err)}`);
557
+ }
602
558
  }
603
559
  }
604
560
  return bundle;
605
561
  }
562
+ async function runBuildCommand(cwd) {
563
+ await runWithInk(async ({ log }) => {
564
+ await buildAgentBundle(cwd, log);
565
+ log(/* @__PURE__ */ jsx3(Step, { action: "Build", msg: "ok" }));
566
+ });
567
+ }
606
568
  var init_build = __esm({
607
- "cli/_build.ts"() {
569
+ "cli/_build.tsx"() {
608
570
  "use strict";
571
+ init_utils();
609
572
  init_bundler();
610
573
  init_discover();
611
574
  init_ink();
612
575
  }
613
576
  });
614
577
 
615
- // cli/_init.ts
616
- var init_exports = {};
617
- __export(init_exports, {
618
- listTemplates: () => listTemplates,
619
- runInit: () => runInit
620
- });
621
- import fs3 from "node:fs/promises";
622
- import path3 from "node:path";
623
- import glob from "fast-glob";
624
- import fsExtra from "fs-extra";
625
- async function listTemplates(dir) {
626
- const templates = [];
627
- const entries = await fs3.readdir(dir, { withFileTypes: true });
628
- for (const entry of entries) {
629
- if (entry.isDirectory() && !entry.name.startsWith("_")) {
630
- templates.push(entry.name);
631
- }
632
- }
633
- return templates.sort();
634
- }
635
- async function copyDirNoOverwrite(src, dest) {
636
- const files = await glob("**/*", { cwd: src, dot: true, onlyFiles: true });
637
- for (const file of files) {
638
- const destPath = path3.join(dest, file);
639
- try {
640
- await fs3.access(destPath);
641
- } catch {
642
- await fs3.mkdir(path3.dirname(destPath), { recursive: true });
643
- await fs3.copyFile(path3.join(src, file), destPath);
644
- }
645
- }
646
- }
647
- async function runInit(opts) {
648
- const { targetDir, template, templatesDir } = opts;
649
- const available = await listTemplates(templatesDir);
650
- if (!available.includes(template)) {
651
- throw new Error(`unknown template '${template}' -- available: ${available.join(", ")}`);
652
- }
653
- await fsExtra.copy(path3.join(templatesDir, template), targetDir, { overwrite: true });
654
- await copyDirNoOverwrite(path3.join(templatesDir, "_shared"), targetDir);
655
- try {
656
- await fs3.copyFile(path3.join(targetDir, ".env.example"), path3.join(targetDir, ".env"));
657
- } catch {
658
- }
659
- const readmePath = path3.join(targetDir, "README.md");
660
- try {
661
- await fs3.access(readmePath);
662
- } catch {
663
- const slug = path3.basename(path3.resolve(targetDir));
664
- const readme = `# ${slug}
665
-
666
- A voice agent built with [aai](https://github.com/anthropics/aai).
667
-
668
- ## Getting started
669
-
670
- \`\`\`sh
671
- npm install # Install dependencies
672
- npm run dev # Run locally (opens browser)
673
- npm run deploy # Deploy to production
674
- \`\`\`
675
-
676
- ## Environment variables
677
-
678
- Secrets are managed on the server, not in local files:
679
-
680
- \`\`\`sh
681
- aai env add MY_KEY # Set a secret (prompts for value)
682
- aai env ls # List secret names
683
- aai env pull # Pull names into .env for reference
684
- aai env rm MY_KEY # Remove a secret
685
- \`\`\`
686
-
687
- Access secrets in your agent via \`ctx.env.MY_KEY\`.
688
-
689
- ## Learn more
690
-
691
- See \`CLAUDE.md\` for the full agent API reference.
692
- `;
693
- await fs3.writeFile(readmePath, readme);
694
- }
695
- return targetDir;
696
- }
697
- var init_init = __esm({
698
- "cli/_init.ts"() {
699
- "use strict";
700
- }
701
- });
702
-
703
578
  // cli/_deploy.ts
704
- async function attemptDeploy(url, slug, apiKey, env, worker, clientFiles) {
579
+ async function attemptDeploy(fetchFn, url, slug, apiKey, env, worker, clientFiles) {
705
580
  try {
706
- return await _internals.fetch(`${url}/${slug}/deploy`, {
581
+ return await fetchFn(`${url}/${slug}/deploy`, {
707
582
  method: "POST",
708
583
  headers: {
709
584
  "Content-Type": "application/json",
@@ -720,37 +595,36 @@ async function attemptDeploy(url, slug, apiKey, env, worker, clientFiles) {
720
595
  }
721
596
  }
722
597
  async function runDeploy(opts) {
723
- const worker = opts.bundle.worker;
724
- const clientFiles = opts.bundle.clientFiles;
598
+ const { worker, clientFiles } = opts.bundle;
599
+ const fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
725
600
  let slug = opts.slug;
726
- if (opts.dryRun) {
727
- return { slug };
728
- }
729
601
  for (let i = 0; i < MAX_RETRIES; i++) {
730
- const resp = await attemptDeploy(opts.url, slug, opts.apiKey, opts.env, worker, clientFiles);
602
+ const resp = await attemptDeploy(
603
+ fetchFn,
604
+ opts.url,
605
+ slug,
606
+ opts.apiKey,
607
+ opts.env,
608
+ worker,
609
+ clientFiles
610
+ );
731
611
  if (resp.ok) {
732
612
  return { slug };
733
613
  }
734
- if (resp.status === 403) {
735
- const text2 = await resp.text();
736
- if (text2.includes("Slug")) {
737
- slug = generateSlug();
738
- continue;
739
- }
740
- }
741
614
  const text = await resp.text();
615
+ if (resp.status === 403 && text.includes("Slug")) {
616
+ slug = generateSlug();
617
+ continue;
618
+ }
742
619
  throw new Error(`deploy failed (${resp.status}): ${text}`);
743
620
  }
744
621
  throw new Error(`deploy failed: could not find available slug after ${MAX_RETRIES} attempts`);
745
622
  }
746
- var _internals, MAX_RETRIES;
623
+ var MAX_RETRIES;
747
624
  var init_deploy = __esm({
748
625
  "cli/_deploy.ts"() {
749
626
  "use strict";
750
627
  init_discover();
751
- _internals = {
752
- fetch: globalThis.fetch.bind(globalThis)
753
- };
754
628
  MAX_RETRIES = 20;
755
629
  }
756
630
  });
@@ -760,103 +634,73 @@ var deploy_exports = {};
760
634
  __export(deploy_exports, {
761
635
  runDeployCommand: () => runDeployCommand
762
636
  });
763
- import path4 from "node:path";
764
- import minimist from "minimist";
765
- import { jsx as jsx3 } from "react/jsx-runtime";
766
- function resolveServerUrl(parsed) {
767
- return parsed.server || (isDevMode() ? "http://localhost:3100" : DEFAULT_SERVER);
768
- }
637
+ import { jsx as jsx4 } from "react/jsx-runtime";
769
638
  async function deployBundle(opts) {
770
639
  const { bundle, serverUrl, apiKey, cwd, log } = opts;
771
640
  let { slug } = opts;
772
- log(/* @__PURE__ */ jsx3(Step, { action: "Deploy", msg: slug }));
641
+ log(/* @__PURE__ */ jsx4(Step, { action: "Deploy", msg: slug }));
773
642
  const deployed = await runDeploy({
774
643
  url: serverUrl,
775
644
  bundle,
776
645
  env: { ASSEMBLYAI_API_KEY: apiKey },
777
646
  slug,
778
- dryRun: false,
779
647
  apiKey
780
648
  });
781
649
  slug = deployed.slug;
782
650
  await writeProjectConfig(cwd, { slug, serverUrl });
783
651
  const agentUrl = `${serverUrl}/${slug}`;
784
- log(/* @__PURE__ */ jsx3(Step, { action: "Ready", msg: agentUrl }));
652
+ log(/* @__PURE__ */ jsx4(Step, { action: "Ready", msg: agentUrl }));
785
653
  return agentUrl;
786
654
  }
787
- async function runDeployCommand(args, version) {
788
- const parsed = minimist(args, {
789
- string: ["server"],
790
- boolean: ["dry-run", "help", "yes"],
791
- alias: { s: "server", h: "help", y: "yes" }
792
- });
793
- if (parsed.help) {
794
- console.log(subcommandHelp(deployCommandDef, version));
795
- return;
796
- }
797
- const cwd = process.env.INIT_CWD || process.cwd();
798
- if (!await fileExists(path4.join(cwd, "agent.ts"))) {
799
- await runInitCommand(parsed.yes ? ["-y"] : [], version, { quiet: true });
800
- }
801
- const serverUrl = resolveServerUrl(parsed);
802
- const dryRun = parsed["dry-run"] ?? false;
655
+ async function runDeployCommand(opts) {
656
+ const { cwd } = opts;
657
+ const dryRun = opts.dryRun ?? false;
803
658
  const apiKey = dryRun ? "" : await getApiKey();
804
659
  const projectConfig = await readProjectConfig(cwd);
660
+ const serverUrl = resolveServerUrl(opts.server, projectConfig?.serverUrl);
805
661
  const slug = projectConfig?.slug ?? generateSlug();
806
662
  let agentUrl = "";
807
- await runWithInk(async (log) => {
663
+ await runWithInk(async ({ log }) => {
808
664
  const bundle = await buildAgentBundle(cwd, log);
809
665
  if (dryRun) {
810
- log(/* @__PURE__ */ jsx3(StepInfo, { action: "Dry run", msg: `would deploy as ${slug}` }));
666
+ log(/* @__PURE__ */ jsx4(StepInfo, { action: "Dry run", msg: `would deploy as ${slug}` }));
811
667
  return;
812
668
  }
813
669
  agentUrl = await deployBundle({ bundle, serverUrl, apiKey, slug, cwd, log });
814
670
  });
815
- if (agentUrl && !dryRun) {
671
+ if (agentUrl) {
816
672
  await askEnter("Press enter to open in browser");
817
673
  const { exec } = await import("node:child_process");
818
674
  exec(`open "${agentUrl}"`);
819
675
  }
820
676
  }
821
- var deployCommandDef;
822
677
  var init_deploy2 = __esm({
823
678
  "cli/deploy.tsx"() {
824
679
  "use strict";
825
680
  init_build();
826
681
  init_deploy();
827
682
  init_discover();
828
- init_help();
829
683
  init_ink();
830
684
  init_prompts();
831
- init_init2();
832
- deployCommandDef = {
833
- name: "deploy",
834
- description: "Bundle and deploy to production",
835
- options: [
836
- { flags: "-s, --server <url>", description: "Server URL" },
837
- {
838
- flags: "--dry-run",
839
- description: "Validate and bundle without deploying"
840
- },
841
- { flags: "-y, --yes", description: "Accept defaults (no prompts)" }
842
- ]
843
- };
844
685
  }
845
686
  });
846
687
 
847
688
  // cli/init.tsx
689
+ var init_exports2 = {};
690
+ __export(init_exports2, {
691
+ runInitCommand: () => runInitCommand
692
+ });
848
693
  import { execFile } from "node:child_process";
849
694
  import fs4 from "node:fs/promises";
850
- import path5 from "node:path";
695
+ import path4 from "node:path";
851
696
  import { fileURLToPath } from "node:url";
852
697
  import { promisify } from "node:util";
853
- import minimist2 from "minimist";
854
- import { jsx as jsx4 } from "react/jsx-runtime";
698
+ import { jsx as jsx5 } from "react/jsx-runtime";
855
699
  async function rewriteDevDeps(cwd, cliDir2) {
856
- const monorepoRoot = path5.join(cliDir2, "..");
857
- const pkgJsonPath2 = path5.join(cwd, "package.json");
700
+ const monorepoRoot = path4.join(cliDir2, "..");
701
+ const pkgJsonPath2 = path4.join(cwd, "package.json");
858
702
  const pkgJson2 = JSON.parse(await fs4.readFile(pkgJsonPath2, "utf-8"));
859
- const rootPkg = JSON.parse(await fs4.readFile(path5.join(monorepoRoot, "package.json"), "utf-8"));
703
+ const rootPkg = JSON.parse(await fs4.readFile(path4.join(monorepoRoot, "package.json"), "utf-8"));
860
704
  const rootPkgName = rootPkg.name;
861
705
  if (pkgJson2.dependencies[rootPkgName]) {
862
706
  pkgJson2.dependencies[rootPkgName] = `file:${monorepoRoot}`;
@@ -865,56 +709,45 @@ async function rewriteDevDeps(cwd, cliDir2) {
865
709
  `);
866
710
  }
867
711
  async function installDeps(cwd, log) {
868
- if (await fileExists(path5.join(cwd, "node_modules"))) return;
712
+ if (await fileExists(path4.join(cwd, "node_modules"))) return;
869
713
  let pkgJson2;
870
714
  try {
871
- pkgJson2 = JSON.parse(await fs4.readFile(path5.join(cwd, "package.json"), "utf-8"));
715
+ pkgJson2 = JSON.parse(await fs4.readFile(path4.join(cwd, "package.json"), "utf-8"));
872
716
  } catch {
873
717
  pkgJson2 = {};
874
718
  }
875
719
  const deps = Object.keys(pkgJson2.dependencies ?? {});
876
720
  const devDeps = Object.keys(pkgJson2.devDependencies ?? {});
877
721
  if (deps.length > 0) {
878
- log(/* @__PURE__ */ jsx4(Step, { action: "Install", msg: deps.join(", ") }));
722
+ log(/* @__PURE__ */ jsx5(Step, { action: "Install", msg: deps.join(", ") }));
879
723
  }
880
724
  if (devDeps.length > 0) {
881
- log(/* @__PURE__ */ jsx4(Step, { action: "Install", msg: `dev: ${devDeps.join(", ")}` }));
725
+ log(/* @__PURE__ */ jsx5(Step, { action: "Install", msg: `dev: ${devDeps.join(", ")}` }));
882
726
  }
883
727
  try {
884
728
  await execFileAsync("npm", ["install"], { cwd });
885
729
  } catch {
886
- log(/* @__PURE__ */ jsx4(Warn, { msg: "npm install failed" }));
730
+ log(/* @__PURE__ */ jsx5(Warn, { msg: "npm install failed" }));
887
731
  }
888
732
  }
889
- async function runInitCommand(args, version, opts) {
890
- const parsed = minimist2(args, {
891
- string: ["template"],
892
- boolean: ["force", "help"],
893
- alias: { t: "template", f: "force", h: "help" }
894
- });
895
- if (parsed.help) {
896
- console.log(subcommandHelp(initCommandDef, version));
897
- return "";
898
- }
899
- const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_discover(), discover_exports));
900
- await getApiKey2();
901
- let dir = parsed._[0];
733
+ async function runInitCommand(opts, extra) {
734
+ await getApiKey();
735
+ let dir = opts.dir;
902
736
  if (!dir) {
903
737
  dir = await askText("What is your project named?", "my-voice-agent");
904
738
  }
905
- const cwd = path5.resolve(process.env.INIT_CWD || process.cwd(), dir);
906
- if (!parsed.force && await fileExists(path5.join(cwd, "agent.ts"))) {
907
- console.log(
739
+ const cwd = path4.resolve(resolveCwd(), dir);
740
+ if (!opts.force && await fileExists(path4.join(cwd, "agent.ts"))) {
741
+ throw new Error(
908
742
  `agent.ts already exists in this directory. Use ${interactive("--force")} to overwrite.`
909
743
  );
910
- process.exit(1);
911
744
  }
912
- const cliDir2 = path5.dirname(fileURLToPath(import.meta.url));
913
- const templatesDir = path5.join(cliDir2, "..", "templates");
745
+ const cliDir2 = path4.dirname(fileURLToPath(import.meta.url));
746
+ const templatesDir = path4.join(cliDir2, "..", "templates");
914
747
  const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
915
- const template = parsed.template || "simple";
916
- await runWithInk(async (log) => {
917
- log(/* @__PURE__ */ jsx4(Step, { action: "Create", msg: dir }));
748
+ const template = opts.template || "simple";
749
+ await runWithInk(async ({ log }) => {
750
+ log(/* @__PURE__ */ jsx5(Step, { action: "Create", msg: dir }));
918
751
  await runInit2({ targetDir: cwd, template, templatesDir });
919
752
  if (isDevMode()) {
920
753
  await rewriteDevDeps(cwd, cliDir2);
@@ -923,60 +756,32 @@ async function runInitCommand(args, version, opts) {
923
756
  });
924
757
  process.chdir(cwd);
925
758
  delete process.env.INIT_CWD;
926
- if (!opts?.quiet) {
759
+ if (!extra?.quiet) {
927
760
  const { runDeployCommand: runDeployCommand2 } = await Promise.resolve().then(() => (init_deploy2(), deploy_exports));
928
- await runDeployCommand2(["-y"], version);
761
+ await runDeployCommand2({ cwd });
929
762
  }
930
763
  return cwd;
931
764
  }
932
- var execFileAsync, initCommandDef;
765
+ var execFileAsync;
933
766
  var init_init2 = __esm({
934
767
  "cli/init.tsx"() {
935
768
  "use strict";
936
- init_colors();
937
769
  init_discover();
938
- init_help();
939
770
  init_ink();
940
771
  init_prompts();
941
772
  execFileAsync = promisify(execFile);
942
- initCommandDef = {
943
- name: "init",
944
- description: "Scaffold a new agent project",
945
- args: [{ name: "dir", optional: true }],
946
- options: [
947
- {
948
- flags: "-t, --template <template>",
949
- description: "Template to use"
950
- },
951
- { flags: "-f, --force", description: "Overwrite existing agent.ts" }
952
- ]
953
- };
954
773
  }
955
774
  });
956
775
 
957
776
  // sdk/protocol.ts
958
777
  import { z } from "zod";
959
- var DEFAULT_TTS_SAMPLE_RATE, AUDIO_FORMAT, _bitsPerSample, _channels, AudioFrameSpec, KvRequestBaseSchema, HOOK_TIMEOUT_MS, SessionErrorCodeSchema, TranscriptEventSchema, ClientEventSchema, ClientMessageSchema;
778
+ var DEFAULT_TTS_SAMPLE_RATE, AUDIO_FORMAT, KvRequestSchema, VectorRequestSchema, HOOK_TIMEOUT_MS, TOOL_EXECUTION_TIMEOUT_MS, SessionErrorCodeSchema, ev, textEv, turnOrder, ClientEventSchema, ClientMessageSchema;
960
779
  var init_protocol = __esm({
961
780
  "sdk/protocol.ts"() {
962
781
  "use strict";
963
782
  DEFAULT_TTS_SAMPLE_RATE = 24e3;
964
783
  AUDIO_FORMAT = "pcm16";
965
- _bitsPerSample = 16;
966
- _channels = 1;
967
- AudioFrameSpec = {
968
- /** Audio codec identifier sent in the `ready` message. */
969
- format: AUDIO_FORMAT,
970
- /** Signed 16-bit integer samples. */
971
- bitsPerSample: _bitsPerSample,
972
- /** Little-endian byte order. */
973
- endianness: "little",
974
- /** Mono audio. */
975
- channels: _channels,
976
- /** Bytes per sample — derived from bitsPerSample and channels. */
977
- bytesPerSample: _bitsPerSample / 8 * _channels
978
- };
979
- KvRequestBaseSchema = z.discriminatedUnion("op", [
784
+ KvRequestSchema = z.discriminatedUnion("op", [
980
785
  z.object({ op: z.literal("get"), key: z.string().min(1) }),
981
786
  z.object({
982
787
  op: z.literal("set"),
@@ -990,9 +795,29 @@ var init_protocol = __esm({
990
795
  prefix: z.string(),
991
796
  limit: z.number().int().positive().optional(),
992
797
  reverse: z.boolean().optional()
798
+ }),
799
+ z.object({ op: z.literal("keys"), pattern: z.string().optional() })
800
+ ]);
801
+ VectorRequestSchema = z.discriminatedUnion("op", [
802
+ z.object({
803
+ op: z.literal("upsert"),
804
+ id: z.string().min(1),
805
+ data: z.string().min(1),
806
+ metadata: z.record(z.string(), z.unknown()).optional()
807
+ }),
808
+ z.object({
809
+ op: z.literal("query"),
810
+ text: z.string().min(1),
811
+ topK: z.number().int().positive().max(100).optional(),
812
+ filter: z.string().optional()
813
+ }),
814
+ z.object({
815
+ op: z.literal("remove"),
816
+ ids: z.array(z.string().min(1)).min(1)
993
817
  })
994
818
  ]);
995
819
  HOOK_TIMEOUT_MS = 5e3;
820
+ TOOL_EXECUTION_TIMEOUT_MS = 3e4;
996
821
  SessionErrorCodeSchema = z.enum([
997
822
  "stt",
998
823
  "llm",
@@ -1003,23 +828,16 @@ var init_protocol = __esm({
1003
828
  "audio",
1004
829
  "internal"
1005
830
  ]);
1006
- TranscriptEventSchema = z.object({
1007
- type: z.literal("transcript"),
1008
- text: z.string(),
1009
- isFinal: z.boolean(),
1010
- turnOrder: z.number().int().nonnegative().optional()
1011
- });
831
+ ev = (t) => z.object({ type: z.literal(t) });
832
+ textEv = (t) => z.object({ type: z.literal(t), text: z.string() });
833
+ turnOrder = z.number().int().nonnegative().optional();
1012
834
  ClientEventSchema = z.discriminatedUnion("type", [
1013
- z.object({ type: z.literal("speech_started") }),
1014
- z.object({ type: z.literal("speech_stopped") }),
1015
- TranscriptEventSchema,
1016
- z.object({
1017
- type: z.literal("turn"),
1018
- text: z.string(),
1019
- turnOrder: z.number().int().nonnegative().optional()
1020
- }),
1021
- z.object({ type: z.literal("chat"), text: z.string() }),
1022
- z.object({ type: z.literal("chat_delta"), text: z.string() }),
835
+ ev("speech_started"),
836
+ ev("speech_stopped"),
837
+ z.object({ type: z.literal("transcript"), text: z.string(), isFinal: z.boolean(), turnOrder }),
838
+ textEv("turn").extend({ turnOrder }),
839
+ textEv("chat"),
840
+ textEv("chat_delta"),
1023
841
  z.object({
1024
842
  type: z.literal("tool_call_start"),
1025
843
  toolCallId: z.string(),
@@ -1031,55 +849,35 @@ var init_protocol = __esm({
1031
849
  toolCallId: z.string(),
1032
850
  result: z.string().max(4e3)
1033
851
  }),
1034
- z.object({ type: z.literal("tts_done") }),
1035
- z.object({ type: z.literal("cancelled") }),
1036
- z.object({ type: z.literal("reset") }),
1037
- z.object({
1038
- type: z.literal("error"),
1039
- code: SessionErrorCodeSchema,
1040
- message: z.string()
1041
- })
852
+ ev("tts_done"),
853
+ ev("cancelled"),
854
+ ev("reset"),
855
+ z.object({ type: z.literal("error"), code: SessionErrorCodeSchema, message: z.string() })
1042
856
  ]);
1043
857
  ClientMessageSchema = z.discriminatedUnion("type", [
1044
- z.object({ type: z.literal("audio_ready") }),
1045
- z.object({ type: z.literal("cancel") }),
1046
- z.object({ type: z.literal("reset") }),
858
+ ev("audio_ready"),
859
+ ev("cancel"),
860
+ ev("reset"),
1047
861
  z.object({
1048
862
  type: z.literal("history"),
1049
- messages: z.array(
1050
- z.object({
1051
- role: z.enum(["user", "assistant"]),
1052
- text: z.string().max(1e5)
1053
- })
1054
- ).max(200)
863
+ messages: z.array(z.object({ role: z.enum(["user", "assistant"]), text: z.string().max(1e5) })).max(200)
1055
864
  })
1056
865
  ]);
1057
866
  }
1058
867
  });
1059
868
 
1060
869
  // sdk/runtime.ts
1061
- var consoleLogger, noopMetrics, DEFAULT_S2S_CONFIG;
870
+ var _log, consoleLogger, noopMetrics, DEFAULT_S2S_CONFIG;
1062
871
  var init_runtime = __esm({
1063
872
  "sdk/runtime.ts"() {
1064
873
  "use strict";
1065
874
  init_protocol();
875
+ _log = (m) => (msg, ctx) => console[m](msg, ...ctx ? [ctx] : []);
1066
876
  consoleLogger = {
1067
- info(msg, ctx) {
1068
- if (ctx) console.log(msg, ctx);
1069
- else console.log(msg);
1070
- },
1071
- warn(msg, ctx) {
1072
- if (ctx) console.warn(msg, ctx);
1073
- else console.warn(msg);
1074
- },
1075
- error(msg, ctx) {
1076
- if (ctx) console.error(msg, ctx);
1077
- else console.error(msg);
1078
- },
1079
- debug(msg, ctx) {
1080
- if (ctx) console.debug(msg, ctx);
1081
- else console.debug(msg);
1082
- }
877
+ info: _log("log"),
878
+ warn: _log("warn"),
879
+ error: _log("error"),
880
+ debug: _log("debug")
1083
881
  };
1084
882
  noopMetrics = {
1085
883
  sessionsTotal: { inc() {
@@ -1096,107 +894,265 @@ var init_runtime = __esm({
1096
894
  }
1097
895
  });
1098
896
 
1099
- // sdk/_internal_types.ts
897
+ // sdk/s2s.ts
898
+ var s2s_exports = {};
899
+ __export(s2s_exports, {
900
+ connectS2s: () => connectS2s,
901
+ wrapOnStyleWebSocket: () => wrapOnStyleWebSocket
902
+ });
1100
903
  import { z as z2 } from "zod";
1101
- function agentToolsToSchemas(tools) {
1102
- return Object.entries(tools).map(([name, def]) => ({
1103
- name,
1104
- description: def.description,
1105
- parameters: z2.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
1106
- }));
904
+ function uint8ToBase64(bytes) {
905
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
1107
906
  }
1108
- var EMPTY_PARAMS;
1109
- var init_internal_types = __esm({
1110
- "sdk/_internal_types.ts"() {
1111
- "use strict";
1112
- EMPTY_PARAMS = z2.object({});
1113
- }
1114
- });
1115
-
1116
- // sdk/types.ts
1117
- function tool(def) {
1118
- return def;
907
+ function base64ToUint8(base64) {
908
+ const buf = Buffer.from(base64, "base64");
909
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
1119
910
  }
1120
- var DEFAULT_INSTRUCTIONS;
1121
- var init_types = __esm({
1122
- "sdk/types.ts"() {
1123
- "use strict";
1124
- DEFAULT_INSTRUCTIONS = `You are AAI, a helpful AI assistant.
1125
-
1126
- Voice-First Rules:
1127
- - Optimize for natural speech. Avoid jargon unless central to the answer. Use short, punchy sentences.
1128
- - Never mention "search results," "sources," or "the provided text." Speak as if the knowledge is your own.
1129
- - No visual formatting. Do not say "bullet point," "bold," or "bracketed one." If you need to list items, say "First," "Next," and "Finally."
1130
- - Start with the most important information. No introductory filler.
1131
- - Be concise. Keep answers to 1-3 sentences. For complex topics, provide a high-level summary.
1132
- - Be confident. Avoid hedging phrases like "It seems that" or "I believe."
1133
- - If you don't have enough information, say so directly rather than guessing.
1134
- - Never use exclamation points. Keep your tone calm and conversational.`;
1135
- }
1136
- });
1137
-
1138
- // sdk/memory_tools.ts
1139
- import { z as z3 } from "zod";
1140
- function memoryTools() {
1141
- return {
1142
- save_memory: tool({
1143
- description: "Save a piece of information to persistent memory. Use a descriptive key like 'user:name' or 'project:status'.",
1144
- parameters: z3.object({
1145
- key: z3.string().describe("A descriptive key for this memory (e.g. 'user:name', 'preference:color')"),
1146
- value: z3.string().describe("The information to remember")
1147
- }),
1148
- execute: async ({ key, value }, ctx) => {
1149
- await ctx.kv.set(key, value);
1150
- return { saved: key };
911
+ function wrapOnStyleWebSocket(ws) {
912
+ const target = new EventTarget();
913
+ ws.on("open", () => target.dispatchEvent(new Event("open")));
914
+ ws.on("message", (data) => target.dispatchEvent(new MessageEvent("message", { data })));
915
+ ws.on("close", (code, reason) => {
916
+ const init = { reason: String(reason ?? "") };
917
+ if (typeof code === "number") init.code = code;
918
+ target.dispatchEvent(new CloseEvent("close", init));
919
+ });
920
+ ws.on(
921
+ "error",
922
+ (err) => target.dispatchEvent(
923
+ new ErrorEvent("error", {
924
+ message: errorMessage(err)
925
+ })
926
+ )
927
+ );
928
+ Object.defineProperties(target, {
929
+ readyState: { get: () => ws.readyState, enumerable: true },
930
+ send: { value: (data) => ws.send(data), enumerable: true },
931
+ close: { value: () => ws.close(), enumerable: true }
932
+ });
933
+ return target;
934
+ }
935
+ function dispatchS2sMessage(target, msg) {
936
+ const entry = S2S_DISPATCH[msg.type]?.(msg);
937
+ if (entry) target.dispatchEvent(new CustomEvent(entry[0], { detail: entry[1] }));
938
+ }
939
+ function connectS2s(opts) {
940
+ const { apiKey, config, createWebSocket, logger: log = consoleLogger } = opts;
941
+ return new Promise((resolve, reject) => {
942
+ log.info("S2S connecting", { url: config.wssUrl });
943
+ const ws = createWebSocket(config.wssUrl, {
944
+ headers: { Authorization: `Bearer ${apiKey}` }
945
+ });
946
+ const target = new EventTarget();
947
+ let opened = false;
948
+ function send(msg) {
949
+ if (ws.readyState !== WS_OPEN) return;
950
+ const json = JSON.stringify(msg);
951
+ if (msg.type !== "input.audio") {
952
+ log.info(
953
+ `S2S >> ${msg.type}`,
954
+ msg.type === "session.update" ? { payload: json } : void 0
955
+ );
1151
956
  }
1152
- }),
1153
- recall_memory: tool({
1154
- description: "Retrieve a previously saved memory by its key.",
1155
- parameters: z3.object({
1156
- key: z3.string().describe("The key to look up")
1157
- }),
1158
- execute: async ({ key }, ctx) => {
1159
- const value = await ctx.kv.get(key);
1160
- if (value === null) return { found: false, key };
1161
- return { found: true, key, value };
957
+ ws.send(json);
958
+ }
959
+ const handle = Object.assign(target, {
960
+ sendAudio(audio) {
961
+ if (ws.readyState !== WS_OPEN) return;
962
+ ws.send(`{"type":"input.audio","audio":"${uint8ToBase64(audio)}"}`);
963
+ },
964
+ sendToolResult(callId, result) {
965
+ const msg = { type: "tool.result", call_id: callId, result };
966
+ log.info("S2S >> tool.result", { call_id: callId, resultLength: result.length });
967
+ send(msg);
968
+ },
969
+ updateSession(sessionConfig) {
970
+ send({ type: "session.update", session: sessionConfig });
971
+ },
972
+ resumeSession(sessionId) {
973
+ send({ type: "session.resume", session_id: sessionId });
974
+ },
975
+ close() {
976
+ log.info("S2S closing");
977
+ ws.close();
1162
978
  }
1163
- }),
1164
- list_memories: tool({
1165
- description: "List all saved memory keys, optionally filtered by a prefix (e.g. 'user:').",
1166
- parameters: z3.object({
1167
- prefix: z3.string().describe("Prefix to filter keys (e.g. 'user:'). Use empty string for all.").optional()
1168
- }),
1169
- execute: async ({ prefix }, ctx) => {
1170
- const entries = await ctx.kv.list(prefix ?? "");
1171
- return { count: entries.length, keys: entries.map((e) => e.key) };
979
+ });
980
+ ws.addEventListener("open", () => {
981
+ opened = true;
982
+ log.info("S2S WebSocket open");
983
+ resolve(handle);
984
+ });
985
+ function handleS2sMessage(ev2) {
986
+ const data = ev2.data;
987
+ let raw;
988
+ try {
989
+ raw = JSON.parse(String(data));
990
+ } catch {
991
+ log.warn("S2S << invalid JSON", { data: String(data).slice(0, 200) });
992
+ return;
1172
993
  }
1173
- }),
1174
- forget_memory: tool({
1175
- description: "Delete a previously saved memory by its key.",
1176
- parameters: z3.object({
1177
- key: z3.string().describe("The key to delete")
1178
- }),
1179
- execute: async ({ key }, ctx) => {
1180
- await ctx.kv.delete(key);
1181
- return { deleted: key };
994
+ const obj = raw;
995
+ if (obj.type !== "reply.audio" && obj.type !== "input.audio") {
996
+ log.info(
997
+ `S2S << ${obj.type}`,
998
+ obj.type === "transcript.agent.delta" ? { delta: obj.delta } : void 0
999
+ );
1182
1000
  }
1183
- })
1184
- };
1001
+ if (obj.type === "reply.audio" && typeof obj.data === "string") {
1002
+ const audioBytes = base64ToUint8(obj.data);
1003
+ target.dispatchEvent(new CustomEvent("audio", { detail: { audio: audioBytes } }));
1004
+ return;
1005
+ }
1006
+ const parsed = S2sServerMessageSchema.safeParse(raw);
1007
+ if (!parsed.success) {
1008
+ log.warn(
1009
+ `S2S << unrecognised message type: ${obj.type ?? JSON.stringify(raw).slice(0, 200)}`
1010
+ );
1011
+ return;
1012
+ }
1013
+ dispatchS2sMessage(target, parsed.data);
1014
+ }
1015
+ ws.addEventListener("message", handleS2sMessage);
1016
+ ws.addEventListener("close", ((ev2) => {
1017
+ log.info("S2S WebSocket closed", {
1018
+ code: ev2.code ?? 0,
1019
+ reason: ev2.reason ?? ""
1020
+ });
1021
+ target.dispatchEvent(new CustomEvent("close"));
1022
+ }));
1023
+ ws.addEventListener("error", ((ev2) => {
1024
+ const message = ev2 instanceof ErrorEvent ? ev2.message : "WebSocket error";
1025
+ const errObj = new Error(message);
1026
+ log.error("S2S WebSocket error", { error: errObj.message });
1027
+ if (!opened) {
1028
+ reject(errObj);
1029
+ } else {
1030
+ target.dispatchEvent(
1031
+ new CustomEvent("error", {
1032
+ detail: { code: "ws_error", message: errObj.message }
1033
+ })
1034
+ );
1035
+ }
1036
+ }));
1037
+ });
1185
1038
  }
1186
- var init_memory_tools = __esm({
1187
- "sdk/memory_tools.ts"() {
1039
+ var WS_OPEN, S2sServerMessageSchema, S2S_DISPATCH;
1040
+ var init_s2s = __esm({
1041
+ "sdk/s2s.ts"() {
1188
1042
  "use strict";
1189
- init_types();
1043
+ init_utils();
1044
+ init_runtime();
1045
+ WS_OPEN = 1;
1046
+ S2sServerMessageSchema = z2.discriminatedUnion("type", [
1047
+ z2.object({ type: z2.literal("session.ready"), session_id: z2.string() }),
1048
+ z2.object({ type: z2.literal("session.updated") }).passthrough(),
1049
+ z2.object({ type: z2.literal("input.speech.started") }),
1050
+ z2.object({ type: z2.literal("input.speech.stopped") }),
1051
+ z2.object({ type: z2.literal("transcript.user.delta"), text: z2.string() }),
1052
+ z2.object({
1053
+ type: z2.literal("transcript.user"),
1054
+ item_id: z2.string(),
1055
+ text: z2.string()
1056
+ }),
1057
+ z2.object({ type: z2.literal("reply.started"), reply_id: z2.string() }),
1058
+ // reply.audio is handled on the fast path before Zod.
1059
+ z2.object({ type: z2.literal("transcript.agent.delta"), delta: z2.string() }).passthrough(),
1060
+ z2.object({ type: z2.literal("transcript.agent"), text: z2.string() }),
1061
+ z2.object({ type: z2.literal("reply.content_part.started") }).passthrough(),
1062
+ z2.object({ type: z2.literal("reply.content_part.done") }).passthrough(),
1063
+ z2.object({
1064
+ type: z2.literal("tool.call"),
1065
+ call_id: z2.string(),
1066
+ name: z2.string(),
1067
+ args: z2.record(z2.string(), z2.unknown()).optional().default({})
1068
+ }),
1069
+ z2.object({
1070
+ type: z2.literal("reply.done"),
1071
+ status: z2.string().optional()
1072
+ }),
1073
+ z2.object({
1074
+ type: z2.literal("session.error"),
1075
+ code: z2.string(),
1076
+ message: z2.string()
1077
+ }),
1078
+ // Connection-level error (bare "error" without "session." prefix).
1079
+ z2.object({
1080
+ type: z2.literal("error"),
1081
+ message: z2.string()
1082
+ })
1083
+ ]);
1084
+ S2S_DISPATCH = {
1085
+ "session.ready": (m) => ["ready", { session_id: m.session_id }],
1086
+ "session.updated": (m) => ["session_updated", m],
1087
+ "input.speech.started": () => ["speech_started", void 0],
1088
+ "input.speech.stopped": () => ["speech_stopped", void 0],
1089
+ "transcript.user.delta": (m) => ["user_transcript_delta", { text: m.text }],
1090
+ "transcript.user": (m) => ["user_transcript", { item_id: m.item_id, text: m.text }],
1091
+ "reply.started": (m) => ["reply_started", { reply_id: m.reply_id }],
1092
+ "transcript.agent.delta": (m) => ["agent_transcript_delta", { text: m.delta }],
1093
+ "transcript.agent": (m) => ["agent_transcript", { text: m.text }],
1094
+ "tool.call": (m) => ["tool_call", { call_id: m.call_id, name: m.name, args: m.args }],
1095
+ "reply.done": (m) => ["reply_done", { status: m.status }],
1096
+ "session.error": (m) => [
1097
+ m.code === "session_not_found" || m.code === "session_forbidden" ? "session_expired" : "error",
1098
+ { code: m.code, message: m.message }
1099
+ ],
1100
+ error: (m) => ["error", { code: "connection", message: m.message }],
1101
+ "reply.content_part.started": () => void 0,
1102
+ "reply.content_part.done": () => void 0
1103
+ };
1104
+ }
1105
+ });
1106
+
1107
+ // sdk/_internal_types.ts
1108
+ import { z as z3 } from "zod";
1109
+ function agentToolsToSchemas(tools) {
1110
+ return Object.entries(tools).map(([name, def]) => ({
1111
+ name,
1112
+ description: def.description,
1113
+ parameters: z3.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
1114
+ }));
1115
+ }
1116
+ var EMPTY_PARAMS;
1117
+ var init_internal_types = __esm({
1118
+ "sdk/_internal_types.ts"() {
1119
+ "use strict";
1120
+ EMPTY_PARAMS = z3.object({});
1121
+ }
1122
+ });
1123
+
1124
+ // sdk/types.ts
1125
+ function tool(def) {
1126
+ return def;
1127
+ }
1128
+ var DEFAULT_INSTRUCTIONS;
1129
+ var init_types = __esm({
1130
+ "sdk/types.ts"() {
1131
+ "use strict";
1132
+ DEFAULT_INSTRUCTIONS = `You are AAI, a helpful AI assistant.
1133
+
1134
+ Voice-First Rules:
1135
+ - Optimize for natural speech. Avoid jargon unless central to the answer. Use short, punchy sentences.
1136
+ - Never mention "search results," "sources," or "the provided text." Speak as if the knowledge is your own.
1137
+ - No visual formatting. Do not say "bullet point," "bold," or "bracketed one." If you need to list items, say "First," "Next," and "Finally."
1138
+ - Start with the most important information. No introductory filler.
1139
+ - Be concise. Keep answers to 1-3 sentences. For complex topics, provide a high-level summary.
1140
+ - Be confident. Avoid hedging phrases like "It seems that" or "I believe."
1141
+ - If you don't have enough information, say so directly rather than guessing.
1142
+ - Never use exclamation points. Keep your tone calm and conversational.`;
1190
1143
  }
1191
1144
  });
1192
1145
 
1193
1146
  // sdk/builtin_tools.ts
1194
1147
  import { convert } from "html-to-text";
1195
1148
  import { z as z4 } from "zod";
1149
+ function fetchSignal(toolSignal) {
1150
+ return AbortSignal.any([toolSignal, AbortSignal.timeout(FETCH_TIMEOUT_MS)]);
1151
+ }
1196
1152
  function htmlToText(html) {
1197
1153
  return convert(html, { wordwrap: false });
1198
1154
  }
1199
- function createWebSearch() {
1155
+ function createWebSearch(fetchFn = globalThis.fetch) {
1200
1156
  return {
1201
1157
  description: "Search the web for current information, facts, news, or answers to questions. Returns a list of results with title, URL, and description. Use this when the user asks about something you don't know, need up-to-date information, or want to verify facts.",
1202
1158
  parameters: webSearchParams,
@@ -1211,9 +1167,9 @@ function createWebSearch() {
1211
1167
  count: String(maxResults),
1212
1168
  text_decorations: "false"
1213
1169
  })}`;
1214
- const resp = await fetch(url, {
1170
+ const resp = await fetchFn(url, {
1215
1171
  headers: { "X-Subscription-Token": apiKey },
1216
- signal: ctx.abortSignal ?? AbortSignal.timeout(15e3)
1172
+ signal: fetchSignal(ctx.abortSignal)
1217
1173
  });
1218
1174
  if (!resp.ok) return [];
1219
1175
  const raw = await resp.json();
@@ -1227,19 +1183,19 @@ function createWebSearch() {
1227
1183
  }
1228
1184
  };
1229
1185
  }
1230
- function createVisitWebpage() {
1186
+ function createVisitWebpage(fetchFn = globalThis.fetch) {
1231
1187
  return {
1232
1188
  description: "Fetch a webpage and return its content as clean text. Use this to read the full content of a URL found via web_search, or any link the user shares. Good for reading articles, documentation, blog posts, or product pages.",
1233
1189
  parameters: visitWebpageParams,
1234
1190
  async execute(args, ctx) {
1235
1191
  const { url } = args;
1236
- const resp = await fetch(url, {
1192
+ const resp = await fetchFn(url, {
1237
1193
  headers: {
1238
1194
  "User-Agent": "Mozilla/5.0 (compatible; VoiceAgent/1.0; +https://github.com/AssemblyAI/aai)",
1239
1195
  Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
1240
1196
  },
1241
1197
  redirect: "follow",
1242
- signal: ctx.abortSignal ?? AbortSignal.timeout(15e3)
1198
+ signal: fetchSignal(ctx.abortSignal)
1243
1199
  });
1244
1200
  if (!resp.ok) {
1245
1201
  return { error: `Failed to fetch: ${resp.status} ${resp.statusText}`, url };
@@ -1257,15 +1213,15 @@ function createVisitWebpage() {
1257
1213
  }
1258
1214
  };
1259
1215
  }
1260
- function createFetchJson() {
1216
+ function createFetchJson(fetchFn = globalThis.fetch) {
1261
1217
  return {
1262
1218
  description: "Call a REST API endpoint via HTTP GET and return the JSON response. Use this to fetch structured data from APIs \u2014 for example, weather data, stock prices, exchange rates, or any public JSON API. Supports custom headers for authenticated APIs.",
1263
1219
  parameters: fetchJsonParams,
1264
1220
  async execute(args, ctx) {
1265
1221
  const { url, headers } = args;
1266
- const resp = await fetch(url, {
1222
+ const resp = await fetchFn(url, {
1267
1223
  ...headers && { headers },
1268
- signal: ctx.abortSignal ?? AbortSignal.timeout(15e3)
1224
+ signal: fetchSignal(ctx.abortSignal)
1269
1225
  });
1270
1226
  if (!resp.ok) {
1271
1227
  return { error: `HTTP ${resp.status} ${resp.statusText}`, url };
@@ -1295,7 +1251,6 @@ function createRunCode() {
1295
1251
  error: capture,
1296
1252
  debug: capture
1297
1253
  };
1298
- const RUN_CODE_TIMEOUT = 5e3;
1299
1254
  const AsyncFunction = Object.getPrototypeOf(async () => {
1300
1255
  }).constructor;
1301
1256
  try {
@@ -1309,7 +1264,7 @@ function createRunCode() {
1309
1264
  const result = output.join("\n").trim();
1310
1265
  return result || "Code ran successfully (no output)";
1311
1266
  } catch (err) {
1312
- return { error: err instanceof Error ? err.message : String(err) };
1267
+ return { error: errorMessage(err) };
1313
1268
  }
1314
1269
  }
1315
1270
  };
@@ -1324,462 +1279,192 @@ function createVectorSearch(vectorSearchFn) {
1324
1279
  }
1325
1280
  };
1326
1281
  }
1282
+ function resolveBuiltin(name, opts) {
1283
+ const entry = TOOL_REGISTRY[name];
1284
+ if (!entry) return [];
1285
+ if ("multi" in entry) return Object.entries(entry.multi());
1286
+ if (name === "vector_search" && !opts?.vectorSearch) return [];
1287
+ return [[name, entry.create(opts)]];
1288
+ }
1327
1289
  function getBuiltinToolDefs(names, opts) {
1328
1290
  const defs = {};
1329
1291
  for (const name of names) {
1330
- switch (name) {
1331
- case "web_search":
1332
- defs[name] = createWebSearch();
1333
- break;
1334
- case "visit_webpage":
1335
- defs[name] = createVisitWebpage();
1336
- break;
1337
- case "fetch_json":
1338
- defs[name] = createFetchJson();
1339
- break;
1340
- case "run_code":
1341
- defs[name] = createRunCode();
1342
- break;
1343
- case "vector_search":
1344
- if (opts?.vectorSearch) {
1345
- defs[name] = createVectorSearch(opts.vectorSearch);
1346
- }
1347
- break;
1348
- case "memory": {
1349
- const mt = memoryTools();
1350
- for (const [toolName, toolDef] of Object.entries(mt)) {
1351
- defs[toolName] = toolDef;
1352
- }
1353
- break;
1354
- }
1355
- }
1292
+ for (const [k, v] of resolveBuiltin(name, opts)) defs[k] = v;
1356
1293
  }
1357
1294
  return defs;
1358
1295
  }
1359
1296
  function getBuiltinToolSchemas(names) {
1360
- return names.flatMap((name) => {
1361
- const multiCreator = MULTI_TOOL_BUILTINS[name];
1362
- if (multiCreator) {
1363
- return Object.entries(multiCreator()).map(([toolName, def2]) => ({
1364
- name: toolName,
1365
- description: def2.description,
1366
- parameters: z4.toJSONSchema(def2.parameters ?? EMPTY_PARAMS)
1367
- }));
1368
- }
1369
- const creator = TOOL_CREATORS[name];
1370
- if (!creator) return [];
1371
- const def = creator();
1372
- return [
1373
- {
1374
- name,
1375
- description: def.description,
1376
- parameters: z4.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
1377
- }
1378
- ];
1379
- });
1380
- }
1381
- var webSearchParams, BRAVE_SEARCH_URL, BraveSearchResponseSchema, MAX_PAGE_CHARS, MAX_HTML_BYTES, visitWebpageParams, fetchJsonParams, runCodeParams, vectorSearchParams, TOOL_CREATORS, MULTI_TOOL_BUILTINS;
1382
- var init_builtin_tools = __esm({
1383
- "sdk/builtin_tools.ts"() {
1384
- "use strict";
1385
- init_internal_types();
1386
- init_memory_tools();
1387
- webSearchParams = z4.object({
1388
- query: z4.string().describe("The search query"),
1389
- max_results: z4.number().describe("Maximum number of results to return (default 5)").optional()
1390
- });
1391
- BRAVE_SEARCH_URL = "https://api.search.brave.com/res/v1/web/search";
1392
- BraveSearchResponseSchema = z4.object({
1393
- web: z4.object({
1394
- results: z4.array(
1395
- z4.object({
1396
- title: z4.string(),
1397
- url: z4.string(),
1398
- description: z4.string()
1399
- })
1400
- )
1401
- }).optional()
1402
- });
1403
- MAX_PAGE_CHARS = 1e4;
1404
- MAX_HTML_BYTES = 2e5;
1405
- visitWebpageParams = z4.object({
1406
- url: z4.string().describe("The full URL to fetch (e.g., 'https://example.com/page')")
1407
- });
1408
- fetchJsonParams = z4.object({
1409
- url: z4.string().describe("The URL to fetch JSON from"),
1410
- headers: z4.record(z4.string(), z4.string()).describe("Optional HTTP headers to include in the request").optional()
1411
- });
1412
- runCodeParams = z4.object({
1413
- code: z4.string().describe("JavaScript code to execute. Use console.log() for output.")
1414
- });
1415
- vectorSearchParams = z4.object({
1416
- query: z4.string().describe(
1417
- 'Short keyword query to search the knowledge base. Use specific topic terms, not full sentences. Do NOT include the company or product name since all documents are from the same source. For example, if the user asks "how much does Acme cost", search for "pricing plans rates".'
1418
- ),
1419
- topK: z4.number().describe("Maximum results to return (default: 5)").optional()
1420
- });
1421
- TOOL_CREATORS = {
1422
- web_search: createWebSearch,
1423
- visit_webpage: createVisitWebpage,
1424
- fetch_json: createFetchJson,
1425
- run_code: createRunCode,
1426
- // vector_search uses a stub for schema generation
1427
- vector_search: () => createVectorSearch(async () => "")
1428
- };
1429
- MULTI_TOOL_BUILTINS = {
1430
- memory: memoryTools
1431
- };
1432
- }
1433
- });
1434
-
1435
- // sdk/kv.ts
1436
- function sortAndPaginate(entries, options) {
1437
- entries.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
1438
- if (options?.reverse) entries.reverse();
1439
- if (options?.limit && options.limit > 0) {
1440
- entries.length = Math.min(entries.length, options.limit);
1441
- }
1442
- return entries;
1297
+ return names.flatMap(
1298
+ (name) => resolveBuiltin(name, { vectorSearch: async () => "" }).map(([toolName, def]) => ({
1299
+ name: toolName,
1300
+ description: def.description,
1301
+ parameters: z4.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
1302
+ }))
1303
+ );
1443
1304
  }
1444
- function createMemoryKv() {
1445
- const store = /* @__PURE__ */ new Map();
1446
- function isExpired(entry) {
1447
- return entry.expiresAt !== void 0 && entry.expiresAt <= Date.now();
1448
- }
1305
+ function memoryTools() {
1449
1306
  return {
1450
- get(key) {
1451
- const entry = store.get(key);
1452
- if (!entry || isExpired(entry)) {
1453
- if (entry) store.delete(key);
1454
- return Promise.resolve(null);
1455
- }
1456
- return Promise.resolve(JSON.parse(entry.raw));
1457
- },
1458
- set(key, value, options) {
1459
- const raw = JSON.stringify(value);
1460
- if (raw.length > MAX_VALUE_SIZE) {
1461
- throw new Error(`Value exceeds max size of ${MAX_VALUE_SIZE} bytes`);
1462
- }
1463
- const expireIn = options?.expireIn;
1464
- const entry = { raw };
1465
- if (expireIn && expireIn > 0) {
1466
- entry.expiresAt = Date.now() + expireIn;
1467
- }
1468
- store.set(key, entry);
1469
- return Promise.resolve();
1470
- },
1471
- delete(key) {
1472
- store.delete(key);
1473
- return Promise.resolve();
1474
- },
1475
- async list(prefix, options) {
1476
- const now = Date.now();
1477
- const entries = [];
1478
- let i = 0;
1479
- for (const [key, entry] of store) {
1480
- if (++i % 500 === 0) await new Promise((r) => setTimeout(r, 0));
1481
- if (entry.expiresAt && entry.expiresAt <= now) {
1482
- store.delete(key);
1483
- continue;
1484
- }
1485
- if (key.startsWith(prefix)) {
1486
- entries.push({ key, value: JSON.parse(entry.raw) });
1487
- }
1488
- }
1489
- return sortAndPaginate(entries, options);
1490
- }
1491
- };
1492
- }
1493
- var MAX_VALUE_SIZE;
1494
- var init_kv = __esm({
1495
- "sdk/kv.ts"() {
1496
- "use strict";
1497
- MAX_VALUE_SIZE = 65536;
1498
- }
1499
- });
1500
-
1501
- // sdk/s2s.ts
1502
- import { z as z5 } from "zod";
1503
- function uint8ToBase64(bytes) {
1504
- return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
1505
- }
1506
- function base64ToUint8(base64) {
1507
- const buf = Buffer.from(base64, "base64");
1508
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
1509
- }
1510
- function dispatchS2sMessage(target, msg) {
1511
- switch (msg.type) {
1512
- case "session.ready":
1513
- target.dispatchEvent(
1514
- new CustomEvent("ready", {
1515
- detail: { session_id: msg.session_id }
1516
- })
1517
- );
1518
- break;
1519
- case "session.updated":
1520
- target.dispatchEvent(new CustomEvent("session_updated", { detail: msg }));
1521
- break;
1522
- case "input.speech.started":
1523
- target.dispatchEvent(new CustomEvent("speech_started"));
1524
- break;
1525
- case "input.speech.stopped":
1526
- target.dispatchEvent(new CustomEvent("speech_stopped"));
1527
- break;
1528
- case "transcript.user.delta":
1529
- target.dispatchEvent(
1530
- new CustomEvent("user_transcript_delta", {
1531
- detail: { text: msg.text }
1532
- })
1533
- );
1534
- break;
1535
- case "transcript.user":
1536
- target.dispatchEvent(
1537
- new CustomEvent("user_transcript", {
1538
- detail: {
1539
- item_id: msg.item_id,
1540
- text: msg.text
1541
- }
1542
- })
1543
- );
1544
- break;
1545
- case "reply.started":
1546
- target.dispatchEvent(
1547
- new CustomEvent("reply_started", {
1548
- detail: { reply_id: msg.reply_id }
1549
- })
1550
- );
1551
- break;
1552
- // reply.audio handled on the fast path — never reaches dispatch.
1553
- case "transcript.agent.delta":
1554
- target.dispatchEvent(
1555
- new CustomEvent("agent_transcript_delta", {
1556
- detail: { text: msg.delta }
1557
- })
1558
- );
1559
- break;
1560
- case "transcript.agent":
1561
- target.dispatchEvent(
1562
- new CustomEvent("agent_transcript", {
1563
- detail: { text: msg.text }
1564
- })
1565
- );
1566
- break;
1567
- case "tool.call":
1568
- target.dispatchEvent(
1569
- new CustomEvent("tool_call", {
1570
- detail: {
1571
- call_id: msg.call_id,
1572
- name: msg.name,
1573
- args: msg.args
1574
- }
1575
- })
1576
- );
1577
- break;
1578
- case "reply.done":
1579
- target.dispatchEvent(
1580
- new CustomEvent("reply_done", {
1581
- detail: { status: msg.status }
1582
- })
1583
- );
1584
- break;
1585
- case "session.error": {
1586
- const isExpired = msg.code === "session_not_found" || msg.code === "session_forbidden";
1587
- target.dispatchEvent(
1588
- new CustomEvent(isExpired ? "session_expired" : "error", {
1589
- detail: { code: msg.code, message: msg.message }
1590
- })
1591
- );
1592
- break;
1593
- }
1594
- case "reply.content_part.started":
1595
- case "reply.content_part.done":
1596
- break;
1597
- case "error":
1598
- target.dispatchEvent(
1599
- new CustomEvent("error", {
1600
- detail: { code: "connection", message: msg.message }
1601
- })
1602
- );
1603
- break;
1604
- }
1605
- }
1606
- function connectS2s(opts) {
1607
- const { apiKey, config, createWebSocket, logger: log = consoleLogger } = opts;
1608
- return new Promise((resolve, reject) => {
1609
- log.debug("S2S connecting", { url: config.wssUrl });
1610
- const ws = createWebSocket(config.wssUrl, {
1611
- headers: { Authorization: `Bearer ${apiKey}` }
1612
- });
1613
- const target = new EventTarget();
1614
- let opened = false;
1615
- function send(msg) {
1616
- if (ws.readyState !== WS_OPEN) return;
1617
- const json = JSON.stringify(msg);
1618
- if (msg.type !== "input.audio") {
1619
- log.debug(
1620
- `S2S >> ${msg.type}`,
1621
- msg.type === "session.update" ? { payload: json } : void 0
1622
- );
1623
- }
1624
- ws.send(json);
1625
- }
1626
- const handle = Object.assign(target, {
1627
- sendAudio(audio) {
1628
- if (ws.readyState !== WS_OPEN) return;
1629
- if (ws.sendBinary) {
1630
- const ab = audio.buffer.slice(
1631
- audio.byteOffset,
1632
- audio.byteOffset + audio.byteLength
1633
- );
1634
- ws.sendBinary(ab);
1635
- } else {
1636
- ws.send(`{"type":"input.audio","audio":"${uint8ToBase64(audio)}"}`);
1637
- }
1638
- },
1639
- sendToolResult(callId, result) {
1640
- const msg = { type: "tool.result", call_id: callId, result };
1641
- log.debug("S2S >> tool.result", { call_id: callId, resultLength: result.length });
1642
- send(msg);
1643
- },
1644
- updateSession(sessionConfig) {
1645
- send({ type: "session.update", session: sessionConfig });
1646
- },
1647
- resumeSession(sessionId) {
1648
- send({ type: "session.resume", session_id: sessionId });
1649
- },
1650
- close() {
1651
- log.debug("S2S closing");
1652
- ws.close();
1653
- }
1654
- });
1655
- ws.on("open", () => {
1656
- opened = true;
1657
- log.info("S2S WebSocket open");
1658
- resolve(handle);
1659
- });
1660
- ws.on("audio", (data) => {
1661
- const ab = data;
1662
- target.dispatchEvent(new CustomEvent("audio", { detail: { audio: new Uint8Array(ab) } }));
1663
- });
1664
- ws.on("message", (data) => {
1665
- let raw;
1666
- try {
1667
- raw = JSON.parse(String(data));
1668
- } catch {
1669
- return;
1670
- }
1671
- const obj = raw;
1672
- if (obj.type !== "reply.audio" && obj.type !== "input.audio" && obj.type !== "transcript.agent.delta") {
1673
- log.info(`S2S << ${obj.type}`);
1307
+ save_memory: tool({
1308
+ description: "Save a piece of information to persistent memory. Use a descriptive key like 'user:name' or 'project:status'.",
1309
+ parameters: z4.object({
1310
+ key: z4.string().describe("A descriptive key for this memory (e.g. 'user:name', 'preference:color')"),
1311
+ value: z4.string().describe("The information to remember")
1312
+ }),
1313
+ execute: async ({ key, value }, ctx) => {
1314
+ await ctx.kv.set(key, value);
1315
+ return { saved: key };
1674
1316
  }
1675
- if (obj.type === "reply.audio" && typeof obj.data === "string") {
1676
- const audioBytes = base64ToUint8(obj.data);
1677
- target.dispatchEvent(new CustomEvent("audio", { detail: { audio: audioBytes } }));
1678
- return;
1317
+ }),
1318
+ recall_memory: tool({
1319
+ description: "Retrieve a previously saved memory by its key.",
1320
+ parameters: z4.object({
1321
+ key: z4.string().describe("The key to look up")
1322
+ }),
1323
+ execute: async ({ key }, ctx) => {
1324
+ const value = await ctx.kv.get(key);
1325
+ if (value === null) return { found: false, key };
1326
+ return { found: true, key, value };
1679
1327
  }
1680
- const parsed = S2sServerMessageSchema.safeParse(raw);
1681
- if (!parsed.success) {
1682
- log.warn(
1683
- `S2S << unrecognised message type: ${obj.type ?? JSON.stringify(raw).slice(0, 200)}`
1684
- );
1685
- return;
1328
+ }),
1329
+ list_memories: tool({
1330
+ description: "List all saved memory keys, optionally filtered by a prefix (e.g. 'user:').",
1331
+ parameters: z4.object({
1332
+ prefix: z4.string().describe("Prefix to filter keys (e.g. 'user:'). Use empty string for all.").optional()
1333
+ }),
1334
+ execute: async ({ prefix }, ctx) => {
1335
+ const entries = await ctx.kv.list(prefix ?? "");
1336
+ return { count: entries.length, keys: entries.map((e) => e.key) };
1686
1337
  }
1687
- const msg = parsed.data;
1688
- dispatchS2sMessage(target, msg);
1689
- });
1690
- ws.on("close", (code, reason) => {
1691
- log.info("S2S WebSocket closed", {
1692
- code: typeof code === "number" ? code : 0,
1693
- reason: reason instanceof Uint8Array ? new TextDecoder().decode(reason) : String(reason ?? "")
1694
- });
1695
- target.dispatchEvent(new CustomEvent("close"));
1696
- });
1697
- ws.on("error", (err) => {
1698
- const errObj = err instanceof Error ? err : new Error(String(err));
1699
- log.error("S2S WebSocket error", { error: errObj.message });
1700
- if (!opened) {
1701
- reject(errObj);
1702
- } else {
1703
- target.dispatchEvent(
1704
- new CustomEvent("error", {
1705
- detail: { code: "ws_error", message: errObj.message }
1706
- })
1707
- );
1338
+ }),
1339
+ forget_memory: tool({
1340
+ description: "Delete a previously saved memory by its key.",
1341
+ parameters: z4.object({
1342
+ key: z4.string().describe("The key to delete")
1343
+ }),
1344
+ execute: async ({ key }, ctx) => {
1345
+ await ctx.kv.delete(key);
1346
+ return { deleted: key };
1708
1347
  }
1709
- });
1710
- });
1348
+ })
1349
+ };
1711
1350
  }
1712
- var WS_OPEN, S2sServerMessageSchema;
1713
- var init_s2s = __esm({
1714
- "sdk/s2s.ts"() {
1351
+ var FETCH_TIMEOUT_MS, RUN_CODE_TIMEOUT, webSearchParams, BRAVE_SEARCH_URL, BraveSearchResponseSchema, MAX_PAGE_CHARS, MAX_HTML_BYTES, visitWebpageParams, fetchJsonParams, runCodeParams, vectorSearchParams, TOOL_REGISTRY;
1352
+ var init_builtin_tools = __esm({
1353
+ "sdk/builtin_tools.ts"() {
1715
1354
  "use strict";
1716
- init_runtime();
1717
- WS_OPEN = 1;
1718
- S2sServerMessageSchema = z5.discriminatedUnion("type", [
1719
- z5.object({ type: z5.literal("session.ready"), session_id: z5.string() }),
1720
- z5.object({ type: z5.literal("session.updated") }).passthrough(),
1721
- z5.object({ type: z5.literal("input.speech.started") }),
1722
- z5.object({ type: z5.literal("input.speech.stopped") }),
1723
- z5.object({ type: z5.literal("transcript.user.delta"), text: z5.string() }),
1724
- z5.object({
1725
- type: z5.literal("transcript.user"),
1726
- item_id: z5.string(),
1727
- text: z5.string()
1728
- }),
1729
- z5.object({ type: z5.literal("reply.started"), reply_id: z5.string() }),
1730
- // reply.audio is handled on the fast path before Zod — see message handler.
1731
- z5.object({ type: z5.literal("transcript.agent.delta"), delta: z5.string() }).passthrough(),
1732
- z5.object({ type: z5.literal("transcript.agent"), text: z5.string() }),
1733
- z5.object({ type: z5.literal("reply.content_part.started") }).passthrough(),
1734
- z5.object({ type: z5.literal("reply.content_part.done") }).passthrough(),
1735
- z5.object({
1736
- type: z5.literal("tool.call"),
1737
- call_id: z5.string(),
1738
- name: z5.string(),
1739
- args: z5.record(z5.string(), z5.unknown()).optional().default({})
1740
- }),
1741
- z5.object({
1742
- type: z5.literal("reply.done"),
1743
- status: z5.string().optional()
1744
- }),
1745
- z5.object({
1746
- type: z5.literal("session.error"),
1747
- code: z5.string(),
1748
- message: z5.string()
1749
- }),
1750
- // Connection-level error (bare "error" without "session." prefix).
1751
- z5.object({
1752
- type: z5.literal("error"),
1753
- message: z5.string()
1754
- })
1755
- ]);
1355
+ init_internal_types();
1356
+ init_utils();
1357
+ init_types();
1358
+ FETCH_TIMEOUT_MS = 15e3;
1359
+ RUN_CODE_TIMEOUT = 5e3;
1360
+ webSearchParams = z4.object({
1361
+ query: z4.string().describe("The search query"),
1362
+ max_results: z4.number().describe("Maximum number of results to return (default 5)").optional()
1363
+ });
1364
+ BRAVE_SEARCH_URL = "https://api.search.brave.com/res/v1/web/search";
1365
+ BraveSearchResponseSchema = z4.object({
1366
+ web: z4.object({
1367
+ results: z4.array(
1368
+ z4.object({
1369
+ title: z4.string(),
1370
+ url: z4.string(),
1371
+ description: z4.string()
1372
+ })
1373
+ )
1374
+ }).optional()
1375
+ });
1376
+ MAX_PAGE_CHARS = 1e4;
1377
+ MAX_HTML_BYTES = 2e5;
1378
+ visitWebpageParams = z4.object({
1379
+ url: z4.string().describe("The full URL to fetch (e.g., 'https://example.com/page')")
1380
+ });
1381
+ fetchJsonParams = z4.object({
1382
+ url: z4.string().describe("The URL to fetch JSON from"),
1383
+ headers: z4.record(z4.string(), z4.string()).describe("Optional HTTP headers to include in the request").optional()
1384
+ });
1385
+ runCodeParams = z4.object({
1386
+ code: z4.string().describe("JavaScript code to execute. Use console.log() for output.")
1387
+ });
1388
+ vectorSearchParams = z4.object({
1389
+ query: z4.string().describe(
1390
+ 'Short keyword query to search the knowledge base. Use specific topic terms, not full sentences. Do NOT include the company or product name since all documents are from the same source. For example, if the user asks "how much does Acme cost", search for "pricing plans rates".'
1391
+ ),
1392
+ topK: z4.number().describe("Maximum results to return (default: 5)").optional()
1393
+ });
1394
+ TOOL_REGISTRY = {
1395
+ web_search: { create: (opts) => createWebSearch(opts?.fetch) },
1396
+ visit_webpage: { create: (opts) => createVisitWebpage(opts?.fetch) },
1397
+ fetch_json: { create: (opts) => createFetchJson(opts?.fetch) },
1398
+ run_code: { create: createRunCode },
1399
+ vector_search: { create: (opts) => createVectorSearch(opts?.vectorSearch ?? (async () => "")) },
1400
+ memory: { multi: memoryTools }
1401
+ };
1756
1402
  }
1757
1403
  });
1758
1404
 
1759
- // sdk/system_prompt.ts
1760
- function buildSystemPrompt(config, opts) {
1761
- const { hasTools } = opts;
1762
- const agentInstructions = config.instructions && config.instructions !== DEFAULT_INSTRUCTIONS ? `
1763
-
1764
- Agent-Specific Instructions:
1765
- ${config.instructions}` : "";
1766
- const toolPreamble = hasTools ? '\n\nWhen you decide to use a tool, ALWAYS say a brief natural phrase BEFORE the tool call (e.g. "Let me look that up" or "One moment while I check"). This fills silence while the tool executes. Keep preambles to one short sentence.' : "";
1767
- const today = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
1768
- weekday: "long",
1769
- year: "numeric",
1770
- month: "long",
1771
- day: "numeric"
1772
- });
1773
- return DEFAULT_INSTRUCTIONS + `
1774
-
1775
- Today's date is ${today}.` + agentInstructions + toolPreamble + (opts?.voice ? VOICE_RULES : "");
1405
+ // sdk/kv.ts
1406
+ function sortAndPaginate(entries, options) {
1407
+ entries.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
1408
+ if (options?.reverse) entries.reverse();
1409
+ if (options?.limit && options.limit > 0) {
1410
+ entries.length = Math.min(entries.length, options.limit);
1411
+ }
1412
+ return entries;
1413
+ }
1414
+ function createMemoryKv() {
1415
+ const store = /* @__PURE__ */ new Map();
1416
+ function isExpired(entry) {
1417
+ return entry.expiresAt !== void 0 && entry.expiresAt <= Date.now();
1418
+ }
1419
+ return {
1420
+ get(key) {
1421
+ const entry = store.get(key);
1422
+ if (!entry || isExpired(entry)) {
1423
+ if (entry) store.delete(key);
1424
+ return Promise.resolve(null);
1425
+ }
1426
+ return Promise.resolve(JSON.parse(entry.raw));
1427
+ },
1428
+ set(key, value, options) {
1429
+ const raw = JSON.stringify(value);
1430
+ if (raw.length > MAX_VALUE_SIZE) {
1431
+ throw new Error(`Value exceeds max size of ${MAX_VALUE_SIZE} bytes`);
1432
+ }
1433
+ const expireIn = options?.expireIn;
1434
+ const entry = { raw };
1435
+ if (expireIn && expireIn > 0) {
1436
+ entry.expiresAt = Date.now() + expireIn;
1437
+ }
1438
+ store.set(key, entry);
1439
+ return Promise.resolve();
1440
+ },
1441
+ delete(key) {
1442
+ store.delete(key);
1443
+ return Promise.resolve();
1444
+ },
1445
+ async list(prefix, options) {
1446
+ const now = Date.now();
1447
+ const entries = [];
1448
+ let i = 0;
1449
+ for (const [key, entry] of store) {
1450
+ if (++i % 500 === 0) await new Promise((r) => setTimeout(r, 0));
1451
+ if (entry.expiresAt && entry.expiresAt <= now) {
1452
+ store.delete(key);
1453
+ continue;
1454
+ }
1455
+ if (key.startsWith(prefix)) {
1456
+ entries.push({ key, value: JSON.parse(entry.raw) });
1457
+ }
1458
+ }
1459
+ return sortAndPaginate(entries, options);
1460
+ }
1461
+ };
1776
1462
  }
1777
- var VOICE_RULES;
1778
- var init_system_prompt = __esm({
1779
- "sdk/system_prompt.ts"() {
1463
+ var MAX_VALUE_SIZE;
1464
+ var init_kv = __esm({
1465
+ "sdk/kv.ts"() {
1780
1466
  "use strict";
1781
- init_types();
1782
- VOICE_RULES = '\n\nCRITICAL OUTPUT RULES \u2014 you MUST follow these for EVERY response:\nYour response will be spoken aloud by a TTS system and displayed as plain text.\n- NEVER use markdown: no **, no *, no _, no #, no `, no [](), no ---\n- NEVER use bullet points (-, *, \u2022) or numbered lists (1., 2.)\n- NEVER use code blocks or inline code\n- NEVER mention tools, search, APIs, or technical failures to the user. If a tool returns no results, just answer naturally without explaining why.\n- Write exactly as you would say it out loud to a friend\n- Use short conversational sentences. To list things, say "First," "Next," "Finally,"\n- Keep responses concise \u2014 1 to 3 sentences max';
1467
+ MAX_VALUE_SIZE = 65536;
1783
1468
  }
1784
1469
  });
1785
1470
 
@@ -1813,7 +1498,6 @@ function createS2sSession(opts) {
1813
1498
  }));
1814
1499
  let s2s = null;
1815
1500
  const sessionAbort = new AbortController();
1816
- let audioReady = false;
1817
1501
  let toolCallCount = 0;
1818
1502
  let turnPromise = null;
1819
1503
  let conversationMessages = [];
@@ -1825,36 +1509,21 @@ function createS2sSession(opts) {
1825
1509
  if (!hookInvoker) return null;
1826
1510
  try {
1827
1511
  return await hookInvoker.resolveTurnConfig(id, HOOK_TIMEOUT_MS);
1828
- } catch {
1512
+ } catch (err) {
1513
+ log.warn("resolveTurnConfig hook failed", { err: errorMessage(err) });
1829
1514
  return null;
1830
1515
  }
1831
1516
  }
1832
1517
  function invokeHook(hook, arg) {
1833
1518
  if (!hookInvoker) return;
1834
- const run = async () => {
1835
- switch (hook) {
1836
- case "onConnect":
1837
- await hookInvoker.onConnect(id, HOOK_TIMEOUT_MS);
1838
- break;
1839
- case "onDisconnect":
1840
- await hookInvoker.onDisconnect(id, HOOK_TIMEOUT_MS);
1841
- break;
1842
- case "onTurn":
1843
- await hookInvoker.onTurn(id, arg, HOOK_TIMEOUT_MS);
1844
- break;
1845
- case "onError":
1846
- await hookInvoker.onError(id, arg, HOOK_TIMEOUT_MS);
1847
- break;
1848
- case "onStep":
1849
- await hookInvoker.onStep(id, arg, HOOK_TIMEOUT_MS);
1850
- break;
1851
- }
1852
- };
1853
- run().catch((err) => {
1854
- log.warn(`${hook} hook failed`, {
1855
- err: err instanceof Error ? err.message : String(err)
1519
+ try {
1520
+ const h = hookInvoker[hook];
1521
+ Promise.resolve(h.call(hookInvoker, id, arg, HOOK_TIMEOUT_MS)).catch((err) => {
1522
+ log.warn(`${hook} hook failed`, { err: errorMessage(err) });
1856
1523
  });
1857
- });
1524
+ } catch (err) {
1525
+ log.warn(`${hook} hook failed`, { err: errorMessage(err) });
1526
+ }
1858
1527
  }
1859
1528
  function checkTurnLimits(turnConfig, name) {
1860
1529
  const maxSteps = turnConfig?.maxSteps ?? agentConfig.maxSteps;
@@ -1894,7 +1563,7 @@ function createS2sSession(opts) {
1894
1563
  try {
1895
1564
  result = await executeTool(name, parsedArgs, id, conversationMessages);
1896
1565
  } catch (err) {
1897
- const msg = err instanceof Error ? err.message : String(err);
1566
+ const msg = errorMessage(err);
1898
1567
  log.error("Tool execution failed", { tool: name, error: msg });
1899
1568
  result = JSON.stringify({ error: msg });
1900
1569
  }
@@ -1909,6 +1578,79 @@ function createS2sSession(opts) {
1909
1578
  function on(target, event, handler) {
1910
1579
  target.addEventListener(event, handler);
1911
1580
  }
1581
+ function setupListeners(handle) {
1582
+ on(handle, "ready", (e) => {
1583
+ s2sSessionId = e.detail.session_id;
1584
+ log.info("S2S session ready", { session_id: s2sSessionId });
1585
+ });
1586
+ on(handle, "session_expired", () => {
1587
+ log.info("S2S session expired, reconnecting fresh");
1588
+ s2sSessionId = null;
1589
+ handle.close();
1590
+ });
1591
+ for (const type of ["speech_started", "speech_stopped"]) {
1592
+ handle.addEventListener(type, () => client.event({ type }));
1593
+ }
1594
+ on(handle, "user_transcript_delta", (e) => {
1595
+ client.event({ type: "transcript", text: e.detail.text, isFinal: false });
1596
+ });
1597
+ on(handle, "user_transcript", (e) => {
1598
+ const { text } = e.detail;
1599
+ log.info("S2S user transcript", { text });
1600
+ client.event({ type: "transcript", text, isFinal: true });
1601
+ client.event({ type: "turn", text });
1602
+ conversationMessages.push({ role: "user", content: text });
1603
+ invokeHook("onTurn", text);
1604
+ });
1605
+ handle.addEventListener("reply_started", () => {
1606
+ toolCallCount = 0;
1607
+ });
1608
+ on(handle, "audio", (e) => {
1609
+ client.playAudioChunk(e.detail.audio);
1610
+ });
1611
+ on(handle, "agent_transcript_delta", (e) => {
1612
+ client.event({ type: "chat_delta", text: e.detail.text });
1613
+ });
1614
+ on(handle, "agent_transcript", (e) => {
1615
+ const { text } = e.detail;
1616
+ client.event({ type: "chat", text });
1617
+ conversationMessages.push({ role: "assistant", content: text });
1618
+ });
1619
+ on(handle, "tool_call", (e) => {
1620
+ const p = handleToolCall(e.detail).catch((err) => {
1621
+ log.error("Tool call handler failed", { err: errorMessage(err) });
1622
+ });
1623
+ turnPromise = (turnPromise ?? Promise.resolve()).then(() => p);
1624
+ });
1625
+ on(handle, "reply_done", (e) => {
1626
+ if (e.detail.status === "interrupted") {
1627
+ log.info("S2S reply interrupted (barge-in)");
1628
+ pendingTools = [];
1629
+ client.event({ type: "cancelled" });
1630
+ } else if (pendingTools.length > 0) {
1631
+ for (const tool2 of pendingTools) s2s?.sendToolResult(tool2.call_id, tool2.result);
1632
+ pendingTools = [];
1633
+ } else {
1634
+ client.playAudioDone();
1635
+ client.event({ type: "tts_done" });
1636
+ }
1637
+ });
1638
+ on(handle, "error", (e) => {
1639
+ log.error("S2S error", { code: e.detail.code, message: e.detail.message });
1640
+ client.event({ type: "error", code: "internal", message: e.detail.message });
1641
+ handle.close();
1642
+ });
1643
+ handle.addEventListener("close", () => {
1644
+ log.info("S2S closed");
1645
+ s2s = null;
1646
+ if (!sessionAbort.signal.aborted) {
1647
+ log.info("Attempting S2S reconnect");
1648
+ connectAndSetup().catch((err) => {
1649
+ log.error("S2S reconnect failed", { error: errorMessage(err) });
1650
+ });
1651
+ }
1652
+ });
1653
+ }
1912
1654
  async function connectAndSetup() {
1913
1655
  if (connecting) {
1914
1656
  pendingReconnect = true;
@@ -1916,106 +1658,15 @@ function createS2sSession(opts) {
1916
1658
  }
1917
1659
  connecting = true;
1918
1660
  try {
1919
- const handle = await _internals2.connectS2s({
1661
+ const handle = await _internals.connectS2s({
1920
1662
  apiKey,
1921
1663
  config: s2sConfig,
1922
1664
  createWebSocket,
1923
1665
  logger: log
1924
1666
  });
1925
- on(handle, "ready", (e) => {
1926
- s2sSessionId = e.detail.session_id;
1927
- log.info("S2S session ready", { session_id: s2sSessionId });
1928
- });
1929
- on(handle, "session_expired", () => {
1930
- log.info("S2S session expired, reconnecting fresh");
1931
- s2sSessionId = null;
1932
- handle.close();
1933
- });
1934
- handle.addEventListener("speech_started", () => {
1935
- client.event({ type: "speech_started" });
1936
- });
1937
- handle.addEventListener("speech_stopped", () => {
1938
- client.event({ type: "speech_stopped" });
1939
- });
1940
- on(handle, "user_transcript_delta", (e) => {
1941
- client.event({
1942
- type: "transcript",
1943
- text: e.detail.text,
1944
- isFinal: false
1945
- });
1946
- });
1947
- on(handle, "user_transcript", (e) => {
1948
- const { text } = e.detail;
1949
- log.info("S2S user transcript", { text });
1950
- client.event({ type: "transcript", text, isFinal: true });
1951
- client.event({ type: "turn", text });
1952
- conversationMessages.push({ role: "user", content: text });
1953
- invokeHook("onTurn", text);
1954
- });
1955
- handle.addEventListener("reply_started", () => {
1956
- toolCallCount = 0;
1957
- });
1958
- on(handle, "audio", (e) => {
1959
- client.playAudioChunk(e.detail.audio);
1960
- });
1961
- on(handle, "agent_transcript_delta", (e) => {
1962
- client.event({ type: "chat_delta", text: e.detail.text });
1963
- });
1964
- on(handle, "agent_transcript", (e) => {
1965
- const { text } = e.detail;
1966
- client.event({ type: "chat", text });
1967
- conversationMessages.push({ role: "assistant", content: text });
1968
- });
1969
- on(handle, "tool_call", (e) => {
1970
- const p = handleToolCall(e.detail).catch((err) => {
1971
- log.error("Tool call handler failed", {
1972
- err: err instanceof Error ? err.message : String(err)
1973
- });
1974
- });
1975
- turnPromise = (turnPromise ?? Promise.resolve()).then(() => p);
1976
- });
1977
- on(handle, "reply_done", (e) => {
1978
- if (e.detail.status === "interrupted") {
1979
- log.info("S2S reply interrupted (barge-in)");
1980
- pendingTools = [];
1981
- client.event({ type: "cancelled" });
1982
- } else if (pendingTools.length > 0) {
1983
- for (const tool2 of pendingTools) {
1984
- s2s?.sendToolResult(tool2.call_id, tool2.result);
1985
- }
1986
- pendingTools = [];
1987
- } else {
1988
- client.playAudioDone();
1989
- client.event({ type: "tts_done" });
1990
- }
1991
- });
1992
- on(handle, "error", (e) => {
1993
- log.error("S2S error", {
1994
- code: e.detail.code,
1995
- message: e.detail.message
1996
- });
1997
- client.event({
1998
- type: "error",
1999
- code: "internal",
2000
- message: e.detail.message
2001
- });
2002
- handle.close();
2003
- });
2004
- handle.addEventListener("close", () => {
2005
- log.info("S2S closed");
2006
- s2s = null;
2007
- if (!sessionAbort.signal.aborted) {
2008
- log.info("Attempting S2S reconnect");
2009
- connectAndSetup().catch((err) => {
2010
- const msg = err instanceof Error ? err.message : String(err);
2011
- log.error("S2S reconnect failed", { error: msg });
2012
- });
2013
- }
2014
- });
1667
+ setupListeners(handle);
2015
1668
  if (s2sSessionId) {
2016
- log.info("Attempting S2S session resume", {
2017
- session_id: s2sSessionId
2018
- });
1669
+ log.info("Attempting S2S session resume", { session_id: s2sSessionId });
2019
1670
  handle.resumeSession(s2sSessionId);
2020
1671
  } else {
2021
1672
  handle.updateSession({
@@ -2026,7 +1677,7 @@ function createS2sSession(opts) {
2026
1677
  }
2027
1678
  s2s = handle;
2028
1679
  } catch (err) {
2029
- const msg = err instanceof Error ? err.message : String(err);
1680
+ const msg = errorMessage(err);
2030
1681
  log.error("S2S connect failed", { error: msg });
2031
1682
  client.event({ type: "error", code: "internal", message: msg });
2032
1683
  } finally {
@@ -2034,8 +1685,7 @@ function createS2sSession(opts) {
2034
1685
  if (pendingReconnect && !sessionAbort.signal.aborted) {
2035
1686
  pendingReconnect = false;
2036
1687
  connectAndSetup().catch((err) => {
2037
- const msg = err instanceof Error ? err.message : String(err);
2038
- log.error("S2S deferred reconnect failed", { error: msg });
1688
+ log.error("S2S deferred reconnect failed", { error: errorMessage(err) });
2039
1689
  });
2040
1690
  }
2041
1691
  }
@@ -2059,8 +1709,6 @@ function createS2sSession(opts) {
2059
1709
  s2s?.sendAudio(data);
2060
1710
  },
2061
1711
  onAudioReady() {
2062
- if (audioReady) return;
2063
- audioReady = true;
2064
1712
  },
2065
1713
  onCancel() {
2066
1714
  client.event({ type: "cancelled" });
@@ -2084,17 +1732,36 @@ function createS2sSession(opts) {
2084
1732
  }
2085
1733
  };
2086
1734
  }
2087
- var _internals2;
1735
+ function buildSystemPrompt(config, opts) {
1736
+ const { hasTools } = opts;
1737
+ const agentInstructions = config.instructions && config.instructions !== DEFAULT_INSTRUCTIONS ? `
1738
+
1739
+ Agent-Specific Instructions:
1740
+ ${config.instructions}` : "";
1741
+ const toolPreamble = hasTools ? '\n\nWhen you decide to use a tool, ALWAYS say a brief natural phrase BEFORE the tool call (e.g. "Let me look that up" or "One moment while I check"). This fills silence while the tool executes. Keep preambles to one short sentence.' : "";
1742
+ const today = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
1743
+ weekday: "long",
1744
+ year: "numeric",
1745
+ month: "long",
1746
+ day: "numeric"
1747
+ });
1748
+ return DEFAULT_INSTRUCTIONS + `
1749
+
1750
+ Today's date is ${today}.` + agentInstructions + toolPreamble + (opts.voice ? VOICE_RULES : "");
1751
+ }
1752
+ var _internals, VOICE_RULES;
2088
1753
  var init_session = __esm({
2089
1754
  "sdk/session.ts"() {
2090
1755
  "use strict";
1756
+ init_utils();
2091
1757
  init_protocol();
2092
1758
  init_runtime();
2093
1759
  init_s2s();
2094
- init_system_prompt();
2095
- _internals2 = {
1760
+ init_types();
1761
+ _internals = {
2096
1762
  connectS2s
2097
1763
  };
1764
+ VOICE_RULES = '\n\nCRITICAL OUTPUT RULES \u2014 you MUST follow these for EVERY response:\nYour response will be spoken aloud by a TTS system and displayed as plain text.\n- NEVER use markdown: no **, no *, no _, no #, no `, no [](), no ---\n- NEVER use bullet points (-, *, \u2022) or numbered lists (1., 2.)\n- NEVER use code blocks or inline code\n- NEVER mention tools, search, APIs, or technical failures to the user. If a tool returns no results, just answer naturally without explaining why.\n- Write exactly as you would say it out loud to a friend\n- Use short conversational sentences. To list things, say "First," "Next," "Finally,"\n- Keep responses concise \u2014 1 to 3 sentences max';
2098
1765
  }
2099
1766
  });
2100
1767
 
@@ -2145,11 +1812,10 @@ var init_vector = __esm({
2145
1812
 
2146
1813
  // sdk/worker_entry.ts
2147
1814
  function buildToolContext(opts) {
2148
- const { env, sessionId, state, kv, vector, messages } = opts;
1815
+ const { env, state, kv, vector, messages } = opts;
2149
1816
  return {
2150
- sessionId: sessionId ?? "",
2151
1817
  env: { ...env },
2152
- abortSignal: AbortSignal.timeout(TOOL_HANDLER_TIMEOUT),
1818
+ abortSignal: AbortSignal.timeout(TOOL_EXECUTION_TIMEOUT_MS),
2153
1819
  state: state ?? {},
2154
1820
  get kv() {
2155
1821
  if (!kv) throw new Error("KV not available");
@@ -2178,21 +1844,18 @@ async function executeToolCall(name, args, options) {
2178
1844
  if (result == null) return "null";
2179
1845
  return typeof result === "string" ? result : JSON.stringify(result);
2180
1846
  } catch (err) {
2181
- if (err instanceof DOMException && err.name === "TimeoutError") {
2182
- console.warn(`[tool-executor] Tool execution timed out: ${name}`);
2183
- return `Error: Tool "${name}" timed out after ${TOOL_HANDLER_TIMEOUT}ms`;
2184
- }
2185
1847
  console.warn(`[tool-executor] Tool execution failed: ${name}`, err);
2186
- return `Error: ${err instanceof Error ? err.message : String(err)}`;
1848
+ return `Error: ${errorMessage(err)}`;
2187
1849
  }
2188
1850
  }
2189
- var yieldTick, TOOL_HANDLER_TIMEOUT;
1851
+ var yieldTick;
2190
1852
  var init_worker_entry = __esm({
2191
1853
  "sdk/worker_entry.ts"() {
2192
1854
  "use strict";
2193
1855
  init_internal_types();
1856
+ init_utils();
1857
+ init_protocol();
2194
1858
  yieldTick = () => new Promise((r) => setTimeout(r, 0));
2195
- TOOL_HANDLER_TIMEOUT = 3e4;
2196
1859
  }
2197
1860
  });
2198
1861
 
@@ -2244,7 +1907,6 @@ function createDirectExecutor(opts) {
2244
1907
  }
2245
1908
  function makeHookContext(sessionId) {
2246
1909
  return {
2247
- sessionId,
2248
1910
  env: frozenEnv,
2249
1911
  state: getState(sessionId),
2250
1912
  get kv() {
@@ -2261,7 +1923,6 @@ function createDirectExecutor(opts) {
2261
1923
  return executeToolCall(name, args, {
2262
1924
  tool: tool2,
2263
1925
  env: frozenEnv,
2264
- sessionId,
2265
1926
  state: getState(sessionId ?? ""),
2266
1927
  kv,
2267
1928
  vector,
@@ -2340,9 +2001,6 @@ var init_direct_executor = __esm({
2340
2001
  });
2341
2002
 
2342
2003
  // sdk/ws_handler.ts
2343
- function isValidAudioChunk(data) {
2344
- return data.byteLength > 0 && data.byteLength <= MAX_AUDIO_CHUNK_BYTES && data.byteLength % 2 === 0;
2345
- }
2346
2004
  function createClientSink(ws) {
2347
2005
  function safeSend(data) {
2348
2006
  try {
@@ -2374,19 +2032,9 @@ function toUint8Array(data) {
2374
2032
  const buf = data;
2375
2033
  return new Uint8Array(buf.buffer ?? data, buf.byteOffset ?? 0, buf.byteLength);
2376
2034
  }
2377
- function handleBinaryAudio(data, session, log, ctx, sid) {
2035
+ function handleBinaryAudio(data, session) {
2378
2036
  if (!isBinaryData(data)) return false;
2379
- const chunk = toUint8Array(data);
2380
- if (!isValidAudioChunk(chunk)) {
2381
- log.warn("Invalid audio chunk, dropping", {
2382
- ...ctx,
2383
- sid,
2384
- bytes: chunk.byteLength,
2385
- aligned: chunk.byteLength % 2 === 0
2386
- });
2387
- return true;
2388
- }
2389
- session.onAudio(chunk);
2037
+ session.onAudio(toUint8Array(data));
2390
2038
  return true;
2391
2039
  }
2392
2040
  function handleTextMessage(data, session, log, ctx, sid) {
@@ -2432,8 +2080,11 @@ function wireSessionSocket(ws, opts) {
2432
2080
  session = opts.createSession(sessionId, client);
2433
2081
  sessions.set(sessionId, session);
2434
2082
  ws.send(JSON.stringify({ type: "config", ...opts.readyConfig }));
2435
- void session.start();
2436
- log.info("Session ready", { ...ctx, sid });
2083
+ session.start().then(() => {
2084
+ log.info("Session ready", { ...ctx, sid });
2085
+ }).catch((err) => {
2086
+ log.error("Session start failed", { ...ctx, sid, error: errorMessage(err) });
2087
+ });
2437
2088
  }
2438
2089
  if (ws.readyState === 1) {
2439
2090
  onOpen();
@@ -2443,7 +2094,7 @@ function wireSessionSocket(ws, opts) {
2443
2094
  ws.addEventListener("message", (event) => {
2444
2095
  if (!session) return;
2445
2096
  const { data } = event;
2446
- if (handleBinaryAudio(data, session, log, ctx, sid)) return;
2097
+ if (handleBinaryAudio(data, session)) return;
2447
2098
  handleTextMessage(data, session, log, ctx, sid);
2448
2099
  });
2449
2100
  ws.addEventListener("close", () => {
@@ -2460,17 +2111,17 @@ function wireSessionSocket(ws, opts) {
2460
2111
  log.error("WebSocket error", { ...ctx, sid, error: msg });
2461
2112
  });
2462
2113
  }
2463
- var MAX_AUDIO_CHUNK_BYTES;
2464
2114
  var init_ws_handler = __esm({
2465
2115
  "sdk/ws_handler.ts"() {
2466
2116
  "use strict";
2117
+ init_utils();
2467
2118
  init_protocol();
2468
2119
  init_runtime();
2469
- MAX_AUDIO_CHUNK_BYTES = 1048576;
2470
2120
  }
2471
2121
  });
2472
2122
 
2473
2123
  // sdk/winterc_server.ts
2124
+ import { Hono } from "hono";
2474
2125
  function createWintercServer(options) {
2475
2126
  const {
2476
2127
  agent,
@@ -2500,26 +2151,19 @@ function createWintercServer(options) {
2500
2151
  sampleRate: s2sConfig.inputSampleRate,
2501
2152
  ttsSampleRate: s2sConfig.outputSampleRate
2502
2153
  };
2154
+ const app = new Hono();
2155
+ app.get("/health", (c) => c.json({ status: "ok", name: agent.name }));
2156
+ app.get("/", (c) => {
2157
+ if (clientHtml) {
2158
+ return c.html(clientHtml);
2159
+ }
2160
+ return c.html(
2161
+ `<!DOCTYPE html><html><body><h1>${agent.name}</h1><p>Agent server running.</p></body></html>`
2162
+ );
2163
+ });
2503
2164
  return {
2504
2165
  async fetch(request) {
2505
- const url = new URL(request.url);
2506
- if (url.pathname === "/health") {
2507
- return new Response(JSON.stringify({ status: "ok", name: agent.name }), {
2508
- headers: { "Content-Type": "application/json" }
2509
- });
2510
- }
2511
- if (url.pathname === "/" && clientHtml) {
2512
- return new Response(clientHtml, {
2513
- headers: { "Content-Type": "text/html" }
2514
- });
2515
- }
2516
- if (url.pathname === "/") {
2517
- return new Response(
2518
- `<!DOCTYPE html><html><body><h1>${agent.name}</h1><p>Agent server running.</p></body></html>`,
2519
- { headers: { "Content-Type": "text/html" } }
2520
- );
2521
- }
2522
- return new Response("Not Found", { status: 404 });
2166
+ return app.fetch(request);
2523
2167
  },
2524
2168
  handleWebSocket(ws, wsOpts) {
2525
2169
  wireSessionSocket(ws, {
@@ -2559,11 +2203,14 @@ var server_exports = {};
2559
2203
  __export(server_exports, {
2560
2204
  createServer: () => createServer
2561
2205
  });
2206
+ import { serve } from "@hono/node-server";
2207
+ import { serveStatic } from "@hono/node-server/serve-static";
2208
+ import { Hono as Hono2 } from "hono";
2562
2209
  async function loadWsFactory() {
2563
2210
  try {
2564
2211
  const mod = await import("ws");
2565
2212
  const WS = mod.default ?? mod;
2566
- return (url, opts) => new WS(url, { headers: opts.headers });
2213
+ return (url, opts) => wrapOnStyleWebSocket(new WS(url, { headers: opts.headers }));
2567
2214
  } catch {
2568
2215
  throw new Error(
2569
2216
  "WebSocket factory not provided and `ws` package not found. Install `ws` (`npm install ws`) or pass `createWebSocket` option."
@@ -2571,11 +2218,7 @@ async function loadWsFactory() {
2571
2218
  }
2572
2219
  }
2573
2220
  function resolveEnv(env) {
2574
- const resolved = {};
2575
- for (const [key, value] of Object.entries(env)) {
2576
- if (value !== void 0) resolved[key] = value;
2577
- }
2578
- return resolved;
2221
+ return Object.fromEntries(Object.entries(env).filter(([, v]) => v !== void 0));
2579
2222
  }
2580
2223
  function createServer(options) {
2581
2224
  const {
@@ -2620,191 +2263,91 @@ function createServer(options) {
2620
2263
  },
2621
2264
  async listen(port = 3e3) {
2622
2265
  await getWsFactory();
2623
- const http = await import("node:http");
2624
- const nodeServer = http.createServer(async (req, res) => {
2625
- if (clientDir && req.url) {
2626
- const served = await serveStaticFile(req.url, clientDir, res);
2627
- if (served) return;
2266
+ const wintercServer = getWinterc();
2267
+ const app = new Hono2();
2268
+ app.onError((err, c) => {
2269
+ logger.error(`${c.req.method} ${new URL(c.req.url).pathname} error: ${err.message}`);
2270
+ return c.json({ error: "Internal Server Error" }, 500);
2271
+ });
2272
+ app.use("/*", async (c, next) => {
2273
+ const start = Date.now();
2274
+ await next();
2275
+ const ms = Date.now() - start;
2276
+ const status = c.res.status;
2277
+ const method = c.req.method;
2278
+ const path8 = new URL(c.req.url).pathname;
2279
+ if (status >= 400) {
2280
+ logger.error(`${method} ${path8} ${status} ${ms}ms`);
2281
+ } else {
2282
+ logger.info(`${method} ${path8} ${status} ${ms}ms`);
2628
2283
  }
2629
- await nodeHttpHandler(req, res, port, getWinterc);
2630
2284
  });
2631
- attachWsUpgrade(nodeServer, port, getWinterc, logger);
2285
+ if (clientDir) {
2286
+ app.use("/*", serveStatic({ root: clientDir }));
2287
+ }
2288
+ app.all("/*", (c) => wintercServer.fetch(c.req.raw));
2289
+ const nodeServer = serve({ fetch: app.fetch, port });
2632
2290
  await new Promise((resolve) => {
2633
- nodeServer.listen(port, () => resolve());
2291
+ nodeServer.on("listening", resolve);
2634
2292
  });
2635
- serverHandle = {
2636
- async shutdown() {
2637
- await new Promise((resolve, reject) => {
2638
- nodeServer.close((err) => err ? reject(err) : resolve());
2293
+ try {
2294
+ const wsMod = await import("ws");
2295
+ const wss = new wsMod.WebSocketServer({ noServer: true });
2296
+ nodeServer.on("upgrade", (req, socket, head) => {
2297
+ const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
2298
+ const resume = reqUrl.searchParams.has("resume");
2299
+ logger.info(`WS upgrade ${reqUrl.pathname}${resume ? " (resume)" : ""}`);
2300
+ wss.handleUpgrade(req, socket, head, (ws) => {
2301
+ wintercServer.handleWebSocket(ws, {
2302
+ skipGreeting: resume
2303
+ });
2639
2304
  });
2640
- }
2641
- };
2642
- },
2643
- async close() {
2644
- await winterc?.close();
2645
- await serverHandle?.shutdown();
2646
- }
2647
- };
2648
- }
2649
- async function serveStaticFile(reqUrl, clientDir, res) {
2650
- const { readFile } = await import("node:fs/promises");
2651
- const { join, extname, normalize } = await import("node:path");
2652
- const urlPath = new URL(reqUrl, "http://localhost").pathname;
2653
- const relPath = urlPath === "/" ? "index.html" : urlPath.slice(1);
2654
- const filePath = normalize(join(clientDir, relPath));
2655
- if (!filePath.startsWith(clientDir)) return false;
2656
- try {
2657
- const content = await readFile(filePath);
2658
- const ext = extname(filePath);
2659
- const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2660
- res.writeHead(200, { "Content-Type": contentType });
2661
- res.end(content);
2662
- return true;
2663
- } catch {
2664
- return false;
2665
- }
2666
- }
2667
- async function nodeHttpHandler(req, res, port, getWinterc) {
2668
- try {
2669
- const protocol = req.socket.encrypted ? "https" : "http";
2670
- const host = req.headers.host ?? `localhost:${port}`;
2671
- const url = new URL(req.url ?? "/", `${protocol}://${host}`);
2672
- const headers = new Headers();
2673
- for (const [key, val] of Object.entries(req.headers)) {
2674
- if (val) headers.set(key, Array.isArray(val) ? val[0] ?? "" : val);
2675
- }
2676
- const request = new Request(url, { method: req.method ?? "GET", headers });
2677
- const response = await getWinterc().fetch(request);
2678
- res.writeHead(response.status, Object.fromEntries(response.headers));
2679
- res.end(await response.text());
2680
- } catch (err) {
2681
- res.writeHead(500);
2682
- res.end(err instanceof Error ? err.message : "Internal Server Error");
2683
- }
2684
- }
2685
- function attachWsUpgrade(nodeServer, port, getWinterc, logger) {
2686
- import("ws").then((wsMod) => {
2687
- const WSServer = wsMod.WebSocketServer;
2688
- if (!WSServer) return;
2689
- const wss = new WSServer({ noServer: true });
2690
- nodeServer.on("upgrade", (req, socket, head) => {
2691
- wss.handleUpgrade(
2692
- req,
2693
- socket,
2694
- head,
2695
- (ws) => {
2696
- const reqUrl = new URL(
2697
- req.url ?? "/",
2698
- `http://localhost:${port}`
2699
- );
2700
- getWinterc().handleWebSocket(ws, {
2701
- skipGreeting: reqUrl.searchParams.has("resume")
2305
+ });
2306
+ } catch {
2307
+ logger.warn("ws package not available for Node.js WebSocket upgrade");
2308
+ }
2309
+ serverHandle = {
2310
+ async shutdown() {
2311
+ await new Promise((resolve, reject) => {
2312
+ nodeServer.close((err) => err ? reject(err) : resolve());
2702
2313
  });
2703
2314
  }
2704
- );
2705
- });
2706
- }).catch(() => {
2707
- logger.warn("ws package not available for Node.js WebSocket upgrade");
2708
- });
2315
+ };
2316
+ },
2317
+ async close() {
2318
+ await winterc?.close();
2319
+ await serverHandle?.shutdown();
2320
+ }
2321
+ };
2709
2322
  }
2710
- var MIME_TYPES;
2711
2323
  var init_server = __esm({
2712
2324
  "sdk/server.ts"() {
2713
2325
  "use strict";
2714
2326
  init_runtime();
2327
+ init_s2s();
2715
2328
  init_winterc_server();
2716
- MIME_TYPES = {
2717
- ".html": "text/html",
2718
- ".js": "application/javascript",
2719
- ".css": "text/css",
2720
- ".json": "application/json",
2721
- ".svg": "image/svg+xml",
2722
- ".png": "image/png",
2723
- ".ico": "image/x-icon",
2724
- ".woff": "font/woff",
2725
- ".woff2": "font/woff2"
2726
- };
2727
2329
  }
2728
2330
  });
2729
2331
 
2730
- // cli/cli.ts
2731
- init_help();
2732
- import { readFileSync } from "node:fs";
2733
- import path11 from "node:path";
2734
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2735
- import minimist8 from "minimist";
2736
-
2737
- // cli/build.tsx
2738
- init_build();
2739
- init_discover();
2740
- init_help();
2741
- init_ink();
2742
- init_init2();
2743
- import path6 from "node:path";
2744
- import minimist3 from "minimist";
2745
- import { jsx as jsx5 } from "react/jsx-runtime";
2746
- var buildCommandDef = {
2747
- name: "build",
2748
- description: "Bundle agent and client (validates code without deploying or starting a server)",
2749
- options: [{ flags: "-y, --yes", description: "Accept defaults (no prompts)" }]
2750
- };
2751
- async function runBuildCommand(args, version) {
2752
- const parsed = minimist3(args, {
2753
- boolean: ["help", "yes"],
2754
- alias: { h: "help", y: "yes" }
2755
- });
2756
- if (parsed.help) {
2757
- console.log(subcommandHelp(buildCommandDef, version));
2758
- return;
2759
- }
2760
- const cwd = process.env.INIT_CWD || process.cwd();
2761
- if (!await fileExists(path6.join(cwd, "agent.ts"))) {
2762
- await runInitCommand(parsed.yes ? ["-y"] : [], version, { quiet: true });
2763
- }
2764
- await runWithInk(async (log) => {
2765
- await buildAgentBundle(cwd, log);
2766
- log(/* @__PURE__ */ jsx5(Step, { action: "Build", msg: "ok" }));
2767
- });
2768
- }
2769
-
2770
- // cli/cli.ts
2771
- init_deploy2();
2772
-
2773
- // cli/dev.tsx
2774
- import path8 from "node:path";
2775
- import minimist4 from "minimist";
2776
-
2777
- // cli/_dev.ts
2778
- init_build();
2779
- init_ink();
2780
- import React3 from "react";
2781
-
2782
2332
  // cli/_server_common.ts
2783
- init_discover();
2784
2333
  import fs5 from "node:fs/promises";
2785
- import path7 from "node:path";
2786
- import { createServer as createViteServer } from "vite";
2334
+ import path5 from "node:path";
2335
+ import { tsImport } from "tsx/esm/api";
2787
2336
  async function loadAgentDef(cwd) {
2788
- const agentPath = path7.resolve(cwd, "agent.ts");
2789
- const vite = await createViteServer({
2790
- root: cwd,
2791
- logLevel: "silent",
2792
- server: { middlewareMode: true }
2793
- });
2794
- try {
2795
- const agentModule = await vite.ssrLoadModule(agentPath);
2796
- const agentDef = agentModule.default;
2797
- if (!agentDef || typeof agentDef !== "object" || !agentDef.name) {
2798
- throw new Error("agent.ts must export a default agent definition (from defineAgent())");
2799
- }
2800
- return agentDef;
2801
- } finally {
2802
- await vite.close();
2337
+ const agentPath = path5.resolve(cwd, "agent.ts");
2338
+ const agentModule = await tsImport(agentPath, cwd);
2339
+ let agentDef = agentModule.default;
2340
+ if (agentDef?.__esModule && agentDef.default) {
2341
+ agentDef = agentDef.default;
2342
+ }
2343
+ if (!agentDef || typeof agentDef !== "object" || !agentDef.name) {
2344
+ throw new Error("agent.ts must export a default agent definition (from defineAgent())");
2803
2345
  }
2346
+ return agentDef;
2804
2347
  }
2805
- async function resolveServerEnv() {
2348
+ async function resolveServerEnv(baseEnv) {
2806
2349
  const env = Object.fromEntries(
2807
- Object.entries(process.env).filter((e) => e[1] !== void 0)
2350
+ Object.entries(baseEnv ?? process.env).filter((e) => e[1] !== void 0)
2808
2351
  );
2809
2352
  if (!env.ASSEMBLYAI_API_KEY) {
2810
2353
  env.ASSEMBLYAI_API_KEY = await getApiKey();
@@ -2812,10 +2355,11 @@ async function resolveServerEnv() {
2812
2355
  return env;
2813
2356
  }
2814
2357
  async function bootServer(agentDef, clientDir, env, port) {
2358
+ const { wrapOnStyleWebSocket: wrapOnStyleWebSocket2 } = await Promise.resolve().then(() => (init_s2s(), s2s_exports));
2815
2359
  const wsMod = await import("ws");
2816
2360
  const WS = wsMod.default ?? wsMod;
2817
- const createWebSocket = (url, opts) => new WS(url, { headers: opts.headers });
2818
- const clientHtml = await fs5.readFile(path7.join(clientDir, "index.html"), "utf-8");
2361
+ const createWebSocket = (url, opts) => wrapOnStyleWebSocket2(new WS(url, { headers: opts.headers }));
2362
+ const clientHtml = await fs5.readFile(path5.join(clientDir, "index.html"), "utf-8");
2819
2363
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
2820
2364
  const server = createServer2({
2821
2365
  agent: agentDef,
@@ -2826,155 +2370,186 @@ async function bootServer(agentDef, clientDir, env, port) {
2826
2370
  });
2827
2371
  await server.listen(port);
2828
2372
  }
2373
+ var init_server_common = __esm({
2374
+ "cli/_server_common.ts"() {
2375
+ "use strict";
2376
+ init_discover();
2377
+ }
2378
+ });
2829
2379
 
2830
- // cli/_dev.ts
2380
+ // cli/dev.tsx
2381
+ var dev_exports = {};
2382
+ __export(dev_exports, {
2383
+ _startDevServer: () => _startDevServer,
2384
+ runDevCommand: () => runDevCommand
2385
+ });
2386
+ import { jsx as jsx6 } from "react/jsx-runtime";
2831
2387
  async function _startDevServer(cwd, port, log) {
2832
- const bundle = await buildAgentBundle(cwd, log);
2388
+ const bundle = await buildAgentBundle(cwd, log, { skipRenderCheck: true });
2833
2389
  const agentDef = await loadAgentDef(cwd);
2834
2390
  const env = await resolveServerEnv();
2835
2391
  await bootServer(agentDef, bundle.clientDir, env, port);
2836
- log(React3.createElement(Step, { action: "Ready", msg: `http://localhost:${port}` }));
2392
+ log(/* @__PURE__ */ jsx6(Step, { action: "Ready", msg: `http://localhost:${port}` }));
2837
2393
  }
2838
-
2839
- // cli/dev.tsx
2840
- init_discover();
2841
- init_help();
2842
- init_ink();
2843
- init_init2();
2844
- var devCommandDef = {
2845
- name: "dev",
2846
- description: "Start a local development server",
2847
- options: [
2848
- {
2849
- flags: "-p, --port <number>",
2850
- description: "Port to listen on (default: 3000)"
2851
- },
2852
- { flags: "-y, --yes", description: "Accept defaults (no prompts)" }
2853
- ]
2854
- };
2855
- async function runDevCommand(args, version) {
2856
- const parsed = minimist4(args, {
2857
- string: ["port"],
2858
- boolean: ["help", "yes"],
2859
- alias: { p: "port", h: "help", y: "yes" }
2394
+ async function runDevCommand(opts) {
2395
+ const port = Number.parseInt(opts.port, 10);
2396
+ await runWithInk(async ({ log }) => {
2397
+ await _startDevServer(opts.cwd, port, log);
2860
2398
  });
2861
- if (parsed.help) {
2862
- console.log(subcommandHelp(devCommandDef, version));
2863
- return;
2399
+ }
2400
+ var init_dev = __esm({
2401
+ "cli/dev.tsx"() {
2402
+ "use strict";
2403
+ init_build();
2404
+ init_ink();
2405
+ init_server_common();
2864
2406
  }
2865
- const cwd = process.env.INIT_CWD || process.cwd();
2866
- const port = Number.parseInt(parsed.port ?? "3000", 10);
2867
- if (!await fileExists(path8.join(cwd, "agent.ts"))) {
2868
- await runInitCommand(parsed.yes ? ["-y"] : [], version, { quiet: true });
2407
+ });
2408
+
2409
+ // cli/start.tsx
2410
+ var start_exports = {};
2411
+ __export(start_exports, {
2412
+ _startProductionServer: () => _startProductionServer,
2413
+ runStartCommand: () => runStartCommand
2414
+ });
2415
+ import path6 from "node:path";
2416
+ import { jsx as jsx7 } from "react/jsx-runtime";
2417
+ async function _startProductionServer(cwd, port, log) {
2418
+ const clientDir = path6.join(cwd, ".aai", "client");
2419
+ log(/* @__PURE__ */ jsx7(Step, { action: "Start", msg: "loading agent" }));
2420
+ const agentDef = await loadAgentDef(cwd);
2421
+ const env = await resolveServerEnv();
2422
+ await bootServer(agentDef, clientDir, env, port);
2423
+ log(/* @__PURE__ */ jsx7(Step, { action: "Ready", msg: `http://localhost:${port}` }));
2424
+ }
2425
+ async function runStartCommand(opts) {
2426
+ const port = Number.parseInt(opts.port, 10);
2427
+ const buildDir = path6.join(opts.cwd, ".aai", "build");
2428
+ if (!await fileExists(path6.join(buildDir, "worker.js"))) {
2429
+ throw new Error("No build found \u2014 run `aai build` first");
2869
2430
  }
2870
- await getApiKey();
2871
- await runWithInk(async (log) => {
2872
- await _startDevServer(cwd, port, log);
2431
+ await runWithInk(async ({ log }) => {
2432
+ log(/* @__PURE__ */ jsx7(Step, { action: "Start", msg: `production server on port ${port}` }));
2433
+ await _startProductionServer(opts.cwd, port, log);
2873
2434
  });
2874
2435
  }
2436
+ var init_start = __esm({
2437
+ "cli/start.tsx"() {
2438
+ "use strict";
2439
+ init_discover();
2440
+ init_ink();
2441
+ init_server_common();
2442
+ }
2443
+ });
2875
2444
 
2876
- // cli/cli.ts
2877
- init_init2();
2445
+ // cli/secret.tsx
2446
+ var secret_exports = {};
2447
+ __export(secret_exports, {
2448
+ runSecretDelete: () => runSecretDelete,
2449
+ runSecretList: () => runSecretList,
2450
+ runSecretPut: () => runSecretPut
2451
+ });
2452
+ import { jsx as jsx8 } from "react/jsx-runtime";
2453
+ async function apiFetch(cwd, pathSuffix, init) {
2454
+ const { serverUrl, slug, apiKey } = await getServerInfo(cwd);
2455
+ const resp = await fetch(`${serverUrl}/${slug}/secret${pathSuffix}`, {
2456
+ ...init,
2457
+ headers: { Authorization: `Bearer ${apiKey}`, ...init?.headers }
2458
+ });
2459
+ if (!resp.ok) {
2460
+ const text = await resp.text();
2461
+ throw new Error(`Secret operation failed: ${text}`);
2462
+ }
2463
+ return { resp, slug };
2464
+ }
2465
+ async function runSecretPut(cwd, name) {
2466
+ const value = await askPassword(`Enter value for ${name}`);
2467
+ if (!value) throw new Error("No value provided");
2468
+ await runWithInk(async ({ log }) => {
2469
+ const { slug } = await apiFetch(cwd, "", {
2470
+ method: "PUT",
2471
+ headers: { "Content-Type": "application/json" },
2472
+ body: JSON.stringify({ [name]: value })
2473
+ });
2474
+ log(/* @__PURE__ */ jsx8(Step, { action: "Set", msg: `${name} for ${slug}` }));
2475
+ });
2476
+ }
2477
+ async function runSecretDelete(cwd, name) {
2478
+ await runWithInk(async ({ log }) => {
2479
+ const { slug } = await apiFetch(cwd, `/${name}`, { method: "DELETE" });
2480
+ log(/* @__PURE__ */ jsx8(Step, { action: "Deleted", msg: `${name} from ${slug}` }));
2481
+ });
2482
+ }
2483
+ async function runSecretList(cwd) {
2484
+ await runWithInk(async ({ log }) => {
2485
+ const { resp } = await apiFetch(cwd, "");
2486
+ const { vars } = await resp.json();
2487
+ if (vars.length === 0) {
2488
+ log(/* @__PURE__ */ jsx8(StepInfo, { action: "Secrets", msg: "none set" }));
2489
+ } else {
2490
+ for (const name of vars) {
2491
+ log(/* @__PURE__ */ jsx8(Detail, { msg: name }));
2492
+ }
2493
+ }
2494
+ });
2495
+ }
2496
+ var init_secret = __esm({
2497
+ "cli/secret.tsx"() {
2498
+ "use strict";
2499
+ init_discover();
2500
+ init_ink();
2501
+ init_prompts();
2502
+ }
2503
+ });
2878
2504
 
2879
2505
  // cli/rag.tsx
2880
- init_discover();
2881
- init_help();
2882
- init_ink();
2883
- import { render as render3, Text as Text3, useApp as useApp2 } from "ink";
2884
- import minimist5 from "minimist";
2506
+ var rag_exports = {};
2507
+ __export(rag_exports, {
2508
+ chunkPages: () => chunkPages,
2509
+ parsePage: () => parsePage,
2510
+ runRagCommand: () => runRagCommand,
2511
+ slugify: () => slugify,
2512
+ splitPages: () => splitPages,
2513
+ stripNoise: () => stripNoise,
2514
+ upsertChunks: () => upsertChunks
2515
+ });
2516
+ import { Text as Text3 } from "ink";
2885
2517
  import pLimit from "p-limit";
2886
- import { useEffect, useState as useState2 } from "react";
2887
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
2888
- var ragCommandDef = {
2889
- name: "rag",
2890
- description: "Ingest a site's llms-full.txt into the vector store",
2891
- args: [{ name: "url" }],
2892
- options: [
2893
- { flags: "-s, --server <url>", description: "Server URL" },
2894
- {
2895
- flags: "--chunk-size <n>",
2896
- description: "Max chunk size in tokens (default: 512)"
2897
- },
2898
- { flags: "-y, --yes", description: "Accept defaults (no prompts)" }
2899
- ]
2900
- };
2901
- var FETCH_TIMEOUT_MS = 6e4;
2902
- var PAD = 2;
2903
- function RagUI({ url, apiKey, serverUrl, slug, chunkSize, onError }) {
2904
- const { exit } = useApp2();
2905
- const { items, log } = useStepLog();
2906
- const [progress, setProgress] = useState2(null);
2907
- const [err, setErr] = useState2(null);
2908
- useEffect(() => {
2909
- (async () => {
2910
- try {
2911
- await runRag({
2912
- url,
2913
- apiKey,
2914
- serverUrl,
2915
- slug,
2916
- chunkSize,
2917
- log,
2918
- setProgress
2919
- });
2920
- } catch (e) {
2921
- const error = e instanceof Error ? e : new Error(String(e));
2922
- setErr(error.message);
2923
- onError?.(error);
2924
- }
2925
- setProgress(null);
2926
- exit();
2927
- })();
2928
- }, [apiKey, chunkSize, exit, log, onError, serverUrl, slug, url]);
2929
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
2930
- /* @__PURE__ */ jsx6(StepLog, { items }),
2931
- err && /* @__PURE__ */ jsx6(ErrorLine, { msg: err }),
2932
- progress && /* @__PURE__ */ jsxs3(Text3, { children: [
2933
- " ".repeat(PAD + 1),
2934
- "Upsert ",
2935
- progress.completed,
2936
- "/",
2937
- progress.total,
2938
- " (",
2939
- Math.round(progress.completed / progress.total * 100),
2940
- "%)"
2941
- ] })
2942
- ] });
2943
- }
2518
+ import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
2944
2519
  async function runRag(opts) {
2945
- const { url, apiKey, serverUrl, slug, chunkSize, log, setProgress } = opts;
2946
- log(/* @__PURE__ */ jsx6(Step, { action: "Fetch", msg: url }));
2520
+ const { url, apiKey, serverUrl, slug, chunkSize, log, setStatus } = opts;
2521
+ log(/* @__PURE__ */ jsx9(Step, { action: "Fetch", msg: url }));
2947
2522
  const resp = await fetch(url, {
2948
2523
  headers: { "User-Agent": "aai-cli/1.0" },
2949
2524
  redirect: "follow",
2950
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
2525
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2)
2951
2526
  });
2952
2527
  if (!resp.ok) {
2953
2528
  throw new Error(`Failed to fetch: ${resp.status} ${resp.statusText}`);
2954
2529
  }
2955
2530
  const content = await resp.text();
2956
2531
  if (content.length === 0) {
2957
- log(/* @__PURE__ */ jsx6(Warn, { msg: "File is empty" }));
2532
+ log(/* @__PURE__ */ jsx9(Warn, { msg: "File is empty" }));
2958
2533
  return;
2959
2534
  }
2960
- log(/* @__PURE__ */ jsx6(Info, { msg: `${(content.length / 1024).toFixed(0)} KB` }));
2535
+ log(/* @__PURE__ */ jsx9(Info, { msg: `${(content.length / 1024).toFixed(0)} KB` }));
2961
2536
  const origin = new URL(url).origin;
2962
2537
  const pages = splitPages(content);
2963
- log(/* @__PURE__ */ jsx6(Step, { action: "Parse", msg: `${pages.length} pages` }));
2538
+ log(/* @__PURE__ */ jsx9(Step, { action: "Parse", msg: `${pages.length} pages` }));
2964
2539
  const { RecursiveChunker } = await import("@chonkiejs/core");
2965
2540
  const chunker = await RecursiveChunker.create({ chunkSize });
2966
2541
  const siteSlug = slugify(origin);
2967
2542
  const allChunks = await chunkPages(pages, chunker, origin, siteSlug);
2968
- log(/* @__PURE__ */ jsx6(Step, { action: "Chunk", msg: `${allChunks.length} chunks` }));
2543
+ log(/* @__PURE__ */ jsx9(Step, { action: "Chunk", msg: `${allChunks.length} chunks` }));
2969
2544
  const vectorUrl = `${serverUrl}/${slug}/vector`;
2970
- log(/* @__PURE__ */ jsx6(Info, { msg: `target: ${vectorUrl}` }));
2971
- const result = await upsertChunks(allChunks, vectorUrl, apiKey, setProgress);
2972
- log(/* @__PURE__ */ jsx6(Step, { action: "Done", msg: `${result.upserted} chunks upserted` }));
2545
+ log(/* @__PURE__ */ jsx9(Info, { msg: `target: ${vectorUrl}` }));
2546
+ const result = await upsertChunks(allChunks, vectorUrl, apiKey, setStatus);
2547
+ log(/* @__PURE__ */ jsx9(Step, { action: "Done", msg: `${result.upserted} chunks upserted` }));
2973
2548
  if (result.errors > 0) {
2974
- log(/* @__PURE__ */ jsx6(Warn, { msg: `${result.errors} failed` }));
2975
- if (result.lastError) log(/* @__PURE__ */ jsx6(Info, { msg: `last error: ${result.lastError}` }));
2549
+ log(/* @__PURE__ */ jsx9(Warn, { msg: `${result.errors} failed` }));
2550
+ if (result.lastError) log(/* @__PURE__ */ jsx9(Info, { msg: `last error: ${result.lastError}` }));
2976
2551
  }
2977
- log(/* @__PURE__ */ jsx6(Detail, { msg: `Agent: ${slug}` }));
2552
+ log(/* @__PURE__ */ jsx9(Detail, { msg: `Agent: ${slug}` }));
2978
2553
  }
2979
2554
  async function chunkPages(pages, chunker, origin, siteSlug) {
2980
2555
  const allChunks = [];
@@ -3000,19 +2575,34 @@ ${c.text}` : c.text;
3000
2575
  }
3001
2576
  return allChunks;
3002
2577
  }
3003
- async function upsertChunks(chunks, vectorUrl, apiKey, setProgress) {
2578
+ async function upsertChunks(chunks, vectorUrl, apiKey, setStatus, fetchFn = globalThis.fetch) {
3004
2579
  const total = chunks.length;
3005
2580
  let completed = 0;
3006
2581
  let upserted = 0;
3007
2582
  let errors = 0;
3008
2583
  let lastError = "";
3009
- setProgress({ completed: 0, total });
2584
+ const updateStatus = () => {
2585
+ const pct = Math.round(completed / total * 100);
2586
+ setStatus(
2587
+ /* @__PURE__ */ jsxs3(Text3, { children: [
2588
+ " ",
2589
+ "Upsert ",
2590
+ completed,
2591
+ "/",
2592
+ total,
2593
+ " (",
2594
+ pct,
2595
+ "%)"
2596
+ ] })
2597
+ );
2598
+ };
2599
+ updateStatus();
3010
2600
  const limit = pLimit(5);
3011
2601
  await Promise.all(
3012
2602
  chunks.map(
3013
2603
  (chunk) => limit(async () => {
3014
2604
  try {
3015
- const r = await fetch(vectorUrl, {
2605
+ const r = await fetchFn(vectorUrl, {
3016
2606
  method: "POST",
3017
2607
  headers: {
3018
2608
  "Content-Type": "application/json",
@@ -3032,65 +2622,29 @@ async function upsertChunks(chunks, vectorUrl, apiKey, setProgress) {
3032
2622
  upserted++;
3033
2623
  }
3034
2624
  } catch (err) {
3035
- lastError = err instanceof Error ? err.message : String(err);
2625
+ lastError = errorMessage(err);
3036
2626
  errors++;
3037
2627
  }
3038
2628
  completed++;
3039
- setProgress({ completed, total });
2629
+ updateStatus();
3040
2630
  })
3041
2631
  )
3042
2632
  );
2633
+ setStatus(null);
3043
2634
  return { upserted, errors, lastError };
3044
2635
  }
3045
- async function runRagCommand(args, version) {
3046
- const parsed = minimist5(args, {
3047
- string: ["server", "chunk-size"],
3048
- boolean: ["help", "yes"],
3049
- alias: { s: "server", h: "help", y: "yes" },
3050
- stopEarly: true
3051
- });
3052
- if (parsed.help) {
3053
- console.log(subcommandHelp(ragCommandDef, version));
3054
- return;
3055
- }
3056
- const url = String(parsed._[0] ?? "");
3057
- if (!url) {
3058
- throw new Error(
3059
- "Usage: aai rag <url>\n\nProvide the full URL to a site's llms-full.txt file"
3060
- );
3061
- }
2636
+ async function runRagCommand(opts) {
2637
+ const { url, cwd } = opts;
3062
2638
  try {
3063
2639
  new URL(url);
3064
2640
  } catch {
3065
2641
  throw new Error(`Invalid URL: ${url}`);
3066
2642
  }
3067
- const cwd = process.env.INIT_CWD || process.cwd();
3068
- const config = await readProjectConfig(cwd);
3069
- if (!config) {
3070
- throw new Error("No .aai/project.json found \u2014 deploy first with `aai deploy`");
3071
- }
3072
- const apiKey = await getApiKey();
3073
- const serverUrl = parsed.server || config.serverUrl || (isDevMode() ? "http://localhost:3100" : DEFAULT_SERVER);
3074
- const slug = config.slug;
3075
- const chunkSize = Number.parseInt(parsed["chunk-size"] ?? "512", 10);
3076
- let thrownError;
3077
- const app = render3(
3078
- /* @__PURE__ */ jsx6(
3079
- RagUI,
3080
- {
3081
- url,
3082
- apiKey,
3083
- serverUrl,
3084
- slug,
3085
- chunkSize,
3086
- onError: (e) => {
3087
- thrownError = e;
3088
- }
3089
- }
3090
- )
3091
- );
3092
- await app.waitUntilExit();
3093
- if (thrownError) throw thrownError;
2643
+ const { apiKey, serverUrl, slug } = await getServerInfo(cwd, opts.server);
2644
+ const chunkSize = Number.parseInt(opts.chunkSize ?? "512", 10);
2645
+ await runWithInk(async ({ log, setStatus }) => {
2646
+ await runRag({ url, apiKey, serverUrl, slug, chunkSize, log, setStatus });
2647
+ });
3094
2648
  }
3095
2649
  function splitPages(content) {
3096
2650
  const raw = content.split(/^\*{3,}$/m);
@@ -3137,243 +2691,153 @@ function stripNoise(text) {
3137
2691
  function slugify(s) {
3138
2692
  return s.replace(/^https?:\/\//, "").replace(/^#+\s*/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase().slice(0, 80);
3139
2693
  }
2694
+ var FETCH_TIMEOUT_MS2;
2695
+ var init_rag = __esm({
2696
+ "cli/rag.tsx"() {
2697
+ "use strict";
2698
+ init_utils();
2699
+ init_discover();
2700
+ init_ink();
2701
+ FETCH_TIMEOUT_MS2 = 6e4;
2702
+ }
2703
+ });
3140
2704
 
3141
- // cli/secret.tsx
2705
+ // cli/cli.ts
2706
+ init_utils();
3142
2707
  init_discover();
3143
- init_help();
3144
2708
  init_ink();
3145
- init_prompts();
3146
- import minimist6 from "minimist";
3147
- import { jsx as jsx7 } from "react/jsx-runtime";
3148
- var secretCommandDef = {
3149
- name: "secret",
3150
- description: "Manage secrets",
3151
- options: [
3152
- { flags: "put <name>", description: "Create or update a secret" },
3153
- { flags: "delete <name>", description: "Delete a secret" },
3154
- { flags: "list", description: "List secret names" }
3155
- ]
3156
- };
3157
- async function requireProjectConfig(cwd) {
3158
- const config = await readProjectConfig(cwd);
3159
- if (!config) {
3160
- throw new Error("No .aai/project.json found \u2014 deploy first with `aai deploy`");
3161
- }
3162
- return config;
3163
- }
3164
- async function runSecretCommand(args, version) {
3165
- const parsed = minimist6(args, {
3166
- boolean: ["help", "yes"],
3167
- alias: { h: "help", y: "yes" },
3168
- stopEarly: true
2709
+ import { readFileSync } from "node:fs";
2710
+ import path7 from "node:path";
2711
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2712
+ import chalk2 from "chalk";
2713
+ import { Command } from "commander";
2714
+ var cliDir = path7.dirname(fileURLToPath2(import.meta.url));
2715
+ var pkgJsonPath = path7.join(cliDir, "..", "package.json");
2716
+ var pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
2717
+ var VERSION = pkgJson.version;
2718
+ var banner = [
2719
+ "",
2720
+ ` ${primary(chalk2.bold(" \u2584\u2580\u2588 \u2584\u2580\u2588 \u2588"))} ${chalk2.dim("Voice agent development kit")}`,
2721
+ ` ${primary(chalk2.bold(" \u2588\u2580\u2588 \u2588\u2580\u2588 \u2588"))} ${primary(`v${VERSION}`)}`,
2722
+ ""
2723
+ ].join("\n");
2724
+ var gettingStarted = [
2725
+ "",
2726
+ ` ${chalk2.bold(interactive("Getting started"))}`,
2727
+ "",
2728
+ ` ${chalk2.dim("$")} ${primary("aai init")} ${interactive("my-agent")}`,
2729
+ ` ${chalk2.dim("$")} ${primary("cd")} ${interactive("my-agent")}`,
2730
+ ` ${chalk2.dim("$")} ${primary("aai dev")}`,
2731
+ ""
2732
+ ].join("\n");
2733
+ async function ensureAgent(cwd, yes) {
2734
+ if (!await fileExists(path7.join(cwd, "agent.ts"))) {
2735
+ const { runInitCommand: runInitCommand2 } = await Promise.resolve().then(() => (init_init2(), init_exports2));
2736
+ await runInitCommand2({ yes }, { quiet: true });
2737
+ }
2738
+ }
2739
+ function withCwd(cmd) {
2740
+ return cmd.hook("preAction", (thisCmd) => {
2741
+ thisCmd.setOptionValue("cwd", resolveCwd());
3169
2742
  });
3170
- if (parsed.help || parsed._.length === 0) {
3171
- console.log(subcommandHelp(secretCommandDef, version));
3172
- return;
3173
- }
3174
- const sub = String(parsed._[0]);
3175
- const cwd = process.env.INIT_CWD || process.cwd();
3176
- await getApiKey();
3177
- let secretValue;
3178
- if (sub === "put") {
3179
- const name = String(parsed._[1] ?? "");
3180
- if (!name) throw new Error("Usage: aai secret put <NAME>");
3181
- secretValue = await askPassword(`Enter value for ${name}`);
3182
- if (!secretValue) throw new Error("No value provided");
3183
- }
3184
- switch (sub) {
3185
- case "put":
3186
- await secretPut(cwd, String(parsed._[1] ?? ""), secretValue ?? "");
3187
- break;
3188
- case "delete":
3189
- await secretDelete(cwd, String(parsed._[1] ?? ""));
3190
- break;
3191
- case "list":
3192
- await secretList(cwd);
3193
- break;
3194
- default:
3195
- throw new Error(`Unknown secret subcommand: ${sub}`);
3196
- }
3197
- }
3198
- async function getServerInfo(cwd) {
3199
- const config = await requireProjectConfig(cwd);
3200
- const apiKey = await getApiKey();
3201
- const serverUrl = config.serverUrl || DEFAULT_SERVER;
3202
- const slug = config.slug;
3203
- return { serverUrl, slug, apiKey };
3204
2743
  }
3205
- async function secretPut(cwd, name, value) {
3206
- await runWithInk(async (log) => {
3207
- const { serverUrl, slug, apiKey } = await getServerInfo(cwd);
3208
- const resp = await fetch(`${serverUrl}/${slug}/secret`, {
3209
- method: "PUT",
3210
- headers: {
3211
- "Content-Type": "application/json",
3212
- Authorization: `Bearer ${apiKey}`
3213
- },
3214
- body: JSON.stringify({ [name]: value })
3215
- });
3216
- if (!resp.ok) {
3217
- const text = await resp.text();
3218
- throw new Error(`Failed to set secret: ${text}`);
3219
- }
3220
- log(/* @__PURE__ */ jsx7(Step, { action: "Set", msg: `${name} for ${slug}` }));
2744
+ function withAgentGuard(cmd) {
2745
+ return cmd.hook("preAction", async (thisCmd) => {
2746
+ await ensureAgent(thisCmd.getOptionValue("cwd"), thisCmd.opts().yes);
3221
2747
  });
3222
2748
  }
3223
- async function secretDelete(cwd, name) {
3224
- if (!name) throw new Error("Usage: aai secret delete <NAME>");
3225
- await runWithInk(async (log) => {
3226
- const { serverUrl, slug, apiKey } = await getServerInfo(cwd);
3227
- const resp = await fetch(`${serverUrl}/${slug}/secret/${name}`, {
3228
- method: "DELETE",
3229
- headers: { Authorization: `Bearer ${apiKey}` }
3230
- });
3231
- if (!resp.ok) {
3232
- const text = await resp.text();
3233
- throw new Error(`Failed to delete secret: ${text}`);
3234
- }
3235
- log(/* @__PURE__ */ jsx7(Step, { action: "Deleted", msg: `${name} from ${slug}` }));
2749
+ function withApiKey(cmd) {
2750
+ return cmd.hook("preAction", async () => {
2751
+ await getApiKey();
3236
2752
  });
3237
2753
  }
3238
- async function secretList(cwd) {
3239
- await runWithInk(async (log) => {
3240
- const { serverUrl, slug, apiKey } = await getServerInfo(cwd);
3241
- const resp = await fetch(`${serverUrl}/${slug}/secret`, {
3242
- headers: { Authorization: `Bearer ${apiKey}` }
3243
- });
3244
- if (!resp.ok) {
3245
- const text = await resp.text();
3246
- throw new Error(`Failed to list secrets: ${text}`);
3247
- }
3248
- const { vars } = await resp.json();
3249
- if (vars.length === 0) {
3250
- log(/* @__PURE__ */ jsx7(StepInfo, { action: "Secrets", msg: "none set" }));
3251
- } else {
3252
- for (const name of vars) {
3253
- log(/* @__PURE__ */ jsx7(Detail, { msg: name }));
3254
- }
2754
+ function createProgram() {
2755
+ const program = new Command();
2756
+ program.name("aai").version(VERSION, "-V, --version").addHelpText("before", banner).addHelpText("after", gettingStarted);
2757
+ program.command("init").description("Scaffold a new agent project").argument("[dir]", "Project directory").option("-t, --template <template>", "Template to use").option("-f, --force", "Overwrite existing agent.ts").option("-y, --yes", "Accept defaults (no prompts)").action(
2758
+ async (dir, opts) => {
2759
+ const { runInitCommand: runInitCommand2 } = await Promise.resolve().then(() => (init_init2(), init_exports2));
2760
+ await runInitCommand2({ dir, ...opts });
3255
2761
  }
2762
+ );
2763
+ withApiKey(
2764
+ withAgentGuard(
2765
+ withCwd(
2766
+ program.command("dev").description("Start a local development server").option("-p, --port <number>", "Port to listen on", "3000").option("-y, --yes", "Accept defaults (no prompts)").action(async (opts) => {
2767
+ const { runDevCommand: runDevCommand2 } = await Promise.resolve().then(() => (init_dev(), dev_exports));
2768
+ await runDevCommand2(opts);
2769
+ })
2770
+ )
2771
+ )
2772
+ );
2773
+ withAgentGuard(
2774
+ withCwd(
2775
+ program.command("build").description("Bundle and validate (no server or deploy)").option("-y, --yes", "Accept defaults (no prompts)").action(async (opts) => {
2776
+ const { runBuildCommand: runBuildCommand2 } = await Promise.resolve().then(() => (init_build(), build_exports));
2777
+ await runBuildCommand2(opts.cwd);
2778
+ })
2779
+ )
2780
+ );
2781
+ withAgentGuard(
2782
+ withCwd(
2783
+ program.command("deploy").description("Bundle and deploy to production").option("-s, --server <url>", "Server URL").option("--dry-run", "Validate and bundle without deploying").option("-y, --yes", "Accept defaults (no prompts)").action(async (opts) => {
2784
+ const { runDeployCommand: runDeployCommand2 } = await Promise.resolve().then(() => (init_deploy2(), deploy_exports));
2785
+ await runDeployCommand2(opts);
2786
+ })
2787
+ )
2788
+ );
2789
+ withApiKey(
2790
+ withCwd(
2791
+ program.command("start").description("Start production server from build").option("-p, --port <number>", "Port to listen on", "3000").option("-y, --yes", "Accept defaults (no prompts)").action(async (opts) => {
2792
+ const { runStartCommand: runStartCommand2 } = await Promise.resolve().then(() => (init_start(), start_exports));
2793
+ await runStartCommand2(opts);
2794
+ })
2795
+ )
2796
+ );
2797
+ const secret = program.command("secret").description("Manage secrets").action(() => secret.help());
2798
+ withApiKey(withCwd(secret));
2799
+ secret.command("put").description("Create or update a secret").argument("<name>", "Secret name").action(async (name, _opts, cmd) => {
2800
+ const cwd = cmd.parent?.getOptionValue("cwd");
2801
+ const { runSecretPut: runSecretPut2 } = await Promise.resolve().then(() => (init_secret(), secret_exports));
2802
+ await runSecretPut2(cwd, name);
3256
2803
  });
3257
- }
3258
-
3259
- // cli/start.tsx
3260
- init_discover();
3261
- init_help();
3262
- init_ink();
3263
- import path10 from "node:path";
3264
- import minimist7 from "minimist";
3265
-
3266
- // cli/_start.ts
3267
- init_ink();
3268
- import path9 from "node:path";
3269
- import React4 from "react";
3270
- async function _startProductionServer(cwd, port, log) {
3271
- const clientDir = path9.join(cwd, ".aai", "client");
3272
- log(React4.createElement(Step, { action: "Start", msg: "loading agent" }));
3273
- const agentDef = await loadAgentDef(cwd);
3274
- const env = await resolveServerEnv();
3275
- await bootServer(agentDef, clientDir, env, port);
3276
- log(React4.createElement(Step, { action: "Ready", msg: `http://localhost:${port}` }));
3277
- }
3278
-
3279
- // cli/start.tsx
3280
- import { jsx as jsx8 } from "react/jsx-runtime";
3281
- var startCommandDef = {
3282
- name: "start",
3283
- description: "Start the production server from a build",
3284
- options: [
3285
- {
3286
- flags: "-p, --port <number>",
3287
- description: "Port to listen on (default: 3000)"
3288
- },
3289
- { flags: "-y, --yes", description: "Accept defaults (no prompts)" }
3290
- ]
3291
- };
3292
- async function runStartCommand(args, version) {
3293
- const parsed = minimist7(args, {
3294
- string: ["port"],
3295
- boolean: ["help", "yes"],
3296
- alias: { p: "port", h: "help", y: "yes" }
2804
+ secret.command("delete").description("Delete a secret").argument("<name>", "Secret name").action(async (name, _opts, cmd) => {
2805
+ const cwd = cmd.parent?.getOptionValue("cwd");
2806
+ const { runSecretDelete: runSecretDelete2 } = await Promise.resolve().then(() => (init_secret(), secret_exports));
2807
+ await runSecretDelete2(cwd, name);
3297
2808
  });
3298
- if (parsed.help) {
3299
- console.log(subcommandHelp(startCommandDef, version));
3300
- return;
3301
- }
3302
- const cwd = process.env.INIT_CWD || process.cwd();
3303
- const port = Number.parseInt(parsed.port ?? "3000", 10);
3304
- const buildDir = path10.join(cwd, ".aai", "build");
3305
- if (!await fileExists(path10.join(buildDir, "worker.js"))) {
3306
- throw new Error("No build found \u2014 run `aai build` first");
3307
- }
3308
- await getApiKey();
3309
- await runWithInk(async (log) => {
3310
- log(/* @__PURE__ */ jsx8(Step, { action: "Start", msg: `production server on port ${port}` }));
3311
- await _startProductionServer(cwd, port, log);
2809
+ secret.command("list").description("List secret names").action(async (_opts, cmd) => {
2810
+ const cwd = cmd.parent?.getOptionValue("cwd");
2811
+ const { runSecretList: runSecretList2 } = await Promise.resolve().then(() => (init_secret(), secret_exports));
2812
+ await runSecretList2(cwd);
3312
2813
  });
2814
+ withApiKey(
2815
+ withCwd(
2816
+ program.command("rag").description("Ingest a site's llms-full.txt into the vector store").argument("<url>", "URL to ingest").option("-s, --server <url>", "Server URL").option("--chunk-size <n>", "Max chunk size in tokens", "512").option("-y, --yes", "Accept defaults (no prompts)").action(async (url, opts) => {
2817
+ const { runRagCommand: runRagCommand2 } = await Promise.resolve().then(() => (init_rag(), rag_exports));
2818
+ await runRagCommand2({ url, ...opts });
2819
+ })
2820
+ )
2821
+ );
2822
+ return program;
3313
2823
  }
3314
-
3315
- // cli/cli.ts
3316
- var cliDir = path11.dirname(fileURLToPath2(import.meta.url));
3317
- var pkgJsonPath = path11.join(cliDir, "..", "package.json");
3318
- var pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
3319
- var VERSION = pkgJson.version;
3320
2824
  async function main(args) {
3321
- const parsed = minimist8(args, {
3322
- boolean: ["help", "version"],
3323
- alias: { h: "help", V: "version" },
3324
- stopEarly: true
3325
- });
3326
- if (parsed.version) {
3327
- console.log(VERSION);
2825
+ if (args.length === 0) {
2826
+ const { runInitCommand: runInitCommand2 } = await Promise.resolve().then(() => (init_init2(), init_exports2));
2827
+ await runInitCommand2({});
3328
2828
  return;
3329
2829
  }
3330
- if (parsed.help && parsed._.length === 0) {
3331
- console.log(rootHelp(VERSION));
3332
- return;
3333
- }
3334
- const [subcommand, ...rest] = parsed._;
3335
- const subArgs = rest.map(String);
3336
- switch (subcommand) {
3337
- case "init":
3338
- await runInitCommand(subArgs, VERSION);
3339
- return;
3340
- case "build":
3341
- await runBuildCommand(subArgs, VERSION);
3342
- return;
3343
- case "deploy":
3344
- await runDeployCommand(subArgs, VERSION);
3345
- return;
3346
- case "dev":
3347
- await runDevCommand(subArgs, VERSION);
3348
- return;
3349
- case "start":
3350
- await runStartCommand(subArgs, VERSION);
3351
- return;
3352
- case "secret":
3353
- await runSecretCommand(subArgs, VERSION);
3354
- return;
3355
- case "rag":
3356
- await runRagCommand(subArgs, VERSION);
3357
- return;
3358
- case "help":
3359
- console.log(rootHelp(VERSION));
3360
- return;
3361
- case void 0:
3362
- await runInitCommand(subArgs, VERSION);
3363
- return;
3364
- default:
3365
- console.error(`Unknown command: ${subcommand}`);
3366
- console.log(rootHelp(VERSION));
3367
- process.exit(1);
3368
- }
2830
+ const program = createProgram();
2831
+ await program.parseAsync(args, { from: "user" });
3369
2832
  }
3370
2833
  if (process.env.VITEST !== "true") {
3371
2834
  process.on("SIGINT", () => process.exit(0));
3372
2835
  main(process.argv.slice(2)).catch((err) => {
3373
- console.error(err instanceof Error ? err.message : String(err));
2836
+ console.error(errorMessage(err));
3374
2837
  process.exit(1);
3375
2838
  });
3376
2839
  }
3377
2840
  export {
2841
+ createProgram,
3378
2842
  main
3379
2843
  };