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,99 @@
1
+ /**
2
+ * MCP Gateway Skill - Placeholder for Model Context Protocol integration.
3
+ *
4
+ * Tier 3 built-in skill: stub implementation for future MCP server gateway.
5
+ * This skill will eventually allow agents to invoke tools exposed by MCP
6
+ * (Model Context Protocol) servers, enabling integration with external
7
+ * tool providers via the MCP standard.
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
+ /**
20
+ * Placeholder skill for future Model Context Protocol (MCP) server integration.
21
+ *
22
+ * Tier 3 stub implementation. When fully implemented, this skill will allow
23
+ * agents to invoke tools exposed by external MCP servers. Currently provides
24
+ * a non-functional `mcp_invoke` tool that returns a "not yet implemented" message.
25
+ */
26
+ export class McpGatewaySkill extends SkillBase {
27
+ /**
28
+ * @param config - Optional configuration (reserved for future MCP server settings).
29
+ */
30
+ constructor(config?: SkillConfig) {
31
+ super('mcp_gateway', config);
32
+ }
33
+
34
+ /** @returns Manifest with placeholder metadata for the MCP gateway. */
35
+ getManifest(): SkillManifest {
36
+ return {
37
+ name: 'mcp_gateway',
38
+ description: 'MCP protocol gateway (placeholder). Will provide integration with Model Context Protocol servers for external tool invocation.',
39
+ version: '0.1.0',
40
+ author: 'SignalWire',
41
+ tags: ['mcp', 'gateway', 'protocol', 'tools', 'integration', 'placeholder'],
42
+ };
43
+ }
44
+
45
+ /** @returns A single placeholder `mcp_invoke` tool (not yet functional). */
46
+ getTools(): SkillToolDefinition[] {
47
+ return [
48
+ {
49
+ name: 'mcp_invoke',
50
+ description:
51
+ 'Invoke a method on an MCP (Model Context Protocol) server. Currently a placeholder for future implementation.',
52
+ parameters: {
53
+ server: {
54
+ type: 'string',
55
+ description: 'The MCP server name or URL to connect to.',
56
+ },
57
+ method: {
58
+ type: 'string',
59
+ description: 'The method/tool name to invoke on the MCP server.',
60
+ },
61
+ params: {
62
+ type: 'object',
63
+ description: 'Parameters to pass to the MCP method.',
64
+ },
65
+ },
66
+ required: ['server', 'method'],
67
+ handler: () => {
68
+ return new SwaigFunctionResult(
69
+ 'MCP gateway is not yet implemented. Configure MCP servers to use this skill.',
70
+ );
71
+ },
72
+ },
73
+ ];
74
+ }
75
+
76
+ /** @returns Prompt section explaining the MCP gateway placeholder status. */
77
+ getPromptSections(): SkillPromptSection[] {
78
+ return [
79
+ {
80
+ title: 'MCP Gateway (Placeholder)',
81
+ body: 'The MCP gateway skill is available but not yet fully implemented.',
82
+ bullets: [
83
+ 'The mcp_invoke tool is a placeholder for future MCP (Model Context Protocol) integration.',
84
+ 'When implemented, it will allow you to invoke tools from external MCP servers.',
85
+ 'If a user asks about MCP functionality, let them know it is planned for a future release.',
86
+ ],
87
+ },
88
+ ];
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Factory function for creating McpGatewaySkill instances.
94
+ * @param config - Optional skill configuration.
95
+ * @returns A new McpGatewaySkill instance.
96
+ */
97
+ export function createSkill(config?: SkillConfig): McpGatewaySkill {
98
+ return new McpGatewaySkill(config);
99
+ }
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Native Vector Search Skill - In-memory document search using TF-IDF-like scoring.
3
+ *
4
+ * Tier 3 built-in skill: no external dependencies or API keys required.
5
+ * Implements a simple text-matching algorithm based on word overlap scoring
6
+ * (TF-IDF inspired) to search through a set of in-memory documents.
7
+ * Suitable for small to medium document collections.
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
+ /** A document entry in the in-memory search index. */
20
+ interface DocumentEntry {
21
+ /** Unique document identifier. */
22
+ id: string;
23
+ /** Full text content of the document. */
24
+ text: string;
25
+ /** Optional metadata associated with the document. */
26
+ metadata?: Record<string, unknown>;
27
+ }
28
+
29
+ /** A document paired with its relevance score from a search query. */
30
+ interface ScoredDocument {
31
+ /** The matched document entry. */
32
+ document: DocumentEntry;
33
+ /** TF-IDF relevance score (higher is more relevant). */
34
+ score: number;
35
+ }
36
+
37
+ /**
38
+ * Tokenize text into lowercase words, removing punctuation and common stop words.
39
+ * @param text - Raw text to tokenize.
40
+ * @returns Array of meaningful word tokens.
41
+ */
42
+ function tokenize(text: string): string[] {
43
+ const stopWords = new Set([
44
+ 'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
45
+ 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
46
+ 'should', 'may', 'might', 'shall', 'can', 'to', 'of', 'in', 'for',
47
+ 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'during',
48
+ 'before', 'after', 'above', 'below', 'between', 'and', 'but', 'or',
49
+ 'nor', 'not', 'so', 'yet', 'both', 'either', 'neither', 'each',
50
+ 'every', 'all', 'any', 'few', 'more', 'most', 'other', 'some',
51
+ 'such', 'no', 'only', 'own', 'same', 'than', 'too', 'very',
52
+ 'just', 'because', 'if', 'when', 'where', 'how', 'what', 'which',
53
+ 'who', 'whom', 'this', 'that', 'these', 'those', 'i', 'me', 'my',
54
+ 'we', 'our', 'you', 'your', 'he', 'him', 'his', 'she', 'her',
55
+ 'it', 'its', 'they', 'them', 'their', 'about', 'up', 'out',
56
+ ]);
57
+
58
+ return text
59
+ .toLowerCase()
60
+ .replace(/[^a-z0-9\s]/g, ' ')
61
+ .split(/\s+/)
62
+ .filter((word) => word.length > 1 && !stopWords.has(word));
63
+ }
64
+
65
+ /**
66
+ * Compute normalized term frequency for a list of tokens.
67
+ * @param tokens - Array of tokenized words.
68
+ * @returns Map from token to its normalized frequency.
69
+ */
70
+ function termFrequency(tokens: string[]): Map<string, number> {
71
+ const tf = new Map<string, number>();
72
+ for (const token of tokens) {
73
+ tf.set(token, (tf.get(token) ?? 0) + 1);
74
+ }
75
+ // Normalize by document length
76
+ const len = tokens.length || 1;
77
+ for (const [term, count] of tf) {
78
+ tf.set(term, count / len);
79
+ }
80
+ return tf;
81
+ }
82
+
83
+ /**
84
+ * Compute inverse document frequency weights for a tokenized corpus.
85
+ * @param corpus - Array of token arrays, one per document.
86
+ * @returns Map from token to its IDF weight.
87
+ */
88
+ function inverseDocumentFrequency(
89
+ corpus: string[][],
90
+ ): Map<string, number> {
91
+ const idf = new Map<string, number>();
92
+ const numDocs = corpus.length || 1;
93
+
94
+ // Count how many documents contain each term
95
+ const docFreq = new Map<string, number>();
96
+ for (const tokens of corpus) {
97
+ const uniqueTokens = new Set(tokens);
98
+ for (const token of uniqueTokens) {
99
+ docFreq.set(token, (docFreq.get(token) ?? 0) + 1);
100
+ }
101
+ }
102
+
103
+ for (const [term, freq] of docFreq) {
104
+ idf.set(term, Math.log((numDocs + 1) / (freq + 1)) + 1);
105
+ }
106
+
107
+ return idf;
108
+ }
109
+
110
+ /**
111
+ * Score a query against a document using TF-IDF cosine-like similarity.
112
+ * @param queryTokens - Tokenized query words.
113
+ * @param docTf - Pre-computed term frequency map for the document.
114
+ * @param idf - Pre-computed inverse document frequency map.
115
+ * @returns Similarity score (higher is more relevant).
116
+ */
117
+ function scoreTfIdf(
118
+ queryTokens: string[],
119
+ docTf: Map<string, number>,
120
+ idf: Map<string, number>,
121
+ ): number {
122
+ if (queryTokens.length === 0) return 0;
123
+
124
+ let score = 0;
125
+ const queryTf = termFrequency(queryTokens);
126
+
127
+ for (const [term, qWeight] of queryTf) {
128
+ const docWeight = docTf.get(term) ?? 0;
129
+ const idfWeight = idf.get(term) ?? 1;
130
+ score += qWeight * docWeight * idfWeight * idfWeight;
131
+ }
132
+
133
+ return score;
134
+ }
135
+
136
+ /**
137
+ * In-memory document search using TF-IDF-like word overlap scoring.
138
+ *
139
+ * Tier 3 built-in skill with no external dependencies or API keys required.
140
+ * Documents are provided via the `documents` config array and indexed at
141
+ * construction time. Suitable for small to medium document collections.
142
+ */
143
+ export class NativeVectorSearchSkill extends SkillBase {
144
+ private _documents: DocumentEntry[] = [];
145
+ private _tokenizedDocs: string[][] = [];
146
+ private _docTfs: Map<string, number>[] = [];
147
+ private _idf: Map<string, number> = new Map();
148
+ private _indexed = false;
149
+
150
+ /**
151
+ * @param config - Optional configuration; supports `documents` array of {id, text, metadata?}.
152
+ */
153
+ constructor(config?: SkillConfig) {
154
+ super('native_vector_search', config);
155
+ this._loadDocuments();
156
+ }
157
+
158
+ /** @returns Manifest with config schema for the documents array. */
159
+ getManifest(): SkillManifest {
160
+ return {
161
+ name: 'native_vector_search',
162
+ description:
163
+ 'In-memory document search using TF-IDF-like word overlap scoring. No external dependencies or API keys required.',
164
+ version: '1.0.0',
165
+ author: 'SignalWire',
166
+ tags: ['search', 'vector', 'tfidf', 'documents', 'local', 'knowledge'],
167
+ configSchema: {
168
+ documents: {
169
+ type: 'array',
170
+ description:
171
+ 'Array of documents to index: [{ id: string, text: string, metadata?: object }].',
172
+ },
173
+ },
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Load and index documents from configuration.
179
+ */
180
+ private _loadDocuments(): void {
181
+ const docs = this.getConfig<DocumentEntry[]>('documents', []);
182
+
183
+ if (!Array.isArray(docs) || docs.length === 0) {
184
+ this._documents = [];
185
+ this._indexed = false;
186
+ return;
187
+ }
188
+
189
+ this._documents = docs.filter(
190
+ (d) => d && typeof d.id === 'string' && typeof d.text === 'string' && d.text.trim().length > 0,
191
+ );
192
+
193
+ // Tokenize all documents
194
+ this._tokenizedDocs = this._documents.map((doc) => tokenize(doc.text));
195
+
196
+ // Compute TF for each document
197
+ this._docTfs = this._tokenizedDocs.map((tokens) => termFrequency(tokens));
198
+
199
+ // Compute IDF across the corpus
200
+ this._idf = inverseDocumentFrequency(this._tokenizedDocs);
201
+
202
+ this._indexed = true;
203
+ }
204
+
205
+ /** @returns A single `search_documents` tool that searches indexed documents by text similarity. */
206
+ getTools(): SkillToolDefinition[] {
207
+ return [
208
+ {
209
+ name: 'search_documents',
210
+ description:
211
+ 'Search through the indexed documents for the most relevant results matching your query. Uses text similarity scoring to rank results.',
212
+ parameters: {
213
+ query: {
214
+ type: 'string',
215
+ description: 'The search query or question.',
216
+ },
217
+ top_k: {
218
+ type: 'number',
219
+ description: 'Number of top results to return (1-20). Defaults to 3.',
220
+ },
221
+ },
222
+ required: ['query'],
223
+ handler: (args: Record<string, unknown>) => {
224
+ const query = args.query as string | undefined;
225
+ const topK = args.top_k as number | undefined;
226
+
227
+ if (!query || typeof query !== 'string' || query.trim().length === 0) {
228
+ return new SwaigFunctionResult('Please provide a search query.');
229
+ }
230
+
231
+ if (!this._indexed || this._documents.length === 0) {
232
+ return new SwaigFunctionResult(
233
+ 'No documents are loaded for searching. Configure the skill with a "documents" array.',
234
+ );
235
+ }
236
+
237
+ const k = Math.max(1, Math.min(20, topK ?? 3));
238
+ const queryTokens = tokenize(query);
239
+
240
+ if (queryTokens.length === 0) {
241
+ return new SwaigFunctionResult(
242
+ 'The search query did not contain any meaningful search terms. Please try a more specific query.',
243
+ );
244
+ }
245
+
246
+ // Score all documents
247
+ const scored: ScoredDocument[] = this._documents.map((doc, i) => ({
248
+ document: doc,
249
+ score: scoreTfIdf(queryTokens, this._docTfs[i], this._idf),
250
+ }));
251
+
252
+ // Sort by score descending
253
+ scored.sort((a, b) => b.score - a.score);
254
+
255
+ // Filter out zero-score results and take top k
256
+ const results = scored.filter((s) => s.score > 0).slice(0, k);
257
+
258
+ if (results.length === 0) {
259
+ return new SwaigFunctionResult(
260
+ `No relevant documents found for "${query}". Try different search terms.`,
261
+ );
262
+ }
263
+
264
+ const parts: string[] = [
265
+ `Found ${results.length} relevant document(s) for "${query}":`,
266
+ '',
267
+ ];
268
+
269
+ for (let i = 0; i < results.length; i++) {
270
+ const { document, score } = results[i];
271
+ const relevance = score.toFixed(4);
272
+
273
+ parts.push(`--- Result ${i + 1} (ID: ${document.id}, relevance: ${relevance}) ---`);
274
+
275
+ // Truncate long texts for readability
276
+ const maxLen = 500;
277
+ const text = document.text.trim();
278
+ if (text.length > maxLen) {
279
+ parts.push(text.slice(0, maxLen) + '...');
280
+ } else {
281
+ parts.push(text);
282
+ }
283
+
284
+ if (document.metadata && Object.keys(document.metadata).length > 0) {
285
+ const metaStr = Object.entries(document.metadata)
286
+ .map(([k, v]) => `${k}: ${String(v)}`)
287
+ .join(', ');
288
+ parts.push(`[Metadata: ${metaStr}]`);
289
+ }
290
+
291
+ parts.push('');
292
+ }
293
+
294
+ return new SwaigFunctionResult(parts.join('\n').trim());
295
+ },
296
+ },
297
+ ];
298
+ }
299
+
300
+ /** @returns Prompt section describing the local document search capabilities. */
301
+ getPromptSections(): SkillPromptSection[] {
302
+ const docCount = this._documents.length;
303
+
304
+ return [
305
+ {
306
+ title: 'Document Search',
307
+ body: `You have access to a local collection of ${docCount} document(s) that can be searched for relevant information.`,
308
+ bullets: [
309
+ 'Use the search_documents tool when the user asks about topics that may be covered in the indexed documents.',
310
+ 'You can specify how many results to return with the top_k parameter.',
311
+ 'Results are ranked by text relevance using word overlap scoring.',
312
+ 'Synthesize information from multiple results when appropriate.',
313
+ 'If no results are found, the query terms may not match the document content. Try different wording.',
314
+ ],
315
+ },
316
+ ];
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Factory function for creating NativeVectorSearchSkill instances.
322
+ * @param config - Optional skill configuration.
323
+ * @returns A new NativeVectorSearchSkill instance.
324
+ */
325
+ export function createSkill(config?: SkillConfig): NativeVectorSearchSkill {
326
+ return new NativeVectorSearchSkill(config);
327
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Play Background File Skill - Controls background audio playback during calls.
3
+ *
4
+ * Tier 2 built-in skill: no external dependencies required.
5
+ * Provides tools to play and stop background audio files (e.g., hold music,
6
+ * ambient sounds) during a call using SWML playback actions.
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
+ * Controls background audio playback during calls via SWML actions.
20
+ *
21
+ * Tier 2 built-in skill with no external dependencies. Provides tools to play
22
+ * and stop background audio files (e.g., hold music, ambient sounds). Supports
23
+ * `default_file_url` and `allowed_domains` config options.
24
+ */
25
+ export class PlayBackgroundFileSkill extends SkillBase {
26
+ /**
27
+ * @param config - Optional configuration; supports `default_file_url` and `allowed_domains`.
28
+ */
29
+ constructor(config?: SkillConfig) {
30
+ super('play_background_file', config);
31
+ }
32
+
33
+ /** @returns Manifest with config schema for default_file_url and allowed_domains. */
34
+ getManifest(): SkillManifest {
35
+ return {
36
+ name: 'play_background_file',
37
+ description:
38
+ 'Controls background audio playback during calls. Play hold music, ambient sounds, or any audio file in the background.',
39
+ version: '1.0.0',
40
+ author: 'SignalWire',
41
+ tags: ['audio', 'playback', 'background', 'music', 'call-control'],
42
+ configSchema: {
43
+ default_file_url: {
44
+ type: 'string',
45
+ description:
46
+ 'Default audio file URL to use when no URL is specified.',
47
+ },
48
+ allowed_domains: {
49
+ type: 'array',
50
+ description:
51
+ 'List of allowed domains for audio file URLs. If set, only URLs from these domains are accepted.',
52
+ },
53
+ },
54
+ };
55
+ }
56
+
57
+ /** @returns Two tools: `play_background` to start audio and `stop_background` to stop it. */
58
+ getTools(): SkillToolDefinition[] {
59
+ const defaultFileUrl = this.getConfig<string | undefined>(
60
+ 'default_file_url',
61
+ undefined,
62
+ );
63
+ const allowedDomains = this.getConfig<string[] | undefined>(
64
+ 'allowed_domains',
65
+ undefined,
66
+ );
67
+
68
+ return [
69
+ {
70
+ name: 'play_background',
71
+ description:
72
+ 'Play an audio file in the background during the call. The audio will loop continuously unless stopped. Useful for hold music, ambient sounds, or background audio.',
73
+ parameters: {
74
+ file_url: {
75
+ type: 'string',
76
+ description:
77
+ 'The URL of the audio file to play (MP3, WAV, or other supported format). Must be a publicly accessible URL.',
78
+ },
79
+ wait: {
80
+ type: 'boolean',
81
+ description:
82
+ 'If true, wait for the audio file to finish playing before continuing. Defaults to false (play in background).',
83
+ },
84
+ },
85
+ required: defaultFileUrl ? [] : ['file_url'],
86
+ handler: (args: Record<string, unknown>) => {
87
+ let fileUrl = (args.file_url as string | undefined) ?? defaultFileUrl;
88
+ const wait = (args.wait as boolean | undefined) ?? false;
89
+
90
+ if (!fileUrl || typeof fileUrl !== 'string' || fileUrl.trim().length === 0) {
91
+ return new SwaigFunctionResult(
92
+ 'Please provide a file URL for the audio to play in the background.',
93
+ );
94
+ }
95
+
96
+ fileUrl = fileUrl.trim();
97
+
98
+ // Validate URL format
99
+ try {
100
+ const parsed = new URL(fileUrl);
101
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
102
+ return new SwaigFunctionResult(
103
+ 'Invalid file URL. Only HTTP and HTTPS URLs are supported.',
104
+ );
105
+ }
106
+
107
+ // Check allowed domains if configured
108
+ if (allowedDomains && allowedDomains.length > 0) {
109
+ const hostname = parsed.hostname.toLowerCase();
110
+ const isAllowed = allowedDomains.some(
111
+ (domain) =>
112
+ hostname === domain.toLowerCase() ||
113
+ hostname.endsWith(`.${domain.toLowerCase()}`),
114
+ );
115
+ if (!isAllowed) {
116
+ return new SwaigFunctionResult(
117
+ `Audio files from "${parsed.hostname}" are not allowed. Allowed domains: ${allowedDomains.join(', ')}.`,
118
+ );
119
+ }
120
+ }
121
+ } catch {
122
+ return new SwaigFunctionResult(
123
+ `Invalid file URL: "${fileUrl}". Please provide a valid HTTP or HTTPS URL.`,
124
+ );
125
+ }
126
+
127
+ const result = new SwaigFunctionResult(
128
+ `Now playing background audio: ${fileUrl}${wait ? ' (waiting for completion)' : ''}.`,
129
+ );
130
+ result.playBackgroundFile(fileUrl, wait);
131
+
132
+ return result;
133
+ },
134
+ },
135
+ {
136
+ name: 'stop_background',
137
+ description:
138
+ 'Stop any audio file currently playing in the background. Use this when the caller is ready to resume the conversation or when background audio is no longer needed.',
139
+ parameters: {},
140
+ handler: () => {
141
+ const result = new SwaigFunctionResult(
142
+ 'Background audio playback has been stopped.',
143
+ );
144
+ result.stopBackgroundFile();
145
+
146
+ return result;
147
+ },
148
+ },
149
+ ];
150
+ }
151
+
152
+ /** @returns Prompt section describing background audio playback capabilities. */
153
+ getPromptSections(): SkillPromptSection[] {
154
+ const defaultFileUrl = this.getConfig<string | undefined>(
155
+ 'default_file_url',
156
+ undefined,
157
+ );
158
+
159
+ const bullets: string[] = [
160
+ 'Use the play_background tool to start playing audio in the background during a call.',
161
+ 'Use the stop_background tool to stop any currently playing background audio.',
162
+ 'Background audio is useful for hold music, ambient sounds, or playing announcements.',
163
+ 'The audio will continue playing until explicitly stopped with stop_background.',
164
+ ];
165
+
166
+ if (defaultFileUrl) {
167
+ bullets.push(
168
+ `A default audio file is configured. You can call play_background without specifying a file URL to use the default.`,
169
+ );
170
+ }
171
+
172
+ return [
173
+ {
174
+ title: 'Background Audio Playback',
175
+ body: 'You can control background audio playback during the call.',
176
+ bullets,
177
+ },
178
+ ];
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Factory function for creating PlayBackgroundFileSkill instances.
184
+ * @param config - Optional skill configuration.
185
+ * @returns A new PlayBackgroundFileSkill instance.
186
+ */
187
+ export function createSkill(config?: SkillConfig): PlayBackgroundFileSkill {
188
+ return new PlayBackgroundFileSkill(config);
189
+ }