@copilotkit/bot 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/dist/action-registry.d.ts +20 -0
  4. package/dist/action-registry.d.ts.map +1 -0
  5. package/dist/action-registry.js +109 -0
  6. package/dist/action-registry.test.d.ts +2 -0
  7. package/dist/action-registry.test.d.ts.map +1 -0
  8. package/dist/action-registry.test.js +45 -0
  9. package/dist/action-store.d.ts +19 -0
  10. package/dist/action-store.d.ts.map +1 -0
  11. package/dist/action-store.js +22 -0
  12. package/dist/action-store.test.d.ts +2 -0
  13. package/dist/action-store.test.d.ts.map +1 -0
  14. package/dist/action-store.test.js +27 -0
  15. package/dist/commands.d.ts +68 -0
  16. package/dist/commands.d.ts.map +1 -0
  17. package/dist/commands.js +30 -0
  18. package/dist/create-bot.d.ts +44 -0
  19. package/dist/create-bot.d.ts.map +1 -0
  20. package/dist/create-bot.js +166 -0
  21. package/dist/create-bot.test.d.ts +2 -0
  22. package/dist/create-bot.test.d.ts.map +1 -0
  23. package/dist/create-bot.test.js +261 -0
  24. package/dist/index.d.ts +17 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +19 -0
  27. package/dist/mint-id.d.ts +3 -0
  28. package/dist/mint-id.d.ts.map +1 -0
  29. package/dist/mint-id.js +20 -0
  30. package/dist/mint-id.test.d.ts +2 -0
  31. package/dist/mint-id.test.d.ts.map +1 -0
  32. package/dist/mint-id.test.js +16 -0
  33. package/dist/platform-adapter.d.ts +125 -0
  34. package/dist/platform-adapter.d.ts.map +1 -0
  35. package/dist/platform-adapter.js +1 -0
  36. package/dist/platform-adapter.test.d.ts +2 -0
  37. package/dist/platform-adapter.test.d.ts.map +1 -0
  38. package/dist/platform-adapter.test.js +32 -0
  39. package/dist/run-loop.d.ts +38 -0
  40. package/dist/run-loop.d.ts.map +1 -0
  41. package/dist/run-loop.js +100 -0
  42. package/dist/run-loop.test.d.ts +2 -0
  43. package/dist/run-loop.test.d.ts.map +1 -0
  44. package/dist/run-loop.test.js +80 -0
  45. package/dist/standard-schema.d.ts +52 -0
  46. package/dist/standard-schema.d.ts.map +1 -0
  47. package/dist/standard-schema.js +66 -0
  48. package/dist/testing/fake-adapter.d.ts +44 -0
  49. package/dist/testing/fake-adapter.d.ts.map +1 -0
  50. package/dist/testing/fake-adapter.js +123 -0
  51. package/dist/testing/fake-agent.d.ts +28 -0
  52. package/dist/testing/fake-agent.d.ts.map +1 -0
  53. package/dist/testing/fake-agent.js +36 -0
  54. package/dist/thread.d.ts +60 -0
  55. package/dist/thread.d.ts.map +1 -0
  56. package/dist/thread.js +109 -0
  57. package/dist/tools.d.ts +67 -0
  58. package/dist/tools.d.ts.map +1 -0
  59. package/dist/tools.js +38 -0
  60. package/dist/tools.test.d.ts +2 -0
  61. package/dist/tools.test.d.ts.map +1 -0
  62. package/dist/tools.test.js +31 -0
  63. package/package.json +60 -0
@@ -0,0 +1,166 @@
1
+ import { ActionRegistry, ActionExpiredError } from "./action-registry.js";
2
+ import { InMemoryActionStore } from "./action-store.js";
3
+ import { toAgentToolDescriptors, parseToolArgs, } from "./tools.js";
4
+ import { normalizeCommandName, toCommandSpec, } from "./commands.js";
5
+ import { Thread } from "./thread.js";
6
+ export function createBot(opts) {
7
+ const registry = new ActionRegistry({
8
+ store: opts.actionStore ?? new InMemoryActionStore(),
9
+ });
10
+ const agentFactory = (() => {
11
+ const a = opts.agent;
12
+ if (typeof a === "function")
13
+ return a;
14
+ if (a)
15
+ return () => a;
16
+ return () => {
17
+ throw new Error("createBot: no agent configured (pass `agent` to use runAgent)");
18
+ };
19
+ })();
20
+ const toolMap = new Map();
21
+ for (const t of opts.tools ?? [])
22
+ toolMap.set(t.name, t);
23
+ const context = opts.context ?? [];
24
+ const mentionHandlers = [];
25
+ const messageHandlers = [];
26
+ const interactionHandlers = new Map();
27
+ const interruptHandlers = new Map();
28
+ const commandHandlers = new Map();
29
+ for (const c of opts.commands ?? [])
30
+ commandHandlers.set(normalizeCommandName(c.name), c);
31
+ const waiters = new Map();
32
+ // Recomputed on start() so tools added via bot.tool() before start are picked up.
33
+ let toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
34
+ function makeThread(adapter, replyTarget, conversationKey) {
35
+ const deps = {
36
+ adapter,
37
+ replyTarget,
38
+ conversationKey,
39
+ registry,
40
+ agentFactory,
41
+ tools: toolMap,
42
+ toolDescriptors,
43
+ context,
44
+ registerWaiter: (k, r) => waiters.set(k, r),
45
+ interruptHandlers,
46
+ };
47
+ return new Thread(deps);
48
+ }
49
+ function makeSink(adapter) {
50
+ return {
51
+ async onTurn(turn) {
52
+ const thread = makeThread(adapter, turn.replyTarget, turn.conversationKey);
53
+ const message = {
54
+ text: turn.userText,
55
+ user: turn.user ?? { id: "" },
56
+ ref: { id: "" },
57
+ platform: turn.platform,
58
+ };
59
+ // v1 routing: there is no turn `kind`, so prefer mention handlers; if
60
+ // none are registered, fall back to message handlers. (The reference
61
+ // example registers identical handlers on both, so this avoids
62
+ // double-firing while still invoking whatever is registered.)
63
+ const handlers = mentionHandlers.length > 0 ? mentionHandlers : messageHandlers;
64
+ for (const h of handlers)
65
+ await h({ thread, message });
66
+ },
67
+ async onInteraction(evt) {
68
+ const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey);
69
+ const user = evt.user ?? { id: "" };
70
+ const ctx = {
71
+ thread,
72
+ message: {
73
+ text: "",
74
+ user,
75
+ ref: evt.messageRef ?? { id: "" },
76
+ platform: adapter.platform,
77
+ },
78
+ action: { id: evt.id, value: evt.value },
79
+ values: {},
80
+ user,
81
+ platform: adapter.platform,
82
+ };
83
+ try {
84
+ const explicit = interactionHandlers.get(evt.id);
85
+ if (explicit) {
86
+ await explicit(ctx);
87
+ }
88
+ else {
89
+ await registry.dispatch(evt.id, ctx);
90
+ }
91
+ }
92
+ catch (err) {
93
+ // v1: swallow expired-action dispatches; surface anything else.
94
+ if (!(err instanceof ActionExpiredError))
95
+ throw err;
96
+ }
97
+ // Resolve any HITL waiter awaiting a choice in this conversation.
98
+ const w = waiters.get(evt.conversationKey);
99
+ if (w) {
100
+ waiters.delete(evt.conversationKey);
101
+ w(evt.value);
102
+ }
103
+ },
104
+ async onCommand(cmd) {
105
+ const command = commandHandlers.get(normalizeCommandName(cmd.command));
106
+ if (!command)
107
+ return; // unregistered command → skip
108
+ const thread = makeThread(adapter, cmd.replyTarget, cmd.conversationKey);
109
+ // Resolve typed options from any structured args the surface supplied
110
+ // (e.g. Discord); text-only surfaces (Slack) leave `options` empty and
111
+ // the handler reads `text`.
112
+ let options = {};
113
+ if (command.options && cmd.rawOptions) {
114
+ const parsed = await parseToolArgs(command.options, cmd.rawOptions);
115
+ if (parsed.ok)
116
+ options = parsed.value;
117
+ }
118
+ const ctx = {
119
+ thread,
120
+ command: normalizeCommandName(cmd.command),
121
+ text: cmd.text,
122
+ options,
123
+ user: cmd.user,
124
+ platform: cmd.platform,
125
+ };
126
+ await command.handler(ctx);
127
+ },
128
+ };
129
+ }
130
+ return {
131
+ onMention(h) {
132
+ mentionHandlers.push(h);
133
+ },
134
+ onMessage(h) {
135
+ messageHandlers.push(h);
136
+ },
137
+ onInteraction(id, h) {
138
+ interactionHandlers.set(id, h);
139
+ },
140
+ onInterrupt(eventName, h) {
141
+ interruptHandlers.set(eventName, h);
142
+ },
143
+ onCommand(commandOrName, handler) {
144
+ const command = typeof commandOrName === "string"
145
+ ? { name: commandOrName, handler: handler }
146
+ : commandOrName;
147
+ commandHandlers.set(normalizeCommandName(command.name), command);
148
+ },
149
+ tool(t) {
150
+ toolMap.set(t.name, t);
151
+ },
152
+ async start() {
153
+ toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
154
+ await Promise.all(opts.adapters.map((a) => a.start(makeSink(a))));
155
+ // Hand declared commands to adapters that register them up front (e.g.
156
+ // Discord); adapters without `registerCommands` are skipped.
157
+ const commandSpecs = [...commandHandlers.values()].map(toCommandSpec);
158
+ if (commandSpecs.length > 0) {
159
+ await Promise.all(opts.adapters.map((a) => a.registerCommands?.(commandSpecs)));
160
+ }
161
+ },
162
+ async stop() {
163
+ await Promise.all(opts.adapters.map((a) => a.stop()));
164
+ },
165
+ };
166
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=create-bot.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-bot.test.d.ts","sourceRoot":"","sources":["../src/create-bot.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,261 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { z } from "zod";
3
+ import { createBot } from "./create-bot.js";
4
+ import { defineBotCommand } from "./commands.js";
5
+ import { FakeAdapter } from "./testing/fake-adapter.js";
6
+ import { FakeAgent } from "./testing/fake-agent.js";
7
+ import { Section, Actions, Button } from "@copilotkit/bot-ui";
8
+ const tick = () => new Promise((r) => setTimeout(r, 0));
9
+ /**
10
+ * Compile-time guards for the handler generics (validated by check-types/build,
11
+ * never executed). `onInterrupt<T>` types `payload`; `onInteraction<T>` types
12
+ * `ctx.action.value`.
13
+ */
14
+ const __handlerTypeGuards = () => {
15
+ const bot = createBot({ adapters: [new FakeAdapter()] });
16
+ bot.onInterrupt("ask", ({ payload }) => {
17
+ payload.question.toUpperCase();
18
+ // @ts-expect-error 'missing' is not on the payload type
19
+ payload.missing;
20
+ });
21
+ bot.onInteraction("next", (ctx) => {
22
+ ctx.action.value?.page.toFixed(0);
23
+ // @ts-expect-error 'nope' is not on the action value type
24
+ ctx.action.value?.nope;
25
+ });
26
+ };
27
+ void __handlerTypeGuards;
28
+ /** Recursively find the first node of a given type in an IR tree. */
29
+ function findNode(nodes, type) {
30
+ for (const n of nodes) {
31
+ if (n.type === type)
32
+ return n;
33
+ const children = n.props.children;
34
+ if (Array.isArray(children)) {
35
+ const found = findNode(children, type);
36
+ if (found)
37
+ return found;
38
+ }
39
+ }
40
+ return undefined;
41
+ }
42
+ /** Concatenate all text node values in an IR tree. */
43
+ function collectText(nodes) {
44
+ let out = "";
45
+ for (const n of nodes) {
46
+ if (n.type === "text" && typeof n.props.value === "string")
47
+ out += n.props.value;
48
+ const children = n.props.children;
49
+ if (Array.isArray(children))
50
+ out += collectText(children);
51
+ }
52
+ return out;
53
+ }
54
+ describe("createBot", () => {
55
+ it("routes a mention to a handler that posts UI", async () => {
56
+ const fake = new FakeAdapter();
57
+ const agent = new FakeAgent();
58
+ const bot = createBot({ adapters: [fake], agent: () => agent });
59
+ bot.onMention(async ({ thread }) => {
60
+ await thread.post(Section({ children: "hi" }));
61
+ });
62
+ await bot.start();
63
+ fake.emitTurn({ userText: "yo", conversationKey: "c1" });
64
+ await tick();
65
+ expect(fake.posted.length).toBe(1);
66
+ const ir = fake.posted[0];
67
+ expect(findNode(ir, "section")).toBeDefined();
68
+ expect(collectText(ir)).toBe("hi");
69
+ });
70
+ it("dispatches a bound onClick handler on interaction", async () => {
71
+ const fake = new FakeAdapter();
72
+ const agent = new FakeAgent();
73
+ const bot = createBot({ adapters: [fake], agent: () => agent });
74
+ let clicked = false;
75
+ bot.onMention(async ({ thread }) => {
76
+ await thread.post(Actions({
77
+ children: [
78
+ Button({
79
+ value: { ok: 1 },
80
+ onClick: () => {
81
+ clicked = true;
82
+ },
83
+ children: "Go",
84
+ }),
85
+ ],
86
+ }));
87
+ });
88
+ await bot.start();
89
+ fake.emitTurn({ userText: "yo", conversationKey: "c1" });
90
+ await tick();
91
+ const button = findNode(fake.posted[0], "button");
92
+ const id = button.props.onClick.id;
93
+ expect(typeof id).toBe("string");
94
+ fake.emitInteraction({ id, conversationKey: "c1", value: { ok: 1 } });
95
+ await tick();
96
+ expect(clicked).toBe(true);
97
+ });
98
+ it("merges per-turn runAgent context with the bot-level context", async () => {
99
+ const fake = new FakeAdapter();
100
+ const agent = new FakeAgent();
101
+ // Capture the context/tools passed to the agent's first runAgent call.
102
+ let seenContext;
103
+ let seenTools;
104
+ const origRunAgent = agent.runAgent.bind(agent);
105
+ agent.runAgent = async (parameters, subscriber) => {
106
+ if (seenContext === undefined) {
107
+ seenContext = parameters
108
+ ?.context;
109
+ seenTools = parameters?.tools;
110
+ }
111
+ return origRunAgent(parameters, subscriber);
112
+ };
113
+ const bot = createBot({
114
+ adapters: [fake],
115
+ agent: () => agent,
116
+ context: [{ description: "bot-level", value: "always here" }],
117
+ });
118
+ bot.onMention(async ({ thread }) => {
119
+ await thread.runAgent({
120
+ context: [{ description: "who", value: "user U1" }],
121
+ });
122
+ });
123
+ await bot.start();
124
+ fake.emitTurn({ userText: "go", conversationKey: "c1" });
125
+ await tick();
126
+ expect(seenContext).toEqual([
127
+ { description: "bot-level", value: "always here" },
128
+ { description: "who", value: "user U1" },
129
+ ]);
130
+ expect(seenTools).toEqual([]);
131
+ });
132
+ it("thread.postFile returns a capability-gated error when the adapter can't upload", async () => {
133
+ const fake = new FakeAdapter();
134
+ const agent = new FakeAgent();
135
+ const bot = createBot({ adapters: [fake], agent: () => agent });
136
+ let result;
137
+ bot.onMention(async ({ thread }) => {
138
+ result = await thread.postFile({
139
+ bytes: new Uint8Array([1, 2, 3]),
140
+ filename: "x.png",
141
+ });
142
+ });
143
+ await bot.start();
144
+ fake.emitTurn({ userText: "hi", conversationKey: "c1" });
145
+ await tick();
146
+ expect(result).toEqual({
147
+ ok: false,
148
+ error: "fake does not support file upload",
149
+ });
150
+ });
151
+ it("thread.getMessages and thread.lookupUser surface the adapter's data", async () => {
152
+ const fake = new FakeAdapter();
153
+ fake.messages = [
154
+ { user: { id: "u1", name: "Ada" }, text: "hi", ts: "1", isBot: false },
155
+ ];
156
+ fake.user = { id: "u1", name: "Ada" };
157
+ const agent = new FakeAgent();
158
+ const bot = createBot({ adapters: [fake], agent: () => agent });
159
+ let history;
160
+ let resolved;
161
+ bot.onMention(async ({ thread }) => {
162
+ history = await thread.getMessages();
163
+ resolved = await thread.lookupUser("Ada");
164
+ });
165
+ await bot.start();
166
+ fake.emitTurn({ userText: "hi", conversationKey: "c1" });
167
+ await tick();
168
+ expect(history).toEqual([
169
+ { user: { id: "u1", name: "Ada" }, text: "hi", ts: "1", isBot: false },
170
+ ]);
171
+ expect(resolved).toEqual({ id: "u1", name: "Ada" });
172
+ });
173
+ it("resolves awaitChoice when a matching interaction arrives", async () => {
174
+ const fake = new FakeAdapter();
175
+ const agent = new FakeAgent();
176
+ const bot = createBot({ adapters: [fake], agent: () => agent });
177
+ let choicePromise;
178
+ bot.onMention(async ({ thread }) => {
179
+ choicePromise = thread.awaitChoice(Actions({
180
+ children: [
181
+ Button({
182
+ value: { confirmed: true },
183
+ onClick: () => { },
184
+ children: "Confirm",
185
+ }),
186
+ ],
187
+ }));
188
+ });
189
+ await bot.start();
190
+ fake.emitTurn({ userText: "decide", conversationKey: "c1" });
191
+ await tick();
192
+ const button = findNode(fake.posted[0], "button");
193
+ const id = button.props.onClick.id;
194
+ fake.emitInteraction({
195
+ id,
196
+ conversationKey: "c1",
197
+ value: { confirmed: true },
198
+ });
199
+ await tick();
200
+ expect(choicePromise).toBeDefined();
201
+ await expect(choicePromise).resolves.toEqual({ confirmed: true });
202
+ });
203
+ });
204
+ describe("createBot slash commands", () => {
205
+ it("routes a command to its handler with the raw text", async () => {
206
+ const fake = new FakeAdapter();
207
+ const bot = createBot({ adapters: [fake] });
208
+ let seen;
209
+ bot.onCommand("triage", ({ command, text }) => {
210
+ seen = { command, text };
211
+ });
212
+ await bot.start();
213
+ await fake.emitCommand({ command: "/Triage", text: "db is down" });
214
+ expect(seen).toEqual({ command: "triage", text: "db is down" });
215
+ });
216
+ it("ignores a command with no registered handler", async () => {
217
+ const fake = new FakeAdapter();
218
+ const bot = createBot({ adapters: [fake] });
219
+ let fired = false;
220
+ bot.onCommand("triage", () => {
221
+ fired = true;
222
+ });
223
+ await bot.start();
224
+ await fake.emitCommand({ command: "unknown", text: "x" });
225
+ expect(fired).toBe(false);
226
+ });
227
+ it("parses rawOptions through the command's schema into ctx.options", async () => {
228
+ let captured;
229
+ const fake = new FakeAdapter();
230
+ const bot = createBot({
231
+ adapters: [fake],
232
+ commands: [
233
+ defineBotCommand({
234
+ name: "book",
235
+ options: z.object({ seat: z.string() }),
236
+ handler: ({ options }) => {
237
+ captured = options; // typed { seat: string }
238
+ },
239
+ }),
240
+ ],
241
+ });
242
+ await bot.start();
243
+ await fake.emitCommand({
244
+ command: "book",
245
+ text: "raw",
246
+ rawOptions: { seat: "12A" },
247
+ });
248
+ expect(captured).toEqual({ seat: "12A" });
249
+ });
250
+ it("hands declared commands to adapters that implement registerCommands", async () => {
251
+ const fake = new FakeAdapter();
252
+ const bot = createBot({ adapters: [fake] });
253
+ bot.onCommand("triage", () => { });
254
+ bot.onCommand("status", () => { });
255
+ await bot.start();
256
+ expect(fake.registeredCommands?.map((c) => c.name).sort()).toEqual([
257
+ "status",
258
+ "triage",
259
+ ]);
260
+ });
261
+ });
@@ -0,0 +1,17 @@
1
+ export { createBot } from "./create-bot.js";
2
+ export type { Bot, CreateBotOptions, BotHandler } from "./create-bot.js";
3
+ export { Thread } from "./thread.js";
4
+ export type { ThreadDeps } from "./thread.js";
5
+ export type { PlatformAdapter, RunRenderer, IngressSink, IncomingTurn, InteractionEvent, IncomingCommand, SurfaceCapabilities, ReplyTarget, ConversationStore, AgentSession, CapturedToolCall, CapturedInterrupt, UserQuery, NativePayload, } from "./platform-adapter.js";
6
+ export { defineBotCommand, normalizeCommandName, toCommandSpec, } from "./commands.js";
7
+ export type { BotCommand, CommandContext, CommandSpec } from "./commands.js";
8
+ export { InMemoryActionStore } from "./action-store.js";
9
+ export type { ActionStore, ActionSnapshot } from "./action-store.js";
10
+ export { ActionRegistry, ActionExpiredError } from "./action-registry.js";
11
+ export { toAgentToolDescriptors, parseToolArgs, stringifyHandlerResult, defineBotTool, } from "./tools.js";
12
+ export type { BotTool, ObjectSchema, BotToolContext, ContextEntry, AgentToolDescriptor, } from "./tools.js";
13
+ export { mintId, stableStringify } from "./mint-id.js";
14
+ export { runAgentLoop } from "./run-loop.js";
15
+ export type { RunLoopArgs } from "./run-loop.js";
16
+ export * from "@copilotkit/bot-ui";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,GAAG,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGzE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,YAAY,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,aAAa,GACd,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGrE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1E,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,OAAO,EACP,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,cAAc,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ // Public API for @copilotkit/bot.
2
+ // Bot orchestration
3
+ export { createBot } from "./create-bot.js";
4
+ // Thread
5
+ export { Thread } from "./thread.js";
6
+ // Slash commands
7
+ export { defineBotCommand, normalizeCommandName, toCommandSpec, } from "./commands.js";
8
+ // Action store
9
+ export { InMemoryActionStore } from "./action-store.js";
10
+ // Action registry
11
+ export { ActionRegistry, ActionExpiredError } from "./action-registry.js";
12
+ // Tools & context
13
+ export { toAgentToolDescriptors, parseToolArgs, stringifyHandlerResult, defineBotTool, } from "./tools.js";
14
+ // Id / serialization helpers
15
+ export { mintId, stableStringify } from "./mint-id.js";
16
+ // Run loop
17
+ export { runAgentLoop } from "./run-loop.js";
18
+ // Re-export the bot-ui component vocabulary + types for convenience.
19
+ export * from "@copilotkit/bot-ui";
@@ -0,0 +1,3 @@
1
+ export declare function stableStringify(v: unknown): string;
2
+ export declare function mintId(componentName: string, path: (string | number)[], props: unknown): string;
3
+ //# sourceMappingURL=mint-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mint-id.d.ts","sourceRoot":"","sources":["../src/mint-id.ts"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAYlD;AAED,wBAAgB,MAAM,CACpB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EACzB,KAAK,EAAE,OAAO,GACb,MAAM,CAKR"}
@@ -0,0 +1,20 @@
1
+ import { createHash } from "node:crypto";
2
+ export function stableStringify(v) {
3
+ if (v === null || typeof v !== "object")
4
+ return JSON.stringify(v) ?? "null";
5
+ if (Array.isArray(v))
6
+ return "[" + v.map(stableStringify).join(",") + "]";
7
+ const obj = v;
8
+ const keys = Object.keys(obj).sort();
9
+ return ("{" +
10
+ keys
11
+ .map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k]))
12
+ .join(",") +
13
+ "}");
14
+ }
15
+ export function mintId(componentName, path, props) {
16
+ const h = createHash("sha1")
17
+ .update(`${componentName}|${path.join(".")}|${stableStringify(props)}`)
18
+ .digest("hex");
19
+ return "ck:" + h.slice(0, 16);
20
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mint-id.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mint-id.test.d.ts","sourceRoot":"","sources":["../src/mint-id.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mintId, stableStringify } from "./mint-id.js";
3
+ describe("mintId", () => {
4
+ it("is content-stable for same component/path/props", () => {
5
+ const a = mintId("Flight", [0, "onClick"], { id: "f1" });
6
+ const b = mintId("Flight", [0, "onClick"], { id: "f1" });
7
+ expect(a).toBe(b);
8
+ expect(a.startsWith("ck:")).toBe(true);
9
+ });
10
+ it("differs when props differ", () => {
11
+ expect(mintId("Flight", [0], { id: "f1" })).not.toBe(mintId("Flight", [0], { id: "f2" }));
12
+ });
13
+ it("stableStringify sorts keys", () => {
14
+ expect(stableStringify({ b: 1, a: 2 })).toBe(stableStringify({ a: 2, b: 1 }));
15
+ });
16
+ });
@@ -0,0 +1,125 @@
1
+ import type { AgentSubscriber, AbstractAgent } from "@ag-ui/client";
2
+ import type { BotNode, MessageRef, PlatformUser, ThreadMessage } from "@copilotkit/bot-ui";
3
+ import type { CommandSpec } from "./commands.js";
4
+ /** Opaque to the bot core — created by an adapter during ingress and passed back to post/createRunRenderer. */
5
+ export type ReplyTarget = unknown;
6
+ /** Opaque native payload produced by an adapter's render(). */
7
+ export type NativePayload = unknown;
8
+ export interface SurfaceCapabilities {
9
+ supportsModals: boolean;
10
+ supportsTyping: boolean;
11
+ supportsReactions: boolean;
12
+ supportsStreaming: boolean;
13
+ maxBlocksPerMessage?: number;
14
+ [k: string]: unknown;
15
+ }
16
+ export interface CapturedToolCall {
17
+ toolCallId: string;
18
+ toolCallName: string;
19
+ toolCallArgs: Record<string, unknown>;
20
+ }
21
+ export interface CapturedInterrupt {
22
+ eventName: string;
23
+ value: unknown;
24
+ }
25
+ /** A per-run handle: the AG-UI subscriber to stream into, plus capture accessors the run-loop reads after each runAgent. */
26
+ export interface RunRenderer {
27
+ subscriber: AgentSubscriber;
28
+ markInterrupted(): Promise<void>;
29
+ getCapturedToolCalls(): readonly CapturedToolCall[];
30
+ getPendingInterrupt(): CapturedInterrupt | undefined;
31
+ clearPendingInterrupt(): void;
32
+ }
33
+ export interface IncomingTurn {
34
+ conversationKey: string;
35
+ replyTarget: ReplyTarget;
36
+ userText: string;
37
+ user?: PlatformUser;
38
+ platform: string;
39
+ }
40
+ export interface InteractionEvent {
41
+ id: string;
42
+ conversationKey: string;
43
+ replyTarget: ReplyTarget;
44
+ value?: unknown;
45
+ user?: PlatformUser;
46
+ /** The message the interaction occurred on (the picker), so handlers can update it in place. */
47
+ messageRef?: MessageRef;
48
+ }
49
+ /** A slash-command invocation normalized by an adapter. */
50
+ export interface IncomingCommand {
51
+ /** Command name as invoked (a leading slash and case are normalized by the engine). */
52
+ command: string;
53
+ /** Raw argument string after the command name (the form text-only surfaces deliver). */
54
+ text: string;
55
+ /** Structured, pre-parsed options when the surface delivers them (e.g. Discord). */
56
+ rawOptions?: Record<string, unknown>;
57
+ conversationKey: string;
58
+ replyTarget: ReplyTarget;
59
+ user?: PlatformUser;
60
+ platform: string;
61
+ }
62
+ export interface IngressSink {
63
+ onTurn(turn: IncomingTurn): void | Promise<void>;
64
+ onInteraction(evt: InteractionEvent): void | Promise<void>;
65
+ /** A slash command fired. Routed to the matching `bot.onCommand` handler (ignored if none). */
66
+ onCommand(cmd: IncomingCommand): void | Promise<void>;
67
+ }
68
+ export interface UserQuery {
69
+ query: string;
70
+ }
71
+ /** A resolved agent session for a conversation (the adapter may build the agent's history from its own state). */
72
+ export interface AgentSession {
73
+ agent: AbstractAgent;
74
+ }
75
+ /** Adapter-owned conversation state; the adapter resolves (or creates) the agent session for a conversation. */
76
+ export interface ConversationStore {
77
+ getOrCreate(conversationKey: string, replyTarget: ReplyTarget, makeAgent: (threadId: string) => AbstractAgent): Promise<AgentSession>;
78
+ }
79
+ export interface PlatformAdapter {
80
+ readonly platform: string;
81
+ readonly capabilities: SurfaceCapabilities;
82
+ readonly ackDeadlineMs: number;
83
+ start(sink: IngressSink): Promise<void>;
84
+ stop(): Promise<void>;
85
+ render(ir: BotNode[]): NativePayload;
86
+ post(target: ReplyTarget, ir: BotNode[]): Promise<MessageRef>;
87
+ update(ref: MessageRef, ir: BotNode[]): Promise<void>;
88
+ stream(target: ReplyTarget, chunks: AsyncIterable<string>): Promise<MessageRef>;
89
+ delete(ref: MessageRef): Promise<void>;
90
+ createRunRenderer(target: ReplyTarget): RunRenderer;
91
+ decodeInteraction(raw: unknown): InteractionEvent | undefined;
92
+ lookupUser(q: UserQuery): Promise<PlatformUser | undefined>;
93
+ readonly conversationStore: ConversationStore;
94
+ /**
95
+ * Optional conversation-history read. Backs the capability-gated
96
+ * `Thread.getMessages()`; adapters that can't read history simply omit this,
97
+ * and `Thread.getMessages()` returns `[]`.
98
+ */
99
+ getMessages?(target: ReplyTarget): Promise<ThreadMessage[]>;
100
+ /**
101
+ * Optional platform file upload. Threads expose `postFile` unconditionally;
102
+ * adapters that can't upload simply omit this, and `Thread.postFile` returns
103
+ * a capability-gated `{ ok: false, error }`.
104
+ */
105
+ postFile?(target: ReplyTarget, args: {
106
+ bytes: Uint8Array;
107
+ filename: string;
108
+ title?: string;
109
+ altText?: string;
110
+ }): Promise<{
111
+ ok: boolean;
112
+ fileId?: string;
113
+ error?: string;
114
+ }>;
115
+ /**
116
+ * Optional slash-command support. Called once on `start()` with the bot's
117
+ * declared commands, so a surface that registers commands up front (e.g.
118
+ * Discord's application-command API) can publish them. Surfaces that match
119
+ * commands dynamically (e.g. Slack, which forwards every `/command` to
120
+ * `sink.onCommand`) need not implement this; adapters that don't support
121
+ * commands at all simply omit it and command handlers never fire there.
122
+ */
123
+ registerCommands?(commands: readonly CommandSpec[]): void | Promise<void>;
124
+ }
125
+ //# sourceMappingURL=platform-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-adapter.d.ts","sourceRoot":"","sources":["../src/platform-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};