@andreprado/agentkit 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.
Files changed (104) hide show
  1. package/README.md +8 -77
  2. package/docs/guides/add-channel.md +14 -92
  3. package/docs/guides/add-knowledge.md +0 -21
  4. package/docs/guides/add-tool.md +5 -11
  5. package/docs/guides/channel-security.md +3 -207
  6. package/docs/guides/connect-discord.md +7 -172
  7. package/docs/guides/connect-slack.md +6 -121
  8. package/docs/guides/connect-telegram.md +6 -165
  9. package/docs/guides/connect-whatsapp-evolution.md +6 -116
  10. package/docs/guides/connect-whatsapp-uazapi.md +6 -134
  11. package/docs/guides/connect-whatsapp-zapster.md +6 -202
  12. package/docs/guides/create-agent.md +5 -14
  13. package/docs/guides/debug-channel.md +4 -156
  14. package/docs/guides/improve-local.md +13 -0
  15. package/docs/guides/local-only-migration.md +35 -0
  16. package/docs/guides/replay-local-traces.md +11 -0
  17. package/docs/guides/run-evals.md +2 -4
  18. package/docs/guides/security-rules.md +5 -154
  19. package/docs/guides/use-jev.md +3 -6
  20. package/docs/guides/use-provider.md +0 -3
  21. package/docs/guides/write-feedback.md +10 -0
  22. package/docs/llms-full.txt +27 -448
  23. package/docs/llms.txt +8 -44
  24. package/package.json +2 -4
  25. package/src/cli/commands/channels.ts +8 -1613
  26. package/src/cli/commands/feedback.ts +8 -86
  27. package/src/cli/constants.ts +0 -3
  28. package/src/cli/flags.ts +0 -28
  29. package/src/cli/help.ts +16 -92
  30. package/src/cli/index.ts +15 -1091
  31. package/src/index.ts +6 -158
  32. package/src/providers/pi.ts +2 -0
  33. package/src/runtime/channels/discord.ts +16 -8
  34. package/src/runtime/channels/generic-webhook.ts +21 -313
  35. package/src/runtime/channels/net-guard.ts +480 -0
  36. package/src/runtime/channels/provider-fetch.ts +54 -0
  37. package/src/runtime/channels/slack.ts +9 -3
  38. package/src/runtime/channels/telegram.ts +43 -10
  39. package/src/runtime/channels/whatsapp-evolution.ts +33 -60
  40. package/src/runtime/channels/whatsapp-uazapi.ts +33 -164
  41. package/src/runtime/channels/whatsapp-zapster.ts +35 -7
  42. package/src/runtime/chat.ts +5 -3
  43. package/src/runtime/config.ts +36 -147
  44. package/src/runtime/database.ts +2 -2
  45. package/src/runtime/dev-server.ts +8 -8
  46. package/src/runtime/env.ts +11 -0
  47. package/src/runtime/improve.ts +2 -262
  48. package/src/runtime/inspect.ts +13 -73
  49. package/src/runtime/knowledge/embeddings.ts +45 -7
  50. package/src/runtime/knowledge/ingest.ts +70 -7
  51. package/src/runtime/knowledge/tool.ts +16 -2
  52. package/src/runtime/knowledge/vector.ts +31 -31
  53. package/src/runtime/tool-runner.ts +5 -3
  54. package/src/runtime/tools.ts +10 -14
  55. package/src/storage/sqlite.ts +11 -32
  56. package/src/templates/blank.ts +15 -102
  57. package/src/templates/common.ts +60 -0
  58. package/src/templates/dentista.ts +7 -74
  59. package/src/templates/skills/agentkit-capsule/SKILL.md +5 -7
  60. package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -3
  61. package/src/templates/skills/agentkit-channels/SKILL.md +6 -119
  62. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +0 -9
  63. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +1 -64
  64. package/src/templates/skills/agentkit-channels/references/discord.md +2 -92
  65. package/src/templates/skills/agentkit-channels/references/slack.md +2 -55
  66. package/src/templates/skills/agentkit-channels/references/telegram.md +2 -71
  67. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +2 -56
  68. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +2 -53
  69. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +2 -70
  70. package/src/templates/skills/agentkit-database/SKILL.md +2 -4
  71. package/src/templates/skills/agentkit-evals/SKILL.md +1 -1
  72. package/src/templates/skills/agentkit-improve/SKILL.md +6 -85
  73. package/src/templates/skills/agentkit-improve/references/trace-packets.md +1 -1
  74. package/src/templates/skills/agentkit-provider/SKILL.md +0 -1
  75. package/src/templates/skills/agentkit-security/SKILL.md +1 -3
  76. package/src/templates/skills/agentkit-tools/SKILL.md +1 -1
  77. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +1 -2
  78. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +5 -11
  79. package/src/templates/support.ts +8 -92
  80. package/docs/guides/add-managed-composio.md +0 -165
  81. package/docs/guides/improve-from-production.md +0 -151
  82. package/docs/guides/prepare-deploy.md +0 -227
  83. package/docs/guides/replay-production-traces.md +0 -72
  84. package/docs/guides/send-feedback.md +0 -135
  85. package/src/cli/cloud-client.ts +0 -377
  86. package/src/cli/deploy-chat-ui.ts +0 -606
  87. package/src/cli/deploy-readiness.ts +0 -561
  88. package/src/cloud/artifact.ts +0 -139
  89. package/src/cloud/client.ts +0 -80
  90. package/src/cloud/contracts.ts +0 -63
  91. package/src/cloud/index.ts +0 -3
  92. package/src/runtime/build.ts +0 -43
  93. package/src/runtime/core/deploy-state.ts +0 -54
  94. package/src/runtime/core/manifest.ts +0 -283
  95. package/src/runtime/core/targets.ts +0 -133
  96. package/src/runtime/deploy-readiness.ts +0 -135
  97. package/src/runtime/deploy.ts +0 -1
  98. package/src/runtime/integrations/composio.ts +0 -425
  99. package/src/runtime/targets/cloudflare/build.ts +0 -3319
  100. package/src/runtime/targets/container/build.ts +0 -146
  101. package/src/runtime/targets/container/server.ts +0 -33
  102. package/src/runtime/targets/vps/deploy.ts +0 -223
  103. package/src/templates/skills/agentkit-deploy/SKILL.md +0 -52
  104. package/src/templates/skills/agentkit-integrations/SKILL.md +0 -98
