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,331 @@
1
+ /**
2
+ * Info Gatherer Skill - Collects structured information from the user.
3
+ *
4
+ * Tier 2 built-in skill: no external dependencies required.
5
+ * Dynamically generates a save_info tool based on configured fields.
6
+ * Fields can have optional validation patterns and required/optional flags.
7
+ * Useful for collecting user details like name, email, phone, address, etc.
8
+ */
9
+
10
+ import { SkillBase } from '../SkillBase.js';
11
+ import type {
12
+ SkillManifest,
13
+ SkillToolDefinition,
14
+ SkillPromptSection,
15
+ SkillConfig,
16
+ } from '../SkillBase.js';
17
+ import { SwaigFunctionResult } from '../../SwaigFunctionResult.js';
18
+
19
+ /** Definition of a single data field to be collected from the user. */
20
+ interface FieldDefinition {
21
+ /** Field name used as the parameter key. */
22
+ name: string;
23
+ /** Description of what this field collects. */
24
+ description: string;
25
+ /** Whether this field must be provided. */
26
+ required?: boolean;
27
+ /** Optional regex pattern for validating the field value. */
28
+ validation?: string;
29
+ /** Parameter type for the tool schema (defaults to "string"). */
30
+ type?: string;
31
+ }
32
+
33
+ /** Key-value map of information gathered from a user during a call. */
34
+ interface GatheredInfo {
35
+ [key: string]: unknown;
36
+ }
37
+
38
+ /**
39
+ * Collects structured information from the user based on configurable fields.
40
+ *
41
+ * Tier 2 built-in skill with no external dependencies. Dynamically generates
42
+ * a `save_info` tool based on the `fields` config array. Fields support
43
+ * optional validation patterns and required/optional flags. Data is stored
44
+ * per-call and optionally merged into global data.
45
+ */
46
+ export class InfoGathererSkill extends SkillBase {
47
+ private gatheredData: Map<string, GatheredInfo> = new Map();
48
+
49
+ /**
50
+ * @param config - Optional configuration; supports `fields`, `purpose`, `confirmation_message`, `store_globally`.
51
+ */
52
+ constructor(config?: SkillConfig) {
53
+ super('info_gatherer', config);
54
+ }
55
+
56
+ /** @returns Manifest with config schema for fields, purpose, confirmation_message, and store_globally. */
57
+ getManifest(): SkillManifest {
58
+ return {
59
+ name: 'info_gatherer',
60
+ description:
61
+ 'Collects structured information from the user based on configurable fields. Validates and stores gathered data.',
62
+ version: '1.0.0',
63
+ author: 'SignalWire',
64
+ tags: ['data-collection', 'form', 'information', 'utility'],
65
+ configSchema: {
66
+ fields: {
67
+ type: 'array',
68
+ description:
69
+ 'Array of field definitions to collect: { name, description, required?, validation?, type? }.',
70
+ items: {
71
+ type: 'object',
72
+ properties: {
73
+ name: { type: 'string', description: 'Field name (used as the parameter key).' },
74
+ description: { type: 'string', description: 'Description of what this field collects.' },
75
+ required: { type: 'boolean', description: 'Whether this field is required. Defaults to false.' },
76
+ validation: {
77
+ type: 'string',
78
+ description: 'Optional regex pattern for validation (e.g., "^[\\\\w.+-]+@[\\\\w-]+\\\\.[\\\\w.]+$" for email).',
79
+ },
80
+ type: {
81
+ type: 'string',
82
+ description: 'Parameter type for the tool schema. Defaults to "string".',
83
+ },
84
+ },
85
+ required: ['name', 'description'],
86
+ },
87
+ },
88
+ purpose: {
89
+ type: 'string',
90
+ description:
91
+ 'A description of why this information is being collected (shown in prompt).',
92
+ },
93
+ confirmation_message: {
94
+ type: 'string',
95
+ description:
96
+ 'Custom message returned after successful info collection.',
97
+ },
98
+ store_globally: {
99
+ type: 'boolean',
100
+ description:
101
+ 'Whether to store gathered info in global data. Defaults to false.',
102
+ },
103
+ },
104
+ };
105
+ }
106
+
107
+ /** @returns A `save_info` tool (dynamic params from config) and a `get_gathered_info` retrieval tool. */
108
+ getTools(): SkillToolDefinition[] {
109
+ const fields = this.getConfig<FieldDefinition[]>('fields', []);
110
+ const confirmationMessage = this.getConfig<string>(
111
+ 'confirmation_message',
112
+ 'Information has been saved successfully.',
113
+ );
114
+ const storeGlobally = this.getConfig<boolean>('store_globally', false);
115
+
116
+ if (fields.length === 0) {
117
+ return [];
118
+ }
119
+
120
+ // Build dynamic parameters from field definitions
121
+ const parameters: Record<string, unknown> = {};
122
+ const requiredParams: string[] = [];
123
+
124
+ for (const field of fields) {
125
+ parameters[field.name] = {
126
+ type: field.type ?? 'string',
127
+ description: field.description,
128
+ };
129
+
130
+ if (field.required) {
131
+ requiredParams.push(field.name);
132
+ }
133
+ }
134
+
135
+ const tools: SkillToolDefinition[] = [
136
+ {
137
+ name: 'save_info',
138
+ description:
139
+ 'Save information gathered from the user. Call this once you have collected the required fields.',
140
+ parameters,
141
+ required: requiredParams.length > 0 ? requiredParams : undefined,
142
+ handler: (args: Record<string, unknown>, rawData: Record<string, unknown>) => {
143
+ const errors: string[] = [];
144
+ const collected: GatheredInfo = {};
145
+
146
+ // Validate each field
147
+ for (const field of fields) {
148
+ const value = args[field.name];
149
+
150
+ // Check required fields
151
+ if (field.required) {
152
+ if (value === undefined || value === null || (typeof value === 'string' && value.trim().length === 0)) {
153
+ errors.push(`"${field.name}" is required but was not provided.`);
154
+ continue;
155
+ }
156
+ }
157
+
158
+ // Skip optional fields that weren't provided
159
+ if (value === undefined || value === null) {
160
+ continue;
161
+ }
162
+
163
+ // Validate against pattern if specified
164
+ if (field.validation && typeof value === 'string') {
165
+ try {
166
+ const regex = new RegExp(field.validation);
167
+ if (!regex.test(value)) {
168
+ errors.push(
169
+ `"${field.name}" value "${value}" does not match the expected format.`,
170
+ );
171
+ continue;
172
+ }
173
+ } catch {
174
+ // If regex is invalid, skip validation
175
+ }
176
+ }
177
+
178
+ collected[field.name] = typeof value === 'string' ? value.trim() : value;
179
+ }
180
+
181
+ if (errors.length > 0) {
182
+ return new SwaigFunctionResult(
183
+ `Could not save information due to validation errors:\n${errors.join('\n')}\nPlease correct these fields and try again.`,
184
+ );
185
+ }
186
+
187
+ if (Object.keys(collected).length === 0) {
188
+ return new SwaigFunctionResult(
189
+ 'No information was provided. Please collect at least one field before saving.',
190
+ );
191
+ }
192
+
193
+ // Store the gathered data keyed by call ID if available
194
+ const callId = (rawData['call_id'] as string | undefined) ?? 'default';
195
+ this.gatheredData.set(callId, {
196
+ ...this.gatheredData.get(callId),
197
+ ...collected,
198
+ });
199
+
200
+ // Build confirmation with collected fields summary
201
+ const fieldSummary = Object.entries(collected)
202
+ .map(([key, val]) => `${key}: ${val}`)
203
+ .join(', ');
204
+
205
+ const result = new SwaigFunctionResult(
206
+ `${confirmationMessage} Saved: ${fieldSummary}.`,
207
+ );
208
+
209
+ // Optionally store in global data
210
+ if (storeGlobally) {
211
+ result.updateGlobalData({ gathered_info: collected });
212
+ }
213
+
214
+ return result;
215
+ },
216
+ },
217
+ {
218
+ name: 'get_gathered_info',
219
+ description:
220
+ 'Retrieve previously gathered information for the current call. Useful to review what has already been collected.',
221
+ parameters: {},
222
+ handler: (_args: Record<string, unknown>, rawData: Record<string, unknown>) => {
223
+ const callId = (rawData['call_id'] as string | undefined) ?? 'default';
224
+ const info = this.gatheredData.get(callId);
225
+
226
+ if (!info || Object.keys(info).length === 0) {
227
+ return new SwaigFunctionResult(
228
+ 'No information has been gathered yet for this call.',
229
+ );
230
+ }
231
+
232
+ const summary = Object.entries(info)
233
+ .map(([key, val]) => `${key}: ${val}`)
234
+ .join(', ');
235
+
236
+ return new SwaigFunctionResult(
237
+ `Previously gathered information: ${summary}.`,
238
+ );
239
+ },
240
+ },
241
+ ];
242
+
243
+ return tools;
244
+ }
245
+
246
+ /** @returns Prompt section listing required/optional fields and collection instructions. */
247
+ getPromptSections(): SkillPromptSection[] {
248
+ const fields = this.getConfig<FieldDefinition[]>('fields', []);
249
+ const purpose = this.getConfig<string | undefined>('purpose', undefined);
250
+
251
+ if (fields.length === 0) {
252
+ return [];
253
+ }
254
+
255
+ const requiredFields = fields.filter((f) => f.required);
256
+ const optionalFields = fields.filter((f) => !f.required);
257
+
258
+ const bullets: string[] = [];
259
+
260
+ if (purpose) {
261
+ bullets.push(`Purpose: ${purpose}`);
262
+ }
263
+
264
+ bullets.push(
265
+ 'Use the save_info tool to store information once you have collected it from the user.',
266
+ );
267
+
268
+ if (requiredFields.length > 0) {
269
+ const reqNames = requiredFields.map((f) => `"${f.name}"`).join(', ');
270
+ bullets.push(
271
+ `Required fields (must be collected): ${reqNames}.`,
272
+ );
273
+ }
274
+
275
+ if (optionalFields.length > 0) {
276
+ const optNames = optionalFields.map((f) => `"${f.name}"`).join(', ');
277
+ bullets.push(
278
+ `Optional fields (collect if available): ${optNames}.`,
279
+ );
280
+ }
281
+
282
+ // Add field descriptions
283
+ for (const field of fields) {
284
+ const reqLabel = field.required ? ' (required)' : ' (optional)';
285
+ bullets.push(`${field.name}${reqLabel}: ${field.description}`);
286
+ }
287
+
288
+ bullets.push(
289
+ 'Ask for information naturally in conversation rather than as a list of questions.',
290
+ 'Use get_gathered_info to review what has already been collected.',
291
+ 'Once all required fields are gathered, call save_info to store the data.',
292
+ );
293
+
294
+ return [
295
+ {
296
+ title: 'Information Collection',
297
+ body: 'You need to collect specific information from the user during this conversation.',
298
+ bullets,
299
+ },
300
+ ];
301
+ }
302
+
303
+ /**
304
+ * Get all gathered data, keyed by call ID.
305
+ * @returns A copy of the internal gathered data map.
306
+ */
307
+ getAllGatheredData(): Map<string, GatheredInfo> {
308
+ return new Map(this.gatheredData);
309
+ }
310
+
311
+ /**
312
+ * Clear gathered data for a specific call or all calls.
313
+ * @param callId - If provided, clear data for this call only; otherwise clear all.
314
+ */
315
+ clearGatheredData(callId?: string): void {
316
+ if (callId) {
317
+ this.gatheredData.delete(callId);
318
+ } else {
319
+ this.gatheredData.clear();
320
+ }
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Factory function for creating InfoGathererSkill instances.
326
+ * @param config - Optional skill configuration.
327
+ * @returns A new InfoGathererSkill instance.
328
+ */
329
+ export function createSkill(config?: SkillConfig): InfoGathererSkill {
330
+ return new InfoGathererSkill(config);
331
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Joke Skill - Tells random jokes from a built-in collection.
3
+ *
4
+ * Tier 1 built-in skill: no external dependencies required.
5
+ * Contains a curated set of jokes across multiple categories.
6
+ */
7
+
8
+ import { SkillBase } from '../SkillBase.js';
9
+ import type {
10
+ SkillManifest,
11
+ SkillToolDefinition,
12
+ SkillPromptSection,
13
+ SkillConfig,
14
+ } from '../SkillBase.js';
15
+ import { SwaigFunctionResult } from '../../SwaigFunctionResult.js';
16
+
17
+ /** Internal representation of a joke with setup and punchline. */
18
+ interface Joke {
19
+ /** Joke category (e.g., "general", "programming", "dad"). */
20
+ category: string;
21
+ /** The joke setup line. */
22
+ setup: string;
23
+ /** The joke punchline. */
24
+ punchline: string;
25
+ }
26
+
27
+ const JOKES: Joke[] = [
28
+ // General
29
+ {
30
+ category: 'general',
31
+ setup: 'Why did the scarecrow win an award?',
32
+ punchline: 'Because he was outstanding in his field.',
33
+ },
34
+ {
35
+ category: 'general',
36
+ setup: 'What do you call a fake noodle?',
37
+ punchline: 'An impasta.',
38
+ },
39
+ {
40
+ category: 'general',
41
+ setup: 'Why did the bicycle fall over?',
42
+ punchline: 'Because it was two-tired.',
43
+ },
44
+ // Programming
45
+ {
46
+ category: 'programming',
47
+ setup: 'Why do programmers prefer dark mode?',
48
+ punchline: 'Because light attracts bugs.',
49
+ },
50
+ {
51
+ category: 'programming',
52
+ setup: 'What is a programmer\'s favorite hangout place?',
53
+ punchline: 'Foo Bar.',
54
+ },
55
+ {
56
+ category: 'programming',
57
+ setup: 'Why do Java developers wear glasses?',
58
+ punchline: 'Because they can\'t C#.',
59
+ },
60
+ {
61
+ category: 'programming',
62
+ setup: 'How many programmers does it take to change a light bulb?',
63
+ punchline: 'None. That\'s a hardware problem.',
64
+ },
65
+ // Dad jokes
66
+ {
67
+ category: 'dad',
68
+ setup: 'I\'m reading a book about anti-gravity.',
69
+ punchline: 'It\'s impossible to put down.',
70
+ },
71
+ {
72
+ category: 'dad',
73
+ setup: 'What did the ocean say to the beach?',
74
+ punchline: 'Nothing, it just waved.',
75
+ },
76
+ {
77
+ category: 'dad',
78
+ setup: 'Why don\'t skeletons fight each other?',
79
+ punchline: 'They don\'t have the guts.',
80
+ },
81
+ ];
82
+
83
+ const VALID_CATEGORIES = ['general', 'programming', 'dad'];
84
+
85
+ /**
86
+ * Tells random jokes from a curated built-in collection.
87
+ *
88
+ * Tier 1 built-in skill with no external dependencies. Includes general,
89
+ * programming, and dad joke categories.
90
+ */
91
+ export class JokeSkill extends SkillBase {
92
+ /**
93
+ * @param config - Optional configuration (no config keys used by this skill).
94
+ */
95
+ constructor(config?: SkillConfig) {
96
+ super('joke', config);
97
+ }
98
+
99
+ /** @returns Manifest with skill metadata and tags. */
100
+ getManifest(): SkillManifest {
101
+ return {
102
+ name: 'joke',
103
+ description: 'Tells random jokes from a built-in collection across several categories.',
104
+ version: '1.0.0',
105
+ tags: ['entertainment', 'joke', 'humor'],
106
+ };
107
+ }
108
+
109
+ /** @returns A single `tell_joke` tool that returns a random joke with optional category filter. */
110
+ getTools(): SkillToolDefinition[] {
111
+ return [
112
+ {
113
+ name: 'tell_joke',
114
+ description:
115
+ 'Tell a random joke. Optionally specify a category to get a joke from that category.',
116
+ parameters: {
117
+ category: {
118
+ type: 'string',
119
+ description:
120
+ 'Joke category: "general", "programming", or "dad". If not specified, a random joke from any category is returned.',
121
+ },
122
+ },
123
+ handler: (args: Record<string, unknown>) => {
124
+ const category = args.category as string | undefined;
125
+
126
+ let pool = JOKES;
127
+
128
+ if (category && typeof category === 'string') {
129
+ const normalized = category.toLowerCase().trim();
130
+
131
+ if (!VALID_CATEGORIES.includes(normalized)) {
132
+ return new SwaigFunctionResult(
133
+ `Unknown joke category "${category}". Available categories are: ${VALID_CATEGORIES.join(', ')}.`,
134
+ );
135
+ }
136
+
137
+ pool = JOKES.filter((j) => j.category === normalized);
138
+ }
139
+
140
+ if (pool.length === 0) {
141
+ return new SwaigFunctionResult(
142
+ 'Sorry, I could not find any jokes for that category.',
143
+ );
144
+ }
145
+
146
+ const joke = pool[Math.floor(Math.random() * pool.length)]!;
147
+
148
+ return new SwaigFunctionResult(
149
+ `${joke.setup} ... ${joke.punchline}`,
150
+ );
151
+ },
152
+ },
153
+ ];
154
+ }
155
+
156
+ /** @returns Prompt section instructing the AI how to deliver jokes. */
157
+ getPromptSections(): SkillPromptSection[] {
158
+ return [
159
+ {
160
+ title: 'Jokes',
161
+ body: 'You have the ability to tell jokes to lighten the mood.',
162
+ bullets: [
163
+ 'Use the tell_joke tool when a user asks for a joke or when humor is appropriate.',
164
+ 'Available joke categories: general, programming, and dad jokes.',
165
+ 'If the user asks for a specific type of joke, pass the category parameter.',
166
+ 'Deliver the joke naturally: say the setup, pause briefly, then deliver the punchline.',
167
+ ],
168
+ },
169
+ ];
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Factory function for creating JokeSkill instances.
175
+ * @param config - Optional skill configuration.
176
+ * @returns A new JokeSkill instance.
177
+ */
178
+ export function createSkill(config?: SkillConfig): JokeSkill {
179
+ return new JokeSkill(config);
180
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Math Skill - Evaluates mathematical expressions safely.
3
+ *
4
+ * Tier 1 built-in skill: no external dependencies required.
5
+ * Uses a safe parser that only allows digits, basic arithmetic operators,
6
+ * parentheses, decimal points, and spaces.
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
+ /**
19
+ * Validate that an expression contains only safe mathematical characters.
20
+ * Allowed: digits (0-9), operators (+, -, *, /, ^, %), parentheses, decimal points, spaces.
21
+ * @param expr - The expression string to validate.
22
+ * @returns True if the expression contains only allowed characters.
23
+ */
24
+ function isSafeExpression(expr: string): boolean {
25
+ return /^[\d+\-*/^%().\s]+$/.test(expr);
26
+ }
27
+
28
+ /**
29
+ * Safely evaluate a mathematical expression.
30
+ * Replaces ^ with ** for exponentiation, then evaluates using Function constructor
31
+ * after validating the expression contains only safe characters.
32
+ * @param expr - The mathematical expression to evaluate.
33
+ * @returns The computed numeric result.
34
+ */
35
+ function safeEvaluate(expr: string): number {
36
+ const trimmed = expr.trim();
37
+
38
+ if (trimmed.length === 0) {
39
+ throw new Error('Expression is empty.');
40
+ }
41
+
42
+ if (!isSafeExpression(trimmed)) {
43
+ throw new Error(
44
+ 'Expression contains invalid characters. Only digits, operators (+, -, *, /, ^, %), parentheses, decimal points, and spaces are allowed.',
45
+ );
46
+ }
47
+
48
+ // Replace ^ with ** for exponentiation support
49
+ const normalized = trimmed.replace(/\^/g, '**');
50
+
51
+ // Use Function constructor (safer than eval, and we have already validated the input)
52
+ const fn = new Function(`"use strict"; return (${normalized});`) as () => unknown;
53
+ const result = fn();
54
+
55
+ if (typeof result !== 'number' || !isFinite(result)) {
56
+ throw new Error('Expression did not evaluate to a finite number.');
57
+ }
58
+
59
+ return result;
60
+ }
61
+
62
+ /**
63
+ * Evaluates mathematical expressions safely using a sandboxed parser.
64
+ *
65
+ * Tier 1 built-in skill with no external dependencies. Only allows digits,
66
+ * basic arithmetic operators, parentheses, decimal points, and spaces.
67
+ */
68
+ export class MathSkill extends SkillBase {
69
+ /**
70
+ * @param config - Optional configuration (no config keys used by this skill).
71
+ */
72
+ constructor(config?: SkillConfig) {
73
+ super('math', config);
74
+ }
75
+
76
+ /** @returns Manifest with skill metadata and tags. */
77
+ getManifest(): SkillManifest {
78
+ return {
79
+ name: 'math',
80
+ description: 'Evaluates mathematical expressions with basic arithmetic operations.',
81
+ version: '1.0.0',
82
+ tags: ['utility', 'math', 'calculator'],
83
+ };
84
+ }
85
+
86
+ /** @returns A single `calculate` tool that evaluates a math expression string. */
87
+ getTools(): SkillToolDefinition[] {
88
+ return [
89
+ {
90
+ name: 'calculate',
91
+ description:
92
+ 'Evaluate a mathematical expression. Supports basic arithmetic: addition (+), subtraction (-), multiplication (*), division (/), exponentiation (^), modulo (%), and parentheses for grouping.',
93
+ parameters: {
94
+ expression: {
95
+ type: 'string',
96
+ description:
97
+ 'The mathematical expression to evaluate (e.g., "2 + 3 * 4", "100 / (5 + 5)", "2 ^ 10").',
98
+ },
99
+ },
100
+ required: ['expression'],
101
+ handler: (args: Record<string, unknown>) => {
102
+ const expression = args.expression as string | undefined;
103
+
104
+ if (!expression || typeof expression !== 'string') {
105
+ return new SwaigFunctionResult(
106
+ 'Please provide a mathematical expression to evaluate.',
107
+ );
108
+ }
109
+
110
+ try {
111
+ const result = safeEvaluate(expression);
112
+ return new SwaigFunctionResult(
113
+ `The result of ${expression.trim()} is ${result}.`,
114
+ );
115
+ } catch (err) {
116
+ const message = err instanceof Error ? err.message : String(err);
117
+ return new SwaigFunctionResult(
118
+ `Could not evaluate the expression "${expression}": ${message}`,
119
+ );
120
+ }
121
+ },
122
+ },
123
+ ];
124
+ }
125
+
126
+ /** @returns Prompt section instructing the AI how to use the calculate tool. */
127
+ getPromptSections(): SkillPromptSection[] {
128
+ return [
129
+ {
130
+ title: 'Mathematical Calculations',
131
+ body: 'You have the ability to perform mathematical calculations.',
132
+ bullets: [
133
+ 'Use the calculate tool to evaluate mathematical expressions.',
134
+ 'Supported operations: addition (+), subtraction (-), multiplication (*), division (/), exponentiation (^), and modulo (%).',
135
+ 'Parentheses can be used for grouping, e.g., (2 + 3) * 4.',
136
+ 'When a user asks you to compute something, use this tool rather than attempting mental math.',
137
+ ],
138
+ },
139
+ ];
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Factory function for creating MathSkill instances.
145
+ * @param config - Optional skill configuration.
146
+ * @returns A new MathSkill instance.
147
+ */
148
+ export function createSkill(config?: SkillConfig): MathSkill {
149
+ return new MathSkill(config);
150
+ }