@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.
@@ -0,0 +1,438 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, extname, join, relative, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { AgentKitError } from "../../runtime/errors";
7
+ import { findAgentCapsuleRoot, loadAgentCapsule } from "../../runtime/config";
8
+ import { readLocalDeployStateIfExists } from "../../runtime/core/deploy-state";
9
+ import type { ParsedArgs } from "../args";
10
+ import {
11
+ cloudApiRequest,
12
+ readCloudAuthForApiUrl,
13
+ resolveCloudApiUrl,
14
+ type FeedbackSendResponse,
15
+ } from "../cloud-client";
16
+ import { parseOptionalStringFlag } from "../flags";
17
+
18
+ type FeedbackKind =
19
+ | "bug"
20
+ | "confusion"
21
+ | "missing_docs"
22
+ | "feature_request"
23
+ | "deploy_issue"
24
+ | "runtime_issue"
25
+ | "other";
26
+
27
+ type FeedbackAbout = "last-run" | "deploy" | "manual";
28
+
29
+ type FeedbackDraft = {
30
+ schema_version: "agentkit.feedback.v1";
31
+ client_report_id: string;
32
+ kind: FeedbackKind;
33
+ summary: string;
34
+ message?: string;
35
+ source: Record<string, unknown>;
36
+ context: Record<string, unknown>;
37
+ diagnostics: Record<string, unknown>;
38
+ };
39
+
40
+ const feedbackKinds = new Set<FeedbackKind>([
41
+ "bug",
42
+ "confusion",
43
+ "missing_docs",
44
+ "feature_request",
45
+ "deploy_issue",
46
+ "runtime_issue",
47
+ "other",
48
+ ]);
49
+
50
+ const feedbackAboutValues = new Set<FeedbackAbout>(["last-run", "deploy", "manual"]);
51
+
52
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
53
+
54
+ export async function handleFeedbackCommand(args: ParsedArgs): Promise<void> {
55
+ const [subcommand, draftPath] = args.positional;
56
+
57
+ if (subcommand === "create") {
58
+ const draft = await buildFeedbackDraft(process.cwd(), args);
59
+ const written = await writeFeedbackDraft(process.cwd(), draft, args.flags.out);
60
+
61
+ console.log("Feedback draft created");
62
+ console.log(`JSON: ${relativePath(process.cwd(), written.jsonPath)}`);
63
+ console.log(`Markdown: ${relativePath(process.cwd(), written.markdownPath)}`);
64
+ console.log(`Send: agentkit feedback send ${relativePath(process.cwd(), written.jsonPath)}`);
65
+ return;
66
+ }
67
+
68
+ if (subcommand === "preview") {
69
+ const draft = draftPath
70
+ ? await readFeedbackDraft(resolve(process.cwd(), draftPath))
71
+ : await buildFeedbackDraft(process.cwd(), args);
72
+ console.log(JSON.stringify(draft, null, 2));
73
+ return;
74
+ }
75
+
76
+ if (subcommand === "send") {
77
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
78
+ const auth = await readCloudAuthForApiUrl(apiUrl);
79
+
80
+ if (!auth) {
81
+ throw new AgentKitError(
82
+ "auth_required",
83
+ `Log in before sending feedback: agentkit login --token <token> --api ${apiUrl}`,
84
+ );
85
+ }
86
+
87
+ const draft = draftPath
88
+ ? await readFeedbackDraft(resolve(process.cwd(), draftPath))
89
+ : await buildFeedbackDraft(process.cwd(), args);
90
+
91
+ if (!draftPath && args.flags.save === true) {
92
+ const written = await writeFeedbackDraft(process.cwd(), draft, args.flags.out);
93
+ console.log(`Feedback draft saved: ${relativePath(process.cwd(), written.jsonPath)}`);
94
+ }
95
+
96
+ const payload = (await cloudApiRequest(apiUrl, "/v1/feedback", {
97
+ method: "POST",
98
+ headers: {
99
+ "Idempotency-Key": draft.client_report_id,
100
+ },
101
+ body: JSON.stringify(feedbackDraftToSubmission(draft)),
102
+ })) as FeedbackSendResponse;
103
+
104
+ const feedback = payload.feedback;
105
+
106
+ if (!feedback?.id) {
107
+ throw new AgentKitError("feedback_response_invalid", "AgentKit Cloud returned an invalid feedback response.");
108
+ }
109
+
110
+ console.log("Feedback sent");
111
+ console.log(`ID: ${feedback.id}`);
112
+ console.log(`Kind: ${feedback.kind ?? draft.kind}`);
113
+ console.log(`Status: ${feedback.status ?? "new"}`);
114
+ return;
115
+ }
116
+
117
+ throw new Error(
118
+ "Usage: agentkit feedback create|preview|send [draft.json] [--about last-run|deploy|manual] [--kind bug|confusion|missing_docs|feature_request|deploy_issue|runtime_issue|other] [--summary <text>] [--message <text>] [--api <url>]",
119
+ );
120
+ }
121
+
122
+ async function buildFeedbackDraft(cwd: string, args: ParsedArgs): Promise<FeedbackDraft> {
123
+ const about = parseFeedbackAbout(args.flags.about);
124
+ const kind = parseFeedbackKind(args.flags.kind, about === "deploy" ? "deploy_issue" : "other");
125
+ const message = parseOptionalStringFlag(args.flags.message, "message", "agentkit feedback create --message <text>");
126
+ const summary =
127
+ parseOptionalStringFlag(args.flags.summary, "summary", "agentkit feedback create --summary <text>") ??
128
+ summarizeMessage(message);
129
+
130
+ if (!summary) {
131
+ throw new Error("Missing summary. Usage: agentkit feedback create --summary <text> [--message <text>]");
132
+ }
133
+
134
+ const now = new Date().toISOString();
135
+ const context = await collectFeedbackContext(cwd);
136
+ const draft: FeedbackDraft = {
137
+ schema_version: "agentkit.feedback.v1",
138
+ client_report_id: `fbdraft_${Date.now().toString(36)}_${randomBytes(6).toString("hex")}`,
139
+ kind,
140
+ summary: redactText(summary).slice(0, 200),
141
+ ...(message ? { message: redactText(message).slice(0, 5_000) } : {}),
142
+ source: {
143
+ channel: "cli",
144
+ about,
145
+ created_at: now,
146
+ agentkit_version: await readAgentKitVersion(),
147
+ },
148
+ context,
149
+ diagnostics: {
150
+ redaction: "agentkit-cli-redaction-v1",
151
+ note:
152
+ about === "last-run"
153
+ ? "AgentKit does not persist a last-command transcript yet; this draft includes current capsule and deploy context."
154
+ : "This draft includes current capsule and deploy context.",
155
+ },
156
+ };
157
+
158
+ return redactJson(draft) as FeedbackDraft;
159
+ }
160
+
161
+ async function collectFeedbackContext(cwd: string): Promise<Record<string, unknown>> {
162
+ const context: Record<string, unknown> = {
163
+ platform: {
164
+ node: process.version,
165
+ bun: (globalThis as { Bun?: { version?: string } }).Bun?.version,
166
+ platform: process.platform,
167
+ arch: process.arch,
168
+ package_manager: await detectPackageManager(cwd),
169
+ },
170
+ };
171
+
172
+ const root = await findAgentCapsuleRoot(cwd).catch(() => null);
173
+
174
+ if (!root) {
175
+ context.capsule = {
176
+ detected: false,
177
+ cwd_name: basename(cwd),
178
+ };
179
+ return redactJson(context) as Record<string, unknown>;
180
+ }
181
+
182
+ const capsule = await loadAgentCapsule(root).catch((error: unknown) => ({
183
+ error: error instanceof Error ? error.message : String(error),
184
+ }));
185
+ const deployState = await readLocalDeployStateIfExists(root).catch(() => null);
186
+
187
+ if ("config" in capsule) {
188
+ context.capsule = {
189
+ detected: true,
190
+ root_name: basename(root),
191
+ name: capsule.config.name,
192
+ runtime: capsule.config.runtime,
193
+ provider: capsule.config.provider,
194
+ access: capsule.config.access,
195
+ storage: capsule.config.storage,
196
+ secret_names: capsule.config.secrets,
197
+ tool_count: capsule.config.tools?.length ?? 0,
198
+ channel_count: capsule.config.channels?.length ?? 0,
199
+ };
200
+ } else {
201
+ context.capsule = {
202
+ detected: true,
203
+ root_name: basename(root),
204
+ load_error: capsule.error,
205
+ };
206
+ }
207
+
208
+ if (deployState) {
209
+ context.deploy = {
210
+ project_id: deployState.project_id,
211
+ deploy_id: deployState.deploy_id,
212
+ target: deployState.target,
213
+ runtime: deployState.runtime,
214
+ owner_state: deployState.owner_state,
215
+ access: deployState.access,
216
+ url: deployState.url,
217
+ created_at: deployState.created_at,
218
+ };
219
+ }
220
+
221
+ return redactJson(context) as Record<string, unknown>;
222
+ }
223
+
224
+ async function detectPackageManager(cwd: string): Promise<string> {
225
+ const candidates = [
226
+ ["bun.lock", "bun"],
227
+ ["bun.lockb", "bun"],
228
+ ["pnpm-lock.yaml", "pnpm"],
229
+ ["yarn.lock", "yarn"],
230
+ ["package-lock.json", "npm"],
231
+ ] as const;
232
+
233
+ for (const [file, name] of candidates) {
234
+ if (await pathExists(join(cwd, file))) {
235
+ return name;
236
+ }
237
+ }
238
+
239
+ return "unknown";
240
+ }
241
+
242
+ async function writeFeedbackDraft(
243
+ cwd: string,
244
+ draft: FeedbackDraft,
245
+ outFlag: string | boolean | undefined,
246
+ ): Promise<{ jsonPath: string; markdownPath: string }> {
247
+ const out = parseOptionalStringFlag(outFlag, "out", "agentkit feedback create --out <path>");
248
+ const defaultDir = join(cwd, ".agentkit", "feedback");
249
+ const slug = slugify(draft.summary);
250
+ const filename = `${new Date().toISOString().replace(/[:.]/g, "-")}-${slug}`;
251
+ const jsonPath = out && extname(out) === ".json" ? resolve(cwd, out) : join(out ? resolve(cwd, out) : defaultDir, `${filename}.json`);
252
+ const markdownPath = jsonPath.replace(/\.json$/, ".md");
253
+
254
+ await mkdir(dirname(jsonPath), { recursive: true });
255
+ await writeFile(jsonPath, `${JSON.stringify(draft, null, 2)}\n`, { mode: 0o600 });
256
+ await chmod(jsonPath, 0o600);
257
+ await writeFile(markdownPath, renderFeedbackMarkdown(draft), { mode: 0o600 });
258
+ await chmod(markdownPath, 0o600);
259
+
260
+ return { jsonPath, markdownPath };
261
+ }
262
+
263
+ async function readFeedbackDraft(path: string): Promise<FeedbackDraft> {
264
+ const parsed = JSON.parse(await readFile(path, "utf8")) as unknown;
265
+ const draft = validateFeedbackDraft(parsed);
266
+
267
+ if (!draft) {
268
+ throw new AgentKitError("feedback_draft_invalid", "Feedback draft must be an agentkit.feedback.v1 JSON object.");
269
+ }
270
+
271
+ return redactJson(draft) as FeedbackDraft;
272
+ }
273
+
274
+ function validateFeedbackDraft(value: unknown): FeedbackDraft | null {
275
+ if (!isRecord(value) || value.schema_version !== "agentkit.feedback.v1") {
276
+ return null;
277
+ }
278
+
279
+ if (typeof value.client_report_id !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value.client_report_id)) {
280
+ return null;
281
+ }
282
+
283
+ if (!isFeedbackKind(value.kind) || typeof value.summary !== "string" || !isRecord(value.source)) {
284
+ return null;
285
+ }
286
+
287
+ return {
288
+ schema_version: "agentkit.feedback.v1",
289
+ client_report_id: value.client_report_id,
290
+ kind: value.kind,
291
+ summary: redactText(value.summary).slice(0, 200),
292
+ ...(typeof value.message === "string" ? { message: redactText(value.message).slice(0, 5_000) } : {}),
293
+ source: redactJson(value.source) as Record<string, unknown>,
294
+ context: isRecord(value.context) ? redactJson(value.context) as Record<string, unknown> : {},
295
+ diagnostics: isRecord(value.diagnostics) ? redactJson(value.diagnostics) as Record<string, unknown> : {},
296
+ };
297
+ }
298
+
299
+ function feedbackDraftToSubmission(draft: FeedbackDraft): Record<string, unknown> {
300
+ return {
301
+ kind: draft.kind,
302
+ summary: draft.summary,
303
+ ...(draft.message ? { message: draft.message } : {}),
304
+ source: draft.source,
305
+ context: draft.context,
306
+ diagnostics: draft.diagnostics,
307
+ client_report_id: draft.client_report_id,
308
+ };
309
+ }
310
+
311
+ function renderFeedbackMarkdown(draft: FeedbackDraft): string {
312
+ const lines = [
313
+ "# AgentKit Feedback Draft",
314
+ "",
315
+ `Kind: ${draft.kind}`,
316
+ `Summary: ${draft.summary}`,
317
+ `Client report: ${draft.client_report_id}`,
318
+ "",
319
+ ];
320
+
321
+ if (draft.message) {
322
+ lines.push("## Message", "", draft.message, "");
323
+ }
324
+
325
+ lines.push("## Context", "", "```json", JSON.stringify(draft.context, null, 2), "```", "");
326
+ lines.push("Send with the JSON path printed by `agentkit feedback create`:", "", "```sh\nagentkit feedback send <draft.json>\n```", "");
327
+
328
+ return `${lines.join("\n")}\n`;
329
+ }
330
+
331
+ function parseFeedbackKind(value: string | boolean | undefined, fallback: FeedbackKind): FeedbackKind {
332
+ if (value === undefined || value === false) {
333
+ return fallback;
334
+ }
335
+
336
+ if (typeof value === "string" && isFeedbackKind(value)) {
337
+ return value;
338
+ }
339
+
340
+ throw new Error("feedback --kind must be bug, confusion, missing_docs, feature_request, deploy_issue, runtime_issue, or other.");
341
+ }
342
+
343
+ function parseFeedbackAbout(value: string | boolean | undefined): FeedbackAbout {
344
+ if (value === undefined || value === false) {
345
+ return "last-run";
346
+ }
347
+
348
+ if (typeof value === "string" && feedbackAboutValues.has(value as FeedbackAbout)) {
349
+ return value as FeedbackAbout;
350
+ }
351
+
352
+ throw new Error("feedback --about must be last-run, deploy, or manual.");
353
+ }
354
+
355
+ function summarizeMessage(message: string | undefined): string | undefined {
356
+ if (!message) {
357
+ return undefined;
358
+ }
359
+
360
+ return message.replace(/\s+/g, " ").trim().slice(0, 120);
361
+ }
362
+
363
+ function isFeedbackKind(value: unknown): value is FeedbackKind {
364
+ return typeof value === "string" && feedbackKinds.has(value as FeedbackKind);
365
+ }
366
+
367
+ function redactJson(value: unknown, key = ""): unknown {
368
+ if (Array.isArray(value)) {
369
+ return value.map((item) => redactJson(item, key));
370
+ }
371
+
372
+ if (!isRecord(value)) {
373
+ return typeof value === "string" ? redactText(value) : value;
374
+ }
375
+
376
+ const out: Record<string, unknown> = {};
377
+
378
+ for (const [nestedKey, nestedValue] of Object.entries(value)) {
379
+ if (shouldRedactKey(nestedKey)) {
380
+ out[nestedKey] = "[redacted]";
381
+ continue;
382
+ }
383
+
384
+ out[nestedKey] = redactJson(nestedValue, nestedKey);
385
+ }
386
+
387
+ return out;
388
+ }
389
+
390
+ function shouldRedactKey(key: string): boolean {
391
+ return /token|password|credential|api[_-]?key|private[_-]?key/i.test(key) && key !== "client_report_id";
392
+ }
393
+
394
+ function redactText(value: string): string {
395
+ return value
396
+ .replace(/Authorization:\s*Bearer\s+[A-Za-z0-9._:-]+/gi, "Authorization: Bearer [redacted]")
397
+ .replace(/\b(?:agk_user|agk_deploy|ak_live|bsec|whsec|sk|rk|pk)[-_A-Za-z0-9]{12,}\b/g, "[redacted-token]")
398
+ .replace(/\b[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{24,}\b/g, "[redacted-token]");
399
+ }
400
+
401
+ function slugify(value: string): string {
402
+ const slug = value
403
+ .toLowerCase()
404
+ .replace(/[^a-z0-9]+/g, "-")
405
+ .replace(/^-+|-+$/g, "")
406
+ .slice(0, 48);
407
+
408
+ return slug || "feedback";
409
+ }
410
+
411
+ async function readAgentKitVersion(): Promise<string> {
412
+ try {
413
+ const packageJson = JSON.parse(await readFile(join(moduleDir, "..", "..", "..", "package.json"), "utf8")) as {
414
+ version?: unknown;
415
+ };
416
+ return typeof packageJson.version === "string" ? packageJson.version : "unknown";
417
+ } catch {
418
+ return "unknown";
419
+ }
420
+ }
421
+
422
+ async function pathExists(path: string): Promise<boolean> {
423
+ try {
424
+ await stat(path);
425
+ return true;
426
+ } catch {
427
+ return false;
428
+ }
429
+ }
430
+
431
+ function relativePath(root: string, path: string): string {
432
+ const output = relative(root, path);
433
+ return output.startsWith("..") ? path : output || ".";
434
+ }
435
+
436
+ function isRecord(value: unknown): value is Record<string, unknown> {
437
+ return typeof value === "object" && value !== null && !Array.isArray(value);
438
+ }
package/src/cli/help.ts CHANGED
@@ -30,6 +30,7 @@ Build:
30
30
  agentkit eval run
31
31
  agentkit eval from-conversation <id>
32
32
  agentkit improve collect --deploy --since 24h
33
+ agentkit feedback create --about last-run --kind bug --summary <text>
33
34
  agentkit replay <bundle-dir> --against local
34
35
  agentkit conversations list
35
36
 
@@ -48,6 +49,7 @@ More:
48
49
  agentkit account token ...
49
50
  agentkit access token ...
50
51
  agentkit env ...
52
+ agentkit feedback create --summary <text>
51
53
  agentkit docs full
52
54
  agentkit help commands
53
55
  `;
@@ -79,13 +81,16 @@ Usage:
79
81
  agentkit eval from-conversation <conversation-id> [--out <path>] [--force]
80
82
  agentkit improve collect [--deploy] [--since <duration|iso>] [--conversation-id <id>] [--out <directory>]
81
83
  agentkit improve evals <bundle-dir-or-json> [--force]
84
+ agentkit feedback create [--about last-run|deploy|manual] [--kind bug|confusion|missing_docs|feature_request|deploy_issue|runtime_issue|other] --summary <text> [--message <text>] [--out <path>]
85
+ agentkit feedback preview [draft.json]
86
+ agentkit feedback send [draft.json] [--about last-run|deploy|manual] [--kind <kind>] --summary <text> [--message <text>] [--api <url>] [--save]
82
87
  agentkit replay <bundle-dir-or-json> --against local
83
88
  agentkit conversations list [--deploy]
84
89
  agentkit conversations show <conversation-id> [--deploy]
85
90
  agentkit conversations trace <conversation-id> [--deploy]
86
91
  agentkit channels list
87
- agentkit channels add <website|telegram|whatsapp|discord> <name> [--provider zapster|meta] [--mode interactions|bot] [--api <url>]
88
- agentkit channels connect <website|telegram|whatsapp|discord> <name> [--provider zapster|meta] [--mode interactions|bot] [--api <url>]
92
+ agentkit channels add <website|telegram|whatsapp|discord|slack> <name> [--provider zapster|meta] [--mode interactions|bot] [--api <url>]
93
+ agentkit channels connect <website|telegram|whatsapp|discord|slack> <name> [--provider zapster|meta] [--mode interactions|bot] [--api <url>]
89
94
  agentkit channels setup <name> [--apply] [--api <url>]
90
95
  agentkit channels status <name> [--api <url>]
91
96
  agentkit channels doctor <name> [--api <url>]
@@ -155,6 +160,7 @@ Commands:
155
160
  db Manage the local SQLite database
156
161
  eval Run local eval files or create one from a conversation
157
162
  improve Collect production or local traces and generate regression evals
163
+ feedback Create local redacted feedback drafts and send them to AgentKit Cloud
158
164
  replay Replay an improve bundle against the local Agent Capsule
159
165
  conversations List, inspect, and trace stored local conversations
160
166
  channels Manage hosted channel resources
@@ -201,6 +207,8 @@ export function renderCommandHelp(command: string): string {
201
207
  eval: "agentkit eval run | agentkit eval from-conversation <conversation-id> [--out <path>] [--force]",
202
208
  improve:
203
209
  "agentkit improve collect [--deploy] [--since <duration|iso>] [--conversation-id <id>] [--out <directory>] | agentkit improve evals <bundle-dir-or-json> [--force]",
210
+ feedback:
211
+ "agentkit feedback create|preview|send [draft.json] [--about last-run|deploy|manual] [--kind <kind>] [--summary <text>] [--message <text>]",
204
212
  replay: "agentkit replay <bundle-dir-or-json> --against local",
205
213
  conversations:
206
214
  "agentkit conversations list [--deploy] | agentkit conversations show <conversation-id> [--deploy] | agentkit conversations trace <conversation-id> [--deploy]",
package/src/cli/index.ts CHANGED
@@ -74,6 +74,7 @@ import {
74
74
  import { renderCommandHelp, renderCommandReference, renderHelp } from "./help";
75
75
  import { runInheritedCommand, waitForShutdown } from "./process";
76
76
  import { handleChannelsCommand } from "./commands/channels";
77
+ import { handleFeedbackCommand } from "./commands/feedback";
77
78
  import { handleKnowledgeCommand } from "./commands/knowledge";
78
79
  import { handleTranscribeCommand } from "./commands/transcribe";
79
80
 
@@ -549,6 +550,11 @@ async function main() {
549
550
  return;
550
551
  }
551
552
 
553
+ if (args.command === "feedback") {
554
+ await handleFeedbackCommand(args);
555
+ return;
556
+ }
557
+
552
558
  if (args.command === "sync") {
553
559
  const [subcommand] = args.positional;
554
560
 
@@ -1846,5 +1852,6 @@ main().catch((error: unknown) => {
1846
1852
  const message = error instanceof Error ? error.message : String(error);
1847
1853
  const prefix = isAgentKitError(error) ? `agentkit ${error.code}` : "agentkit";
1848
1854
  console.error(`${prefix}: ${message}`);
1855
+ console.error("Feedback draft: agentkit feedback create --about last-run --kind bug --summary <text>");
1849
1856
  process.exitCode = 1;
1850
1857
  });
package/src/index.ts CHANGED
@@ -2,8 +2,8 @@ export type AgentRuntime = "local" | "edge";
2
2
  export type AgentProviderName = "test" | "openai" | "anthropic" | "openrouter" | "custom";
3
3
  export type AccessMode = "private" | "token" | "public";
4
4
  export type ToolVisibility = "user" | "internal";
5
- export type ChannelType = "website" | "telegram" | "whatsapp" | "discord";
6
- export type ChannelProvider = "agentkit" | "telegram" | "zapster" | "meta" | "discord";
5
+ export type ChannelType = "website" | "telegram" | "whatsapp" | "discord" | "slack";
6
+ export type ChannelProvider = "agentkit" | "telegram" | "zapster" | "meta" | "discord" | "slack";
7
7
  export type WhatsappChannelProvider = "zapster" | "meta";
8
8
  export type DiscordChannelMode = "interactions" | "bot";
9
9
  export type TelegramAllowedUpdate = "message";
@@ -163,7 +163,29 @@ export type DiscordChannelConfig = {
163
163
  audio?: never;
164
164
  };
165
165
 
166
- export type AgentChannel = WebsiteChannelConfig | TelegramChannelConfig | WhatsappChannelConfig | DiscordChannelConfig;
166
+ export type SlackChannelInput = {
167
+ name: string;
168
+ secrets?: string[];
169
+ limits?: ChannelLimits;
170
+ buffer?: ChannelBufferInput;
171
+ };
172
+
173
+ export type SlackChannelConfig = {
174
+ type: "slack";
175
+ provider: "slack";
176
+ name: string;
177
+ secrets: string[];
178
+ limits?: ChannelLimits;
179
+ buffer?: ChannelBufferConfig;
180
+ audio?: never;
181
+ };
182
+
183
+ export type AgentChannel =
184
+ | WebsiteChannelConfig
185
+ | TelegramChannelConfig
186
+ | WhatsappChannelConfig
187
+ | DiscordChannelConfig
188
+ | SlackChannelConfig;
167
189
 
168
190
  export type IntegrationTagsConfig = {
169
191
  enable?: string[];
@@ -462,6 +484,17 @@ export function discordChannel(input: DiscordChannelInput): DiscordChannelConfig
462
484
  };
463
485
  }
464
486
 
487
+ export function slackChannel(input: SlackChannelInput): SlackChannelConfig {
488
+ return {
489
+ type: "slack",
490
+ provider: "slack",
491
+ name: input.name,
492
+ secrets: normalizeChannelSecrets(input.secrets ?? ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], "slack channel"),
493
+ ...(input.limits ? { limits: input.limits } : {}),
494
+ ...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "slack channel") } : {}),
495
+ };
496
+ }
497
+
465
498
  function defaultDiscordChannelSecrets(mode: DiscordChannelMode): string[] {
466
499
  return mode === "bot" ? ["DISCORD_BOT_TOKEN"] : ["DISCORD_PUBLIC_KEY"];
467
500
  }
@@ -692,6 +725,7 @@ export type {
692
725
  ChannelSendResult,
693
726
  ChannelStatusInput,
694
727
  ChannelStatusResult,
728
+ ChannelWebhookResponse,
695
729
  NormalizedChannelEvent,
696
730
  NormalizedChannelMessage,
697
731
  RawWebhookEvent,
@@ -291,6 +291,13 @@ function normalizeUsage(usage: Usage | undefined): ProviderRunResult["usage"] |
291
291
  inputTokens: usage.input,
292
292
  outputTokens: usage.output,
293
293
  totalTokens: usage.totalTokens,
294
+ cost: {
295
+ input: usage.cost.input,
296
+ output: usage.cost.output,
297
+ cacheRead: usage.cost.cacheRead,
298
+ cacheWrite: usage.cost.cacheWrite,
299
+ total: usage.cost.total,
300
+ },
294
301
  };
295
302
  }
296
303
 
@@ -24,6 +24,13 @@ export type ProviderRunResult = {
24
24
  inputTokens?: number;
25
25
  outputTokens?: number;
26
26
  totalTokens?: number;
27
+ cost?: {
28
+ input?: number;
29
+ output?: number;
30
+ cacheRead?: number;
31
+ cacheWrite?: number;
32
+ total?: number;
33
+ };
27
34
  };
28
35
  raw?: unknown;
29
36
  };
@@ -17,7 +17,9 @@ export type ChannelFixtureName =
17
17
  | "zapster-unsupported-media"
18
18
  | "discord-command"
19
19
  | "discord-message-create"
20
- | "discord-ping";
20
+ | "discord-ping"
21
+ | "slack-app-mention"
22
+ | "slack-direct-message";
21
23
 
22
24
  export type ChannelFixture = {
23
25
  name: ChannelFixtureName;
@@ -20,6 +20,15 @@ export const discordChannelAdapter: ChannelAdapter = {
20
20
  async verifyWebhook(input): Promise<WebhookVerificationResult> {
21
21
  return verifyDiscordWebhook(input);
22
22
  },
23
+ getWebhookResponse(input) {
24
+ if (!isDiscordPingInteraction(input.rawBody)) {
25
+ return null;
26
+ }
27
+
28
+ return {
29
+ body: { type: 1 },
30
+ };
31
+ },
23
32
  normalizeInbound(input) {
24
33
  const body = parseJsonObject(input);
25
34
  const interactionId = readString(body, "id");