@@ -4,11 +4,6 @@ import type { AgentChannel, AgentProvider, AgentRuntime, AccessMode } from "../i
4
4
  import type { LoadedAgentCapsule } from "./config";
5
5
  import { loadAgentCapsule } from "./config";
6
6
  import { loadCapsuleEnv } from "./env";
7
- import {
8
- createManagedComposioTools,
9
- managedComposioRequiredSecrets,
10
- resolveManagedComposioIntegrations,
11
- } from "./integrations/composio";
12
7
  import { resolveKnowledgeConfig } from "./knowledge/config";
13
8
  import { KNOWLEDGE_SEARCH_TOOL_NAME } from "./knowledge/tool";
14
9
  import { resolveRuntimeTimeZone } from "./prompt-context";
@@ -27,7 +22,6 @@ export type AgentInspectState = {
27
22
  };
28
23
  prompt: string;
29
24
  tools: string[];
30
- integrations: AgentInspectIntegration[];
31
25
  channels: AgentInspectChannel[];
32
26
  transcription: {
33
27
  enabled: boolean;
@@ -67,16 +61,9 @@ export type AgentInspectState = {
67
61
  target: "bun";
68
62
  database: "sqlite" | "none";
69
63
  };
70
- hosted: {
71
- environment: "hosted";
72
- target: "cloudflare";
73
- database: "turso" | "none";
74
- providerSupported: boolean;
75
- };
76
64
  aliases: ["ctx.db", "ctx.database", "ctx.storage.sql"];
77
65
  };
78
66
  secrets: Record<string, SecretState>;
79
- managedSecrets: string[];
80
67
  userSecrets: string[];
81
68
  skills: AgentInspectSkills;
82
69
  warnings: AgentInspectWarning[];
@@ -89,12 +76,6 @@ export type AgentInspectDatabase = {
89
76
  schema: string | null;
90
77
  migrations: string | null;
91
78
  };
92
- hosted: {
93
- driver: "none" | "turso";
94
- provisioning: "none" | "agentkit-managed";
95
- schema: string | null;
96
- migrations: string | null;
97
- };
98
79
  };
99
80
 
