@economic/agents 0.0.1-beta.2 → 0.0.1-beta.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/README.md CHANGED
@@ -339,6 +339,33 @@ export const datetimeSkill: Skill = {
339
339
 
340
340
  ---
341
341
 
342
+ ## Surfacing source URLs from tools
343
+
344
+ Any tool can surface source URLs into the message stream by including a `sources` array in its return value. `buildLLMParams` automatically detects this and emits `source-url` stream parts that the playground's Sources block renders.
345
+
346
+ ```typescript
347
+ execute: async ({ query }) => {
348
+ const data = await fetchResults(query);
349
+ return {
350
+ results: data.results, // LLM receives full content
351
+ sources: data.results.map(r => ({ url: r.url, title: r.title })), // rendered as source links
352
+ };
353
+ },
354
+ ```
355
+
356
+ The `sources` array is picked up by a built-in `experimental_transform` inside `buildLLMParams` — no agent changes, no writer passed to the tool, no additional wiring. The LLM continues to receive the full result object including `sources`. The transform fires on every `tool-result` stream part and emits a `source` part for each entry.
357
+
358
+ Each source entry shape:
359
+
360
+ | Field | Type | Required | Description |
361
+ | ------- | -------- | -------- | ----------------------- |
362
+ | `url` | `string` | Yes | The URL to link to. |
363
+ | `title` | `string` | No | Display name in the UI. |
364
+
365
+ The playground's `UIMessageRenderer` collects all `source-url` parts from a message and displays them in a collapsible **Sources** block above the response text.
366
+
367
+ ---
368
+
342
369
  ## Compaction
343
370
 
344
371
  When `fastModel` is set on the agent class, compaction runs automatically before each turn:
package/dist/index.d.mts CHANGED
@@ -16,8 +16,9 @@ interface Skill {
16
16
  description: string;
17
17
  /**
18
18
  * Guidance text for this skill — e.g. rate limits, preferred patterns,
19
- * when to use each tool. Appended to the `system` prompt each turn for any
20
- * skill that is loaded, keeping `system` mostly cacheable.
19
+ * when to use each tool. Injected into the `## Tools` section of the
20
+ * system prompt via `buildSystemPrompt` in `llm.ts` whenever this skill
21
+ * is loaded.
21
22
  */
22
23
  guidance?: string;
23
24
  tools: ToolSet;
package/dist/index.mjs CHANGED
@@ -40,12 +40,7 @@ function buildActivateSkillDescription(skills) {
40
40
  ].join("\n");
41
41
  }
42
42
  function buildAvailableSkillList(skills) {
43
- return [
44
- "**Loading More Tools:**",
45
- "Use `activate_skill` to load these categories (BE PROACTIVE on requesting tools based on the user's request AND you DON'T need to mention that you are loading more tools):",
46
- "",
47
- skills.map((s) => `**${s.name}**: ${s.description}`).join("\n")
48
- ].join("\n");
43
+ return skills.map((s) => `**${s.name}**: ${s.description}`).join("\n");
49
44
  }
50
45
  const LIST_CAPABILITIES_DESCRIPTION = "List all tools currently available to you, which skills are loaded, and which can still be loaded. Call this when the user asks about your capabilities or what you can do.";
51
46
  /**
@@ -95,20 +90,7 @@ function createSkills(config) {
95
90
  return [`**${skill.name}**\n${skill.guidance}`];
96
91
  });
97
92
  if (sections.length === 0) return "";
98
- return [
99
- "**Loaded skill instructions**",
100
- "The following skills are currently active. Apply their instructions when using the corresponding tools.",
101
- "",
102
- sections.join("\n\n")
103
- ].join("\n");
104
- }
105
- function getSystem() {
106
- const guidance = getLoadedGuidance();
107
- return [
108
- config.systemPrompt,
109
- availableSkillList,
110
- guidance
111
- ].filter(Boolean).join("\n\n");
93
+ return sections.join("\n\n");
112
94
  }
113
95
  allTools[ACTIVATE_SKILL] = tool({
114
96
  description: buildActivateSkillDescription(skills),
@@ -155,17 +137,14 @@ function createSkills(config) {
155
137
  }
156
138
  });
157
139
  const prepareStep = async () => {
158
- return {
159
- activeTools: getActiveToolNames(),
160
- ...config.systemPrompt !== void 0 && { system: getSystem() }
161
- };
140
+ return { activeTools: getActiveToolNames() };
162
141
  };
163
142
  return {
164
143
  tools: allTools,
165
144
  activeTools: getActiveToolNames(),
166
145
  prepareStep,
146
+ availableSkillList,
167
147
  getLoadedGuidance,
168
- getSystem,
169
148
  getLoadedSkills() {
170
149
  return [...loadedSkills];
171
150
  }
@@ -304,6 +283,80 @@ async function compactIfNeeded(messages, model, tailSize) {
304
283
  //#endregion
305
284
  //#region src/server/llm.ts
306
285
  /**
286
+ * Composes the full system prompt from its three parts: the consumer's base
287
+ * string, the static skill roster, and the dynamic loaded-skill guidance.
288
+ *
289
+ * The full shape, at a glance:
290
+ *
291
+ * {base}
292
+ *
293
+ * ## Tools
294
+ *
295
+ * Use `activate_skill` to load these skills (BE PROACTIVE on requesting
296
+ * tools based on the user's request AND you DON'T need to mention that you
297
+ * are loading more tools):
298
+ *
299
+ * **{name}**: {description}
300
+ * ...
301
+ *
302
+ * **Loaded skill instructions**
303
+ * The following skills are currently active. Apply their instructions when
304
+ * using the corresponding tools.
305
+ *
306
+ * **{name}**
307
+ * {guidance body}
308
+ */
309
+ function buildSystemPrompt(basePrompt, availableSkillList, loadedGuidance) {
310
+ let prompt = `${basePrompt}
311
+
312
+ ## Tools
313
+
314
+ ### Loading More Tools:
315
+ Use \`activate_skill\` to load these skills (BE PROACTIVE on requesting tools based on the user's request AND you DON'T need to mention that you are loading more tools):
316
+
317
+ ${availableSkillList}`;
318
+ if (loadedGuidance) prompt += `
319
+
320
+ ### Loaded skill instructions:
321
+ The following skills are currently active. Apply their instructions when using the corresponding tools.
322
+
323
+ ${loadedGuidance}`;
324
+ return prompt.trim();
325
+ }
326
+ /**
327
+ * Convention: tools can include a `sources` array in their return value to surface
328
+ * source URLs in the message stream. This transform detects it automatically.
329
+ *
330
+ * ```typescript
331
+ * // In any tool's execute function:
332
+ * return { myContent: "...", sources: [{ url: "https://...", title: "Page title" }] };
333
+ * ```
334
+ */
335
+ function buildSourcesTransform(additional) {
336
+ const sourcesTransform = () => new TransformStream({ transform(chunk, controller) {
337
+ controller.enqueue(chunk);
338
+ if (chunk.type !== "tool-result") return;
339
+ const output = chunk.output;
340
+ if (!output || typeof output !== "object" || Array.isArray(output)) return;
341
+ const sources = output.sources;
342
+ if (!Array.isArray(sources)) return;
343
+ for (const source of sources) {
344
+ if (!source || typeof source !== "object") continue;
345
+ const s = source;
346
+ if (typeof s.url !== "string") continue;
347
+ controller.enqueue({
348
+ type: "source",
349
+ sourceType: "url",
350
+ id: crypto.randomUUID(),
351
+ url: s.url,
352
+ ...typeof s.title === "string" ? { title: s.title } : {}
353
+ });
354
+ }
355
+ } });
356
+ if (!additional) return sourcesTransform;
357
+ return [sourcesTransform, ...Array.isArray(additional) ? additional : [additional]];
358
+ }
359
+ /**
307
360
  * Builds the parameter object for a Vercel AI SDK `streamText` or `generateText` call.
308
361
  *
309
362
  * Handles message conversion, optional compaction, skill wiring (`activate_skill`,
@@ -318,33 +371,34 @@ async function compactIfNeeded(messages, model, tailSize) {
318
371
  * ```
319
372
  */
320
373
  async function buildLLMParams(config) {
321
- const { options, messages, activeSkills = [], skills, fastModel, maxMessagesBeforeCompaction, ...rest } = config;
374
+ const { options, messages, activeSkills = [], skills, fastModel, maxMessagesBeforeCompaction, experimental_transform, ...rest } = config;
322
375
  const rawMessages = await convertToModelMessages(messages);
323
376
  const processedMessages = fastModel && maxMessagesBeforeCompaction !== void 0 ? await compactIfNeeded(rawMessages, fastModel, maxMessagesBeforeCompaction) : rawMessages;
377
+ const composedTransform = buildSourcesTransform(experimental_transform);
324
378
  const baseParams = {
325
379
  ...rest,
380
+ experimental_transform: composedTransform,
326
381
  messages: processedMessages,
327
382
  experimental_context: options?.body,
328
383
  abortSignal: options?.abortSignal,
329
384
  stopWhen: rest.stopWhen ?? stepCountIs(20)
330
385
  };
331
386
  if (!skills?.length) return baseParams;
387
+ const base = typeof rest.system === "string" ? rest.system : void 0;
332
388
  const skillsCtx = createSkills({
333
389
  tools: rest.tools ?? {},
334
390
  skills,
335
- initialLoadedSkills: activeSkills,
336
- systemPrompt: typeof rest.system === "string" ? rest.system : void 0
391
+ initialLoadedSkills: activeSkills
337
392
  });
338
393
  const prepareStep = async (stepOptions) => {
339
- const skillsResult = await skillsCtx.prepareStep(stepOptions) ?? {};
340
394
  return {
341
- activeTools: skillsResult.activeTools ?? [],
342
- system: skillsResult.system
395
+ activeTools: (await skillsCtx.prepareStep(stepOptions) ?? {}).activeTools ?? [],
396
+ system: buildSystemPrompt(base, skillsCtx.availableSkillList, skillsCtx.getLoadedGuidance())
343
397
  };
344
398
  };
345
399
  return {
346
400
  ...baseParams,
347
- system: skillsCtx.getSystem() || rest.system,
401
+ system: buildSystemPrompt(base, skillsCtx.availableSkillList, skillsCtx.getLoadedGuidance()),
348
402
  tools: skillsCtx.tools,
349
403
  activeTools: skillsCtx.activeTools,
350
404
  prepareStep
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "0.0.1-beta.2",
3
+ "version": "0.0.1-beta.4",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [