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.
- package/CLAUDE.md +112 -0
- package/README.md +417 -0
- package/dist/AgentBase.d.ts +481 -0
- package/dist/AgentBase.js +1155 -0
- package/dist/AgentBase.js.map +1 -0
- package/dist/AgentServer.d.ts +60 -0
- package/dist/AgentServer.js +128 -0
- package/dist/AgentServer.js.map +1 -0
- package/dist/AuthHandler.d.ts +56 -0
- package/dist/AuthHandler.js +119 -0
- package/dist/AuthHandler.js.map +1 -0
- package/dist/ConfigLoader.d.ts +68 -0
- package/dist/ConfigLoader.js +150 -0
- package/dist/ConfigLoader.js.map +1 -0
- package/dist/ContextBuilder.d.ts +431 -0
- package/dist/ContextBuilder.js +751 -0
- package/dist/ContextBuilder.js.map +1 -0
- package/dist/DataMap.d.ts +179 -0
- package/dist/DataMap.js +321 -0
- package/dist/DataMap.js.map +1 -0
- package/dist/Logger.d.ts +91 -0
- package/dist/Logger.js +191 -0
- package/dist/Logger.js.map +1 -0
- package/dist/PomBuilder.d.ts +144 -0
- package/dist/PomBuilder.js +249 -0
- package/dist/PomBuilder.js.map +1 -0
- package/dist/PromptManager.d.ts +85 -0
- package/dist/PromptManager.js +108 -0
- package/dist/PromptManager.js.map +1 -0
- package/dist/SchemaUtils.d.ts +42 -0
- package/dist/SchemaUtils.js +148 -0
- package/dist/SchemaUtils.js.map +1 -0
- package/dist/ServerlessAdapter.d.ts +100 -0
- package/dist/ServerlessAdapter.js +166 -0
- package/dist/ServerlessAdapter.js.map +1 -0
- package/dist/SessionManager.d.ts +86 -0
- package/dist/SessionManager.js +155 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/SslConfig.d.ts +77 -0
- package/dist/SslConfig.js +100 -0
- package/dist/SslConfig.js.map +1 -0
- package/dist/SwaigFunction.d.ts +88 -0
- package/dist/SwaigFunction.js +125 -0
- package/dist/SwaigFunction.js.map +1 -0
- package/dist/SwaigFunctionResult.d.ts +442 -0
- package/dist/SwaigFunctionResult.js +735 -0
- package/dist/SwaigFunctionResult.js.map +1 -0
- package/dist/SwmlBuilder.d.ts +37 -0
- package/dist/SwmlBuilder.js +55 -0
- package/dist/SwmlBuilder.js.map +1 -0
- package/dist/cli/agent-loader.d.ts +22 -0
- package/dist/cli/agent-loader.js +106 -0
- package/dist/cli/agent-loader.js.map +1 -0
- package/dist/cli/mock-data.d.ts +37 -0
- package/dist/cli/mock-data.js +79 -0
- package/dist/cli/mock-data.js.map +1 -0
- package/dist/cli/swaig-test.d.ts +32 -0
- package/dist/cli/swaig-test.js +405 -0
- package/dist/cli/swaig-test.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/prefabs/ConciergeAgent.d.ts +64 -0
- package/dist/prefabs/ConciergeAgent.js +188 -0
- package/dist/prefabs/ConciergeAgent.js.map +1 -0
- package/dist/prefabs/FAQBotAgent.d.ts +68 -0
- package/dist/prefabs/FAQBotAgent.js +206 -0
- package/dist/prefabs/FAQBotAgent.js.map +1 -0
- package/dist/prefabs/InfoGathererAgent.d.ts +73 -0
- package/dist/prefabs/InfoGathererAgent.js +202 -0
- package/dist/prefabs/InfoGathererAgent.js.map +1 -0
- package/dist/prefabs/ReceptionistAgent.d.ts +65 -0
- package/dist/prefabs/ReceptionistAgent.js +205 -0
- package/dist/prefabs/ReceptionistAgent.js.map +1 -0
- package/dist/prefabs/SurveyAgent.d.ts +84 -0
- package/dist/prefabs/SurveyAgent.js +324 -0
- package/dist/prefabs/SurveyAgent.js.map +1 -0
- package/dist/prefabs/index.d.ts +13 -0
- package/dist/prefabs/index.js +14 -0
- package/dist/prefabs/index.js.map +1 -0
- package/dist/skills/SkillBase.d.ts +134 -0
- package/dist/skills/SkillBase.js +105 -0
- package/dist/skills/SkillBase.js.map +1 -0
- package/dist/skills/SkillManager.d.ts +83 -0
- package/dist/skills/SkillManager.js +159 -0
- package/dist/skills/SkillManager.js.map +1 -0
- package/dist/skills/SkillRegistry.d.ts +105 -0
- package/dist/skills/SkillRegistry.js +195 -0
- package/dist/skills/SkillRegistry.js.map +1 -0
- package/dist/skills/builtin/api_ninjas_trivia.d.ts +35 -0
- package/dist/skills/builtin/api_ninjas_trivia.js +161 -0
- package/dist/skills/builtin/api_ninjas_trivia.js.map +1 -0
- package/dist/skills/builtin/claude_skills.d.ts +34 -0
- package/dist/skills/builtin/claude_skills.js +156 -0
- package/dist/skills/builtin/claude_skills.js.map +1 -0
- package/dist/skills/builtin/custom_skills.d.ts +59 -0
- package/dist/skills/builtin/custom_skills.js +231 -0
- package/dist/skills/builtin/custom_skills.js.map +1 -0
- package/dist/skills/builtin/datasphere.d.ts +34 -0
- package/dist/skills/builtin/datasphere.js +163 -0
- package/dist/skills/builtin/datasphere.js.map +1 -0
- package/dist/skills/builtin/datasphere_serverless.d.ts +54 -0
- package/dist/skills/builtin/datasphere_serverless.js +155 -0
- package/dist/skills/builtin/datasphere_serverless.js.map +1 -0
- package/dist/skills/builtin/datetime.d.ts +31 -0
- package/dist/skills/builtin/datetime.js +95 -0
- package/dist/skills/builtin/datetime.js.map +1 -0
- package/dist/skills/builtin/google_maps.d.ts +33 -0
- package/dist/skills/builtin/google_maps.js +233 -0
- package/dist/skills/builtin/google_maps.js.map +1 -0
- package/dist/skills/builtin/index.d.ts +28 -0
- package/dist/skills/builtin/index.js +75 -0
- package/dist/skills/builtin/index.js.map +1 -0
- package/dist/skills/builtin/info_gatherer.d.ts +52 -0
- package/dist/skills/builtin/info_gatherer.js +237 -0
- package/dist/skills/builtin/info_gatherer.js.map +1 -0
- package/dist/skills/builtin/joke.d.ts +32 -0
- package/dist/skills/builtin/joke.js +142 -0
- package/dist/skills/builtin/joke.js.map +1 -0
- package/dist/skills/builtin/math.d.ts +33 -0
- package/dist/skills/builtin/math.js +120 -0
- package/dist/skills/builtin/math.js.map +1 -0
- package/dist/skills/builtin/mcp_gateway.d.ts +35 -0
- package/dist/skills/builtin/mcp_gateway.js +85 -0
- package/dist/skills/builtin/mcp_gateway.js.map +1 -0
- package/dist/skills/builtin/native_vector_search.d.ts +44 -0
- package/dist/skills/builtin/native_vector_search.js +248 -0
- package/dist/skills/builtin/native_vector_search.js.map +1 -0
- package/dist/skills/builtin/play_background_file.d.ts +34 -0
- package/dist/skills/builtin/play_background_file.js +135 -0
- package/dist/skills/builtin/play_background_file.js.map +1 -0
- package/dist/skills/builtin/spider.d.ts +34 -0
- package/dist/skills/builtin/spider.js +164 -0
- package/dist/skills/builtin/spider.js.map +1 -0
- package/dist/skills/builtin/swml_transfer.d.ts +44 -0
- package/dist/skills/builtin/swml_transfer.js +204 -0
- package/dist/skills/builtin/swml_transfer.js.map +1 -0
- package/dist/skills/builtin/weather_api.d.ts +34 -0
- package/dist/skills/builtin/weather_api.js +136 -0
- package/dist/skills/builtin/weather_api.js.map +1 -0
- package/dist/skills/builtin/web_search.d.ts +34 -0
- package/dist/skills/builtin/web_search.js +141 -0
- package/dist/skills/builtin/web_search.js.map +1 -0
- package/dist/skills/builtin/wikipedia_search.d.ts +34 -0
- package/dist/skills/builtin/wikipedia_search.js +166 -0
- package/dist/skills/builtin/wikipedia_search.js.map +1 -0
- package/dist/skills/index.d.ts +11 -0
- package/dist/skills/index.js +10 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/docs/agent-guide.md +869 -0
- package/docs/api-reference.md +2419 -0
- package/docs/architecture.md +843 -0
- package/docs/cli-guide.md +579 -0
- package/docs/configuration.md +422 -0
- package/docs/contexts-guide.md +787 -0
- package/docs/datamap-guide.md +1141 -0
- package/docs/prefabs-guide.md +830 -0
- package/docs/security.md +436 -0
- package/docs/serverless-guide.md +513 -0
- package/docs/skills-guide.md +845 -0
- package/docs/swaig-reference.md +1627 -0
- package/examples/call-flow.ts +60 -0
- package/examples/contexts-steps.ts +62 -0
- package/examples/datamap-tools.ts +52 -0
- package/examples/dynamic-config.ts +51 -0
- package/examples/multi-agent.ts +67 -0
- package/examples/pom-prompt.ts +68 -0
- package/examples/simple-agent.ts +33 -0
- package/package.json +35 -0
- package/src/AgentBase.ts +1279 -0
- package/src/AgentServer.ts +149 -0
- package/src/AuthHandler.ts +141 -0
- package/src/ConfigLoader.ts +165 -0
- package/src/ContextBuilder.ts +796 -0
- package/src/DataMap.ts +365 -0
- package/src/Logger.ts +197 -0
- package/src/PomBuilder.ts +298 -0
- package/src/PromptManager.ts +133 -0
- package/src/SchemaUtils.ts +170 -0
- package/src/ServerlessAdapter.ts +218 -0
- package/src/SessionManager.ts +161 -0
- package/src/SslConfig.ts +118 -0
- package/src/SwaigFunction.ts +168 -0
- package/src/SwaigFunctionResult.ts +864 -0
- package/src/SwmlBuilder.ts +62 -0
- package/src/cli/agent-loader.ts +122 -0
- package/src/cli/mock-data.ts +110 -0
- package/src/cli/swaig-test.ts +440 -0
- package/src/index.ts +99 -0
- package/src/prefabs/ConciergeAgent.ts +251 -0
- package/src/prefabs/FAQBotAgent.ts +263 -0
- package/src/prefabs/InfoGathererAgent.ts +276 -0
- package/src/prefabs/ReceptionistAgent.ts +277 -0
- package/src/prefabs/SurveyAgent.ts +413 -0
- package/src/prefabs/index.ts +23 -0
- package/src/skills/SkillBase.ts +184 -0
- package/src/skills/SkillManager.ts +177 -0
- package/src/skills/SkillRegistry.ts +231 -0
- package/src/skills/builtin/api_ninjas_trivia.ts +230 -0
- package/src/skills/builtin/claude_skills.ts +239 -0
- package/src/skills/builtin/custom_skills.ts +326 -0
- package/src/skills/builtin/datasphere.ts +225 -0
- package/src/skills/builtin/datasphere_serverless.ts +194 -0
- package/src/skills/builtin/datetime.ts +115 -0
- package/src/skills/builtin/google_maps.ts +347 -0
- package/src/skills/builtin/index.ts +79 -0
- package/src/skills/builtin/info_gatherer.ts +331 -0
- package/src/skills/builtin/joke.ts +180 -0
- package/src/skills/builtin/math.ts +150 -0
- package/src/skills/builtin/mcp_gateway.ts +99 -0
- package/src/skills/builtin/native_vector_search.ts +327 -0
- package/src/skills/builtin/play_background_file.ts +189 -0
- package/src/skills/builtin/spider.ts +232 -0
- package/src/skills/builtin/swml_transfer.ts +285 -0
- package/src/skills/builtin/weather_api.ts +193 -0
- package/src/skills/builtin/web_search.ts +212 -0
- package/src/skills/builtin/wikipedia_search.ts +248 -0
- package/src/skills/index.ts +12 -0
- package/src/types.ts +99 -0
- package/tests/AgentBase.test.ts +693 -0
- package/tests/AgentServer.test.ts +102 -0
- package/tests/AuthHandler.test.ts +121 -0
- package/tests/ConfigLoader.test.ts +121 -0
- package/tests/DataMap.test.ts +286 -0
- package/tests/Logger.test.ts +218 -0
- package/tests/PomBuilder.test.ts +164 -0
- package/tests/ProxyDetection.test.ts +185 -0
- package/tests/SchemaUtils.test.ts +106 -0
- package/tests/ServerlessAdapter.test.ts +200 -0
- package/tests/SessionManager.test.ts +139 -0
- package/tests/SslConfig.test.ts +97 -0
- package/tests/SwaigFunctionResult.test.ts +298 -0
- package/tests/cli/mock-data.test.ts +94 -0
- package/tests/cli/swaig-test.test.ts +96 -0
- package/tests/prefabs/prefabs.test.ts +854 -0
- package/tests/setup.ts +6 -0
- package/tests/skills/SkillBase.test.ts +118 -0
- package/tests/skills/SkillManager.test.ts +164 -0
- package/tests/skills/SkillRegistry.test.ts +124 -0
- package/tests/skills/builtin.test.ts +924 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SurveyAgent - A prefab agent that conducts surveys with branching logic,
|
|
3
|
+
* scoring, and conditional question flow.
|
|
4
|
+
*
|
|
5
|
+
* Supports multiple question types (multiple choice, open ended, rating,
|
|
6
|
+
* yes/no), conditional branching based on answers, and per-answer scoring.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AgentBase } from '../AgentBase.js';
|
|
10
|
+
import { SwaigFunctionResult } from '../SwaigFunctionResult.js';
|
|
11
|
+
import type { AgentOptions } from '../types.js';
|
|
12
|
+
|
|
13
|
+
// ── Config types ────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface SurveyQuestion {
|
|
16
|
+
/** Unique question identifier. */
|
|
17
|
+
id: string;
|
|
18
|
+
/** The question text to ask the caller. */
|
|
19
|
+
text: string;
|
|
20
|
+
/** Question type determines validation and display. */
|
|
21
|
+
type: 'multiple_choice' | 'open_ended' | 'rating' | 'yes_no';
|
|
22
|
+
/** Options for multiple_choice questions. */
|
|
23
|
+
options?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Next question ID after this one.
|
|
26
|
+
* - string: always go to that question
|
|
27
|
+
* - Record<string, string>: map from answer value to next question ID
|
|
28
|
+
* - undefined: proceed to next question in array order
|
|
29
|
+
*/
|
|
30
|
+
nextQuestion?: string | Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Points awarded for answers.
|
|
33
|
+
* - number: fixed points for any answer
|
|
34
|
+
* - Record<string, number>: points per specific answer value
|
|
35
|
+
*/
|
|
36
|
+
points?: number | Record<string, number>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SurveyConfig {
|
|
40
|
+
/** Agent display name. */
|
|
41
|
+
name?: string;
|
|
42
|
+
/** Ordered list of survey questions. */
|
|
43
|
+
questions: SurveyQuestion[];
|
|
44
|
+
/** Opening message before the first question. */
|
|
45
|
+
introMessage?: string;
|
|
46
|
+
/** Message after the survey is complete. */
|
|
47
|
+
completionMessage?: string;
|
|
48
|
+
/** Callback fired when the survey is finished. */
|
|
49
|
+
onComplete?: (responses: Record<string, unknown>, score: number) => void | Promise<void>;
|
|
50
|
+
/** Additional AgentBase options forwarded to super(). */
|
|
51
|
+
agentOptions?: Partial<AgentOptions>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Per-call session state ──────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
interface SurveySession {
|
|
57
|
+
currentQuestionIndex: number;
|
|
58
|
+
currentQuestionId: string;
|
|
59
|
+
responses: Record<string, unknown>;
|
|
60
|
+
score: number;
|
|
61
|
+
completed: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Agent ───────────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/** Prefab agent that conducts surveys with branching logic, answer scoring, and conditional question flow. */
|
|
67
|
+
export class SurveyAgent extends AgentBase {
|
|
68
|
+
private questions: SurveyQuestion[];
|
|
69
|
+
private questionMap: Map<string, SurveyQuestion>;
|
|
70
|
+
private introMessage: string;
|
|
71
|
+
private completionMessage: string;
|
|
72
|
+
private onCompleteCallback?: (responses: Record<string, unknown>, score: number) => void | Promise<void>;
|
|
73
|
+
private sessions: Map<string, SurveySession> = new Map();
|
|
74
|
+
|
|
75
|
+
/** Declarative prompt sections merged by AgentBase constructor. */
|
|
76
|
+
static override PROMPT_SECTIONS = [
|
|
77
|
+
{
|
|
78
|
+
title: 'Role',
|
|
79
|
+
body: 'You are a friendly survey agent. Your job is to ask the caller a series of questions and record their answers accurately.',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: 'Rules',
|
|
83
|
+
bullets: [
|
|
84
|
+
'Always start by calling get_current_question to know which question to ask.',
|
|
85
|
+
'Ask one question at a time.',
|
|
86
|
+
'For multiple choice questions, read all available options to the caller.',
|
|
87
|
+
'For rating questions, specify the scale (1-5 or 1-10 as appropriate).',
|
|
88
|
+
'For yes/no questions, accept variations like "yeah", "nope", "sure", etc.',
|
|
89
|
+
'After each answer, use answer_question to record the response.',
|
|
90
|
+
'Use get_survey_progress to check how far along the survey is.',
|
|
91
|
+
'Do not skip questions unless the branching logic directs you to.',
|
|
92
|
+
'When the survey is complete, thank the caller for their time.',
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create a SurveyAgent with the specified questions and callbacks.
|
|
99
|
+
* @param config - Configuration including questions, messages, and completion callback.
|
|
100
|
+
*/
|
|
101
|
+
constructor(config: SurveyConfig) {
|
|
102
|
+
const agentName = config.name ?? 'SurveyAgent';
|
|
103
|
+
super({
|
|
104
|
+
name: agentName,
|
|
105
|
+
...config.agentOptions,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.questions = config.questions;
|
|
109
|
+
this.questionMap = new Map(config.questions.map((q) => [q.id, q]));
|
|
110
|
+
this.introMessage = config.introMessage ?? 'Thank you for taking our survey. I have a few questions for you.';
|
|
111
|
+
this.completionMessage = config.completionMessage ?? 'Thank you for completing the survey! Your responses have been recorded.';
|
|
112
|
+
this.onCompleteCallback = config.onComplete;
|
|
113
|
+
|
|
114
|
+
// Build questions overview for the prompt
|
|
115
|
+
const questionBullets = this.questions.map((q) => {
|
|
116
|
+
let desc = `[${q.id}] (${q.type}) ${q.text}`;
|
|
117
|
+
if (q.options) desc += ` Options: ${q.options.join(', ')}`;
|
|
118
|
+
return desc;
|
|
119
|
+
});
|
|
120
|
+
this.promptAddSection('Survey Questions', { bullets: questionBullets, numbered: true });
|
|
121
|
+
|
|
122
|
+
this.promptAddSection('Introduction', {
|
|
123
|
+
body: `Begin the conversation with: "${this.introMessage}"`,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Register tools after all fields are initialized
|
|
127
|
+
this.defineTools();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Session helpers ───────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
private getSession(rawData: Record<string, unknown>): SurveySession {
|
|
133
|
+
const callId = (rawData['call_id'] as string) ?? 'default';
|
|
134
|
+
let session = this.sessions.get(callId);
|
|
135
|
+
if (!session) {
|
|
136
|
+
const firstQuestion = this.questions[0];
|
|
137
|
+
session = {
|
|
138
|
+
currentQuestionIndex: 0,
|
|
139
|
+
currentQuestionId: firstQuestion ? firstQuestion.id : '',
|
|
140
|
+
responses: {},
|
|
141
|
+
score: 0,
|
|
142
|
+
completed: false,
|
|
143
|
+
};
|
|
144
|
+
this.sessions.set(callId, session);
|
|
145
|
+
}
|
|
146
|
+
return session;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private resolveNextQuestion(question: SurveyQuestion, answer: string): string | null {
|
|
150
|
+
if (!question.nextQuestion) {
|
|
151
|
+
// Default: find next in array order
|
|
152
|
+
const idx = this.questions.findIndex((q) => q.id === question.id);
|
|
153
|
+
if (idx >= 0 && idx < this.questions.length - 1) {
|
|
154
|
+
return this.questions[idx + 1].id;
|
|
155
|
+
}
|
|
156
|
+
return null; // end of survey
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (typeof question.nextQuestion === 'string') {
|
|
160
|
+
return question.nextQuestion;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Conditional branching: try exact match first, then case-insensitive
|
|
164
|
+
const normalizedAnswer = answer.toLowerCase().trim();
|
|
165
|
+
for (const [key, nextId] of Object.entries(question.nextQuestion)) {
|
|
166
|
+
if (key.toLowerCase().trim() === normalizedAnswer) {
|
|
167
|
+
return nextId;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If no branch matches, try default key or fall through to next in order
|
|
172
|
+
if ('_default' in question.nextQuestion) {
|
|
173
|
+
return question.nextQuestion['_default'];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const idx = this.questions.findIndex((q) => q.id === question.id);
|
|
177
|
+
if (idx >= 0 && idx < this.questions.length - 1) {
|
|
178
|
+
return this.questions[idx + 1].id;
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private calculatePoints(question: SurveyQuestion, answer: string): number {
|
|
184
|
+
if (question.points === undefined) return 0;
|
|
185
|
+
if (typeof question.points === 'number') return question.points;
|
|
186
|
+
|
|
187
|
+
// Per-answer scoring
|
|
188
|
+
const normalizedAnswer = answer.toLowerCase().trim();
|
|
189
|
+
for (const [key, pts] of Object.entries(question.points)) {
|
|
190
|
+
if (key.toLowerCase().trim() === normalizedAnswer) {
|
|
191
|
+
return pts;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private validateAnswer(question: SurveyQuestion, answer: string): string | null {
|
|
198
|
+
switch (question.type) {
|
|
199
|
+
case 'multiple_choice': {
|
|
200
|
+
if (!question.options || question.options.length === 0) return null;
|
|
201
|
+
const normalized = answer.toLowerCase().trim();
|
|
202
|
+
const match = question.options.find((o) => o.toLowerCase().trim() === normalized);
|
|
203
|
+
if (!match) {
|
|
204
|
+
return `Invalid answer. Please choose one of: ${question.options.join(', ')}`;
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
case 'rating': {
|
|
209
|
+
const num = parseInt(answer, 10);
|
|
210
|
+
if (isNaN(num) || num < 1 || num > 10) {
|
|
211
|
+
return 'Please provide a rating between 1 and 10.';
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
case 'yes_no': {
|
|
216
|
+
const normalized = answer.toLowerCase().trim();
|
|
217
|
+
const yesValues = ['yes', 'y', 'yeah', 'yep', 'sure', 'absolutely', 'correct', 'true'];
|
|
218
|
+
const noValues = ['no', 'n', 'nah', 'nope', 'negative', 'false'];
|
|
219
|
+
if (!yesValues.includes(normalized) && !noValues.includes(normalized)) {
|
|
220
|
+
return 'Please answer with yes or no.';
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
case 'open_ended':
|
|
225
|
+
return null; // Accept anything
|
|
226
|
+
default:
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private normalizeAnswer(question: SurveyQuestion, answer: string): string {
|
|
232
|
+
if (question.type === 'yes_no') {
|
|
233
|
+
const normalized = answer.toLowerCase().trim();
|
|
234
|
+
const yesValues = ['yes', 'y', 'yeah', 'yep', 'sure', 'absolutely', 'correct', 'true'];
|
|
235
|
+
return yesValues.includes(normalized) ? 'yes' : 'no';
|
|
236
|
+
}
|
|
237
|
+
return answer;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Tool registration ─────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
/** Register the answer_question, get_current_question, and get_survey_progress SWAIG tools. */
|
|
243
|
+
protected override defineTools(): void {
|
|
244
|
+
// Tool: answer_question
|
|
245
|
+
this.defineTool({
|
|
246
|
+
name: 'answer_question',
|
|
247
|
+
description: 'Record the caller\'s answer to the current survey question. Validates the answer based on question type and advances to the next question.',
|
|
248
|
+
parameters: {
|
|
249
|
+
type: 'object',
|
|
250
|
+
properties: {
|
|
251
|
+
question_id: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: 'The ID of the question being answered.',
|
|
254
|
+
},
|
|
255
|
+
answer: {
|
|
256
|
+
type: 'string',
|
|
257
|
+
description: 'The caller\'s answer to the question.',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
required: ['question_id', 'answer'],
|
|
261
|
+
},
|
|
262
|
+
handler: async (args: Record<string, unknown>, rawData: Record<string, unknown>) => {
|
|
263
|
+
const questionId = args['question_id'] as string;
|
|
264
|
+
const answer = args['answer'] as string;
|
|
265
|
+
|
|
266
|
+
if (!questionId || !answer) {
|
|
267
|
+
return new SwaigFunctionResult('Both question_id and answer are required.');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const question = this.questionMap.get(questionId);
|
|
271
|
+
if (!question) {
|
|
272
|
+
return new SwaigFunctionResult(`Unknown question ID "${questionId}".`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const session = this.getSession(rawData);
|
|
276
|
+
|
|
277
|
+
if (session.completed) {
|
|
278
|
+
return new SwaigFunctionResult('The survey has already been completed.');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Validate the answer
|
|
282
|
+
const validationError = this.validateAnswer(question, answer);
|
|
283
|
+
if (validationError) {
|
|
284
|
+
return new SwaigFunctionResult(validationError);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Normalize and save
|
|
288
|
+
const normalizedAnswer = this.normalizeAnswer(question, answer);
|
|
289
|
+
session.responses[questionId] = normalizedAnswer;
|
|
290
|
+
|
|
291
|
+
// Calculate and add points
|
|
292
|
+
const points = this.calculatePoints(question, normalizedAnswer);
|
|
293
|
+
session.score += points;
|
|
294
|
+
|
|
295
|
+
// Determine next question
|
|
296
|
+
const nextId = this.resolveNextQuestion(question, normalizedAnswer);
|
|
297
|
+
|
|
298
|
+
if (!nextId || !this.questionMap.has(nextId)) {
|
|
299
|
+
// Survey complete
|
|
300
|
+
session.completed = true;
|
|
301
|
+
if (this.onCompleteCallback) {
|
|
302
|
+
try {
|
|
303
|
+
await this.onCompleteCallback({ ...session.responses }, session.score);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
this.log.error(`onComplete callback error: ${err}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const answeredCount = Object.keys(session.responses).length;
|
|
309
|
+
return new SwaigFunctionResult(
|
|
310
|
+
`Answer recorded. The survey is now complete! ${answeredCount} questions answered, total score: ${session.score}. ${this.completionMessage}`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Advance to next question
|
|
315
|
+
session.currentQuestionId = nextId;
|
|
316
|
+
const nextQ = this.questionMap.get(nextId)!;
|
|
317
|
+
const nextIdx = this.questions.findIndex((q) => q.id === nextId);
|
|
318
|
+
if (nextIdx >= 0) session.currentQuestionIndex = nextIdx;
|
|
319
|
+
|
|
320
|
+
let nextInfo = `Answer recorded. Next question [${nextQ.id}]: "${nextQ.text}"`;
|
|
321
|
+
if (nextQ.type === 'multiple_choice' && nextQ.options) {
|
|
322
|
+
nextInfo += ` (Options: ${nextQ.options.join(', ')})`;
|
|
323
|
+
} else if (nextQ.type === 'rating') {
|
|
324
|
+
nextInfo += ' (Rating: 1-10)';
|
|
325
|
+
} else if (nextQ.type === 'yes_no') {
|
|
326
|
+
nextInfo += ' (Yes/No)';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return new SwaigFunctionResult(nextInfo);
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Tool: get_current_question
|
|
334
|
+
this.defineTool({
|
|
335
|
+
name: 'get_current_question',
|
|
336
|
+
description: 'Get the current question that should be asked to the caller.',
|
|
337
|
+
parameters: {
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {},
|
|
340
|
+
},
|
|
341
|
+
handler: (_args: Record<string, unknown>, rawData: Record<string, unknown>) => {
|
|
342
|
+
const session = this.getSession(rawData);
|
|
343
|
+
|
|
344
|
+
if (session.completed) {
|
|
345
|
+
return new SwaigFunctionResult('The survey has been completed. No more questions.');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const question = this.questionMap.get(session.currentQuestionId);
|
|
349
|
+
if (!question) {
|
|
350
|
+
return new SwaigFunctionResult('No current question available.');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let info = `Current question [${question.id}] (${question.type}): "${question.text}"`;
|
|
354
|
+
if (question.type === 'multiple_choice' && question.options) {
|
|
355
|
+
info += ` Options: ${question.options.join(', ')}`;
|
|
356
|
+
} else if (question.type === 'rating') {
|
|
357
|
+
info += ' (Caller should provide a rating from 1 to 10)';
|
|
358
|
+
} else if (question.type === 'yes_no') {
|
|
359
|
+
info += ' (Caller should answer yes or no)';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return new SwaigFunctionResult(info);
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Tool: get_survey_progress
|
|
367
|
+
this.defineTool({
|
|
368
|
+
name: 'get_survey_progress',
|
|
369
|
+
description: 'Get the current progress of the survey, including how many questions have been answered and the current score.',
|
|
370
|
+
parameters: {
|
|
371
|
+
type: 'object',
|
|
372
|
+
properties: {},
|
|
373
|
+
},
|
|
374
|
+
handler: (_args: Record<string, unknown>, rawData: Record<string, unknown>) => {
|
|
375
|
+
const session = this.getSession(rawData);
|
|
376
|
+
const answeredCount = Object.keys(session.responses).length;
|
|
377
|
+
const totalCount = this.questions.length;
|
|
378
|
+
const percentage = totalCount > 0 ? Math.round((answeredCount / totalCount) * 100) : 0;
|
|
379
|
+
|
|
380
|
+
let progress = `Survey progress: ${answeredCount}/${totalCount} questions answered (${percentage}%). Current score: ${session.score}.`;
|
|
381
|
+
|
|
382
|
+
if (session.completed) {
|
|
383
|
+
progress += ' Survey is COMPLETE.';
|
|
384
|
+
} else {
|
|
385
|
+
progress += ` Current question: ${session.currentQuestionId}.`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// List answered questions
|
|
389
|
+
if (answeredCount > 0) {
|
|
390
|
+
const answered = Object.entries(session.responses)
|
|
391
|
+
.map(([qId, ans]) => `${qId}: ${ans}`)
|
|
392
|
+
.join('; ');
|
|
393
|
+
progress += ` Answers so far: ${answered}`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return new SwaigFunctionResult(progress);
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ── Factory function ────────────────────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Factory function that creates and returns a new SurveyAgent.
|
|
406
|
+
* @param config - Configuration for the survey agent.
|
|
407
|
+
* @returns A configured SurveyAgent instance.
|
|
408
|
+
*/
|
|
409
|
+
export function createSurveyAgent(config: SurveyConfig): SurveyAgent {
|
|
410
|
+
return new SurveyAgent(config);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export default SurveyAgent;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefab agents - Ready-to-use agent implementations built on AgentBase.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// InfoGathererAgent
|
|
6
|
+
export { InfoGathererAgent, createInfoGathererAgent } from './InfoGathererAgent.js';
|
|
7
|
+
export type { InfoGathererConfig, InfoGathererField } from './InfoGathererAgent.js';
|
|
8
|
+
|
|
9
|
+
// SurveyAgent
|
|
10
|
+
export { SurveyAgent, createSurveyAgent } from './SurveyAgent.js';
|
|
11
|
+
export type { SurveyConfig, SurveyQuestion } from './SurveyAgent.js';
|
|
12
|
+
|
|
13
|
+
// FAQBotAgent
|
|
14
|
+
export { FAQBotAgent, createFAQBotAgent } from './FAQBotAgent.js';
|
|
15
|
+
export type { FAQBotConfig, FAQEntry } from './FAQBotAgent.js';
|
|
16
|
+
|
|
17
|
+
// ConciergeAgent
|
|
18
|
+
export { ConciergeAgent, createConciergeAgent } from './ConciergeAgent.js';
|
|
19
|
+
export type { ConciergeConfig, Department } from './ConciergeAgent.js';
|
|
20
|
+
|
|
21
|
+
// ReceptionistAgent
|
|
22
|
+
export { ReceptionistAgent, createReceptionistAgent } from './ReceptionistAgent.js';
|
|
23
|
+
export type { ReceptionistConfig, ReceptionistDepartment } from './ReceptionistAgent.js';
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillBase - Abstract base class for agent skills.
|
|
3
|
+
*
|
|
4
|
+
* Skills are modular capabilities that can be added to an agent.
|
|
5
|
+
* They define tools, prompt sections, hints, and global data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
import type { SwaigHandler } from '../SwaigFunction.js';
|
|
10
|
+
|
|
11
|
+
/** Configuration key-value pairs passed to a skill at construction time. */
|
|
12
|
+
export interface SkillConfig {
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Definition of a SWAIG tool provided by a skill. */
|
|
17
|
+
export interface SkillToolDefinition {
|
|
18
|
+
/** Unique tool name used in SWAIG function registration. */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Human-readable description of what the tool does, shown to the AI. */
|
|
21
|
+
description: string;
|
|
22
|
+
/** Parameter schema for the tool, keyed by parameter name. */
|
|
23
|
+
parameters?: Record<string, unknown>;
|
|
24
|
+
/** Handler function invoked when the tool is called. */
|
|
25
|
+
handler: SwaigHandler;
|
|
26
|
+
/** Whether the tool requires secure (authenticated) invocation. */
|
|
27
|
+
secure?: boolean;
|
|
28
|
+
/** Filler phrases spoken while the tool executes, keyed by language. */
|
|
29
|
+
fillers?: Record<string, string[]>;
|
|
30
|
+
/** List of parameter names that are required. */
|
|
31
|
+
required?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A section of prompt content injected into the agent's system prompt by a skill. */
|
|
35
|
+
export interface SkillPromptSection {
|
|
36
|
+
/** Section heading displayed in the prompt. */
|
|
37
|
+
title: string;
|
|
38
|
+
/** Optional body text for the section. */
|
|
39
|
+
body?: string;
|
|
40
|
+
/** Optional bullet points appended after the body. */
|
|
41
|
+
bullets?: string[];
|
|
42
|
+
/** If true, render bullets as a numbered list instead of unordered. */
|
|
43
|
+
numbered?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Metadata describing a skill's identity, requirements, and configuration schema. */
|
|
47
|
+
export interface SkillManifest {
|
|
48
|
+
/** Unique skill name used for registration and lookup. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Human-readable description of the skill's purpose. */
|
|
51
|
+
description: string;
|
|
52
|
+
/** Semantic version string (e.g., "1.0.0"). */
|
|
53
|
+
version: string;
|
|
54
|
+
/** Author or organization that created the skill. */
|
|
55
|
+
author?: string;
|
|
56
|
+
/** Tags for categorization and discovery. */
|
|
57
|
+
tags?: string[];
|
|
58
|
+
/** Environment variables that must be set for the skill to function. */
|
|
59
|
+
requiredEnvVars?: string[];
|
|
60
|
+
/** NPM packages or external dependencies required by the skill. */
|
|
61
|
+
requiredPackages?: string[];
|
|
62
|
+
/** JSON-schema-like description of the skill's configuration options. */
|
|
63
|
+
configSchema?: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Abstract base class for agent skills.
|
|
68
|
+
*
|
|
69
|
+
* Skills are modular capabilities that can be added to an agent.
|
|
70
|
+
* They define tools, prompt sections, hints, and global data.
|
|
71
|
+
*/
|
|
72
|
+
export abstract class SkillBase {
|
|
73
|
+
/** The registered name of this skill type. */
|
|
74
|
+
readonly skillName: string;
|
|
75
|
+
/** Unique identifier for this skill instance (includes timestamp and random bytes). */
|
|
76
|
+
readonly instanceId: string;
|
|
77
|
+
/** Configuration options provided at construction time. */
|
|
78
|
+
protected config: SkillConfig;
|
|
79
|
+
private _initialized = false;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a new skill instance.
|
|
83
|
+
* @param skillName - The registered name for this skill type.
|
|
84
|
+
* @param config - Optional configuration key-value pairs.
|
|
85
|
+
*/
|
|
86
|
+
constructor(skillName: string, config?: SkillConfig) {
|
|
87
|
+
this.skillName = skillName;
|
|
88
|
+
this.config = config ?? {};
|
|
89
|
+
this.instanceId = `${skillName}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get the skill manifest containing metadata, requirements, and config schema.
|
|
94
|
+
* @returns The skill's manifest object.
|
|
95
|
+
*/
|
|
96
|
+
abstract getManifest(): SkillManifest;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Setup the skill. Called when the skill is added to an agent.
|
|
100
|
+
* Override to perform initialization (API connections, config validation, etc.)
|
|
101
|
+
*/
|
|
102
|
+
async setup(): Promise<void> {
|
|
103
|
+
// Default no-op
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Return the SWAIG tool definitions this skill provides.
|
|
108
|
+
* @returns Array of tool definitions to register with the agent.
|
|
109
|
+
*/
|
|
110
|
+
abstract getTools(): SkillToolDefinition[];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get prompt sections to inject into the agent's system prompt.
|
|
114
|
+
* @returns Array of prompt sections with titles, bodies, and bullets.
|
|
115
|
+
*/
|
|
116
|
+
getPromptSections(): SkillPromptSection[] {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get speech recognition hints relevant to this skill.
|
|
122
|
+
* @returns Array of hint strings to improve speech recognition accuracy.
|
|
123
|
+
*/
|
|
124
|
+
getHints(): string[] {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get global data to merge into the agent's global data store.
|
|
130
|
+
* @returns Key-value pairs to be merged into the agent's global data.
|
|
131
|
+
*/
|
|
132
|
+
getGlobalData(): Record<string, unknown> {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Cleanup resources. Called when the skill is removed from an agent.
|
|
138
|
+
*/
|
|
139
|
+
async cleanup(): Promise<void> {
|
|
140
|
+
// Default no-op
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Validate that all required environment variables declared in the manifest are set.
|
|
145
|
+
* @returns Array of missing environment variable names (empty if all are present).
|
|
146
|
+
*/
|
|
147
|
+
validateEnvVars(): string[] {
|
|
148
|
+
const manifest = this.getManifest();
|
|
149
|
+
const missing: string[] = [];
|
|
150
|
+
if (manifest.requiredEnvVars) {
|
|
151
|
+
for (const envVar of manifest.requiredEnvVars) {
|
|
152
|
+
if (!process.env[envVar]) {
|
|
153
|
+
missing.push(envVar);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return missing;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if the skill has been initialized by the SkillManager.
|
|
162
|
+
* @returns True if setup() has completed and the skill is marked initialized.
|
|
163
|
+
*/
|
|
164
|
+
isInitialized(): boolean {
|
|
165
|
+
return this._initialized;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Mark the skill as initialized (called by SkillManager).
|
|
170
|
+
*/
|
|
171
|
+
markInitialized(): void {
|
|
172
|
+
this._initialized = true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get a configuration value by key, falling back to a default if not set.
|
|
177
|
+
* @param key - The configuration key to look up.
|
|
178
|
+
* @param defaultValue - Value to return if the key is not present.
|
|
179
|
+
* @returns The configuration value cast to type T, or the default value.
|
|
180
|
+
*/
|
|
181
|
+
getConfig<T = unknown>(key: string, defaultValue?: T): T {
|
|
182
|
+
return (this.config[key] !== undefined ? this.config[key] : defaultValue) as T;
|
|
183
|
+
}
|
|
184
|
+
}
|