@~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 +29 -4
- package/dist/chunk-7CW6W47V.js +64 -0
- package/dist/index.d.ts +243 -0
- package/dist/index.js +268 -0
- package/dist/sanitize.d.ts +12 -0
- package/dist/sanitize.js +10 -0
- package/package.json +20 -14
- package/src/agents.ts +0 -53
- package/src/client.ts +0 -52
- package/src/index.ts +0 -19
- package/src/run.ts +0 -153
- package/src/sanitize.ts +0 -80
- package/src/types.ts +0 -110
package/README.md
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
|
-
#
|
|
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
|
|
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 '
|
|
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, """)}"`;
|
|
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
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|
package/dist/sanitize.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@~lyre/ai-agents",
|
|
3
|
-
"version": "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/
|
|
12
|
-
"directory": "packages/ai-agents"
|
|
11
|
+
"url": "git+https://github.com/kigathi-chege/lyre-ai-agents.git"
|
|
13
12
|
},
|
|
14
|
-
"homepage": "https://github.com/
|
|
13
|
+
"homepage": "https://github.com/kigathi-chege/lyre-ai-agents#readme",
|
|
15
14
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/
|
|
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": "./
|
|
32
|
-
"types": "./
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
33
32
|
"exports": {
|
|
34
33
|
".": {
|
|
35
|
-
"types": "./
|
|
36
|
-
"import": "./
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
37
36
|
},
|
|
38
37
|
"./sanitize": {
|
|
39
|
-
"types": "./
|
|
40
|
-
"import": "./
|
|
38
|
+
"types": "./dist/sanitize.d.ts",
|
|
39
|
+
"import": "./dist/sanitize.js"
|
|
41
40
|
}
|
|
42
41
|
},
|
|
43
42
|
"files": [
|
|
44
|
-
"
|
|
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
|
-
"
|
|
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, '"')}"`;
|
|
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 };
|