@skillrecordings/cli 0.1.0 → 0.2.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/bin/skill.mjs +27 -0
- package/dist/chunk-2NCCVTEE.js +22342 -0
- package/dist/chunk-2NCCVTEE.js.map +1 -0
- package/dist/chunk-3E3GYSZR.js +7071 -0
- package/dist/chunk-3E3GYSZR.js.map +1 -0
- package/dist/chunk-F4EM72IH.js +86 -0
- package/dist/chunk-F4EM72IH.js.map +1 -0
- package/dist/chunk-FGP7KUQW.js +432 -0
- package/dist/chunk-FGP7KUQW.js.map +1 -0
- package/dist/chunk-H3D6VCME.js +55 -0
- package/dist/chunk-H3D6VCME.js.map +1 -0
- package/dist/chunk-HK3PEWFD.js +208 -0
- package/dist/chunk-HK3PEWFD.js.map +1 -0
- package/dist/chunk-KEV3QKXP.js +4495 -0
- package/dist/chunk-KEV3QKXP.js.map +1 -0
- package/dist/chunk-MG37YDAK.js +882 -0
- package/dist/chunk-MG37YDAK.js.map +1 -0
- package/dist/chunk-MLNDSBZ4.js +482 -0
- package/dist/chunk-MLNDSBZ4.js.map +1 -0
- package/dist/chunk-N2WIV2JV.js +22 -0
- package/dist/chunk-N2WIV2JV.js.map +1 -0
- package/dist/chunk-PWWRCN5W.js +2067 -0
- package/dist/chunk-PWWRCN5W.js.map +1 -0
- package/dist/chunk-SKHBM3XP.js +7746 -0
- package/dist/chunk-SKHBM3XP.js.map +1 -0
- package/dist/chunk-WFANXVQG.js +64 -0
- package/dist/chunk-WFANXVQG.js.map +1 -0
- package/dist/chunk-WYKL32C3.js +275 -0
- package/dist/chunk-WYKL32C3.js.map +1 -0
- package/dist/chunk-ZNF7XD2S.js +134 -0
- package/dist/chunk-ZNF7XD2S.js.map +1 -0
- package/dist/config-AUAIYDSI.js +20 -0
- package/dist/config-AUAIYDSI.js.map +1 -0
- package/dist/fileFromPath-XN7LXIBI.js +134 -0
- package/dist/fileFromPath-XN7LXIBI.js.map +1 -0
- package/dist/getMachineId-bsd-KW2E7VK3.js +42 -0
- package/dist/getMachineId-bsd-KW2E7VK3.js.map +1 -0
- package/dist/getMachineId-darwin-ROXJUJX5.js +42 -0
- package/dist/getMachineId-darwin-ROXJUJX5.js.map +1 -0
- package/dist/getMachineId-linux-KVZEHQSU.js +34 -0
- package/dist/getMachineId-linux-KVZEHQSU.js.map +1 -0
- package/dist/getMachineId-unsupported-PPRILPPA.js +25 -0
- package/dist/getMachineId-unsupported-PPRILPPA.js.map +1 -0
- package/dist/getMachineId-win-IIF36LEJ.js +44 -0
- package/dist/getMachineId-win-IIF36LEJ.js.map +1 -0
- package/dist/index.js +112703 -0
- package/dist/index.js.map +1 -0
- package/dist/lib-R6DEEJCP.js +7623 -0
- package/dist/lib-R6DEEJCP.js.map +1 -0
- package/dist/pipeline-IAVVAKTU.js +120 -0
- package/dist/pipeline-IAVVAKTU.js.map +1 -0
- package/dist/query-NTP5NVXN.js +25 -0
- package/dist/query-NTP5NVXN.js.map +1 -0
- package/dist/routing-BAEPFB7V.js +390 -0
- package/dist/routing-BAEPFB7V.js.map +1 -0
- package/dist/stripe-lookup-charge-EPRUMZDL.js +56 -0
- package/dist/stripe-lookup-charge-EPRUMZDL.js.map +1 -0
- package/dist/stripe-payment-history-SJPKA63N.js +67 -0
- package/dist/stripe-payment-history-SJPKA63N.js.map +1 -0
- package/dist/stripe-subscription-status-L4Z65GB3.js +58 -0
- package/dist/stripe-subscription-status-L4Z65GB3.js.map +1 -0
- package/dist/stripe-verify-refund-FZDKCIUQ.js +54 -0
- package/dist/stripe-verify-refund-FZDKCIUQ.js.map +1 -0
- package/dist/support-memory-WSG7SDKG.js +10 -0
- package/dist/support-memory-WSG7SDKG.js.map +1 -0
- package/package.json +10 -7
- package/.env.encrypted +0 -0
- package/CHANGELOG.md +0 -35
- package/data/tt-archive-dataset.json +0 -1
- package/data/validate-test-dataset.json +0 -97
- package/docs/CLI-AUTH.md +0 -504
- package/preload.ts +0 -18
- package/src/__tests__/init.test.ts +0 -74
- package/src/alignment-test.ts +0 -64
- package/src/check-apps.ts +0 -16
- package/src/commands/auth/decrypt.ts +0 -123
- package/src/commands/auth/encrypt.ts +0 -81
- package/src/commands/auth/index.ts +0 -50
- package/src/commands/auth/keygen.ts +0 -41
- package/src/commands/auth/status.ts +0 -164
- package/src/commands/axiom/forensic.ts +0 -868
- package/src/commands/axiom/index.ts +0 -697
- package/src/commands/build-dataset.ts +0 -311
- package/src/commands/db-status.ts +0 -47
- package/src/commands/deploys.ts +0 -219
- package/src/commands/eval-local/compare.ts +0 -171
- package/src/commands/eval-local/health.ts +0 -212
- package/src/commands/eval-local/index.ts +0 -76
- package/src/commands/eval-local/real-tools.ts +0 -416
- package/src/commands/eval-local/run.ts +0 -1168
- package/src/commands/eval-local/score-production.ts +0 -256
- package/src/commands/eval-local/seed.ts +0 -276
- package/src/commands/eval-pipeline/index.ts +0 -53
- package/src/commands/eval-pipeline/real-tools.ts +0 -492
- package/src/commands/eval-pipeline/run.ts +0 -1316
- package/src/commands/eval-pipeline/seed.ts +0 -395
- package/src/commands/eval-prompt.ts +0 -496
- package/src/commands/eval.test.ts +0 -253
- package/src/commands/eval.ts +0 -108
- package/src/commands/faq-classify.ts +0 -460
- package/src/commands/faq-cluster.ts +0 -135
- package/src/commands/faq-extract.ts +0 -249
- package/src/commands/faq-mine.ts +0 -432
- package/src/commands/faq-review.ts +0 -426
- package/src/commands/front/index.ts +0 -351
- package/src/commands/front/pull-conversations.ts +0 -275
- package/src/commands/front/tags.ts +0 -825
- package/src/commands/front-cache.ts +0 -1277
- package/src/commands/front-stats.ts +0 -75
- package/src/commands/health.test.ts +0 -82
- package/src/commands/health.ts +0 -362
- package/src/commands/init.test.ts +0 -89
- package/src/commands/init.ts +0 -106
- package/src/commands/inngest/client.ts +0 -294
- package/src/commands/inngest/events.ts +0 -296
- package/src/commands/inngest/investigate.ts +0 -382
- package/src/commands/inngest/runs.ts +0 -149
- package/src/commands/inngest/signal.ts +0 -143
- package/src/commands/kb-sync.ts +0 -498
- package/src/commands/memory/find.ts +0 -135
- package/src/commands/memory/get.ts +0 -87
- package/src/commands/memory/index.ts +0 -97
- package/src/commands/memory/stats.ts +0 -163
- package/src/commands/memory/store.ts +0 -49
- package/src/commands/memory/vote.ts +0 -159
- package/src/commands/pipeline.ts +0 -127
- package/src/commands/responses.ts +0 -856
- package/src/commands/tools.ts +0 -293
- package/src/commands/wizard.ts +0 -319
- package/src/index.ts +0 -172
- package/src/lib/crypto.ts +0 -56
- package/src/lib/env-loader.ts +0 -206
- package/src/lib/onepassword.ts +0 -137
- package/src/test-agent-local.ts +0 -115
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -10
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __esm = (fn, res) => function __init() {
|
|
14
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
|
+
};
|
|
16
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
17
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
22
|
+
};
|
|
23
|
+
var __copyProps = (to, from, except, desc) => {
|
|
24
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
25
|
+
for (let key of __getOwnPropNames(from))
|
|
26
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
27
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
28
|
+
}
|
|
29
|
+
return to;
|
|
30
|
+
};
|
|
31
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
32
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
33
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
34
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
35
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
36
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
37
|
+
mod
|
|
38
|
+
));
|
|
39
|
+
|
|
40
|
+
// ../../node_modules/.bun/tsup@8.5.1+9907de614a4f44e1/node_modules/tsup/assets/esm_shims.js
|
|
41
|
+
import path from "path";
|
|
42
|
+
import { fileURLToPath } from "url";
|
|
43
|
+
var getFilename, getDirname, __dirname, __filename;
|
|
44
|
+
var init_esm_shims = __esm({
|
|
45
|
+
"../../node_modules/.bun/tsup@8.5.1+9907de614a4f44e1/node_modules/tsup/assets/esm_shims.js"() {
|
|
46
|
+
"use strict";
|
|
47
|
+
getFilename = () => fileURLToPath(import.meta.url);
|
|
48
|
+
getDirname = () => path.dirname(getFilename());
|
|
49
|
+
__dirname = /* @__PURE__ */ getDirname();
|
|
50
|
+
__filename = /* @__PURE__ */ getFilename();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
__require,
|
|
56
|
+
__esm,
|
|
57
|
+
__commonJS,
|
|
58
|
+
__export,
|
|
59
|
+
__toESM,
|
|
60
|
+
__dirname,
|
|
61
|
+
__filename,
|
|
62
|
+
init_esm_shims
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=chunk-WFANXVQG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../node_modules/.bun/tsup@8.5.1+9907de614a4f44e1/node_modules/tsup/assets/esm_shims.js"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAF9B,IAIM,aACA,YAEO,WACA;AARb;AAAA;AAAA;AAIA,IAAM,cAAc,MAAM,cAAc,YAAY,GAAG;AACvD,IAAM,aAAa,MAAM,KAAK,QAAQ,YAAY,CAAC;AAE5C,IAAM,YAA4B,2BAAW;AAC7C,IAAM,aAA6B,4BAAY;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MemoryService,
|
|
3
|
+
VotingService,
|
|
4
|
+
calculateConfidence,
|
|
5
|
+
fetchMemory,
|
|
6
|
+
queryMemories,
|
|
7
|
+
upsertMemory
|
|
8
|
+
} from "./chunk-MLNDSBZ4.js";
|
|
9
|
+
import {
|
|
10
|
+
init_esm_shims
|
|
11
|
+
} from "./chunk-WFANXVQG.js";
|
|
12
|
+
|
|
13
|
+
// ../memory/src/support-memory.ts
|
|
14
|
+
init_esm_shims();
|
|
15
|
+
import { randomUUID } from "crypto";
|
|
16
|
+
var SUPPORT_COLLECTION_PREFIX = "support";
|
|
17
|
+
var GLOBAL_COLLECTION = `${SUPPORT_COLLECTION_PREFIX}:global`;
|
|
18
|
+
function getCollection(appSlug) {
|
|
19
|
+
if (!appSlug) return GLOBAL_COLLECTION;
|
|
20
|
+
return `${SUPPORT_COLLECTION_PREFIX}:${appSlug}`;
|
|
21
|
+
}
|
|
22
|
+
function formatContent(situation, decision) {
|
|
23
|
+
return `SITUATION: ${situation.trim()}
|
|
24
|
+
|
|
25
|
+
DECISION: ${decision.trim()}`;
|
|
26
|
+
}
|
|
27
|
+
function parseContent(content) {
|
|
28
|
+
const situationMatch = content.match(/SITUATION:\s*(.+?)(?=\n\nDECISION:|$)/s);
|
|
29
|
+
const decisionMatch = content.match(/DECISION:\s*(.+)/s);
|
|
30
|
+
return {
|
|
31
|
+
situation: situationMatch?.[1]?.trim() ?? content,
|
|
32
|
+
decision: decisionMatch?.[1]?.trim() ?? ""
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
var SupportMemoryService = {
|
|
36
|
+
/**
|
|
37
|
+
* Store a new support memory
|
|
38
|
+
*
|
|
39
|
+
* @param input - Situation, decision, and metadata
|
|
40
|
+
* @returns The created support memory
|
|
41
|
+
*/
|
|
42
|
+
async store(input) {
|
|
43
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
44
|
+
const collection = getCollection(input.app_slug);
|
|
45
|
+
const content = formatContent(input.situation, input.decision);
|
|
46
|
+
const memory = {
|
|
47
|
+
id: randomUUID(),
|
|
48
|
+
content,
|
|
49
|
+
metadata: {
|
|
50
|
+
collection,
|
|
51
|
+
source: "agent",
|
|
52
|
+
app_slug: input.app_slug,
|
|
53
|
+
tags: input.tags ?? [],
|
|
54
|
+
confidence: 1,
|
|
55
|
+
created_at: now,
|
|
56
|
+
votes: {
|
|
57
|
+
upvotes: 0,
|
|
58
|
+
downvotes: 0,
|
|
59
|
+
citations: 0,
|
|
60
|
+
success_rate: 0
|
|
61
|
+
},
|
|
62
|
+
stage: input.stage,
|
|
63
|
+
outcome: input.outcome ?? "success",
|
|
64
|
+
correction: input.correction,
|
|
65
|
+
category: input.category,
|
|
66
|
+
conversation_id: input.conversation_id
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
await upsertMemory(memory);
|
|
70
|
+
return memory;
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Find similar support memories by semantic search
|
|
74
|
+
*
|
|
75
|
+
* @param query - Search query (situation description)
|
|
76
|
+
* @param options - Filters and limits
|
|
77
|
+
* @returns Array of matching memories with decay-adjusted scores
|
|
78
|
+
*/
|
|
79
|
+
async findSimilar(query, options = {}) {
|
|
80
|
+
const {
|
|
81
|
+
app_slug,
|
|
82
|
+
stage,
|
|
83
|
+
outcome,
|
|
84
|
+
category,
|
|
85
|
+
limit = 10,
|
|
86
|
+
threshold = 0.5,
|
|
87
|
+
include_stale = false
|
|
88
|
+
} = options;
|
|
89
|
+
const collection = getCollection(app_slug);
|
|
90
|
+
const filters = [];
|
|
91
|
+
if (stage) filters.push(`stage = "${stage}"`);
|
|
92
|
+
if (outcome) filters.push(`outcome = "${outcome}"`);
|
|
93
|
+
if (category) filters.push(`category = "${category}"`);
|
|
94
|
+
const filter = filters.length > 0 ? filters.join(" AND ") : void 0;
|
|
95
|
+
const queryResults = await queryMemories({
|
|
96
|
+
query,
|
|
97
|
+
collection,
|
|
98
|
+
topK: limit * 2,
|
|
99
|
+
// Over-fetch to account for confidence filtering
|
|
100
|
+
filter
|
|
101
|
+
});
|
|
102
|
+
const results = [];
|
|
103
|
+
for (const result of queryResults) {
|
|
104
|
+
const memory = await fetchMemory(result.id, collection);
|
|
105
|
+
if (!memory) continue;
|
|
106
|
+
const confidence = calculateConfidence(memory);
|
|
107
|
+
if (!include_stale && confidence < 0.25) continue;
|
|
108
|
+
const createdAt = new Date(memory.metadata.created_at);
|
|
109
|
+
const lastValidatedAt = memory.metadata.last_validated_at ? new Date(memory.metadata.last_validated_at) : void 0;
|
|
110
|
+
const referenceDate = lastValidatedAt || createdAt;
|
|
111
|
+
const ageDays = (Date.now() - referenceDate.getTime()) / (24 * 60 * 60 * 1e3);
|
|
112
|
+
const decayFactor = Math.pow(0.5, ageDays / 30);
|
|
113
|
+
const finalScore = result.score * confidence;
|
|
114
|
+
if (finalScore < threshold) continue;
|
|
115
|
+
results.push({
|
|
116
|
+
memory,
|
|
117
|
+
score: finalScore,
|
|
118
|
+
raw_score: result.score,
|
|
119
|
+
age_days: ageDays,
|
|
120
|
+
decay_factor: decayFactor
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
results.sort((a, b) => b.score - a.score);
|
|
124
|
+
return results.slice(0, limit);
|
|
125
|
+
},
|
|
126
|
+
/**
|
|
127
|
+
* Get a support memory by ID
|
|
128
|
+
*
|
|
129
|
+
* @param id - Memory ID
|
|
130
|
+
* @param appSlug - App slug (for collection lookup)
|
|
131
|
+
* @returns Memory or null if not found
|
|
132
|
+
*/
|
|
133
|
+
async get(id, appSlug) {
|
|
134
|
+
const collection = getCollection(appSlug);
|
|
135
|
+
const memory = await fetchMemory(id, collection);
|
|
136
|
+
return memory;
|
|
137
|
+
},
|
|
138
|
+
/**
|
|
139
|
+
* Record a correction for a memory
|
|
140
|
+
*
|
|
141
|
+
* Sets outcome to 'corrected' and stores what should have happened.
|
|
142
|
+
* Also records a downvote for the memory.
|
|
143
|
+
*
|
|
144
|
+
* @param id - Memory ID
|
|
145
|
+
* @param appSlug - App slug
|
|
146
|
+
* @param correction - What should have happened
|
|
147
|
+
*/
|
|
148
|
+
async correct(id, appSlug, correction) {
|
|
149
|
+
const collection = getCollection(appSlug);
|
|
150
|
+
const memory = await fetchMemory(id, collection);
|
|
151
|
+
if (!memory) {
|
|
152
|
+
throw new Error("Memory not found");
|
|
153
|
+
}
|
|
154
|
+
const metadata = memory.metadata;
|
|
155
|
+
metadata.outcome = "corrected";
|
|
156
|
+
metadata.correction = correction.correction;
|
|
157
|
+
if (correction.category) {
|
|
158
|
+
metadata.category = correction.category;
|
|
159
|
+
}
|
|
160
|
+
await upsertMemory(memory);
|
|
161
|
+
await VotingService.vote(id, collection, "downvote");
|
|
162
|
+
},
|
|
163
|
+
/**
|
|
164
|
+
* Record success outcome for a memory
|
|
165
|
+
*
|
|
166
|
+
* Confirms the decision was correct and records an upvote.
|
|
167
|
+
*
|
|
168
|
+
* @param id - Memory ID
|
|
169
|
+
* @param appSlug - App slug
|
|
170
|
+
*/
|
|
171
|
+
async recordSuccess(id, appSlug) {
|
|
172
|
+
const collection = getCollection(appSlug);
|
|
173
|
+
const memory = await fetchMemory(id, collection);
|
|
174
|
+
if (!memory) {
|
|
175
|
+
throw new Error("Memory not found");
|
|
176
|
+
}
|
|
177
|
+
const metadata = memory.metadata;
|
|
178
|
+
metadata.outcome = "success";
|
|
179
|
+
await upsertMemory(memory);
|
|
180
|
+
await VotingService.vote(id, collection, "upvote");
|
|
181
|
+
},
|
|
182
|
+
/**
|
|
183
|
+
* Record failure outcome for a memory (without correction details)
|
|
184
|
+
*
|
|
185
|
+
* @param id - Memory ID
|
|
186
|
+
* @param appSlug - App slug
|
|
187
|
+
*/
|
|
188
|
+
async recordFailure(id, appSlug) {
|
|
189
|
+
const collection = getCollection(appSlug);
|
|
190
|
+
const memory = await fetchMemory(id, collection);
|
|
191
|
+
if (!memory) {
|
|
192
|
+
throw new Error("Memory not found");
|
|
193
|
+
}
|
|
194
|
+
const metadata = memory.metadata;
|
|
195
|
+
metadata.outcome = "failed";
|
|
196
|
+
await upsertMemory(memory);
|
|
197
|
+
await VotingService.vote(id, collection, "downvote");
|
|
198
|
+
},
|
|
199
|
+
/**
|
|
200
|
+
* Validate a memory (reset decay clock)
|
|
201
|
+
*
|
|
202
|
+
* Call when human confirms memory is still accurate.
|
|
203
|
+
*
|
|
204
|
+
* @param id - Memory ID
|
|
205
|
+
* @param appSlug - App slug
|
|
206
|
+
*/
|
|
207
|
+
async validate(id, appSlug) {
|
|
208
|
+
const collection = getCollection(appSlug);
|
|
209
|
+
await MemoryService.validate(id, collection);
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* Delete a memory
|
|
213
|
+
*
|
|
214
|
+
* @param id - Memory ID
|
|
215
|
+
* @param appSlug - App slug
|
|
216
|
+
*/
|
|
217
|
+
async delete(id, appSlug) {
|
|
218
|
+
const collection = getCollection(appSlug);
|
|
219
|
+
await MemoryService.delete(id, collection);
|
|
220
|
+
},
|
|
221
|
+
/**
|
|
222
|
+
* Cite memories (record they were used in a decision)
|
|
223
|
+
*
|
|
224
|
+
* Call when memories are retrieved and used to inform a decision.
|
|
225
|
+
*
|
|
226
|
+
* @param memoryIds - Memory IDs that were cited
|
|
227
|
+
* @param runId - Run/trace ID for tracking
|
|
228
|
+
* @param appSlug - App slug
|
|
229
|
+
*/
|
|
230
|
+
async cite(memoryIds, runId, appSlug) {
|
|
231
|
+
const collection = getCollection(appSlug);
|
|
232
|
+
await VotingService.cite(memoryIds, runId, collection);
|
|
233
|
+
},
|
|
234
|
+
/**
|
|
235
|
+
* Record outcome for cited memories
|
|
236
|
+
*
|
|
237
|
+
* Call after human review to track whether cited memories led to success.
|
|
238
|
+
*
|
|
239
|
+
* @param memoryIds - Memory IDs that were cited
|
|
240
|
+
* @param runId - Run/trace ID
|
|
241
|
+
* @param outcome - 'success' or 'failure'
|
|
242
|
+
* @param appSlug - App slug
|
|
243
|
+
*/
|
|
244
|
+
async recordCitationOutcome(memoryIds, runId, outcome, appSlug) {
|
|
245
|
+
const collection = getCollection(appSlug);
|
|
246
|
+
await VotingService.recordOutcome(memoryIds, runId, outcome, collection);
|
|
247
|
+
},
|
|
248
|
+
/**
|
|
249
|
+
* Parse stored content back to situation and decision
|
|
250
|
+
*
|
|
251
|
+
* @param content - Stored content string
|
|
252
|
+
* @returns Parsed situation and decision
|
|
253
|
+
*/
|
|
254
|
+
parseContent,
|
|
255
|
+
/**
|
|
256
|
+
* Format situation and decision into content string
|
|
257
|
+
*
|
|
258
|
+
* @param situation - Situation description
|
|
259
|
+
* @param decision - Decision made
|
|
260
|
+
* @returns Formatted content
|
|
261
|
+
*/
|
|
262
|
+
formatContent,
|
|
263
|
+
/**
|
|
264
|
+
* Get collection name for app
|
|
265
|
+
*
|
|
266
|
+
* @param appSlug - App slug
|
|
267
|
+
* @returns Collection name
|
|
268
|
+
*/
|
|
269
|
+
getCollection
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export {
|
|
273
|
+
SupportMemoryService
|
|
274
|
+
};
|
|
275
|
+
//# sourceMappingURL=chunk-WYKL32C3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../memory/src/support-memory.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { fetchMemory, queryMemories, upsertMemory } from './client'\nimport { calculateConfidence } from './decay'\nimport { MemoryService } from './memory'\nimport type {\n CorrectionInput,\n FindSimilarOptions,\n StoreSupportMemoryInput,\n SupportMemory,\n SupportMemoryMetadata,\n SupportSearchResult,\n} from './support-schemas'\nimport { VotingService } from './voting'\n\n/**\n * Collection prefix for support memories\n */\nconst SUPPORT_COLLECTION_PREFIX = 'support'\n\n/**\n * Global collection for cross-app patterns\n */\nconst GLOBAL_COLLECTION = `${SUPPORT_COLLECTION_PREFIX}:global`\n\n/**\n * Get collection name for app\n */\nfunction getCollection(appSlug?: string): string {\n if (!appSlug) return GLOBAL_COLLECTION\n return `${SUPPORT_COLLECTION_PREFIX}:${appSlug}`\n}\n\n/**\n * Format situation and decision into searchable content\n */\nfunction formatContent(situation: string, decision: string): string {\n return `SITUATION: ${situation.trim()}\\n\\nDECISION: ${decision.trim()}`\n}\n\n/**\n * Parse content back to situation and decision\n */\nfunction parseContent(content: string): {\n situation: string\n decision: string\n} {\n const situationMatch = content.match(/SITUATION:\\s*(.+?)(?=\\n\\nDECISION:|$)/s)\n const decisionMatch = content.match(/DECISION:\\s*(.+)/s)\n\n return {\n situation: situationMatch?.[1]?.trim() ?? content,\n decision: decisionMatch?.[1]?.trim() ?? '',\n }\n}\n\n/**\n * Support Memory Service\n *\n * High-level operations for storing and retrieving support decision memories\n * with semantic search, time decay, and outcome tracking.\n */\nexport const SupportMemoryService = {\n /**\n * Store a new support memory\n *\n * @param input - Situation, decision, and metadata\n * @returns The created support memory\n */\n async store(input: StoreSupportMemoryInput): Promise<SupportMemory> {\n const now = new Date().toISOString()\n const collection = getCollection(input.app_slug)\n\n const content = formatContent(input.situation, input.decision)\n\n const memory: SupportMemory = {\n id: randomUUID(),\n content,\n metadata: {\n collection,\n source: 'agent',\n app_slug: input.app_slug,\n tags: input.tags ?? [],\n confidence: 1,\n created_at: now,\n votes: {\n upvotes: 0,\n downvotes: 0,\n citations: 0,\n success_rate: 0,\n },\n stage: input.stage,\n outcome: input.outcome ?? 'success',\n correction: input.correction,\n category: input.category,\n conversation_id: input.conversation_id,\n },\n }\n\n await upsertMemory(memory)\n return memory\n },\n\n /**\n * Find similar support memories by semantic search\n *\n * @param query - Search query (situation description)\n * @param options - Filters and limits\n * @returns Array of matching memories with decay-adjusted scores\n */\n async findSimilar(\n query: string,\n options: FindSimilarOptions = {}\n ): Promise<SupportSearchResult[]> {\n const {\n app_slug,\n stage,\n outcome,\n category,\n limit = 10,\n threshold = 0.5,\n include_stale = false,\n } = options\n\n const collection = getCollection(app_slug)\n\n // Build metadata filter\n const filters: string[] = []\n if (stage) filters.push(`stage = \"${stage}\"`)\n if (outcome) filters.push(`outcome = \"${outcome}\"`)\n if (category) filters.push(`category = \"${category}\"`)\n const filter = filters.length > 0 ? filters.join(' AND ') : undefined\n\n // Query vector index\n const queryResults = await queryMemories({\n query,\n collection,\n topK: limit * 2, // Over-fetch to account for confidence filtering\n filter,\n })\n\n // Fetch full memories and calculate scores\n const results: SupportSearchResult[] = []\n\n for (const result of queryResults) {\n const memory = await fetchMemory(result.id, collection)\n if (!memory) continue\n\n // Calculate confidence with decay\n const confidence = calculateConfidence(memory)\n\n // Filter low-confidence unless explicitly included\n if (!include_stale && confidence < 0.25) continue\n\n // Calculate age\n const createdAt = new Date(memory.metadata.created_at)\n const lastValidatedAt = memory.metadata.last_validated_at\n ? new Date(memory.metadata.last_validated_at)\n : undefined\n const referenceDate = lastValidatedAt || createdAt\n const ageDays =\n (Date.now() - referenceDate.getTime()) / (24 * 60 * 60 * 1000)\n const decayFactor = Math.pow(0.5, ageDays / 30)\n\n // Final score combines similarity with confidence\n const finalScore = result.score * confidence\n\n if (finalScore < threshold) continue\n\n results.push({\n memory: memory as SupportMemory,\n score: finalScore,\n raw_score: result.score,\n age_days: ageDays,\n decay_factor: decayFactor,\n })\n }\n\n // Sort by score and limit\n results.sort((a, b) => b.score - a.score)\n return results.slice(0, limit)\n },\n\n /**\n * Get a support memory by ID\n *\n * @param id - Memory ID\n * @param appSlug - App slug (for collection lookup)\n * @returns Memory or null if not found\n */\n async get(id: string, appSlug?: string): Promise<SupportMemory | null> {\n const collection = getCollection(appSlug)\n const memory = await fetchMemory(id, collection)\n return memory as SupportMemory | null\n },\n\n /**\n * Record a correction for a memory\n *\n * Sets outcome to 'corrected' and stores what should have happened.\n * Also records a downvote for the memory.\n *\n * @param id - Memory ID\n * @param appSlug - App slug\n * @param correction - What should have happened\n */\n async correct(\n id: string,\n appSlug: string | undefined,\n correction: CorrectionInput\n ): Promise<void> {\n const collection = getCollection(appSlug)\n const memory = await fetchMemory(id, collection)\n\n if (!memory) {\n throw new Error('Memory not found')\n }\n\n // Update metadata with correction\n const metadata = memory.metadata as SupportMemoryMetadata\n metadata.outcome = 'corrected'\n metadata.correction = correction.correction\n if (correction.category) {\n metadata.category = correction.category\n }\n\n await upsertMemory(memory)\n\n // Record downvote\n await VotingService.vote(id, collection, 'downvote')\n },\n\n /**\n * Record success outcome for a memory\n *\n * Confirms the decision was correct and records an upvote.\n *\n * @param id - Memory ID\n * @param appSlug - App slug\n */\n async recordSuccess(id: string, appSlug?: string): Promise<void> {\n const collection = getCollection(appSlug)\n const memory = await fetchMemory(id, collection)\n\n if (!memory) {\n throw new Error('Memory not found')\n }\n\n const metadata = memory.metadata as SupportMemoryMetadata\n metadata.outcome = 'success'\n\n await upsertMemory(memory)\n\n // Record upvote\n await VotingService.vote(id, collection, 'upvote')\n },\n\n /**\n * Record failure outcome for a memory (without correction details)\n *\n * @param id - Memory ID\n * @param appSlug - App slug\n */\n async recordFailure(id: string, appSlug?: string): Promise<void> {\n const collection = getCollection(appSlug)\n const memory = await fetchMemory(id, collection)\n\n if (!memory) {\n throw new Error('Memory not found')\n }\n\n const metadata = memory.metadata as SupportMemoryMetadata\n metadata.outcome = 'failed'\n\n await upsertMemory(memory)\n\n // Record downvote\n await VotingService.vote(id, collection, 'downvote')\n },\n\n /**\n * Validate a memory (reset decay clock)\n *\n * Call when human confirms memory is still accurate.\n *\n * @param id - Memory ID\n * @param appSlug - App slug\n */\n async validate(id: string, appSlug?: string): Promise<void> {\n const collection = getCollection(appSlug)\n await MemoryService.validate(id, collection)\n },\n\n /**\n * Delete a memory\n *\n * @param id - Memory ID\n * @param appSlug - App slug\n */\n async delete(id: string, appSlug?: string): Promise<void> {\n const collection = getCollection(appSlug)\n await MemoryService.delete(id, collection)\n },\n\n /**\n * Cite memories (record they were used in a decision)\n *\n * Call when memories are retrieved and used to inform a decision.\n *\n * @param memoryIds - Memory IDs that were cited\n * @param runId - Run/trace ID for tracking\n * @param appSlug - App slug\n */\n async cite(\n memoryIds: string[],\n runId: string,\n appSlug?: string\n ): Promise<void> {\n const collection = getCollection(appSlug)\n await VotingService.cite(memoryIds, runId, collection)\n },\n\n /**\n * Record outcome for cited memories\n *\n * Call after human review to track whether cited memories led to success.\n *\n * @param memoryIds - Memory IDs that were cited\n * @param runId - Run/trace ID\n * @param outcome - 'success' or 'failure'\n * @param appSlug - App slug\n */\n async recordCitationOutcome(\n memoryIds: string[],\n runId: string,\n outcome: 'success' | 'failure',\n appSlug?: string\n ): Promise<void> {\n const collection = getCollection(appSlug)\n await VotingService.recordOutcome(memoryIds, runId, outcome, collection)\n },\n\n /**\n * Parse stored content back to situation and decision\n *\n * @param content - Stored content string\n * @returns Parsed situation and decision\n */\n parseContent,\n\n /**\n * Format situation and decision into content string\n *\n * @param situation - Situation description\n * @param decision - Decision made\n * @returns Formatted content\n */\n formatContent,\n\n /**\n * Get collection name for app\n *\n * @param appSlug - App slug\n * @returns Collection name\n */\n getCollection,\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_esm_shims
|
|
3
|
+
} from "./chunk-WFANXVQG.js";
|
|
4
|
+
|
|
5
|
+
// ../core/src/observability/otel.ts
|
|
6
|
+
init_esm_shims();
|
|
7
|
+
var telemetryConfig = {
|
|
8
|
+
isEnabled: true
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// ../core/src/router/classifier.ts
|
|
12
|
+
init_esm_shims();
|
|
13
|
+
import { generateObject } from "ai";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var CLASSIFIER_CATEGORIES = [
|
|
16
|
+
"needs_response",
|
|
17
|
+
"no_response",
|
|
18
|
+
"canned_response",
|
|
19
|
+
"human_required",
|
|
20
|
+
"refund",
|
|
21
|
+
"transfer",
|
|
22
|
+
"account_issue",
|
|
23
|
+
"billing",
|
|
24
|
+
"discount",
|
|
25
|
+
"technical",
|
|
26
|
+
"general",
|
|
27
|
+
"instructor_correspondence",
|
|
28
|
+
"team_correspondence"
|
|
29
|
+
];
|
|
30
|
+
var COMPLEXITY_TIERS = ["skip", "simple", "complex"];
|
|
31
|
+
var ClassifierResultSchema = z.object({
|
|
32
|
+
category: z.string(),
|
|
33
|
+
confidence: z.number(),
|
|
34
|
+
reasoning: z.string(),
|
|
35
|
+
complexity: z.string()
|
|
36
|
+
// skip, simple, complex
|
|
37
|
+
});
|
|
38
|
+
var categorySet = new Set(CLASSIFIER_CATEGORIES);
|
|
39
|
+
var complexitySet = new Set(COMPLEXITY_TIERS);
|
|
40
|
+
function validateCategory(raw) {
|
|
41
|
+
if (!categorySet.has(raw)) {
|
|
42
|
+
throw new Error(`Invalid category: ${raw}`);
|
|
43
|
+
}
|
|
44
|
+
return raw;
|
|
45
|
+
}
|
|
46
|
+
function validateComplexity(raw) {
|
|
47
|
+
if (!complexitySet.has(raw)) {
|
|
48
|
+
return "complex";
|
|
49
|
+
}
|
|
50
|
+
return raw;
|
|
51
|
+
}
|
|
52
|
+
async function classifyMessage(message, context) {
|
|
53
|
+
let prompt = `Classify this customer support message.
|
|
54
|
+
|
|
55
|
+
## Categories
|
|
56
|
+
- needs_response: Requires agent reply
|
|
57
|
+
- no_response: Automated/spam messages, acknowledgments (thanks, got it)
|
|
58
|
+
- canned_response: Can use template response
|
|
59
|
+
- human_required: Complex or sensitive issues
|
|
60
|
+
- refund: Refund request
|
|
61
|
+
- transfer: License transfer request
|
|
62
|
+
- account_issue: Login or access problems
|
|
63
|
+
- billing: Invoice or charge inquiries
|
|
64
|
+
- discount: Discount requests (student, non-profit, team, special pricing, coupon codes). NOT refunds.
|
|
65
|
+
- technical: Product functionality issues
|
|
66
|
+
- general: Other inquiries
|
|
67
|
+
- instructor_correspondence: ONLY use for genuine personal fan mail - compliments about teaching style, heartfelt appreciation, personal life questions to the instructor. NOT discount requests, pricing questions, partnership pitches, or any message asking for something. If they want a discount, student rate, non-profit pricing, or special deal - that's billing/general, NOT instructor_correspondence.
|
|
68
|
+
- team_correspondence: Business discussions between team members, partners, or internal stakeholders about product strategy, operations, launches, cohorts, scheduling. NOT customer support. Route to human without AI response.
|
|
69
|
+
|
|
70
|
+
## Complexity (for model selection)
|
|
71
|
+
- skip: Don't respond (spam, acks, bounces, auto-replies, team correspondence)
|
|
72
|
+
- simple: Easy to answer (FAQ, magic link, basic info), instructor correspondence - fast model OK
|
|
73
|
+
- complex: Nuanced issue, frustrated customer, needs reasoning - use powerful model
|
|
74
|
+
|
|
75
|
+
## Sender context
|
|
76
|
+
${context?.senderEmail ? `Current message from: ${context.senderEmail}` : "Sender unknown"}
|
|
77
|
+
|
|
78
|
+
Message: ${message}`;
|
|
79
|
+
if (context?.threadMessages && context.threadMessages.length > 0) {
|
|
80
|
+
const threadContext = context.threadMessages.map((m) => {
|
|
81
|
+
const direction = m.isInbound ? "\u2192" : "\u2190";
|
|
82
|
+
const preview = m.body.length > 300 ? m.body.slice(0, 300) + "..." : m.body;
|
|
83
|
+
return `[${m.sender}] ${direction}
|
|
84
|
+
${preview}`;
|
|
85
|
+
}).join("\n\n");
|
|
86
|
+
prompt += `
|
|
87
|
+
|
|
88
|
+
## Thread history (oldest first)
|
|
89
|
+
${threadContext}`;
|
|
90
|
+
} else if (context?.recentMessages && context.recentMessages.length > 0) {
|
|
91
|
+
const conversationContext = context.recentMessages.join("\n---\n");
|
|
92
|
+
prompt += `
|
|
93
|
+
|
|
94
|
+
Recent conversation context:
|
|
95
|
+
${conversationContext}`;
|
|
96
|
+
}
|
|
97
|
+
if (context?.priorKnowledge && context.priorKnowledge.trim().length > 0) {
|
|
98
|
+
prompt += `
|
|
99
|
+
|
|
100
|
+
Prior knowledge from memory:
|
|
101
|
+
${context.priorKnowledge}`;
|
|
102
|
+
}
|
|
103
|
+
prompt += `
|
|
104
|
+
|
|
105
|
+
Provide:
|
|
106
|
+
1. category: One of the categories above
|
|
107
|
+
2. complexity: skip, simple, or complex
|
|
108
|
+
3. confidence: Score 0-1 (>0.9 for clear cases, 0.7-0.9 for likely, <0.7 for uncertain)
|
|
109
|
+
4. reasoning: Brief explanation (1-2 sentences)`;
|
|
110
|
+
const result = await generateObject({
|
|
111
|
+
model: "anthropic/claude-haiku-4-5",
|
|
112
|
+
prompt,
|
|
113
|
+
schema: ClassifierResultSchema,
|
|
114
|
+
experimental_telemetry: telemetryConfig
|
|
115
|
+
});
|
|
116
|
+
const usage = result.usage;
|
|
117
|
+
return {
|
|
118
|
+
category: validateCategory(result.object.category),
|
|
119
|
+
confidence: result.object.confidence,
|
|
120
|
+
reasoning: result.object.reasoning,
|
|
121
|
+
complexity: validateComplexity(result.object.complexity),
|
|
122
|
+
usage: usage?.promptTokens !== void 0 ? {
|
|
123
|
+
promptTokens: usage.promptTokens,
|
|
124
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
125
|
+
totalTokens: usage.totalTokens ?? usage.promptTokens
|
|
126
|
+
} : void 0
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
telemetryConfig,
|
|
132
|
+
classifyMessage
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=chunk-ZNF7XD2S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../core/src/observability/otel.ts","../../core/src/router/classifier.ts"],"sourcesContent":["/**\n * OpenTelemetry + Langfuse integration for AI SDK tracing\n *\n * This captures telemetry spans from the Vercel AI SDK and sends them to Langfuse\n * for LLM observability including token usage, latency, and cost tracking.\n *\n * Usage:\n * 1. Call initializeOtel() at app startup (before any AI SDK calls)\n * 2. Pass experimental_telemetry: { isEnabled: true } to AI SDK functions\n *\n * @see https://langfuse.com/docs/integrations/opentelemetry\n */\n\nimport { LangfuseSpanProcessor } from '@langfuse/otel'\nimport { NodeSDK } from '@opentelemetry/sdk-node'\n\nlet sdk: NodeSDK | null = null\nlet initialized = false\n\n/**\n * Initialize OpenTelemetry with Langfuse span processor.\n *\n * Should be called once at app startup. Safe to call multiple times\n * (subsequent calls are no-ops).\n *\n * Required env vars:\n * - LANGFUSE_PUBLIC_KEY\n * - LANGFUSE_SECRET_KEY\n * - LANGFUSE_HOST (optional, defaults to cloud.langfuse.com)\n */\nexport function initializeOtel(): void {\n if (initialized) {\n return\n }\n\n const publicKey = process.env.LANGFUSE_PUBLIC_KEY\n const secretKey = process.env.LANGFUSE_SECRET_KEY\n\n if (!publicKey || !secretKey) {\n console.warn(\n '[OTel] LANGFUSE_PUBLIC_KEY or LANGFUSE_SECRET_KEY not set, AI SDK telemetry disabled'\n )\n initialized = true\n return\n }\n\n try {\n sdk = new NodeSDK({\n spanProcessors: [new LangfuseSpanProcessor()],\n })\n\n sdk.start()\n initialized = true\n console.log('[OTel] OpenTelemetry + Langfuse initialized')\n } catch (error) {\n console.error('[OTel] Failed to initialize:', error)\n initialized = true // Mark as initialized to prevent retry loops\n }\n}\n\n/**\n * Shutdown OpenTelemetry gracefully.\n * Call this before process exit to flush pending spans.\n */\nexport async function shutdownOtel(): Promise<void> {\n if (sdk) {\n await sdk.shutdown()\n sdk = null\n initialized = false\n console.log('[OTel] OpenTelemetry shutdown complete')\n }\n}\n\n/**\n * Telemetry config for AI SDK calls.\n * Pass this to generateText/generateObject/streamText etc.\n */\nexport const telemetryConfig = {\n isEnabled: true,\n} as const\n","import { generateObject } from 'ai'\nimport { z } from 'zod'\nimport { telemetryConfig } from '../observability/otel'\n\n// Valid categories for classification\nexport const CLASSIFIER_CATEGORIES = [\n 'needs_response',\n 'no_response',\n 'canned_response',\n 'human_required',\n 'refund',\n 'transfer',\n 'account_issue',\n 'billing',\n 'discount',\n 'technical',\n 'general',\n 'instructor_correspondence',\n 'team_correspondence',\n] as const\n\n/**\n * Structured thread message for classifier context\n */\nexport type ThreadMessage = {\n sender: string // email address\n isInbound: boolean\n body: string\n}\n\nexport type ClassifierCategory = (typeof CLASSIFIER_CATEGORIES)[number]\n\n// Complexity tiers for model selection\nexport const COMPLEXITY_TIERS = ['skip', 'simple', 'complex'] as const\nexport type ComplexityTier = (typeof COMPLEXITY_TIERS)[number]\n\n// Flat schema to avoid TS2589 with Output.object generics\n// Category validation happens at runtime via Set lookup\nexport const ClassifierResultSchema = z.object({\n category: z.string(),\n confidence: z.number(),\n reasoning: z.string(),\n complexity: z.string(), // skip, simple, complex\n})\n\n// Runtime validation for categories\nconst categorySet = new Set<string>(CLASSIFIER_CATEGORIES)\nconst complexitySet = new Set<string>(COMPLEXITY_TIERS)\n\nexport type ClassifierResult = {\n category: ClassifierCategory\n confidence: number\n reasoning: string\n complexity: ComplexityTier\n usage?: {\n promptTokens: number\n completionTokens: number\n totalTokens: number\n }\n}\n\nfunction validateCategory(raw: string): ClassifierCategory {\n if (!categorySet.has(raw)) {\n throw new Error(`Invalid category: ${raw}`)\n }\n return raw as ClassifierCategory\n}\n\nfunction validateComplexity(raw: string): ComplexityTier {\n if (!complexitySet.has(raw)) {\n // Default to complex if unknown\n return 'complex'\n }\n return raw as ComplexityTier\n}\n\nexport async function classifyMessage(\n message: string,\n context?: {\n recentMessages?: string[] // deprecated: use threadMessages instead\n threadMessages?: ThreadMessage[]\n senderEmail?: string\n priorKnowledge?: string\n }\n): Promise<ClassifierResult> {\n // Build prompt with category guidance and optional conversation context\n let prompt = `Classify this customer support message.\n\n## Categories\n- needs_response: Requires agent reply\n- no_response: Automated/spam messages, acknowledgments (thanks, got it)\n- canned_response: Can use template response\n- human_required: Complex or sensitive issues\n- refund: Refund request\n- transfer: License transfer request\n- account_issue: Login or access problems\n- billing: Invoice or charge inquiries\n- discount: Discount requests (student, non-profit, team, special pricing, coupon codes). NOT refunds.\n- technical: Product functionality issues\n- general: Other inquiries\n- instructor_correspondence: ONLY use for genuine personal fan mail - compliments about teaching style, heartfelt appreciation, personal life questions to the instructor. NOT discount requests, pricing questions, partnership pitches, or any message asking for something. If they want a discount, student rate, non-profit pricing, or special deal - that's billing/general, NOT instructor_correspondence.\n- team_correspondence: Business discussions between team members, partners, or internal stakeholders about product strategy, operations, launches, cohorts, scheduling. NOT customer support. Route to human without AI response.\n\n## Complexity (for model selection)\n- skip: Don't respond (spam, acks, bounces, auto-replies, team correspondence)\n- simple: Easy to answer (FAQ, magic link, basic info), instructor correspondence - fast model OK\n- complex: Nuanced issue, frustrated customer, needs reasoning - use powerful model\n\n## Sender context\n${context?.senderEmail ? `Current message from: ${context.senderEmail}` : 'Sender unknown'}\n\nMessage: ${message}`\n\n // Prefer structured thread messages over legacy string array\n if (context?.threadMessages && context.threadMessages.length > 0) {\n const threadContext = context.threadMessages\n .map((m) => {\n const direction = m.isInbound ? '→' : '←'\n const preview =\n m.body.length > 300 ? m.body.slice(0, 300) + '...' : m.body\n return `[${m.sender}] ${direction}\\n${preview}`\n })\n .join('\\n\\n')\n prompt += `\\n\\n## Thread history (oldest first)\\n${threadContext}`\n } else if (context?.recentMessages && context.recentMessages.length > 0) {\n // Legacy fallback\n const conversationContext = context.recentMessages.join('\\n---\\n')\n prompt += `\\n\\nRecent conversation context:\\n${conversationContext}`\n }\n\n if (context?.priorKnowledge && context.priorKnowledge.trim().length > 0) {\n prompt += `\\n\\nPrior knowledge from memory:\\n${context.priorKnowledge}`\n }\n\n prompt += `\\n\\nProvide:\n1. category: One of the categories above\n2. complexity: skip, simple, or complex\n3. confidence: Score 0-1 (>0.9 for clear cases, 0.7-0.9 for likely, <0.7 for uncertain)\n4. reasoning: Brief explanation (1-2 sentences)`\n\n const result = await generateObject({\n model: 'anthropic/claude-haiku-4-5',\n prompt,\n schema: ClassifierResultSchema,\n experimental_telemetry: telemetryConfig,\n })\n\n // AI SDK v6 usage type - extract token counts safely\n const usage = result.usage as\n | { promptTokens?: number; completionTokens?: number; totalTokens?: number }\n | undefined\n\n return {\n category: validateCategory(result.object.category),\n confidence: result.object.confidence,\n reasoning: result.object.reasoning,\n complexity: validateComplexity(result.object.complexity),\n usage:\n usage?.promptTokens !== undefined\n ? {\n promptTokens: usage.promptTokens,\n completionTokens: usage.completionTokens ?? 0,\n totalTokens: usage.totalTokens ?? usage.promptTokens,\n }\n : undefined,\n }\n}\n"],"mappings":";;;;;AAAA;AA6EO,IAAM,kBAAkB;AAAA,EAC7B,WAAW;AACb;;;AC/EA;AAAA,SAAS,sBAAsB;AAC/B,SAAS,SAAS;AAIX,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAcO,IAAM,mBAAmB,CAAC,QAAQ,UAAU,SAAS;AAKrD,IAAM,yBAAyB,EAAE,OAAO;AAAA,EAC7C,UAAU,EAAE,OAAO;AAAA,EACnB,YAAY,EAAE,OAAO;AAAA,EACrB,WAAW,EAAE,OAAO;AAAA,EACpB,YAAY,EAAE,OAAO;AAAA;AACvB,CAAC;AAGD,IAAM,cAAc,IAAI,IAAY,qBAAqB;AACzD,IAAM,gBAAgB,IAAI,IAAY,gBAAgB;AActD,SAAS,iBAAiB,KAAiC;AACzD,MAAI,CAAC,YAAY,IAAI,GAAG,GAAG;AACzB,UAAM,IAAI,MAAM,qBAAqB,GAAG,EAAE;AAAA,EAC5C;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,KAA6B;AACvD,MAAI,CAAC,cAAc,IAAI,GAAG,GAAG;AAE3B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,eAAsB,gBACpB,SACA,SAM2B;AAE3B,MAAI,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBb,SAAS,cAAc,yBAAyB,QAAQ,WAAW,KAAK,gBAAgB;AAAA;AAAA,WAE/E,OAAO;AAGhB,MAAI,SAAS,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAChE,UAAM,gBAAgB,QAAQ,eAC3B,IAAI,CAAC,MAAM;AACV,YAAM,YAAY,EAAE,YAAY,WAAM;AACtC,YAAM,UACJ,EAAE,KAAK,SAAS,MAAM,EAAE,KAAK,MAAM,GAAG,GAAG,IAAI,QAAQ,EAAE;AACzD,aAAO,IAAI,EAAE,MAAM,KAAK,SAAS;AAAA,EAAK,OAAO;AAAA,IAC/C,CAAC,EACA,KAAK,MAAM;AACd,cAAU;AAAA;AAAA;AAAA,EAAyC,aAAa;AAAA,EAClE,WAAW,SAAS,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAEvE,UAAM,sBAAsB,QAAQ,eAAe,KAAK,SAAS;AACjE,cAAU;AAAA;AAAA;AAAA,EAAqC,mBAAmB;AAAA,EACpE;AAEA,MAAI,SAAS,kBAAkB,QAAQ,eAAe,KAAK,EAAE,SAAS,GAAG;AACvE,cAAU;AAAA;AAAA;AAAA,EAAqC,QAAQ,cAAc;AAAA,EACvE;AAEA,YAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMV,QAAM,SAAS,MAAM,eAAe;AAAA,IAClC,OAAO;AAAA,IACP;AAAA,IACA,QAAQ;AAAA,IACR,wBAAwB;AAAA,EAC1B,CAAC;AAGD,QAAM,QAAQ,OAAO;AAIrB,SAAO;AAAA,IACL,UAAU,iBAAiB,OAAO,OAAO,QAAQ;AAAA,IACjD,YAAY,OAAO,OAAO;AAAA,IAC1B,WAAW,OAAO,OAAO;AAAA,IACzB,YAAY,mBAAmB,OAAO,OAAO,UAAU;AAAA,IACvD,OACE,OAAO,iBAAiB,SACpB;AAAA,MACE,cAAc,MAAM;AAAA,MACpB,kBAAkB,MAAM,oBAAoB;AAAA,MAC5C,aAAa,MAAM,eAAe,MAAM;AAAA,IAC1C,IACA;AAAA,EACR;AACF;","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_AGENT_MODEL,
|
|
3
|
+
SUPPORT_AGENT_PROMPT,
|
|
4
|
+
agentTools,
|
|
5
|
+
runSupportAgent
|
|
6
|
+
} from "./chunk-2NCCVTEE.js";
|
|
7
|
+
import "./chunk-KEV3QKXP.js";
|
|
8
|
+
import "./chunk-MLNDSBZ4.js";
|
|
9
|
+
import "./chunk-ZNF7XD2S.js";
|
|
10
|
+
import "./chunk-H3D6VCME.js";
|
|
11
|
+
import "./chunk-MG37YDAK.js";
|
|
12
|
+
import "./chunk-F4EM72IH.js";
|
|
13
|
+
import "./chunk-WFANXVQG.js";
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_AGENT_MODEL,
|
|
16
|
+
SUPPORT_AGENT_PROMPT,
|
|
17
|
+
agentTools,
|
|
18
|
+
runSupportAgent
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=config-AUAIYDSI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|