signalwire-agents 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CLAUDE.md +112 -0
  2. package/README.md +417 -0
  3. package/dist/AgentBase.d.ts +481 -0
  4. package/dist/AgentBase.js +1155 -0
  5. package/dist/AgentBase.js.map +1 -0
  6. package/dist/AgentServer.d.ts +60 -0
  7. package/dist/AgentServer.js +128 -0
  8. package/dist/AgentServer.js.map +1 -0
  9. package/dist/AuthHandler.d.ts +56 -0
  10. package/dist/AuthHandler.js +119 -0
  11. package/dist/AuthHandler.js.map +1 -0
  12. package/dist/ConfigLoader.d.ts +68 -0
  13. package/dist/ConfigLoader.js +150 -0
  14. package/dist/ConfigLoader.js.map +1 -0
  15. package/dist/ContextBuilder.d.ts +431 -0
  16. package/dist/ContextBuilder.js +751 -0
  17. package/dist/ContextBuilder.js.map +1 -0
  18. package/dist/DataMap.d.ts +179 -0
  19. package/dist/DataMap.js +321 -0
  20. package/dist/DataMap.js.map +1 -0
  21. package/dist/Logger.d.ts +91 -0
  22. package/dist/Logger.js +191 -0
  23. package/dist/Logger.js.map +1 -0
  24. package/dist/PomBuilder.d.ts +144 -0
  25. package/dist/PomBuilder.js +249 -0
  26. package/dist/PomBuilder.js.map +1 -0
  27. package/dist/PromptManager.d.ts +85 -0
  28. package/dist/PromptManager.js +108 -0
  29. package/dist/PromptManager.js.map +1 -0
  30. package/dist/SchemaUtils.d.ts +42 -0
  31. package/dist/SchemaUtils.js +148 -0
  32. package/dist/SchemaUtils.js.map +1 -0
  33. package/dist/ServerlessAdapter.d.ts +100 -0
  34. package/dist/ServerlessAdapter.js +166 -0
  35. package/dist/ServerlessAdapter.js.map +1 -0
  36. package/dist/SessionManager.d.ts +86 -0
  37. package/dist/SessionManager.js +155 -0
  38. package/dist/SessionManager.js.map +1 -0
  39. package/dist/SslConfig.d.ts +77 -0
  40. package/dist/SslConfig.js +100 -0
  41. package/dist/SslConfig.js.map +1 -0
  42. package/dist/SwaigFunction.d.ts +88 -0
  43. package/dist/SwaigFunction.js +125 -0
  44. package/dist/SwaigFunction.js.map +1 -0
  45. package/dist/SwaigFunctionResult.d.ts +442 -0
  46. package/dist/SwaigFunctionResult.js +735 -0
  47. package/dist/SwaigFunctionResult.js.map +1 -0
  48. package/dist/SwmlBuilder.d.ts +37 -0
  49. package/dist/SwmlBuilder.js +55 -0
  50. package/dist/SwmlBuilder.js.map +1 -0
  51. package/dist/cli/agent-loader.d.ts +22 -0
  52. package/dist/cli/agent-loader.js +106 -0
  53. package/dist/cli/agent-loader.js.map +1 -0
  54. package/dist/cli/mock-data.d.ts +37 -0
  55. package/dist/cli/mock-data.js +79 -0
  56. package/dist/cli/mock-data.js.map +1 -0
  57. package/dist/cli/swaig-test.d.ts +32 -0
  58. package/dist/cli/swaig-test.js +405 -0
  59. package/dist/cli/swaig-test.js.map +1 -0
  60. package/dist/index.d.ts +36 -0
  61. package/dist/index.js +44 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/prefabs/ConciergeAgent.d.ts +64 -0
  64. package/dist/prefabs/ConciergeAgent.js +188 -0
  65. package/dist/prefabs/ConciergeAgent.js.map +1 -0
  66. package/dist/prefabs/FAQBotAgent.d.ts +68 -0
  67. package/dist/prefabs/FAQBotAgent.js +206 -0
  68. package/dist/prefabs/FAQBotAgent.js.map +1 -0
  69. package/dist/prefabs/InfoGathererAgent.d.ts +73 -0
  70. package/dist/prefabs/InfoGathererAgent.js +202 -0
  71. package/dist/prefabs/InfoGathererAgent.js.map +1 -0
  72. package/dist/prefabs/ReceptionistAgent.d.ts +65 -0
  73. package/dist/prefabs/ReceptionistAgent.js +205 -0
  74. package/dist/prefabs/ReceptionistAgent.js.map +1 -0
  75. package/dist/prefabs/SurveyAgent.d.ts +84 -0
  76. package/dist/prefabs/SurveyAgent.js +324 -0
  77. package/dist/prefabs/SurveyAgent.js.map +1 -0
  78. package/dist/prefabs/index.d.ts +13 -0
  79. package/dist/prefabs/index.js +14 -0
  80. package/dist/prefabs/index.js.map +1 -0
  81. package/dist/skills/SkillBase.d.ts +134 -0
  82. package/dist/skills/SkillBase.js +105 -0
  83. package/dist/skills/SkillBase.js.map +1 -0
  84. package/dist/skills/SkillManager.d.ts +83 -0
  85. package/dist/skills/SkillManager.js +159 -0
  86. package/dist/skills/SkillManager.js.map +1 -0
  87. package/dist/skills/SkillRegistry.d.ts +105 -0
  88. package/dist/skills/SkillRegistry.js +195 -0
  89. package/dist/skills/SkillRegistry.js.map +1 -0
  90. package/dist/skills/builtin/api_ninjas_trivia.d.ts +35 -0
  91. package/dist/skills/builtin/api_ninjas_trivia.js +161 -0
  92. package/dist/skills/builtin/api_ninjas_trivia.js.map +1 -0
  93. package/dist/skills/builtin/claude_skills.d.ts +34 -0
  94. package/dist/skills/builtin/claude_skills.js +156 -0
  95. package/dist/skills/builtin/claude_skills.js.map +1 -0
  96. package/dist/skills/builtin/custom_skills.d.ts +59 -0
  97. package/dist/skills/builtin/custom_skills.js +231 -0
  98. package/dist/skills/builtin/custom_skills.js.map +1 -0
  99. package/dist/skills/builtin/datasphere.d.ts +34 -0
  100. package/dist/skills/builtin/datasphere.js +163 -0
  101. package/dist/skills/builtin/datasphere.js.map +1 -0
  102. package/dist/skills/builtin/datasphere_serverless.d.ts +54 -0
  103. package/dist/skills/builtin/datasphere_serverless.js +155 -0
  104. package/dist/skills/builtin/datasphere_serverless.js.map +1 -0
  105. package/dist/skills/builtin/datetime.d.ts +31 -0
  106. package/dist/skills/builtin/datetime.js +95 -0
  107. package/dist/skills/builtin/datetime.js.map +1 -0
  108. package/dist/skills/builtin/google_maps.d.ts +33 -0
  109. package/dist/skills/builtin/google_maps.js +233 -0
  110. package/dist/skills/builtin/google_maps.js.map +1 -0
  111. package/dist/skills/builtin/index.d.ts +28 -0
  112. package/dist/skills/builtin/index.js +75 -0
  113. package/dist/skills/builtin/index.js.map +1 -0
  114. package/dist/skills/builtin/info_gatherer.d.ts +52 -0
  115. package/dist/skills/builtin/info_gatherer.js +237 -0
  116. package/dist/skills/builtin/info_gatherer.js.map +1 -0
  117. package/dist/skills/builtin/joke.d.ts +32 -0
  118. package/dist/skills/builtin/joke.js +142 -0
  119. package/dist/skills/builtin/joke.js.map +1 -0
  120. package/dist/skills/builtin/math.d.ts +33 -0
  121. package/dist/skills/builtin/math.js +120 -0
  122. package/dist/skills/builtin/math.js.map +1 -0
  123. package/dist/skills/builtin/mcp_gateway.d.ts +35 -0
  124. package/dist/skills/builtin/mcp_gateway.js +85 -0
  125. package/dist/skills/builtin/mcp_gateway.js.map +1 -0
  126. package/dist/skills/builtin/native_vector_search.d.ts +44 -0
  127. package/dist/skills/builtin/native_vector_search.js +248 -0
  128. package/dist/skills/builtin/native_vector_search.js.map +1 -0
  129. package/dist/skills/builtin/play_background_file.d.ts +34 -0
  130. package/dist/skills/builtin/play_background_file.js +135 -0
  131. package/dist/skills/builtin/play_background_file.js.map +1 -0
  132. package/dist/skills/builtin/spider.d.ts +34 -0
  133. package/dist/skills/builtin/spider.js +164 -0
  134. package/dist/skills/builtin/spider.js.map +1 -0
  135. package/dist/skills/builtin/swml_transfer.d.ts +44 -0
  136. package/dist/skills/builtin/swml_transfer.js +204 -0
  137. package/dist/skills/builtin/swml_transfer.js.map +1 -0
  138. package/dist/skills/builtin/weather_api.d.ts +34 -0
  139. package/dist/skills/builtin/weather_api.js +136 -0
  140. package/dist/skills/builtin/weather_api.js.map +1 -0
  141. package/dist/skills/builtin/web_search.d.ts +34 -0
  142. package/dist/skills/builtin/web_search.js +141 -0
  143. package/dist/skills/builtin/web_search.js.map +1 -0
  144. package/dist/skills/builtin/wikipedia_search.d.ts +34 -0
  145. package/dist/skills/builtin/wikipedia_search.js +166 -0
  146. package/dist/skills/builtin/wikipedia_search.js.map +1 -0
  147. package/dist/skills/index.d.ts +11 -0
  148. package/dist/skills/index.js +10 -0
  149. package/dist/skills/index.js.map +1 -0
  150. package/dist/types.d.ts +85 -0
  151. package/dist/types.js +5 -0
  152. package/dist/types.js.map +1 -0
  153. package/docs/agent-guide.md +869 -0
  154. package/docs/api-reference.md +2419 -0
  155. package/docs/architecture.md +843 -0
  156. package/docs/cli-guide.md +579 -0
  157. package/docs/configuration.md +422 -0
  158. package/docs/contexts-guide.md +787 -0
  159. package/docs/datamap-guide.md +1141 -0
  160. package/docs/prefabs-guide.md +830 -0
  161. package/docs/security.md +436 -0
  162. package/docs/serverless-guide.md +513 -0
  163. package/docs/skills-guide.md +845 -0
  164. package/docs/swaig-reference.md +1627 -0
  165. package/examples/call-flow.ts +60 -0
  166. package/examples/contexts-steps.ts +62 -0
  167. package/examples/datamap-tools.ts +52 -0
  168. package/examples/dynamic-config.ts +51 -0
  169. package/examples/multi-agent.ts +67 -0
  170. package/examples/pom-prompt.ts +68 -0
  171. package/examples/simple-agent.ts +33 -0
  172. package/package.json +35 -0
  173. package/src/AgentBase.ts +1279 -0
  174. package/src/AgentServer.ts +149 -0
  175. package/src/AuthHandler.ts +141 -0
  176. package/src/ConfigLoader.ts +165 -0
  177. package/src/ContextBuilder.ts +796 -0
  178. package/src/DataMap.ts +365 -0
  179. package/src/Logger.ts +197 -0
  180. package/src/PomBuilder.ts +298 -0
  181. package/src/PromptManager.ts +133 -0
  182. package/src/SchemaUtils.ts +170 -0
  183. package/src/ServerlessAdapter.ts +218 -0
  184. package/src/SessionManager.ts +161 -0
  185. package/src/SslConfig.ts +118 -0
  186. package/src/SwaigFunction.ts +168 -0
  187. package/src/SwaigFunctionResult.ts +864 -0
  188. package/src/SwmlBuilder.ts +62 -0
  189. package/src/cli/agent-loader.ts +122 -0
  190. package/src/cli/mock-data.ts +110 -0
  191. package/src/cli/swaig-test.ts +440 -0
  192. package/src/index.ts +99 -0
  193. package/src/prefabs/ConciergeAgent.ts +251 -0
  194. package/src/prefabs/FAQBotAgent.ts +263 -0
  195. package/src/prefabs/InfoGathererAgent.ts +276 -0
  196. package/src/prefabs/ReceptionistAgent.ts +277 -0
  197. package/src/prefabs/SurveyAgent.ts +413 -0
  198. package/src/prefabs/index.ts +23 -0
  199. package/src/skills/SkillBase.ts +184 -0
  200. package/src/skills/SkillManager.ts +177 -0
  201. package/src/skills/SkillRegistry.ts +231 -0
  202. package/src/skills/builtin/api_ninjas_trivia.ts +230 -0
  203. package/src/skills/builtin/claude_skills.ts +239 -0
  204. package/src/skills/builtin/custom_skills.ts +326 -0
  205. package/src/skills/builtin/datasphere.ts +225 -0
  206. package/src/skills/builtin/datasphere_serverless.ts +194 -0
  207. package/src/skills/builtin/datetime.ts +115 -0
  208. package/src/skills/builtin/google_maps.ts +347 -0
  209. package/src/skills/builtin/index.ts +79 -0
  210. package/src/skills/builtin/info_gatherer.ts +331 -0
  211. package/src/skills/builtin/joke.ts +180 -0
  212. package/src/skills/builtin/math.ts +150 -0
  213. package/src/skills/builtin/mcp_gateway.ts +99 -0
  214. package/src/skills/builtin/native_vector_search.ts +327 -0
  215. package/src/skills/builtin/play_background_file.ts +189 -0
  216. package/src/skills/builtin/spider.ts +232 -0
  217. package/src/skills/builtin/swml_transfer.ts +285 -0
  218. package/src/skills/builtin/weather_api.ts +193 -0
  219. package/src/skills/builtin/web_search.ts +212 -0
  220. package/src/skills/builtin/wikipedia_search.ts +248 -0
  221. package/src/skills/index.ts +12 -0
  222. package/src/types.ts +99 -0
  223. package/tests/AgentBase.test.ts +693 -0
  224. package/tests/AgentServer.test.ts +102 -0
  225. package/tests/AuthHandler.test.ts +121 -0
  226. package/tests/ConfigLoader.test.ts +121 -0
  227. package/tests/DataMap.test.ts +286 -0
  228. package/tests/Logger.test.ts +218 -0
  229. package/tests/PomBuilder.test.ts +164 -0
  230. package/tests/ProxyDetection.test.ts +185 -0
  231. package/tests/SchemaUtils.test.ts +106 -0
  232. package/tests/ServerlessAdapter.test.ts +200 -0
  233. package/tests/SessionManager.test.ts +139 -0
  234. package/tests/SslConfig.test.ts +97 -0
  235. package/tests/SwaigFunctionResult.test.ts +298 -0
  236. package/tests/cli/mock-data.test.ts +94 -0
  237. package/tests/cli/swaig-test.test.ts +96 -0
  238. package/tests/prefabs/prefabs.test.ts +854 -0
  239. package/tests/setup.ts +6 -0
  240. package/tests/skills/SkillBase.test.ts +118 -0
  241. package/tests/skills/SkillManager.test.ts +164 -0
  242. package/tests/skills/SkillRegistry.test.ts +124 -0
  243. package/tests/skills/builtin.test.ts +924 -0
  244. package/tsconfig.json +18 -0
  245. package/vitest.config.ts +9 -0
