@andreprado/agentkit 0.1.0-alpha.5 → 0.1.0-alpha.7

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.
Files changed (87) hide show
  1. package/README.md +9 -0
  2. package/docs/guides/add-channel.md +25 -0
  3. package/docs/guides/add-knowledge.md +134 -0
  4. package/docs/guides/agentkit-skills-architecture.md +471 -0
  5. package/docs/guides/channels-production-handoff.md +2 -0
  6. package/docs/guides/connect-telegram.md +17 -0
  7. package/docs/guides/connect-whatsapp-zapster.md +16 -0
  8. package/docs/guides/create-agent.md +10 -1
  9. package/docs/guides/run-evals.md +36 -1
  10. package/docs/llms-full.txt +90 -1
  11. package/docs/llms.txt +9 -2
  12. package/package.json +2 -1
  13. package/src/cli/cloud-client.ts +10 -2
  14. package/src/cli/commands/channels.ts +67 -3
  15. package/src/cli/commands/knowledge.ts +136 -0
  16. package/src/cli/deploy-chat-ui.ts +7 -0
  17. package/src/cli/deploy-readiness.ts +19 -0
  18. package/src/cli/help.ts +26 -2
  19. package/src/cli/index.ts +140 -8
  20. package/src/cloud/artifact.ts +92 -1
  21. package/src/cloud/contracts.ts +16 -0
  22. package/src/create-project.ts +38 -6
  23. package/src/index.ts +142 -1
  24. package/src/providers/pi.ts +1 -1
  25. package/src/providers/test.ts +1 -1
  26. package/src/runtime/channel-buffer.ts +30 -0
  27. package/src/runtime/channels.ts +1 -0
  28. package/src/runtime/chat.ts +21 -2
  29. package/src/runtime/config.ts +175 -0
  30. package/src/runtime/core/manifest.ts +37 -0
  31. package/src/runtime/database.ts +93 -2
  32. package/src/runtime/db-commands.ts +9 -0
  33. package/src/runtime/deploy-readiness.ts +12 -0
  34. package/src/runtime/dev-server.ts +201 -11
  35. package/src/runtime/evals.ts +210 -20
  36. package/src/runtime/inspect.ts +39 -0
  37. package/src/runtime/knowledge/chunk.ts +333 -0
  38. package/src/runtime/knowledge/config.ts +135 -0
  39. package/src/runtime/knowledge/embeddings.ts +133 -0
  40. package/src/runtime/knowledge/ingest.ts +521 -0
  41. package/src/runtime/knowledge/prompt-policy.ts +30 -0
  42. package/src/runtime/knowledge/retrieve.ts +283 -0
  43. package/src/runtime/knowledge/schema.ts +56 -0
  44. package/src/runtime/knowledge/tool.ts +64 -0
  45. package/src/runtime/knowledge/vector.ts +258 -0
  46. package/src/runtime/spec.ts +152 -0
  47. package/src/runtime/sync.ts +144 -0
  48. package/src/runtime/targets/cloudflare/build.ts +514 -4
  49. package/src/runtime/tools.ts +121 -1
  50. package/src/runtime/traces.ts +41 -0
  51. package/src/storage/sqlite.ts +141 -0
  52. package/src/templates/blank.ts +16 -5
  53. package/src/templates/dentista.ts +17 -2
  54. package/src/templates/skills/agentkit-build-agent/SKILL.md +51 -0
  55. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +20 -0
  56. package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
  57. package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
  58. package/src/templates/skills/agentkit-capsule/SKILL.md +62 -0
  59. package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
  60. package/src/templates/skills/agentkit-channels/SKILL.md +62 -0
  61. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +58 -0
  62. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +37 -0
  63. package/src/templates/skills/agentkit-channels/references/telegram.md +37 -0
  64. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +37 -0
  65. package/src/templates/skills/agentkit-database/SKILL.md +45 -0
  66. package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
  67. package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
  68. package/src/templates/skills/agentkit-deploy/SKILL.md +44 -0
  69. package/src/templates/skills/agentkit-evals/SKILL.md +60 -0
  70. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +22 -0
  71. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +14 -0
  72. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +14 -0
  73. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +18 -0
  74. package/src/templates/skills/agentkit-knowledge/SKILL.md +40 -0
  75. package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
  76. package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
  77. package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
  78. package/src/templates/skills/agentkit-prompts/SKILL.md +45 -0
  79. package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
  80. package/src/templates/skills/agentkit-provider/SKILL.md +57 -0
  81. package/src/templates/skills/agentkit-security/SKILL.md +55 -0
  82. package/src/templates/skills/agentkit-tools/SKILL.md +36 -0
  83. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
  84. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
  85. package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
  86. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +52 -0
  87. package/src/templates/support.ts +15 -4
