@andreprado/agentkit 0.1.0-alpha.19 → 0.1.0-alpha.20
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/docs/guides/add-channel.md +5 -3
- package/docs/guides/channel-security.md +13 -1
- package/docs/guides/connect-slack.md +126 -0
- package/docs/guides/debug-channel.md +1 -1
- package/docs/guides/security-rules.md +1 -0
- package/docs/guides/send-feedback.md +135 -0
- package/docs/llms-full.txt +11 -3
- package/docs/llms.txt +5 -0
- package/package.json +1 -1
- package/src/cli/cloud-client.ts +12 -0
- package/src/cli/commands/channels.ts +86 -9
- package/src/cli/commands/feedback.ts +438 -0
- package/src/cli/help.ts +10 -2
- package/src/cli/index.ts +7 -0
- package/src/index.ts +37 -3
- package/src/providers/pi.ts +7 -0
- package/src/providers/types.ts +7 -0
- package/src/runtime/channel-test-harness.ts +3 -1
- package/src/runtime/channels/discord.ts +9 -0
- package/src/runtime/channels/slack.ts +646 -0
- package/src/runtime/channels/whatsapp-meta.ts +9 -0
- package/src/runtime/channels.ts +13 -0
- package/src/runtime/config.ts +20 -3
- package/src/runtime/core/targets.ts +5 -5
- package/src/runtime/dev-server.ts +30 -7
- package/src/runtime/targets/cloudflare/build.ts +203 -10
- package/src/templates/skills/agentkit-capsule/SKILL.md +1 -1
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -1
- package/src/templates/skills/agentkit-channels/SKILL.md +3 -1
- package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +14 -0
package/src/runtime/config.ts
CHANGED
|
@@ -30,8 +30,8 @@ const AGENT_RUNTIMES = new Set<AgentRuntime>(["local", "edge"]);
|
|
|
30
30
|
const PROVIDERS = new Set<AgentProviderName>(["test", "openai", "anthropic", "openrouter", "custom"]);
|
|
31
31
|
const ACCESS_MODES = new Set<AccessMode>(["private", "token", "public"]);
|
|
32
32
|
const TOOL_VISIBILITIES = new Set<ToolVisibility>(["user", "internal"]);
|
|
33
|
-
const CHANNEL_TYPES = new Set<ChannelType>(["website", "telegram", "whatsapp", "discord"]);
|
|
34
|
-
const CHANNEL_PROVIDERS = new Set<ChannelProvider>(["agentkit", "telegram", "zapster", "meta", "discord"]);
|
|
33
|
+
const CHANNEL_TYPES = new Set<ChannelType>(["website", "telegram", "whatsapp", "discord", "slack"]);
|
|
34
|
+
const CHANNEL_PROVIDERS = new Set<ChannelProvider>(["agentkit", "telegram", "zapster", "meta", "discord", "slack"]);
|
|
35
35
|
const CHANNEL_BUFFER_MODES = new Set<ChannelBufferMode>(["off", "debounce"]);
|
|
36
36
|
const CHANNEL_AUDIO_MODES = new Set<ChannelAudioMode>(["off", "reject", "transcribe"]);
|
|
37
37
|
const DISCORD_CHANNEL_MODES = new Set<DiscordChannelMode>(["interactions", "bot"]);
|
|
@@ -622,6 +622,11 @@ function validateChannel(value: unknown, index: number, hasTranscriptionConfig:
|
|
|
622
622
|
return;
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
+
if (value.type === "slack") {
|
|
626
|
+
validateSlackChannel(value, index);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
625
630
|
validateChannelAudio(value.audio, index, hasTranscriptionConfig);
|
|
626
631
|
|
|
627
632
|
if (value.type === "website") {
|
|
@@ -694,7 +699,7 @@ function validateTelegramChannel(value: Record<string, unknown>, index: number):
|
|
|
694
699
|
|
|
695
700
|
function validateWhatsappChannel(value: Record<string, unknown>, index: number): void {
|
|
696
701
|
if (value.type !== "whatsapp") {
|
|
697
|
-
throw invalidConfig(`channels[${index}].type must be "website", "telegram", "whatsapp", or "
|
|
702
|
+
throw invalidConfig(`channels[${index}].type must be "website", "telegram", "whatsapp", "discord", or "slack".`);
|
|
698
703
|
}
|
|
699
704
|
|
|
700
705
|
if (value.provider === "zapster") {
|
|
@@ -738,6 +743,18 @@ function validateDiscordChannel(value: Record<string, unknown>, index: number):
|
|
|
738
743
|
);
|
|
739
744
|
}
|
|
740
745
|
|
|
746
|
+
function validateSlackChannel(value: Record<string, unknown>, index: number): void {
|
|
747
|
+
if (value.provider !== "slack") {
|
|
748
|
+
throw invalidConfig(`channels[${index}].provider must be "slack" when type is "slack".`);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (value.audio !== undefined) {
|
|
752
|
+
throw invalidConfig(`channels[${index}].audio is not supported when type is "slack".`);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
expectRequiredSecrets(value.secrets, ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], `channels[${index}]`);
|
|
756
|
+
}
|
|
757
|
+
|
|
741
758
|
function validateChannelLimits(value: unknown, index: number): void {
|
|
742
759
|
if (value === undefined) {
|
|
743
760
|
return;
|
|
@@ -6,7 +6,7 @@ export type RuntimeTargetId = (typeof runtimeTargetIds)[number];
|
|
|
6
6
|
export type RuntimeTargetCapabilityState = "available" | "planned" | "unsupported";
|
|
7
7
|
export type RuntimeStorageCapability = "agentkit" | "durable-object-sqlite" | "local-sqlite" | "postgres";
|
|
8
8
|
export type RuntimeFilesCapability = "none" | "r2" | "local-disk" | "s3-compatible";
|
|
9
|
-
export type RuntimeChannelCapability = "website" | "telegram" | "whatsapp";
|
|
9
|
+
export type RuntimeChannelCapability = "website" | "telegram" | "whatsapp" | "discord" | "slack";
|
|
10
10
|
export type RuntimeProviderSupport = "pi-local" | "edge-compatible" | "server-env";
|
|
11
11
|
|
|
12
12
|
export type RuntimeTargetCapabilities = {
|
|
@@ -35,7 +35,7 @@ export const runtimeTargets: Readonly<Record<RuntimeTargetId, RuntimeTarget>> =
|
|
|
35
35
|
dev: "available",
|
|
36
36
|
storage: ["local-sqlite"],
|
|
37
37
|
files: ["local-disk"],
|
|
38
|
-
channels: ["website"],
|
|
38
|
+
channels: ["website", "slack"],
|
|
39
39
|
providers: ["pi-local"],
|
|
40
40
|
},
|
|
41
41
|
},
|
|
@@ -48,7 +48,7 @@ export const runtimeTargets: Readonly<Record<RuntimeTargetId, RuntimeTarget>> =
|
|
|
48
48
|
dev: "planned",
|
|
49
49
|
storage: ["agentkit", "durable-object-sqlite", "postgres"],
|
|
50
50
|
files: ["none", "r2"],
|
|
51
|
-
channels: ["website", "telegram", "whatsapp"],
|
|
51
|
+
channels: ["website", "telegram", "whatsapp", "discord", "slack"],
|
|
52
52
|
providers: ["edge-compatible"],
|
|
53
53
|
},
|
|
54
54
|
},
|
|
@@ -61,7 +61,7 @@ export const runtimeTargets: Readonly<Record<RuntimeTargetId, RuntimeTarget>> =
|
|
|
61
61
|
dev: "planned",
|
|
62
62
|
storage: ["local-sqlite", "postgres"],
|
|
63
63
|
files: ["none", "local-disk", "s3-compatible"],
|
|
64
|
-
channels: ["website", "telegram", "whatsapp"],
|
|
64
|
+
channels: ["website", "telegram", "whatsapp", "discord", "slack"],
|
|
65
65
|
providers: ["server-env"],
|
|
66
66
|
},
|
|
67
67
|
},
|
|
@@ -74,7 +74,7 @@ export const runtimeTargets: Readonly<Record<RuntimeTargetId, RuntimeTarget>> =
|
|
|
74
74
|
dev: "unsupported",
|
|
75
75
|
storage: ["local-sqlite", "postgres"],
|
|
76
76
|
files: ["none", "local-disk", "s3-compatible"],
|
|
77
|
-
channels: ["website", "telegram", "whatsapp"],
|
|
77
|
+
channels: ["website", "telegram", "whatsapp", "discord", "slack"],
|
|
78
78
|
providers: ["server-env"],
|
|
79
79
|
},
|
|
80
80
|
},
|
|
@@ -8,7 +8,8 @@ import { Readable } from "node:stream";
|
|
|
8
8
|
import { openCapsuleStore, type ConversationRecord } from "../storage/sqlite";
|
|
9
9
|
import type { AgentChannel, ChannelProvider, ChannelType } from "../index";
|
|
10
10
|
import type { ChannelAdapter, NormalizedChannelMessage, RawWebhookEvent } from "./channels";
|
|
11
|
-
import { discordChannelAdapter
|
|
11
|
+
import { discordChannelAdapter } from "./channels/discord";
|
|
12
|
+
import { slackChannelAdapter } from "./channels/slack";
|
|
12
13
|
import { telegramChannelAdapter } from "./channels/telegram";
|
|
13
14
|
import { websiteChannelAdapter } from "./channels/website";
|
|
14
15
|
import { metaWhatsappChannelAdapter } from "./channels/whatsapp-meta";
|
|
@@ -319,7 +320,7 @@ async function handleDevServerRequest(
|
|
|
319
320
|
return await handleLocalFile(capsule, request, decodeURIComponent(fileMatch[1]));
|
|
320
321
|
}
|
|
321
322
|
|
|
322
|
-
const channelMatch = route.match(/^\/channels\/([^/]+)\/(website|telegram|whatsapp|discord)\/([^/]+)\/webhook$/);
|
|
323
|
+
const channelMatch = route.match(/^\/channels\/([^/]+)\/(website|telegram|whatsapp|discord|slack)\/([^/]+)\/webhook$/);
|
|
323
324
|
|
|
324
325
|
if (channelMatch) {
|
|
325
326
|
return await handlePortableChannel(capsule, request, channelRuntime, {
|
|
@@ -504,12 +505,12 @@ async function handlePortableChannel(
|
|
|
504
505
|
throw new AgentKitError(verification.code, verification.message);
|
|
505
506
|
}
|
|
506
507
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
508
|
+
const webhookResponse = adapter.getWebhookResponse
|
|
509
|
+
? await adapter.getWebhookResponse({ ...rawEvent, channel, secrets })
|
|
510
|
+
: null;
|
|
510
511
|
|
|
511
|
-
if (
|
|
512
|
-
return
|
|
512
|
+
if (webhookResponse) {
|
|
513
|
+
return channelAdapterResponse(webhookResponse);
|
|
513
514
|
}
|
|
514
515
|
|
|
515
516
|
const events = await adapter.normalizeInbound(rawEvent);
|
|
@@ -1140,9 +1141,31 @@ function adapterForChannel(type: ChannelType, provider: ChannelProvider): Channe
|
|
|
1140
1141
|
return discordChannelAdapter;
|
|
1141
1142
|
}
|
|
1142
1143
|
|
|
1144
|
+
if (type === "slack" && provider === "slack") {
|
|
1145
|
+
return slackChannelAdapter;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1143
1148
|
throw new AgentKitError("channel_not_found", `No channel adapter for ${type}/${provider}.`);
|
|
1144
1149
|
}
|
|
1145
1150
|
|
|
1151
|
+
function channelAdapterResponse(response: {
|
|
1152
|
+
status?: number;
|
|
1153
|
+
headers?: Record<string, string>;
|
|
1154
|
+
body: string | Record<string, unknown>;
|
|
1155
|
+
}): Response {
|
|
1156
|
+
if (typeof response.body === "string") {
|
|
1157
|
+
return new Response(response.body, {
|
|
1158
|
+
status: response.status ?? 200,
|
|
1159
|
+
headers: response.headers,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
return jsonResponse(response.body, {
|
|
1164
|
+
status: response.status ?? 200,
|
|
1165
|
+
headers: response.headers,
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1146
1169
|
function discordLocalInteractionResponse(
|
|
1147
1170
|
deliveries: Array<{
|
|
1148
1171
|
state: string;
|
|
@@ -451,6 +451,17 @@ export function discordChannel(input) {
|
|
|
451
451
|
};
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
+
export function slackChannel(input) {
|
|
455
|
+
return {
|
|
456
|
+
type: "slack",
|
|
457
|
+
provider: "slack",
|
|
458
|
+
name: input.name,
|
|
459
|
+
secrets: normalizeChannelSecrets(input.secrets ?? ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], "slack channel"),
|
|
460
|
+
...(input.limits ? { limits: input.limits } : {}),
|
|
461
|
+
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "slack channel") } : {}),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
454
465
|
function defaultWhatsappChannelSecrets(provider) {
|
|
455
466
|
if (provider === "zapster") {
|
|
456
467
|
return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID"];
|
|
@@ -715,6 +726,10 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
715
726
|
status TEXT NOT NULL,
|
|
716
727
|
error_code TEXT,
|
|
717
728
|
error_message TEXT,
|
|
729
|
+
input_tokens INTEGER,
|
|
730
|
+
output_tokens INTEGER,
|
|
731
|
+
total_tokens INTEGER,
|
|
732
|
+
cost_json TEXT,
|
|
718
733
|
created_at TEXT NOT NULL,
|
|
719
734
|
completed_at TEXT
|
|
720
735
|
);
|
|
@@ -738,6 +753,18 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
738
753
|
} catch {
|
|
739
754
|
// Existing Durable Object tables either already have this column or do not need migration.
|
|
740
755
|
}
|
|
756
|
+
for (const statement of [
|
|
757
|
+
"ALTER TABLE runs ADD COLUMN input_tokens INTEGER",
|
|
758
|
+
"ALTER TABLE runs ADD COLUMN output_tokens INTEGER",
|
|
759
|
+
"ALTER TABLE runs ADD COLUMN total_tokens INTEGER",
|
|
760
|
+
"ALTER TABLE runs ADD COLUMN cost_json TEXT",
|
|
761
|
+
]) {
|
|
762
|
+
try {
|
|
763
|
+
this.sql.exec(statement);
|
|
764
|
+
} catch {
|
|
765
|
+
// Existing Durable Object tables either already have this column or do not need migration.
|
|
766
|
+
}
|
|
767
|
+
}
|
|
741
768
|
}
|
|
742
769
|
|
|
743
770
|
async fetch(request) {
|
|
@@ -835,7 +862,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
835
862
|
}
|
|
836
863
|
|
|
837
864
|
const runs = this.sql.exec(
|
|
838
|
-
"SELECT id, conversation_id, provider, model, status, error_code, error_message, created_at, completed_at FROM runs WHERE conversation_id = ? ORDER BY rowid ASC",
|
|
865
|
+
"SELECT id, conversation_id, provider, model, status, error_code, error_message, input_tokens, output_tokens, total_tokens, cost_json, created_at, completed_at FROM runs WHERE conversation_id = ? ORDER BY rowid ASC",
|
|
839
866
|
conversationId,
|
|
840
867
|
).toArray();
|
|
841
868
|
|
|
@@ -849,6 +876,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
849
876
|
status: String(run.status),
|
|
850
877
|
errorCode: run.error_code === null || run.error_code === undefined ? null : String(run.error_code),
|
|
851
878
|
errorMessage: run.error_message === null || run.error_message === undefined ? null : String(run.error_message),
|
|
879
|
+
usage: readStoredUsage(run),
|
|
852
880
|
startedAt: String(run.created_at),
|
|
853
881
|
completedAt: run.completed_at === null || run.completed_at === undefined ? null : String(run.completed_at),
|
|
854
882
|
toolCalls: this.readToolCalls(String(run.id)),
|
|
@@ -920,6 +948,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
920
948
|
const userMessageId = crypto.randomUUID();
|
|
921
949
|
const assistantMessageId = crypto.randomUUID();
|
|
922
950
|
const title = titleFromMessage(String(input.userMessage || ""));
|
|
951
|
+
const usage = normalizeRuntimeUsage(input.usage);
|
|
923
952
|
|
|
924
953
|
this.sql.exec(
|
|
925
954
|
"INSERT OR IGNORE INTO conversations (id, agent_name, title, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
@@ -939,7 +968,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
939
968
|
now,
|
|
940
969
|
);
|
|
941
970
|
this.sql.exec(
|
|
942
|
-
"INSERT INTO runs (id, conversation_id, provider, model, status, error_code, error_message, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
971
|
+
"INSERT INTO runs (id, conversation_id, provider, model, status, error_code, error_message, input_tokens, output_tokens, total_tokens, cost_json, created_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
943
972
|
runId,
|
|
944
973
|
conversationId,
|
|
945
974
|
manifest.provider.name,
|
|
@@ -947,6 +976,10 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
947
976
|
String(input.status || "completed"),
|
|
948
977
|
input.errorCode ? String(input.errorCode) : null,
|
|
949
978
|
input.errorMessage ? String(input.errorMessage) : null,
|
|
979
|
+
usage.inputTokens ?? null,
|
|
980
|
+
usage.outputTokens ?? null,
|
|
981
|
+
usage.totalTokens ?? null,
|
|
982
|
+
usage.cost && Object.keys(usage.cost).length > 0 ? JSON.stringify(usage.cost) : null,
|
|
950
983
|
now,
|
|
951
984
|
now,
|
|
952
985
|
);
|
|
@@ -990,7 +1023,7 @@ export class AgentKitConversationStore extends DurableObject {
|
|
|
990
1023
|
}
|
|
991
1024
|
|
|
992
1025
|
export default {
|
|
993
|
-
async fetch(request, env) {
|
|
1026
|
+
async fetch(request, env, ctx) {
|
|
994
1027
|
const url = new URL(request.url);
|
|
995
1028
|
|
|
996
1029
|
if (request.method === "GET" && url.pathname === "/_agentkit") {
|
|
@@ -1062,7 +1095,7 @@ export default {
|
|
|
1062
1095
|
}
|
|
1063
1096
|
|
|
1064
1097
|
if (request.method === "POST" && (url.pathname === "/chat" || url.pathname === "/v1/chat")) {
|
|
1065
|
-
return handleChat(request, env);
|
|
1098
|
+
return handleChat(request, env, ctx);
|
|
1066
1099
|
}
|
|
1067
1100
|
|
|
1068
1101
|
if (request.method === "GET" && (url.pathname === "/conversations" || url.pathname === "/v1/conversations")) {
|
|
@@ -1095,8 +1128,8 @@ export default {
|
|
|
1095
1128
|
|
|
1096
1129
|
function isHostedChannelWebhookPath(pathname) {
|
|
1097
1130
|
return (
|
|
1098
|
-
/^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta)|discord\\/discord)\\/webhook$/.test(pathname) ||
|
|
1099
|
-
/^\\/channels\\/[^/]+\\/(?:website|telegram|discord)\\/webhook$/.test(pathname) ||
|
|
1131
|
+
/^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta)|discord\\/discord|slack\\/slack)\\/webhook$/.test(pathname) ||
|
|
1132
|
+
/^\\/channels\\/[^/]+\\/(?:website|telegram|discord|slack)\\/webhook$/.test(pathname) ||
|
|
1100
1133
|
/^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta)\\/webhook$/.test(pathname)
|
|
1101
1134
|
);
|
|
1102
1135
|
}
|
|
@@ -1269,7 +1302,7 @@ async function handleTool(request, env, toolName) {
|
|
|
1269
1302
|
}
|
|
1270
1303
|
}
|
|
1271
1304
|
|
|
1272
|
-
async function handleChat(request, env) {
|
|
1305
|
+
async function handleChat(request, env, ctx) {
|
|
1273
1306
|
let body;
|
|
1274
1307
|
|
|
1275
1308
|
try {
|
|
@@ -1306,13 +1339,26 @@ async function handleChat(request, env) {
|
|
|
1306
1339
|
|
|
1307
1340
|
const previousMessages = await loadConversationMessages(env, conversationId).catch(() => []);
|
|
1308
1341
|
let content;
|
|
1342
|
+
let providerResult;
|
|
1309
1343
|
const toolCalls = [];
|
|
1310
1344
|
|
|
1311
1345
|
try {
|
|
1312
|
-
|
|
1346
|
+
providerResult = await callHostedProvider(env, [...previousMessages, { role: "user", content: message }], request.signal, toolCalls);
|
|
1313
1347
|
content = providerResult.content;
|
|
1314
1348
|
} catch (error) {
|
|
1315
|
-
await persistFailure(env, conversationId, message, error, toolCalls);
|
|
1349
|
+
const persisted = await persistFailure(env, conversationId, message, error, toolCalls);
|
|
1350
|
+
queueRuntimeUsage(ctx, sendRuntimeUsage(env, {
|
|
1351
|
+
kind: "chat_run",
|
|
1352
|
+
status: "failed",
|
|
1353
|
+
conversationId,
|
|
1354
|
+
runId: persisted?.runId,
|
|
1355
|
+
provider: manifest.provider.name,
|
|
1356
|
+
model: manifest.provider.model,
|
|
1357
|
+
metadata: {
|
|
1358
|
+
error_code: error && typeof error === "object" && "code" in error ? String(error.code) : "runtime_error",
|
|
1359
|
+
tool_calls: toolCalls.length,
|
|
1360
|
+
},
|
|
1361
|
+
}));
|
|
1316
1362
|
return hostedErrorResponse(error);
|
|
1317
1363
|
}
|
|
1318
1364
|
|
|
@@ -1320,8 +1366,21 @@ async function handleChat(request, env) {
|
|
|
1320
1366
|
conversationId,
|
|
1321
1367
|
userMessage: message,
|
|
1322
1368
|
assistantMessage: content,
|
|
1369
|
+
usage: providerResult.usage,
|
|
1323
1370
|
toolCalls,
|
|
1324
1371
|
});
|
|
1372
|
+
queueRuntimeUsage(ctx, sendRuntimeUsage(env, {
|
|
1373
|
+
kind: "chat_run",
|
|
1374
|
+
status: "succeeded",
|
|
1375
|
+
conversationId: persisted.conversationId ?? conversationId,
|
|
1376
|
+
runId: persisted.runId,
|
|
1377
|
+
provider: manifest.provider.name,
|
|
1378
|
+
model: manifest.provider.model,
|
|
1379
|
+
usage: providerResult.usage,
|
|
1380
|
+
metadata: {
|
|
1381
|
+
tool_calls: toolCalls.length,
|
|
1382
|
+
},
|
|
1383
|
+
}));
|
|
1325
1384
|
|
|
1326
1385
|
return Response.json({
|
|
1327
1386
|
message: {
|
|
@@ -1409,6 +1468,7 @@ async function callHostedProvider(env, messages, signal, toolCalls = []) {
|
|
|
1409
1468
|
|
|
1410
1469
|
return {
|
|
1411
1470
|
content: result.message.content,
|
|
1471
|
+
usage: result.usage,
|
|
1412
1472
|
};
|
|
1413
1473
|
}
|
|
1414
1474
|
|
|
@@ -1836,7 +1896,7 @@ async function hostedFakeEmbedding(input, dimensions) {
|
|
|
1836
1896
|
}
|
|
1837
1897
|
|
|
1838
1898
|
async function persistFailure(env, conversationId, userMessage, error, toolCalls = []) {
|
|
1839
|
-
|
|
1899
|
+
return persist(env, {
|
|
1840
1900
|
conversationId,
|
|
1841
1901
|
userMessage,
|
|
1842
1902
|
status: "failed",
|
|
@@ -2530,6 +2590,73 @@ async function persist(env, input) {
|
|
|
2530
2590
|
return response.json();
|
|
2531
2591
|
}
|
|
2532
2592
|
|
|
2593
|
+
function queueRuntimeUsage(ctx, promise) {
|
|
2594
|
+
const safePromise = promise.catch(() => undefined);
|
|
2595
|
+
|
|
2596
|
+
if (ctx && typeof ctx.waitUntil === "function") {
|
|
2597
|
+
ctx.waitUntil(safePromise);
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
void safePromise;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
async function sendRuntimeUsage(env, input) {
|
|
2605
|
+
if (!env.AGENTKIT_CLOUD_API_URL || !env.AGENTKIT_DEPLOY_ID || !env.AGENTKIT_RUNTIME_GATE_TOKEN) {
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
const usage = normalizeRuntimeUsage(input.usage);
|
|
2610
|
+
const metadata = isRecord(input.metadata) ? input.metadata : {};
|
|
2611
|
+
const body = {
|
|
2612
|
+
kind: input.kind || "chat_run",
|
|
2613
|
+
status: input.status || "succeeded",
|
|
2614
|
+
...(input.runId ? { run_id: String(input.runId) } : {}),
|
|
2615
|
+
...(input.conversationId ? { conversation_id: String(input.conversationId) } : {}),
|
|
2616
|
+
...(input.provider ? { provider: String(input.provider) } : {}),
|
|
2617
|
+
...(input.model ? { model: String(input.model) } : {}),
|
|
2618
|
+
...(usage.inputTokens !== undefined ? { input_tokens: usage.inputTokens } : {}),
|
|
2619
|
+
...(usage.outputTokens !== undefined ? { output_tokens: usage.outputTokens } : {}),
|
|
2620
|
+
...(usage.totalTokens !== undefined ? { total_tokens: usage.totalTokens } : {}),
|
|
2621
|
+
...(usage.cost && Object.keys(usage.cost).length > 0 ? { cost: usage.cost } : {}),
|
|
2622
|
+
...(usage.cost?.total !== undefined ? { cost_usd: usage.cost.total } : {}),
|
|
2623
|
+
metadata: sanitizeRuntimeUsageMetadata(metadata),
|
|
2624
|
+
};
|
|
2625
|
+
|
|
2626
|
+
const url = new URL(
|
|
2627
|
+
"/v1/runtime/deploys/" + encodeURIComponent(String(env.AGENTKIT_DEPLOY_ID)) + "/usage",
|
|
2628
|
+
env.AGENTKIT_CLOUD_API_URL,
|
|
2629
|
+
);
|
|
2630
|
+
await fetch(url.href, {
|
|
2631
|
+
method: "POST",
|
|
2632
|
+
headers: {
|
|
2633
|
+
Authorization: "Bearer " + env.AGENTKIT_RUNTIME_GATE_TOKEN,
|
|
2634
|
+
"Content-Type": "application/json",
|
|
2635
|
+
},
|
|
2636
|
+
body: JSON.stringify(body),
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
function sanitizeRuntimeUsageMetadata(value) {
|
|
2641
|
+
const metadata = {};
|
|
2642
|
+
|
|
2643
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
2644
|
+
if (!/^[A-Za-z0-9_.:-]{1,80}$/.test(key)) {
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
if (typeof raw === "string") {
|
|
2649
|
+
metadata[key] = raw.length > 160 ? raw.slice(0, 160) : raw;
|
|
2650
|
+
} else if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
2651
|
+
metadata[key] = raw;
|
|
2652
|
+
} else if (typeof raw === "boolean") {
|
|
2653
|
+
metadata[key] = raw;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
return metadata;
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2533
2660
|
async function loadConversationMessages(env, conversationId) {
|
|
2534
2661
|
if (!env.AGENTKIT_CONVERSATIONS || !conversationId) {
|
|
2535
2662
|
return [];
|
|
@@ -2863,6 +2990,68 @@ function titleFromMessage(content) {
|
|
|
2863
2990
|
return compact.length <= 80 ? compact : \`\${compact.slice(0, 77)}...\`;
|
|
2864
2991
|
}
|
|
2865
2992
|
|
|
2993
|
+
function readStoredUsage(row) {
|
|
2994
|
+
const usage = {};
|
|
2995
|
+
const inputTokens = normalizeRuntimeInteger(row.input_tokens);
|
|
2996
|
+
const outputTokens = normalizeRuntimeInteger(row.output_tokens);
|
|
2997
|
+
const totalTokens = normalizeRuntimeInteger(row.total_tokens);
|
|
2998
|
+
const cost = normalizeRuntimeCost(parseStoredJson(row.cost_json, {}));
|
|
2999
|
+
|
|
3000
|
+
if (inputTokens !== undefined) usage.inputTokens = inputTokens;
|
|
3001
|
+
if (outputTokens !== undefined) usage.outputTokens = outputTokens;
|
|
3002
|
+
if (totalTokens !== undefined) usage.totalTokens = totalTokens;
|
|
3003
|
+
if (Object.keys(cost).length > 0) usage.cost = cost;
|
|
3004
|
+
|
|
3005
|
+
return usage;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
function normalizeRuntimeUsage(value) {
|
|
3009
|
+
if (!isRecord(value)) {
|
|
3010
|
+
return {};
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
const usage = {};
|
|
3014
|
+
const inputTokens = normalizeRuntimeInteger(value.inputTokens ?? value.input_tokens);
|
|
3015
|
+
const outputTokens = normalizeRuntimeInteger(value.outputTokens ?? value.output_tokens);
|
|
3016
|
+
const totalTokens = normalizeRuntimeInteger(value.totalTokens ?? value.total_tokens);
|
|
3017
|
+
const cost = normalizeRuntimeCost(value.cost);
|
|
3018
|
+
|
|
3019
|
+
if (inputTokens !== undefined) usage.inputTokens = inputTokens;
|
|
3020
|
+
if (outputTokens !== undefined) usage.outputTokens = outputTokens;
|
|
3021
|
+
if (totalTokens !== undefined) usage.totalTokens = totalTokens;
|
|
3022
|
+
if (Object.keys(cost).length > 0) usage.cost = cost;
|
|
3023
|
+
|
|
3024
|
+
return usage;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
function normalizeRuntimeCost(value) {
|
|
3028
|
+
if (!isRecord(value)) {
|
|
3029
|
+
return {};
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
const cost = {};
|
|
3033
|
+
|
|
3034
|
+
for (const field of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
|
|
3035
|
+
const normalized = normalizeRuntimeNumber(value[field]);
|
|
3036
|
+
|
|
3037
|
+
if (normalized !== undefined) {
|
|
3038
|
+
cost[field] = normalized;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
return cost;
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
function normalizeRuntimeInteger(value) {
|
|
3046
|
+
const normalized = normalizeRuntimeNumber(value);
|
|
3047
|
+
return normalized !== undefined && Number.isInteger(normalized) ? normalized : undefined;
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
function normalizeRuntimeNumber(value) {
|
|
3051
|
+
const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
|
|
3052
|
+
return typeof parsed === "number" && Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
3053
|
+
}
|
|
3054
|
+
|
|
2866
3055
|
function parseStoredJson(value, fallback) {
|
|
2867
3056
|
if (typeof value !== "string") {
|
|
2868
3057
|
return fallback;
|
|
@@ -2944,6 +3133,10 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
2944
3133
|
status TEXT NOT NULL,
|
|
2945
3134
|
error_code TEXT,
|
|
2946
3135
|
error_message TEXT,
|
|
3136
|
+
input_tokens INTEGER,
|
|
3137
|
+
output_tokens INTEGER,
|
|
3138
|
+
total_tokens INTEGER,
|
|
3139
|
+
cost_json TEXT,
|
|
2947
3140
|
created_at TEXT NOT NULL,
|
|
2948
3141
|
completed_at TEXT
|
|
2949
3142
|
);
|
|
@@ -27,7 +27,7 @@ Use this first inside an AgentKit Agent Capsule.
|
|
|
27
27
|
- Improve from hosted or local production evidence: `skills/agentkit-improve/SKILL.md`
|
|
28
28
|
- Prepare hosted deploy: `skills/agentkit-deploy/SKILL.md`
|
|
29
29
|
- Work with secrets, external APIs, public access, channels, or real data: `skills/agentkit-security/SKILL.md`
|
|
30
|
-
- Add or debug website, Telegram, WhatsApp, or
|
|
30
|
+
- Add or debug website, Telegram, WhatsApp, Discord, or Slack channels: `skills/agentkit-channels/SKILL.md`
|
|
31
31
|
- Investigate command failures: `skills/agentkit-troubleshooting/SKILL.md`
|
|
32
32
|
|
|
33
33
|
For a compact docs map, read `references/docs-router.md`.
|
|
@@ -6,9 +6,10 @@ Prefer the narrowest source that covers the task.
|
|
|
6
6
|
- Tools, schemas, secrets, and direct tool tests: `docs/guides/add-tool.md`
|
|
7
7
|
- Knowledge sources, indexing, search, and hosted sync: `docs/guides/add-knowledge.md`
|
|
8
8
|
- Evals and deterministic side-effect guards: `docs/guides/run-evals.md`
|
|
9
|
+
- Safe AgentKit product feedback drafts/submission: `docs/guides/send-feedback.md`
|
|
9
10
|
- Real provider setup: `docs/guides/use-provider.md`
|
|
10
11
|
- Deploy readiness, managed secrets, smoke checks, hosted UI: `docs/guides/prepare-deploy.md`
|
|
11
|
-
- Channels: `docs/guides/add-channel.md`, `connect-discord.md`, `connect-telegram.md`, `connect-whatsapp-zapster.md`, `debug-channel.md`
|
|
12
|
+
- Channels: `docs/guides/add-channel.md`, `connect-discord.md`, `connect-slack.md`, `connect-telegram.md`, `connect-whatsapp-zapster.md`, `debug-channel.md`
|
|
12
13
|
- Security: `docs/guides/security-rules.md`
|
|
13
14
|
|
|
14
15
|
Use `npm run agentkit -- docs full` only for a complete-contract audit, framework internals, or a behavior not covered by the task guide.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agentkit-channels
|
|
3
|
-
description: Use when adding, connecting, testing, buffering, transcribing audio, or debugging AgentKit website, Telegram, WhatsApp, or
|
|
3
|
+
description: Use when adding, connecting, testing, buffering, transcribing audio, or debugging AgentKit website, Telegram, WhatsApp, Discord, or Slack channels, including channel config helpers, provider secrets, webhook setup, channel tests, delivery logs, burst-message buffers, and transcription provider secrets.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# AgentKit Channels
|
|
@@ -82,6 +82,7 @@ npm run agentkit -- channels connect telegram support-telegram
|
|
|
82
82
|
npm run agentkit -- channels add whatsapp support-whatsapp --provider zapster
|
|
83
83
|
npm run agentkit -- channels connect discord support-discord
|
|
84
84
|
npm run agentkit -- channels connect discord server-discord --mode bot
|
|
85
|
+
npm run agentkit -- channels connect slack support-slack
|
|
85
86
|
npm run agentkit -- channels doctor support-telegram
|
|
86
87
|
npm run agentkit -- channels test support-telegram --message "hello"
|
|
87
88
|
npm run agentkit -- channels test-audio support-telegram --fixture voice-note
|
|
@@ -92,6 +93,7 @@ npm run agentkit -- channels deliveries list support-telegram
|
|
|
92
93
|
## References
|
|
93
94
|
|
|
94
95
|
- `references/discord.md`
|
|
96
|
+
- `references/slack.md`
|
|
95
97
|
- `references/telegram.md`
|
|
96
98
|
- `references/whatsapp-zapster.md`
|
|
97
99
|
- `references/channel-buffering.md`
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Slack Channel
|
|
2
|
+
|
|
3
|
+
Slack uses Events API webhooks.
|
|
4
|
+
|
|
5
|
+
V1 supports:
|
|
6
|
+
|
|
7
|
+
- `app_mention` in channels.
|
|
8
|
+
- `message.im` direct messages.
|
|
9
|
+
- Outbound replies through `chat.postMessage`.
|
|
10
|
+
|
|
11
|
+
V1 does not support Socket Mode, files, audio transcription, app-management automation, or all-channel message listening.
|
|
12
|
+
|
|
13
|
+
## Required Secrets
|
|
14
|
+
|
|
15
|
+
```txt
|
|
16
|
+
SLACK_BOT_TOKEN
|
|
17
|
+
SLACK_SIGNING_SECRET
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Config
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
slackChannel({
|
|
24
|
+
name: "support-slack",
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Commands
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
agentkit deploy
|
|
32
|
+
agentkit secret set SLACK_BOT_TOKEN --stdin
|
|
33
|
+
agentkit secret set SLACK_SIGNING_SECRET --stdin
|
|
34
|
+
agentkit channels connect slack support-slack
|
|
35
|
+
agentkit channels test support-slack --message "hello"
|
|
36
|
+
agentkit channels deliveries list support-slack
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Slack Setup
|
|
40
|
+
|
|
41
|
+
In Slack app configuration:
|
|
42
|
+
|
|
43
|
+
1. Basic Information: copy Signing Secret into `SLACK_SIGNING_SECRET`.
|
|
44
|
+
2. OAuth & Permissions: add bot scopes `app_mentions:read`, `im:history`, and `chat:write`.
|
|
45
|
+
3. Install or reinstall the app to the workspace.
|
|
46
|
+
4. Copy the Bot User OAuth Token into `SLACK_BOT_TOKEN`.
|
|
47
|
+
5. Event Subscriptions: enable events and paste the AgentKit webhook URL.
|
|
48
|
+
6. Subscribe to bot events `app_mention` and `message.im`.
|
|
49
|
+
|
|
50
|
+
## Runtime Behavior
|
|
51
|
+
|
|
52
|
+
AgentKit verifies `X-Slack-Signature` and `X-Slack-Request-Timestamp` against the raw body before processing events. It rejects stale timestamps, answers `url_verification` with the literal challenge, skips bot/subtype messages, maps channel mentions to Slack threads, maps DMs to the Slack DM channel and user, and replies with `chat.postMessage`.
|
|
53
|
+
|
|
54
|
+
Outbound Slack text escapes Slack control mentions and disables link-name expansion and unfurls.
|
|
55
|
+
|
|
56
|
+
Slack audio is not supported in V1; do not configure `audio` on `slackChannel`.
|
|
@@ -13,6 +13,7 @@ Use this when something fails.
|
|
|
13
13
|
2. Run the narrow inspect command before guessing.
|
|
14
14
|
3. Route to a task skill when the failure points to config, tools, database, provider, evals, deploy, Knowledge, or channels.
|
|
15
15
|
4. Load `llms-full.txt` only when the narrow skill and guide do not explain the behavior.
|
|
16
|
+
5. If the failure appears to be an AgentKit bug, missing docs, or unclear recovery path, create a local feedback draft after diagnosis.
|
|
16
17
|
|
|
17
18
|
## First Commands
|
|
18
19
|
|
|
@@ -42,6 +43,12 @@ For deploy issues:
|
|
|
42
43
|
npm run agentkit -- deploy doctor
|
|
43
44
|
```
|
|
44
45
|
|
|
46
|
+
For AgentKit product feedback:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
npm run agentkit -- feedback create --about last-run --kind bug --summary "short concrete summary"
|
|
50
|
+
```
|
|
51
|
+
|
|
45
52
|
For production behavior issues:
|
|
46
53
|
|
|
47
54
|
```sh
|
|
@@ -60,3 +67,10 @@ npm run agentkit -- replay .agentkit/improve/<run> --against local
|
|
|
60
67
|
- channel secret missing: set hosted managed secret, not source files;
|
|
61
68
|
- production behavior drift: collect an improve bundle and convert it to regression evals before patching;
|
|
62
69
|
- local AgentKit skills are stale: run `npm run agentkit -- skills sync`.
|
|
70
|
+
|
|
71
|
+
## Feedback Rules
|
|
72
|
+
|
|
73
|
+
- `feedback create` writes a local draft only; it does not send anything.
|
|
74
|
+
- Review the draft before `feedback send`.
|
|
75
|
+
- Sending requires `agentkit login --token agk_user_...`.
|
|
76
|
+
- Do not paste `.env` values, provider keys, cookies, client PII, or full private transcripts into feedback.
|