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,239 @@
1
+ /**
2
+ * Claude Skills - Provides access to Anthropic's Claude AI for sub-queries.
3
+ *
4
+ * Tier 3 built-in skill: requires ANTHROPIC_API_KEY environment variable.
5
+ * Allows the agent to send prompts to Claude for complex reasoning,
6
+ * analysis, or sub-tasks that benefit from a dedicated AI query.
7
+ */
8
+
9
+ import { SkillBase } from '../SkillBase.js';
10
+ import type {
11
+ SkillManifest,
12
+ SkillToolDefinition,
13
+ SkillPromptSection,
14
+ SkillConfig,
15
+ } from '../SkillBase.js';
16
+ import { SwaigFunctionResult } from '../../SwaigFunctionResult.js';
17
+
18
+ /** A single message in the Anthropic Messages API format. */
19
+ interface AnthropicMessage {
20
+ /** Message role ("user" or "assistant"). */
21
+ role: string;
22
+ /** Message content as text or structured content blocks. */
23
+ content: string | Array<{ type: string; text: string }>;
24
+ }
25
+
26
+ /** Successful response shape from the Anthropic Messages API. */
27
+ interface AnthropicResponse {
28
+ /** Unique message ID. */
29
+ id: string;
30
+ /** Response type identifier. */
31
+ type: string;
32
+ /** Role of the responder. */
33
+ role: string;
34
+ /** Array of content blocks in the response. */
35
+ content: Array<{
36
+ type: string;
37
+ text?: string;
38
+ }>;
39
+ /** Model used for the response. */
40
+ model: string;
41
+ /** Reason the response stopped. */
42
+ stop_reason: string;
43
+ /** Token usage statistics. */
44
+ usage: {
45
+ input_tokens: number;
46
+ output_tokens: number;
47
+ };
48
+ /** Error information, if the request failed. */
49
+ error?: {
50
+ type: string;
51
+ message: string;
52
+ };
53
+ }
54
+
55
+ /** Error response shape from the Anthropic API. */
56
+ interface AnthropicErrorResponse {
57
+ /** Always "error" for error responses. */
58
+ type: 'error';
59
+ /** Error details. */
60
+ error: {
61
+ type: string;
62
+ message: string;
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Provides access to Anthropic's Claude AI for sub-queries and complex reasoning.
68
+ *
69
+ * Tier 3 built-in skill. Requires the `ANTHROPIC_API_KEY` environment variable.
70
+ * Supports `model` and `max_tokens` config options to control which Claude model
71
+ * is used and the maximum response length.
72
+ */
73
+ export class ClaudeSkill extends SkillBase {
74
+ /**
75
+ * @param config - Optional configuration; supports `model` and `max_tokens`.
76
+ */
77
+ constructor(config?: SkillConfig) {
78
+ super('claude_skills', config);
79
+ }
80
+
81
+ /** @returns Manifest declaring ANTHROPIC_API_KEY as required and config schema for model/max_tokens. */
82
+ getManifest(): SkillManifest {
83
+ return {
84
+ name: 'claude_skills',
85
+ description:
86
+ 'Provides access to Anthropic Claude AI for complex reasoning, analysis, and sub-queries.',
87
+ version: '1.0.0',
88
+ author: 'SignalWire',
89
+ tags: ['ai', 'claude', 'anthropic', 'reasoning', 'analysis', 'external'],
90
+ requiredEnvVars: ['ANTHROPIC_API_KEY'],
91
+ configSchema: {
92
+ model: {
93
+ type: 'string',
94
+ description: 'The Claude model to use. Defaults to "claude-sonnet-4-5-20250929".',
95
+ default: 'claude-sonnet-4-5-20250929',
96
+ },
97
+ max_tokens: {
98
+ type: 'number',
99
+ description: 'Maximum tokens in the response. Defaults to 1024.',
100
+ default: 1024,
101
+ },
102
+ },
103
+ };
104
+ }
105
+
106
+ /** @returns A single `ask_claude` tool that sends prompts to the Claude API. */
107
+ getTools(): SkillToolDefinition[] {
108
+ const model = this.getConfig<string>('model', 'claude-sonnet-4-5-20250929');
109
+ const maxTokens = this.getConfig<number>('max_tokens', 1024);
110
+
111
+ return [
112
+ {
113
+ name: 'ask_claude',
114
+ description:
115
+ 'Send a prompt to Claude AI for complex reasoning, analysis, summarization, or any task that benefits from dedicated AI processing. Returns Claude\'s response.',
116
+ parameters: {
117
+ prompt: {
118
+ type: 'string',
119
+ description: 'The prompt or question to send to Claude.',
120
+ },
121
+ system_prompt: {
122
+ type: 'string',
123
+ description:
124
+ 'Optional system prompt to set context or instructions for Claude.',
125
+ },
126
+ },
127
+ required: ['prompt'],
128
+ handler: async (args: Record<string, unknown>) => {
129
+ const prompt = args.prompt as string | undefined;
130
+ const systemPrompt = args.system_prompt as string | undefined;
131
+
132
+ if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
133
+ return new SwaigFunctionResult('Please provide a prompt for Claude.');
134
+ }
135
+
136
+ const apiKey = process.env['ANTHROPIC_API_KEY'];
137
+ if (!apiKey) {
138
+ return new SwaigFunctionResult(
139
+ 'Claude AI is not configured. The ANTHROPIC_API_KEY environment variable is required.',
140
+ );
141
+ }
142
+
143
+ try {
144
+ const messages: AnthropicMessage[] = [
145
+ { role: 'user', content: prompt.trim() },
146
+ ];
147
+
148
+ const requestBody: Record<string, unknown> = {
149
+ model,
150
+ max_tokens: maxTokens,
151
+ messages,
152
+ };
153
+
154
+ if (systemPrompt && typeof systemPrompt === 'string' && systemPrompt.trim().length > 0) {
155
+ requestBody['system'] = systemPrompt.trim();
156
+ }
157
+
158
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
159
+ method: 'POST',
160
+ headers: {
161
+ 'Content-Type': 'application/json',
162
+ 'x-api-key': apiKey,
163
+ 'anthropic-version': '2023-06-01',
164
+ },
165
+ body: JSON.stringify(requestBody),
166
+ });
167
+
168
+ if (!response.ok) {
169
+ const errorText = await response.text();
170
+ let errorMsg: string;
171
+ try {
172
+ const errorData = JSON.parse(errorText) as AnthropicErrorResponse;
173
+ errorMsg = errorData.error?.message ?? `HTTP ${response.status}`;
174
+ } catch {
175
+ errorMsg = `HTTP ${response.status}: ${errorText.slice(0, 300)}`;
176
+ }
177
+ return new SwaigFunctionResult(
178
+ `Claude API request failed: ${errorMsg}`,
179
+ );
180
+ }
181
+
182
+ const data = (await response.json()) as AnthropicResponse;
183
+
184
+ if (data.error) {
185
+ return new SwaigFunctionResult(
186
+ `Claude returned an error: ${data.error.message}`,
187
+ );
188
+ }
189
+
190
+ // Extract text content from the response
191
+ const textParts = data.content
192
+ .filter((block) => block.type === 'text' && block.text)
193
+ .map((block) => block.text!);
194
+
195
+ if (textParts.length === 0) {
196
+ return new SwaigFunctionResult(
197
+ 'Claude returned an empty response. Try rephrasing your prompt.',
198
+ );
199
+ }
200
+
201
+ const responseText = textParts.join('\n\n');
202
+ return new SwaigFunctionResult(responseText);
203
+ } catch (err) {
204
+ const message = err instanceof Error ? err.message : String(err);
205
+ return new SwaigFunctionResult(
206
+ `Failed to query Claude AI: ${message}`,
207
+ );
208
+ }
209
+ },
210
+ },
211
+ ];
212
+ }
213
+
214
+ /** @returns Prompt section describing Claude AI delegation capabilities. */
215
+ getPromptSections(): SkillPromptSection[] {
216
+ return [
217
+ {
218
+ title: 'Claude AI Assistant',
219
+ body: 'You can delegate complex reasoning or analysis tasks to Claude AI.',
220
+ bullets: [
221
+ 'Use the ask_claude tool for tasks requiring detailed analysis, summarization, or complex reasoning.',
222
+ 'You can provide an optional system prompt to set specific instructions or context.',
223
+ 'This is useful for tasks like: detailed explanations, code analysis, text summarization, creative writing, or multi-step reasoning.',
224
+ 'Do not use this tool for simple factual questions you already know the answer to.',
225
+ 'Summarize or relay Claude\'s response naturally to the user.',
226
+ ],
227
+ },
228
+ ];
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Factory function for creating ClaudeSkill instances.
234
+ * @param config - Optional skill configuration.
235
+ * @returns A new ClaudeSkill instance.
236
+ */
237
+ export function createSkill(config?: SkillConfig): ClaudeSkill {
238
+ return new ClaudeSkill(config);
239
+ }
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Custom Skills - A meta-skill that registers user-defined tools from configuration.
3
+ *
4
+ * Tier 2 built-in skill: no external dependencies required.
5
+ * Allows users to define arbitrary tools via config without writing skill classes.
6
+ * Each tool definition in the config specifies its name, description, parameters,
7
+ * and a handler function body (executed via Function constructor).
8
+ *
9
+ * This is useful for rapid prototyping, simple integrations, and cases where
10
+ * creating a full skill class would be overkill.
11
+ */
12
+
13
+ import { SkillBase } from '../SkillBase.js';
14
+ import type {
15
+ SkillManifest,
16
+ SkillToolDefinition,
17
+ SkillPromptSection,
18
+ SkillConfig,
19
+ } from '../SkillBase.js';
20
+ import { SwaigFunctionResult } from '../../SwaigFunctionResult.js';
21
+
22
+ /** Parameter definition for a custom tool. */
23
+ interface CustomToolParameter {
24
+ /** Parameter name. */
25
+ name: string;
26
+ /** Parameter type (e.g., "string", "number"). */
27
+ type: string;
28
+ /** Description of the parameter. */
29
+ description: string;
30
+ /** Whether this parameter is required. */
31
+ required?: boolean;
32
+ }
33
+
34
+ /** Full definition of a user-defined custom tool provided via configuration. */
35
+ interface CustomToolDefinition {
36
+ /** Unique tool name. */
37
+ name: string;
38
+ /** Tool description shown to the AI. */
39
+ description: string;
40
+ /** Optional array of parameter definitions. */
41
+ parameters?: CustomToolParameter[];
42
+ /** JavaScript function body executed when the tool is invoked. */
43
+ handler_code: string;
44
+ /** Names of required parameters. */
45
+ required?: string[];
46
+ /** Optional description used in the prompt section instead of the tool description. */
47
+ prompt_description?: string;
48
+ /** Whether to mark the tool as secure. */
49
+ secure?: boolean;
50
+ /** Filler phrases spoken while the tool executes. */
51
+ fillers?: Record<string, string[]>;
52
+ }
53
+
54
+ /** Typed shape of the custom skills configuration object. */
55
+ interface CustomSkillsConfigData {
56
+ /** Array of custom tool definitions. */
57
+ tools?: CustomToolDefinition[];
58
+ /** Custom title for the prompt section. */
59
+ prompt_title?: string;
60
+ /** Custom body text for the prompt section. */
61
+ prompt_body?: string;
62
+ }
63
+
64
+ /**
65
+ * A meta-skill that registers user-defined tools from configuration.
66
+ *
67
+ * Tier 2 built-in skill with no external dependencies. Allows users to define
68
+ * arbitrary tools via config without writing skill classes. Each tool definition
69
+ * specifies a name, description, parameters, and a JavaScript handler function
70
+ * body that is compiled via the Function constructor at instantiation time.
71
+ */
72
+ export class CustomSkillsSkill extends SkillBase {
73
+ private _compiledHandlers: Map<string, Function> = new Map();
74
+ private _compilationErrors: Map<string, string> = new Map();
75
+
76
+ /**
77
+ * @param config - Configuration object containing a `tools` array of custom tool definitions.
78
+ */
79
+ constructor(config?: SkillConfig) {
80
+ super('custom_skills', config);
81
+ this._compileHandlers();
82
+ }
83
+
84
+ /** @returns Manifest with config schema describing the tools array format. */
85
+ getManifest(): SkillManifest {
86
+ return {
87
+ name: 'custom_skills',
88
+ description:
89
+ 'A meta-skill that registers user-defined tools from configuration. Define tools with names, descriptions, parameters, and handler code.',
90
+ version: '1.0.0',
91
+ author: 'SignalWire',
92
+ tags: ['meta', 'custom', 'extensible', 'dynamic'],
93
+ configSchema: {
94
+ tools: {
95
+ type: 'array',
96
+ description:
97
+ 'Array of custom tool definitions: { name, description, parameters?, handler_code, required?, prompt_description?, secure?, fillers? }.',
98
+ items: {
99
+ type: 'object',
100
+ properties: {
101
+ name: { type: 'string', description: 'Tool name (must be unique).' },
102
+ description: { type: 'string', description: 'Tool description for the AI.' },
103
+ parameters: {
104
+ type: 'array',
105
+ description: 'Tool parameters: [{ name, type, description, required? }].',
106
+ },
107
+ handler_code: {
108
+ type: 'string',
109
+ description:
110
+ 'JavaScript function body. Receives (args, rawData, SwaigFunctionResult) as arguments. Must return a SwaigFunctionResult, string, or object.',
111
+ },
112
+ required: {
113
+ type: 'array',
114
+ description: 'Array of required parameter names.',
115
+ },
116
+ prompt_description: {
117
+ type: 'string',
118
+ description: 'Description to include in the prompt section for this tool.',
119
+ },
120
+ secure: { type: 'boolean', description: 'Whether to mark the tool as secure.' },
121
+ fillers: {
122
+ type: 'object',
123
+ description: 'Filler phrases for the tool.',
124
+ },
125
+ },
126
+ required: ['name', 'description', 'handler_code'],
127
+ },
128
+ },
129
+ prompt_title: {
130
+ type: 'string',
131
+ description: 'Custom title for the prompt section. Defaults to "Custom Tools".',
132
+ },
133
+ prompt_body: {
134
+ type: 'string',
135
+ description: 'Custom body text for the prompt section.',
136
+ },
137
+ },
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Pre-compile handler code into functions during construction.
143
+ * This catches syntax errors early and avoids re-compilation on each call.
144
+ */
145
+ private _compileHandlers(): void {
146
+ const toolDefs = this._getToolDefs();
147
+
148
+ for (const toolDef of toolDefs) {
149
+ try {
150
+ // The handler code receives: args, rawData, SwaigFunctionResult
151
+ // It should return a SwaigFunctionResult, string, or plain object
152
+ const handler = new Function(
153
+ 'args',
154
+ 'rawData',
155
+ 'SwaigFunctionResult',
156
+ toolDef.handler_code,
157
+ ) as (
158
+ args: Record<string, unknown>,
159
+ rawData: Record<string, unknown>,
160
+ resultClass: typeof SwaigFunctionResult,
161
+ ) => unknown;
162
+
163
+ this._compiledHandlers.set(toolDef.name, handler);
164
+ } catch (err) {
165
+ const message = err instanceof Error ? err.message : String(err);
166
+ this._compilationErrors.set(toolDef.name, message);
167
+ }
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Get the raw tool definitions from config.
173
+ */
174
+ private _getToolDefs(): CustomToolDefinition[] {
175
+ const configData = this.config as unknown as CustomSkillsConfigData;
176
+ return configData.tools ?? [];
177
+ }
178
+
179
+ /** @returns Array of dynamically generated tools from the configuration, with compiled handlers. */
180
+ getTools(): SkillToolDefinition[] {
181
+ const toolDefs = this._getToolDefs();
182
+ const tools: SkillToolDefinition[] = [];
183
+
184
+ for (const toolDef of toolDefs) {
185
+ // Check for compilation errors
186
+ const compError = this._compilationErrors.get(toolDef.name);
187
+ if (compError) {
188
+ // Still register the tool but with an error handler
189
+ tools.push({
190
+ name: toolDef.name,
191
+ description: toolDef.description,
192
+ parameters: this._buildParameters(toolDef),
193
+ required: toolDef.required,
194
+ secure: toolDef.secure,
195
+ fillers: toolDef.fillers,
196
+ handler: () => {
197
+ return new SwaigFunctionResult(
198
+ `Custom tool "${toolDef.name}" has a compilation error in its handler code: ${compError}. Please fix the handler_code configuration.`,
199
+ );
200
+ },
201
+ });
202
+ continue;
203
+ }
204
+
205
+ const compiledHandler = this._compiledHandlers.get(toolDef.name);
206
+ if (!compiledHandler) {
207
+ continue;
208
+ }
209
+
210
+ tools.push({
211
+ name: toolDef.name,
212
+ description: toolDef.description,
213
+ parameters: this._buildParameters(toolDef),
214
+ required: toolDef.required,
215
+ secure: toolDef.secure,
216
+ fillers: toolDef.fillers,
217
+ handler: async (
218
+ args: Record<string, unknown>,
219
+ rawData: Record<string, unknown>,
220
+ ) => {
221
+ try {
222
+ const result = await compiledHandler(args, rawData, SwaigFunctionResult);
223
+
224
+ // Normalize the result
225
+ if (result instanceof SwaigFunctionResult) {
226
+ return result;
227
+ }
228
+
229
+ if (typeof result === 'string') {
230
+ return new SwaigFunctionResult(result);
231
+ }
232
+
233
+ if (result && typeof result === 'object') {
234
+ return result as Record<string, unknown>;
235
+ }
236
+
237
+ return new SwaigFunctionResult('Action completed.');
238
+ } catch (err) {
239
+ const message = err instanceof Error ? err.message : String(err);
240
+ return new SwaigFunctionResult(
241
+ `Custom tool "${toolDef.name}" encountered a runtime error: ${message}`,
242
+ );
243
+ }
244
+ },
245
+ });
246
+ }
247
+
248
+ return tools;
249
+ }
250
+
251
+ /** @returns Prompt section listing all custom tools and their descriptions. */
252
+ getPromptSections(): SkillPromptSection[] {
253
+ const toolDefs = this._getToolDefs();
254
+ const configData = this.config as unknown as CustomSkillsConfigData;
255
+
256
+ if (toolDefs.length === 0) {
257
+ return [];
258
+ }
259
+
260
+ const title = configData.prompt_title ?? 'Custom Tools';
261
+ const body =
262
+ configData.prompt_body ??
263
+ 'The following custom tools are available for use.';
264
+
265
+ const bullets: string[] = [];
266
+
267
+ for (const toolDef of toolDefs) {
268
+ const compError = this._compilationErrors.get(toolDef.name);
269
+
270
+ if (compError) {
271
+ bullets.push(
272
+ `${toolDef.name}: [ERROR - handler compilation failed] ${toolDef.description}`,
273
+ );
274
+ } else if (toolDef.prompt_description) {
275
+ bullets.push(`${toolDef.name}: ${toolDef.prompt_description}`);
276
+ } else {
277
+ bullets.push(`${toolDef.name}: ${toolDef.description}`);
278
+ }
279
+ }
280
+
281
+ return [
282
+ {
283
+ title,
284
+ body,
285
+ bullets,
286
+ },
287
+ ];
288
+ }
289
+
290
+ /**
291
+ * Build tool parameters from the custom tool definition.
292
+ */
293
+ private _buildParameters(
294
+ toolDef: CustomToolDefinition,
295
+ ): Record<string, unknown> {
296
+ if (!toolDef.parameters || toolDef.parameters.length === 0) {
297
+ return {};
298
+ }
299
+
300
+ const params: Record<string, unknown> = {};
301
+ for (const param of toolDef.parameters) {
302
+ params[param.name] = {
303
+ type: param.type,
304
+ description: param.description,
305
+ };
306
+ }
307
+ return params;
308
+ }
309
+
310
+ /**
311
+ * Get handler compilation errors for diagnostic purposes.
312
+ * @returns A copy of the map from tool name to error message.
313
+ */
314
+ getCompilationErrors(): Map<string, string> {
315
+ return new Map(this._compilationErrors);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Factory function for creating CustomSkillsSkill instances.
321
+ * @param config - Configuration containing a `tools` array of custom tool definitions.
322
+ * @returns A new CustomSkillsSkill instance.
323
+ */
324
+ export function createSkill(config?: SkillConfig): CustomSkillsSkill {
325
+ return new CustomSkillsSkill(config);
326
+ }