chat-agent-toolkit 1.2.49 → 1.2.51

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,5 +1,5 @@
1
1
  /**
2
- * Convert markdown text to HTML with Highlight.js syntax highlighting.
2
+ * Convert markdown text to HTML with Prism.js syntax highlighting.
3
3
  * Unescapes HTML entities like `&` \u2192 `&`.
4
4
  */
5
5
  export declare function convertMarkdownToHTMLEscaped(markdown: string): Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-agent-toolkit",
3
- "version": "1.2.49",
3
+ "version": "1.2.51",
4
4
  "description": "Multi-provider AI agent toolkit: generate language responses, search the web, extract content, and manage memory across 10+ LLM providers.",
5
5
  "author": "vtempest <grokthiscontact@gmail.com>",
6
6
  "license": "AGPL-3.0",
@@ -81,7 +81,7 @@
81
81
  "@mastra/memory": "^1.22.2",
82
82
  "ai": "^5.0.0",
83
83
  "drizzle-orm": "^0.45.1",
84
- "highlight.js": "^11.11.1",
84
+ "prismjs": "^1.30.0",
85
85
  "html-entities": "^2.6.0",
86
86
  "manage-storage": "^0.0.13",
87
87
  "marked": "^17.0.4",
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ export * from "./tools";
20
20
  // Both write-language and ./tools export AGENT_TOOLS; this package's own wins.
21
21
  export { AGENT_TOOLS } from "./tools";
22
22
  export * from "./utils";
23
+ export * from "./mastra";
23
24
  export { configManager, ModelRegistry, getEnv, getModelProvidersUIConfigSection } from "./config";
24
25
  export type { Config, ConfigModelProvider, MCPServerConfig, UIConfigSections, Model, ModelWithProvider } from "./config";