100
81
  export type AgentInspectChannel = {
@@ -127,15 +108,6 @@ export type AgentInspectWarning = {
127
108
  command?: string;
128
109
  };
129
110
 
130
- export type AgentInspectIntegration = {
131
- name: string;
132
- provider: "composio";
133
- mode: "managed";
134
- toolkits: string[];
135
- tools: string[];
136
- userId: string;
137
- };
138
-
139
111
  export async function inspectAgentCapsule(
140
112
  cwd = process.cwd(),
141
113
  env: Record<string, string | undefined> = process.env,
@@ -151,10 +123,9 @@ export function buildInspectState(
151
123
  ): AgentInspectState {
152
124
  const declaredSecrets = new Set(capsule.config.secrets);
153
125
  const knowledge = resolveKnowledgeConfig(capsule.config);
154
- const integrationTools = createManagedComposioTools(capsule.config);
155
126
  const transcription = resolveTranscriptionConfig(capsule.config.transcription);
156
127
 
157
- for (const tool of [...integrationTools, ...(capsule.config.tools ?? [])]) {
128
+ for (const tool of capsule.config.tools ?? []) {
158
129
  for (const secret of tool.secrets ?? []) {
159
130
  declaredSecrets.add(secret);
160
131
  }
@@ -174,10 +145,7 @@ export function buildInspectState(
174
145
  declaredSecrets.add(transcription.secret);
175
146
  }
176
147
 
177
- const managedSecrets = managedCloudSecrets(capsule);
178
- const managedSecretSet = new Set(managedSecrets);
179
148
  const userSecrets = Array.from(declaredSecrets)
180
- .filter((secret) => !managedSecretSet.has(secret))
181
149
  .sort();
182
150
  const secrets: Record<string, SecretState> = {};
183
151
 
@@ -202,16 +170,7 @@ export function buildInspectState(
202
170
  }),
203
171
  },
204
172
  prompt: toCapsulePath(capsule.root, capsule.instructionsPath),
205
- tools: [...integrationTools, ...(capsule.config.tools ?? [])].map((tool) => tool.name),
206
- integrations: resolveManagedComposioIntegrations(capsule.config).map((integration) => ({
207
- name: integration.name,
208
- provider: integration.provider,
209
- mode: integration.mode,
210
- toolkits: integration.toolkits,
211
- tools: integration.allowedTools,
212
- userId: integration.userId,
213
- confirmExternalWrites: integration.confirmExternalWrites !== false,
214
- })),
173
+ tools: (capsule.config.tools ?? []).map((tool) => tool.name),
215
174
  channels: (capsule.config.channels ?? []).map((channel) => ({
216
175
  name: channel.name,
217
176
  type: channel.type,
@@ -264,16 +223,9 @@ export function buildInspectState(
264
223
  target: "bun",
265
224
  database: capsule.storagePath ? "sqlite" : "none",
266
225
  },
267
- hosted: {
268
- environment: "hosted",
269
- target: "cloudflare",
270
- database: database.hosted.driver === "turso" ? "turso" : "none",
271
- providerSupported: true,
272
- },
273
226
  aliases: ["ctx.db", "ctx.database", "ctx.storage.sql"],
274
227
  },
275
228
  secrets,
276
- managedSecrets,
277
229
  userSecrets,
278
230
  skills,
279
231
  warnings,
@@ -287,8 +239,8 @@ function toCapsulePath(root: string, path: string): string {
287
239
 
288
240
  function inspectDatabase(capsule: LoadedAgentCapsule): AgentInspectDatabase {
289
241
  const database = capsule.config.storage.database;
290
- const schema = database?.driver === "turso" && database.schema ? database.schema : null;
291
- const migrations = database?.driver === "turso" ? database.migrations ?? "migrations" : null;
242
+ const schema = database?.driver === "sqlite" && database.schema ? database.schema : null;
243
+ const migrations = database?.driver === "sqlite" ? database.migrations ?? "migrations" : null;
292
244
 
293
245
  return {
294
246
  local: {
@@ -297,12 +249,6 @@ function inspectDatabase(capsule: LoadedAgentCapsule): AgentInspectDatabase {
297
249
  schema,
298
250
  migrations,
299
251
  },
300
- hosted: {
301
- driver: database?.driver === "turso" ? "turso" : "none",
302
- provisioning: database?.driver === "turso" ? "agentkit-managed" : "none",
303
- schema,
304
- migrations,
305
- },
306
252
  };
307
253
  }
308
254
 
@@ -333,6 +279,15 @@ function inspectWarnings(
333
279
  ): AgentInspectWarning[] {
334
280
  const warnings: AgentInspectWarning[] = [];
335
281
 
282
+ for (const channel of capsule.config.channels ?? []) {
283
+ if (channel.type === "discord" && channel.mode === "bot") {
284
+ warnings.push({
285
+ code: "discord_gateway_bridge_required",
286
+ message: `Discord channel "${channel.name}" uses bot mode, but AgentKit does not connect to the Gateway. Inviting the bot alone cannot deliver messages. Use interactions or run your own Gateway bridge to forward authenticated events.`,
287
+ });
288
+ }
289
+ }
290
+
336
291
  if (skills.status === "missing" || skills.status === "outdated") {
337
292
  warnings.push({
338
293
  code: "local_skills_outdated",
@@ -355,18 +310,3 @@ function inspectWarnings(
355
310
 
356
311
  return warnings;
357
312
  }
358
-
359
- function managedCloudSecrets(capsule: LoadedAgentCapsule): string[] {
360
- const secrets = new Set<string>();
361
-
362
- if (capsule.config.storage.database?.driver === "turso") {
363
- secrets.add("TURSO_AUTH_TOKEN");
364
- secrets.add("TURSO_DATABASE_URL");
365
- }
366
-
367
- for (const secret of managedComposioRequiredSecrets(capsule.config)) {
368
- secrets.add(secret);
369
- }
370
-
371
- return Array.from(secrets).sort();
372
- }
@@ -5,8 +5,15 @@ import { AgentKitError } from "../errors";
5
5
 
6
6
  export type EmbeddingProvider = {
7
7
  embed(input: string): Promise<number[]>;
8
+ /** Embeds inputs in as few provider requests as possible; results keep input order. */
9
+ embedMany(inputs: string[]): Promise<number[][]>;
8
10
  };
9
11
 
12
+ // Each valid input is at most 8,192 tokens; 36 inputs cannot exceed the
13
+ // 300,000-token request limit, even for long CSV rows or dense text.
14
+ // ponytail: conservative batches; add token-aware packing if throughput needs it.
15
+ const OPENAI_EMBEDDING_BATCH_SIZE = Math.floor(300_000 / 8_192);
16
+
10
17
  export type CreateEmbeddingProviderOptions = {
11
18
  config: ResolvedKnowledgeConfig["embedding"];
12
19
  env?: Record<string, string | undefined>;
@@ -23,13 +30,26 @@ export function createEmbeddingProvider(options: CreateEmbeddingProviderOptions)
23
30
  async embed(input) {
24
31
  return fakeEmbedding(input, options.config.dimensions ?? 32);
25
32
  },
33
+ async embedMany(inputs) {
34
+ return inputs.map((input) => fakeEmbedding(input, options.config.dimensions ?? 32));
35
+ },
26
36
  };
27
37
  }
28
38
 
29
39
  if (options.config.provider === "openai") {
30
40
  return {
31
41
  async embed(input) {
32
- return openAiEmbedding(input, options);
42
+ const [embedding] = await openAiEmbeddings([input], options);
43
+ return embedding;
44
+ },
45
+ async embedMany(inputs) {
46
+ const embeddings: number[][] = [];
47
+
48
+ for (let start = 0; start < inputs.length; start += OPENAI_EMBEDDING_BATCH_SIZE) {
49
+ embeddings.push(...(await openAiEmbeddings(inputs.slice(start, start + OPENAI_EMBEDDING_BATCH_SIZE), options)));
50
+ }
51
+
52
+ return embeddings;
33
53
  },
34
54
  };
35
55
  }
@@ -74,7 +94,7 @@ export function cosineSimilarity(left: number[], right: number[]): number {
74
94
  return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
75
95
  }
76
96
 
77
- async function openAiEmbedding(input: string, options: CreateEmbeddingProviderOptions): Promise<number[]> {
97
+ async function openAiEmbeddings(inputs: string[], options: CreateEmbeddingProviderOptions): Promise<number[][]> {
78
98
  const secretName = options.config.secret ?? "OPENAI_API_KEY";
79
99
  const apiKey = options.env?.[secretName] ?? process.env[secretName];
80
100
 
@@ -94,7 +114,7 @@ async function openAiEmbedding(input: string, options: CreateEmbeddingProviderOp
94
114
  },
95
115
  body: JSON.stringify({
96
116
  model: options.config.model ?? "text-embedding-3-small",
97
- input,
117
+ input: inputs,
98
118
  encoding_format: "float",
99
119
  ...(options.config.dimensions ? { dimensions: options.config.dimensions } : {}),
100
120
  }),
@@ -109,13 +129,31 @@ async function openAiEmbedding(input: string, options: CreateEmbeddingProviderOp
109
129
  }
110
130
 
111
131
  const payload = await response.json();
112
- const embedding = payload?.data?.[0]?.embedding;
132
+ const data: unknown[] = Array.isArray(payload?.data) ? payload.data : [];
133
+ const embeddings: number[][] = new Array(inputs.length);
134
+
135
+ for (const [position, item] of data.entries()) {
136
+ const record = item as { index?: unknown; embedding?: unknown };
137
+ const index = typeof record.index === "number" ? record.index : position;
138
+ const embedding = record.embedding;
139
+
140
+ if (
141
+ index >= 0 &&
142
+ index < inputs.length &&
143
+ Array.isArray(embedding) &&
144
+ embedding.every((value) => typeof value === "number")
145
+ ) {
146
+ embeddings[index] = embedding;
147
+ }
148
+ }
113
149
 
114
- if (!Array.isArray(embedding) || embedding.some((value) => typeof value !== "number")) {
115
- throw new AgentKitError("knowledge_embedding_failed", "OpenAI embedding response did not contain a numeric vector.");
150
+ for (let index = 0; index < inputs.length; index += 1) {
151
+ if (!embeddings[index]) {
152
+ throw new AgentKitError("knowledge_embedding_failed", "OpenAI embedding response did not contain a numeric vector.");
153
+ }
116
154
  }
117
155
 
118
- return embedding;
156
+ return embeddings;
119
157
  }
120
158
 
121
159
  function normalizeVector(values: number[]): number[] {
@@ -151,7 +151,9 @@ async function ingestKnowledgeSources(
151
151
  }
152
152
  }
153
153
 
154
- await checkpointLocalKnowledgeDatabase(db);
154
+ if (results.some((result) => result.status === "indexed")) {
155
+ await checkpointLocalKnowledgeDatabase(db);
156
+ }
155
157
  } finally {
156
158
  store.close();
157
159
  }
@@ -269,7 +271,7 @@ async function ingestKnowledgeSource(
269
271
  if (source.kind === "url") {
270
272
  throw new AgentKitError(
271
273
  "knowledge_source_unsupported",
272
- "URL knowledge sources are planned for hosted ingestion. Local Knowledge currently supports .md, .txt, and .csv files.",
274
+ "URL knowledge ingestion is not supported. Local Knowledge currently supports .md, .txt, and .csv files.",
273
275
  );
274
276
  }
275
277
 
@@ -304,13 +306,28 @@ async function ingestKnowledgeSource(
304
306
  id: string;
305
307
  }> = [];
306
308
 
307
- for (const chunk of chunks) {
308
- const embedding = embeddingProvider ? await embeddingProvider.embed(chunk.content) : null;
309
+ const chunkHashes = chunks.map((chunk) => sha256(chunk.content));
310
+ const embeddingsByHash = embeddingProvider
311
+ ? await loadReusableChunkEmbeddings(db, sourceId, resolvedKnowledge)
312
+ : new Map<string, number[]>();
313
+
314
+ if (embeddingProvider) {
315
+ const missingHashes = [...new Set(chunkHashes.filter((hash) => !embeddingsByHash.has(hash)))];
316
+
317
+ if (missingHashes.length > 0) {
318
+ const contentByHash = new Map(chunks.map((chunk, index) => [chunkHashes[index], chunk.content]));
319
+ const embeddings = await embeddingProvider.embedMany(missingHashes.map((hash) => contentByHash.get(hash)!));
320
+ missingHashes.forEach((hash, index) => embeddingsByHash.set(hash, embeddings[index]));
321
+ }
322
+ }
323
+
324
+ for (const [index, chunk] of chunks.entries()) {
325
+ const contentSha256 = chunkHashes[index];
309
326
  embeddedChunks.push({
310
327
  chunk,
311
- embedding,
312
- contentSha256: sha256(chunk.content),
313
- id: stableId("kchk", `${sourceId}:${chunk.ordinal}:${sha256(chunk.content)}`),
328
+ embedding: embeddingProvider ? embeddingsByHash.get(contentSha256) ?? null : null,
329
+ contentSha256,
330
+ id: stableId("kchk", `${sourceId}:${chunk.ordinal}:${contentSha256}`),
314
331
  });
315
332
  }
316
333
 
@@ -491,6 +508,52 @@ function capsuleRelativePath(root: string, absolutePath: string): string {
491
508
  return relativePath.split("\\").join("/");
492
509
  }
493
510
 
511
+ // Chunks whose text is unchanged keep their stored vector, so editing one part
512
+ // of a file only pays for embedding the chunks that actually changed.
513
+ async function loadReusableChunkEmbeddings(
514
+ db: DatabaseRunner,
515
+ sourceId: string,
516
+ knowledge: ReturnType<typeof resolveKnowledgeConfig>,
517
+ ): Promise<Map<string, number[]>> {
518
+ const result = await db.query(
519
+ `
520
+ SELECT content_sha256, embedding_json
521
+ FROM agentkit_knowledge_chunks
522
+ WHERE source_id = ?
523
+ AND embedding_json IS NOT NULL
524
+ AND embedding_provider = ?
525
+ AND embedding_model = ?
526
+ AND (? IS NULL OR embedding_dimensions = ?)
527
+ `,
528
+ [
529
+ sourceId,
530
+ knowledge.embedding.provider,
531
+ knowledge.embedding.model,
532
+ knowledge.embedding.dimensions,
533
+ knowledge.embedding.dimensions,
534
+ ],
535
+ );
536
+ const embeddings = new Map<string, number[]>();
537
+
538
+ for (const row of result.rows) {
539
+ if (typeof row.content_sha256 !== "string" || typeof row.embedding_json !== "string") {
540
+ continue;
541
+ }
542
+
543
+ try {
544
+ const embedding = JSON.parse(row.embedding_json);
545
+
546
+ if (Array.isArray(embedding) && embedding.every((value) => typeof value === "number")) {
547
+ embeddings.set(row.content_sha256, embedding);
548
+ }
549
+ } catch {
550
+ // A corrupt stored vector is simply re-embedded.
551
+ }
552
+ }
553
+
554
+ return embeddings;
555
+ }
556
+
494
557
  async function findExistingSource(db: DatabaseRunner, sourceKey: string): Promise<DatabaseRow | undefined> {
495
558
  const result = await db.query(
496
559
  "SELECT id, content_sha256, status FROM agentkit_knowledge_sources WHERE source_key = ?",
@@ -1,10 +1,22 @@
1
1
  import type { AgentTool } from "../../index";
2
2
  import type { LoadedAgentCapsule } from "../config";
3
3
  import { resolveKnowledgeConfig } from "./config";
4
- import { searchKnowledge } from "./retrieve";
4
+ import { searchKnowledge, type KnowledgeSearchResult } from "./retrieve";
5
5
 
6
6
  export const KNOWLEDGE_SEARCH_TOOL_NAME = "agentkit_search_knowledge";
7
7
 
8
+ // Only the actual built-in tool may expose retrieval facts to the model.
9
+ const knowledgeTools = new WeakSet<AgentTool>();
10
+
11
+ export function knowledgeModelText(tool: AgentTool, output: unknown): string | undefined {
12
+ if (!knowledgeTools.has(tool)) return undefined;
13
+ const { results } = output as { results: Array<Pick<KnowledgeSearchResult, "content" | "source">> };
14
+ return results.map(({ content, source }) => {
15
+ const reference = [source.path, source.title, source.section, source.locator].filter(Boolean).join(" > ");
16
+ return `${content}\nSource: ${reference}`;
17
+ }).join("\n\n") || "No relevant Knowledge results found.";
18
+ }
19
+
8
20
  export function createKnowledgeSearchTool(capsule: LoadedAgentCapsule): AgentTool | null {
9
21
  const knowledge = resolveKnowledgeConfig(capsule.config);
10
22
 
@@ -12,7 +24,7 @@ export function createKnowledgeSearchTool(capsule: LoadedAgentCapsule): AgentToo
12
24
  return null;
13
25
  }
14
26
 
15
- return {
27
+ const tool: AgentTool = {
16
28
  name: KNOWLEDGE_SEARCH_TOOL_NAME,
17
29
  description: "Search the Agent Capsule's configured Knowledge sources for business-specific facts.",
18
30
  visibility: "internal",
@@ -61,4 +73,6 @@ export function createKnowledgeSearchTool(capsule: LoadedAgentCapsule): AgentToo
61
73
  };
62
74
  },
63
75
  };
76
+ knowledgeTools.add(tool);
77
+ return tool;
64
78
  }
@@ -2,7 +2,7 @@ import { access } from "node:fs/promises";
2
2
  import { basename, dirname, extname, join } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
 
5
- import { createClient } from "@libsql/client";
5
+ import { createClient, type InStatement } from "@libsql/client";
6
6
 
7
7
  import type { DatabaseRow } from "../../index";
8
8
 
@@ -48,32 +48,6 @@ export async function rebuildLocalKnowledgeVectorIndex(options: {
48
48
  const dimensions = options.dimensions;
49
49
 
50
50
  try {
51
- await vectorClient.batch(
52
- [
53
- `DROP INDEX IF EXISTS ${KNOWLEDGE_VECTOR_INDEX}`,
54
- `DROP INDEX IF EXISTS ${KNOWLEDGE_VECTOR_SOURCE_INDEX}`,
55
- `DROP TABLE IF EXISTS ${KNOWLEDGE_VECTOR_TABLE}`,
56
- `
57
- CREATE TABLE ${KNOWLEDGE_VECTOR_TABLE} (
58
- id INTEGER PRIMARY KEY,
59
- chunk_id TEXT NOT NULL UNIQUE,
60
- source_id TEXT NOT NULL,
61
- source_key TEXT NOT NULL,
62
- content TEXT NOT NULL,
63
- title TEXT,
64
- section TEXT,
65
- locator TEXT,
66
- embedding F32_BLOB(${dimensions}) NOT NULL,
67
- embedding_hash TEXT NOT NULL,
68
- created_at TEXT NOT NULL
69
- )
70
- `,
71
- `CREATE INDEX ${KNOWLEDGE_VECTOR_SOURCE_INDEX} ON ${KNOWLEDGE_VECTOR_TABLE}(source_id)`,
72
- `CREATE INDEX ${KNOWLEDGE_VECTOR_INDEX} ON ${KNOWLEDGE_VECTOR_TABLE}(libsql_vector_idx(embedding, 'metric=cosine'))`,
73
- ],
74
- "write",
75
- );
76
-
77
51
  const rows = await mainClient.execute({
78
52
  sql: `
79
53
  SELECT
@@ -97,7 +71,31 @@ export async function rebuildLocalKnowledgeVectorIndex(options: {
97
71
  args: [dimensions],
98
72
  });
99
73
 
100
- let chunksIndexed = 0;
74
+ // Drop, recreate, and refill in one write batch so a failed rebuild never
75
+ // leaves a half-populated index behind, and inserts share one transaction.
76
+ const schemaStatements: InStatement[] = [
77
+ `DROP INDEX IF EXISTS ${KNOWLEDGE_VECTOR_INDEX}`,
78
+ `DROP INDEX IF EXISTS ${KNOWLEDGE_VECTOR_SOURCE_INDEX}`,
79
+ `DROP TABLE IF EXISTS ${KNOWLEDGE_VECTOR_TABLE}`,
80
+ `
81
+ CREATE TABLE ${KNOWLEDGE_VECTOR_TABLE} (
82
+ id INTEGER PRIMARY KEY,
83
+ chunk_id TEXT NOT NULL UNIQUE,
84
+ source_id TEXT NOT NULL,
85
+ source_key TEXT NOT NULL,
86
+ content TEXT NOT NULL,
87
+ title TEXT,
88
+ section TEXT,
89
+ locator TEXT,
90
+ embedding F32_BLOB(${dimensions}) NOT NULL,
91
+ embedding_hash TEXT NOT NULL,
92
+ created_at TEXT NOT NULL
93
+ )
94
+ `,
95
+ `CREATE INDEX ${KNOWLEDGE_VECTOR_SOURCE_INDEX} ON ${KNOWLEDGE_VECTOR_TABLE}(source_id)`,
96
+ `CREATE INDEX ${KNOWLEDGE_VECTOR_INDEX} ON ${KNOWLEDGE_VECTOR_TABLE}(libsql_vector_idx(embedding, 'metric=cosine'))`,
97
+ ];
98
+ const inserts: InStatement[] = [];
101
99
 
102
100
  for (const row of rows.rows) {
103
101
  const chunkId = readString(row, "chunk_id");
@@ -112,7 +110,7 @@ export async function rebuildLocalKnowledgeVectorIndex(options: {
112
110
  continue;
113
111
  }
114
112
 
115
- await vectorClient.execute({
113
+ inserts.push({
116
114
  sql: `
117
115
  INSERT INTO ${KNOWLEDGE_VECTOR_TABLE} (
118
116
  chunk_id,
@@ -141,9 +139,11 @@ export async function rebuildLocalKnowledgeVectorIndex(options: {
141
139
  createdAt,
142
140
  ],
143
141
  });
144
- chunksIndexed += 1;
145
142
  }
146
143
 
144
+ await vectorClient.batch([...schemaStatements, ...inserts], "write");
145
+ const chunksIndexed = inserts.length;
146
+
147
147
  await vectorClient.execute("PRAGMA wal_checkpoint(TRUNCATE)").catch(() => undefined);
148
148
 
149
149
  return {
@@ -181,7 +181,7 @@ export async function searchLocalKnowledgeVectorRows(options: {
181
181
  const result = await client.execute({
182
182
  sql: `
183
183
  SELECT
184
- c.id AS chunk_id,
184
+ c.chunk_id AS chunk_id,
185
185
  c.source_id AS source_id,
186
186
  c.content AS content,
187
187
  c.title AS title,
@@ -1,8 +1,7 @@
1
1
  import { loadAgentCapsule } from "./config";
2
2
  import { openPreparedCapsuleStore } from "./database";
3
- import { loadCapsuleEnv } from "./env";
3
+ import { collectSecretValues, loadCapsuleEnv } from "./env";
4
4
  import { isAgentKitError } from "./errors";
5
- import { createManagedComposioTools } from "./integrations/composio";
6
5
  import { buildRuntimeClockContext } from "./prompt-context";
7
6
  import { createToolRuntime, type ToolCallResult } from "./tools";
8
7
 
@@ -16,6 +15,7 @@ export type RunToolOptions = {
16
15
  export async function runToolFromCwd(cwd: string, options: RunToolOptions): Promise<ToolCallResult> {
17
16
  const capsule = await loadAgentCapsule(cwd);
18
17
  const env = await loadCapsuleEnv(capsule.root);
18
+ const secretValues = collectSecretValues(capsule.config, env);
19
19
  const { store } = await openPreparedCapsuleStore(capsule);
20
20
  const conversation = store.createConversation({
21
21
  agentName: capsule.config.name,
@@ -27,11 +27,12 @@ export async function runToolFromCwd(cwd: string, options: RunToolOptions): Prom
27
27
  model: capsule.config.provider.model,
28
28
  });
29
29
  const toolRuntime = createToolRuntime({
30
- tools: [...createManagedComposioTools(capsule.config), ...(capsule.config.tools ?? [])],
30
+ tools: capsule.config.tools ?? [],
31
31
  store,
32
32
  runId: run.id,
33
33
  conversationId: conversation.id,
34
34
  env,
35
+ secretValues,
35
36
  signal: options.signal,
36
37
  clock: buildRuntimeClockContext({
37
38
  timeZone: capsule.config.timeZone,
@@ -64,6 +65,7 @@ export async function runToolFromCwd(cwd: string, options: RunToolOptions): Prom
64
65
  status: "failed",
65
66
  errorCode: isAgentKitError(error) ? error.code : "runtime_error",
66
67
  errorMessage: error instanceof Error ? error.message : String(error),
68
+ secretValues,
67
69
  });
68
70
 
69
71
  throw error;
@@ -1,6 +1,8 @@
1
1
  import type { AgentTool, JsonSchema, ToolContext, ToolRenderResult, ToolRuntimeContext } from "../index";
2
- import type { SqliteAgentKitStore } from "../storage/sqlite";
2
+ import { redactSecrets, type SqliteAgentKitStore } from "../storage/sqlite";
3
3
  import { createLocalDatabaseRunner } from "./database";
4
+ import { collectSecretValues } from "./env";
5
+ import { knowledgeModelText } from "./knowledge/tool";
4
6
  import { AgentKitError } from "./errors";
5
7
  import type { RuntimeClockContext } from "./prompt-context";
6
8
 
@@ -16,6 +18,8 @@ export type ToolCallResult = {
16
18
  input: unknown;
17
19
  output: unknown;
18
20
  rendered?: ToolRenderResult;
21
+ // Redacted facts from built-in Knowledge only; never a user-facing renderer.
22
+ modelText?: string;
19
23
  };
20
24
 
21
25
  export type ToolRuntime = {
@@ -29,6 +33,7 @@ export type ToolRuntimeOptions = {
29
33
  runId: string;
30
34
  conversationId: string;
31
35
  env?: Record<string, string | undefined>;
36
+ secretValues?: string[];
32
37
  signal?: AbortSignal;
33
38
  defaultTimeoutMs?: number;
34
39
  runtime: ToolRuntimeContext;
@@ -63,7 +68,7 @@ async function executeToolCall(
63
68
  ): Promise<ToolCallResult> {
64
69
  let input: unknown = request.input ?? {};
65
70
  const tool = toolsByName.get(request.name);
66
- const preliminarySecretValues = collectConfiguredSecretValues(tool, options.env ?? process.env);
71
+ const preliminarySecretValues = collectSecretValues(tool, options.env ?? process.env);
67
72
  const toolCall = options.store.createToolCall({
68
73
  runId: options.runId,
69
74
  conversationId: options.conversationId,
@@ -112,6 +117,7 @@ async function executeToolCall(
112
117
  secretValues,
113
118
  });
114
119
 
120
+ const modelText = knowledgeModelText(tool, output);
115
121
  return {
116
122
  id: toolCall.id,
117
123
  name: tool.name,
@@ -119,9 +125,10 @@ async function executeToolCall(
119
125
  input,
120
126
  output,
121
127
  ...(rendered ? { rendered } : {}),
128
+ ...(modelText !== undefined ? { modelText: redactSecrets(modelText, options.secretValues ?? secretValues) } : {}),
122
129
  };
123
130
  } catch (error) {
124
- const secretValues = collectConfiguredSecretValues(tool, options.env ?? process.env);
131
+ const secretValues = collectSecretValues(tool, options.env ?? process.env);
125
132
 
126
133
  options.store.completeToolCall({
127
134
  toolCallId: toolCall.id,
@@ -287,17 +294,6 @@ function resolveToolSecrets(
287
294
  return { secrets, secretValues: Object.values(secrets) };
288
295
  }
289
296
 
290
- function collectConfiguredSecretValues(tool: AgentTool | undefined, env: Record<string, string | undefined>): string[] {
291
- if (!tool) {
292
- return [];
293
- }
294
-
295
- return (tool.secrets ?? []).flatMap((secretName) => {
296
- const value = env[secretName];
297
- return value ? [value] : [];
298
- });
299
- }
300
-
301
297
  async function runToolWithTimeout(
302
298
  tool: AgentTool,
303
299
  input: unknown,