@copilotkit/bot 0.0.1 → 0.0.3

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 CHANGED
@@ -44,6 +44,9 @@ await bot.start();
44
44
  `{ thread, message }`. (Routing is mention-preferred: if any mention
45
45
  handler is registered, all turns route to it; otherwise message handlers
46
46
  fire.)
47
+ - `onThreadStarted(handler)` — a conversation surface opened (e.g. the Slack
48
+ assistant pane); receives `{ thread, user? }`. Greet, set suggested prompts
49
+ or a title, or run the agent. Adapters without the concept never fire it.
47
50
  - `onInteraction<TValue>(id, handler)` — explicit escape-hatch handler for a
48
51
  known action id, bypassing the registry; `ctx.action.value` is typed `TValue`.
49
52
  - `onInterrupt<TPayload>(eventName, handler)` — handle a captured agent
@@ -80,6 +83,12 @@ interface Thread {
80
83
  }): Promise<MessageRef | undefined>;
81
84
  resume(value: unknown): Promise<MessageRef | undefined>;
82
85
  awaitChoice<T = unknown>(ui: Renderable): Promise<T>;
86
+ // Capability-gated (return { ok: false } on surfaces without support):
87
+ setSuggestedPrompts(
88
+ prompts: ReadonlyArray<{ title: string; message: string }>,
89
+ opts?: { title?: string },
90
+ ): Promise<{ ok: boolean; error?: string }>;
91
+ setTitle(title: string): Promise<{ ok: boolean; error?: string }>;
83
92
  }
