@~lyre/ai-agents 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/README.md CHANGED
@@ -1,6 +1,31 @@
1
- # @lyre/ai-agents
1
+ # @~lyre/ai-agents
2
+
3
+ Multi-provider AI agents SDK for SvelteKit and Node. Thin agent/tool/run/runStream/runObject API on top of the [Vercel AI SDK](https://ai-sdk.dev/) — supports OpenAI, Anthropic, Google Gemini, Mistral, Cohere, and any other provider the AI SDK targets.
4
+
5
+ ## Structured output (`runObject`) — added in 0.2.0
6
+
7
+ `run`/`runStream` return free text. For analysis/extraction where you want a validated,
8
+ typed object, use `runObject` — single-shot, schema-constrained generation over the AI
9
+ SDK's `generateObject`. Purely additive; existing `run`/`runStream` are unchanged.
10
+
11
+ ```ts
12
+ import { createClient } from '@~lyre/ai-agents';
13
+ import { z } from 'zod';
14
+
15
+ const ai = createClient();
16
+ ai.createAgent({ name: 'extractor', model: anthropic('claude-sonnet-4-5'), instructions: '...' });
17
+
18
+ const { object } = await ai.runObject({
19
+ agent: 'extractor',
20
+ message: 'Summarize these conversations …',
21
+ inputSchema: z.object({
22
+ topics: z.array(z.string()),
23
+ sentiment: z.enum(['positive', 'neutral', 'negative', 'mixed'])
24
+ })
25
+ });
26
+ // object is typed + validated; no manual JSON parsing.
27
+ ```
2
28
 
3
- Multi-provider AI agents SDK for SvelteKit and Node. Thin agent/tool/run/runStream API on top of the [Vercel AI SDK](https://ai-sdk.dev/) — supports OpenAI, Anthropic, Google Gemini, Mistral, Cohere, and any other provider the AI SDK targets.
4
29
 
5
30
  ## Why
6
31
 
@@ -9,12 +34,12 @@ The original `@kigathi/ai-agents` v1.1.0 (in `belva/axis/packages/lyre-ai-agents
9
34
  ## Quick start
10
35
 
11
36
  ```bash
12
- pnpm add @lyre/ai-agents ai zod
37
+ pnpm add @~lyre/ai-agents ai zod
13
38
  pnpm add @ai-sdk/anthropic # or @ai-sdk/openai, @ai-sdk/google, etc.
14
39
  ```
15
40
 
16
41
  ```ts
17
- import { createClient } from '@lyre/ai-agents';
42
+ import { createClient } from '@~lyre/ai-agents';
18
43
  import { z } from 'zod';
19
44
 
20
45
  const ai = createClient();
@@ -0,0 +1,64 @@
1
+ // src/sanitize.ts
2
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
3
+ "a",
4
+ "b",
5
+ "blockquote",
6
+ "br",
7
+ "code",
8
+ "div",
9
+ "em",
10
+ "h1",
11
+ "h2",
12
+ "h3",
13
+ "h4",
14
+ "h5",
15
+ "h6",
16
+ "hr",
17
+ "i",
18
+ "li",
19
+ "ol",
20
+ "p",
21
+ "pre",
22
+ "span",
23
+ "strong",
24
+ "table",
25
+ "tbody",
26
+ "td",
27
+ "th",
28
+ "thead",
29
+ "tr",
30
+ "u",
31
+ "ul"
32
+ ]);
33
+ var ALLOWED_ATTRS = {
34
+ a: /* @__PURE__ */ new Set(["href", "title", "target", "rel"]),
35
+ "*": /* @__PURE__ */ new Set(["class"])
36
+ };
37
+ var UNSAFE_PROTOCOLS = /^\s*(javascript|data|vbscript):/i;
38
+ function sanitizeRichHtml(input) {
39
+ if (!input) return "";
40
+ let html = String(input);
41
+ html = html.replace(/<\s*(script|style|iframe|object|embed|link|meta)[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/gi, "");
42
+ html = html.replace(/<\s*(script|style|iframe|object|embed|link|meta)[^>]*\/?\s*>/gi, "");
43
+ html = html.replace(/\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, "");
44
+ html = html.replace(
45
+ /\s+(href|src)\s*=\s*("(?:[^"]*?)"|'(?:[^']*?)'|[^\s>]+)/gi,
46
+ (match, attr, value) => {
47
+ const v = value.replace(/^['"]|['"]$/g, "");
48
+ if (UNSAFE_PROTOCOLS.test(v)) return "";
49
+ return ` ${attr}="${v.replace(/"/g, "&quot;")}"`;
50
+ }
51
+ );
52
+ html = html.replace(/<\s*\/?\s*([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g, (match, tagName) => {
53
+ const lower = tagName.toLowerCase();
54
+ if (ALLOWED_TAGS.has(lower)) return match;
55
+ return "";
56
+ });
57
+ return html;
58
+ }
59
+
60
+ export {
61
+ ALLOWED_TAGS,
62
+ ALLOWED_ATTRS,
63
+ sanitizeRichHtml
64
+ };
@@ -0,0 +1,214 @@
1
+ import { z } from 'zod';
2
+ export { sanitizeRichHtml } from './sanitize.js';
3
+
4
+ /**
5
+ * A model identifier. Vercel AI SDK v6 accepts strings like `'openai/gpt-4o'`,
6
+ * `'anthropic/claude-sonnet-4.5'`, `'google/gemini-2.5-flash'`. Provider auth comes
7
+ * from environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
8
+ * `GOOGLE_GENERATIVE_AI_API_KEY`) unless you wire an explicit provider object.
9
+ */
10
+ type ModelId = string;
11
+ /**
12
+ * Anything `streamText` / `generateText` accepts as `model`. Use a string for
13
+ * registry-resolved providers; pass a `LanguageModel` instance for custom wiring.
14
+ */
15
+ type ModelLike = ModelId | {
16
+ specificationVersion: string;
17
+ [k: string]: any;
18
+ };
19
+ type ToolDefinition<Input = unknown, Output = unknown> = {
20
+ name: string;
21
+ description: string;
22
+ /**
23
+ * Zod schema for the tool's input arguments. The AI SDK uses this to constrain
24
+ * the model's function-call output and to type the `execute` callback.
25
+ */
26
+ inputSchema: z.ZodSchema<Input>;
27
+ execute: (input: Input, context: ToolContext) => Promise<Output> | Output;
28
+ };
29
+ type ToolContext = {
30
+ /** Unique tool call id from the model, useful for correlating tool-call → tool-result. */
31
+ toolCallId: string;
32
+ /** Forwarded from RunParams.context — apps thread per-request state (user id, locale, etc.). */
33
+ app: Record<string, any>;
34
+ };
35
+ type Message = {
36
+ role: 'system';
37
+ content: string;
38
+ } | {
39
+ role: 'user';
40
+ content: string;
41
+ } | {
42
+ role: 'assistant';
43
+ content: string;
44
+ };
45
+ type AgentDefinition = {
46
+ /** Stable identifier; used as the lookup key. Defaults to `name`. */
47
+ id?: string;
48
+ name: string;
49
+ model: ModelLike;
50
+ /** System prompt. Inserted as a `system` message when no system message is in the history. */
51
+ instructions?: string;
52
+ temperature?: number;
53
+ maxOutputTokens?: number;
54
+ /**
55
+ * Tool names this agent may call. If omitted, all globally-registered tools are available.
56
+ */
57
+ tools?: readonly string[];
58
+ /**
59
+ * Provider-specific options. Forwarded to Vercel AI SDK's `providerOptions`.
60
+ * Example: `{ anthropic: { cacheControl: { type: 'ephemeral' } } }`.
61
+ */
62
+ providerOptions?: Record<string, Record<string, any>>;
63
+ metadata?: Record<string, any>;
64
+ };
65
+ type Agent = Required<Pick<AgentDefinition, 'id' | 'name' | 'model' | 'instructions'>> & Omit<AgentDefinition, 'id' | 'name' | 'model' | 'instructions'>;
66
+ type RunParams = {
67
+ /** Agent id, name, or definition object. */
68
+ agent: string | AgentDefinition;
69
+ /** The new user message. Combined with `history` to form the full conversation. */
70
+ message?: string;
71
+ /** Prior conversation. The agent's `instructions` is prepended as a system message. */
72
+ history?: readonly Message[];
73
+ /** Override `maxOutputTokens` per call. */
74
+ maxOutputTokens?: number;
75
+ /** Override `temperature` per call. */
76
+ temperature?: number;
77
+ /** Max number of tool-call iterations before bailing. Default 8. */
78
+ maxSteps?: number;
79
+ /** Forwarded to tool `execute(input, context)`. */
80
+ context?: Record<string, any>;
81
+ };
82
+ type RunResult = {
83
+ text: string;
84
+ finishReason: string;
85
+ usage: {
86
+ inputTokens: number;
87
+ outputTokens: number;
88
+ totalTokens: number;
89
+ };
90
+ toolCalls: {
91
+ toolCallId: string;
92
+ toolName: string;
93
+ input: unknown;
94
+ }[];
95
+ toolResults: {
96
+ toolCallId: string;
97
+ toolName: string;
98
+ output: unknown;
99
+ }[];
100
+ raw: any;
101
+ };
102
+ type StreamEvent = {
103
+ type: 'text-delta';
104
+ text: string;
105
+ } | {
106
+ type: 'tool-call';
107
+ toolCallId: string;
108
+ toolName: string;
109
+ input: unknown;
110
+ } | {
111
+ type: 'tool-result';
112
+ toolCallId: string;
113
+ toolName: string;
114
+ output: unknown;
115
+ } | {
116
+ type: 'tool-error';
117
+ toolCallId: string;
118
+ toolName: string;
119
+ error: unknown;
120
+ } | {
121
+ type: 'finish';
122
+ finishReason: string;
123
+ usage: {
124
+ inputTokens: number;
125
+ outputTokens: number;
126
+ totalTokens: number;
127
+ };
128
+ } | {
129
+ type: 'error';
130
+ error: unknown;
131
+ };
132
+ /**
133
+ * Parameters for `runObject` — schema-constrained structured generation. Mirrors
134
+ * `RunParams` minus tools/maxSteps (structured generation is single-shot), plus a Zod
135
+ * `schema` the model output is validated against. Added in 0.2.0; non-breaking.
136
+ */
137
+ type RunObjectParams<T = unknown> = {
138
+ /** Agent id, name, or definition object. */
139
+ agent: string | AgentDefinition;
140
+ /** The new user message. Combined with `history` to form the full conversation. */
141
+ message?: string;
142
+ /** Prior conversation. The agent's `instructions` is prepended as a system message. */
143
+ history?: readonly Message[];
144
+ /** Zod schema the model's JSON output must satisfy. The SDK constrains + validates. */
145
+ inputSchema: z.ZodSchema<T>;
146
+ /** Optional schema name/description forwarded to the provider for better grounding. */
147
+ schemaName?: string;
148
+ schemaDescription?: string;
149
+ maxOutputTokens?: number;
150
+ temperature?: number;
151
+ /** Forwarded to the model call (not to tools — structured generation has no tools). */
152
+ context?: Record<string, any>;
153
+ };
154
+ type RunObjectResult<T = unknown> = {
155
+ /** The validated, typed object. */
156
+ object: T;
157
+ finishReason: string;
158
+ usage: {
159
+ inputTokens: number;
160
+ outputTokens: number;
161
+ totalTokens: number;
162
+ };
163
+ raw: any;
164
+ };
165
+
166
+ /**
167
+ * In-memory registry of agents and tools. One per `Client`.
168
+ */
169
+ declare class Registry {
170
+ readonly tools: Map<string, ToolDefinition>;
171
+ readonly agents: Map<string, Agent>;
172
+ registerTool<I, O>(tool: ToolDefinition<I, O>): ToolDefinition<I, O>;
173
+ createAgent(definition: AgentDefinition): Agent;
174
+ resolveAgent(input: string | AgentDefinition): Agent;
175
+ resolveTools(agent: Agent): ToolDefinition[];
176
+ }
177
+
178
+ /**
179
+ * AI agents client. Stateless besides the agent/tool registry; create one per app
180
+ * (or per request — they're cheap).
181
+ */
182
+ declare class AiAgentsClient {
183
+ private readonly registry;
184
+ constructor();
185
+ registerTool<I, O>(tool: ToolDefinition<I, O>): ToolDefinition<I, O>;
186
+ createAgent(definition: AgentDefinition): Agent;
187
+ run(params: RunParams): Promise<RunResult>;
188
+ runStream(params: RunParams): AsyncGenerator<StreamEvent, void, unknown>;
189
+ /**
190
+ * Schema-constrained structured generation. Returns a validated, typed object
191
+ * instead of free text. Single-shot (no tools). Added in 0.2.0.
192
+ */
193
+ runObject<T>(params: RunObjectParams<T>): Promise<RunObjectResult<T>>;
194
+ /** Direct access to the underlying registry for advanced introspection. */
195
+ get raw(): Registry;
196
+ }
197
+ /**
198
+ * Factory. Provider auth comes from environment variables. To explicitly configure
199
+ * provider clients (e.g., for testing), pass `LanguageModel` instances to
200
+ * `createAgent({ model })` instead of string identifiers.
201
+ */
202
+ declare function createClient(): AiAgentsClient;
203
+
204
+ declare function run(registry: Registry, params: RunParams): Promise<RunResult>;
205
+ declare function runStream(registry: Registry, params: RunParams): AsyncGenerator<StreamEvent, void, unknown>;
206
+ /**
207
+ * Schema-constrained structured generation. Single-shot (no tools): the model is
208
+ * forced to return JSON matching `params.inputSchema`, validated by the AI SDK before
209
+ * it resolves. Use this for analysis/extraction where you want a typed object instead
210
+ * of free text. Additive — does not touch `run`/`runStream`.
211
+ */
212
+ declare function runObject<T>(registry: Registry, params: RunObjectParams<T>): Promise<RunObjectResult<T>>;
213
+
214
+ export { type Agent, type AgentDefinition, AiAgentsClient, type Message, type ModelId, type ModelLike, Registry, type RunObjectParams, type RunObjectResult, type RunParams, type RunResult, type StreamEvent, type ToolContext, type ToolDefinition, createClient, run, runObject, runStream };
package/dist/index.js ADDED
@@ -0,0 +1,237 @@
1
+ import {
2
+ sanitizeRichHtml
3
+ } from "./chunk-7CW6W47V.js";
4
+
5
+ // src/agents.ts
6
+ var Registry = class {
7
+ tools = /* @__PURE__ */ new Map();
8
+ agents = /* @__PURE__ */ new Map();
9
+ registerTool(tool2) {
10
+ this.tools.set(tool2.name, tool2);
11
+ return tool2;
12
+ }
13
+ createAgent(definition) {
14
+ const agent = {
15
+ id: definition.id ?? definition.name,
16
+ name: definition.name,
17
+ model: definition.model,
18
+ instructions: definition.instructions ?? "",
19
+ temperature: definition.temperature,
20
+ maxOutputTokens: definition.maxOutputTokens,
21
+ tools: definition.tools,
22
+ providerOptions: definition.providerOptions,
23
+ metadata: definition.metadata
24
+ };
25
+ this.agents.set(agent.id, agent);
26
+ if (agent.name !== agent.id) this.agents.set(agent.name, agent);
27
+ return agent;
28
+ }
29
+ resolveAgent(input) {
30
+ if (typeof input !== "string") {
31
+ return this.createAgent(input);
32
+ }
33
+ const found = this.agents.get(input);
34
+ if (!found) throw new Error(`Unknown agent: ${input}`);
35
+ return found;
36
+ }
37
+ resolveTools(agent) {
38
+ const names = agent.tools && agent.tools.length > 0 ? agent.tools : [...this.tools.keys()];
39
+ const out = [];
40
+ for (const name of names) {
41
+ const tool2 = this.tools.get(name);
42
+ if (tool2) out.push(tool2);
43
+ }
44
+ return out;
45
+ }
46
+ };
47
+
48
+ // src/run.ts
49
+ import { generateObject, generateText, streamText, tool } from "ai";
50
+ function resolveModel(agent) {
51
+ return agent.model;
52
+ }
53
+ function buildMessages(agent, params) {
54
+ const messages = [];
55
+ const history = params.history ?? [];
56
+ const hasSystem = history.some((m) => m.role === "system");
57
+ if (!hasSystem && agent.instructions) {
58
+ messages.push({ role: "system", content: agent.instructions });
59
+ }
60
+ for (const m of history) messages.push(m);
61
+ if (params.message) {
62
+ messages.push({ role: "user", content: params.message });
63
+ }
64
+ return messages;
65
+ }
66
+ function buildTools(registry, agent, context) {
67
+ const defs = registry.resolveTools(agent);
68
+ const out = {};
69
+ for (const def of defs) {
70
+ out[def.name] = tool({
71
+ description: def.description,
72
+ inputSchema: def.inputSchema,
73
+ execute: async (input, opts) => def.execute(input, { toolCallId: opts.toolCallId, app: context })
74
+ });
75
+ }
76
+ return out;
77
+ }
78
+ async function run(registry, params) {
79
+ const agent = registry.resolveAgent(params.agent);
80
+ const messages = buildMessages(agent, params);
81
+ const tools = buildTools(registry, agent, params.context ?? {});
82
+ const result = await generateText({
83
+ model: resolveModel(agent),
84
+ messages,
85
+ tools,
86
+ temperature: params.temperature ?? agent.temperature,
87
+ maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
88
+ stopWhen: ({ steps }) => steps.length >= (params.maxSteps ?? 8),
89
+ providerOptions: agent.providerOptions
90
+ });
91
+ return {
92
+ text: result.text,
93
+ finishReason: result.finishReason,
94
+ usage: {
95
+ inputTokens: result.usage?.inputTokens ?? 0,
96
+ outputTokens: result.usage?.outputTokens ?? 0,
97
+ totalTokens: result.usage?.totalTokens ?? 0
98
+ },
99
+ toolCalls: (result.toolCalls ?? []).map((c) => ({
100
+ toolCallId: c.toolCallId,
101
+ toolName: c.toolName,
102
+ input: c.input
103
+ })),
104
+ toolResults: (result.toolResults ?? []).map((r) => ({
105
+ toolCallId: r.toolCallId,
106
+ toolName: r.toolName,
107
+ output: r.output
108
+ })),
109
+ raw: result
110
+ };
111
+ }
112
+ async function* runStream(registry, params) {
113
+ const agent = registry.resolveAgent(params.agent);
114
+ const messages = buildMessages(agent, params);
115
+ const tools = buildTools(registry, agent, params.context ?? {});
116
+ const result = streamText({
117
+ model: resolveModel(agent),
118
+ messages,
119
+ tools,
120
+ temperature: params.temperature ?? agent.temperature,
121
+ maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
122
+ stopWhen: ({ steps }) => steps.length >= (params.maxSteps ?? 8),
123
+ providerOptions: agent.providerOptions
124
+ });
125
+ for await (const part of result.fullStream) {
126
+ switch (part.type) {
127
+ case "text-delta":
128
+ yield { type: "text-delta", text: part.text ?? part.delta ?? "" };
129
+ break;
130
+ case "tool-call":
131
+ yield {
132
+ type: "tool-call",
133
+ toolCallId: part.toolCallId,
134
+ toolName: part.toolName,
135
+ input: part.input ?? part.args
136
+ };
137
+ break;
138
+ case "tool-result":
139
+ yield {
140
+ type: "tool-result",
141
+ toolCallId: part.toolCallId,
142
+ toolName: part.toolName,
143
+ output: part.output ?? part.result
144
+ };
145
+ break;
146
+ case "tool-error":
147
+ yield {
148
+ type: "tool-error",
149
+ toolCallId: part.toolCallId,
150
+ toolName: part.toolName,
151
+ error: part.error
152
+ };
153
+ break;
154
+ case "finish":
155
+ yield {
156
+ type: "finish",
157
+ finishReason: part.finishReason,
158
+ usage: {
159
+ inputTokens: part.usage?.inputTokens ?? 0,
160
+ outputTokens: part.usage?.outputTokens ?? 0,
161
+ totalTokens: part.usage?.totalTokens ?? 0
162
+ }
163
+ };
164
+ break;
165
+ case "error":
166
+ yield { type: "error", error: part.error };
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ async function runObject(registry, params) {
172
+ const agent = registry.resolveAgent(params.agent);
173
+ const messages = buildMessages(agent, params);
174
+ const result = await generateObject({
175
+ model: resolveModel(agent),
176
+ messages,
177
+ schema: params.inputSchema,
178
+ schemaName: params.schemaName,
179
+ schemaDescription: params.schemaDescription,
180
+ temperature: params.temperature ?? agent.temperature,
181
+ maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
182
+ providerOptions: agent.providerOptions
183
+ });
184
+ return {
185
+ object: result.object,
186
+ finishReason: result.finishReason ?? "stop",
187
+ usage: {
188
+ inputTokens: result.usage?.inputTokens ?? 0,
189
+ outputTokens: result.usage?.outputTokens ?? 0,
190
+ totalTokens: result.usage?.totalTokens ?? 0
191
+ },
192
+ raw: result
193
+ };
194
+ }
195
+
196
+ // src/client.ts
197
+ var AiAgentsClient = class {
198
+ registry;
199
+ constructor() {
200
+ this.registry = new Registry();
201
+ }
202
+ registerTool(tool2) {
203
+ return this.registry.registerTool(tool2);
204
+ }
205
+ createAgent(definition) {
206
+ return this.registry.createAgent(definition);
207
+ }
208
+ async run(params) {
209
+ return run(this.registry, params);
210
+ }
211
+ runStream(params) {
212
+ return runStream(this.registry, params);
213
+ }
214
+ /**
215
+ * Schema-constrained structured generation. Returns a validated, typed object
216
+ * instead of free text. Single-shot (no tools). Added in 0.2.0.
217
+ */
218
+ async runObject(params) {
219
+ return runObject(this.registry, params);
220
+ }
221
+ /** Direct access to the underlying registry for advanced introspection. */
222
+ get raw() {
223
+ return this.registry;
224
+ }
225
+ };
226
+ function createClient() {
227
+ return new AiAgentsClient();
228
+ }
229
+ export {
230
+ AiAgentsClient,
231
+ Registry,
232
+ createClient,
233
+ run,
234
+ runObject,
235
+ runStream,
236
+ sanitizeRichHtml
237
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Minimal HTML sanitizer for AI-generated rich text. Strips script/style/iframe and
3
+ * inline event handlers; keeps a small allowlist of tags and attributes suitable for
4
+ * advisor output.
5
+ *
6
+ * Ported from @kigathi/ai-agents v1.1.0 with minor TS typing tweaks.
7
+ */
8
+ declare const ALLOWED_TAGS: Set<string>;
9
+ declare const ALLOWED_ATTRS: Record<string, Set<string>>;
10
+ declare function sanitizeRichHtml(input: string | null | undefined): string;
11
+
12
+ export { ALLOWED_ATTRS, ALLOWED_TAGS, sanitizeRichHtml };
@@ -0,0 +1,10 @@
1
+ import {
2
+ ALLOWED_ATTRS,
3
+ ALLOWED_TAGS,
4
+ sanitizeRichHtml
5
+ } from "./chunk-7CW6W47V.js";
6
+ export {
7
+ ALLOWED_ATTRS,
8
+ ALLOWED_TAGS,
9
+ sanitizeRichHtml
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@~lyre/ai-agents",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Multi-provider AI agents SDK for SvelteKit + Node. Thin agent/tool/run/runStream surface over the Vercel AI SDK — OpenAI, Anthropic, Google Gemini, Mistral, Cohere. Zod-typed tools, full streaming event surface, optional HTML sanitizer.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -8,12 +8,11 @@
8
8
  "author": "Britam Trust Services / Lyre",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/REPLACE_ME/britam-legacy.git",
12
- "directory": "packages/ai-agents"
11
+ "url": "git+https://github.com/kigathi-chege/lyre-ai-agents.git"
13
12
  },
14
- "homepage": "https://github.com/REPLACE_ME/britam-legacy/tree/main/packages/ai-agents#readme",
13
+ "homepage": "https://github.com/kigathi-chege/lyre-ai-agents#readme",
15
14
  "bugs": {
16
- "url": "https://github.com/REPLACE_ME/britam-legacy/issues"
15
+ "url": "https://github.com/kigathi-chege/lyre-ai-agents/issues"
17
16
  },
18
17
  "keywords": [
19
18
  "ai",
@@ -28,24 +27,25 @@
28
27
  "sveltekit",
29
28
  "lyre"
30
29
  ],
31
- "main": "./src/index.ts",
32
- "types": "./src/index.ts",
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
33
32
  "exports": {
34
33
  ".": {
35
- "types": "./src/index.ts",
36
- "import": "./src/index.ts"
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
37
36
  },
38
37
  "./sanitize": {
39
- "types": "./src/sanitize.ts",
40
- "import": "./src/sanitize.ts"
38
+ "types": "./dist/sanitize.d.ts",
39
+ "import": "./dist/sanitize.js"
41
40
  }
42
41
  },
43
42
  "files": [
44
- "src",
43
+ "dist",
45
44
  "README.md",
46
45
  "LICENSE"
47
46
  ],
48
47
  "scripts": {
48
+ "build": "tsup src/index.ts src/sanitize.ts --format esm --dts --clean",
49
49
  "check": "tsc --noEmit"
50
50
  },
51
51
  "peerDependencies": {
@@ -55,6 +55,7 @@
55
55
  "devDependencies": {
56
56
  "@types/node": "^22",
57
57
  "ai": "^6.0.0",
58
+ "tsup": "^8.3.5",
58
59
  "typescript": "^6.0.2",
59
60
  "zod": "^4.0.0"
60
61
  },
package/src/agents.ts DELETED
@@ -1,53 +0,0 @@
1
- import type { Agent, AgentDefinition, ToolDefinition } from './types';
2
-
3
- /**
4
- * In-memory registry of agents and tools. One per `Client`.
5
- */
6
- export class Registry {
7
- readonly tools = new Map<string, ToolDefinition>();
8
- readonly agents = new Map<string, Agent>();
9
-
10
- registerTool<I, O>(tool: ToolDefinition<I, O>): ToolDefinition<I, O> {
11
- this.tools.set(tool.name, tool as unknown as ToolDefinition);
12
- return tool;
13
- }
14
-
15
- createAgent(definition: AgentDefinition): Agent {
16
- const agent: Agent = {
17
- id: definition.id ?? definition.name,
18
- name: definition.name,
19
- model: definition.model,
20
- instructions: definition.instructions ?? '',
21
- temperature: definition.temperature,
22
- maxOutputTokens: definition.maxOutputTokens,
23
- tools: definition.tools,
24
- providerOptions: definition.providerOptions,
25
- metadata: definition.metadata
26
- };
27
-
28
- this.agents.set(agent.id, agent);
29
- // Also key by name when distinct from id, so consumers can look up by either.
30
- if (agent.name !== agent.id) this.agents.set(agent.name, agent);
31
- return agent;
32
- }
33
-
34
- resolveAgent(input: string | AgentDefinition): Agent {
35
- if (typeof input !== 'string') {
36
- // Allow callers to pass a fully-formed definition for one-off calls.
37
- return this.createAgent(input);
38
- }
39
- const found = this.agents.get(input);
40
- if (!found) throw new Error(`Unknown agent: ${input}`);
41
- return found;
42
- }
43
-
44
- resolveTools(agent: Agent): ToolDefinition[] {
45
- const names = agent.tools && agent.tools.length > 0 ? agent.tools : [...this.tools.keys()];
46
- const out: ToolDefinition[] = [];
47
- for (const name of names) {
48
- const tool = this.tools.get(name);
49
- if (tool) out.push(tool);
50
- }
51
- return out;
52
- }
53
- }
package/src/client.ts DELETED
@@ -1,52 +0,0 @@
1
- import { Registry } from './agents';
2
- import { run, runStream } from './run';
3
- import type {
4
- Agent,
5
- AgentDefinition,
6
- RunParams,
7
- RunResult,
8
- StreamEvent,
9
- ToolDefinition
10
- } from './types';
11
-
12
- /**
13
- * AI agents client. Stateless besides the agent/tool registry; create one per app
14
- * (or per request — they're cheap).
15
- */
16
- export class AiAgentsClient {
17
- private readonly registry: Registry;
18
-
19
- constructor() {
20
- this.registry = new Registry();
21
- }
22
-
23
- registerTool<I, O>(tool: ToolDefinition<I, O>): ToolDefinition<I, O> {
24
- return this.registry.registerTool(tool);
25
- }
26
-
27
- createAgent(definition: AgentDefinition): Agent {
28
- return this.registry.createAgent(definition);
29
- }
30
-
31
- async run(params: RunParams): Promise<RunResult> {
32
- return run(this.registry, params);
33
- }
34
-
35
- runStream(params: RunParams): AsyncGenerator<StreamEvent, void, unknown> {
36
- return runStream(this.registry, params);
37
- }
38
-
39
- /** Direct access to the underlying registry for advanced introspection. */
40
- get raw(): Registry {
41
- return this.registry;
42
- }
43
- }
44
-
45
- /**
46
- * Factory. Provider auth comes from environment variables. To explicitly configure
47
- * provider clients (e.g., for testing), pass `LanguageModel` instances to
48
- * `createAgent({ model })` instead of string identifiers.
49
- */
50
- export function createClient(): AiAgentsClient {
51
- return new AiAgentsClient();
52
- }
package/src/index.ts DELETED
@@ -1,19 +0,0 @@
1
- // Public API
2
- export { createClient, AiAgentsClient } from './client';
3
- export { Registry } from './agents';
4
- export { run, runStream } from './run';
5
- export { sanitizeRichHtml } from './sanitize';
6
-
7
- // Types
8
- export type {
9
- ModelId,
10
- ModelLike,
11
- Message,
12
- ToolDefinition,
13
- ToolContext,
14
- AgentDefinition,
15
- Agent,
16
- RunParams,
17
- RunResult,
18
- StreamEvent
19
- } from './types';
package/src/run.ts DELETED
@@ -1,153 +0,0 @@
1
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
- import { generateText, streamText, tool, type ModelMessage, type Tool } from 'ai';
3
- import type { Registry } from './agents';
4
- import type {
5
- Message,
6
- RunParams,
7
- RunResult,
8
- StreamEvent,
9
- Agent,
10
- ToolDefinition
11
- } from './types';
12
-
13
- function buildMessages(agent: Agent, params: RunParams): ModelMessage[] {
14
- const messages: ModelMessage[] = [];
15
-
16
- const history = params.history ?? [];
17
- const hasSystem = history.some((m) => m.role === 'system');
18
- if (!hasSystem && agent.instructions) {
19
- messages.push({ role: 'system', content: agent.instructions });
20
- }
21
-
22
- for (const m of history) messages.push(m as unknown as ModelMessage);
23
-
24
- if (params.message) {
25
- messages.push({ role: 'user', content: params.message });
26
- }
27
-
28
- return messages;
29
- }
30
-
31
- function buildTools(
32
- registry: Registry,
33
- agent: Agent,
34
- context: Record<string, unknown>
35
- ): Record<string, Tool> {
36
- const defs = registry.resolveTools(agent);
37
- const out: Record<string, Tool> = {};
38
- for (const def of defs as ToolDefinition[]) {
39
- out[def.name] = tool({
40
- description: def.description,
41
- inputSchema: def.inputSchema,
42
- execute: async (input, opts: { toolCallId: string }) =>
43
- def.execute(input, { toolCallId: opts.toolCallId, app: context })
44
- });
45
- }
46
- return out;
47
- }
48
-
49
- export async function run(registry: Registry, params: RunParams): Promise<RunResult> {
50
- const agent = registry.resolveAgent(params.agent);
51
- const messages = buildMessages(agent, params);
52
- const tools = buildTools(registry, agent, params.context ?? {});
53
-
54
- const result = await generateText({
55
- model: agent.model,
56
- messages,
57
- tools,
58
- temperature: params.temperature ?? agent.temperature,
59
- maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
60
- stopWhen: ({ steps }) => steps.length >= (params.maxSteps ?? 8),
61
- providerOptions: agent.providerOptions
62
- });
63
-
64
- return {
65
- text: result.text,
66
- finishReason: result.finishReason as string,
67
- usage: {
68
- inputTokens: result.usage?.inputTokens ?? 0,
69
- outputTokens: result.usage?.outputTokens ?? 0,
70
- totalTokens: result.usage?.totalTokens ?? 0
71
- },
72
- toolCalls: (result.toolCalls ?? []).map((c) => ({
73
- toolCallId: c.toolCallId,
74
- toolName: c.toolName,
75
- input: c.input
76
- })),
77
- toolResults: (result.toolResults ?? []).map((r) => ({
78
- toolCallId: r.toolCallId,
79
- toolName: r.toolName,
80
- output: (r as { output: unknown }).output
81
- })),
82
- raw: result
83
- };
84
- }
85
-
86
- export async function* runStream(
87
- registry: Registry,
88
- params: RunParams
89
- ): AsyncGenerator<StreamEvent, void, unknown> {
90
- const agent = registry.resolveAgent(params.agent);
91
- const messages = buildMessages(agent, params);
92
- const tools = buildTools(registry, agent, params.context ?? {});
93
-
94
- const result = streamText({
95
- model: agent.model,
96
- messages,
97
- tools,
98
- temperature: params.temperature ?? agent.temperature,
99
- maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
100
- stopWhen: ({ steps }) => steps.length >= (params.maxSteps ?? 8),
101
- providerOptions: agent.providerOptions
102
- });
103
-
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
- for await (const part of result.fullStream as AsyncIterable<any>) {
106
- switch (part.type) {
107
- case 'text-delta':
108
- yield { type: 'text-delta', text: part.text ?? part.delta ?? '' };
109
- break;
110
- case 'tool-call':
111
- yield {
112
- type: 'tool-call',
113
- toolCallId: part.toolCallId,
114
- toolName: part.toolName,
115
- input: part.input ?? part.args
116
- };
117
- break;
118
- case 'tool-result':
119
- yield {
120
- type: 'tool-result',
121
- toolCallId: part.toolCallId,
122
- toolName: part.toolName,
123
- output: part.output ?? part.result
124
- };
125
- break;
126
- case 'tool-error':
127
- yield {
128
- type: 'tool-error',
129
- toolCallId: part.toolCallId,
130
- toolName: part.toolName,
131
- error: part.error
132
- };
133
- break;
134
- case 'finish':
135
- yield {
136
- type: 'finish',
137
- finishReason: part.finishReason,
138
- usage: {
139
- inputTokens: part.usage?.inputTokens ?? 0,
140
- outputTokens: part.usage?.outputTokens ?? 0,
141
- totalTokens: part.usage?.totalTokens ?? 0
142
- }
143
- };
144
- break;
145
- case 'error':
146
- yield { type: 'error', error: part.error };
147
- break;
148
- // Silently drop other chunk types (start/step boundaries, raw text accumulators, etc.).
149
- }
150
- }
151
- }
152
-
153
- export type { Message };
package/src/sanitize.ts DELETED
@@ -1,80 +0,0 @@
1
- /**
2
- * Minimal HTML sanitizer for AI-generated rich text. Strips script/style/iframe and
3
- * inline event handlers; keeps a small allowlist of tags and attributes suitable for
4
- * advisor output.
5
- *
6
- * Ported from @kigathi/ai-agents v1.1.0 with minor TS typing tweaks.
7
- */
8
-
9
- const ALLOWED_TAGS = new Set([
10
- 'a',
11
- 'b',
12
- 'blockquote',
13
- 'br',
14
- 'code',
15
- 'div',
16
- 'em',
17
- 'h1',
18
- 'h2',
19
- 'h3',
20
- 'h4',
21
- 'h5',
22
- 'h6',
23
- 'hr',
24
- 'i',
25
- 'li',
26
- 'ol',
27
- 'p',
28
- 'pre',
29
- 'span',
30
- 'strong',
31
- 'table',
32
- 'tbody',
33
- 'td',
34
- 'th',
35
- 'thead',
36
- 'tr',
37
- 'u',
38
- 'ul'
39
- ]);
40
-
41
- const ALLOWED_ATTRS: Record<string, Set<string>> = {
42
- a: new Set(['href', 'title', 'target', 'rel']),
43
- '*': new Set(['class'])
44
- };
45
-
46
- const UNSAFE_PROTOCOLS = /^\s*(javascript|data|vbscript):/i;
47
-
48
- export function sanitizeRichHtml(input: string | null | undefined): string {
49
- if (!input) return '';
50
- let html = String(input);
51
-
52
- // Strip dangerous tags wholesale.
53
- html = html.replace(/<\s*(script|style|iframe|object|embed|link|meta)[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/gi, '');
54
- html = html.replace(/<\s*(script|style|iframe|object|embed|link|meta)[^>]*\/?\s*>/gi, '');
55
-
56
- // Strip on* attributes.
57
- html = html.replace(/\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '');
58
-
59
- // Strip unsafe href/src values.
60
- html = html.replace(
61
- /\s+(href|src)\s*=\s*("(?:[^"]*?)"|'(?:[^']*?)'|[^\s>]+)/gi,
62
- (match, attr: string, value: string) => {
63
- const v = value.replace(/^['"]|['"]$/g, '');
64
- if (UNSAFE_PROTOCOLS.test(v)) return '';
65
- return ` ${attr}="${v.replace(/"/g, '&quot;')}"`;
66
- }
67
- );
68
-
69
- // Optional: drop tags not in allowlist. We do this last so attribute stripping above
70
- // applies to the broader HTML stream first.
71
- html = html.replace(/<\s*\/?\s*([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g, (match, tagName: string) => {
72
- const lower = tagName.toLowerCase();
73
- if (ALLOWED_TAGS.has(lower)) return match;
74
- return '';
75
- });
76
-
77
- return html;
78
- }
79
-
80
- export { ALLOWED_TAGS, ALLOWED_ATTRS };
package/src/types.ts DELETED
@@ -1,110 +0,0 @@
1
- import type { z } from 'zod';
2
-
3
- /**
4
- * A model identifier. Vercel AI SDK v6 accepts strings like `'openai/gpt-4o'`,
5
- * `'anthropic/claude-sonnet-4.5'`, `'google/gemini-2.5-flash'`. Provider auth comes
6
- * from environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
7
- * `GOOGLE_GENERATIVE_AI_API_KEY`) unless you wire an explicit provider object.
8
- */
9
- export type ModelId = string;
10
-
11
- /**
12
- * Anything `streamText` / `generateText` accepts as `model`. Use a string for
13
- * registry-resolved providers; pass a `LanguageModel` instance for custom wiring.
14
- */
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
- export type ModelLike = ModelId | { specificationVersion: string; [k: string]: any };
17
-
18
- export type ToolDefinition<Input = unknown, Output = unknown> = {
19
- name: string;
20
- description: string;
21
- /**
22
- * Zod schema for the tool's input arguments. The AI SDK uses this to constrain
23
- * the model's function-call output and to type the `execute` callback.
24
- */
25
- inputSchema: z.ZodSchema<Input>;
26
- execute: (input: Input, context: ToolContext) => Promise<Output> | Output;
27
- };
28
-
29
- export type ToolContext = {
30
- /** Unique tool call id from the model, useful for correlating tool-call → tool-result. */
31
- toolCallId: string;
32
- /** Forwarded from RunParams.context — apps thread per-request state (user id, locale, etc.). */
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- app: Record<string, any>;
35
- };
36
-
37
- export type Message =
38
- | { role: 'system'; content: string }
39
- | { role: 'user'; content: string }
40
- | { role: 'assistant'; content: string };
41
-
42
- export type AgentDefinition = {
43
- /** Stable identifier; used as the lookup key. Defaults to `name`. */
44
- id?: string;
45
- name: string;
46
- model: ModelLike;
47
- /** System prompt. Inserted as a `system` message when no system message is in the history. */
48
- instructions?: string;
49
- temperature?: number;
50
- maxOutputTokens?: number;
51
- /**
52
- * Tool names this agent may call. If omitted, all globally-registered tools are available.
53
- */
54
- tools?: readonly string[];
55
- /**
56
- * Provider-specific options. Forwarded to Vercel AI SDK's `providerOptions`.
57
- * Example: `{ anthropic: { cacheControl: { type: 'ephemeral' } } }`.
58
- */
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
- providerOptions?: Record<string, Record<string, any>>;
61
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
- metadata?: Record<string, any>;
63
- };
64
-
65
- export type Agent = Required<Pick<AgentDefinition, 'id' | 'name' | 'model' | 'instructions'>> &
66
- Omit<AgentDefinition, 'id' | 'name' | 'model' | 'instructions'>;
67
-
68
- export type RunParams = {
69
- /** Agent id, name, or definition object. */
70
- agent: string | AgentDefinition;
71
- /** The new user message. Combined with `history` to form the full conversation. */
72
- message?: string;
73
- /** Prior conversation. The agent's `instructions` is prepended as a system message. */
74
- history?: readonly Message[];
75
- /** Override `maxOutputTokens` per call. */
76
- maxOutputTokens?: number;
77
- /** Override `temperature` per call. */
78
- temperature?: number;
79
- /** Max number of tool-call iterations before bailing. Default 8. */
80
- maxSteps?: number;
81
- /** Forwarded to tool `execute(input, context)`. */
82
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
- context?: Record<string, any>;
84
- };
85
-
86
- export type RunResult = {
87
- text: string;
88
- finishReason: string;
89
- usage: {
90
- inputTokens: number;
91
- outputTokens: number;
92
- totalTokens: number;
93
- };
94
- toolCalls: { toolCallId: string; toolName: string; input: unknown }[];
95
- toolResults: { toolCallId: string; toolName: string; output: unknown }[];
96
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
- raw: any;
98
- };
99
-
100
- export type StreamEvent =
101
- | { type: 'text-delta'; text: string }
102
- | { type: 'tool-call'; toolCallId: string; toolName: string; input: unknown }
103
- | { type: 'tool-result'; toolCallId: string; toolName: string; output: unknown }
104
- | { type: 'tool-error'; toolCallId: string; toolName: string; error: unknown }
105
- | {
106
- type: 'finish';
107
- finishReason: string;
108
- usage: { inputTokens: number; outputTokens: number; totalTokens: number };
109
- }
110
- | { type: 'error'; error: unknown };