package/src/index.ts CHANGED
@@ -17,6 +17,28 @@ export type ChannelLimits = {
17
17
  usdPerDay?: number;
18
18
  };
19
19
 
20
+ export type ChannelBufferMode = "off" | "debounce";
21
+
22
+ export type ChannelBufferInput = {
23
+ mode?: ChannelBufferMode;
24
+ quietWindowMs?: number;
25
+ maxWaitMs?: number;
26
+ maxMessages?: number;
27
+ maxChars?: number;
28
+ };
29
+
30
+ export type ChannelBufferConfig =
31
+ | {
32
+ mode: "off";
33
+ }
34
+ | {
35
+ mode: "debounce";
36
+ quietWindowMs: number;
37
+ maxWaitMs: number;
38
+ maxMessages: number;
39
+ maxChars: number;
40
+ };
41
+
20
42
  export type WebsiteChannelAccess = {
21
43
  mode: "token";
22
44
  };
@@ -26,6 +48,7 @@ export type WebsiteChannelInput = {
26
48
  access?: WebsiteChannelAccess;
27
49
  secrets?: string[];
28
50
  limits?: ChannelLimits;
51
+ buffer?: ChannelBufferInput;
29
52
  };
30
53
 
31
54
  export type WebsiteChannelConfig = {
@@ -35,6 +58,7 @@ export type WebsiteChannelConfig = {
35
58
  access: WebsiteChannelAccess;
36
59
  secrets: string[];
37
60
  limits?: ChannelLimits;
61
+ buffer?: ChannelBufferConfig;
38
62
  };
39
63
 
40
64
  export type TelegramChannelInput = {
@@ -42,6 +66,7 @@ export type TelegramChannelInput = {
42
66
  secrets?: string[];
43
67
  allowedUpdates?: TelegramAllowedUpdate[];
44
68
  limits?: ChannelLimits;
69
+ buffer?: ChannelBufferInput;
45
70
  };
46
71
 
47
72
  export type TelegramChannelConfig = {
@@ -51,6 +76,7 @@ export type TelegramChannelConfig = {
51
76
  secrets: string[];
52
77
  allowedUpdates: TelegramAllowedUpdate[];
53
78
  limits?: ChannelLimits;
79
+ buffer?: ChannelBufferConfig;
54
80
  };
55
81
 
56
82
  export type WhatsappChannelInput = {
@@ -58,6 +84,7 @@ export type WhatsappChannelInput = {
58
84
  provider: WhatsappChannelProvider;
59
85
  secrets?: string[];
60
86
  limits?: ChannelLimits;
87
+ buffer?: ChannelBufferInput;
61
88
  };
62
89
 
63
90
  export type WhatsappChannelConfig = {
@@ -66,6 +93,7 @@ export type WhatsappChannelConfig = {
66
93
  name: string;
67
94
  secrets: string[];
68
95
  limits?: ChannelLimits;
96
+ buffer?: ChannelBufferConfig;
69
97
  };
70
98
 
71
99
  export type AgentChannel = WebsiteChannelConfig | TelegramChannelConfig | WhatsappChannelConfig;
@@ -77,6 +105,7 @@ export type AgentDatabaseConfig =
77
105
  | {
78
106
  driver: "turso";
79
107
  schema?: string;
108
+ migrations?: string;
80
109
  };
81
110
 
82
111
  export type AgentFilesConfig =
@@ -90,6 +119,37 @@ export type AgentFilesConfig =
90
119
  maxUploadBytes?: number;
91
120
  };
92
121
 
122
+ export type AgentKnowledgeSource =
123
+ | string
124
+ | {
125
+ path: string;
126
+ title?: string;
127
+ }
128
+ | {
129
+ url: string;
130
+ title?: string;
131
+ };
132
+
133
+ export type AgentKnowledgeEmbeddingProvider = "none" | "test" | "openai";
134
+
135
+ export type AgentKnowledgeEmbeddingConfig = {
136
+ provider?: AgentKnowledgeEmbeddingProvider;
137
+ model?: string;
138
+ dimensions?: number;
139
+ secret?: string;
140
+ };
141
+
142
+ export type AgentKnowledgeRetrievalConfig = {
143
+ topK?: number;
144
+ hybrid?: boolean;
145
+ };
146
+
147
+ export type AgentKnowledgeConfig = {
148
+ sources?: AgentKnowledgeSource[];
149
+ embedding?: AgentKnowledgeEmbeddingConfig;
150
+ retrieval?: AgentKnowledgeRetrievalConfig;
151
+ };
152
+
93
153
  export type JsonSchema = {
94
154
  type: string | string[];
95
155
  properties?: Record<string, JsonSchema>;
@@ -119,9 +179,42 @@ export type DatabaseRunner = {
119
179
  batch(statements: DatabaseStatement[]): Promise<DatabaseResult[]>;
120
180
  };
121
181
 
182
+ export type ToolRenderField = {
183
+ label: string;
184
+ value: string;
185
+ };
186
+
187
+ export type ToolRenderAction = {
188
+ label: string;
189
+ href?: string;
190
+ };
191
+
192
+ export type ToolRenderBlock =
193
+ | {
194
+ type: "text";
195
+ text: string;
196
+ }
197
+ | {
198
+ type: "card";
199
+ title: string;
200
+ subtitle?: string;
201
+ fields?: ToolRenderField[];
202
+ actions?: ToolRenderAction[];
203
+ };
204
+
205
+ export type ToolRenderResult = {
206
+ text: string;
207
+ blocks?: ToolRenderBlock[];
208
+ };
209
+
210
+ export type ToolRenderContext<Input = unknown> = {
211
+ input: Input;
212
+ runtime: ToolRuntimeContext;
213
+ };
214
+
122
215
  export type ToolRuntimeContext = {
123
216
  environment: "local" | "hosted" | "eval" | "test";
124
- invocation: "chat" | "tool" | "dev" | "channel" | "eval";
217
+ invocation: "chat" | "tool" | "dev" | "channel" | "eval" | "sync";
125
218
  target: "bun" | "cloudflare";
126
219
  database: "sqlite" | "turso" | "none";
127
220
  };
@@ -137,6 +230,13 @@ export type ToolContext = {
137
230
  };
138
231
  };
139
232
 
233
+ export type SyncContext = {
234
+ env: Record<string, string | undefined>;
235
+ database: DatabaseRunner;
236
+ db: DatabaseRunner;
237
+ runtime: ToolRuntimeContext;
238
+ };
239
+
140
240
  export type AgentTool<Input = unknown, Output = unknown> = {
141
241
  name: string;
142
242
  description: string;
@@ -147,6 +247,7 @@ export type AgentTool<Input = unknown, Output = unknown> = {
147
247
  permissions?: string[];
148
248
  timeoutMs?: number;
149
249
  execute(input: Input, ctx: ToolContext): Promise<Output> | Output;
250
+ render?(output: Output, ctx: ToolRenderContext<Input>): string | ToolRenderResult;
150
251
  };
151
252
 
152
253
  export type AgentConfig = {
@@ -157,6 +258,7 @@ export type AgentConfig = {
157
258
  secrets: string[];
158
259
  tools?: AgentTool[];
159
260
  channels?: AgentChannel[];
261
+ knowledge?: AgentKnowledgeConfig;
160
262
  access: {
161
263
  mode: AccessMode;
162
264
  };
@@ -184,6 +286,7 @@ export function websiteChannel(input: WebsiteChannelInput): WebsiteChannelConfig
184
286
  access: input.access ?? { mode: "token" },
185
287
  secrets: normalizeChannelSecrets(input.secrets ?? ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"], "website channel"),
186
288
  ...(input.limits ? { limits: input.limits } : {}),
289
+ ...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "website channel") } : {}),
187
290
  };
188
291
  }
189
292
 
@@ -198,6 +301,7 @@ export function telegramChannel(input: TelegramChannelInput): TelegramChannelCon
198
301
  ),
199
302
  allowedUpdates: input.allowedUpdates ?? ["message"],
200
303
  ...(input.limits ? { limits: input.limits } : {}),
304
+ ...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "telegram channel") } : {}),
201
305
  };