25
26
  export { cropProvider, cropProviderAsBlob, cropProviderAsDataURL, getProviderImage, getProviderNames } from "./utils/provider-image-cropper";
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @fileoverview Mastra Agent Factory
3
+ *
4
+ * Creates typed Mastra agents with tool-calling, model selection,
5
+ * and optional memory integration.
6
+ */
7
+
8
+ import { Agent } from "@mastra/core/agent";
9
+ import { createTool } from "@mastra/core/tools";
10
+ import { z } from "zod";
11
+
12
+ export interface MastraAgentConfig {
13
+ id: string;
14
+ name: string;
15
+ instructions: string;
16
+ model: any;
17
+ tools?: Record<string, any>;
18
+ maxSteps?: number;
19
+ memory?: {
20
+ enabled: boolean;
21
+ threadId?: string;
22
+ userId?: string;
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Create a Mastra agent with full configuration.
28
+ * Supports tool-calling, multi-step reasoning, and memory.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { createMastraAgent } from "chat-agent-toolkit/mastra";
33
+ * import { openai } from "@ai-sdk/openai";
34
+ *
35
+ * const agent = createMastraAgent({
36
+ * id: "research-assistant",
37
+ * name: "Research Assistant",
38
+ * instructions: "Help users research topics thoroughly.",
39
+ * model: openai("gpt-4o"),
40
+ * tools: { webSearch, extractPage },
41
+ * });
42
+ *
43
+ * const result = await agent.generate("What is RAG?");
44
+ * ```
45
+ */
46
+ export function createMastraAgent(config: MastraAgentConfig): Agent {
47
+ return new Agent({
48
+ id: config.id,
49
+ name: config.name,
50
+ instructions: config.instructions,
51
+ model: config.model,
52
+ tools: config.tools || {},
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Create a Mastra agent with inline tool definitions using Zod schemas.
58
+ * Convenience wrapper for quick prototyping.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const agent = createToolAgent({
63
+ * id: "calculator",
64
+ * name: "Calculator",
65
+ * instructions: "Perform math operations.",
66
+ * model: openai("gpt-4o"),
67
+ * toolDefs: [{
68
+ * id: "add",
69
+ * description: "Add two numbers",
70
+ * inputSchema: z.object({ a: z.number(), b: z.number() }),
71
+ * outputSchema: z.object({ result: z.number() }),
72
+ * execute: async ({ context }) => ({ result: context.a + context.b }),
73
+ * }],
74
+ * });
75
+ * ```
76
+ */
77
+ export function createToolAgent(config: {
78
+ id: string;
79
+ name: string;
80
+ instructions: string;
81
+ model: any;
82
+ toolDefs: Array<{
83
+ id: string;
84
+ description: string;
85
+ inputSchema: z.ZodType;
86
+ outputSchema: z.ZodType;
87
+ execute: (params: { context: any }) => Promise<any>;
88
+ }>;
89
+ }): Agent {
90
+ const tools: Record<string, any> = {};
91
+
92
+ for (const def of config.toolDefs) {
93
+ tools[def.id] = createTool({
94
+ id: def.id,
95
+ description: def.description,
96
+ inputSchema: def.inputSchema,
97
+ outputSchema: def.outputSchema,
98
+ execute: def.execute,
99
+ });
100
+ }
101
+
102
+ return new Agent({
103
+ id: config.id,
104
+ name: config.name,
105
+ instructions: config.instructions,
106
+ model: config.model,
107
+ tools,
108
+ });
109
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * @fileoverview Mastra Evals
3
+ *
4
+ * Scorer-based evaluation functions for LLM outputs.
5
+ * Assess factuality, relevance, coherence, and safety.
6
+ */
7
+
8
+ export interface EvalResult {
9
+ score: number;
10
+ reason: string;
11
+ }
12
+
13
+ export interface EvalSuiteResult {
14
+ overall: number;
15
+ results: Record<string, EvalResult>;
16
+ }
17
+
18
+ /**
19
+ * Evaluate factuality/groundedness of a response.
20
+ * Checks whether the output references sources or evidence.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const result = await factualityEval(
25
+ * "The earth orbits the sun (source: NASA).",
26
+ * "What does the earth orbit?"
27
+ * );
28
+ * // { score: 0.9, reason: "Response includes source attribution." }
29
+ * ```
30
+ */
31
+ export async function factualityEval(
32
+ output: string,
33
+ _query?: string,
34
+ sources?: string[]
35
+ ): Promise<EvalResult> {
36
+ const hasSourceRef = /\b(source|according to|reference|cited?|per)\b/i.test(output);
37
+ const hasUrl = /https?:\/\/\S+/.test(output);
38
+ const matchesSources = sources?.some((s) =>
39
+ output.toLowerCase().includes(s.toLowerCase().slice(0, 30))
40
+ );
41
+
42
+ let score = 0.3;
43
+ const reasons: string[] = [];
44
+
45
+ if (hasSourceRef) {
46
+ score += 0.25;
47
+ reasons.push("includes source attribution");
48
+ }
49
+ if (hasUrl) {
50
+ score += 0.15;
51
+ reasons.push("contains URL reference");
52
+ }
53
+ if (matchesSources) {
54
+ score += 0.3;
55
+ reasons.push("matches provided source material");
56
+ }
57
+ if (!hasSourceRef && !hasUrl && !matchesSources) {
58
+ reasons.push("no grounding evidence found");
59
+ }
60
+
61
+ return {
62
+ score: Math.min(score, 1.0),
63
+ reason: reasons.join("; ") + ".",
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Evaluate relevance of a response to the query.
69
+ * Uses keyword overlap and length heuristics.
70
+ */
71
+ export async function relevanceEval(
72
+ output: string,
73
+ query: string
74
+ ): Promise<EvalResult> {
75
+ const queryWords = new Set(
76
+ query.toLowerCase().split(/\s+/).filter((w) => w.length > 2)
77
+ );
78
+ const outputWords = output.toLowerCase().split(/\s+/);
79
+ const matchCount = outputWords.filter((w) => queryWords.has(w)).length;
80
+ const overlapRatio = queryWords.size > 0 ? matchCount / queryWords.size : 0;
81
+
82
+ const isTooShort = output.length < 20;
83
+ const isTooLong = output.length > query.length * 50;
84
+
85
+ let score = Math.min(overlapRatio * 1.5, 0.7);
86
+ const reasons: string[] = [];
87
+
88
+ if (overlapRatio > 0.5) {
89
+ score += 0.2;
90
+ reasons.push("high keyword overlap with query");
91
+ } else if (overlapRatio > 0.2) {
92
+ reasons.push("moderate keyword overlap");
93
+ } else {
94
+ reasons.push("low keyword overlap with query");
95
+ }
96
+
97
+ if (isTooShort) {
98
+ score -= 0.2;
99
+ reasons.push("response too short");
100
+ }
101
+ if (isTooLong) {
102
+ score -= 0.1;
103
+ reasons.push("response excessively long");
104
+ }
105
+
106
+ if (!isTooShort && !isTooLong && overlapRatio > 0.3) {
107
+ score += 0.1;
108
+ }
109
+
110
+ return {
111
+ score: Math.max(0, Math.min(score, 1.0)),
112
+ reason: reasons.join("; ") + ".",
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Evaluate coherence and structure of a response.
118
+ * Checks sentence structure, paragraph flow, and logical markers.
119
+ */
120
+ export async function coherenceEval(output: string): Promise<EvalResult> {
121
+ const sentences = output.split(/[.!?]+/).filter((s) => s.trim().length > 5);
122
+ const hasLogicalMarkers = /\b(therefore|because|however|furthermore|additionally|first|second|finally)\b/i.test(output);
123
+ const hasParagraphs = output.includes("\n\n") || output.includes("\n");
124
+ const avgSentenceLength =
125
+ sentences.length > 0
126
+ ? sentences.reduce((sum, s) => sum + s.trim().split(/\s+/).length, 0) / sentences.length
127
+ : 0;
128
+
129
+ let score = 0.4;
130
+ const reasons: string[] = [];
131
+
132
+ if (sentences.length >= 2) {
133
+ score += 0.15;
134
+ reasons.push("multiple sentences");
135
+ }
136
+ if (hasLogicalMarkers) {
137
+ score += 0.2;
138
+ reasons.push("uses logical connectors");
139
+ }
140
+ if (hasParagraphs && output.length > 200) {
141
+ score += 0.1;
142
+ reasons.push("structured with paragraphs");
143
+ }
144
+ if (avgSentenceLength > 5 && avgSentenceLength < 40) {
145
+ score += 0.15;
146
+ reasons.push("appropriate sentence length");
147
+ }
148
+ if (sentences.length < 2 && output.length > 100) {
149
+ score -= 0.2;
150
+ reasons.push("run-on text without sentence breaks");
151
+ }
152
+
153
+ return {
154
+ score: Math.max(0, Math.min(score, 1.0)),
155
+ reason: reasons.join("; ") + ".",
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Evaluate output for toxic or harmful content.
161
+ * Basic keyword/pattern-based detection.
162
+ */
163
+ export async function toxicityEval(output: string): Promise<EvalResult> {
164
+ const toxicPatterns = [
165
+ /\b(hate|kill|destroy|attack)\s+(all|every|them)\b/i,
166
+ /\b(stupid|idiot|moron|dumb)\s+(people|person|users?)\b/i,
167
+ /\b(should\s+die|deserve\s+to\s+die)\b/i,
168
+ ];
169
+
170
+ const matches = toxicPatterns.filter((p) => p.test(output));
171
+ const score = matches.length === 0 ? 1.0 : Math.max(0, 1.0 - matches.length * 0.4);
172
+
173
+ return {
174
+ score,
175
+ reason:
176
+ matches.length === 0
177
+ ? "No toxic patterns detected."
178
+ : `Detected ${matches.length} potentially harmful pattern(s).`,
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Run a full evaluation suite against a response.
184
+ *
185
+ * @example
186
+ * ```ts
187
+ * const results = await runEvalSuite({
188
+ * output: agentResponse,
189
+ * query: "What is photosynthesis?",
190
+ * sources: ["Biology textbook chapter 3"],
191
+ * });
192
+ *
193
+ * console.log(results.overall); // 0.78
194
+ * console.log(results.results.factuality.score); // 0.85
195
+ * ```
196
+ */
197
+ export async function runEvalSuite(params: {
198
+ output: string;
199
+ query?: string;
200
+ sources?: string[];
201
+ }): Promise<EvalSuiteResult> {
202
+ const { output, query = "", sources } = params;
203
+
204
+ const [factuality, relevance, coherence, toxicity] = await Promise.all([
205
+ factualityEval(output, query, sources),
206
+ relevanceEval(output, query),
207
+ coherenceEval(output),
208
+ toxicityEval(output),
209
+ ]);
210
+
211
+ const results: Record<string, EvalResult> = {
212
+ factuality,
213
+ relevance,
214
+ coherence,
215
+ toxicity,
216
+ };
217
+
218
+ const scores = Object.values(results).map((r) => r.score);
219
+ const overall = scores.reduce((sum, s) => sum + s, 0) / scores.length;
220
+
221
+ return { overall, results };
222
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Mastra Integration Module
3
+ *
4
+ * Full-featured Mastra AI framework integration providing agents, workflows,
5
+ * RAG, evals, telemetry, and model routing. Built on @mastra/core with
6
+ * Vercel AI SDK compatibility.
7
+ *
8
+ * @module mastra
9
+ */
10
+
11
+ export { createMastraAgent, createToolAgent, type MastraAgentConfig } from "./agents";
12
+ export {
13
+ createWorkflowStep,
14
+ createResearchWorkflow,
15
+ createRAGWorkflow,
16
+ type WorkflowStepConfig,
17
+ } from "./workflows";
18
+ export {
19
+ MastraRAG,
20
+ createRAGPipeline,
21
+ type RAGConfig,
22
+ type RAGDocument,
23
+ type RAGChunk,
24
+ type RAGRetrievalResult,
25
+ } from "./rag";
26
+ export {
27
+ factualityEval,
28
+ relevanceEval,
29
+ coherenceEval,
30
+ toxicityEval,
31
+ runEvalSuite,
32
+ type EvalResult,
33
+ type EvalSuiteResult,
34
+ } from "./evals";
35
+ export {
36
+ createMastraInstance,
37
+ type MastraInstanceConfig,
38
+ type TelemetryConfig,
39
+ } from "./telemetry";
40
+ export {
41
+ createModelRouter,
42
+ loadMastraModel,
43
+ type ModelRouterConfig,
44
+ type RoutingStrategy,
45
+ } from "./model-routing";
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @fileoverview Mastra Model Routing
3
+ *
4
+ * Multi-provider model routing with strategy selection:
5
+ * cost-optimized, latency-optimized, capability-based, or round-robin.
6
+ */
7
+
8
+ import { Agent } from "@mastra/core/agent";
9
+
10
+ export type RoutingStrategy = "cost" | "latency" | "capability" | "round-robin";
11
+
12
+ export interface ModelRouterConfig {
13
+ providers: Array<{
14
+ id: string;
15
+ model: any;
16
+ costPer1kTokens?: number;
17
+ avgLatencyMs?: number;
18
+ capabilities?: string[];
19
+ priority?: number;
20
+ }>;
21
+ strategy: RoutingStrategy;
22
+ fallbackProviderId?: string;
23
+ }
24
+
25
+ /**
26
+ * Model router that selects providers based on configurable strategies.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { createModelRouter } from "chat-agent-toolkit/mastra";
31
+ * import { openai } from "@ai-sdk/openai";
32
+ * import { anthropic } from "@ai-sdk/anthropic";
33
+ *
34
+ * const router = createModelRouter({
35
+ * providers: [
36
+ * { id: "openai", model: openai("gpt-4o"), costPer1kTokens: 0.005 },
37
+ * { id: "anthropic", model: anthropic("claude-sonnet-4-20250514"), costPer1kTokens: 0.003 },
38
+ * { id: "fast", model: openai("gpt-4o-mini"), costPer1kTokens: 0.0002, avgLatencyMs: 200 },
39
+ * ],
40
+ * strategy: "cost",
41
+ * });
42
+ *
43
+ * const model = router.select(); // cheapest provider
44
+ * const model2 = router.select({ capabilities: ["vision"] }); // capability match
45
+ * ```
46
+ */
47
+ export function createModelRouter(config: ModelRouterConfig) {
48
+ let roundRobinIndex = 0;
49
+
50
+ return {
51
+ select(options?: {
52
+ capabilities?: string[];
53
+ maxCost?: number;
54
+ maxLatency?: number;
55
+ }): any {
56
+ let candidates = [...config.providers];
57
+
58
+ if (options?.capabilities) {
59
+ candidates = candidates.filter((p) =>
60
+ options.capabilities!.every(
61
+ (cap) => p.capabilities?.includes(cap)
62
+ )
63
+ );
64
+ }
65
+
66
+ if (options?.maxCost) {
67
+ candidates = candidates.filter(
68
+ (p) => (p.costPer1kTokens ?? Infinity) <= options.maxCost!
69
+ );
70
+ }
71
+
72
+ if (options?.maxLatency) {
73
+ candidates = candidates.filter(
74
+ (p) => (p.avgLatencyMs ?? Infinity) <= options.maxLatency!
75
+ );
76
+ }
77
+
78
+ if (candidates.length === 0) {
79
+ const fallback = config.providers.find(
80
+ (p) => p.id === config.fallbackProviderId
81
+ );
82
+ if (fallback) return fallback.model;
83
+ return config.providers[0]?.model;
84
+ }
85
+
86
+ switch (config.strategy) {
87
+ case "cost":
88
+ candidates.sort(
89
+ (a, b) => (a.costPer1kTokens ?? 0) - (b.costPer1kTokens ?? 0)
90
+ );
91
+ return candidates[0].model;
92
+
93
+ case "latency":
94
+ candidates.sort(
95
+ (a, b) => (a.avgLatencyMs ?? 0) - (b.avgLatencyMs ?? 0)
96
+ );
97
+ return candidates[0].model;
98
+
99
+ case "capability":
100
+ candidates.sort(
101
+ (a, b) => (b.capabilities?.length ?? 0) - (a.capabilities?.length ?? 0)
102
+ );
103
+ return candidates[0].model;
104
+
105
+ case "round-robin":
106
+ const selected = candidates[roundRobinIndex % candidates.length];
107
+ roundRobinIndex++;
108
+ return selected.model;
109
+
110
+ default:
111
+ return candidates[0].model;
112
+ }
113
+ },
114
+
115
+ createAgent(
116
+ agentConfig: { id: string; name: string; instructions: string; tools?: Record<string, any> },
117
+ routingOptions?: { capabilities?: string[]; maxCost?: number; maxLatency?: number }
118
+ ): Agent {
119
+ const model = this.select(routingOptions);
120
+ return new Agent({
121
+ id: agentConfig.id,
122
+ name: agentConfig.name,
123
+ instructions: agentConfig.instructions,
124
+ model,
125
+ tools: agentConfig.tools || {},
126
+ });
127
+ },
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Load a Mastra-compatible model from a provider string.
133
+ * Convenience wrapper around Vercel AI SDK provider imports.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * const model = await loadMastraModel("openai", "gpt-4o");
138
+ * const model2 = await loadMastraModel("anthropic", "claude-sonnet-4-20250514");
139
+ * const model3 = await loadMastraModel("groq", "llama-3.3-70b-versatile");
140
+ * ```
141
+ */
142
+ export async function loadMastraModel(
143
+ provider: "openai" | "anthropic" | "groq" | "google",
144
+ modelId: string
145
+ ): Promise<any> {
146
+ switch (provider) {
147
+ case "openai": {
148
+ const { openai } = await import("@ai-sdk/openai");
149
+ return openai(modelId);
150
+ }
151
+ case "anthropic": {
152
+ const { anthropic } = await import("@ai-sdk/anthropic");
153
+ return anthropic(modelId);
154
+ }
155
+ case "groq": {
156
+ const { createGroq } = await import("@ai-sdk/groq");
157
+ return createGroq({})(modelId);
158
+ }
159
+ case "google": {
160
+ const { google } = await import("@ai-sdk/google");
161
+ return google(modelId);
162
+ }
163
+ default:
164
+ throw new Error(`Unsupported provider: ${provider}`);
165
+ }
166
+ }