84
93
  ```
85
94
 
@@ -158,8 +167,9 @@ pass it as `actionStore` to make actions survive restarts.
158
167
 
159
168
  To target a new surface, implement `PlatformAdapter` from this package. The
160
169
  engine drives ingress through the `IngressSink` you receive in `start(sink)`
161
- (`sink.onTurn(IncomingTurn)` / `sink.onInteraction(InteractionEvent)`) and
162
- egress through your `post` / `update` / `stream` / `delete` (which receive
170
+ (`sink.onTurn(IncomingTurn)` / `sink.onInteraction(InteractionEvent)` /
171
+ `sink.onCommand(IncomingCommand)` / `sink.onThreadStarted(IncomingThreadStart)`)
172
+ and egress through your `post` / `update` / `stream` / `delete` (which receive
163
173
  `BotNode[]` to translate to a native payload via `render`). You also provide
164
174
  `createRunRenderer(target)` (an AG-UI `RunRenderer`: the subscriber to stream
165
175
  into, plus accessors for captured tool calls and interrupts that the run-loop
@@ -168,7 +178,10 @@ reads after each `runAgent`), `decodeInteraction(raw)` (native event → opaque
168
178
  (`getOrCreate` → `AgentSession`), and the surface `capabilities` /
169
179
  `ackDeadlineMs`. Optional capability methods like `getMessages(target)` and
170
180
  `postFile(target, args)` back the matching `thread` methods when the surface
171
- supports them. Slash commands are also capability-gated: an adapter forwards
181
+ supports them likewise `setSuggestedPrompts(target, prompts, opts?)` and
182
+ `setThreadTitle(target, title)` back `thread.setSuggestedPrompts` /
183
+ `thread.setTitle`, and `sink.onThreadStarted(...)` emits the "conversation
184
+ opened" lifecycle event. Slash commands are also capability-gated: an adapter forwards
172
185
  invocations via `sink.onCommand(IncomingCommand)`, and may implement
173
186
  `registerCommands(specs)` to publish the bot's declared commands up front
174
187
  (e.g. Discord's application-command API); adapters that omit it are skipped.
@@ -176,9 +189,10 @@ See `@copilotkit/bot-slack` for a complete implementation.
176
189
 
177
190
  ## Exports
178
191
 
179
- `createBot`, `Bot`, `CreateBotOptions`, `BotHandler`; `Thread`; the
180
- `PlatformAdapter` boundary types (`RunRenderer`, `IngressSink`,
181
- `IncomingTurn`, `InteractionEvent`, `IncomingCommand`, `SurfaceCapabilities`,
192
+ `createBot`, `Bot`, `CreateBotOptions`, `BotHandler`, `ThreadStartHandler`;
193
+ `Thread`; the `PlatformAdapter` boundary types (`RunRenderer`, `IngressSink`,
194
+ `IncomingTurn`, `InteractionEvent`, `IncomingCommand`, `IncomingThreadStart`,
195
+ `SurfaceCapabilities`,
182
196
  `ReplyTarget`, `ConversationStore`, `AgentSession`, `CapturedToolCall`,
183
197
  `CapturedInterrupt`, `UserQuery`); `ActionStore` / `InMemoryActionStore` /
184
198
  `ActionSnapshot` / `ActionRegistry` / `ActionExpiredError`; `BotTool` /
@@ -15,6 +15,12 @@ export declare class ActionRegistry {
15
15
  bindTree(componentName: string, props: Record<string, unknown>, conversationKey: string): Promise<BotNode[]>;
16
16
  bindRenderable(ui: Renderable, conversationKey: string): Promise<BotNode[]>;
17
17
  private walk;
18
+ /**
19
+ * Run the click handler for `id` and return the clicked element's `value`
20
+ * (so callers can resolve a HITL `awaitChoice` waiter even when the platform
21
+ * couldn't carry the value in its callback payload). Returns `undefined` when
22
+ * the element has no `value`.
23
+ */
18
24
  dispatch(id: string, ctx: InteractionContext): Promise<unknown>;
19
25
  }
20
26
  //# sourceMappingURL=action-registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"action-registry.d.ts","sourceRoot":"","sources":["../src/action-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAEP,kBAAkB,EAClB,WAAW,EACX,UAAU,EACX,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,EAAE,EAAE,MAAM;CAIvB;AAcD,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,GAAG,CAAmC;gBAElC,IAAI,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE;IAIxC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI;IAItD,aAAa,IAAI,IAAI;IAMf,QAAQ,CACZ,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,EAAE,CAAC;IAYf,cAAc,CAClB,EAAE,EAAE,UAAU,EACd,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,EAAE,CAAC;YAgBP,IAAI;IAuCZ,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;CAetE"}
1
+ {"version":3,"file":"action-registry.d.ts","sourceRoot":"","sources":["../src/action-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAEP,kBAAkB,EAClB,WAAW,EACX,UAAU,EACX,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,EAAE,EAAE,MAAM;CAIvB;AAcD,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,UAAU,CAAkC;IAKpD,OAAO,CAAC,GAAG,CAAgE;gBAE/D,IAAI,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE;IAIxC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI;IAItD,aAAa,IAAI,IAAI;IAMf,QAAQ,CACZ,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,EAAE,CAAC;IAYf,cAAc,CAClB,EAAE,EAAE,UAAU,EACd,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,EAAE,CAAC;YAgBP,IAAI;IA0ClB;;;;;OAKG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;CAsBtE"}
@@ -15,6 +15,10 @@ function isComponentElement(ui) {
15
15
  export class ActionRegistry {
16
16
  store;
17
17
  components = new Map();
18
+ // Cache the handler AND the element's `value` per minted id. The value is
19
+ // needed to resolve HITL `awaitChoice` waiters on platforms whose callback
20
+ // payload can't carry it (e.g. Telegram's 64-byte callback_data only holds
21
+ // the action id), where `evt.value` arrives undefined.
18
22
  hot = new Map();
19
23
  constructor(opts) {
20
24
  this.store = opts.store;
@@ -58,7 +62,10 @@ export class ActionRegistry {
58
62
  if (typeof handler === "function") {
59
63
  const fullPath = [...path, ep];
60
64
  const id = mintId(comp, fullPath, props);
61
- this.hot.set(id, handler);
65
+ this.hot.set(id, {
66
+ handler: handler,
67
+ value: node.props.value,
68
+ });
62
69
  await this.store.put(id, {
63
70
  component: comp,
64
71
  props,
@@ -75,9 +82,21 @@ export class ActionRegistry {
75
82
  }
76
83
  }
77
84
  }
85
+ /**
86
+ * Run the click handler for `id` and return the clicked element's `value`
87
+ * (so callers can resolve a HITL `awaitChoice` waiter even when the platform
88
+ * couldn't carry the value in its callback payload). Returns `undefined` when
89
+ * the element has no `value`.
90
+ */
78
91
  async dispatch(id, ctx) {
79
- let handler = this.hot.get(id);
80
- if (!handler) {
92
+ let handler;
93
+ let value;
94
+ const hot = this.hot.get(id);
95
+ if (hot) {
96
+ handler = hot.handler;
97
+ value = hot.value;
98
+ }
99
+ else {
81
100
  const snap = await this.store.get(id);
82
101
  if (!snap || !snap.component)
83
102
  throw new ActionExpiredError(id);
@@ -86,11 +105,26 @@ export class ActionRegistry {
86
105
  throw new ActionExpiredError(id);
87
106
  const tree = renderToIR(fn(snap.props));
88
107
  handler = pluck(tree, snap.path);
108
+ value = pluckValue(tree, snap.path);
89
109
  if (!handler)
90
110
  throw new ActionExpiredError(id);
91
111
  }
92
- return handler({ ...ctx, action: { ...ctx.action, id } });
112
+ await handler({ ...ctx, action: { ...ctx.action, id } });
113
+ return value;
114
+ }
115
+ }
116
+ /** Navigate to the node owning the event-prop at `path` and read its `value`. */
117
+ function pluckValue(tree, path) {
118
+ let cur = tree;
119
+ for (const seg of path.slice(0, -1)) {
120
+ if (Array.isArray(cur))
121
+ cur = cur[seg];
122
+ else if (cur && typeof cur === "object")
123
+ cur = cur.props?.[seg];
124
+ else
125
+ return undefined;
93
126
  }
127
+ return cur?.props?.value;
94
128
  }
95
129
  function pluck(tree, path) {
96
130
  let cur = tree;
@@ -1,6 +1,10 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
2
  import { ActionRegistry, ActionExpiredError } from "./action-registry.js";
3
3
  import { InMemoryActionStore } from "./action-store.js";
4
+ // Records each click so a test can assert the handler ran — dispatch() now
5
+ // returns the clicked element's `value` (needed to resolve HITL waiters on
6
+ // platforms whose callback payload can't carry it), not the handler's return.
7
+ const clicks = [];
4
8
  function Confirm(props) {
5
9
  return {
6
10
  type: "actions",
@@ -9,7 +13,10 @@ function Confirm(props) {
9
13
  {
10
14
  type: "button",
11
15
  props: {
12
- onClick: ({ action }) => `ok:${props.action}:${action.id}`,
16
+ value: { ok: props.action },
17
+ onClick: ({ action }) => {
18
+ clicks.push(`ok:${props.action}:${action.id}`);
19
+ },
13
20
  children: "Yes",
14
21
  },
15
22
  },
@@ -19,24 +26,29 @@ function Confirm(props) {
19
26
  }
20
27
  const ctx = {};
21
28
  describe("ActionRegistry", () => {
22
- it("binds onClick handlers and dispatches via hot cache", async () => {
29
+ beforeEach(() => {
30
+ clicks.length = 0;
31
+ });
32
+ it("dispatches via hot cache, runs the handler, and returns the element value", async () => {
23
33
  const reg = new ActionRegistry({ store: new InMemoryActionStore() });
24
34
  reg.registerComponent("Confirm", Confirm);
25
35
  const ir = await reg.bindTree("Confirm", { action: "write" }, "conv1");
26
36
  const button = ir[0].props.children[0];
27
37
  const id = button.props.onClick.id;
28
38
  expect(typeof id).toBe("string");
29
- const out = await reg.dispatch(id, ctx);
30
- expect(out).toContain("ok:write:");
39
+ const value = await reg.dispatch(id, ctx);
40
+ expect(value).toEqual({ ok: "write" });
41
+ expect(clicks[0]).toContain("ok:write:");
31
42
  });
32
- it("cold path re-renders from snapshot when hot cache is cleared", async () => {
43
+ it("cold path re-renders from snapshot when hot cache is cleared, still returning the value", async () => {
33
44
  const reg = new ActionRegistry({ store: new InMemoryActionStore() });
34
45
  reg.registerComponent("Confirm", Confirm);
35
46
  const ir = await reg.bindTree("Confirm", { action: "write" }, "conv1");
36
47
  const id = ir[0].props.children[0].props.onClick.id;
37
48
  reg.clearHotCache();
38
- const out = await reg.dispatch(id, ctx);
39
- expect(out).toContain("ok:write:");
49
+ const value = await reg.dispatch(id, ctx);
50
+ expect(value).toEqual({ ok: "write" });
51
+ expect(clicks[0]).toContain("ok:write:");
40
52
  });
41
53
  it("throws ActionExpiredError on full miss", async () => {
42
54
  const reg = new ActionRegistry({ store: new InMemoryActionStore() });
@@ -1,14 +1,19 @@
1
1
  import type { PlatformAdapter } from "./platform-adapter.js";
2
- import { type ActionStore } from "./action-store.js";
3
- import { type BotTool, type ContextEntry } from "./tools.js";
4
- import { type BotCommand, type CommandContext } from "./commands.js";
2
+ import type { ActionStore } from "./action-store.js";
3
+ import type { BotTool, ContextEntry } from "./tools.js";
4
+ import type { BotCommand, CommandContext } from "./commands.js";
5
5
  import { Thread } from "./thread.js";
6
6
  import type { AbstractAgent } from "@ag-ui/client";
7
- import type { InteractionContext, IncomingMessage } from "@copilotkit/bot-ui";
7
+ import type { InteractionContext, IncomingMessage, PlatformUser } from "@copilotkit/bot-ui";
8
8
  export type BotHandler = (ctx: {
9
9
  thread: Thread;
10
10
  message: IncomingMessage;
11
11
  }) => void | Promise<void>;
12
+ /** Handler for a "conversation opened" lifecycle event (e.g. the Slack assistant pane). */
13
+ export type ThreadStartHandler = (ctx: {
14
+ thread: Thread;
15
+ user?: PlatformUser;
16
+ }) => void | Promise<void>;
12
17
  export interface CreateBotOptions {
13
18
  adapters: PlatformAdapter[];
14
19
  agent?: AbstractAgent | ((threadId: string) => AbstractAgent);
@@ -21,6 +26,12 @@ export interface CreateBotOptions {
21
26
  export interface Bot {
22
27
  onMention(h: BotHandler): void;
23
28
  onMessage(h: BotHandler): void;
29
+ /**
30
+ * A conversation surface opened (e.g. the Slack assistant pane). Greet, set
31
+ * suggested prompts, set a title, or run the agent. Adapters without the
32
+ * concept never fire this.
33
+ */
34
+ onThreadStarted(h: ThreadStartHandler): void;
24
35
  /** Handle clicks on a specific action `id`. `ctx.action.value` is typed as `TValue`. */
25
36
  onInteraction<TValue = unknown>(id: string, h: (ctx: InteractionContext<TValue>) => void | Promise<void>): void;
26
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"create-bot.d.ts","sourceRoot":"","sources":["../src/create-bot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAmB,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,CAAC;CAC1B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,aAAa,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,CAAC,CAAC;IAC9D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,GAAG;IAClB,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,wFAAwF;IACxF,aAAa,CAAC,MAAM,GAAG,OAAO,EAC5B,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC3D,IAAI,CAAC;IACR;;;;OAIG;IACH,WAAW,CAAC,QAAQ,GAAG,OAAO,EAC5B,SAAS,EAAE,MAAM,EACjB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACvE,IAAI,CAAC;IACR,8DAA8D;IAC9D,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACrC,kDAAkD;IAClD,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACrD,IAAI,CAAC;IACR,IAAI,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,GAAG,CA6MrD"}
1
+ {"version":3,"file":"create-bot.d.ts","sourceRoot":"","sources":["../src/create-bot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAMhB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EACf,YAAY,EACb,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,CAAC;CAC1B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3B,2FAA2F;AAC3F,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,aAAa,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,CAAC,CAAC;IAC9D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,GAAG;IAClB,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,eAAe,CAAC,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC7C,wFAAwF;IACxF,aAAa,CAAC,MAAM,GAAG,OAAO,EAC5B,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC3D,IAAI,CAAC;IACR;;;;OAIG;IACH,WAAW,CAAC,QAAQ,GAAG,OAAO,EAC5B,SAAS,EAAE,MAAM,EACjB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACvE,IAAI,CAAC;IACR,8DAA8D;IAC9D,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACrC,kDAAkD;IAClD,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACrD,IAAI,CAAC;IACR,IAAI,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,GAAG,CAqOrD"}
@@ -1,7 +1,7 @@
1
1
  import { ActionRegistry, ActionExpiredError } from "./action-registry.js";
2
2
  import { InMemoryActionStore } from "./action-store.js";
3
- import { toAgentToolDescriptors, parseToolArgs, } from "./tools.js";
4
- import { normalizeCommandName, toCommandSpec, } from "./commands.js";
3
+ import { toAgentToolDescriptors, parseToolArgs } from "./tools.js";
4
+ import { normalizeCommandName, toCommandSpec } from "./commands.js";
5
5
  import { Thread } from "./thread.js";
6
6
  export function createBot(opts) {
7
7
  const registry = new ActionRegistry({
@@ -23,6 +23,7 @@ export function createBot(opts) {
23
23
  const context = opts.context ?? [];
24
24
  const mentionHandlers = [];
25
25
  const messageHandlers = [];
26
+ const threadStartedHandlers = [];
26
27
  const interactionHandlers = new Map();
27
28
  const interruptHandlers = new Map();
28
29
  const commandHandlers = new Map();
@@ -52,6 +53,7 @@ export function createBot(opts) {
52
53
  const thread = makeThread(adapter, turn.replyTarget, turn.conversationKey);
53
54
  const message = {
54
55
  text: turn.userText,
56
+ contentParts: turn.contentParts,
55
57
  user: turn.user ?? { id: "" },
56
58
  ref: { id: "" },
57
59
  platform: turn.platform,
@@ -80,13 +82,18 @@ export function createBot(opts) {
80
82
  user,
81
83
  platform: adapter.platform,
82
84
  };
85
+ // The clicked element's `value`, recovered by the registry when it
86
+ // re-renders to find the handler. Used to resolve a HITL waiter on
87
+ // platforms whose callback payload can't carry the value (Telegram),
88
+ // where `evt.value` is undefined.
89
+ let dispatchedValue;
83
90
  try {
84
91
  const explicit = interactionHandlers.get(evt.id);
85
92
  if (explicit) {
86
93
  await explicit(ctx);
87
94
  }
88
95
  else {
89
- await registry.dispatch(evt.id, ctx);
96
+ dispatchedValue = await registry.dispatch(evt.id, ctx);
90
97
  }
91
98
  }
92
99
  catch (err) {
@@ -94,11 +101,13 @@ export function createBot(opts) {
94
101
  if (!(err instanceof ActionExpiredError))
95
102
  throw err;
96
103
  }
97
- // Resolve any HITL waiter awaiting a choice in this conversation.
104
+ // Resolve any HITL waiter awaiting a choice in this conversation. Prefer
105
+ // the value carried in the event (Slack), falling back to the value the
106
+ // registry recovered from the rendered element (Telegram).
98
107
  const w = waiters.get(evt.conversationKey);
99
108
  if (w) {
100
109
  waiters.delete(evt.conversationKey);
101
- w(evt.value);
110
+ w(evt.value !== undefined ? evt.value : dispatchedValue);
102
111
  }
103
112
  },
104
113
  async onCommand(cmd) {
@@ -125,6 +134,14 @@ export function createBot(opts) {
125
134
  };
126
135
  await command.handler(ctx);
127
136
  },
137
+ async onThreadStarted(evt) {
138
+ // The adapter has already applied its static defaults (greeting /
139
+ // prompts) before emitting this, so handlers layer on top and never
140
+ // race. Zero handlers → no-op.
141
+ const thread = makeThread(adapter, evt.replyTarget, evt.conversationKey);
142
+ for (const h of threadStartedHandlers)
143
+ await h({ thread, user: evt.user });
144
+ },
128
145
  };
129
146
  }
130
147
  return {
@@ -134,6 +151,9 @@ export function createBot(opts) {
134
151
  onMessage(h) {
135
152
  messageHandlers.push(h);
136
153
  },
154
+ onThreadStarted(h) {
155
+ threadStartedHandlers.push(h);
156
+ },
137
157
  onInteraction(id, h) {
138
158
  interactionHandlers.set(id, h);
139
159
  },
@@ -67,6 +67,58 @@ describe("createBot", () => {
67
67
  expect(findNode(ir, "section")).toBeDefined();
68
68
  expect(collectText(ir)).toBe("hi");
69
69
  });
70
+ it("calls renderer.finish() once after a turn's run-loop resolves", async () => {
71
+ const fake = new FakeAdapter();
72
+ const agent = new FakeAgent();
73
+ const bot = createBot({ adapters: [fake], agent: () => agent });
74
+ bot.onMention(async ({ thread }) => {
75
+ await thread.runAgent();
76
+ });
77
+ await bot.start();
78
+ fake.emitTurn({ userText: "yo", conversationKey: "c1" });
79
+ await tick();
80
+ const renderer = fake.lastRunRenderer;
81
+ expect(renderer.finishCalls).toBe(1);
82
+ });
83
+ it("delivers a turn's contentParts as the runAgent prompt to agent.addMessage", async () => {
84
+ const fake = new FakeAdapter();
85
+ const agent = new FakeAgent();
86
+ // Capture what the framework injects as the user message.
87
+ const added = [];
88
+ const origAddMessage = agent.addMessage.bind(agent);
89
+ agent.addMessage = (m) => {
90
+ added.push(m);
91
+ return origAddMessage(m);
92
+ };
93
+ const bot = createBot({ adapters: [fake], agent: () => agent });
94
+ const parts = [
95
+ { type: "text", text: "look" },
96
+ {
97
+ type: "image",
98
+ source: { type: "data", value: "QUJD", mimeType: "image/png" },
99
+ },
100
+ ];
101
+ bot.onMention(async ({ thread, message }) => {
102
+ // The example mirrors this: prefer multimodal parts over plain text.
103
+ await thread.runAgent({
104
+ prompt: message.contentParts && message.contentParts.length > 0
105
+ ? message.contentParts
106
+ : message.text,
107
+ });
108
+ });
109
+ await bot.start();
110
+ fake.emitTurn({
111
+ userText: "look",
112
+ conversationKey: "c1",
113
+ contentParts: parts,
114
+ });
115
+ await tick();
116
+ expect(added).toHaveLength(1);
117
+ const msg = added[0];
118
+ expect(msg.role).toBe("user");
119
+ // The multimodal parts array survives the string-typed `content` cast.
120
+ expect(msg.content).toEqual(parts);
121
+ });
70
122
  it("dispatches a bound onClick handler on interaction", async () => {
71
123
  const fake = new FakeAdapter();
72
124
  const agent = new FakeAgent();
@@ -95,6 +147,34 @@ describe("createBot", () => {
95
147
  await tick();
96
148
  expect(clicked).toBe(true);
97
149
  });
150
+ it("resolves a HITL awaitChoice with the element value when the event carries none (Telegram)", async () => {
151
+ const fake = new FakeAdapter();
152
+ const agent = new FakeAgent();
153
+ const bot = createBot({ adapters: [fake], agent: () => agent });
154
+ let chosen;
155
+ bot.onMention(async ({ thread }) => {
156
+ chosen = await thread.awaitChoice(Actions({
157
+ children: [
158
+ Button({
159
+ value: { confirmed: true },
160
+ onClick: () => { },
161
+ children: "Create",
162
+ }),
163
+ ],
164
+ }));
165
+ });
166
+ await bot.start();
167
+ fake.emitTurn({ userText: "create a thing", conversationKey: "c1" });
168
+ await tick();
169
+ const button = findNode(fake.posted[0], "button");
170
+ const id = button.props.onClick.id;
171
+ // Telegram can't carry the button value in callback_data, so the event has
172
+ // no `value`. The waiter must still resolve with the button's value, which
173
+ // the registry recovers from the rendered element.
174
+ fake.emitInteraction({ id, conversationKey: "c1" });
175
+ await tick();
176
+ expect(chosen).toEqual({ confirmed: true });
177
+ });
98
178
  it("merges per-turn runAgent context with the bot-level context", async () => {
99
179
  const fake = new FakeAdapter();
100
180
  const agent = new FakeAgent();
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export { createBot } from "./create-bot.js";
2
- export type { Bot, CreateBotOptions, BotHandler } from "./create-bot.js";
2
+ export type { Bot, CreateBotOptions, BotHandler, ThreadStartHandler, } from "./create-bot.js";
3
3
  export { Thread } from "./thread.js";
4
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";
5
+ export type { PlatformAdapter, RunRenderer, IngressSink, IncomingTurn, InteractionEvent, IncomingCommand, IncomingThreadStart, SurfaceCapabilities, ReplyTarget, ConversationStore, AgentSession, CapturedToolCall, CapturedInterrupt, UserQuery, NativePayload, } from "./platform-adapter.js";
6
6
  export { defineBotCommand, normalizeCommandName, toCommandSpec, } from "./commands.js";
7
7
  export type { BotCommand, CommandContext, CommandSpec } from "./commands.js";
8
8
  export { InMemoryActionStore } from "./action-store.js";
@@ -1 +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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EACV,GAAG,EACH,gBAAgB,EAChB,UAAU,EACV,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAGzB,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,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"}
@@ -1,5 +1,5 @@
1
1
  import type { AgentSubscriber, AbstractAgent } from "@ag-ui/client";
2
- import type { BotNode, MessageRef, PlatformUser, ThreadMessage } from "@copilotkit/bot-ui";
2
+ import type { AgentContentPart, BotNode, MessageRef, PlatformUser, ThreadMessage } from "@copilotkit/bot-ui";
3
3
  import type { CommandSpec } from "./commands.js";
4
4
  /** Opaque to the bot core — created by an adapter during ingress and passed back to post/createRunRenderer. */
5
5
  export type ReplyTarget = unknown;
@@ -11,6 +11,10 @@ export interface SurfaceCapabilities {
11
11
  supportsReactions: boolean;
12
12
  supportsStreaming: boolean;
13
13
  maxBlocksPerMessage?: number;
14
+ /** Pinned prompt chips on a conversation surface (Slack assistant pane). */
15
+ supportsSuggestedPrompts?: boolean;
16
+ /** Nameable conversations (Slack assistant-thread titles). */
17
+ supportsThreadTitle?: boolean;
14
18
  [k: string]: unknown;
15
19
  }
16
20
  export interface CapturedToolCall {
@@ -29,11 +33,26 @@ export interface RunRenderer {
29
33
  getCapturedToolCalls(): readonly CapturedToolCall[];
30
34
  getPendingInterrupt(): CapturedInterrupt | undefined;
31
35
  clearPendingInterrupt(): void;
36
+ /**
37
+ * Optional turn-end hook. Called once after the run-loop resolves normally
38
+ * (no more tool calls, or an interrupt was acked), so a renderer that keeps a
39
+ * turn-scoped resource open across `runAgent` iterations — e.g. a single
40
+ * native streaming message that interleaves text and tool-progress — can
41
+ * finalize it. Symmetric with {@link markInterrupted}; renderers whose
42
+ * streams self-terminate per message simply omit it. Must be a no-op if the
43
+ * run was already interrupted.
44
+ */
45
+ finish?(): Promise<void>;
32
46
  }
33
47
  export interface IncomingTurn {
34
48
  conversationKey: string;
35
49
  replyTarget: ReplyTarget;
36
50
  userText: string;
51
+ /**
52
+ * Optional multimodal content parts built by the adapter (e.g. inbound
53
+ * image/file attachments). Carried through to `IncomingMessage.contentParts`.
54
+ */
55
+ contentParts?: AgentContentPart[];
37
56
  user?: PlatformUser;
38
57
  platform: string;
39
58
  }
@@ -59,11 +78,23 @@ export interface IncomingCommand {
59
78
  user?: PlatformUser;
60
79
  platform: string;
61
80
  }
81
+ /**
82
+ * A "conversation opened" lifecycle event (Slack: `assistant_thread_started`).
83
+ * Adapters without the concept never emit it.
84
+ */
85
+ export interface IncomingThreadStart {
86
+ conversationKey: string;
87
+ replyTarget: ReplyTarget;
88
+ user?: PlatformUser;
89
+ platform: string;
90
+ }
62
91
  export interface IngressSink {
63
92
  onTurn(turn: IncomingTurn): void | Promise<void>;
64
93
  onInteraction(evt: InteractionEvent): void | Promise<void>;
65
94
  /** A slash command fired. Routed to the matching `bot.onCommand` handler (ignored if none). */
66
95
  onCommand(cmd: IncomingCommand): void | Promise<void>;
96
+ /** A conversation surface opened. Adapters without the concept never call it. */
97
+ onThreadStarted(evt: IncomingThreadStart): void | Promise<void>;
67
98
  }
68
99
  export interface UserQuery {
69
100
  query: string;
@@ -121,5 +152,29 @@ export interface PlatformAdapter {
121
152
  * commands at all simply omit it and command handlers never fire there.
122
153
  */
123
154
  registerCommands?(commands: readonly CommandSpec[]): void | Promise<void>;
155
+ /**
156
+ * Optional: pin suggested prompts on a conversation surface (backs the
157
+ * capability-gated `Thread.setSuggestedPrompts`). Adapters without the
158
+ * concept omit this, and `Thread.setSuggestedPrompts` returns
159
+ * `{ ok: false, error }` without throwing.
160
+ */
161
+ setSuggestedPrompts?(target: ReplyTarget, prompts: ReadonlyArray<{
162
+ title: string;
163
+ message: string;
164
+ }>, opts?: {
165
+ title?: string;
166
+ }): Promise<{
167
+ ok: boolean;
168
+ error?: string;
169
+ }>;
170
+ /**
171
+ * Optional: set the conversation's display title (backs `Thread.setTitle`).
172
+ * Adapters without the concept omit this, and `Thread.setTitle` returns
173
+ * `{ ok: false, error }` without throwing.
174
+ */
175
+ setThreadTitle?(target: ReplyTarget, title: string): Promise<{
176
+ ok: boolean;
177
+ error?: string;
178
+ }>;
124
179
  }
125
180
  //# sourceMappingURL=platform-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform-adapter.d.ts","sourceRoot":"","sources":["../src/platform-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform-adapter.d.ts","sourceRoot":"","sources":["../src/platform-adapter.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ describe("FakeAdapter", () => {
10
10
  },
11
11
  onInteraction: () => { },
12
12
  onCommand: () => { },
13
+ onThreadStarted: () => { },
13
14
  });
14
15
  a.emitTurn({ userText: "hi" });
15
16
  expect(got).toBe("hi");
@@ -25,6 +26,7 @@ describe("FakeAdapter", () => {
25
26
  id = e.id;
26
27
  },
27
28
  onCommand: () => { },
29
+ onThreadStarted: () => { },
28
30
  });
29
31
  a.emitInteraction({ id: "ck:abc" });
30
32
  expect(id).toBe("ck:abc");
@@ -1,5 +1,5 @@
1
1
  import type { BotNode, MessageRef, PlatformUser, ThreadMessage } from "@copilotkit/bot-ui";
2
- import type { PlatformAdapter, SurfaceCapabilities, IngressSink, IncomingTurn, InteractionEvent, IncomingCommand, RunRenderer, ReplyTarget, NativePayload, UserQuery, ConversationStore } from "../platform-adapter.js";
2
+ import type { PlatformAdapter, SurfaceCapabilities, IngressSink, IncomingTurn, IncomingThreadStart, InteractionEvent, IncomingCommand, RunRenderer, ReplyTarget, NativePayload, UserQuery, ConversationStore } from "../platform-adapter.js";
3
3
  import type { CommandSpec } from "../commands.js";
4
4
  /** A RunRenderer whose subscriber captures tool-call-end and custom (interrupt) events — used by run-loop tests. */
5
5
  export declare function makeFakeRunRenderer(): RunRenderer;
@@ -7,6 +7,15 @@ export declare class FakeAdapter implements PlatformAdapter {
7
7
  readonly platform = "fake";
8
8
  readonly capabilities: SurfaceCapabilities;
9
9
  readonly ackDeadlineMs = 3000;
10
+ /**
11
+ * @param fakeOpts.paneMethods When `false`, the optional
12
+ * `setSuggestedPrompts`/`setThreadTitle` methods are omitted (and the
13
+ * matching capability flags cleared) so tests can exercise the
14
+ * capability-gated `{ ok: false }` path. Defaults to present.
15
+ */
16
+ constructor(fakeOpts?: {
17
+ paneMethods?: boolean;
18
+ });
10
19
  readonly conversationStore: ConversationStore;
11
20
  posted: BotNode[][];
12
21
  updated: {
@@ -32,7 +41,26 @@ export declare class FakeAdapter implements PlatformAdapter {
32
41
  decodeInteraction(raw: unknown): InteractionEvent | undefined;
33
42
  lookupUser(_q: UserQuery): Promise<PlatformUser | undefined>;
34
43
  getMessages(_target: ReplyTarget): Promise<ThreadMessage[]>;
44
+ /** Suggested-prompt calls recorded by the capability-gated method (when present). */
45
+ suggestedPromptsCalls: {
46
+ target: ReplyTarget;
47
+ prompts: ReadonlyArray<{
48
+ title: string;
49
+ message: string;
50
+ }>;
51
+ opts?: {
52
+ title?: string;
53
+ };
54
+ }[];
55
+ setSuggestedPrompts?: PlatformAdapter["setSuggestedPrompts"];
56
+ /** Thread-title calls recorded by the capability-gated method (when present). */
57
+ threadTitleCalls: {
58
+ target: ReplyTarget;
59
+ title: string;
60
+ }[];
61
+ setThreadTitle?: PlatformAdapter["setThreadTitle"];
35
62
  emitTurn(partial: Partial<IncomingTurn>): void;
63
+ emitThreadStarted(partial?: Partial<IncomingThreadStart>): Promise<void> | void;
36
64
  emitInteraction(partial: Partial<InteractionEvent>): void;
37
65
  emitCommand(partial: Partial<IncomingCommand> & {
38
66
  command: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fake-adapter.d.ts","sourceRoot":"","sources":["../../src/testing/fake-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,YAAY,EACZ,aAAa,EACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,WAAW,EAGX,WAAW,EACX,aAAa,EACb,SAAS,EACT,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,oHAAoH;AACpH,wBAAgB,mBAAmB,IAAI,WAAW,CAyBjD;AAED,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,QAAQ,UAAU;IAC3B,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAKxC;IACF,QAAQ,CAAC,aAAa,QAAQ;IAC9B,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAI3C;IAEF,MAAM,EAAE,OAAO,EAAE,EAAE,CAAM;IACzB,OAAO,EAAE;QAAE,GAAG,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,OAAO,EAAE,CAAA;KAAE,EAAE,CAAM;IACnD,gBAAgB,EAAE,gBAAgB,EAAE,CAAM;IAC1C,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,EAAE,aAAa,EAAE,CAAM;IAC/B,wDAAwD;IACxD,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,OAAO,CAAC,IAAI,CAAC,CAAc;IAC3B,OAAO,CAAC,OAAO,CAAK;IAEd,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAGvC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAE3B,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,aAAa;IAG9B,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAI9D,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAGrD,MAAM,CACV,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAC5B,OAAO,CAAC,UAAU,CAAC;IAMhB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAE7C,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW;IAKpD,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS;IAGvD,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAG5D,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAKjE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAS9C,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI;IAUzD,WAAW,CACT,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GACtD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAUvB,gGAAgG;IAChG,kBAAkB,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC5C,gBAAgB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI;CAGzD"}
1
+ {"version":3,"file":"fake-adapter.d.ts","sourceRoot":"","sources":["../../src/testing/fake-adapter.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,7 @@ export function makeFakeRunRenderer() {
16
16
  pending = { eventName: e.name, value: e.value };
17
17
  },
18
18
  };
19
+ let finishCalls = 0;
19
20
  return {
20
21
  subscriber,
21
22
  async markInterrupted() { },
@@ -24,17 +25,45 @@ export function makeFakeRunRenderer() {
24
25
  clearPendingInterrupt: () => {
25
26
  pending = undefined;
26
27
  },
28
+ async finish() {
29
+ finishCalls++;
30
+ },
31
+ get finishCalls() {
32
+ return finishCalls;
33
+ },
27
34
  };
28
35
  }
29
36
  export class FakeAdapter {
30
37
  platform = "fake";
31
- capabilities = {
32
- supportsModals: false,
33
- supportsTyping: false,
34
- supportsReactions: false,
35
- supportsStreaming: true,
36
- };
38
+ capabilities;
37
39
  ackDeadlineMs = 3000;
40
+ /**
41
+ * @param fakeOpts.paneMethods When `false`, the optional
42
+ * `setSuggestedPrompts`/`setThreadTitle` methods are omitted (and the
43
+ * matching capability flags cleared) so tests can exercise the
44
+ * capability-gated `{ ok: false }` path. Defaults to present.
45
+ */
46
+ constructor(fakeOpts = {}) {
47
+ const paneMethods = fakeOpts.paneMethods !== false;
48
+ this.capabilities = {
49
+ supportsModals: false,
50
+ supportsTyping: false,
51
+ supportsReactions: false,
52
+ supportsStreaming: true,
53
+ supportsSuggestedPrompts: paneMethods,
54
+ supportsThreadTitle: paneMethods,
55
+ };
56
+ if (paneMethods) {
57
+ this.setSuggestedPrompts = async (target, prompts, opts) => {
58
+ this.suggestedPromptsCalls.push({ target, prompts, opts });
59
+ return { ok: true };
60
+ };
61
+ this.setThreadTitle = async (target, title) => {
62
+ this.threadTitleCalls.push({ target, title });
63
+ return { ok: true };
64
+ };
65
+ }
66
+ }
38
67
  conversationStore = {
39
68
  async getOrCreate(conversationKey, _replyTarget, makeAgent) {
40
69
  return { agent: makeAgent(conversationKey) };
@@ -86,6 +115,12 @@ export class FakeAdapter {
86
115
  async getMessages(_target) {
87
116
  return this.messages;
88
117
  }
118
+ /** Suggested-prompt calls recorded by the capability-gated method (when present). */
119
+ suggestedPromptsCalls = [];
120
+ setSuggestedPrompts;
121
+ /** Thread-title calls recorded by the capability-gated method (when present). */
122
+ threadTitleCalls = [];
123
+ setThreadTitle;
89
124
  // --- test helpers ---
90
125
  emitTurn(partial) {
91
126
  void this.sink?.onTurn({
@@ -96,6 +131,14 @@ export class FakeAdapter {
96
131
  ...partial,
97
132
  });
98
133
  }
134
+ emitThreadStarted(partial) {
135
+ return this.sink?.onThreadStarted({
136
+ conversationKey: "c",
137
+ replyTarget: {},
138
+ platform: "fake",
139
+ ...partial,
140
+ });
141
+ }
99
142
  emitInteraction(partial) {
100
143
  const evt = {
101
144
  id: "",
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=thread-capabilities.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-capabilities.test.d.ts","sourceRoot":"","sources":["../src/thread-capabilities.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { createBot } from "./create-bot.js";
3
+ import { FakeAdapter } from "./testing/fake-adapter.js";
4
+ describe("onThreadStarted routing", () => {
5
+ it("invokes registered handlers with the thread and user", async () => {
6
+ const fake = new FakeAdapter();
7
+ const bot = createBot({ adapters: [fake] });
8
+ const seen = [];
9
+ bot.onThreadStarted(({ thread, user }) => {
10
+ seen.push({ user: user?.id, platform: thread.platform });
11
+ });
12
+ await bot.start();
13
+ await fake.emitThreadStarted({ user: { id: "U1", name: "Ada" } });
14
+ expect(seen).toEqual([{ user: "U1", platform: "fake" }]);
15
+ });
16
+ it("invokes every registered handler in order", async () => {
17
+ const fake = new FakeAdapter();
18
+ const bot = createBot({ adapters: [fake] });
19
+ const order = [];
20
+ bot.onThreadStarted(() => {
21
+ order.push(1);
22
+ });
23
+ bot.onThreadStarted(() => {
24
+ order.push(2);
25
+ });
26
+ await bot.start();
27
+ await fake.emitThreadStarted();
28
+ expect(order).toEqual([1, 2]);
29
+ });
30
+ it("is a no-op when no handler is registered", async () => {
31
+ const fake = new FakeAdapter();
32
+ const bot = createBot({ adapters: [fake] });
33
+ await bot.start();
34
+ // Should not throw.
35
+ await expect(Promise.resolve(fake.emitThreadStarted())).resolves.toBeUndefined();
36
+ });
37
+ });
38
+ describe("Thread.setSuggestedPrompts / setTitle capability gating", () => {
39
+ it("delegates to the adapter when supported", async () => {
40
+ const fake = new FakeAdapter();
41
+ const bot = createBot({ adapters: [fake] });
42
+ const results = [];
43
+ bot.onThreadStarted(async ({ thread }) => {
44
+ results.push(await thread.setSuggestedPrompts([{ title: "Triage", message: "Triage my issues" }], { title: "Try" }));
45
+ results.push(await thread.setTitle("My conversation"));
46
+ });
47
+ await bot.start();
48
+ await fake.emitThreadStarted({ replyTarget: { channel: "D1" } });
49
+ expect(results).toEqual([{ ok: true }, { ok: true }]);
50
+ expect(fake.suggestedPromptsCalls).toHaveLength(1);
51
+ expect(fake.suggestedPromptsCalls[0]).toMatchObject({
52
+ target: { channel: "D1" },
53
+ prompts: [{ title: "Triage", message: "Triage my issues" }],
54
+ opts: { title: "Try" },
55
+ });
56
+ expect(fake.threadTitleCalls).toEqual([
57
+ { target: { channel: "D1" }, title: "My conversation" },
58
+ ]);
59
+ });
60
+ it("returns { ok: false } without throwing when unsupported", async () => {
61
+ const fake = new FakeAdapter({ paneMethods: false });
62
+ const bot = createBot({ adapters: [fake] });
63
+ const results = [];
64
+ bot.onThreadStarted(async ({ thread }) => {
65
+ results.push(await thread.setSuggestedPrompts([]));
66
+ results.push(await thread.setTitle("nope"));
67
+ });
68
+ await bot.start();
69
+ await fake.emitThreadStarted();
70
+ expect(results[0].ok).toBe(false);
71
+ expect(results[0].error).toMatch(/does not support suggested prompts/);
72
+ expect(results[1].ok).toBe(false);
73
+ expect(results[1].error).toMatch(/does not support thread titles/);
74
+ expect(fake.suggestedPromptsCalls).toHaveLength(0);
75
+ expect(fake.threadTitleCalls).toHaveLength(0);
76
+ });
77
+ });
package/dist/thread.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { PlatformAdapter, ReplyTarget } from "./platform-adapter.js";
2
2
  import type { ActionRegistry } from "./action-registry.js";
3
- import type { Renderable, MessageRef, PlatformUser, ThreadMessage, Thread as ThreadInterface } from "@copilotkit/bot-ui";
3
+ import type { AgentContentPart, Renderable, MessageRef, PlatformUser, ThreadMessage, Thread as ThreadInterface } from "@copilotkit/bot-ui";
4
4
  import type { BotTool, ContextEntry, AgentToolDescriptor } from "./tools.js";
5
5
  import type { AbstractAgent } from "@ag-ui/client";
6
6
  export interface ThreadDeps {
@@ -38,6 +38,21 @@ export declare class Thread implements ThreadInterface {
38
38
  fileId?: string;
39
39
  error?: string;
40
40
  }>;
41
+ /** Pin suggested prompts (returns `{ ok: false }` on surfaces without support). */
42
+ setSuggestedPrompts(prompts: ReadonlyArray<{
43
+ title: string;
44
+ message: string;
45
+ }>, opts?: {
46
+ title?: string;
47
+ }): Promise<{
48
+ ok: boolean;
49
+ error?: string;
50
+ }>;
51
+ /** Name this conversation (returns `{ ok: false }` on surfaces without support). */
52
+ setTitle(title: string): Promise<{
53
+ ok: boolean;
54
+ error?: string;
55
+ }>;
41
56
  /** Read the conversation's messages (returns `[]` when the adapter can't read history). */
42
57
  getMessages(): Promise<ThreadMessage[]>;
43
58
  /** Resolve a platform user by free-form query (returns `undefined` when unsupported). */
@@ -50,9 +65,11 @@ export declare class Thread implements ThreadInterface {
50
65
  /**
51
66
  * A user message to inject before running. Needed when the input isn't
52
67
  * already in the conversation history the adapter reconstructs — e.g. a
53
- * slash command, whose args are never posted to the channel.
68
+ * slash command, whose args are never posted to the channel. A
69
+ * `AgentContentPart[]` carries multimodal content (e.g. inbound image/file
70
+ * attachments) the model can read.
54
71
  */
55
- prompt?: string;
72
+ prompt?: string | AgentContentPart[];
56
73
  }): Promise<MessageRef | undefined>;
57
74
  resume(value: unknown): Promise<MessageRef | undefined>;
58
75
  private run;
@@ -1 +1 @@
1
- {"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../src/thread.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../src/thread.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,MAAM,IAAI,eAAe,EAC1B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EACV,OAAO,EAEP,YAAY,EACZ,mBAAmB,EACpB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,CAAC;IAClD,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,cAAc,EAAE,CACd,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KAC9B,IAAI,CAAC;IACV,iBAAiB,EAAE,GAAG,CACpB,MAAM,EACN,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CACrE,CAAC;CACH;AAED,gGAAgG;AAChG,qBAAa,MAAO,YAAW,eAAe;IAGhC,OAAO,CAAC,IAAI;IAFxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEN,IAAI,EAAE,UAAU;YAItB,WAAW;IAInB,IAAI,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAOzC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAK5D,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IAUhE,QAAQ,CAAC,IAAI,EAAE;QACnB,KAAK,EAAE,UAAU,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAW7D,mFAAmF;IAC7E,mBAAmB,CACvB,OAAO,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,EAC1D,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACxB,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAW3C,oFAAoF;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAWvE,2FAA2F;IACrF,WAAW,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAI7C,yFAAyF;IACnF,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAIlE,oFAAoF;IAC9E,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IAWpD,QAAQ,CAAC,KAAK,CAAC,EAAE;QACrB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;QACzB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;QAClB;;;;;;WAMG;QACH,MAAM,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAAC;KACtC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAI7B,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;YAI/C,GAAG;CAsElB"}
package/dist/thread.js CHANGED
@@ -39,6 +39,28 @@ export class Thread {
39
39
  }
40
40
  return adapter.postFile(this.deps.replyTarget, args);
41
41
  }
42
+ /** Pin suggested prompts (returns `{ ok: false }` on surfaces without support). */
43
+ async setSuggestedPrompts(prompts, opts) {
44
+ const adapter = this.deps.adapter;
45
+ if (!adapter.setSuggestedPrompts) {
46
+ return {
47
+ ok: false,
48
+ error: `${this.platform} does not support suggested prompts`,
49
+ };
50
+ }
51
+ return adapter.setSuggestedPrompts(this.deps.replyTarget, prompts, opts);
52
+ }
53
+ /** Name this conversation (returns `{ ok: false }` on surfaces without support). */
54
+ async setTitle(title) {
55
+ const adapter = this.deps.adapter;
56
+ if (!adapter.setThreadTitle) {
57
+ return {
58
+ ok: false,
59
+ error: `${this.platform} does not support thread titles`,
60
+ };
61
+ }
62
+ return adapter.setThreadTitle(this.deps.replyTarget, title);
63
+ }
42
64
  /** Read the conversation's messages (returns `[]` when the adapter can't read history). */
43
65
  async getMessages() {
44
66
  return (await this.deps.adapter.getMessages?.(this.deps.replyTarget)) ?? [];
@@ -62,11 +84,18 @@ export class Thread {
62
84
  async run(initialResume, extra) {
63
85
  const session = await this.deps.adapter.conversationStore.getOrCreate(this.deps.conversationKey, this.deps.replyTarget, this.deps.agentFactory);
64
86
  // Inject an explicit user message when the input isn't in the adapter's
65
- // reconstructed history (e.g. a slash command's args).
87
+ // reconstructed history (e.g. a slash command's args, or inbound image/file
88
+ // attachments built into multimodal content parts). A non-empty array is
89
+ // truthy, so this guard also admits multimodal prompts.
66
90
  if (extra?.prompt) {
67
91
  session.agent.addMessage({
68
92
  id: globalThis.crypto.randomUUID(),
69
93
  role: "user",
94
+ // AG-UI types `content` as `string`, but multimodal works at runtime by
95
+ // setting it to an `AgentContentPart[]` — the runtime's LLM adapter
96
+ // converts the parts to the provider's multimodal format. We cast to
97
+ // satisfy the string-typed field (bot-slack parity — it does the same
98
+ // when assigning multimodal `content` to its reconstructed messages).
70
99
  content: extra.prompt,
71
100
  });
72
101
  }
@@ -104,6 +133,11 @@ export class Thread {
104
133
  },
105
134
  initialResume,
106
135
  });
136
+ // Turn-end hook: lets a renderer finalize any turn-scoped resource it kept
137
+ // open across runAgent iterations (e.g. a native streaming message). A
138
+ // no-op for renderers whose per-message streams already self-terminate, and
139
+ // for runs that were interrupted (the renderer guards that internally).
140
+ await renderer.finish?.();
107
141
  return undefined;
108
142
  }
109
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkit/bot",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Platform-agnostic JSX bot engine for CopilotKit (createBot, Thread, PlatformAdapter, ActionStore).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,12 +35,12 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
- "@ag-ui/client": "0.0.53",
39
- "@ag-ui/core": "0.0.53",
38
+ "@ag-ui/client": "0.0.57",
39
+ "@ag-ui/core": "0.0.57",
40
40
  "zod-to-json-schema": "^3.24.1",
41
- "@copilotkit/bot-ui": "~0.0.1",
42
- "@copilotkit/shared": "^1.59.5",
43
- "@copilotkit/core": "^1.59.5"
41
+ "@copilotkit/bot-ui": "~0.0.3",
42
+ "@copilotkit/core": "^1.61.0",
43
+ "@copilotkit/shared": "^1.61.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^22.10.0",