202
306
  }
203
307
 
@@ -211,6 +315,7 @@ export function whatsappChannel(input: WhatsappChannelInput): WhatsappChannelCon
211
315
  `${input.provider} whatsapp channel`,
212
316
  ),
213
317
  ...(input.limits ? { limits: input.limits } : {}),
318
+ ...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, `${input.provider} whatsapp channel`) } : {}),
214
319
  };
215
320
  }
216
321
 
@@ -236,10 +341,46 @@ function normalizeChannelSecrets(secrets: string[], context: string): string[] {
236
341
  return Array.from(uniqueSecrets).sort();
237
342
  }
238
343
 
344
+ function normalizeChannelBuffer(input: ChannelBufferInput, context: string): ChannelBufferConfig {
345
+ if (input.mode === "off") {
346
+ return { mode: "off" };
347
+ }
348
+
349
+ if (input.mode !== undefined && input.mode !== "debounce") {
350
+ throw new Error(`${context} buffer.mode must be "off" or "debounce".`);
351
+ }
352
+
353
+ const buffer: ChannelBufferConfig = {
354
+ mode: "debounce",
355
+ quietWindowMs: input.quietWindowMs ?? 2500,
356
+ maxWaitMs: input.maxWaitMs ?? 12000,
357
+ maxMessages: input.maxMessages ?? 20,
358
+ maxChars: input.maxChars ?? 8000,
359
+ };
360
+
361
+ validatePositiveInteger(buffer.quietWindowMs, `${context} buffer.quietWindowMs`);
362
+ validatePositiveInteger(buffer.maxWaitMs, `${context} buffer.maxWaitMs`);
363
+ validatePositiveInteger(buffer.maxMessages, `${context} buffer.maxMessages`);
364
+ validatePositiveInteger(buffer.maxChars, `${context} buffer.maxChars`);
365
+
366
+ if (buffer.maxWaitMs < buffer.quietWindowMs) {
367
+ throw new Error(`${context} buffer.maxWaitMs must be greater than or equal to buffer.quietWindowMs.`);
368
+ }
369
+
370
+ return buffer;
371
+ }
372
+
373
+ function validatePositiveInteger(value: number, field: string): void {
374
+ if (!Number.isInteger(value) || value <= 0) {
375
+ throw new Error(`${field} must be a positive integer.`);
376
+ }
377
+ }
378
+
239
379
  export { loadAgentCapsule, findAgentCapsuleRoot } from "./runtime/config";
