@andreprado/agentkit 0.1.0-alpha.4 → 0.1.0-alpha.6
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 +5 -0
- package/docs/guides/add-channel.md +25 -0
- package/docs/guides/add-knowledge.md +134 -0
- package/docs/guides/agentkit-skills-architecture.md +471 -0
- package/docs/guides/channels-production-handoff.md +2 -0
- package/docs/guides/connect-telegram.md +15 -0
- package/docs/guides/connect-whatsapp-zapster.md +16 -0
- package/docs/guides/create-agent.md +1 -1
- package/docs/llms-full.txt +90 -1
- package/docs/llms.txt +9 -2
- package/package.json +2 -1
- package/src/cli/args.ts +36 -0
- package/src/cli/cloud-client.ts +257 -0
- package/src/cli/commands/channels.ts +459 -0
- package/src/cli/commands/knowledge.ts +136 -0
- package/src/cli/constants.ts +4 -0
- package/src/cli/deploy-chat-ui.ts +385 -0
- package/src/cli/deploy-readiness.ts +348 -0
- package/src/cli/flags.ts +162 -0
- package/src/cli/help.ts +166 -0
- package/src/cli/index.ts +219 -1912
- package/src/cli/process.ts +31 -0
- package/src/cloud/artifact.ts +92 -1
- package/src/cloud/contracts.ts +16 -0
- package/src/create-project.ts +38 -6
- package/src/index.ts +98 -0
- package/src/runtime/channel-buffer.ts +30 -0
- package/src/runtime/channels.ts +1 -0
- package/src/runtime/chat.ts +21 -2
- package/src/runtime/config.ts +167 -0
- package/src/runtime/core/manifest.ts +37 -0
- package/src/runtime/deploy-readiness.ts +12 -0
- package/src/runtime/dev-server.ts +159 -10
- package/src/runtime/inspect.ts +34 -0
- package/src/runtime/knowledge/chunk.ts +333 -0
- package/src/runtime/knowledge/config.ts +135 -0
- package/src/runtime/knowledge/embeddings.ts +133 -0
- package/src/runtime/knowledge/ingest.ts +521 -0
- package/src/runtime/knowledge/prompt-policy.ts +30 -0
- package/src/runtime/knowledge/retrieve.ts +283 -0
- package/src/runtime/knowledge/schema.ts +56 -0
- package/src/runtime/knowledge/tool.ts +64 -0
- package/src/runtime/knowledge/vector.ts +258 -0
- package/src/runtime/targets/cloudflare/build.ts +469 -4
- package/src/storage/sqlite.ts +5 -0
- package/src/templates/blank.ts +8 -4
- package/src/templates/dentista.ts +3 -1
- package/src/templates/skills/agentkit-build-agent/SKILL.md +49 -0
- package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +20 -0
- package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
- package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
- package/src/templates/skills/agentkit-capsule/SKILL.md +62 -0
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
- package/src/templates/skills/agentkit-channels/SKILL.md +62 -0
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +58 -0
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +37 -0
- package/src/templates/skills/agentkit-channels/references/telegram.md +37 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +37 -0
- package/src/templates/skills/agentkit-database/SKILL.md +42 -0
- package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
- package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
- package/src/templates/skills/agentkit-deploy/SKILL.md +44 -0
- package/src/templates/skills/agentkit-evals/SKILL.md +31 -0
- package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +14 -0
- package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +14 -0
- package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +18 -0
- package/src/templates/skills/agentkit-knowledge/SKILL.md +40 -0
- package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
- package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
- package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
- package/src/templates/skills/agentkit-prompts/SKILL.md +45 -0
- package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
- package/src/templates/skills/agentkit-provider/SKILL.md +57 -0
- package/src/templates/skills/agentkit-security/SKILL.md +55 -0
- package/src/templates/skills/agentkit-tools/SKILL.md +36 -0
- package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
- package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
- package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +52 -0
- package/src/templates/support.ts +8 -4
package/src/runtime/config.ts
CHANGED
|
@@ -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)) {
|
|
@@ -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,
|
|
@@ -24,6 +24,12 @@ export type DeployReadinessContext = {
|
|
|
24
24
|
userManagedSecrets: string[];
|
|
25
25
|
agentKitManagedSecrets: string[];
|
|
26
26
|
localSecrets: Record<string, DeployReadinessSecretSource>;
|
|
27
|
+
knowledge: {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
sourceCount: number;
|
|
30
|
+
embeddingProvider: string;
|
|
31
|
+
databaseDriver: "none" | "turso";
|
|
32
|
+
};
|
|
27
33
|
};
|
|
28
34
|
|
|
29
35
|
export async function loadDeployReadinessContext(
|
|
@@ -70,6 +76,12 @@ export async function loadDeployReadinessContext(
|
|
|
70
76
|
userManagedSecrets: requiredSecrets.filter((name) => !managed.has(name)),
|
|
71
77
|
agentKitManagedSecrets,
|
|
72
78
|
localSecrets,
|
|
79
|
+
knowledge: {
|
|
80
|
+
enabled: manifest.knowledge.enabled,
|
|
81
|
+
sourceCount: manifest.knowledge.sources.length,
|
|
82
|
+
embeddingProvider: manifest.knowledge.embedding.provider,
|
|
83
|
+
databaseDriver: manifest.storage.database.driver,
|
|
84
|
+
},
|
|
73
85
|
};
|
|
74
86
|
}
|
|
75
87
|
|
|
@@ -13,10 +13,12 @@ import { websiteChannelAdapter } from "./channels/website";
|
|
|
13
13
|
import { metaWhatsappChannelAdapter } from "./channels/whatsapp-meta";
|
|
14
14
|
import { zapsterWhatsappChannelAdapter } from "./channels/whatsapp-zapster";
|
|
15
15
|
import { runAgentMessage } from "./chat";
|
|
16
|
+
import { isChannelBufferEnabled, renderBufferedChannelMessage, resolveChannelBuffer } from "./channel-buffer";
|
|
16
17
|
import { type LoadedAgentCapsule, loadAgentCapsule } from "./config";
|
|
17
18
|
import { AgentKitError, isAgentKitError } from "./errors";
|
|
18
19
|
import { loadCapsuleEnv } from "./env";
|
|
19
20
|
import { buildInspectState } from "./inspect";
|
|
21
|
+
import { syncConfiguredKnowledgeSources } from "./knowledge/ingest";
|
|
20
22
|
import { runToolFromCwd } from "./tool-runner";
|
|
21
23
|
|
|
22
24
|
export type AgentDevServerOptions = {
|
|
@@ -47,11 +49,28 @@ export type AgentDevServer = {
|
|
|
47
49
|
const DEFAULT_PORT = 4123;
|
|
48
50
|
const DEFAULT_HOSTNAME = "localhost";
|
|
49
51
|
|
|
52
|
+
type DevChannelRuntimeState = {
|
|
53
|
+
dedupeKeys: Set<string>;
|
|
54
|
+
buffers: Map<string, DevChannelBuffer>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type DevChannelBuffer = {
|
|
58
|
+
conversationId: string;
|
|
59
|
+
messages: Array<{
|
|
60
|
+
content: string;
|
|
61
|
+
receivedAt: string;
|
|
62
|
+
}>;
|
|
63
|
+
firstReceivedMs: number;
|
|
64
|
+
lastReceivedMs: number;
|
|
65
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
66
|
+
};
|
|
67
|
+
|
|
50
68
|
export async function startAgentDevServer(
|
|
51
69
|
cwd = process.cwd(),
|
|
52
70
|
options: AgentDevServerOptions = {},
|
|
53
71
|
): Promise<AgentDevServer> {
|
|
54
72
|
const capsule = await loadAgentCapsule(cwd);
|
|
73
|
+
await syncConfiguredKnowledgeSources(capsule);
|
|
55
74
|
const hostname = options.hostname ?? DEFAULT_HOSTNAME;
|
|
56
75
|
const requestedPort = options.port ?? options.basePort ?? DEFAULT_PORT;
|
|
57
76
|
const autoPort = options.port === undefined;
|
|
@@ -124,9 +143,12 @@ async function listenForDevServer(
|
|
|
124
143
|
}
|
|
125
144
|
|
|
126
145
|
function createDevHttpServer(capsule: LoadedAgentCapsule): Server {
|
|
127
|
-
const
|
|
146
|
+
const channelRuntime: DevChannelRuntimeState = {
|
|
147
|
+
dedupeKeys: new Set<string>(),
|
|
148
|
+
buffers: new Map<string, DevChannelBuffer>(),
|
|
149
|
+
};
|
|
128
150
|
return createHttpServer((incoming, outgoing) => {
|
|
129
|
-
void handleNodeRequest(capsule, incoming, outgoing,
|
|
151
|
+
void handleNodeRequest(capsule, incoming, outgoing, channelRuntime);
|
|
130
152
|
});
|
|
131
153
|
}
|
|
132
154
|
|
|
@@ -134,11 +156,11 @@ async function handleNodeRequest(
|
|
|
134
156
|
capsule: LoadedAgentCapsule,
|
|
135
157
|
incoming: IncomingMessage,
|
|
136
158
|
outgoing: ServerResponse,
|
|
137
|
-
|
|
159
|
+
channelRuntime: DevChannelRuntimeState,
|
|
138
160
|
): Promise<void> {
|
|
139
161
|
try {
|
|
140
162
|
const request = nodeRequestToFetchRequest(incoming);
|
|
141
|
-
const response = await handleDevServerRequest(capsule, request,
|
|
163
|
+
const response = await handleDevServerRequest(capsule, request, channelRuntime);
|
|
142
164
|
await writeFetchResponse(outgoing, response);
|
|
143
165
|
} catch (error) {
|
|
144
166
|
await writeFetchResponse(outgoing, jsonError(error, statusForError(error)));
|
|
@@ -197,7 +219,7 @@ async function writeFetchResponse(outgoing: ServerResponse, response: Response):
|
|
|
197
219
|
async function handleDevServerRequest(
|
|
198
220
|
capsule: LoadedAgentCapsule,
|
|
199
221
|
request: Request,
|
|
200
|
-
|
|
222
|
+
channelRuntime: DevChannelRuntimeState,
|
|
201
223
|
): Promise<Response> {
|
|
202
224
|
try {
|
|
203
225
|
const url = new URL(request.url);
|
|
@@ -257,7 +279,7 @@ async function handleDevServerRequest(
|
|
|
257
279
|
const channelMatch = route.match(/^\/channels\/([^/]+)\/(website|telegram|whatsapp)\/([^/]+)\/webhook$/);
|
|
258
280
|
|
|
259
281
|
if (channelMatch) {
|
|
260
|
-
return await handlePortableChannel(capsule, request,
|
|
282
|
+
return await handlePortableChannel(capsule, request, channelRuntime, {
|
|
261
283
|
name: decodeURIComponent(channelMatch[1]),
|
|
262
284
|
type: channelMatch[2] as ChannelType,
|
|
263
285
|
provider: decodeURIComponent(channelMatch[3]) as ChannelProvider,
|
|
@@ -283,7 +305,7 @@ async function handleDevServerRequest(
|
|
|
283
305
|
async function handlePortableChannel(
|
|
284
306
|
capsule: LoadedAgentCapsule,
|
|
285
307
|
request: Request,
|
|
286
|
-
|
|
308
|
+
channelRuntime: DevChannelRuntimeState,
|
|
287
309
|
route: { name: string; type: ChannelType; provider: ChannelProvider },
|
|
288
310
|
): Promise<Response> {
|
|
289
311
|
const channel = (capsule.config.channels ?? []).find(
|
|
@@ -323,21 +345,45 @@ async function handlePortableChannel(
|
|
|
323
345
|
const deliveries = [];
|
|
324
346
|
|
|
325
347
|
for (const event of events) {
|
|
326
|
-
if (dedupeKeys.has(event.dedupeKey)) {
|
|
348
|
+
if (channelRuntime.dedupeKeys.has(event.dedupeKey)) {
|
|
327
349
|
deliveries.push({ state: "duplicate", dedupeKey: event.dedupeKey });
|
|
328
350
|
continue;
|
|
329
351
|
}
|
|
330
352
|
|
|
331
|
-
dedupeKeys.add(event.dedupeKey);
|
|
353
|
+
channelRuntime.dedupeKeys.add(event.dedupeKey);
|
|
332
354
|
|
|
333
355
|
if (event.kind !== "message" || !event.message) {
|
|
334
356
|
deliveries.push({ state: "skipped", dedupeKey: event.dedupeKey, unsupportedReason: event.unsupportedReason });
|
|
335
357
|
continue;
|
|
336
358
|
}
|
|
337
359
|
|
|
360
|
+
const conversationId = channelConversationId(channel.name, event.externalIdentity.key);
|
|
361
|
+
|
|
362
|
+
if (isChannelBufferEnabled(channel)) {
|
|
363
|
+
const buffer = resolveChannelBuffer(channel);
|
|
364
|
+
if (buffer.mode !== "debounce") {
|
|
365
|
+
throw new AgentKitError("runtime_error", "Unexpected channel buffer mode.");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
bufferDevChannelMessage(capsule, channelRuntime, {
|
|
369
|
+
bufferKey: `${channel.name}:${event.externalIdentity.key}`,
|
|
370
|
+
conversationId,
|
|
371
|
+
content: event.message.content,
|
|
372
|
+
receivedAt: event.receivedAt,
|
|
373
|
+
buffer,
|
|
374
|
+
});
|
|
375
|
+
deliveries.push({
|
|
376
|
+
state: "buffered",
|
|
377
|
+
dedupeKey: event.dedupeKey,
|
|
378
|
+
conversationId,
|
|
379
|
+
flushAfterMs: buffer.quietWindowMs,
|
|
380
|
+
});
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
338
384
|
const result = await runAgentMessage(capsule, {
|
|
339
385
|
message: event.message.content,
|
|
340
|
-
conversationId
|
|
386
|
+
conversationId,
|
|
341
387
|
signal: request.signal,
|
|
342
388
|
});
|
|
343
389
|
deliveries.push({
|
|
@@ -351,6 +397,109 @@ async function handlePortableChannel(
|
|
|
351
397
|
return jsonResponse({ deliveries });
|
|
352
398
|
}
|
|
353
399
|
|
|
400
|
+
function bufferDevChannelMessage(
|
|
401
|
+
capsule: LoadedAgentCapsule,
|
|
402
|
+
runtime: DevChannelRuntimeState,
|
|
403
|
+
input: {
|
|
404
|
+
bufferKey: string;
|
|
405
|
+
conversationId: string;
|
|
406
|
+
content: string;
|
|
407
|
+
receivedAt: string;
|
|
408
|
+
buffer: {
|
|
409
|
+
quietWindowMs: number;
|
|
410
|
+
maxWaitMs: number;
|
|
411
|
+
maxMessages: number;
|
|
412
|
+
maxChars: number;
|
|
413
|
+
};
|
|
414
|
+
},
|
|
415
|
+
): void {
|
|
416
|
+
const now = Date.now();
|
|
417
|
+
const existing = runtime.buffers.get(input.bufferKey);
|
|
418
|
+
const pending: DevChannelBuffer = existing ?? {
|
|
419
|
+
conversationId: input.conversationId,
|
|
420
|
+
messages: [],
|
|
421
|
+
firstReceivedMs: now,
|
|
422
|
+
lastReceivedMs: now,
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
if (existing?.timer) {
|
|
426
|
+
clearTimeout(existing.timer);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
pending.messages.push({
|
|
430
|
+
content: input.content,
|
|
431
|
+
receivedAt: input.receivedAt,
|
|
432
|
+
});
|
|
433
|
+
pending.lastReceivedMs = now;
|
|
434
|
+
runtime.buffers.set(input.bufferKey, pending);
|
|
435
|
+
|
|
436
|
+
if (shouldFlushDevChannelBuffer(pending, input.buffer)) {
|
|
437
|
+
void flushDevChannelBuffer(capsule, runtime, input.bufferKey);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const elapsedMs = now - pending.firstReceivedMs;
|
|
442
|
+
const delayMs = Math.max(0, Math.min(input.buffer.quietWindowMs, input.buffer.maxWaitMs - elapsedMs));
|
|
443
|
+
pending.timer = setTimeout(() => {
|
|
444
|
+
void flushDevChannelBuffer(capsule, runtime, input.bufferKey);
|
|
445
|
+
}, delayMs);
|
|
446
|
+
unrefTimer(pending.timer);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function shouldFlushDevChannelBuffer(
|
|
450
|
+
pending: DevChannelBuffer,
|
|
451
|
+
buffer: {
|
|
452
|
+
maxWaitMs: number;
|
|
453
|
+
maxMessages: number;
|
|
454
|
+
maxChars: number;
|
|
455
|
+
},
|
|
456
|
+
): boolean {
|
|
457
|
+
const totalChars = pending.messages.reduce((sum, message) => sum + message.content.length, 0);
|
|
458
|
+
return (
|
|
459
|
+
pending.messages.length >= buffer.maxMessages ||
|
|
460
|
+
totalChars >= buffer.maxChars ||
|
|
461
|
+
Date.now() - pending.firstReceivedMs >= buffer.maxWaitMs
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async function flushDevChannelBuffer(
|
|
466
|
+
capsule: LoadedAgentCapsule,
|
|
467
|
+
runtime: DevChannelRuntimeState,
|
|
468
|
+
bufferKey: string,
|
|
469
|
+
): Promise<void> {
|
|
470
|
+
const pending = runtime.buffers.get(bufferKey);
|
|
471
|
+
|
|
472
|
+
if (!pending) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
runtime.buffers.delete(bufferKey);
|
|
477
|
+
|
|
478
|
+
if (pending.timer) {
|
|
479
|
+
clearTimeout(pending.timer);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const message = renderBufferedChannelMessage(pending.messages);
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
await runAgentMessage(capsule, {
|
|
486
|
+
message,
|
|
487
|
+
conversationId: pending.conversationId,
|
|
488
|
+
runtime: {
|
|
489
|
+
invocation: "channel",
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
} catch {
|
|
493
|
+
// Local channel buffering is best-effort; hosted delivery logs own production failures.
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function unrefTimer(timer: ReturnType<typeof setTimeout>): void {
|
|
498
|
+
if (typeof timer === "object" && timer && "unref" in timer && typeof timer.unref === "function") {
|
|
499
|
+
timer.unref();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
354
503
|
async function handleLocalFile(capsule: LoadedAgentCapsule, request: Request, rawKey: string): Promise<Response> {
|
|
355
504
|
const key = normalizeFileKey(rawKey);
|
|
356
505
|
const root = resolve(capsule.root, ".agentkit/files");
|
package/src/runtime/inspect.ts
CHANGED
|
@@ -4,6 +4,8 @@ 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 { resolveKnowledgeConfig } from "./knowledge/config";
|
|
8
|
+
import { KNOWLEDGE_SEARCH_TOOL_NAME } from "./knowledge/tool";
|
|
7
9
|
|
|
8
10
|
export type SecretState = "set" | "missing";
|
|
9
11
|
|
|
@@ -14,6 +16,20 @@ export type AgentInspectState = {
|
|
|
14
16
|
prompt: string;
|
|
15
17
|
tools: string[];
|
|
16
18
|
channels: AgentInspectChannel[];
|
|
19
|
+
knowledge: {
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
sources: string[];
|
|
22
|
+
embedding: {
|
|
23
|
+
provider: string;
|
|
24
|
+
model: string | null;
|
|
25
|
+
dimensions: number | null;
|
|
26
|
+
};
|
|
27
|
+
retrieval: {
|
|
28
|
+
topK: number;
|
|
29
|
+
hybrid: boolean;
|
|
30
|
+
};
|
|
31
|
+
internalTool: string | null;
|
|
32
|
+
};
|
|
17
33
|
access: {
|
|
18
34
|
mode: AccessMode;
|
|
19
35
|
};
|
|
@@ -56,6 +72,7 @@ export type AgentInspectChannel = {
|
|
|
56
72
|
type: AgentChannel["type"];
|
|
57
73
|
provider: AgentChannel["provider"];
|
|
58
74
|
secrets: string[];
|
|
75
|
+
buffer?: AgentChannel["buffer"];
|
|
59
76
|
};
|
|
60
77
|
|
|
61
78
|
export async function inspectAgentCapsule(
|
|
@@ -71,6 +88,7 @@ export function buildInspectState(
|
|
|
71
88
|
env: Record<string, string | undefined> = process.env,
|
|
72
89
|
): AgentInspectState {
|
|
73
90
|
const declaredSecrets = new Set(capsule.config.secrets);
|
|
91
|
+
const knowledge = resolveKnowledgeConfig(capsule.config);
|
|
74
92
|
|
|
75
93
|
for (const tool of capsule.config.tools ?? []) {
|
|
76
94
|
for (const secret of tool.secrets ?? []) {
|
|
@@ -84,6 +102,10 @@ export function buildInspectState(
|
|
|
84
102
|
}
|
|
85
103
|
}
|
|
86
104
|
|
|
105
|
+
if (knowledge.embedding.secret) {
|
|
106
|
+
declaredSecrets.add(knowledge.embedding.secret);
|
|
107
|
+
}
|
|
108
|
+
|
|
87
109
|
const managedSecrets = managedCloudSecrets(capsule);
|
|
88
110
|
const userSecrets = Array.from(declaredSecrets).sort();
|
|
89
111
|
const secrets: Record<string, SecretState> = {};
|
|
@@ -105,7 +127,19 @@ export function buildInspectState(
|
|
|
105
127
|
type: channel.type,
|
|
106
128
|
provider: channel.provider,
|
|
107
129
|
secrets: [...channel.secrets],
|
|
130
|
+
...(channel.buffer ? { buffer: channel.buffer } : {}),
|
|
108
131
|
})),
|
|
132
|
+
knowledge: {
|
|
133
|
+
enabled: knowledge.enabled,
|
|
134
|
+
sources: knowledge.sources.map((source) => source.value),
|
|
135
|
+
embedding: {
|
|
136
|
+
provider: knowledge.embedding.provider,
|
|
137
|
+
model: knowledge.embedding.model,
|
|
138
|
+
dimensions: knowledge.embedding.dimensions,
|
|
139
|
+
},
|
|
140
|
+
retrieval: knowledge.retrieval,
|
|
141
|
+
internalTool: knowledge.enabled ? KNOWLEDGE_SEARCH_TOOL_NAME : null,
|
|
142
|
+
},
|
|
109
143
|
access: capsule.config.access,
|
|
110
144
|
storage: capsule.storagePath ? toCapsulePath(capsule.root, capsule.storagePath) : null,
|
|
111
145
|
database,
|