@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.
- package/README.md +9 -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 +17 -0
- package/docs/guides/connect-whatsapp-zapster.md +16 -0
- package/docs/guides/create-agent.md +10 -1
- package/docs/guides/run-evals.md +36 -1
- package/docs/llms-full.txt +90 -1
- package/docs/llms.txt +9 -2
- package/package.json +2 -1
- package/src/cli/cloud-client.ts +10 -2
- package/src/cli/commands/channels.ts +67 -3
- package/src/cli/commands/knowledge.ts +136 -0
- package/src/cli/deploy-chat-ui.ts +7 -0
- package/src/cli/deploy-readiness.ts +19 -0
- package/src/cli/help.ts +26 -2
- package/src/cli/index.ts +140 -8
- 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 +142 -1
- package/src/providers/pi.ts +1 -1
- package/src/providers/test.ts +1 -1
- 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 +175 -0
- package/src/runtime/core/manifest.ts +37 -0
- package/src/runtime/database.ts +93 -2
- package/src/runtime/db-commands.ts +9 -0
- package/src/runtime/deploy-readiness.ts +12 -0
- package/src/runtime/dev-server.ts +201 -11
- package/src/runtime/evals.ts +210 -20
- package/src/runtime/inspect.ts +39 -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/spec.ts +152 -0
- package/src/runtime/sync.ts +144 -0
- package/src/runtime/targets/cloudflare/build.ts +514 -4
- package/src/runtime/tools.ts +121 -1
- package/src/runtime/traces.ts +41 -0
- package/src/storage/sqlite.ts +141 -0
- package/src/templates/blank.ts +16 -5
- package/src/templates/dentista.ts +17 -2
- package/src/templates/skills/agentkit-build-agent/SKILL.md +51 -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 +45 -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 +60 -0
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +22 -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 +15 -4
package/src/runtime/database.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import type { Dirent } from "node:fs";
|
|
3
|
+
import { basename, relative, resolve } from "node:path";
|
|
3
4
|
|
|
4
5
|
import type { DatabaseArgs, DatabaseResult, DatabaseRunner, DatabaseStatement } from "../index";
|
|
5
6
|
import { openCapsuleStore, type SqliteAgentKitStore } from "../storage/sqlite";
|
|
@@ -9,6 +10,12 @@ import { AgentKitError } from "./errors";
|
|
|
9
10
|
export type LocalDatabasePrepareResult = {
|
|
10
11
|
store: SqliteAgentKitStore;
|
|
11
12
|
databasePath: string;
|
|
13
|
+
migrations: {
|
|
14
|
+
configured: boolean;
|
|
15
|
+
path: string | null;
|
|
16
|
+
applied: string[];
|
|
17
|
+
skipped: string[];
|
|
18
|
+
};
|
|
12
19
|
schema: {
|
|
13
20
|
configured: boolean;
|
|
14
21
|
applied: boolean;
|
|
@@ -20,10 +27,12 @@ export async function openPreparedCapsuleStore(capsule: LoadedAgentCapsule): Pro
|
|
|
20
27
|
const store = await openCapsuleStore(capsule);
|
|
21
28
|
|
|
22
29
|
try {
|
|
30
|
+
const migrations = await applyCapsuleDatabaseMigrations(capsule, store);
|
|
23
31
|
const schema = await applyCapsuleDatabaseSchema(capsule, store);
|
|
24
32
|
return {
|
|
25
33
|
store,
|
|
26
34
|
databasePath: store.dbPath,
|
|
35
|
+
migrations,
|
|
27
36
|
schema,
|
|
28
37
|
};
|
|
29
38
|
} catch (error) {
|
|
@@ -32,6 +41,84 @@ export async function openPreparedCapsuleStore(capsule: LoadedAgentCapsule): Pro
|
|
|
32
41
|
}
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
export async function applyCapsuleDatabaseMigrations(
|
|
45
|
+
capsule: LoadedAgentCapsule,
|
|
46
|
+
store: SqliteAgentKitStore,
|
|
47
|
+
): Promise<LocalDatabasePrepareResult["migrations"]> {
|
|
48
|
+
const database = capsule.config.storage.database;
|
|
49
|
+
|
|
50
|
+
if (!database || database.driver !== "turso") {
|
|
51
|
+
return {
|
|
52
|
+
configured: false,
|
|
53
|
+
path: null,
|
|
54
|
+
applied: [],
|
|
55
|
+
skipped: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const migrationsPath = resolve(capsule.root, database.migrations ?? "migrations");
|
|
60
|
+
const migrations = await readMigrationFiles(migrationsPath);
|
|
61
|
+
|
|
62
|
+
if (migrations.length === 0) {
|
|
63
|
+
return {
|
|
64
|
+
configured: false,
|
|
65
|
+
path: migrationsPath,
|
|
66
|
+
applied: [],
|
|
67
|
+
skipped: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const result = store.applyApplicationMigrations(migrations);
|
|
73
|
+
return {
|
|
74
|
+
configured: true,
|
|
75
|
+
path: migrationsPath,
|
|
76
|
+
applied: result.applied,
|
|
77
|
+
skipped: result.skipped,
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error instanceof AgentKitError && error.code === "database_schema_error") {
|
|
81
|
+
throw new AgentKitError(
|
|
82
|
+
"database_schema_error",
|
|
83
|
+
[
|
|
84
|
+
`Could not apply database migrations from ${relative(capsule.root, migrationsPath)}.`,
|
|
85
|
+
"Migrations must be ordered .sql files and should use safe, forward-only changes.",
|
|
86
|
+
error.message,
|
|
87
|
+
].join(" "),
|
|
88
|
+
{ cause: error },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function readMigrationFiles(directory: string): Promise<Array<{ id: string; sql: string }>> {
|
|
97
|
+
let entries: Dirent[];
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const files = entries
|
|
110
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".sql"))
|
|
111
|
+
.map((entry) => entry.name)
|
|
112
|
+
.sort();
|
|
113
|
+
|
|
114
|
+
return Promise.all(
|
|
115
|
+
files.map(async (file) => ({
|
|
116
|
+
id: basename(file, ".sql"),
|
|
117
|
+
sql: await readFile(resolve(directory, file), "utf8"),
|
|
118
|
+
})),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
35
122
|
export async function applyCapsuleDatabaseSchema(
|
|
36
123
|
capsule: LoadedAgentCapsule,
|
|
37
124
|
store: SqliteAgentKitStore,
|
|
@@ -163,3 +250,7 @@ function validateStatements(statements: DatabaseStatement[]): void {
|
|
|
163
250
|
validateSql(statement.sql, statement.args ?? []);
|
|
164
251
|
}
|
|
165
252
|
}
|
|
253
|
+
|
|
254
|
+
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
255
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
256
|
+
}
|
|
@@ -10,6 +10,9 @@ import { AgentKitError } from "./errors";
|
|
|
10
10
|
export type DbMigrateResult = {
|
|
11
11
|
root: string;
|
|
12
12
|
databasePath: string;
|
|
13
|
+
migrationsPath: string | null;
|
|
14
|
+
migrationsApplied: string[];
|
|
15
|
+
migrationsSkipped: string[];
|
|
13
16
|
schemaPath: string | null;
|
|
14
17
|
schemaApplied: boolean;
|
|
15
18
|
};
|
|
@@ -43,6 +46,9 @@ export async function migrateLocalDatabase(cwd = process.cwd()): Promise<DbMigra
|
|
|
43
46
|
return {
|
|
44
47
|
root: capsule.root,
|
|
45
48
|
databasePath: prepared.databasePath,
|
|
49
|
+
migrationsPath: prepared.migrations.path,
|
|
50
|
+
migrationsApplied: prepared.migrations.applied,
|
|
51
|
+
migrationsSkipped: prepared.migrations.skipped,
|
|
46
52
|
schemaPath: prepared.schema.path,
|
|
47
53
|
schemaApplied: prepared.schema.applied,
|
|
48
54
|
};
|
|
@@ -87,6 +93,9 @@ export async function seedLocalDatabase(cwd = process.cwd(), seedFile = "seed.sq
|
|
|
87
93
|
return {
|
|
88
94
|
root: capsule.root,
|
|
89
95
|
databasePath: prepared.databasePath,
|
|
96
|
+
migrationsPath: prepared.migrations.path,
|
|
97
|
+
migrationsApplied: prepared.migrations.applied,
|
|
98
|
+
migrationsSkipped: prepared.migrations.skipped,
|
|
90
99
|
schemaPath: prepared.schema.path,
|
|
91
100
|
schemaApplied: prepared.schema.applied,
|
|
92
101
|
seedPath,
|
|
@@ -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,21 +143,26 @@ 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
|
+
};
|
|
150
|
+
const capsuleRoot = capsule.root;
|
|
128
151
|
return createHttpServer((incoming, outgoing) => {
|
|
129
|
-
void handleNodeRequest(
|
|
152
|
+
void handleNodeRequest(capsuleRoot, incoming, outgoing, channelRuntime);
|
|
130
153
|
});
|
|
131
154
|
}
|
|
132
155
|
|
|
133
156
|
async function handleNodeRequest(
|
|
134
|
-
|
|
157
|
+
capsuleRoot: string,
|
|
135
158
|
incoming: IncomingMessage,
|
|
136
159
|
outgoing: ServerResponse,
|
|
137
|
-
|
|
160
|
+
channelRuntime: DevChannelRuntimeState,
|
|
138
161
|
): Promise<void> {
|
|
139
162
|
try {
|
|
163
|
+
const capsule = await loadAgentCapsule(capsuleRoot);
|
|
140
164
|
const request = nodeRequestToFetchRequest(incoming);
|
|
141
|
-
const response = await handleDevServerRequest(capsule, request,
|
|
165
|
+
const response = await handleDevServerRequest(capsule, request, channelRuntime);
|
|
142
166
|
await writeFetchResponse(outgoing, response);
|
|
143
167
|
} catch (error) {
|
|
144
168
|
await writeFetchResponse(outgoing, jsonError(error, statusForError(error)));
|
|
@@ -197,7 +221,7 @@ async function writeFetchResponse(outgoing: ServerResponse, response: Response):
|
|
|
197
221
|
async function handleDevServerRequest(
|
|
198
222
|
capsule: LoadedAgentCapsule,
|
|
199
223
|
request: Request,
|
|
200
|
-
|
|
224
|
+
channelRuntime: DevChannelRuntimeState,
|
|
201
225
|
): Promise<Response> {
|
|
202
226
|
try {
|
|
203
227
|
const url = new URL(request.url);
|
|
@@ -257,7 +281,7 @@ async function handleDevServerRequest(
|
|
|
257
281
|
const channelMatch = route.match(/^\/channels\/([^/]+)\/(website|telegram|whatsapp)\/([^/]+)\/webhook$/);
|
|
258
282
|
|
|
259
283
|
if (channelMatch) {
|
|
260
|
-
return await handlePortableChannel(capsule, request,
|
|
284
|
+
return await handlePortableChannel(capsule, request, channelRuntime, {
|
|
261
285
|
name: decodeURIComponent(channelMatch[1]),
|
|
262
286
|
type: channelMatch[2] as ChannelType,
|
|
263
287
|
provider: decodeURIComponent(channelMatch[3]) as ChannelProvider,
|
|
@@ -274,6 +298,12 @@ async function handleDevServerRequest(
|
|
|
274
298
|
return jsonResponse(await getConversation(capsule, decodeURIComponent(conversationMatch[1])));
|
|
275
299
|
}
|
|
276
300
|
|
|
301
|
+
const conversationTraceMatch = route.match(/^\/v1\/conversations\/([^/]+)\/trace$/);
|
|
302
|
+
|
|
303
|
+
if (request.method === "GET" && conversationTraceMatch) {
|
|
304
|
+
return jsonResponse(await getConversationTrace(capsule, decodeURIComponent(conversationTraceMatch[1])));
|
|
305
|
+
}
|
|
306
|
+
|
|
277
307
|
return jsonError(new AgentKitError("not_found", `Route ${request.method} ${route} was not found.`), 404);
|
|
278
308
|
} catch (error) {
|
|
279
309
|
return jsonError(error, statusForError(error));
|
|
@@ -283,7 +313,7 @@ async function handleDevServerRequest(
|
|
|
283
313
|
async function handlePortableChannel(
|
|
284
314
|
capsule: LoadedAgentCapsule,
|
|
285
315
|
request: Request,
|
|
286
|
-
|
|
316
|
+
channelRuntime: DevChannelRuntimeState,
|
|
287
317
|
route: { name: string; type: ChannelType; provider: ChannelProvider },
|
|
288
318
|
): Promise<Response> {
|
|
289
319
|
const channel = (capsule.config.channels ?? []).find(
|
|
@@ -323,21 +353,45 @@ async function handlePortableChannel(
|
|
|
323
353
|
const deliveries = [];
|
|
324
354
|
|
|
325
355
|
for (const event of events) {
|
|
326
|
-
if (dedupeKeys.has(event.dedupeKey)) {
|
|
356
|
+
if (channelRuntime.dedupeKeys.has(event.dedupeKey)) {
|
|
327
357
|
deliveries.push({ state: "duplicate", dedupeKey: event.dedupeKey });
|
|
328
358
|
continue;
|
|
329
359
|
}
|
|
330
360
|
|
|
331
|
-
dedupeKeys.add(event.dedupeKey);
|
|
361
|
+
channelRuntime.dedupeKeys.add(event.dedupeKey);
|
|
332
362
|
|
|
333
363
|
if (event.kind !== "message" || !event.message) {
|
|
334
364
|
deliveries.push({ state: "skipped", dedupeKey: event.dedupeKey, unsupportedReason: event.unsupportedReason });
|
|
335
365
|
continue;
|
|
336
366
|
}
|
|
337
367
|
|
|
368
|
+
const conversationId = channelConversationId(channel.name, event.externalIdentity.key);
|
|
369
|
+
|
|
370
|
+
if (isChannelBufferEnabled(channel)) {
|
|
371
|
+
const buffer = resolveChannelBuffer(channel);
|
|
372
|
+
if (buffer.mode !== "debounce") {
|
|
373
|
+
throw new AgentKitError("runtime_error", "Unexpected channel buffer mode.");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
bufferDevChannelMessage(capsule, channelRuntime, {
|
|
377
|
+
bufferKey: `${channel.name}:${event.externalIdentity.key}`,
|
|
378
|
+
conversationId,
|
|
379
|
+
content: event.message.content,
|
|
380
|
+
receivedAt: event.receivedAt,
|
|
381
|
+
buffer,
|
|
382
|
+
});
|
|
383
|
+
deliveries.push({
|
|
384
|
+
state: "buffered",
|
|
385
|
+
dedupeKey: event.dedupeKey,
|
|
386
|
+
conversationId,
|
|
387
|
+
flushAfterMs: buffer.quietWindowMs,
|
|
388
|
+
});
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
338
392
|
const result = await runAgentMessage(capsule, {
|
|
339
393
|
message: event.message.content,
|
|
340
|
-
conversationId
|
|
394
|
+
conversationId,
|
|
341
395
|
signal: request.signal,
|
|
342
396
|
});
|
|
343
397
|
deliveries.push({
|
|
@@ -351,6 +405,109 @@ async function handlePortableChannel(
|
|
|
351
405
|
return jsonResponse({ deliveries });
|
|
352
406
|
}
|
|
353
407
|
|
|
408
|
+
function bufferDevChannelMessage(
|
|
409
|
+
capsule: LoadedAgentCapsule,
|
|
410
|
+
runtime: DevChannelRuntimeState,
|
|
411
|
+
input: {
|
|
412
|
+
bufferKey: string;
|
|
413
|
+
conversationId: string;
|
|
414
|
+
content: string;
|
|
415
|
+
receivedAt: string;
|
|
416
|
+
buffer: {
|
|
417
|
+
quietWindowMs: number;
|
|
418
|
+
maxWaitMs: number;
|
|
419
|
+
maxMessages: number;
|
|
420
|
+
maxChars: number;
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
): void {
|
|
424
|
+
const now = Date.now();
|
|
425
|
+
const existing = runtime.buffers.get(input.bufferKey);
|
|
426
|
+
const pending: DevChannelBuffer = existing ?? {
|
|
427
|
+
conversationId: input.conversationId,
|
|
428
|
+
messages: [],
|
|
429
|
+
firstReceivedMs: now,
|
|
430
|
+
lastReceivedMs: now,
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
if (existing?.timer) {
|
|
434
|
+
clearTimeout(existing.timer);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
pending.messages.push({
|
|
438
|
+
content: input.content,
|
|
439
|
+
receivedAt: input.receivedAt,
|
|
440
|
+
});
|
|
441
|
+
pending.lastReceivedMs = now;
|
|
442
|
+
runtime.buffers.set(input.bufferKey, pending);
|
|
443
|
+
|
|
444
|
+
if (shouldFlushDevChannelBuffer(pending, input.buffer)) {
|
|
445
|
+
void flushDevChannelBuffer(capsule, runtime, input.bufferKey);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const elapsedMs = now - pending.firstReceivedMs;
|
|
450
|
+
const delayMs = Math.max(0, Math.min(input.buffer.quietWindowMs, input.buffer.maxWaitMs - elapsedMs));
|
|
451
|
+
pending.timer = setTimeout(() => {
|
|
452
|
+
void flushDevChannelBuffer(capsule, runtime, input.bufferKey);
|
|
453
|
+
}, delayMs);
|
|
454
|
+
unrefTimer(pending.timer);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function shouldFlushDevChannelBuffer(
|
|
458
|
+
pending: DevChannelBuffer,
|
|
459
|
+
buffer: {
|
|
460
|
+
maxWaitMs: number;
|
|
461
|
+
maxMessages: number;
|
|
462
|
+
maxChars: number;
|
|
463
|
+
},
|
|
464
|
+
): boolean {
|
|
465
|
+
const totalChars = pending.messages.reduce((sum, message) => sum + message.content.length, 0);
|
|
466
|
+
return (
|
|
467
|
+
pending.messages.length >= buffer.maxMessages ||
|
|
468
|
+
totalChars >= buffer.maxChars ||
|
|
469
|
+
Date.now() - pending.firstReceivedMs >= buffer.maxWaitMs
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function flushDevChannelBuffer(
|
|
474
|
+
capsule: LoadedAgentCapsule,
|
|
475
|
+
runtime: DevChannelRuntimeState,
|
|
476
|
+
bufferKey: string,
|
|
477
|
+
): Promise<void> {
|
|
478
|
+
const pending = runtime.buffers.get(bufferKey);
|
|
479
|
+
|
|
480
|
+
if (!pending) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
runtime.buffers.delete(bufferKey);
|
|
485
|
+
|
|
486
|
+
if (pending.timer) {
|
|
487
|
+
clearTimeout(pending.timer);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const message = renderBufferedChannelMessage(pending.messages);
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
await runAgentMessage(capsule, {
|
|
494
|
+
message,
|
|
495
|
+
conversationId: pending.conversationId,
|
|
496
|
+
runtime: {
|
|
497
|
+
invocation: "channel",
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
} catch {
|
|
501
|
+
// Local channel buffering is best-effort; hosted delivery logs own production failures.
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function unrefTimer(timer: ReturnType<typeof setTimeout>): void {
|
|
506
|
+
if (typeof timer === "object" && timer && "unref" in timer && typeof timer.unref === "function") {
|
|
507
|
+
timer.unref();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
354
511
|
async function handleLocalFile(capsule: LoadedAgentCapsule, request: Request, rawKey: string): Promise<Response> {
|
|
355
512
|
const key = normalizeFileKey(rawKey);
|
|
356
513
|
const root = resolve(capsule.root, ".agentkit/files");
|
|
@@ -421,6 +578,32 @@ async function getConversation(capsule: LoadedAgentCapsule, conversationId: stri
|
|
|
421
578
|
}
|
|
422
579
|
}
|
|
423
580
|
|
|
581
|
+
async function getConversationTrace(capsule: LoadedAgentCapsule, conversationId: string) {
|
|
582
|
+
if (!conversationId) {
|
|
583
|
+
throw new AgentKitError("validation_error", "Missing conversation id.");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const store = await openCapsuleStore(capsule);
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
const conversation = store.getConversation(conversationId);
|
|
590
|
+
|
|
591
|
+
if (!conversation) {
|
|
592
|
+
throw new AgentKitError("conversation_not_found", `Conversation "${conversationId}" was not found.`);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return {
|
|
596
|
+
...conversation,
|
|
597
|
+
runs: store.listRunsForConversation(conversation.id).map((run) => ({
|
|
598
|
+
...run,
|
|
599
|
+
toolCalls: store.listToolCallsForRun(run.id),
|
|
600
|
+
})),
|
|
601
|
+
};
|
|
602
|
+
} finally {
|
|
603
|
+
store.close();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
424
607
|
async function readJsonBody(request: Request): Promise<unknown> {
|
|
425
608
|
const contentType = request.headers.get("content-type") ?? "";
|
|
426
609
|
|
|
@@ -917,6 +1100,13 @@ function renderChatApp(capsule: LoadedAgentCapsule, baseUrl: string, env: Record
|
|
|
917
1100
|
messages.scrollTop = messages.scrollHeight;
|
|
918
1101
|
}
|
|
919
1102
|
|
|
1103
|
+
textarea.addEventListener("keydown", (event) => {
|
|
1104
|
+
if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
|
|
1105
|
+
event.preventDefault();
|
|
1106
|
+
if (button.disabled) return;
|
|
1107
|
+
form.requestSubmit();
|
|
1108
|
+
});
|
|
1109
|
+
|
|
920
1110
|
form.addEventListener("submit", async (event) => {
|
|
921
1111
|
event.preventDefault();
|
|
922
1112
|
const content = textarea.value.trim();
|