@~lyre/ai-agents 0.1.0 → 0.3.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,243 @@
1
+ import { z } from 'zod';
2
+ import { LanguageModel } from 'ai';
3
+ export { sanitizeRichHtml } from './sanitize.js';
4
+
5
+ /**
6
+ * A model identifier. Vercel AI SDK v6 accepts strings like `'openai/gpt-4o'`,
7
+ * `'anthropic/claude-sonnet-4.5'`, `'google/gemini-2.5-flash'`. Provider auth comes
8
+ * from environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
9
+ * `GOOGLE_GENERATIVE_AI_API_KEY`) unless you wire an explicit provider object.
10
+ */
11
+ type ModelId = string;
12
+ /**
13
+ * Anything `streamText` / `generateText` accepts as `model`. Use a string for
14
+ * registry-resolved providers; pass a `LanguageModel` instance for custom wiring.
15
+ */
16
+ type ModelLike = ModelId | {
17
+ specificationVersion: string;
18
+ [k: string]: any;
19
+ };
20
+ type ToolDefinition<Input = unknown, Output = unknown> = {
21
+ name: string;
22
+ description: string;
23
+ /**
24
+ * Zod schema for the tool's input arguments. The AI SDK uses this to constrain
25
+ * the model's function-call output and to type the `execute` callback.
26
+ */
27
+ inputSchema: z.ZodSchema<Input>;
28
+ execute: (input: Input, context: ToolContext) => Promise<Output> | Output;
29
+ };
30
+ type ToolContext = {
31
+ /** Unique tool call id from the model, useful for correlating tool-call → tool-result. */
32
+ toolCallId: string;
33
+ /** Forwarded from RunParams.context — apps thread per-request state (user id, locale, etc.). */
34
+ app: Record<string, any>;
35
+ };
36
+ type Message = {
37
+ role: 'system';
38
+ content: string;
39
+ } | {
40
+ role: 'user';
41
+ content: string;
42
+ } | {
43
+ role: 'assistant';
44
+ content: string;
45
+ };
46
+ /**
47
+ * Client-level defaults applied to every run. `apiKey` is a single generic key the package uses
48
+ * to construct whichever provider a `provider/model` string names (see providers.ts), so consumers
49
+ * never import `@ai-sdk/*`. `model` is the default model when an agent doesn't specify one.
50
+ */
51
+ type ClientDefaults = {
52
+ apiKey?: string;
53
+ model?: ModelLike;
54
+ };
55
+ type AgentDefinition = {
56
+ /** Stable identifier; used as the lookup key. Defaults to `name`. */
57
+ id?: string;
58
+ name: string;
59
+ /**
60
+ * Model to use. A `provider/model` string (e.g. 'anthropic/claude-sonnet-4-5', 'openai/gpt-4o',
61
+ * 'xai/grok-2-latest') is resolved to a provider using the client's generic `apiKey`; a
62
+ * `LanguageModel` instance is used as-is. Optional — falls back to the client default model.
63
+ */
64
+ model?: ModelLike;
65
+ /** System prompt. Inserted as a `system` message when no system message is in the history. */
66
+ instructions?: string;
67
+ temperature?: number;
68
+ maxOutputTokens?: number;
69
+ /**
70
+ * Tool names this agent may call. If omitted, all globally-registered tools are available.
71
+ */
72
+ tools?: readonly string[];
73
+ /**
74
+ * Provider-specific options. Forwarded to Vercel AI SDK's `providerOptions`.
75
+ * Example: `{ anthropic: { cacheControl: { type: 'ephemeral' } } }`.
76
+ */
77
+ providerOptions?: Record<string, Record<string, any>>;
78
+ metadata?: Record<string, any>;
79
+ };
80
+ type Agent = Required<Pick<AgentDefinition, 'id' | 'name' | 'instructions'>> & Omit<AgentDefinition, 'id' | 'name' | 'instructions'>;
81
+ type RunParams = {
82
+ /** Agent id, name, or definition object. */
83
+ agent: string | AgentDefinition;
84
+ /** The new user message. Combined with `history` to form the full conversation. */
85
+ message?: string;
86
+ /** Prior conversation. The agent's `instructions` is prepended as a system message. */
87
+ history?: readonly Message[];
88
+ /** Override `maxOutputTokens` per call. */
89
+ maxOutputTokens?: number;
90
+ /** Override `temperature` per call. */
91
+ temperature?: number;
92
+ /** Max number of tool-call iterations before bailing. Default 8. */
93
+ maxSteps?: number;
94
+ /** Forwarded to tool `execute(input, context)`. */
95
+ context?: Record<string, any>;
96
+ };
97
+ type RunResult = {
98
+ text: string;
99
+ finishReason: string;
100
+ usage: {
101
+ inputTokens: number;
102
+ outputTokens: number;
103
+ totalTokens: number;
104
+ };
105
+ toolCalls: {
106
+ toolCallId: string;
107
+ toolName: string;
108
+ input: unknown;
109
+ }[];
110
+ toolResults: {
111
+ toolCallId: string;
112
+ toolName: string;
113
+ output: unknown;
114
+ }[];
115
+ raw: any;
116
+ };
117
+ type StreamEvent = {
118
+ type: 'text-delta';
119
+ text: string;
120
+ } | {
121
+ type: 'tool-call';
122
+ toolCallId: string;
123
+ toolName: string;
124
+ input: unknown;
125
+ } | {
126
+ type: 'tool-result';
127
+ toolCallId: string;
128
+ toolName: string;
129
+ output: unknown;
130
+ } | {
131
+ type: 'tool-error';
132
+ toolCallId: string;
133
+ toolName: string;
134
+ error: unknown;
135
+ } | {
136
+ type: 'finish';
137
+ finishReason: string;
138
+ usage: {
139
+ inputTokens: number;
140
+ outputTokens: number;
141
+ totalTokens: number;
142
+ };
143
+ } | {
144
+ type: 'error';
145
+ error: unknown;
146
+ };
147
+ /**
148
+ * Parameters for `runObject` — schema-constrained structured generation. Mirrors
149
+ * `RunParams` minus tools/maxSteps (structured generation is single-shot), plus a Zod
150
+ * `schema` the model output is validated against. Added in 0.2.0; non-breaking.
151
+ */
152
+ type RunObjectParams<T = unknown> = {
153
+ /** Agent id, name, or definition object. */
154
+ agent: string | AgentDefinition;
155
+ /** The new user message. Combined with `history` to form the full conversation. */
156
+ message?: string;
157
+ /** Prior conversation. The agent's `instructions` is prepended as a system message. */
158
+ history?: readonly Message[];
159
+ /** Zod schema the model's JSON output must satisfy. The SDK constrains + validates. */
160
+ inputSchema: z.ZodSchema<T>;
161
+ /** Optional schema name/description forwarded to the provider for better grounding. */
162
+ schemaName?: string;
163
+ schemaDescription?: string;
164
+ maxOutputTokens?: number;
165
+ temperature?: number;
166
+ /** Forwarded to the model call (not to tools — structured generation has no tools). */
167
+ context?: Record<string, any>;
168
+ };
169
+ type RunObjectResult<T = unknown> = {
170
+ /** The validated, typed object. */
171
+ object: T;
172
+ finishReason: string;
173
+ usage: {
174
+ inputTokens: number;
175
+ outputTokens: number;
176
+ totalTokens: number;
177
+ };
178
+ raw: any;
179
+ };
180
+
181
+ /**
182
+ * In-memory registry of agents and tools. One per `Client`.
183
+ */
184
+ declare class Registry {
185
+ readonly tools: Map<string, ToolDefinition>;
186
+ readonly agents: Map<string, Agent>;
187
+ registerTool<I, O>(tool: ToolDefinition<I, O>): ToolDefinition<I, O>;
188
+ createAgent(definition: AgentDefinition): Agent;
189
+ resolveAgent(input: string | AgentDefinition): Agent;
190
+ resolveTools(agent: Agent): ToolDefinition[];
191
+ }
192
+
193
+ /**
194
+ * AI agents client. Stateless besides the agent/tool registry; create one per app
195
+ * (or per request — they're cheap).
196
+ */
197
+ declare class AiAgentsClient {
198
+ private readonly registry;
199
+ private readonly defaults;
200
+ constructor(config?: ClientDefaults);
201
+ registerTool<I, O>(tool: ToolDefinition<I, O>): ToolDefinition<I, O>;
202
+ createAgent(definition: AgentDefinition): Agent;
203
+ run(params: RunParams): Promise<RunResult>;
204
+ runStream(params: RunParams): AsyncGenerator<StreamEvent, void, unknown>;
205
+ /**
206
+ * Schema-constrained structured generation. Returns a validated, typed object
207
+ * instead of free text. Single-shot (no tools). Added in 0.2.0.
208
+ */
209
+ runObject<T>(params: RunObjectParams<T>): Promise<RunObjectResult<T>>;
210
+ /** Direct access to the underlying registry for advanced introspection. */
211
+ get raw(): Registry;
212
+ }
213
+ /**
214
+ * Factory. Pass a single generic `{ apiKey, model }` and the package constructs whichever
215
+ * provider the model's `provider/` prefix names — so apps never import `@ai-sdk/*`. Omit the
216
+ * config to fall back to the Vercel AI SDK's own env-var/gateway resolution, or pass a
217
+ * `LanguageModel` instance via `createAgent({ model })` for fully custom wiring.
218
+ */
219
+ declare function createClient(config?: ClientDefaults): AiAgentsClient;
220
+
221
+ declare function run(registry: Registry, params: RunParams, defaults?: ClientDefaults): Promise<RunResult>;
222
+ declare function runStream(registry: Registry, params: RunParams, defaults?: ClientDefaults): AsyncGenerator<StreamEvent, void, unknown>;
223
+ /**
224
+ * Schema-constrained structured generation. Single-shot (no tools): the model is
225
+ * forced to return JSON matching `params.inputSchema`, validated by the AI SDK before
226
+ * it resolves. Use this for analysis/extraction where you want a typed object instead
227
+ * of free text. Additive — does not touch `run`/`runStream`.
228
+ */
229
+ declare function runObject<T>(registry: Registry, params: RunObjectParams<T>, defaults?: ClientDefaults): Promise<RunObjectResult<T>>;
230
+
231
+ /** Provider prefixes this package can construct directly from a generic API key. */
232
+ declare const SUPPORTED_PROVIDERS: string[];
233
+ /**
234
+ * Turn a `provider/model` string (e.g. 'anthropic/claude-sonnet-4-5', 'openai/gpt-4o',
235
+ * 'xai/grok-2-latest') into a concrete provider model, using a single generic `apiKey`.
236
+ *
237
+ * Falls back to returning the original string when there's no recognizable provider prefix,
238
+ * the provider is unknown, or no `apiKey` is given — in which case the Vercel AI SDK resolves
239
+ * it itself (its gateway / provider-specific env vars). This keeps existing string usage working.
240
+ */
241
+ declare function resolveModelString(model: string, apiKey?: string): LanguageModel | string;
242
+
243
+ export { type Agent, type AgentDefinition, AiAgentsClient, type ClientDefaults, type Message, type ModelId, type ModelLike, Registry, type RunObjectParams, type RunObjectResult, type RunParams, type RunResult, SUPPORTED_PROVIDERS, type StreamEvent, type ToolContext, type ToolDefinition, createClient, resolveModelString, run, runObject, runStream };
package/dist/index.js ADDED
@@ -0,0 +1,268 @@
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
+
51
+ // src/providers.ts
52
+ import { createAnthropic } from "@ai-sdk/anthropic";
53
+ import { createOpenAI } from "@ai-sdk/openai";
54
+ import { createXai } from "@ai-sdk/xai";
55
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
56
+ var PROVIDERS = {
57
+ anthropic: (apiKey, id) => createAnthropic({ apiKey })(id),
58
+ openai: (apiKey, id) => createOpenAI({ apiKey })(id),
59
+ xai: (apiKey, id) => createXai({ apiKey })(id),
60
+ // Grok
61
+ google: (apiKey, id) => createGoogleGenerativeAI({ apiKey })(id)
62
+ };
63
+ var SUPPORTED_PROVIDERS = Object.keys(PROVIDERS);
64
+ function resolveModelString(model, apiKey) {
65
+ const slash = model.indexOf("/");
66
+ if (slash <= 0) return model;
67
+ const provider = model.slice(0, slash);
68
+ const modelId = model.slice(slash + 1);
69
+ const factory = PROVIDERS[provider];
70
+ if (factory && apiKey && modelId) return factory(apiKey, modelId);
71
+ return model;
72
+ }
73
+
74
+ // src/run.ts
75
+ function resolveModel(agent, defaults) {
76
+ const model = agent.model ?? defaults?.model;
77
+ if (typeof model === "string") return resolveModelString(model, defaults?.apiKey);
78
+ return model;
79
+ }
80
+ function buildMessages(agent, params) {
81
+ const messages = [];
82
+ const history = params.history ?? [];
83
+ const hasSystem = history.some((m) => m.role === "system");
84
+ if (!hasSystem && agent.instructions) {
85
+ messages.push({ role: "system", content: agent.instructions });
86
+ }
87
+ for (const m of history) messages.push(m);
88
+ if (params.message) {
89
+ messages.push({ role: "user", content: params.message });
90
+ }
91
+ return messages;
92
+ }
93
+ function buildTools(registry, agent, context) {
94
+ const defs = registry.resolveTools(agent);
95
+ const out = {};
96
+ for (const def of defs) {
97
+ out[def.name] = tool({
98
+ description: def.description,
99
+ inputSchema: def.inputSchema,
100
+ execute: async (input, opts) => def.execute(input, { toolCallId: opts.toolCallId, app: context })
101
+ });
102
+ }
103
+ return out;
104
+ }
105
+ async function run(registry, params, defaults) {
106
+ const agent = registry.resolveAgent(params.agent);
107
+ const messages = buildMessages(agent, params);
108
+ const tools = buildTools(registry, agent, params.context ?? {});
109
+ const result = await generateText({
110
+ model: resolveModel(agent, defaults),
111
+ messages,
112
+ tools,
113
+ temperature: params.temperature ?? agent.temperature,
114
+ maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
115
+ stopWhen: ({ steps }) => steps.length >= (params.maxSteps ?? 8),
116
+ providerOptions: agent.providerOptions
117
+ });
118
+ return {
119
+ text: result.text,
120
+ finishReason: result.finishReason,
121
+ usage: {
122
+ inputTokens: result.usage?.inputTokens ?? 0,
123
+ outputTokens: result.usage?.outputTokens ?? 0,
124
+ totalTokens: result.usage?.totalTokens ?? 0
125
+ },
126
+ toolCalls: (result.toolCalls ?? []).map((c) => ({
127
+ toolCallId: c.toolCallId,
128
+ toolName: c.toolName,
129
+ input: c.input
130
+ })),
131
+ toolResults: (result.toolResults ?? []).map((r) => ({
132
+ toolCallId: r.toolCallId,
133
+ toolName: r.toolName,
134
+ output: r.output
135
+ })),
136
+ raw: result
137
+ };
138
+ }
139
+ async function* runStream(registry, params, defaults) {
140
+ const agent = registry.resolveAgent(params.agent);
141
+ const messages = buildMessages(agent, params);
142
+ const tools = buildTools(registry, agent, params.context ?? {});
143
+ const result = streamText({
144
+ model: resolveModel(agent, defaults),
145
+ messages,
146
+ tools,
147
+ temperature: params.temperature ?? agent.temperature,
148
+ maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
149
+ stopWhen: ({ steps }) => steps.length >= (params.maxSteps ?? 8),
150
+ providerOptions: agent.providerOptions
151
+ });
152
+ for await (const part of result.fullStream) {
153
+ switch (part.type) {
154
+ case "text-delta":
155
+ yield { type: "text-delta", text: part.text ?? part.delta ?? "" };
156
+ break;
157
+ case "tool-call":
158
+ yield {
159
+ type: "tool-call",
160
+ toolCallId: part.toolCallId,
161
+ toolName: part.toolName,
162
+ input: part.input ?? part.args
163
+ };
164
+ break;
165
+ case "tool-result":
166
+ yield {
167
+ type: "tool-result",
168
+ toolCallId: part.toolCallId,
169
+ toolName: part.toolName,
170
+ output: part.output ?? part.result
171
+ };
172
+ break;
173
+ case "tool-error":
174
+ yield {
175
+ type: "tool-error",
176
+ toolCallId: part.toolCallId,
177
+ toolName: part.toolName,
178
+ error: part.error
179
+ };
180
+ break;
181
+ case "finish":
182
+ yield {
183
+ type: "finish",
184
+ finishReason: part.finishReason,
185
+ usage: {
186
+ inputTokens: part.usage?.inputTokens ?? 0,
187
+ outputTokens: part.usage?.outputTokens ?? 0,
188
+ totalTokens: part.usage?.totalTokens ?? 0
189
+ }
190
+ };
191
+ break;
192
+ case "error":
193
+ yield { type: "error", error: part.error };
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ async function runObject(registry, params, defaults) {
199
+ const agent = registry.resolveAgent(params.agent);
200
+ const messages = buildMessages(agent, params);
201
+ const result = await generateObject({
202
+ model: resolveModel(agent, defaults),
203
+ messages,
204
+ schema: params.inputSchema,
205
+ schemaName: params.schemaName,
206
+ schemaDescription: params.schemaDescription,
207
+ temperature: params.temperature ?? agent.temperature,
208
+ maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
209
+ providerOptions: agent.providerOptions
210
+ });
211
+ return {
212
+ object: result.object,
213
+ finishReason: result.finishReason ?? "stop",
214
+ usage: {
215
+ inputTokens: result.usage?.inputTokens ?? 0,
216
+ outputTokens: result.usage?.outputTokens ?? 0,
217
+ totalTokens: result.usage?.totalTokens ?? 0
218
+ },
219
+ raw: result
220
+ };
221
+ }
222
+
223
+ // src/client.ts
224
+ var AiAgentsClient = class {
225
+ registry;
226
+ defaults;
227
+ constructor(config) {
228
+ this.registry = new Registry();
229
+ this.defaults = { apiKey: config?.apiKey, model: config?.model };
230
+ }
231
+ registerTool(tool2) {
232
+ return this.registry.registerTool(tool2);
233
+ }
234
+ createAgent(definition) {
235
+ return this.registry.createAgent(definition);
236
+ }
237
+ async run(params) {
238
+ return run(this.registry, params, this.defaults);
239
+ }
240
+ runStream(params) {
241
+ return runStream(this.registry, params, this.defaults);
242
+ }
243
+ /**
244
+ * Schema-constrained structured generation. Returns a validated, typed object
245
+ * instead of free text. Single-shot (no tools). Added in 0.2.0.
246
+ */
247
+ async runObject(params) {
248
+ return runObject(this.registry, params, this.defaults);
249
+ }
250
+ /** Direct access to the underlying registry for advanced introspection. */
251
+ get raw() {
252
+ return this.registry;
253
+ }
254
+ };
255
+ function createClient(config) {
256
+ return new AiAgentsClient(config);
257
+ }
258
+ export {
259
+ AiAgentsClient,
260
+ Registry,
261
+ SUPPORTED_PROVIDERS,
262
+ createClient,
263
+ resolveModelString,
264
+ run,
265
+ runObject,
266
+ runStream,
267
+ sanitizeRichHtml
268
+ };
@@ -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.3.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,33 +27,40 @@
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
+ "dependencies": {
52
+ "@ai-sdk/anthropic": "^2.0.0",
53
+ "@ai-sdk/google": "^2.0.0",
54
+ "@ai-sdk/openai": "^2.0.0",
55
+ "@ai-sdk/xai": "^2.0.0",
56
+ "ai": "^6.0.0"
57
+ },
51
58
  "peerDependencies": {
52
- "ai": "^6.0.0",
53
59
  "zod": "^3.23.0 || ^4.0.0"
54
60
  },
55
61
  "devDependencies": {
56
62
  "@types/node": "^22",
57
- "ai": "^6.0.0",
63
+ "tsup": "^8.3.5",
58
64
  "typescript": "^6.0.2",
59
65
  "zod": "^4.0.0"
60
66
  },
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 };