@lssm/lib.support-bot 0.0.0-canary-20251217063201 → 0.0.0-canary-20251217072406

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.
@@ -1 +1 @@
1
- import"./contract-spec-agent.js";
1
+ import "./contract-spec-agent.js";
@@ -1 +1,18 @@
1
- import"../tools/tool-adapter.js";import"../tools/knowledge-tool.js";import*as e from"zod";import"ai";e.object({tenantId:e.string().optional(),actorId:e.string().optional(),sessionId:e.string().optional(),metadata:e.record(e.string(),e.unknown()).optional()});
1
+ import "../tools/tool-adapter.js";
2
+ import "../tools/knowledge-tool.js";
3
+ import * as z$1 from "zod";
4
+ import "ai";
5
+
6
+ //#region ../ai-agent/dist/agent/contract-spec-agent.js
7
+ /**
8
+ * Call options schema for AI SDK v6.
9
+ * Maps ContractSpec's tenant/actor system to AI SDK callOptionsSchema.
10
+ */
11
+ const ContractSpecCallOptionsSchema = z$1.object({
12
+ tenantId: z$1.string().optional(),
13
+ actorId: z$1.string().optional(),
14
+ sessionId: z$1.string().optional(),
15
+ metadata: z$1.record(z$1.string(), z$1.unknown()).optional()
16
+ });
17
+
18
+ //#endregion
@@ -1 +1,2 @@
1
- import"./contract-spec-agent.js";import"./agent-factory.js";
1
+ import "./contract-spec-agent.js";
2
+ import "./agent-factory.js";
@@ -1 +1 @@
1
- import"./workflow.js";
1
+ import "./workflow.js";
@@ -1 +1 @@
1
- import"node:crypto";
1
+ import "node:crypto";
@@ -1 +1,15 @@
1
- import{e}from"./spec/spec.js";import"./schema/json-schema-to-zod.js";import"./tools/tool-adapter.js";import"./tools/knowledge-tool.js";import"./agent/contract-spec-agent.js";import"./agent/agent-factory.js";import"./agent/index.js";import"./tools/mcp-client.js";import"./tools/mcp-server.js";import"./tools/index.js";import"./schema/schema-output.js";import"./schema/index.js";import"./approval/workflow.js";import"./approval/index.js";import"ai";
1
+ import { defineAgent } from "./spec/spec.js";
2
+ import "./schema/json-schema-to-zod.js";
3
+ import "./tools/tool-adapter.js";
4
+ import "./tools/knowledge-tool.js";
5
+ import "./agent/contract-spec-agent.js";
6
+ import "./agent/agent-factory.js";
7
+ import "./agent/index.js";
8
+ import "./tools/mcp-client.js";
9
+ import "./tools/mcp-server.js";
10
+ import "./tools/index.js";
11
+ import "./schema/schema-output.js";
12
+ import "./schema/index.js";
13
+ import "./approval/workflow.js";
14
+ import "./approval/index.js";
15
+ import "ai";
@@ -1 +1,2 @@
1
- import"./json-schema-to-zod.js";import"./schema-output.js";
1
+ import "./json-schema-to-zod.js";
2
+ import "./schema-output.js";
@@ -1 +1 @@
1
- import"zod";
1
+ import "zod";
@@ -1 +1,3 @@
1
- import"./json-schema-to-zod.js";import"zod";import"ai";
1
+ import "./json-schema-to-zod.js";
2
+ import "zod";
3
+ import "ai";
@@ -1 +1,23 @@
1
- function e(e){if(!e.meta?.name)throw Error(`Agent name is required`);if(!Number.isFinite(e.meta.version))throw Error(`Agent ${e.meta.name} is missing a numeric version`);if(!e.instructions?.trim())throw Error(`Agent ${e.meta.name} requires instructions`);if(!e.tools?.length)throw Error(`Agent ${e.meta.name} must expose at least one tool`);let t=new Set;for(let n of e.tools){if(t.has(n.name))throw Error(`Agent ${e.meta.name} has duplicate tool name: ${n.name}`);t.add(n.name)}return Object.freeze(e)}export{e};
1
+ //#region ../ai-agent/dist/spec/spec.js
2
+ /**
3
+ * Define and validate an agent specification.
4
+ *
5
+ * @param spec - The agent specification
6
+ * @returns The frozen, validated specification
7
+ * @throws Error if the specification is invalid
8
+ */
9
+ function defineAgent(spec) {
10
+ if (!spec.meta?.name) throw new Error("Agent name is required");
11
+ if (!Number.isFinite(spec.meta.version)) throw new Error(`Agent ${spec.meta.name} is missing a numeric version`);
12
+ if (!spec.instructions?.trim()) throw new Error(`Agent ${spec.meta.name} requires instructions`);
13
+ if (!spec.tools?.length) throw new Error(`Agent ${spec.meta.name} must expose at least one tool`);
14
+ const toolNames = /* @__PURE__ */ new Set();
15
+ for (const tool of spec.tools) {
16
+ if (toolNames.has(tool.name)) throw new Error(`Agent ${spec.meta.name} has duplicate tool name: ${tool.name}`);
17
+ toolNames.add(tool.name);
18
+ }
19
+ return Object.freeze(spec);
20
+ }
21
+
22
+ //#endregion
23
+ export { defineAgent };
@@ -1 +1,4 @@
1
- import"./tool-adapter.js";import"./knowledge-tool.js";import"./mcp-client.js";import"./mcp-server.js";
1
+ import "./tool-adapter.js";
2
+ import "./knowledge-tool.js";
3
+ import "./mcp-client.js";
4
+ import "./mcp-server.js";
@@ -1 +1,2 @@
1
- import"zod";import"ai";
1
+ import "zod";
2
+ import "ai";
@@ -1 +1,2 @@
1
- import"@ai-sdk/mcp";import"@ai-sdk/mcp/mcp-stdio";
1
+ import "@ai-sdk/mcp";
2
+ import "@ai-sdk/mcp/mcp-stdio";
@@ -1 +1,3 @@
1
- import"../schema/json-schema-to-zod.js";import"zod";import"@modelcontextprotocol/sdk/server/mcp.js";
1
+ import "../schema/json-schema-to-zod.js";
2
+ import "zod";
3
+ import "@modelcontextprotocol/sdk/server/mcp.js";
@@ -1 +1,2 @@
1
- import"../schema/json-schema-to-zod.js";import"ai";
1
+ import "../schema/json-schema-to-zod.js";
2
+ import "ai";
@@ -1,18 +1,83 @@
1
- var e=class{llm;model;tone;closing;constructor(e){this.llm=e?.llm,this.model=e?.model,this.tone=e?.tone??`friendly`,this.closing=e?.closing??(this.tone===`friendly`?`We remain available if you need anything else.`:`Please let us know if you require additional assistance.`)}async draft(e,t,n){return this.llm?this.generateWithLLM(e,t,n):this.generateTemplate(e,t,n)}async generateWithLLM(e,t,n){let r=`You are a ${this.tone} support agent. Draft an email response.
2
- Ticket Subject: ${e.subject}
3
- Ticket Body: ${e.body}
4
- Detected Category: ${n.category}
5
- Detected Priority: ${n.priority}
1
+ //#region src/bot/auto-responder.ts
2
+ var AutoResponder = class {
3
+ llm;
4
+ model;
5
+ tone;
6
+ closing;
7
+ constructor(options) {
8
+ this.llm = options?.llm;
9
+ this.model = options?.model;
10
+ this.tone = options?.tone ?? "friendly";
11
+ this.closing = options?.closing ?? (this.tone === "friendly" ? "We remain available if you need anything else." : "Please let us know if you require additional assistance.");
12
+ }
13
+ async draft(ticket, resolution, classification) {
14
+ if (this.llm) return this.generateWithLLM(ticket, resolution, classification);
15
+ return this.generateTemplate(ticket, resolution, classification);
16
+ }
17
+ async generateWithLLM(ticket, resolution, classification) {
18
+ const prompt = `You are a ${this.tone} support agent. Draft an email response.
19
+ Ticket Subject: ${ticket.subject}
20
+ Ticket Body: ${ticket.body}
21
+ Detected Category: ${classification.category}
22
+ Detected Priority: ${classification.priority}
6
23
  Resolution:
7
- ${t.answer}
8
- Citations: ${t.citations.map(e=>e.label).join(`, `)}`,i=(await this.llm.chat([{role:`system`,content:[{type:`text`,text:`Write empathetic, accurate support replies that cite sources when relevant.`}]},{role:`user`,content:[{type:`text`,text:r}]}],{model:this.model})).message.content.map(e=>`text`in e?e.text:``).join(``).trim();return this.buildDraft(e,t,n,i)}generateTemplate(e,t,n){let r=`${e.customerName?`Hi ${e.customerName},`:`Hi there,`}
24
+ ${resolution.answer}
25
+ Citations: ${resolution.citations.map((c) => c.label).join(", ")}`;
26
+ const body = (await this.llm.chat([{
27
+ role: "system",
28
+ content: [{
29
+ type: "text",
30
+ text: "Write empathetic, accurate support replies that cite sources when relevant."
31
+ }]
32
+ }, {
33
+ role: "user",
34
+ content: [{
35
+ type: "text",
36
+ text: prompt
37
+ }]
38
+ }], { model: this.model })).message.content.map((part) => "text" in part ? part.text : "").join("").trim();
39
+ return this.buildDraft(ticket, resolution, classification, body);
40
+ }
41
+ generateTemplate(ticket, resolution, classification) {
42
+ const body = `${ticket.customerName ? `Hi ${ticket.customerName},` : "Hi there,"}
9
43
 
10
- Thanks for contacting us about "${e.subject}". ${this.renderCategoryIntro(n)}
44
+ Thanks for contacting us about "${ticket.subject}". ${this.renderCategoryIntro(classification)}
11
45
 
12
- ${t.answer}
46
+ ${resolution.answer}
13
47
 
14
- ${this.renderCitations(t)}
48
+ ${this.renderCitations(resolution)}
15
49
  ${this.closing}
16
50
 
17
- — ContractSpec Support`;return this.buildDraft(e,t,n,r)}buildDraft(e,t,n,r){return{ticketId:e.id,subject:e.subject.startsWith(`Re:`)?e.subject:`Re: ${e.subject}`,body:r,confidence:Math.min(t.confidence,n.confidence),requiresEscalation:t.actions.some(e=>e.type===`escalate`)||!!n.escalationRequired,citations:t.citations}}renderCategoryIntro(e){switch(e.category){case`billing`:return`I understand billing issues can be stressful, so let me clarify the situation.`;case`technical`:return`I see you encountered a technical issue. Here is what happened and how to fix it.`;case`product`:return`Thanks for sharing feedback about the product. Here are the next steps.`;case`account`:return`Account access is critical, so let me walk you through the resolution.`;case`compliance`:return`Compliance questions require precision. See the policy-aligned answer below.`;default:return`Here is what we found after reviewing your request.`}}renderCitations(e){return e.citations.length?`References:\n${e.citations.map((e,t)=>`- ${e.label||`Source ${t+1}`}${e.url?` (${e.url})`:``}`).join(`
18
- `)}`:``}};export{e as AutoResponder};
51
+ — ContractSpec Support`;
52
+ return this.buildDraft(ticket, resolution, classification, body);
53
+ }
54
+ buildDraft(ticket, resolution, classification, body) {
55
+ return {
56
+ ticketId: ticket.id,
57
+ subject: ticket.subject.startsWith("Re:") ? ticket.subject : `Re: ${ticket.subject}`,
58
+ body,
59
+ confidence: Math.min(resolution.confidence, classification.confidence),
60
+ requiresEscalation: resolution.actions.some((action) => action.type === "escalate") || Boolean(classification.escalationRequired),
61
+ citations: resolution.citations
62
+ };
63
+ }
64
+ renderCategoryIntro(classification) {
65
+ switch (classification.category) {
66
+ case "billing": return "I understand billing issues can be stressful, so let me clarify the situation.";
67
+ case "technical": return "I see you encountered a technical issue. Here is what happened and how to fix it.";
68
+ case "product": return "Thanks for sharing feedback about the product. Here are the next steps.";
69
+ case "account": return "Account access is critical, so let me walk you through the resolution.";
70
+ case "compliance": return "Compliance questions require precision. See the policy-aligned answer below.";
71
+ default: return "Here is what we found after reviewing your request.";
72
+ }
73
+ }
74
+ renderCitations(resolution) {
75
+ if (!resolution.citations.length) return "";
76
+ return `References:\n${resolution.citations.map((citation, index) => {
77
+ return `- ${citation.label || `Source ${index + 1}`}${citation.url ? ` (${citation.url})` : ""}`;
78
+ }).join("\n")}`;
79
+ }
80
+ };
81
+
82
+ //#endregion
83
+ export { AutoResponder };
@@ -1,2 +1,34 @@
1
- var e=class{history=[];responseTimes=new Map;recordResolution(e,t){this.history.push(e),t!=null&&this.responseTimes.set(e.ticket.id,t)}metrics(){let e=this.history.length,t=this.history.filter(e=>!e.resolution.actions.some(e=>e.type===`escalate`)).length,n=e-t,r=e===0?0:this.history.reduce((e,t)=>e+t.resolution.confidence,0)/e,i=this.responseTimes.size===0?0:[...this.responseTimes.values()].reduce((e,t)=>e+t,0)/this.responseTimes.size;return{totalTickets:e,autoResolved:t,escalated:n,avgConfidence:Number(r.toFixed(2)),avgResponseTimeMs:Math.round(i)}}feedbackSummary(e=5){let t=this.history.slice(-e);return t.length?t.map(e=>{let t=e.resolution.actions.some(e=>e.type===`escalate`)?`Escalated`:`Auto-resolved`;return`${e.ticket.subject} – ${t} (confidence: ${e.resolution.confidence})`}).join(`
2
- `):`No feedback recorded yet.`}};export{e as SupportFeedbackLoop};
1
+ //#region src/bot/feedback-loop.ts
2
+ var SupportFeedbackLoop = class {
3
+ history = [];
4
+ responseTimes = /* @__PURE__ */ new Map();
5
+ recordResolution(payload, responseTimeMs) {
6
+ this.history.push(payload);
7
+ if (responseTimeMs != null) this.responseTimes.set(payload.ticket.id, responseTimeMs);
8
+ }
9
+ metrics() {
10
+ const total = this.history.length;
11
+ const autoResolved = this.history.filter((entry) => !entry.resolution.actions.some((action) => action.type === "escalate")).length;
12
+ const escalated = total - autoResolved;
13
+ const avgConfidence = total === 0 ? 0 : this.history.reduce((sum, entry) => sum + entry.resolution.confidence, 0) / total;
14
+ const avgResponseTimeMs = this.responseTimes.size === 0 ? 0 : [...this.responseTimes.values()].reduce((a, b) => a + b, 0) / this.responseTimes.size;
15
+ return {
16
+ totalTickets: total,
17
+ autoResolved,
18
+ escalated,
19
+ avgConfidence: Number(avgConfidence.toFixed(2)),
20
+ avgResponseTimeMs: Math.round(avgResponseTimeMs)
21
+ };
22
+ }
23
+ feedbackSummary(limit = 5) {
24
+ const recent = this.history.slice(-limit);
25
+ if (!recent.length) return "No feedback recorded yet.";
26
+ return recent.map((entry) => {
27
+ const status = entry.resolution.actions.some((action) => action.type === "escalate") ? "Escalated" : "Auto-resolved";
28
+ return `${entry.ticket.subject} – ${status} (confidence: ${entry.resolution.confidence})`;
29
+ }).join("\n");
30
+ }
31
+ };
32
+
33
+ //#endregion
34
+ export { SupportFeedbackLoop };
package/dist/bot/index.js CHANGED
@@ -1 +1,5 @@
1
- import{AutoResponder as e}from"./auto-responder.js";import{SupportFeedbackLoop as t}from"./feedback-loop.js";import{createSupportTools as n}from"./tools.js";export{e as AutoResponder,t as SupportFeedbackLoop,n as createSupportTools};
1
+ import { AutoResponder } from "./auto-responder.js";
2
+ import { SupportFeedbackLoop } from "./feedback-loop.js";
3
+ import { createSupportTools } from "./tools.js";
4
+
5
+ export { AutoResponder, SupportFeedbackLoop, createSupportTools };
package/dist/bot/tools.js CHANGED
@@ -1 +1,136 @@
1
- import*as e from"zod";const t=e.object({id:e.string(),subject:e.string(),body:e.string(),channel:e.enum([`email`,`chat`,`phone`,`portal`]),customerName:e.string().optional(),customerEmail:e.string().optional(),metadata:e.object().optional()}),n=e.object({label:e.string(),url:e.string().optional(),snippet:e.string().optional(),score:e.number().optional()}),r=e.object({type:e.enum([`respond`,`escalate`,`refund`,`manual`]),label:e.string(),payload:e.record(e.string(),e.string())}),i=e.object({ticketId:e.string(),answer:e.string(),confidence:e.number(),citations:n.array(),actions:r.array(),escalationReason:e.string().optional(),knowledgeUpdates:e.array(e.string()).optional()}),a=e.object({ticketId:e.string(),category:e.enum([`billing`,`technical`,`product`,`account`,`compliance`,`other`]),priority:e.enum([`urgent`,`high`,`medium`,`low`]),sentiment:e.enum([`positive`,`neutral`,`negative`,`frustrated`]),intents:e.array(e.string()),tags:e.array(e.string()),confidence:e.number(),escalationRequired:e.boolean().optional()});function o(e){if(!e||typeof e!=`object`||!(`ticket`in e))throw Error(`Input must include ticket`);let t=e.ticket;if(!t?.id)throw Error(`Ticket is missing id`);return t}function s(e){if(!(!e||typeof e!=`object`||!(`resolution`in e)))return e.resolution}function c(e){if(!(!e||typeof e!=`object`||!(`classification`in e)))return e.classification}function l(n){return[{title:`support_classify_ticket`,description:`Classify a ticket for priority, sentiment, and category`,inputSchema:e.object({ticket:t}),execute:async e=>{let t=o(e),r=await n.classifier.classify(t);return{content:JSON.stringify(r),metadata:{ticketId:t.id}}}},{title:`support_resolve_ticket`,description:`Generate a knowledge-grounded resolution for a ticket`,inputSchema:e.object({ticket:t}),execute:async e=>{let t=o(e),r=await n.resolver.resolve(t);return{content:JSON.stringify(r),metadata:{ticketId:t.id}}}},{title:`support_draft_response`,description:`Draft a user-facing reply based on resolution + classification`,inputSchema:e.object({ticket:t,resolution:i,classification:a}),execute:async e=>{let t=o(e),r=s(e),i=c(e);if(!r||!i)throw Error(`resolution and classification are required`);let a=await n.responder.draft(t,r,i);return{content:JSON.stringify(a),metadata:{ticketId:t.id}}}}]}export{l as createSupportTools};
1
+ import * as z from "zod";
2
+
3
+ //#region src/bot/tools.ts
4
+ const ticketSchema = z.object({
5
+ id: z.string(),
6
+ subject: z.string(),
7
+ body: z.string(),
8
+ channel: z.enum([
9
+ "email",
10
+ "chat",
11
+ "phone",
12
+ "portal"
13
+ ]),
14
+ customerName: z.string().optional(),
15
+ customerEmail: z.string().optional(),
16
+ metadata: z.object().optional()
17
+ });
18
+ const supportCitationSchema = z.object({
19
+ label: z.string(),
20
+ url: z.string().optional(),
21
+ snippet: z.string().optional(),
22
+ score: z.number().optional()
23
+ });
24
+ const supportActionSchema = z.object({
25
+ type: z.enum([
26
+ "respond",
27
+ "escalate",
28
+ "refund",
29
+ "manual"
30
+ ]),
31
+ label: z.string(),
32
+ payload: z.record(z.string(), z.string())
33
+ });
34
+ const supportResolutionSchema = z.object({
35
+ ticketId: z.string(),
36
+ answer: z.string(),
37
+ confidence: z.number(),
38
+ citations: supportCitationSchema.array(),
39
+ actions: supportActionSchema.array(),
40
+ escalationReason: z.string().optional(),
41
+ knowledgeUpdates: z.array(z.string()).optional()
42
+ });
43
+ const ticketClassificationSchema = z.object({
44
+ ticketId: z.string(),
45
+ category: z.enum([
46
+ "billing",
47
+ "technical",
48
+ "product",
49
+ "account",
50
+ "compliance",
51
+ "other"
52
+ ]),
53
+ priority: z.enum([
54
+ "urgent",
55
+ "high",
56
+ "medium",
57
+ "low"
58
+ ]),
59
+ sentiment: z.enum([
60
+ "positive",
61
+ "neutral",
62
+ "negative",
63
+ "frustrated"
64
+ ]),
65
+ intents: z.array(z.string()),
66
+ tags: z.array(z.string()),
67
+ confidence: z.number(),
68
+ escalationRequired: z.boolean().optional()
69
+ });
70
+ function ensureTicket(input) {
71
+ if (!input || typeof input !== "object" || !("ticket" in input)) throw new Error("Input must include ticket");
72
+ const ticket = input.ticket;
73
+ if (!ticket?.id) throw new Error("Ticket is missing id");
74
+ return ticket;
75
+ }
76
+ function extractResolution(input) {
77
+ if (!input || typeof input !== "object" || !("resolution" in input)) return void 0;
78
+ return input.resolution;
79
+ }
80
+ function extractClassification(input) {
81
+ if (!input || typeof input !== "object" || !("classification" in input)) return void 0;
82
+ return input.classification;
83
+ }
84
+ function createSupportTools(options) {
85
+ return [
86
+ {
87
+ title: "support_classify_ticket",
88
+ description: "Classify a ticket for priority, sentiment, and category",
89
+ inputSchema: z.object({ ticket: ticketSchema }),
90
+ execute: async (input) => {
91
+ const ticket = ensureTicket(input);
92
+ const classification = await options.classifier.classify(ticket);
93
+ return {
94
+ content: JSON.stringify(classification),
95
+ metadata: { ticketId: ticket.id }
96
+ };
97
+ }
98
+ },
99
+ {
100
+ title: "support_resolve_ticket",
101
+ description: "Generate a knowledge-grounded resolution for a ticket",
102
+ inputSchema: z.object({ ticket: ticketSchema }),
103
+ execute: async (input) => {
104
+ const ticket = ensureTicket(input);
105
+ const resolution = await options.resolver.resolve(ticket);
106
+ return {
107
+ content: JSON.stringify(resolution),
108
+ metadata: { ticketId: ticket.id }
109
+ };
110
+ }
111
+ },
112
+ {
113
+ title: "support_draft_response",
114
+ description: "Draft a user-facing reply based on resolution + classification",
115
+ inputSchema: z.object({
116
+ ticket: ticketSchema,
117
+ resolution: supportResolutionSchema,
118
+ classification: ticketClassificationSchema
119
+ }),
120
+ execute: async (input) => {
121
+ const ticket = ensureTicket(input);
122
+ const resolution = extractResolution(input);
123
+ const classification = extractClassification(input);
124
+ if (!resolution || !classification) throw new Error("resolution and classification are required");
125
+ const draft = await options.responder.draft(ticket, resolution, classification);
126
+ return {
127
+ content: JSON.stringify(draft),
128
+ metadata: { ticketId: ticket.id }
129
+ };
130
+ }
131
+ }
132
+ ];
133
+ }
134
+
135
+ //#endregion
136
+ export { createSupportTools };
package/dist/index.js CHANGED
@@ -1 +1,9 @@
1
- import{defineSupportBot as e}from"./spec.js";import{TicketResolver as t}from"./rag/ticket-resolver.js";import{TicketClassifier as n}from"./tickets/classifier.js";import{AutoResponder as r}from"./bot/auto-responder.js";import{SupportFeedbackLoop as i}from"./bot/feedback-loop.js";import{createSupportTools as a}from"./bot/tools.js";import"./bot/index.js";export{r as AutoResponder,i as SupportFeedbackLoop,n as TicketClassifier,t as TicketResolver,a as createSupportTools,e as defineSupportBot};
1
+ import { defineSupportBot } from "./spec.js";
2
+ import { TicketResolver } from "./rag/ticket-resolver.js";
3
+ import { TicketClassifier } from "./tickets/classifier.js";
4
+ import { AutoResponder } from "./bot/auto-responder.js";
5
+ import { SupportFeedbackLoop } from "./bot/feedback-loop.js";
6
+ import { createSupportTools } from "./bot/tools.js";
7
+ import "./bot/index.js";
8
+
9
+ export { AutoResponder, SupportFeedbackLoop, TicketClassifier, TicketResolver, createSupportTools, defineSupportBot };
package/dist/rag/index.js CHANGED
@@ -1 +1,3 @@
1
- import{TicketResolver as e}from"./ticket-resolver.js";export{e as TicketResolver};
1
+ import { TicketResolver } from "./ticket-resolver.js";
2
+
3
+ export { TicketResolver };
@@ -1,3 +1,63 @@
1
- var e=class{knowledge;minConfidence;prependPrompt;constructor(e){this.knowledge=e.knowledge,this.minConfidence=e.minConfidence??.65,this.prependPrompt=e.prependPrompt}async resolve(e){let t=this.buildQuestion(e),n=await this.knowledge.query(t);return this.toResolution(e,n)}buildQuestion(e){let t=[`Subject: ${e.subject}`,`Channel: ${e.channel}`];return e.customerName&&t.push(`Customer: ${e.customerName}`),[this.prependPrompt,t.join(`
2
- `),`---`,e.body].filter(Boolean).join(`
3
- `)}toResolution(e,t){let n=t.references.map(e=>({label:typeof e.payload?.title==`string`?e.payload.title:typeof e.payload?.documentId==`string`?e.payload.documentId:e.id,url:typeof e.payload?.url==`string`?e.payload.url:void 0,snippet:typeof e.payload?.text==`string`?e.payload.text.slice(0,280):void 0,score:e.score})),r=this.deriveConfidence(t),i=r<this.minConfidence||n.length===0;return{ticketId:e.id,answer:t.answer,confidence:r,citations:n,actions:[i?{type:`escalate`,label:`Escalate for human review`}:{type:`respond`,label:`Send automated response`}],escalationReason:i?`Insufficient confidence or missing knowledge references`:void 0,knowledgeUpdates:i?[e.body.slice(0,200)]:void 0}}deriveConfidence(e){if(!e.references.length)return .3;let t=e.references[0]?.score??.4,n=Math.min(1,Math.max(0,t)),r=e.usage?.completionTokens?Math.min(e.usage.completionTokens/1e3,.2):0;return Number((n-r).toFixed(2))}};export{e as TicketResolver};
1
+ //#region src/rag/ticket-resolver.ts
2
+ var TicketResolver = class {
3
+ knowledge;
4
+ minConfidence;
5
+ prependPrompt;
6
+ constructor(options) {
7
+ this.knowledge = options.knowledge;
8
+ this.minConfidence = options.minConfidence ?? .65;
9
+ this.prependPrompt = options.prependPrompt;
10
+ }
11
+ async resolve(ticket) {
12
+ const question = this.buildQuestion(ticket);
13
+ const answer = await this.knowledge.query(question);
14
+ return this.toResolution(ticket, answer);
15
+ }
16
+ buildQuestion(ticket) {
17
+ const header = [`Subject: ${ticket.subject}`, `Channel: ${ticket.channel}`];
18
+ if (ticket.customerName) header.push(`Customer: ${ticket.customerName}`);
19
+ return [
20
+ this.prependPrompt,
21
+ header.join("\n"),
22
+ "---",
23
+ ticket.body
24
+ ].filter(Boolean).join("\n");
25
+ }
26
+ toResolution(ticket, answer) {
27
+ const citations = answer.references.map((ref) => {
28
+ return {
29
+ label: typeof ref.payload?.title === "string" ? ref.payload.title : typeof ref.payload?.documentId === "string" ? ref.payload.documentId : ref.id,
30
+ url: typeof ref.payload?.url === "string" ? ref.payload.url : void 0,
31
+ snippet: typeof ref.payload?.text === "string" ? ref.payload.text.slice(0, 280) : void 0,
32
+ score: ref.score
33
+ };
34
+ });
35
+ const confidence = this.deriveConfidence(answer);
36
+ const escalate = confidence < this.minConfidence || citations.length === 0;
37
+ return {
38
+ ticketId: ticket.id,
39
+ answer: answer.answer,
40
+ confidence,
41
+ citations,
42
+ actions: [escalate ? {
43
+ type: "escalate",
44
+ label: "Escalate for human review"
45
+ } : {
46
+ type: "respond",
47
+ label: "Send automated response"
48
+ }],
49
+ escalationReason: escalate ? "Insufficient confidence or missing knowledge references" : void 0,
50
+ knowledgeUpdates: escalate ? [ticket.body.slice(0, 200)] : void 0
51
+ };
52
+ }
53
+ deriveConfidence(answer) {
54
+ if (!answer.references.length) return .3;
55
+ const topScore = answer.references[0]?.score ?? .4;
56
+ const normalized = Math.min(1, Math.max(0, topScore));
57
+ const tokenPenalty = answer.usage?.completionTokens ? Math.min(answer.usage.completionTokens / 1e3, .2) : 0;
58
+ return Number((normalized - tokenPenalty).toFixed(2));
59
+ }
60
+ };
61
+
62
+ //#endregion
63
+ export { TicketResolver };
package/dist/spec.js CHANGED
@@ -1 +1,35 @@
1
- import{e}from"./ai-agent/dist/spec/spec.js";import"./ai-agent/dist/index.js";function t(t){return{...e({...t.base,policy:{...t.base.policy,confidence:{min:t.base.policy?.confidence?.min??.7,default:t.base.policy?.confidence?.default??.6},escalation:{confidenceThreshold:t.autoEscalateThreshold??t.base.policy?.escalation?.confidenceThreshold??t.base.policy?.confidence?.min??.7,...t.base.policy?.escalation}},memory:t.base.memory??{maxEntries:120,ttlMinutes:120},tools:t.tools??t.base.tools,instructions:`${t.base.instructions}\n\nAlways cite support knowledge sources and flag compliance/billing issues for human review when unsure.`}),thresholds:{autoResolveMinConfidence:t.autoEscalateThreshold??.75,maxIterations:6}}}export{t as defineSupportBot};
1
+ import { defineAgent } from "./ai-agent/dist/spec/spec.js";
2
+ import "./ai-agent/dist/index.js";
3
+
4
+ //#region src/spec.ts
5
+ function defineSupportBot(definition) {
6
+ return {
7
+ ...defineAgent({
8
+ ...definition.base,
9
+ policy: {
10
+ ...definition.base.policy,
11
+ confidence: {
12
+ min: definition.base.policy?.confidence?.min ?? .7,
13
+ default: definition.base.policy?.confidence?.default ?? .6
14
+ },
15
+ escalation: {
16
+ confidenceThreshold: definition.autoEscalateThreshold ?? definition.base.policy?.escalation?.confidenceThreshold ?? definition.base.policy?.confidence?.min ?? .7,
17
+ ...definition.base.policy?.escalation
18
+ }
19
+ },
20
+ memory: definition.base.memory ?? {
21
+ maxEntries: 120,
22
+ ttlMinutes: 120
23
+ },
24
+ tools: definition.tools ?? definition.base.tools,
25
+ instructions: `${definition.base.instructions}\n\nAlways cite support knowledge sources and flag compliance/billing issues for human review when unsure.`
26
+ }),
27
+ thresholds: {
28
+ autoResolveMinConfidence: definition.autoEscalateThreshold ?? .75,
29
+ maxIterations: 6
30
+ }
31
+ };
32
+ }
33
+
34
+ //#endregion
35
+ export { defineSupportBot };
@@ -1 +1,198 @@
1
- const e={billing:[`invoice`,`payout`,`refund`,`charge`,`billing`,`payment`],technical:[`bug`,`error`,`crash`,`issue`,`failed`,`timeout`],product:[`feature`,`roadmap`,`idea`,`request`,`feedback`],account:[`login`,`password`,`2fa`,`account`,`profile`,`email change`],compliance:[`kyc`,`aml`,`compliance`,`regulation`,`gdpr`],other:[]},t={urgent:[`urgent`,`asap`,`immediately`,`today`,`right away`],high:[`high priority`,`blocking`,`major`,`critical`],medium:[`soon`,`next few days`],low:[`nice to have`,`when possible`,`later`]},n={positive:[`love`,`great`,`awesome`,`thank you`],neutral:[`question`,`wonder`,`curious`],negative:[`unhappy`,`bad`,`terrible`,`awful`,`angry`],frustrated:[`furious`,`frustrated`,`fed up`,`ridiculous`]};var r=class{keywords;llm;llmModel;constructor(t){this.keywords={...e,...t?.keywords??{}},this.llm=t?.llm,this.llmModel=t?.llmModel}async classify(e){let t=this.heuristicClassification(e);if(!this.llm)return t;try{let n=(await this.llm.chat([{role:`system`,content:[{type:`text`,text:`Classify the support ticket.`}]},{role:`user`,content:[{type:`text`,text:JSON.stringify({subject:e.subject,body:e.body,channel:e.channel})}]}],{responseFormat:`json`,model:this.llmModel})).message.content.find(e=>`text`in e);if(n&&`text`in n){let e=JSON.parse(n.text);return{...t,...e,intents:e.intents??t.intents,tags:e.tags??t.tags}}}catch{}return t}heuristicClassification(e){let t=`${e.subject}\n${e.body}`.toLowerCase(),n=this.detectCategory(t),r=this.detectPriority(t),i=this.detectSentiment(t),a=this.extractIntents(t),o=a.slice(0,3),s=this.estimateConfidence(n,r,i);return{ticketId:e.id,category:n,priority:r,sentiment:i,intents:a,tags:o,confidence:s,escalationRequired:r===`urgent`||n===`compliance`}}detectCategory(e){for(let[t,n]of Object.entries(this.keywords))if(n.some(t=>e.includes(t)))return t;return`other`}detectPriority(e){for(let n of[`urgent`,`high`,`medium`,`low`])if(t[n].some(t=>e.includes(t)))return n;return`medium`}detectSentiment(e){for(let t of[`frustrated`,`negative`,`neutral`,`positive`])if(n[t].some(t=>e.includes(t)))return t;return`neutral`}extractIntents(e){let t=[];return(e.includes(`refund`)||e.includes(`chargeback`))&&t.push(`refund`),e.includes(`payout`)&&t.push(`payout`),e.includes(`login`)&&t.push(`login-help`),e.includes(`feature`)&&t.push(`feature-request`),(e.includes(`bug`)||e.includes(`error`))&&t.push(`bug-report`),t.length?t:[`general`]}estimateConfidence(e,t,n){let r=.6;return e!==`other`&&(r+=.1),(t===`urgent`||t===`low`)&&(r+=.05),n===`frustrated`&&(r-=.05),Math.min(.95,Math.max(.4,Number(r.toFixed(2))))}};export{r as TicketClassifier};
1
+ //#region src/tickets/classifier.ts
2
+ const CATEGORY_KEYWORDS = {
3
+ billing: [
4
+ "invoice",
5
+ "payout",
6
+ "refund",
7
+ "charge",
8
+ "billing",
9
+ "payment"
10
+ ],
11
+ technical: [
12
+ "bug",
13
+ "error",
14
+ "crash",
15
+ "issue",
16
+ "failed",
17
+ "timeout"
18
+ ],
19
+ product: [
20
+ "feature",
21
+ "roadmap",
22
+ "idea",
23
+ "request",
24
+ "feedback"
25
+ ],
26
+ account: [
27
+ "login",
28
+ "password",
29
+ "2fa",
30
+ "account",
31
+ "profile",
32
+ "email change"
33
+ ],
34
+ compliance: [
35
+ "kyc",
36
+ "aml",
37
+ "compliance",
38
+ "regulation",
39
+ "gdpr"
40
+ ],
41
+ other: []
42
+ };
43
+ const PRIORITY_HINTS = {
44
+ urgent: [
45
+ "urgent",
46
+ "asap",
47
+ "immediately",
48
+ "today",
49
+ "right away"
50
+ ],
51
+ high: [
52
+ "high priority",
53
+ "blocking",
54
+ "major",
55
+ "critical"
56
+ ],
57
+ medium: ["soon", "next few days"],
58
+ low: [
59
+ "nice to have",
60
+ "when possible",
61
+ "later"
62
+ ]
63
+ };
64
+ const SENTIMENT_HINTS = {
65
+ positive: [
66
+ "love",
67
+ "great",
68
+ "awesome",
69
+ "thank you"
70
+ ],
71
+ neutral: [
72
+ "question",
73
+ "wonder",
74
+ "curious"
75
+ ],
76
+ negative: [
77
+ "unhappy",
78
+ "bad",
79
+ "terrible",
80
+ "awful",
81
+ "angry"
82
+ ],
83
+ frustrated: [
84
+ "furious",
85
+ "frustrated",
86
+ "fed up",
87
+ "ridiculous"
88
+ ]
89
+ };
90
+ var TicketClassifier = class {
91
+ keywords;
92
+ llm;
93
+ llmModel;
94
+ constructor(options) {
95
+ this.keywords = {
96
+ ...CATEGORY_KEYWORDS,
97
+ ...options?.keywords ?? {}
98
+ };
99
+ this.llm = options?.llm;
100
+ this.llmModel = options?.llmModel;
101
+ }
102
+ async classify(ticket) {
103
+ const heuristics = this.heuristicClassification(ticket);
104
+ if (!this.llm) return heuristics;
105
+ try {
106
+ const content = (await this.llm.chat([{
107
+ role: "system",
108
+ content: [{
109
+ type: "text",
110
+ text: "Classify the support ticket."
111
+ }]
112
+ }, {
113
+ role: "user",
114
+ content: [{
115
+ type: "text",
116
+ text: JSON.stringify({
117
+ subject: ticket.subject,
118
+ body: ticket.body,
119
+ channel: ticket.channel
120
+ })
121
+ }]
122
+ }], {
123
+ responseFormat: "json",
124
+ model: this.llmModel
125
+ })).message.content.find((part) => "text" in part);
126
+ if (content && "text" in content) {
127
+ const parsed = JSON.parse(content.text);
128
+ return {
129
+ ...heuristics,
130
+ ...parsed,
131
+ intents: parsed.intents ?? heuristics.intents,
132
+ tags: parsed.tags ?? heuristics.tags
133
+ };
134
+ }
135
+ } catch {}
136
+ return heuristics;
137
+ }
138
+ heuristicClassification(ticket) {
139
+ const text = `${ticket.subject}\n${ticket.body}`.toLowerCase();
140
+ const category = this.detectCategory(text);
141
+ const priority = this.detectPriority(text);
142
+ const sentiment = this.detectSentiment(text);
143
+ const intents = this.extractIntents(text);
144
+ const tags = intents.slice(0, 3);
145
+ const confidence = this.estimateConfidence(category, priority, sentiment);
146
+ return {
147
+ ticketId: ticket.id,
148
+ category,
149
+ priority,
150
+ sentiment,
151
+ intents,
152
+ tags,
153
+ confidence,
154
+ escalationRequired: priority === "urgent" || category === "compliance"
155
+ };
156
+ }
157
+ detectCategory(text) {
158
+ for (const [category, keywords] of Object.entries(this.keywords)) if (keywords.some((keyword) => text.includes(keyword))) return category;
159
+ return "other";
160
+ }
161
+ detectPriority(text) {
162
+ for (const priority of [
163
+ "urgent",
164
+ "high",
165
+ "medium",
166
+ "low"
167
+ ]) if (PRIORITY_HINTS[priority].some((word) => text.includes(word))) return priority;
168
+ return "medium";
169
+ }
170
+ detectSentiment(text) {
171
+ for (const sentiment of [
172
+ "frustrated",
173
+ "negative",
174
+ "neutral",
175
+ "positive"
176
+ ]) if (SENTIMENT_HINTS[sentiment].some((word) => text.includes(word))) return sentiment;
177
+ return "neutral";
178
+ }
179
+ extractIntents(text) {
180
+ const intents = [];
181
+ if (text.includes("refund") || text.includes("chargeback")) intents.push("refund");
182
+ if (text.includes("payout")) intents.push("payout");
183
+ if (text.includes("login")) intents.push("login-help");
184
+ if (text.includes("feature")) intents.push("feature-request");
185
+ if (text.includes("bug") || text.includes("error")) intents.push("bug-report");
186
+ return intents.length ? intents : ["general"];
187
+ }
188
+ estimateConfidence(category, priority, sentiment) {
189
+ let base = .6;
190
+ if (category !== "other") base += .1;
191
+ if (priority === "urgent" || priority === "low") base += .05;
192
+ if (sentiment === "frustrated") base -= .05;
193
+ return Math.min(.95, Math.max(.4, Number(base.toFixed(2))));
194
+ }
195
+ };
196
+
197
+ //#endregion
198
+ export { TicketClassifier };
@@ -1 +1,3 @@
1
- import{TicketClassifier as e}from"./classifier.js";export{e as TicketClassifier};
1
+ import { TicketClassifier } from "./classifier.js";
2
+
3
+ export { TicketClassifier };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lssm/lib.support-bot",
3
- "version": "0.0.0-canary-20251217063201",
3
+ "version": "0.0.0-canary-20251217072406",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -23,12 +23,14 @@
23
23
  "test": "bun run"
24
24
  },
25
25
  "dependencies": {
26
- "@lssm/lib.ai-agent": "0.0.0-canary-20251217063201",
27
- "@lssm/lib.contracts": "0.0.0-canary-20251217063201"
26
+ "@lssm/lib.ai-agent": "0.0.0-canary-20251217072406",
27
+ "@lssm/lib.contracts": "0.0.0-canary-20251217072406",
28
+ "@lssm/lib.knowledge": "0.0.0-canary-20251217072406",
29
+ "@lssm/lib.schema": "0.0.0-canary-20251217072406"
28
30
  },
29
31
  "devDependencies": {
30
- "@lssm/tool.tsdown": "0.0.0-canary-20251217063201",
31
- "@lssm/tool.typescript": "0.0.0-canary-20251217063201",
32
+ "@lssm/tool.tsdown": "0.0.0-canary-20251217072406",
33
+ "@lssm/tool.typescript": "0.0.0-canary-20251217072406",
32
34
  "tsdown": "^0.17.4",
33
35
  "typescript": "^5.9.3"
34
36
  },