@~lyre/ai-agents 0.5.0 → 0.5.4

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/dist/index.d.ts CHANGED
@@ -51,6 +51,33 @@ type Message = {
51
51
  type ClientDefaults = {
52
52
  apiKey?: string;
53
53
  model?: ModelLike;
54
+ /**
55
+ * Remote agent source (e.g. Axis Intelligence). When set, a run for an agent NOT registered locally
56
+ * fetches its definition from `${remoteBaseUrl}/agents/{slug}/definition`, runs it locally against the
57
+ * provider, and reports the result to `${remoteBaseUrl}/runs`. Tools the definition names that aren't
58
+ * registered locally become proxy tools that POST to `${remoteBaseUrl}/tools/{slug}/call`.
59
+ */
60
+ remoteBaseUrl?: string;
61
+ /** Bearer token (service key) sent to the remote source for fetch/proxy/report calls. */
62
+ remoteToken?: string;
63
+ };
64
+ /** A tool spec carried by a remote agent definition (name-only for built-ins; schema for apiTools). */
65
+ type RemoteToolSpec = {
66
+ name: string;
67
+ description?: string | null;
68
+ inputSchema?: any;
69
+ /** The slug the consumer POSTs to `${remoteBaseUrl}/tools/{proxySlug}/call`. Null for built-ins. */
70
+ proxySlug?: string | null;
71
+ };
72
+ /** A registered remote proxy tool — materialized (with the run's context) into an AI-SDK tool at run time
73
+ * in buildTools, so the tool call sent to the source carries the run's scope (appId/tenantId/etc.). */
74
+ type ProxyToolSpec = {
75
+ name: string;
76
+ description: string;
77
+ inputSchema: any;
78
+ proxySlug: string;
79
+ base: string;
80
+ headers: Record<string, string>;
54
81
  };
55
82
  type AgentDefinition = {
56
83
  /** Stable identifier; used as the lookup key. Defaults to `name`. */
@@ -91,6 +118,31 @@ type RunParams = {
91
118
  temperature?: number;
92
119
  /** Max number of tool-call iterations before bailing. Default 8. */
93
120
  maxSteps?: number;
121
+ /**
122
+ * Optional schema-constrained final output. When set, the run uses tools AND forces the model's
123
+ * FINAL answer to be a JSON object matching this schema (AI SDK `Output.object`) — i.e. call tools,
124
+ * read their results, then produce a validated object. The validated object is surfaced on the
125
+ * `finish` stream event as `object` (and on `RunResult.object`). Generating the object counts as a
126
+ * step, so keep `maxSteps` high enough for tool calls + the object step. Omit for free-text runs.
127
+ */
128
+ output?: {
129
+ schema: z.ZodSchema<any>;
130
+ name?: string;
131
+ description?: string;
132
+ };
133
+ /**
134
+ * Tool-selection policy for the FIRST step (AI SDK / OpenAI `tool_choice`):
135
+ * - `'auto'` (default): the model decides whether to call a tool.
136
+ * - `'required'`: the model MUST call a tool before it can answer — use this to force a data
137
+ * round-trip so the model can't fabricate an answer (e.g. a canvas with placeholder values).
138
+ * - `'none'`: the model must not call tools.
139
+ * - `{ toolName }`: force a specific tool.
140
+ * Only the first step is constrained; later steps fall back to `auto` so the model can stop calling
141
+ * tools and produce its final (optionally schema-constrained) answer.
142
+ */
143
+ toolChoice?: 'auto' | 'required' | 'none' | {
144
+ toolName: string;
145
+ };
94
146
  /** Forwarded to tool `execute(input, context)`. */
95
147
  context?: Record<string, any>;
96
148
  };
@@ -112,6 +164,8 @@ type RunResult = {
112
164
  toolName: string;
113
165
  output: unknown;
114
166
  }[];
167
+ /** The schema-validated final object, present only when the run was given `output`. */
168
+ object?: unknown;
115
169
  raw: any;
116
170
  };
117
171
  type StreamEvent = {
@@ -140,6 +194,8 @@ type StreamEvent = {
140
194
  outputTokens: number;
141
195
  totalTokens: number;
142
196
  };
197
+ /** The schema-validated final object, present only when the run was given `output`. */
198
+ object?: unknown;
143
199
  } | {
144
200
  type: 'error';
145
201
  error: unknown;
@@ -183,8 +239,15 @@ type RunObjectResult<T = unknown> = {
183
239
  */
184
240
  declare class Registry {
185
241
  readonly tools: Map<string, ToolDefinition>;
242
+ readonly proxyTools: Map<string, ProxyToolSpec>;
186
243
  readonly agents: Map<string, Agent>;
187
244
  registerTool<I, O>(tool: ToolDefinition<I, O>): ToolDefinition<I, O>;
245
+ /** Register a remote proxy tool spec by name (materialized with run context in buildTools). */
246
+ registerProxyTool(spec: ProxyToolSpec): void;
247
+ /** True if a tool with this name is registered (either shape). */
248
+ hasTool(name: string): boolean;
249
+ /** True if an agent with this id/name is registered. */
250
+ hasAgent(idOrName: string): boolean;
188
251
  createAgent(definition: AgentDefinition): Agent;
189
252
  resolveAgent(input: string | AgentDefinition): Agent;
190
253
  resolveTools(agent: Agent): ToolDefinition[];
@@ -240,4 +303,4 @@ declare const SUPPORTED_PROVIDERS: string[];
240
303
  */
241
304
  declare function resolveModelString(model: string, apiKey?: string): LanguageModel | string;
242
305
 
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 };
306
+ export { type Agent, type AgentDefinition, AiAgentsClient, type ClientDefaults, type Message, type ModelId, type ModelLike, type ProxyToolSpec, Registry, type RemoteToolSpec, 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 CHANGED
@@ -5,11 +5,26 @@ import {
5
5
  // src/agents.ts
6
6
  var Registry = class {
7
7
  tools = /* @__PURE__ */ new Map();
8
+ // Remote proxy tool specs — materialized into AI-SDK tools at run time in buildTools, so each call can
9
+ // fold the run's CONTEXT (scope: appId/tenantId/targetApps/…) into the request sent to the source.
10
+ proxyTools = /* @__PURE__ */ new Map();
8
11
  agents = /* @__PURE__ */ new Map();
9
12
  registerTool(tool2) {
10
13
  this.tools.set(tool2.name, tool2);
11
14
  return tool2;
12
15
  }
16
+ /** Register a remote proxy tool spec by name (materialized with run context in buildTools). */
17
+ registerProxyTool(spec) {
18
+ this.proxyTools.set(spec.name, spec);
19
+ }
20
+ /** True if a tool with this name is registered (either shape). */
21
+ hasTool(name) {
22
+ return this.tools.has(name) || this.proxyTools.has(name);
23
+ }
24
+ /** True if an agent with this id/name is registered. */
25
+ hasAgent(idOrName) {
26
+ return this.agents.has(idOrName);
27
+ }
13
28
  createAgent(definition) {
14
29
  const agent = {
15
30
  id: definition.id ?? definition.name,
@@ -80,7 +95,103 @@ function resolveModelString(model, apiKey) {
80
95
  return model;
81
96
  }
82
97
 
98
+ // src/remote.ts
99
+ import { tool as aiTool, jsonSchema } from "ai";
100
+ function authHeaders(defaults) {
101
+ const h = { "Content-Type": "application/json" };
102
+ if (defaults.remoteToken) h.Authorization = `Bearer ${defaults.remoteToken}`;
103
+ return h;
104
+ }
105
+ async function ensureRemoteAgent(registry, agentSlug, defaults) {
106
+ if (!defaults.remoteBaseUrl) return null;
107
+ if (registry.hasAgent(agentSlug)) return agentSlug;
108
+ const base = defaults.remoteBaseUrl.replace(/\/+$/, "");
109
+ let def;
110
+ try {
111
+ const res = await fetch(`${base}/agents/${encodeURIComponent(agentSlug)}/definition`, {
112
+ method: "GET",
113
+ headers: authHeaders(defaults)
114
+ });
115
+ if (!res.ok) return null;
116
+ def = await res.json();
117
+ } catch {
118
+ return null;
119
+ }
120
+ for (const spec of def.tools ?? []) {
121
+ if (!spec?.name || registry.hasTool(spec.name)) continue;
122
+ if (!spec.proxySlug) continue;
123
+ registry.registerProxyTool({
124
+ name: spec.name,
125
+ description: spec.description ?? `Proxied tool "${spec.proxySlug}".`,
126
+ inputSchema: spec.inputSchema ?? { type: "object", properties: {} },
127
+ proxySlug: spec.proxySlug,
128
+ base,
129
+ headers: authHeaders(defaults)
130
+ });
131
+ }
132
+ registry.createAgent({
133
+ id: def.slug,
134
+ name: def.slug,
135
+ model: def.model,
136
+ instructions: def.instructions ?? "",
137
+ temperature: def.temperature,
138
+ maxOutputTokens: def.maxOutputTokens,
139
+ tools: (def.tools ?? []).map((t) => t.name)
140
+ });
141
+ return def.slug;
142
+ }
143
+ function buildProxyTool(spec, context) {
144
+ const schema = spec.inputSchema ?? { type: "object", properties: {} };
145
+ const scope = pickScope(context);
146
+ return aiTool({
147
+ description: spec.description,
148
+ inputSchema: jsonSchema(schema),
149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
+ execute: async (input) => {
151
+ const res = await fetch(`${spec.base}/tools/${encodeURIComponent(spec.proxySlug)}/call`, {
152
+ method: "POST",
153
+ headers: spec.headers,
154
+ body: JSON.stringify({ input, ...scope })
155
+ });
156
+ if (!res.ok) {
157
+ const text = await res.text().catch(() => "");
158
+ return { error: `Proxied tool "${spec.proxySlug}" failed: HTTP ${res.status} ${text}`.trim() };
159
+ }
160
+ const body = await res.json().catch(() => ({}));
161
+ return body.output ?? body;
162
+ }
163
+ });
164
+ }
165
+ function pickScope(context) {
166
+ const out = {};
167
+ for (const key of ["appId", "tenantId", "scopeMode", "targetApps", "collectionIds", "range"]) {
168
+ if (context[key] !== void 0 && context[key] !== null) out[key] = context[key];
169
+ }
170
+ if (context.toolContext && typeof context.toolContext === "object") {
171
+ out.toolContext = context.toolContext;
172
+ }
173
+ return out;
174
+ }
175
+ async function reportRun(defaults, report) {
176
+ if (!defaults.remoteBaseUrl) return;
177
+ const base = defaults.remoteBaseUrl.replace(/\/+$/, "");
178
+ try {
179
+ await fetch(`${base}/runs`, {
180
+ method: "POST",
181
+ headers: authHeaders(defaults),
182
+ body: JSON.stringify(report)
183
+ });
184
+ } catch {
185
+ }
186
+ }
187
+
83
188
  // src/run.ts
189
+ async function resolveAgentMaybeRemote(registry, agentInput, defaults) {
190
+ if (typeof agentInput === "string" && defaults?.remoteBaseUrl && !registry.hasAgent(agentInput)) {
191
+ await ensureRemoteAgent(registry, agentInput, defaults);
192
+ }
193
+ return registry.resolveAgent(agentInput);
194
+ }
84
195
  function resolveModel(agent, defaults) {
85
196
  const model = agent.model ?? defaults?.model;
86
197
  if (typeof model === "string") return resolveModelString(model, defaults?.apiKey);
@@ -109,22 +220,44 @@ function buildTools(registry, agent, context) {
109
220
  execute: async (input, opts) => def.execute(input, { toolCallId: opts.toolCallId, app: context })
110
221
  });
111
222
  }
223
+ const allowed = agent.tools && agent.tools.length > 0 ? new Set(agent.tools) : null;
224
+ for (const [name, spec] of registry.proxyTools) {
225
+ if (allowed && !allowed.has(name)) continue;
226
+ if (!out[name]) out[name] = buildProxyTool(spec, context);
227
+ }
112
228
  return out;
113
229
  }
230
+ function buildRunOptions(params) {
231
+ const opts = {};
232
+ if (params.output) {
233
+ opts.output = Output.object({
234
+ schema: params.output.schema,
235
+ name: params.output.name,
236
+ description: params.output.description
237
+ });
238
+ }
239
+ if (params.toolChoice && params.toolChoice !== "auto") {
240
+ opts.toolChoice = params.toolChoice;
241
+ opts.prepareStep = ({ stepNumber }) => stepNumber === 0 ? { toolChoice: params.toolChoice } : { toolChoice: "auto" };
242
+ }
243
+ return opts;
244
+ }
114
245
  async function run(registry, params, defaults) {
115
- const agent = registry.resolveAgent(params.agent);
246
+ const startedAt = Date.now();
247
+ const agent = await resolveAgentMaybeRemote(registry, params.agent, defaults);
116
248
  const messages = buildMessages(agent, params);
117
249
  const tools = buildTools(registry, agent, params.context ?? {});
118
250
  const result = await generateText({
119
251
  model: resolveModel(agent, defaults),
120
252
  messages,
121
253
  tools,
254
+ ...buildRunOptions(params),
122
255
  temperature: params.temperature ?? agent.temperature,
123
256
  maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
124
257
  stopWhen: ({ steps }) => steps.length >= (params.maxSteps ?? 8),
125
258
  providerOptions: agent.providerOptions
126
259
  });
127
- return {
260
+ const runResult = {
128
261
  text: result.text,
129
262
  finishReason: result.finishReason,
130
263
  usage: {
@@ -142,17 +275,44 @@ async function run(registry, params, defaults) {
142
275
  toolName: r.toolName,
143
276
  output: r.output
144
277
  })),
278
+ ...params.output ? { object: result.experimental_output } : {},
145
279
  raw: result
146
280
  };
281
+ void reportRun(defaults ?? {}, buildRunReport(agent, params, runResult, true, null, startedAt));
282
+ return runResult;
283
+ }
284
+ function buildRunReport(agent, params, result, ok, error, startedAt) {
285
+ return {
286
+ operation: "agent_ask",
287
+ agentSlug: agent.name,
288
+ model: typeof agent.model === "string" ? agent.model : null,
289
+ question: params.message ?? null,
290
+ requestPayload: { message: params.message ?? null, context: params.context ?? null },
291
+ responsePayload: { text: result.text, object: result.object ?? null, finishReason: result.finishReason ?? null },
292
+ toolCalls: result.toolCalls,
293
+ toolResults: result.toolResults,
294
+ inputTokens: result.usage.inputTokens,
295
+ outputTokens: result.usage.outputTokens,
296
+ totalTokens: result.usage.totalTokens,
297
+ latencyMs: Date.now() - startedAt,
298
+ ok,
299
+ error
300
+ };
147
301
  }
148
302
  async function* runStream(registry, params, defaults) {
149
- const agent = registry.resolveAgent(params.agent);
303
+ const startedAt = Date.now();
304
+ const agent = await resolveAgentMaybeRemote(registry, params.agent, defaults);
150
305
  const messages = buildMessages(agent, params);
151
306
  const tools = buildTools(registry, agent, params.context ?? {});
307
+ let reportText = "";
308
+ let reportObject;
309
+ const reportToolCalls = [];
310
+ const reportToolResults = [];
152
311
  const result = streamText({
153
312
  model: resolveModel(agent, defaults),
154
313
  messages,
155
314
  tools,
315
+ ...buildRunOptions(params),
156
316
  temperature: params.temperature ?? agent.temperature,
157
317
  maxOutputTokens: params.maxOutputTokens ?? agent.maxOutputTokens,
158
318
  stopWhen: ({ steps }) => steps.length >= (params.maxSteps ?? 8),
@@ -167,13 +327,17 @@ async function* runStream(registry, params, defaults) {
167
327
  };
168
328
  for await (const part of result.fullStream) {
169
329
  switch (part.type) {
170
- case "text-delta":
171
- yield { type: "text-delta", text: part.text ?? part.delta ?? "" };
330
+ case "text-delta": {
331
+ const t = part.text ?? part.delta ?? "";
332
+ reportText += t;
333
+ yield { type: "text-delta", text: t };
172
334
  break;
335
+ }
173
336
  case "finish-step":
174
337
  addUsage(part.usage);
175
338
  break;
176
339
  case "tool-call":
340
+ reportToolCalls.push({ toolCallId: part.toolCallId, toolName: part.toolName, input: part.input ?? part.args });
177
341
  yield {
178
342
  type: "tool-call",
179
343
  toolCallId: part.toolCallId,
@@ -182,6 +346,7 @@ async function* runStream(registry, params, defaults) {
182
346
  };
183
347
  break;
184
348
  case "tool-result":
349
+ reportToolResults.push({ toolCallId: part.toolCallId, toolName: part.toolName, output: part.output ?? part.result });
185
350
  yield {
186
351
  type: "tool-result",
187
352
  toolCallId: part.toolCallId,
@@ -199,18 +364,47 @@ async function* runStream(registry, params, defaults) {
199
364
  break;
200
365
  case "finish": {
201
366
  const finalUsage = part.totalUsage ?? part.usage;
367
+ let object;
368
+ if (params.output) {
369
+ object = await result.experimental_output;
370
+ }
371
+ reportObject = object;
372
+ const usage = {
373
+ inputTokens: finalUsage?.inputTokens ?? accumulatedUsage.inputTokens,
374
+ outputTokens: finalUsage?.outputTokens ?? accumulatedUsage.outputTokens,
375
+ totalTokens: finalUsage?.totalTokens ?? accumulatedUsage.totalTokens
376
+ };
377
+ void reportRun(
378
+ defaults ?? {},
379
+ buildRunReport(
380
+ agent,
381
+ params,
382
+ { text: reportText, usage, toolCalls: reportToolCalls, toolResults: reportToolResults, object: reportObject, finishReason: part.finishReason },
383
+ true,
384
+ null,
385
+ startedAt
386
+ )
387
+ );
202
388
  yield {
203
389
  type: "finish",
204
390
  finishReason: part.finishReason,
205
- usage: {
206
- inputTokens: finalUsage?.inputTokens ?? accumulatedUsage.inputTokens,
207
- outputTokens: finalUsage?.outputTokens ?? accumulatedUsage.outputTokens,
208
- totalTokens: finalUsage?.totalTokens ?? accumulatedUsage.totalTokens
209
- }
391
+ usage,
392
+ ...params.output ? { object } : {}
210
393
  };
211
394
  break;
212
395
  }
213
396
  case "error":
397
+ void reportRun(
398
+ defaults ?? {},
399
+ buildRunReport(
400
+ agent,
401
+ params,
402
+ { text: reportText, usage: accumulatedUsage, toolCalls: reportToolCalls, toolResults: reportToolResults, object: reportObject },
403
+ false,
404
+ part.error instanceof Error ? part.error.message : String(part.error),
405
+ startedAt
406
+ )
407
+ );
214
408
  yield { type: "error", error: part.error };
215
409
  break;
216
410
  }
@@ -249,7 +443,12 @@ var AiAgentsClient = class {
249
443
  defaults;
250
444
  constructor(config) {
251
445
  this.registry = new Registry();
252
- this.defaults = { apiKey: config?.apiKey, model: config?.model };
446
+ this.defaults = {
447
+ apiKey: config?.apiKey,
448
+ model: config?.model,
449
+ remoteBaseUrl: config?.remoteBaseUrl,
450
+ remoteToken: config?.remoteToken
451
+ };
253
452
  }
254
453
  registerTool(tool2) {
255
454
  return this.registry.registerTool(tool2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@~lyre/ai-agents",
3
- "version": "0.5.0",
3
+ "version": "0.5.4",
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,