@@ -0,0 +1,843 @@
1
+ # SignalWire AI Agents TypeScript SDK -- Architecture
2
+
3
+ ## Table of Contents
4
+
5
+ - [Overview](#overview)
6
+ - [Core Components](#core-components)
7
+ - [AgentBase](#agentbase)
8
+ - [PromptManager and PomBuilder](#promptmanager-and-pombuilder)
9
+ - [SwmlBuilder](#swmlbuilder)
10
+ - [SwaigFunction](#swaigfunction)
11
+ - [SwaigFunctionResult](#swaigfunctionresult)
12
+ - [SessionManager](#sessionmanager)
13
+ - [ContextBuilder](#contextbuilder)
14
+ - [DataMap](#datamap)
15
+ - [Composition Architecture](#composition-architecture)
16
+ - [SWML Rendering Pipeline](#swml-rendering-pipeline)
17
+ - [Request Flow](#request-flow)
18
+ - [SWML Request (GET/POST /)](#swml-request-getpost-)
19
+ - [SWAIG Function Dispatch (POST /swaig)](#swaig-function-dispatch-post-swaig)
20
+ - [Post-Prompt Handler (POST /post_prompt)](#post-prompt-handler-post-post_prompt)
21
+ - [Tool System](#tool-system)
22
+ - [SwaigFunction Wrapper](#swaigfunction-wrapper)
23
+ - [Handler Signature](#handler-signature)
24
+ - [Result Serialization](#result-serialization)
25
+ - [DataMap Tools](#datamap-tools)
26
+ - [Session Management](#session-management)
27
+ - [Token Format](#token-format)
28
+ - [Token Generation](#token-generation)
29
+ - [Token Validation](#token-validation)
30
+ - [Proxy Detection](#proxy-detection)
31
+ - [Security Layers](#security-layers)
32
+ - [Basic Authentication](#basic-authentication)
33
+ - [CORS](#cors)
34
+ - [Security Headers](#security-headers)
35
+ - [Rate Limiting](#rate-limiting)
36
+ - [Allowed Hosts](#allowed-hosts)
37
+ - [Request Size Limits](#request-size-limits)
38
+ - [Extension Points](#extension-points)
39
+
40
+ ---
41
+
42
+ ## Overview
43
+
44
+ The SignalWire AI Agents TypeScript SDK implements an agent-as-microservice architecture. Each agent is an HTTP server that serves SWML documents and handles SWAIG tool callbacks. The SDK is built on the [Hono](https://hono.dev/) web framework and uses Node.js crypto for session management.
45
+
46
+ ```
47
+ +--------------------------------------------------+
48
+ | AgentBase |
49
+ | |
50
+ | +----------------+ +---------------------+ |
51
+ | | PromptManager | | SessionManager | |
52
+ | | +----------+ | | (HMAC tokens, | |
53
+ | | |PomBuilder| | | session metadata) | |
54
+ | | +----------+ | +---------------------+ |
55
+ | +----------------+ |
56
+ | |
57
+ | +----------------+ +---------------------+ |
58
+ | | SwmlBuilder | | Tool Registry | |
59
+ | | (5-phase SWML | | Map<string, | |
60
+ | | document) | | SwaigFunction | | |
61
+ | +----------------+ | Record<>> | | |
62
+ | +---------------------+ |
63
+ | |
64
+ | +----------------+ +---------------------+ |
65
+ | | ContextBuilder | | SkillManager | |
66
+ | | (optional) | | (optional) | |
67
+ | +----------------+ +---------------------+ |
68
+ | |
69
+ | +--------------------------------------------+ |
70
+ | | Hono HTTP App | |
71
+ | | GET/POST / -> renderSwml() | |
72
+ | | POST /swaig -> tool dispatch | |
73
+ | | POST /post_prompt -> onSummary() | |
74
+ | | GET /health -> { status: ok } | |
75
+ | | GET /ready -> { status: ready } | |
76
+ | +--------------------------------------------+ |
77
+ +--------------------------------------------------+
78
+
79
+ Inbound Call
80
+ |
81
+ v
82
+ SignalWire Platform --GET/POST--> AgentBase / (returns SWML)
83
+ |
84
+ v
85
+ AI engine processes call using SWML config
86
+ |
87
+ v
88
+ AI invokes tool --POST--> AgentBase /swaig (executes handler)
89
+ |
90
+ v
91
+ Call ends --POST--> AgentBase /post_prompt (delivers summary)
92
+ ```
93
+
94
+ ### AgentServer (Multi-Agent)
95
+
96
+ For hosting multiple agents on a single port, `AgentServer` mounts each agent's Hono app at its route prefix:
97
+
98
+ ```
99
+ AgentServer (Hono)
100
+ |-- /sales -> SalesAgent.getApp()
101
+ |-- /support -> SupportAgent.getApp()
102
+ |-- /health -> global health check
103
+ |-- /ready -> global readiness check
104
+ |-- / -> agent listing (if no agent mounted at /)
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Core Components
110
+
111
+ ### AgentBase
112
+
113
+ **File**: `src/AgentBase.ts` (~1280 lines)
114
+
115
+ The central class that composes all other components. It is responsible for:
116
+
117
+ - **HTTP server lifecycle**: Creating and configuring the Hono app, binding middleware, registering routes, starting the server.
118
+ - **SWML rendering**: Assembling the 5-phase SWML document from all configured verbs, prompts, tools, and AI parameters.
119
+ - **Tool dispatch**: Routing incoming SWAIG function calls to the correct handler, validating secure tokens.
120
+ - **Proxy detection**: Auto-detecting reverse proxy configurations from request headers.
121
+ - **Dynamic configuration**: Creating ephemeral copies and invoking the dynamic config callback per-request.
122
+ - **Lifecycle hooks**: Providing `onSummary`, `onFunctionCall`, `onSwmlRequest`, `onDebugEvent`, and `validateBasicAuth` for subclass customization.
123
+
124
+ Key internal state:
125
+
126
+ ```typescript
127
+ // Managers (composition)
128
+ private promptManager: PromptManager;
129
+ private sessionManager: SessionManager;
130
+ private swmlBuilder: SwmlBuilder;
131
+ private toolRegistry: Map<string, SwaigFunction | Record<string, unknown>>;
132
+
133
+ // AI configuration
134
+ private hints: string[];
135
+ private languages: LanguageConfig[];
136
+ private pronounce: PronunciationRule[];
137
+ private params: Record<string, unknown>;
138
+ private globalData: Record<string, unknown>;
139
+ private promptLlmParams: Record<string, unknown>;
140
+ private postPromptLlmParams: Record<string, unknown>;
141
+
142
+ // Call flow verbs (5 phases)
143
+ private preAnswerVerbs: [string, Record<string, unknown>][];
144
+ private answerConfig: Record<string, unknown>;
145
+ private postAnswerVerbs: [string, Record<string, unknown>][];
146
+ private postAiVerbs: [string, Record<string, unknown>][];
147
+
148
+ // Dynamic config
149
+ private dynamicConfigCallback: DynamicConfigCallback | null;
150
+ ```
151
+
152
+ ### PromptManager and PomBuilder
153
+
154
+ **Files**: `src/PromptManager.ts`, `src/PomBuilder.ts`
155
+
156
+ `PromptManager` is a facade that manages the agent's prompt text. It supports two modes:
157
+
158
+ 1. **Raw text mode** -- A plain string set via `setPromptText()`. When set, it takes precedence over POM.
159
+ 2. **POM mode** -- A `PomBuilder` instance that assembles structured sections into Markdown.
160
+
161
+ `PomBuilder` maintains an ordered list of `PomSection` objects. Each section has:
162
+
163
+ - `title` -- Rendered as a Markdown heading (e.g., `## Title`).
164
+ - `body` -- Paragraph text below the heading.
165
+ - `bullets` -- Rendered as `- item` or `1. item` (numbered).
166
+ - `subsections` -- Nested `PomSection` instances rendered at a deeper heading level.
167
+
168
+ The rendering pipeline:
169
+
170
+ ```
171
+ PomBuilder.renderMarkdown()
172
+ -> for each PomSection:
173
+ PomSection.renderMarkdown(level=2, sectionNumber=[])
174
+ -> heading: "## [N.] Title"
175
+ -> body paragraph
176
+ -> bullet list (- or 1.)
177
+ -> recurse into subsections at level+1
178
+ ```
179
+
180
+ `PromptManager.getPrompt()` returns:
181
+ 1. `rawText` if set (bypasses POM).
182
+ 2. `pom.renderMarkdown()` if POM sections exist.
183
+ 3. Empty string otherwise.
184
+
185
+ ### SwmlBuilder
186
+
187
+ **File**: `src/SwmlBuilder.ts` (~62 lines)
188
+
189
+ A minimal builder that assembles SWML documents. A SWML document has the structure:
190
+
191
+ ```json
192
+ {
193
+ "version": "1.0.0",
194
+ "sections": {
195
+ "main": [
196
+ { "answer": {} },
197
+ { "ai": { ... } },
198
+ { "hangup": {} }
199
+ ]
200
+ }
201
+ }
202
+ ```
203
+
204
+ Key methods:
205
+
206
+ | Method | Description |
207
+ |--------|-------------|
208
+ | `reset()` | Clears the document back to an empty `{ version, sections: { main: [] } }`. |
209
+ | `addVerb(verbName, config)` | Appends `{ [verbName]: config }` to the `main` section. |
210
+ | `addVerbToSection(section, verb, config)` | Appends to a named section (creates if absent). |
211
+ | `renderDocument()` | Returns `JSON.stringify(document)`. |
212
+
213
+ `SwmlBuilder` is stateless between render cycles. `AgentBase.renderSwml()` calls `reset()` at the start, builds the 5 phases by calling `addVerb()`, then calls `renderDocument()`.
214
+
215
+ ### SwaigFunction
216
+
217
+ **File**: `src/SwaigFunction.ts` (~168 lines)
218
+
219
+ Wraps a tool handler with metadata for SWAIG registration:
220
+
221
+ ```typescript
222
+ class SwaigFunction {
223
+ name: string; // Tool name
224
+ handler: SwaigHandler; // The callback function
225
+ description: string; // Shown to the AI
226
+ parameters: Record; // JSON Schema for arguments
227
+ secure: boolean; // Requires HMAC token
228
+ fillers?: Record; // Per-language filler phrases
229
+ waitFile?: string; // Audio to play while executing
230
+ waitFileLoops?: number; // Loop count for wait file
231
+ webhookUrl?: string; // External webhook (bypasses local handler)
232
+ required: string[]; // Required parameter names
233
+ extraFields: Record; // Additional SWAIG fields
234
+ }
235
+ ```
236
+
237
+ Two primary operations:
238
+
239
+ 1. **`execute(args, rawData)`** -- Invokes the handler and normalizes the result:
240
+ - `SwaigFunctionResult` -> `result.toDict()`
241
+ - Object with `response` key -> returned as-is
242
+ - Plain object -> wrapped with "Function completed successfully" message
243
+ - String -> wrapped in `SwaigFunctionResult`
244
+ - Exception -> returns a generic error message
245
+
246
+ 2. **`toSwaig(baseUrl, token?, callId?)`** -- Serializes to the SWAIG wire format for SWML.
247
+
248
+ ### SwaigFunctionResult
249
+
250
+ **File**: `src/SwaigFunctionResult.ts` (~864 lines)
251
+
252
+ A fluent builder for SWAIG function responses. Carries:
253
+
254
+ - `response: string` -- Text returned to the AI.
255
+ - `action: Record<string, unknown>[]` -- Ordered list of actions.
256
+ - `postProcess: boolean` -- Whether actions run after the AI responds.
257
+
258
+ The `toDict()` serialization:
259
+
260
+ ```typescript
261
+ toDict(): Record<string, unknown> {
262
+ const result: Record<string, unknown> = {};
263
+ if (this.response) result['response'] = this.response;
264
+ if (this.action.length > 0) result['action'] = this.action;
265
+ if (this.postProcess && this.action.length > 0) result['post_process'] = true;
266
+ if (Object.keys(result).length === 0) result['response'] = 'Action completed.';
267
+ return result;
268
+ }
269
+ ```
270
+
271
+ The fallback `"Action completed."` ensures the response is never empty.
272
+
273
+ Action categories:
274
+
275
+ | Category | Methods |
276
+ |----------|---------|
277
+ | Call control | `connect()`, `hangup()`, `hold()`, `stop()`, `waitForUser()` |
278
+ | Audio | `say()`, `playBackgroundFile()`, `stopBackgroundFile()` |
279
+ | Speech | `addDynamicHints()`, `clearDynamicHints()`, `setEndOfSpeechTimeout()` |
280
+ | Data | `updateGlobalData()`, `removeGlobalData()`, `setMetadata()`, `removeMetadata()` |
281
+ | SWML | `executeSwml()`, `swmlChangeStep()`, `swmlChangeContext()`, `swmlUserEvent()` |
282
+ | Context | `switchContext()` |
283
+ | Functions | `toggleFunctions()`, `enableFunctionsOnTimeout()`, `updateSettings()` |
284
+ | Comms | `sendSms()`, `recordCall()`, `stopRecordCall()`, `tap()`, `stopTap()`, `sipRefer()` |
285
+ | Rooms | `joinRoom()`, `joinConference()` |
286
+ | RPC | `executeRpc()`, `rpcDial()`, `rpcAiMessage()`, `rpcAiUnhold()` |
287
+ | Payments | `pay()` |
288
+
289
+ ### SessionManager
290
+
291
+ **File**: `src/SessionManager.ts` (~161 lines)
292
+
293
+ Stateless HMAC-SHA256 token manager. See [Session Management](#session-management) for details.
294
+
295
+ ### ContextBuilder
296
+
297
+ **File**: `src/ContextBuilder.ts` (~797 lines)
298
+
299
+ Builds multi-context, multi-step conversation workflows. A `ContextBuilder` contains named `Context` objects, each containing ordered `Step` objects. Steps have prompt content, completion criteria, function restrictions, and navigation rules.
300
+
301
+ ```
302
+ ContextBuilder
303
+ |-- Context "greeting"
304
+ | |-- Step "welcome" (text, criteria, functions)
305
+ | |-- Step "identify" (gather_info questions)
306
+ |
307
+ |-- Context "support"
308
+ |-- Step "diagnose" (POM sections, valid_steps)
309
+ |-- Step "resolve" (end: true)
310
+ ```
311
+
312
+ Contexts are serialized into the `contexts` key of the `ai` verb in SWML.
313
+
314
+ ### DataMap
315
+
316
+ **File**: `src/DataMap.ts` (~365 lines)
317
+
318
+ Fluent builder for server-side SWAIG tools that execute on SignalWire without webhook callbacks. A DataMap can include:
319
+
320
+ - **Webhooks**: HTTP calls to external APIs with response template interpolation.
321
+ - **Expressions**: Pattern-matching rules evaluated against template variables.
322
+ - **Output templates**: `SwaigFunctionResult` instances serialized for response formatting.
323
+
324
+ DataMap tools are registered as raw dictionary objects in the tool registry (not `SwaigFunction` instances).
325
+
326
+ ---
327
+
328
+ ## Composition Architecture
329
+
330
+ The Python SDK uses 8 mixins to compose agent behavior. The TypeScript SDK replaces this with **internal composition** -- `AgentBase` holds manager instances as private fields:
331
+
332
+ ```
333
+ Python SDK (Mixins) TypeScript SDK (Composition)
334
+ -------------------------- ---------------------------------
335
+ AgentBase( class AgentBase {
336
+ PromptMixin, private promptManager: PromptManager;
337
+ SwaigMixin, private toolRegistry: Map<...>;
338
+ SessionMixin, private sessionManager: SessionManager;
339
+ SwmlMixin, private swmlBuilder: SwmlBuilder;
340
+ HttpMixin, private _app: Hono;
341
+ ConfigMixin, private dynamicConfigCallback: ...;
342
+ HintsMixin, private hints: string[];
343
+ LanguageMixin private languages: LanguageConfig[];
344
+ ) }
345
+ ```
346
+
347
+ Design decisions:
348
+
349
+ 1. **No multiple inheritance**: TypeScript does not natively support mixins in the same way Python does. Composition provides cleaner encapsulation.
350
+ 2. **Private managers**: `PromptManager`, `SessionManager`, and `SwmlBuilder` are private. Public methods on `AgentBase` delegate to them (e.g., `setPromptText()` delegates to `promptManager.setPromptText()`).
351
+ 3. **Tool registry is a Map**: Supports both `SwaigFunction` instances (handler-based tools) and plain `Record<string, unknown>` objects (DataMap/raw definitions).
352
+ 4. **Single public surface**: All configuration happens through `AgentBase` methods. Users do not need to interact with internal managers directly.
353
+ 5. **Ephemeral copies**: The `createEphemeralCopy()` method shallow-copies the agent, then deep-copies all mutable collections (hints, languages, params, verbs, tool registry, etc.) and creates fresh `PromptManager` and `SwmlBuilder` instances.
354
+
355
+ ---
356
+
357
+ ## SWML Rendering Pipeline
358
+
359
+ The `renderSwml(callId?)` method on `AgentBase` assembles the complete SWML document in five sequential phases:
360
+
361
+ ```
362
+ renderSwml(callId?)
363
+ |
364
+ |-- 1. swmlBuilder.reset()
365
+ |
366
+ |-- 2. Build webhook URLs (swaig, post_prompt)
367
+ | - Combine proxy base URL + route + endpoint
368
+ | - Embed basic auth credentials in URL
369
+ | - Append query params (swaigQueryParams, __token)
370
+ |
371
+ |-- 3. Build SWAIG object
372
+ | - native_functions[]
373
+ | - includes[]
374
+ | - internal_fillers{}
375
+ | - functions[] (iterate toolRegistry)
376
+ | For each SwaigFunction:
377
+ | - name, description, parameters (ensure schema structure)
378
+ | - fillers, wait_file, wait_file_loops
379
+ | - web_hook_url (with token for secure tools)
380
+ | For each raw dict (DataMap):
381
+ | - spread as-is with function name
382
+ | - defaults.web_hook_url
383
+ |
384
+ |-- PHASE 1: Pre-Answer Verbs
385
+ | for [verb, config] of preAnswerVerbs:
386
+ | swmlBuilder.addVerb(verb, config)
387
+ |
388
+ |-- PHASE 2: Answer Verb
389
+ | if autoAnswer:
390
+ | swmlBuilder.addVerb('answer', answerConfig)
391
+ |
392
+ |-- PHASE 3: Post-Answer Verbs
393
+ | if recordCall:
394
+ | swmlBuilder.addVerb('record_call', { format, stereo })
395
+ | for [verb, config] of postAnswerVerbs:
396
+ | swmlBuilder.addVerb(verb, config)
397
+ |
398
+ |-- PHASE 4: AI Verb
399
+ | Build aiConfig object:
400
+ | prompt: { text: ..., ...promptLlmParams }
401
+ | post_prompt: { text: ..., ...postPromptLlmParams }
402
+ | post_prompt_url: ...
403
+ | SWAIG: swaigObj
404
+ | contexts: contextsBuilder.toDict() (if defined)
405
+ | hints: [...]
406
+ | languages: [...]
407
+ | pronounce: [...]
408
+ | params: {...}
409
+ | global_data: {...}
410
+ | debug_webhook_url / debug_webhook_level (if enabled)
411
+ | swmlBuilder.addVerb('ai', aiConfig)
412
+ |
413
+ |-- PHASE 5: Post-AI Verbs
414
+ | for [verb, config] of postAiVerbs:
415
+ | swmlBuilder.addVerb(verb, config)
416
+ |
417
+ |-- Return swmlBuilder.renderDocument() (JSON string)
418
+ ```
419
+
420
+ The resulting SWML document structure:
421
+
422
+ ```json
423
+ {
424
+ "version": "1.0.0",
425
+ "sections": {
426
+ "main": [
427
+ { "play": { "url": "..." } },
428
+ { "answer": {} },
429
+ { "record_call": { "format": "mp4", "stereo": true } },
430
+ {
431
+ "ai": {
432
+ "prompt": { "text": "..." },
433
+ "post_prompt": { "text": "..." },
434
+ "post_prompt_url": "https://...",
435
+ "SWAIG": {
436
+ "functions": [ ... ],
437
+ "defaults": { "web_hook_url": "https://..." }
438
+ },
439
+ "hints": [ "SignalWire", "HIPAA" ],
440
+ "params": { "temperature": 0.7 },
441
+ "global_data": { "company": "Acme" }
442
+ }
443
+ },
444
+ { "hangup": {} }
445
+ ]
446
+ }
447
+ }
448
+ ```
449
+
450
+ ---
451
+
452
+ ## Request Flow
453
+
454
+ ### SWML Request (GET/POST /)
455
+
456
+ ```
457
+ Client Request
458
+ |
459
+ v
460
+ Hono Middleware Stack:
461
+ 1. Security headers (X-Content-Type-Options, X-Frame-Options, etc.)
462
+ 2. Request size limit check (SWML_MAX_REQUEST_SIZE)
463
+ 3. Allowed hosts check (SWML_ALLOWED_HOSTS)
464
+ 4. Rate limiter (SWML_RATE_LIMIT)
465
+ 5. CORS (SWML_CORS_ORIGINS)
466
+ 6. Basic Auth (credential validation)
467
+ |
468
+ v
469
+ handleSwml():
470
+ 1. Parse request body (JSON, or empty for GET)
471
+ 2. detectProxyFromRequest(c) -- inspect headers for proxy info
472
+ 3. await onSwmlRequest(body) -- lifecycle hook
473
+ 4. If dynamicConfigCallback is set:
474
+ a. createEphemeralCopy() -- deep-copy mutable state
475
+ b. Extract queryParams, headers from request
476
+ c. await dynamicConfigCallback(queryParams, body, headers, copy)
477
+ d. Use copy for rendering
478
+ 5. Extract call_id from body (if present)
479
+ 6. agentToUse.renderSwml(callId) -- 5-phase rendering
480
+ 7. Return JSON response
481
+ ```
482
+
483
+ ### SWAIG Function Dispatch (POST /swaig)
484
+
485
+ ```
486
+ Client Request (from SignalWire platform)
487
+ |
488
+ v
489
+ Middleware Stack (same as above)
490
+ |
491
+ v
492
+ handleSwaig():
493
+ 1. Parse request body
494
+ 2. Extract function name from body["function"]
495
+ 3. Look up in toolRegistry
496
+ - Not found -> 404 { error: "Unknown function: ..." }
497
+ - Not a SwaigFunction -> 404
498
+ 4. If fn.secure:
499
+ a. Extract __token from URL query params
500
+ b. Extract call_id from body
501
+ c. sessionManager.validateToken(callId, fnName, token)
502
+ d. Invalid -> 403 { error: "Invalid or expired token" }
503
+ 5. Extract args from body["argument"]
504
+ 6. await onFunctionCall(fnName, args, body) -- lifecycle hook
505
+ 7. result = await fn.execute(args, body)
506
+ 8. Return JSON response (result dict)
507
+ ```
508
+
509
+ ### Post-Prompt Handler (POST /post_prompt)
510
+
511
+ ```
512
+ Client Request (from SignalWire platform, end of call)
513
+ |
514
+ v
515
+ Middleware Stack
516
+ |
517
+ v
518
+ handlePostPrompt():
519
+ 1. Parse request body
520
+ 2. findSummary(body):
521
+ a. Check body["summary"]
522
+ b. Check body["post_prompt_data"]["parsed"][0]
523
+ c. Try JSON.parse(body["post_prompt_data"]["raw"])
524
+ d. Return raw value as fallback
525
+ e. Return null if nothing found
526
+ 3. await onSummary(summary, body) -- lifecycle hook
527
+ 4. Return { ok: true }
528
+ ```
529
+
530
+ ---
531
+
532
+ ## Tool System
533
+
534
+ ### SwaigFunction Wrapper
535
+
536
+ Each tool registered via `defineTool()` creates a `SwaigFunction` instance stored in the `toolRegistry` Map. The registry also supports raw dictionary entries (DataMap tools registered via `registerSwaigFunction()`).
537
+
538
+ ```typescript
539
+ // Handler-based tool
540
+ toolRegistry.set('get_weather', new SwaigFunction({ name, handler, description, ... }));
541
+
542
+ // DataMap (raw dict)
543
+ toolRegistry.set('lookup_zip', { function: 'lookup_zip', description: '...', data_map: {...} });
544
+ ```
545
+
546
+ During SWML rendering, the registry is iterated. `SwaigFunction` instances are serialized with their schema, webhook URLs, and optional tokens. Raw dicts are spread as-is.
547
+
548
+ ### Handler Signature
549
+
550
+ ```typescript
551
+ type SwaigHandler = (
552
+ args: Record<string, unknown>, // AI-extracted arguments
553
+ rawData: Record<string, unknown>, // Full request payload
554
+ ) => SwaigFunctionResult | Record<string, unknown> | string
555
+ | Promise<SwaigFunctionResult | Record<string, unknown> | string>;
556
+ ```
557
+
558
+ The handler is intentionally flexible in its return type. The `execute()` method normalizes all return types into a consistent dict format.
559
+
560
+ ### Result Serialization
561
+
562
+ The `execute()` method in `SwaigFunction` handles all return type normalization:
563
+
564
+ ```
565
+ Handler returns:
566
+ |
567
+ |-- SwaigFunctionResult --> result.toDict()
568
+ |-- { response: "..." } --> returned as-is
569
+ |-- { ... } (no response) --> SwaigFunctionResult("Function completed successfully").toDict()
570
+ |-- "string" --> SwaigFunctionResult(string).toDict()
571
+ |-- throws Error --> SwaigFunctionResult("Sorry, I couldn't...").toDict()
572
+ ```
573
+
574
+ `SwaigFunctionResult.toDict()` produces:
575
+
576
+ ```json
577
+ {
578
+ "response": "The weather is 72F and sunny.",
579
+ "action": [
580
+ { "set_global_data": { "last_city": "Austin" } }
581
+ ],
582
+ "post_process": false
583
+ }
584
+ ```
585
+
586
+ If both `response` and `action` are empty, the fallback `"Action completed."` is used.
587
+
588
+ ### DataMap Tools
589
+
590
+ DataMap tools differ from handler-based tools in that they execute entirely on the SignalWire platform. They are serialized as raw dictionaries with a `data_map` key instead of a `web_hook_url`:
591
+
592
+ ```json
593
+ {
594
+ "function": "check_status",
595
+ "description": "Check order status",
596
+ "parameters": { "type": "object", "properties": { ... } },
597
+ "data_map": {
598
+ "webhooks": [
599
+ {
600
+ "url": "https://api.example.com/orders/${args.order_id}",
601
+ "method": "GET",
602
+ "output": { "response": "Order status: ${status}" }
603
+ }
604
+ ]
605
+ }
606
+ }
607
+ ```
608
+
609
+ ---
610
+
611
+ ## Session Management
612
+
613
+ The `SessionManager` provides stateless HMAC-SHA256 token generation and validation. No server-side session state is stored for tokens -- all information is encoded within the token itself.
614
+
615
+ ### Token Format
616
+
617
+ Tokens are base64url-encoded strings containing five dot-separated fields:
618
+
619
+ ```
620
+ base64url( callId . functionName . expiry . nonce . hmacSignature )
621
+ ```
622
+
623
+ | Field | Description |
624
+ |-------|-------------|
625
+ | `callId` | The call ID this token is bound to. |
626
+ | `functionName` | The SWAIG function name this token authorizes. |
627
+ | `expiry` | Unix timestamp (seconds) when the token expires. |
628
+ | `nonce` | Random 4-byte hex string for uniqueness. |
629
+ | `hmacSignature` | First 16 hex chars of HMAC-SHA256 over `callId:functionName:expiry:nonce`. |
630
+
631
+ ### Token Generation
632
+
633
+ ```typescript
634
+ generateToken(functionName: string, callId: string): string {
635
+ const expiry = Math.floor(Date.now() / 1000) + this.tokenExpirySecs;
636
+ const nonce = randomBytes(4).toString('hex');
637
+ const message = `${callId}:${functionName}:${expiry}:${nonce}`;
638
+ const signature = createHmac('sha256', this.secretKey)
639
+ .update(message)
640
+ .digest('hex')
641
+ .slice(0, 16);
642
+ const token = `${callId}.${functionName}.${expiry}.${nonce}.${signature}`;
643
+ return Buffer.from(token).toString('base64url');
644
+ }
645
+ ```
646
+
647
+ The secret key is a random 32-byte hex string generated at startup (per `SessionManager` instance). Tokens are generated during SWML rendering and embedded in the `__token` query parameter of each secure tool's webhook URL.
648
+
649
+ ### Token Validation
650
+
651
+ On each `/swaig` POST for a secure tool:
652
+
653
+ 1. Decode the base64url token.
654
+ 2. Split into 5 parts; reject if not exactly 5.
655
+ 3. Verify `functionName` matches the requested function.
656
+ 4. Verify `expiry` has not passed.
657
+ 5. Recompute HMAC-SHA256 over the same message format.
658
+ 6. Compare the first 16 hex chars of the computed signature to the token's signature.
659
+ 7. Verify `callId` matches the request's call ID.
660
+
661
+ All checks must pass. Any failure returns a `403 Forbidden` response.
662
+
663
+ ### Session Metadata
664
+
665
+ `SessionManager` also provides an in-memory metadata store keyed by session ID:
666
+
667
+ ```typescript
668
+ setSessionMetadata(sessionId: string, metadata: Record<string, unknown>): void;
669
+ getSessionMetadata(sessionId: string): Record<string, unknown> | undefined;
670
+ deleteSessionMetadata(sessionId: string): boolean;
671
+ ```
672
+
673
+ This is used for storing per-session state that persists across multiple tool invocations within the same call.
674
+
675
+ ---
676
+
677
+ ## Proxy Detection
678
+
679
+ When running behind a reverse proxy (nginx, Cloudflare, AWS ALB, etc.), the agent needs to know its external URL to generate correct webhook URLs in SWML. The proxy detection system checks multiple sources in priority order:
680
+
681
+ ### Detection Priority
682
+
683
+ 1. **Environment variable** (`SWML_PROXY_URL_BASE`) -- Highest priority. Once set, header-based detection is disabled.
684
+ 2. **Manual override** (`manualSetProxyUrl()`) -- Programmatic override.
685
+ 3. **X-Forwarded-Host + X-Forwarded-Proto** headers.
686
+ 4. **Forwarded** header (RFC 7239) -- Parses `host=` and `proto=` directives.
687
+ 5. **X-Original-Host + X-Forwarded-Proto** headers.
688
+ 6. **X-Forwarded-For** -- Detected but cannot determine host; logs a warning suggesting manual configuration.
689
+ 7. **Fallback** -- Uses `http://localhost:{port}{route}`.
690
+
691
+ The detection runs on every SWML request (`detectProxyFromRequest()`). Once the environment variable is set, it is never overridden by header detection.
692
+
693
+ ### Webhook URL Building
694
+
695
+ ```typescript
696
+ buildWebhookUrl(endpoint: string, extraParams?: Record<string, string>): string
697
+ ```
698
+
699
+ This method:
700
+
701
+ 1. Calls `getFullUrl(includeAuth=true)` to get the base URL with embedded basic auth credentials.
702
+ 2. Appends the endpoint path (e.g., `/swaig`, `/post_prompt`).
703
+ 3. Appends query parameters (SWAIG query params, `__token` for secure tools).
704
+
705
+ Example generated URL:
706
+ ```
707
+ https://admin:s3cret@agents.example.com/support/swaig?__token=abc123&tenant=acme
708
+ ```
709
+
710
+ ---
711
+
712
+ ## Security Layers
713
+
714
+ The SDK implements multiple security layers, all configured through the Hono middleware stack.
715
+
716
+ ### Basic Authentication
717
+
718
+ All endpoints except `/health` and `/ready` require HTTP Basic Authentication. Credentials are resolved in priority order:
719
+
720
+ 1. `basicAuth` constructor option.
721
+ 2. `SWML_BASIC_AUTH_USER` / `SWML_BASIC_AUTH_PASSWORD` environment variables.
722
+ 3. Auto-generated (agent name as username, random 8-byte hex as password).
723
+
724
+ The `validateBasicAuth(username, password)` hook can be overridden in subclasses to add custom validation logic (e.g., checking against a database).
725
+
726
+ Credentials are embedded in webhook URLs so that SignalWire can authenticate when calling back to the agent.
727
+
728
+ ### CORS
729
+
730
+ Controlled by `SWML_CORS_ORIGINS`:
731
+
732
+ ```typescript
733
+ // Default: allow all origins
734
+ cors({ origin: '*', credentials: true })
735
+
736
+ // Restricted: SWML_CORS_ORIGINS=https://app.example.com,https://admin.example.com
737
+ cors({ origin: ['https://app.example.com', 'https://admin.example.com'], credentials: true })
738
+ ```
739
+
740
+ ### Security Headers
741
+
742
+ Applied to all responses via middleware:
743
+
744
+ | Header | Value |
745
+ |--------|-------|
746
+ | `X-Content-Type-Options` | `nosniff` |
747
+ | `X-Frame-Options` | `DENY` |
748
+ | `X-XSS-Protection` | `1; mode=block` |
749
+ | `Referrer-Policy` | `strict-origin-when-cross-origin` |
750
+
751
+ ### Rate Limiting
752
+
753
+ When `SWML_RATE_LIMIT` is set (requests per minute per IP):
754
+
755
+ - IP is extracted from `X-Forwarded-For` (first entry), `X-Real-IP`, or falls back to `"unknown"`.
756
+ - An in-memory Map tracks `{ count, resetAt }` per IP.
757
+ - The window resets every 60 seconds.
758
+ - Exceeding the limit returns `429 Too Many Requests`.
759
+
760
+ ### Allowed Hosts
761
+
762
+ When `SWML_ALLOWED_HOSTS` is set (comma-separated hostnames):
763
+
764
+ - The `Host` header is extracted (port stripped, lowercased).
765
+ - If the host is not in the allowed set, returns `403 Forbidden`.
766
+
767
+ ### Request Size Limits
768
+
769
+ When `SWML_MAX_REQUEST_SIZE` is set (bytes, default 1 MB):
770
+
771
+ - The `Content-Length` header is checked.
772
+ - Requests exceeding the limit return `413 Request Too Large`.
773
+
774
+ ---
775
+
776
+ ## Extension Points
777
+
778
+ The SDK provides multiple mechanisms for customizing agent behavior without modifying the core:
779
+
780
+ ### Lifecycle Hooks (Override in Subclass)
781
+
782
+ | Hook | Signature | When Called |
783
+ |------|-----------|------------|
784
+ | `defineTools()` | `protected defineTools(): void` | Called explicitly by the subclass constructor to register tools. |
785
+ | `onSummary(summary, rawData)` | `void \| Promise<void>` | When a post-prompt summary is received at `/post_prompt`. |
786
+ | `onFunctionCall(name, args, rawData)` | `void \| Promise<void>` | Before each SWAIG function handler executes at `/swaig`. |
787
+ | `onSwmlRequest(rawData)` | `void \| Promise<void>` | On every SWML request at `/` before rendering. |
788
+ | `onDebugEvent(event)` | `void \| Promise<void>` | When a debug event webhook is received at `/debug_events`. |
789
+ | `validateBasicAuth(username, password)` | `boolean \| Promise<boolean>` | Called during auth middleware to add custom validation. |
790
+
791
+ ### Dynamic Configuration Callback
792
+
793
+ ```typescript
794
+ agent.setDynamicConfigCallback(async (queryParams, bodyParams, headers, ephemeralAgent) => {
795
+ // Mutate ephemeralAgent -- original agent is unaffected
796
+ });
797
+ ```
798
+
799
+ The callback receives an ephemeral copy created by `createEphemeralCopy()`. This copy has:
800
+
801
+ - Fresh `PromptManager` and `SwmlBuilder` instances.
802
+ - Shallow copies of all mutable arrays and objects (hints, languages, params, globalData, verbs, toolRegistry).
803
+ - The same prototype chain as the original (so subclass methods are available).
804
+
805
+ ### Declarative PROMPT_SECTIONS
806
+
807
+ ```typescript
808
+ static PROMPT_SECTIONS?: {
809
+ title: string;
810
+ body?: string;
811
+ bullets?: string[];
812
+ numbered?: boolean;
813
+ }[];
814
+ ```
815
+
816
+ Applied automatically in the `AgentBase` constructor via `promptAddSection()` for each entry.
817
+
818
+ ### Skills System
819
+
820
+ Skills are modular bundles of tools, prompt sections, hints, and global data:
821
+
822
+ ```typescript
823
+ await agent.addSkill(new WeatherSkill({ apiKey: '...' }));
824
+ ```
825
+
826
+ `addSkill()` iterates the skill's contributed tools, prompt sections, hints, and global data, registering each with the agent.
827
+
828
+ ### Webhook URL Overrides
829
+
830
+ ```typescript
831
+ agent.setWebHookUrl('https://custom-swaig-endpoint.example.com/swaig');
832
+ agent.setPostPromptUrl('https://custom-endpoint.example.com/post_prompt');
833
+ ```
834
+
835
+ These override the auto-generated webhook URLs in the rendered SWML document.
836
+
837
+ ### Debug Events
838
+
839
+ ```typescript
840
+ agent.enableDebugEvents(2); // level 1-3
841
+ ```
842
+
843
+ When enabled, the SWML document includes `debug_webhook_url` and `debug_webhook_level` in the AI configuration. SignalWire posts debug events to `/debug_events`, which calls `onDebugEvent()`.