240
380
  export { runAgentMessage, runAgentMessageFromCwd } from "./runtime/chat";
241
381
  export { listConversationsFromCwd, getConversationFromCwd } from "./runtime/conversations";
242
382
  export { runEvalsFromCwd } from "./runtime/evals";
383
+ export { getConversationTraceFromCwd } from "./runtime/traces";
243
384
  export { runToolFromCwd } from "./runtime/tool-runner";
244
385
  export { startAgentDevServer } from "./runtime/dev-server";
245
386
  export { inspectAgentCapsule } from "./runtime/inspect";
@@ -97,7 +97,7 @@ async function runPiProvider(input: ProviderRunInput): Promise<ProviderRunResult
97
97
  content: [
98
98
  {
99
99
  type: "text",
100
- text: JSON.stringify(result.output),
100
+ text: result.rendered?.text ?? JSON.stringify(result.output),
101
101
  },
102
102
  ],
103
103
  isError: false,
@@ -29,7 +29,7 @@ export const testProviderAdapter: ProviderAdapter = {
29
29
  content:
30
30
  toolResult.visibility === "internal"
31
31
  ? `Tool ${toolResult.name}: completed`
32
- : `Tool ${toolResult.name}: ${JSON.stringify(toolResult.output)}`,
32
+ : toolResult.rendered?.text ?? `Tool ${toolResult.name}: ${JSON.stringify(toolResult.output)}`,
33
33
  },
