@~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 +29 -4
- package/dist/chunk-7CW6W47V.js +64 -0
- package/dist/index.d.ts +214 -0
- package/dist/index.js +237 -0
- package/dist/sanitize.d.ts +12 -0
- package/dist/sanitize.js +10 -0
- package/package.json +13 -12
- 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,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 };
|
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.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/
|
|
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,24 +27,25 @@
|
|
|
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
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, '"')}"`;
|
|
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 };
|