34
34
  raw: {
35
35
  toolCalls: [toolResult],
@@ -0,0 +1,30 @@
1
+ import type { ChannelBufferConfig, ChannelType } from "../index";
2
+
3
+ export type ChannelBufferCarrier = {
4
+ type: ChannelType;
5
+ buffer?: ChannelBufferConfig;
6
+ };
7
+
8
+ export type ChannelBufferedMessage = {
9
+ content: string;
10
+ receivedAt: string;
11
+ };
12
+
13
+ export function resolveChannelBuffer(channel: ChannelBufferCarrier): ChannelBufferConfig {
14
+ return channel.buffer ?? { mode: "off" };
15
+ }
16
+
17
+ export function isChannelBufferEnabled(channel: ChannelBufferCarrier): boolean {
18
+ return resolveChannelBuffer(channel).mode === "debounce";
19
+ }
20
+
21
+ export function renderBufferedChannelMessage(messages: ChannelBufferedMessage[]): string {
22
+ if (messages.length === 1) {
23
+ return messages[0]?.content ?? "";
24
+ }
25
+
26
+ const lines = messages.map((message, index) => `${index + 1}. ${message.content}`);
27
+
28
+ return `The client sent ${messages.length} messages in one burst. Answer once, using the full context below:\n\n${lines.join("\n")}`;
29
+ }
30
+
@@ -8,6 +8,7 @@ export type ChannelDeliveryState =
8
8
  | "received"
9
9
  | "validated"
10
10
  | "duplicate"
11
+ | "buffered"
11
12
  | "queued"
12
13
  | "running"
13
14
  | "sent"
@@ -7,6 +7,9 @@ import { loadAgentCapsule } from "./config";
7
7
  import { openPreparedCapsuleStore } from "./database";
8
8
  import { loadCapsuleEnv } from "./env";
9
9
  import { AgentKitError, isAgentKitError } from "./errors";
10
+ import { syncConfiguredKnowledgeSources } from "./knowledge/ingest";
11
+ import { appendKnowledgePromptPolicy } from "./knowledge/prompt-policy";
12
+ import { createKnowledgeSearchTool } from "./knowledge/tool";
10
13
  import { createToolRuntime } from "./tools";
11
14
 
12
15
  export type RunAgentMessageOptions = {
@@ -37,6 +40,7 @@ export async function runAgentMessage(
37
40
 
38
41
  const adapter = getProviderAdapter(capsule.config.provider);
39
42
  const env = await loadCapsuleEnv(capsule.root);
43
+ await syncConfiguredKnowledgeSources(capsule);
40
44
  const { store } = await openPreparedCapsuleStore(capsule);
41
45
  const conversationId = normalizeConversationId(options.conversationId);
42
46
  const { conversation, previousMessages } = resolveConversation(store, {
@@ -55,8 +59,9 @@ export async function runAgentMessage(
55
59
  provider: capsule.config.provider.name,
56
60
  model: capsule.config.provider.model,
57
61
  });
62
+ const runtimeTools = withKnowledgeTools(capsule);
58
63
  const toolRuntime = createToolRuntime({
59
- tools: capsule.config.tools ?? [],
64
+ tools: runtimeTools,
60
65
  store,
61
66
  runId: run.id,
62
67
  conversationId: conversation.id,
@@ -78,7 +83,10 @@ export async function runAgentMessage(
78
83
  runtime: capsule.config.runtime,
79
84
  provider: capsule.config.provider,
80
85
  },
81
- instructions: withToolVisibilityInstructions(capsule.instructions, capsule.config.tools ?? []),
86
+ instructions: withToolVisibilityInstructions(
87
+ appendKnowledgePromptPolicy(capsule.instructions, capsule.config),
88
+ runtimeTools,
89
+ ),
82
90
  messages,
83
91
  tools: toolRuntime.definitions,
84
92
  toolRuntime,
@@ -119,6 +127,17 @@ export async function runAgentMessage(
119
127
  }
120
128
  }
121
129
 
130
+ function withKnowledgeTools(capsule: LoadedAgentCapsule): NonNullable<LoadedAgentCapsule["config"]["tools"]> {
131
+ const tools = [...(capsule.config.tools ?? [])];
132
+ const knowledgeTool = createKnowledgeSearchTool(capsule);
133
+
134
+ if (knowledgeTool) {
135
+ return [knowledgeTool, ...tools];
136
+ }
137
+
138
+ return tools;
139
+ }
140
+
122
141
  function resolveConversation(
123
142
  store: Awaited<ReturnType<typeof openPreparedCapsuleStore>>["store"],
124
143
  input: {
@@ -14,6 +14,8 @@ import type {
14
14
  ChannelProvider,
15
15
  ChannelType,
16
16
  TelegramAllowedUpdate,
17
+ AgentKnowledgeEmbeddingProvider,
18
+ ChannelBufferMode,
17
19
  } from "../index";
18
20
  import { AgentKitError } from "./errors";
19
21
 
@@ -24,7 +26,9 @@ const ACCESS_MODES = new Set<AccessMode>(["private", "token", "public"]);
24
26
  const TOOL_VISIBILITIES = new Set<ToolVisibility>(["user", "internal"]);
25
27
  const CHANNEL_TYPES = new Set<ChannelType>(["website", "telegram", "whatsapp"]);
26
28
  const CHANNEL_PROVIDERS = new Set<ChannelProvider>(["agentkit", "telegram", "zapster", "meta"]);
29
+ const CHANNEL_BUFFER_MODES = new Set<ChannelBufferMode>(["off", "debounce"]);
27
30
  const TELEGRAM_ALLOWED_UPDATES = new Set<TelegramAllowedUpdate>(["message"]);
31
+ const KNOWLEDGE_EMBEDDING_PROVIDERS = new Set<AgentKnowledgeEmbeddingProvider>(["none", "test", "openai"]);
28
32
  const CHANNEL_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
29
33
  const SECRET_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
30
34
  const moduleDir = dirname(fileURLToPath(import.meta.url));
@@ -205,6 +209,7 @@ function validateAgentConfig(value: unknown): asserts value is AgentConfig {
205
209
  }
206
210
 
207
211
  validateChannels(value.channels, value.runtime as AgentRuntime);
212
+ validateKnowledgeConfig(value.knowledge);
208
213
 
209
214
  if (!isRecord(value.access) || !ACCESS_MODES.has(value.access.mode as AccessMode)) {
210
215
  throw invalidConfig('access.mode must be "private", "token", or "public".');
@@ -226,6 +231,134 @@ function validateAgentConfig(value: unknown): asserts value is AgentConfig {
226
231
  validateFilesConfig(value.storage.files);
227
232
  }
228
233
 
234
+ function validateKnowledgeConfig(value: unknown): void {
235
+ if (value === undefined) {
236
+ return;
237
+ }
238
+
239
+ if (!isRecord(value)) {
240
+ throw invalidConfig("knowledge must be an object when provided.");
241
+ }
242
+
243
+ if (value.sources !== undefined) {
244
+ if (!Array.isArray(value.sources)) {
245
+ throw invalidConfig("knowledge.sources must be an array when provided.");
246
+ }
247
+
248
+ for (const [index, source] of value.sources.entries()) {
249
+ validateKnowledgeSource(source, index);
250
+ }
251
+ }
252
+
253
+ if (value.embedding !== undefined) {
254
+ validateKnowledgeEmbedding(value.embedding);
255
+ }
256
+
257
+ if (value.retrieval !== undefined) {
258
+ validateKnowledgeRetrieval(value.retrieval);
259
+ }
260
+ }
261
+
262
+ function validateKnowledgeSource(value: unknown, index: number): void {
263
+ if (typeof value === "string") {
264
+ expectString(value, `knowledge.sources[${index}]`);
265
+ validateKnowledgeSourceReference(value, `knowledge.sources[${index}]`);
266
+ return;
267
+ }
268
+
269
+ if (!isRecord(value)) {
270
+ throw invalidConfig(`knowledge.sources[${index}] must be a path string or an object with path or url.`);
271
+ }
272
+
273
+ const hasPath = "path" in value;
274
+ const hasUrl = "url" in value;
275
+
276
+ if (hasPath === hasUrl) {
277
+ throw invalidConfig(`knowledge.sources[${index}] must set exactly one of path or url.`);
278
+ }
279
+
280
+ if (hasPath) {
281
+ expectString(value.path, `knowledge.sources[${index}].path`);
282
+ validateKnowledgeSourceReference(value.path, `knowledge.sources[${index}].path`);
283
+ }
284
+
285
+ if (hasUrl) {
286
+ expectString(value.url, `knowledge.sources[${index}].url`);
287
+ validateKnowledgeUrl(value.url, `knowledge.sources[${index}].url`);
288
+ }
289
+
290
+ if (value.title !== undefined) {
291
+ expectString(value.title, `knowledge.sources[${index}].title`);
292
+ }
293
+ }
294
+
295
+ function validateKnowledgeSourceReference(value: string, label: string): void {
296
+ if (value.includes("\0")) {
297
+ throw invalidConfig(`${label} must not contain null bytes.`);
298
+ }
299
+
300
+ if (/^https?:\/\//i.test(value)) {
301
+ validateKnowledgeUrl(value, label);
302
+ }
303
+ }
304
+
305
+ function validateKnowledgeUrl(value: string, label: string): void {
306
+ let url: URL;
307
+
308
+ try {
309
+ url = new URL(value);
310
+ } catch {
311
+ throw invalidConfig(`${label} must be a valid http or https URL.`);
312
+ }
313
+
314
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
315
+ throw invalidConfig(`${label} must be a valid http or https URL.`);
316
+ }
317
+ }
318
+
319
+ function validateKnowledgeEmbedding(value: unknown): void {
320
+ if (!isRecord(value)) {
321
+ throw invalidConfig("knowledge.embedding must be an object when provided.");
322
+ }
323
+
324
+ if (value.provider !== undefined && !KNOWLEDGE_EMBEDDING_PROVIDERS.has(value.provider as AgentKnowledgeEmbeddingProvider)) {
325
+ throw invalidConfig(
326
+ `knowledge.embedding.provider must be one of: ${Array.from(KNOWLEDGE_EMBEDDING_PROVIDERS).join(", ")}.`,
327
+ );
328
+ }
329
+
330
+ if (value.model !== undefined) {
331
+ expectString(value.model, "knowledge.embedding.model");
332
+ }
333
+
334
+ if (value.secret !== undefined) {
335
+ expectString(value.secret, "knowledge.embedding.secret");
336
+ validateSecretNames([value.secret], "knowledge.embedding.secret");
337
+ }
338
+
339
+ const dimensions = value.dimensions;
340
+ if (dimensions !== undefined) {
341
+ if (typeof dimensions !== "number" || !Number.isInteger(dimensions) || dimensions <= 0 || dimensions > 65_536) {
342
+ throw invalidConfig("knowledge.embedding.dimensions must be a positive integer no larger than 65536.");
343
+ }
344
+ }
345
+ }
346
+
347
+ function validateKnowledgeRetrieval(value: unknown): void {
348
+ if (!isRecord(value)) {
349
+ throw invalidConfig("knowledge.retrieval must be an object when provided.");
350
+ }
351
+
352
+ const topK = value.topK;
353
+ if (topK !== undefined && (typeof topK !== "number" || !Number.isInteger(topK) || topK <= 0 || topK > 50)) {
354
+ throw invalidConfig("knowledge.retrieval.topK must be a positive integer no larger than 50.");
355
+ }
356
+
357
+ if (value.hybrid !== undefined && typeof value.hybrid !== "boolean") {
358
+ throw invalidConfig("knowledge.retrieval.hybrid must be a boolean when provided.");
359
+ }
360
+ }
361
+
229
362
  function validateChannels(value: unknown, runtime: AgentRuntime): void {
230
363
  if (value === undefined) {
231
364
  return;
@@ -276,6 +409,7 @@ function validateChannel(value: unknown, index: number): asserts value is AgentC
276
409
  expectStringArray(value.secrets, `channels[${index}].secrets`);
277
410
  validateSecretNames(value.secrets, `channels[${index}].secrets`);
278
411
  validateChannelLimits(value.limits, index);
412
+ validateChannelBuffer(value.buffer, index);
279
413
 
280
414
  if (value.type === "website") {
281
415
  validateWebsiteChannel(value, index);
@@ -361,6 +495,39 @@ function validateChannelLimits(value: unknown, index: number): void {
361
495
  }
362
496
  }
363
497
 
498
+ function validateChannelBuffer(value: unknown, index: number): void {
499
+ if (value === undefined) {
500
+ return;
501
+ }
502
+
503
+ if (!isRecord(value)) {
504
+ throw invalidConfig(`channels[${index}].buffer must be an object when provided.`);
505
+ }
506
+
507
+ if (!CHANNEL_BUFFER_MODES.has(value.mode as ChannelBufferMode)) {
508
+ throw invalidConfig(`channels[${index}].buffer.mode must be "off" or "debounce".`);
509
+ }
510
+
511
+ if (value.mode === "off") {
512
+ return;
513
+ }
514
+
515
+ for (const field of ["quietWindowMs", "maxWaitMs", "maxMessages", "maxChars"] as const) {
516
+ if (
517
+ value[field] === undefined ||
518
+ typeof value[field] !== "number" ||
519
+ !Number.isInteger(value[field]) ||
520
+ value[field] <= 0
521
+ ) {
522
+ throw invalidConfig(`channels[${index}].buffer.${field} must be a positive integer.`);
523
+ }
524
+ }
525
+
526
+ if ((value.maxWaitMs as number) < (value.quietWindowMs as number)) {
527
+ throw invalidConfig(`channels[${index}].buffer.maxWaitMs must be greater than or equal to quietWindowMs.`);
528
+ }
529
+ }
530
+
364
531
  function validateSecretNames(secrets: string[], field: string): void {
365
532
  for (const secret of secrets) {
366
533
  if (!SECRET_NAME_PATTERN.test(secret)) {
@@ -394,6 +561,10 @@ function validateDatabaseConfig(value: unknown): void {
394
561
  if (value.schema !== undefined && typeof value.schema !== "string") {
395
562
  throw invalidConfig("storage.database.schema must be a string when provided.");
396
563
  }
564
+
565
+ if (value.migrations !== undefined && typeof value.migrations !== "string") {
566
+ throw invalidConfig("storage.database.migrations must be a string when provided.");
567
+ }
397
568
  }
398
569
 
399
570
  function validateFilesConfig(value: unknown): void {
@@ -464,6 +635,10 @@ function validateTool(value: unknown, index: number): asserts value is AgentTool
464
635
  if (typeof value.execute !== "function") {
465
636
  throw invalidConfig(`tools[${index}].execute must be a function.`);
466
637
  }
638
+
639
+ if (value.render !== undefined && typeof value.render !== "function") {
640
+ throw invalidConfig(`tools[${index}].render must be a function when provided.`);
641
+ }
467
642
  }
468
643
 
469
644
  function resolvePath(root: string, path: string): string {
@@ -2,6 +2,8 @@ import { relative, resolve } from "node:path";
2
2
 
3
3
  import type { AgentConfig, AgentTool } from "../../index";
4
4
  import type { LoadedAgentCapsule } from "../config";
5
+ import { resolveKnowledgeConfig } from "../knowledge/config";
6
+ import { KNOWLEDGE_SEARCH_TOOL_NAME } from "../knowledge/tool";
5
7
 
6
8
  export type ManifestSchema = {
7
9
  source: string;
@@ -25,6 +27,25 @@ export type SharedAgentManifest = {
25
27
  timeoutMs: number | null;
26
28
  }>;
27
29
  channels: NonNullable<AgentConfig["channels"]>;
30
+ knowledge: {
31
+ enabled: boolean;
32
+ sources: Array<{
33
+ kind: "file" | "url";
34
+ value: string;
35
+ title: string | null;
36
+ }>;
37
+ embedding: {
38
+ provider: string;
39
+ model: string | null;
40
+ dimensions: number | null;
41
+ secret: string | null;
42
+ };
43
+ retrieval: {
44
+ topK: number;
45
+ hybrid: boolean;
46
+ };
47
+ internalTool: string | null;
48
+ };
28
49
  access: AgentConfig["access"];
29
50
  storage: {
30
51
  driver: AgentConfig["storage"]["driver"];
@@ -57,6 +78,7 @@ export function buildSharedAgentManifest(
57
78
  const requiredSecrets = new Set(capsule.config.secrets);
58
79
  const database = buildDatabaseManifest(capsule, options.databaseSchema ?? null);
59
80
  const files = buildFilesManifest(capsule);
81
+ const knowledge = resolveKnowledgeConfig(capsule.config);
60
82
 
61
83
  if (capsule.config.access.mode === "private") {
62
84
  requiredSecrets.add("AGENTKIT_PRIVATE_TOKEN");
@@ -75,6 +97,10 @@ export function buildSharedAgentManifest(
75
97
  requiredSecrets.add("TURSO_AUTH_TOKEN");
76
98
  }
77
99
 
100
+ if (knowledge.embedding.secret) {
101
+ requiredSecrets.add(knowledge.embedding.secret);
102
+ }
103
+
78
104
  for (const tool of capsule.config.tools ?? []) {
79
105
  for (const secret of tool.secrets ?? []) {
80
106
  requiredSecrets.add(secret);
@@ -104,6 +130,17 @@ export function buildSharedAgentManifest(
104
130
  timeoutMs: tool.timeoutMs ?? null,
105
131
  })),
106
132
  channels: capsule.config.channels ?? [],
133
+ knowledge: {
134
+ enabled: knowledge.enabled,
135
+ sources: knowledge.sources.map((source) => ({
136
+ kind: source.kind,
137
+ value: source.value,
138
+ title: source.title,
139
+ })),
140
+ embedding: knowledge.embedding,
141
+ retrieval: knowledge.retrieval,
142
+ internalTool: knowledge.enabled ? KNOWLEDGE_SEARCH_TOOL_NAME : null,
143
+ },
107
144
  access: capsule.config.access,
108
145
  storage: {
109
146
  driver: capsule.config.storage.driver,