@copilotkit/channels-core 0.6.0 → 0.6.2-canary.1785779327
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 +37 -0
- package/dist/action-registry.d.ts +23 -4
- package/dist/action-registry.d.ts.map +1 -1
- package/dist/action-registry.js +139 -26
- package/dist/action-store.d.ts +6 -0
- package/dist/action-store.d.ts.map +1 -1
- package/dist/channel-component.d.ts +18 -0
- package/dist/channel-component.d.ts.map +1 -0
- package/dist/channel-component.js +13 -0
- package/dist/channel-component.test.d.ts +2 -0
- package/dist/channel-component.test.d.ts.map +1 -0
- package/dist/channel-component.test.js +120 -0
- package/dist/component-reaction-recovery.test.d.ts +2 -0
- package/dist/component-reaction-recovery.test.d.ts.map +1 -0
- package/dist/component-reaction-recovery.test.js +29 -0
- package/dist/create-channel.d.ts +8 -6
- package/dist/create-channel.d.ts.map +1 -1
- package/dist/create-channel.js +97 -18
- package/dist/create-channel.test.js +36 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/keyed-action-recovery.test.d.ts +2 -0
- package/dist/keyed-action-recovery.test.d.ts.map +1 -0
- package/dist/keyed-action-recovery.test.js +121 -0
- package/dist/thread.d.ts +3 -0
- package/dist/thread.d.ts.map +1 -1
- package/dist/thread.js +14 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -189,6 +189,43 @@ methods (e.g. `thread.getMessages()`, `thread.lookupUser(query)`,
|
|
|
189
189
|
A `ContextEntry` is `{ description: string; value: string }` — knowledge
|
|
190
190
|
folded into the agent's system context on each `runAgent`.
|
|
191
191
|
|
|
192
|
+
## Agent-rendered components
|
|
193
|
+
|
|
194
|
+
`defineChannelComponent` turns a server-rendered JSX function into an agent
|
|
195
|
+
tool. Its Standard Schema validates tool args before `render` runs. The render
|
|
196
|
+
context supplies the source `platform` and the run's `AbortSignal`.
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import { createChannel, defineChannelComponent } from "@copilotkit/channels";
|
|
200
|
+
import { z } from "zod";
|
|
201
|
+
|
|
202
|
+
const Approval = defineChannelComponent({
|
|
203
|
+
name: "show_approval",
|
|
204
|
+
description: "Post an approval request.",
|
|
205
|
+
parameters: z.object({ title: z.string() }),
|
|
206
|
+
render: ({ title }, { platform, signal }) => (
|
|
207
|
+
<Card title={`${title} (${platform})`}>
|
|
208
|
+
<Button key="approve" value="approve" onClick={approve}>
|
|
209
|
+
Approve
|
|
210
|
+
</Button>
|
|
211
|
+
</Card>
|
|
212
|
+
),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const channel = createChannel({
|
|
216
|
+
name: "approvals",
|
|
217
|
+
components: [Approval],
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
When the agent calls `show_approval`, Channels posts the rendered message as a
|
|
222
|
+
separate provider message and returns a short tool acknowledgement. The agent
|
|
223
|
+
run then continues. Interactive nodes in a component definition need stable,
|
|
224
|
+
unique JSX keys. Channels stores those keys with the source platform and action
|
|
225
|
+
value, so a click or reaction can recover the same handler after a restart.
|
|
226
|
+
Legacy `components: { Name: Component }` registrations still use positional
|
|
227
|
+
recovery for old snapshots.
|
|
228
|
+
|
|
192
229
|
## ActionStore
|
|
193
230
|
|
|
194
231
|
Inline JSX handlers are bound by content. Each interactive node gets a
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ChannelNode, InteractionContext,
|
|
1
|
+
import type { ChannelNode, InteractionContext, Renderable, MessageReactionHandler } from "@copilotkit/channels-ui";
|
|
2
|
+
import type { ChannelComponentRenderContext } from "./channel-component.js";
|
|
2
3
|
import type { ActionContinuationContext, ActionContinuationBinding, ActionContinuationSnapshot, ActionStore } from "./action-store.js";
|
|
3
4
|
export declare class ActionExpiredError extends Error {
|
|
4
5
|
readonly code = "channel_action_expired";
|
|
@@ -31,6 +32,7 @@ export declare class ActionRegistry {
|
|
|
31
32
|
component: string;
|
|
32
33
|
props: Record<string, unknown>;
|
|
33
34
|
conversationKey: string;
|
|
35
|
+
platform?: string;
|
|
34
36
|
}): Promise<void>;
|
|
35
37
|
/**
|
|
36
38
|
* Resolve the `onReaction` handler for `messageId`: the hot cache first, then
|
|
@@ -39,10 +41,22 @@ export declare class ActionRegistry {
|
|
|
39
41
|
* handler whose closure can't be re-derived after a restart.
|
|
40
42
|
*/
|
|
41
43
|
resolveMessageReaction(messageId: string): Promise<MessageReactionHandler | undefined>;
|
|
42
|
-
registerComponent(name: string, fn:
|
|
44
|
+
registerComponent(name: string, fn: (props: Record<string, unknown>, context: ChannelComponentRenderContext) => Renderable | Promise<Renderable>, options?: {
|
|
45
|
+
requireKeys?: boolean;
|
|
46
|
+
}): void;
|
|
43
47
|
clearHotCache(): void;
|
|
44
|
-
bindTree(componentName: string, props: Record<string, unknown>, conversationKey: string, continuation?: ActionContinuationContext): Promise<ChannelNode[]>;
|
|
45
|
-
|
|
48
|
+
bindTree(componentName: string, props: Record<string, unknown>, conversationKey: string, continuation?: ActionContinuationContext, renderContext?: ChannelComponentRenderContext): Promise<ChannelNode[]>;
|
|
49
|
+
/** Bind a named component and detach its root reaction for message routing. */
|
|
50
|
+
bindRegisteredRenderable(componentName: string, props: Record<string, unknown>, conversationKey: string, continuation: ActionContinuationContext | undefined, renderContext: ChannelComponentRenderContext): Promise<{
|
|
51
|
+
root: ChannelNode[];
|
|
52
|
+
onReaction?: MessageReactionHandler;
|
|
53
|
+
reactionComponent?: {
|
|
54
|
+
component: string;
|
|
55
|
+
props: Record<string, unknown>;
|
|
56
|
+
platform: string;
|
|
57
|
+
};
|
|
58
|
+
}>;
|
|
59
|
+
bindRenderable(ui: Renderable, conversationKey: string, continuation?: ActionContinuationContext, renderContext?: ChannelComponentRenderContext): Promise<{
|
|
46
60
|
root: ChannelNode[];
|
|
47
61
|
onReaction?: MessageReactionHandler;
|
|
48
62
|
/**
|
|
@@ -53,9 +67,14 @@ export declare class ActionRegistry {
|
|
|
53
67
|
reactionComponent?: {
|
|
54
68
|
component: string;
|
|
55
69
|
props: Record<string, unknown>;
|
|
70
|
+
platform: string;
|
|
56
71
|
};
|
|
57
72
|
}>;
|
|
58
73
|
private walk;
|
|
74
|
+
/** Traverse children and provider-native named slots through one path model. */
|
|
75
|
+
private walkValue;
|
|
76
|
+
/** Bind one node reached through a singular provider-native slot. */
|
|
77
|
+
private walkNode;
|
|
59
78
|
/**
|
|
60
79
|
* Run the click handler for `id` and return the clicked element's `value`
|
|
61
80
|
* (so callers can resolve a HITL `awaitChoice` waiter even when the platform
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-registry.d.ts","sourceRoot":"","sources":["../src/action-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAEX,kBAAkB,
|
|
1
|
+
{"version":3,"file":"action-registry.d.ts","sourceRoot":"","sources":["../src/action-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/action-registry.js
CHANGED
|
@@ -54,6 +54,7 @@ export class ActionRegistry {
|
|
|
54
54
|
props: snap.props,
|
|
55
55
|
path: [],
|
|
56
56
|
conversationKey: snap.conversationKey,
|
|
57
|
+
platform: snap.platform,
|
|
57
58
|
});
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
@@ -69,26 +70,52 @@ export class ActionRegistry {
|
|
|
69
70
|
const snap = await this.store.get(reactionKey(messageId));
|
|
70
71
|
if (!snap?.component)
|
|
71
72
|
return undefined;
|
|
72
|
-
const
|
|
73
|
-
if (!
|
|
73
|
+
const registered = this.components.get(snap.component);
|
|
74
|
+
if (!registered)
|
|
74
75
|
return undefined;
|
|
75
|
-
const root = renderToIR(
|
|
76
|
+
const root = renderToIR(await registered.render(snap.props, {
|
|
77
|
+
platform: (snap.platform ??
|
|
78
|
+
"slack"),
|
|
79
|
+
signal: new AbortController().signal,
|
|
80
|
+
}));
|
|
76
81
|
return takeMessageReaction(root);
|
|
77
82
|
}
|
|
78
|
-
registerComponent(name, fn) {
|
|
79
|
-
this.components.set(name,
|
|
83
|
+
registerComponent(name, fn, options = {}) {
|
|
84
|
+
this.components.set(name, {
|
|
85
|
+
render: fn,
|
|
86
|
+
requireKeys: options.requireKeys ?? false,
|
|
87
|
+
});
|
|
80
88
|
}
|
|
81
89
|
clearHotCache() {
|
|
82
90
|
this.hot.clear();
|
|
83
91
|
}
|
|
84
92
|
// Renders the named component, binds all event-prop handlers in the tree
|
|
85
93
|
// (mint id, hot-cache + ActionStore snapshot, rewrite prop to { id }), returns the bound IR.
|
|
86
|
-
async bindTree(componentName, props, conversationKey, continuation
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
async bindTree(componentName, props, conversationKey, continuation, renderContext = {
|
|
95
|
+
platform: "slack",
|
|
96
|
+
signal: new AbortController().signal,
|
|
97
|
+
}) {
|
|
98
|
+
const registered = this.components.get(componentName);
|
|
99
|
+
const rendered = registered
|
|
100
|
+
? await registered.render(props, renderContext)
|
|
101
|
+
: props;
|
|
102
|
+
const root = renderToIR(rendered);
|
|
103
|
+
const interactiveKeys = new Set();
|
|
104
|
+
await this.walk(root, [], componentName, props, conversationKey, continuation, registered?.requireKeys ?? false, renderContext.platform, interactiveKeys);
|
|
90
105
|
return root;
|
|
91
106
|
}
|
|
107
|
+
/** Bind a named component and detach its root reaction for message routing. */
|
|
108
|
+
async bindRegisteredRenderable(componentName, props, conversationKey, continuation, renderContext) {
|
|
109
|
+
const root = await this.bindTree(componentName, props, conversationKey, continuation, renderContext);
|
|
110
|
+
const onReaction = takeMessageReaction(root);
|
|
111
|
+
return {
|
|
112
|
+
root,
|
|
113
|
+
onReaction,
|
|
114
|
+
reactionComponent: onReaction
|
|
115
|
+
? { component: componentName, props, platform: renderContext.platform }
|
|
116
|
+
: undefined,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
92
119
|
// Binds an arbitrary Renderable for posting. If `ui` is a component element
|
|
93
120
|
// (`{ type: fn, props }`), it is registered + bound by name (cold-path
|
|
94
121
|
// re-render supported). Otherwise the IR is bound inline with `component:""`,
|
|
@@ -96,7 +123,10 @@ export class ActionRegistry {
|
|
|
96
123
|
// degradation for inline handlers that can't be re-derived). A top-level
|
|
97
124
|
// `<Message onReaction>` handler is pulled off the IR (so it never reaches the
|
|
98
125
|
// adapter) and returned for the caller to associate with the posted message.
|
|
99
|
-
async bindRenderable(ui, conversationKey, continuation
|
|
126
|
+
async bindRenderable(ui, conversationKey, continuation, renderContext = {
|
|
127
|
+
platform: "slack",
|
|
128
|
+
signal: new AbortController().signal,
|
|
129
|
+
}) {
|
|
100
130
|
let root;
|
|
101
131
|
let component;
|
|
102
132
|
let props;
|
|
@@ -104,31 +134,50 @@ export class ActionRegistry {
|
|
|
104
134
|
const fn = ui.type;
|
|
105
135
|
component = fn.name || "anonymous";
|
|
106
136
|
props = (ui.props ?? {});
|
|
107
|
-
this.registerComponent(component, fn);
|
|
108
|
-
root = await this.bindTree(component, props, conversationKey, continuation
|
|
137
|
+
this.registerComponent(component, (componentProps) => fn(componentProps));
|
|
138
|
+
root = await this.bindTree(component, props, conversationKey, continuation, {
|
|
139
|
+
platform: renderContext.platform,
|
|
140
|
+
signal: renderContext.signal,
|
|
141
|
+
});
|
|
109
142
|
}
|
|
110
143
|
else {
|
|
111
144
|
root = renderToIR(ui);
|
|
112
|
-
await this.walk(root, [], "", undefined, conversationKey, continuation);
|
|
145
|
+
await this.walk(root, [], "", undefined, conversationKey, continuation, false, renderContext.platform, new Set());
|
|
113
146
|
}
|
|
114
147
|
const onReaction = takeMessageReaction(root);
|
|
115
148
|
return {
|
|
116
149
|
root,
|
|
117
150
|
onReaction,
|
|
118
|
-
reactionComponent: onReaction && component && props
|
|
151
|
+
reactionComponent: onReaction && component && props
|
|
152
|
+
? { component, props, platform: renderContext.platform }
|
|
153
|
+
: undefined,
|
|
119
154
|
};
|
|
120
155
|
}
|
|
121
|
-
async walk(nodes, base, comp, props, conv, continuation) {
|
|
156
|
+
async walk(nodes, base, comp, props, conv, continuation, requireKeys, platform, interactiveKeys, exactPath) {
|
|
122
157
|
for (let i = 0; i < nodes.length; i++) {
|
|
123
158
|
const node = nodes[i];
|
|
124
|
-
const path = [...base, i];
|
|
159
|
+
const path = exactPath ?? [...base, i];
|
|
160
|
+
const eventProps = EVENT_PROPS.filter((eventProp) => typeof node.props[eventProp] === "function");
|
|
161
|
+
if (requireKeys && eventProps.length > 0) {
|
|
162
|
+
if (node.key === undefined ||
|
|
163
|
+
(typeof node.key === "string" && node.key.trim().length === 0)) {
|
|
164
|
+
throw new Error(`${formatComponentPath(comp, path)}.${eventProps[0]} requires a non-empty JSX key`);
|
|
165
|
+
}
|
|
166
|
+
if (interactiveKeys.has(node.key)) {
|
|
167
|
+
throw new Error(`duplicate interactive JSX key "${node.key}"`);
|
|
168
|
+
}
|
|
169
|
+
interactiveKeys.add(node.key);
|
|
170
|
+
}
|
|
125
171
|
for (const ep of EVENT_PROPS) {
|
|
126
172
|
const handler = node.props[ep];
|
|
127
173
|
if (typeof handler === "function") {
|
|
128
174
|
const fullPath = [...path, ep];
|
|
175
|
+
const locator = requireKeys && node.key !== undefined
|
|
176
|
+
? { key: node.key, eventProp: ep }
|
|
177
|
+
: undefined;
|
|
129
178
|
const id = continuation
|
|
130
179
|
? `ck:${globalThis.crypto.randomUUID()}`
|
|
131
|
-
: mintId(comp, fullPath, props);
|
|
180
|
+
: mintId(comp, locator ? [locator.key, locator.eventProp] : fullPath, props);
|
|
132
181
|
this.hot.set(id, {
|
|
133
182
|
handler: handler,
|
|
134
183
|
value: node.props.value,
|
|
@@ -138,6 +187,9 @@ export class ActionRegistry {
|
|
|
138
187
|
component: comp,
|
|
139
188
|
props,
|
|
140
189
|
path: fullPath,
|
|
190
|
+
...(locator ? { locator } : {}),
|
|
191
|
+
platform,
|
|
192
|
+
actionValue: node.props.value,
|
|
141
193
|
conversationKey: conv,
|
|
142
194
|
boundArgs: isBound(handler) ? getBoundArgs(handler) : undefined,
|
|
143
195
|
...(continuation
|
|
@@ -147,12 +199,29 @@ export class ActionRegistry {
|
|
|
147
199
|
node.props[ep] = { id };
|
|
148
200
|
}
|
|
149
201
|
}
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
202
|
+
for (const [propName, propValue] of Object.entries(node.props)) {
|
|
203
|
+
if (EVENT_PROPS.includes(propName))
|
|
204
|
+
continue;
|
|
205
|
+
await this.walkValue(propValue, [...path, propName], comp, props, conv, continuation, requireKeys, platform, interactiveKeys);
|
|
153
206
|
}
|
|
154
207
|
}
|
|
155
208
|
}
|
|
209
|
+
/** Traverse children and provider-native named slots through one path model. */
|
|
210
|
+
async walkValue(value, path, comp, props, conv, continuation, requireKeys, platform, interactiveKeys) {
|
|
211
|
+
if (isChannelNode(value)) {
|
|
212
|
+
await this.walkNode(value, path, comp, props, conv, continuation, requireKeys, platform, interactiveKeys);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (!Array.isArray(value))
|
|
216
|
+
return;
|
|
217
|
+
for (let index = 0; index < value.length; index++) {
|
|
218
|
+
await this.walkValue(value[index], [...path, index], comp, props, conv, continuation, requireKeys, platform, interactiveKeys);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** Bind one node reached through a singular provider-native slot. */
|
|
222
|
+
async walkNode(node, path, comp, props, conv, continuation, requireKeys, platform, interactiveKeys) {
|
|
223
|
+
await this.walk([node], [], comp, props, conv, continuation, requireKeys, platform, interactiveKeys, path);
|
|
224
|
+
}
|
|
156
225
|
/**
|
|
157
226
|
* Run the click handler for `id` and return the clicked element's `value`
|
|
158
227
|
* (so callers can resolve a HITL `awaitChoice` waiter even when the platform
|
|
@@ -175,16 +244,26 @@ export class ActionRegistry {
|
|
|
175
244
|
const snap = await this.store.get(id);
|
|
176
245
|
if (!snap || !snap.component)
|
|
177
246
|
throw new ActionExpiredError(id);
|
|
178
|
-
const
|
|
179
|
-
if (!
|
|
247
|
+
const registered = this.components.get(snap.component);
|
|
248
|
+
if (!registered)
|
|
180
249
|
throw new ActionExpiredError(id);
|
|
181
|
-
const tree = renderToIR(
|
|
182
|
-
|
|
183
|
-
|
|
250
|
+
const tree = renderToIR(await registered.render(snap.props, {
|
|
251
|
+
platform: (snap.platform ??
|
|
252
|
+
ctx.platform),
|
|
253
|
+
signal: new AbortController().signal,
|
|
254
|
+
}));
|
|
255
|
+
handler = snap.locator
|
|
256
|
+
? findHandlerByLocator(tree, snap.locator)
|
|
257
|
+
: pluck(tree, snap.path);
|
|
258
|
+
value = snap.locator ? snap.actionValue : pluckValue(tree, snap.path);
|
|
184
259
|
if (!handler)
|
|
185
260
|
throw new ActionExpiredError(id);
|
|
186
261
|
}
|
|
187
|
-
|
|
262
|
+
const actionValue = value === undefined ? ctx.action.value : value;
|
|
263
|
+
await handler({
|
|
264
|
+
...ctx,
|
|
265
|
+
action: { ...ctx.action, id, value: actionValue },
|
|
266
|
+
});
|
|
188
267
|
return value;
|
|
189
268
|
}
|
|
190
269
|
/** Read and validate one continuation capability without consuming it. */
|
|
@@ -219,6 +298,40 @@ function assertContinuationBinding(actual, actionId, expected) {
|
|
|
219
298
|
throw new ActionContinuationMismatchError();
|
|
220
299
|
}
|
|
221
300
|
}
|
|
301
|
+
function isChannelNode(value) {
|
|
302
|
+
return (typeof value === "object" &&
|
|
303
|
+
value !== null &&
|
|
304
|
+
"type" in value &&
|
|
305
|
+
"props" in value &&
|
|
306
|
+
typeof value.props === "object" &&
|
|
307
|
+
value.props !== null);
|
|
308
|
+
}
|
|
309
|
+
function formatComponentPath(component, path) {
|
|
310
|
+
return path.reduce((result, segment) => typeof segment === "number"
|
|
311
|
+
? `${result}[${segment}]`
|
|
312
|
+
: `${result}.${segment}`, component);
|
|
313
|
+
}
|
|
314
|
+
function findHandlerByLocator(tree, locator) {
|
|
315
|
+
const matches = [];
|
|
316
|
+
const visit = (value) => {
|
|
317
|
+
if (Array.isArray(value)) {
|
|
318
|
+
for (const item of value)
|
|
319
|
+
visit(item);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (!isChannelNode(value))
|
|
323
|
+
return;
|
|
324
|
+
if (value.key === locator.key) {
|
|
325
|
+
const handler = value.props[locator.eventProp];
|
|
326
|
+
if (typeof handler === "function")
|
|
327
|
+
matches.push(handler);
|
|
328
|
+
}
|
|
329
|
+
for (const propValue of Object.values(value.props))
|
|
330
|
+
visit(propValue);
|
|
331
|
+
};
|
|
332
|
+
visit(tree);
|
|
333
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
334
|
+
}
|
|
222
335
|
/** Store key for a message's durable reaction snapshot (distinct from minted action ids). */
|
|
223
336
|
function reactionKey(messageId) {
|
|
224
337
|
return `reaction:${messageId}`;
|
package/dist/action-store.d.ts
CHANGED
|
@@ -22,6 +22,12 @@ export interface ActionSnapshot {
|
|
|
22
22
|
component?: string;
|
|
23
23
|
props?: unknown;
|
|
24
24
|
path: (string | number)[];
|
|
25
|
+
locator?: {
|
|
26
|
+
key: string | number;
|
|
27
|
+
eventProp: "onClick" | "onSelect" | "onSubmit";
|
|
28
|
+
};
|
|
29
|
+
platform?: string;
|
|
30
|
+
actionValue?: unknown;
|
|
25
31
|
boundArgs?: unknown;
|
|
26
32
|
conversationKey: string;
|
|
27
33
|
continuation?: ActionContinuationSnapshot;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-store.d.ts","sourceRoot":"","sources":["../src/action-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE9E,iEAAiE;AACjE,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,gFAAgF;AAChF,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,2BAA2B,CAAC;CACxC;AAED,+EAA+E;AAC/E,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,yBAAyB,EACzB,aAAa,GAAG,iBAAiB,GAAG,UAAU,CAC/C,CAAC;AAEF,8EAA8E;AAC9E,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,0BAA0B,CAAC;CAC3C;AACD,iHAAiH;AACjH,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACrD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACzD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AACD,iHAAiH;AACjH,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,GAAG,CAAmE;IACxE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IASpD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAGjC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;CAU/D"}
|
|
1
|
+
{"version":3,"file":"action-store.d.ts","sourceRoot":"","sources":["../src/action-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE9E,iEAAiE;AACjE,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,gFAAgF;AAChF,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,2BAA2B,CAAC;CACxC;AAED,+EAA+E;AAC/E,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,yBAAyB,EACzB,aAAa,GAAG,iBAAiB,GAAG,UAAU,CAC/C,CAAC;AAEF,8EAA8E;AAC9E,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE;QACR,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;QACrB,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;KAChD,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,0BAA0B,CAAC;CAC3C;AACD,iHAAiH;AACjH,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACrD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACzD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AACD,iHAAiH;AACjH,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,GAAG,CAAmE;IACxE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IASpD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAGjC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;CAU/D"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Renderable } from "@copilotkit/channels-ui";
|
|
2
|
+
import type { InferSchemaOutput, ObjectSchema } from "./standard-schema.js";
|
|
3
|
+
export type ChannelComponentPlatform = "slack" | "teams" | "discord" | "telegram" | "whatsapp";
|
|
4
|
+
export interface ChannelComponentRenderContext {
|
|
5
|
+
platform: ChannelComponentPlatform;
|
|
6
|
+
signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export interface ChannelComponentDefinition<Schema extends ObjectSchema = ObjectSchema> {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
parameters: Schema;
|
|
12
|
+
render(props: InferSchemaOutput<Schema>, context: ChannelComponentRenderContext): Renderable | Promise<Renderable>;
|
|
13
|
+
}
|
|
14
|
+
/** Define an agent-rendered Channel component with schema-inferred props. */
|
|
15
|
+
export declare function defineChannelComponent<Schema extends ObjectSchema>(component: ChannelComponentDefinition<Schema>): ChannelComponentDefinition<Schema>;
|
|
16
|
+
/** Distinguish component-tool descriptors from legacy JSX component functions. */
|
|
17
|
+
export declare function isChannelComponentDefinition(value: unknown): value is ChannelComponentDefinition;
|
|
18
|
+
//# sourceMappingURL=channel-component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-component.d.ts","sourceRoot":"","sources":["../src/channel-component.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE5E,MAAM,MAAM,wBAAwB,GAChC,OAAO,GACP,OAAO,GACP,SAAS,GACT,UAAU,GACV,UAAU,CAAC;AAEf,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,wBAAwB,CAAC;IACnC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B,CACzC,MAAM,SAAS,YAAY,GAAG,YAAY;IAE1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CACJ,KAAK,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAChC,OAAO,EAAE,6BAA6B,GACrC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACrC;AAED,6EAA6E;AAC7E,wBAAgB,sBAAsB,CAAC,MAAM,SAAS,YAAY,EAChE,SAAS,EAAE,0BAA0B,CAAC,MAAM,CAAC,GAC5C,0BAA0B,CAAC,MAAM,CAAC,CAEpC;AAED,kFAAkF;AAClF,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,0BAA0B,CAWrC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Define an agent-rendered Channel component with schema-inferred props. */
|
|
2
|
+
export function defineChannelComponent(component) {
|
|
3
|
+
return component;
|
|
4
|
+
}
|
|
5
|
+
/** Distinguish component-tool descriptors from legacy JSX component functions. */
|
|
6
|
+
export function isChannelComponentDefinition(value) {
|
|
7
|
+
return (typeof value === "object" &&
|
|
8
|
+
value !== null &&
|
|
9
|
+
typeof value.name === "string" &&
|
|
10
|
+
typeof value.description === "string" &&
|
|
11
|
+
typeof value.render === "function" &&
|
|
12
|
+
typeof value.parameters?.["~standard"] === "object");
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-component.test.d.ts","sourceRoot":"","sources":["../src/channel-component.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Section } from "@copilotkit/channels-ui";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { expect, test, vi } from "vitest";
|
|
4
|
+
import { createChannel } from "./create-channel.js";
|
|
5
|
+
import { defineChannelComponent } from "./channel-component.js";
|
|
6
|
+
import { FakeAdapter } from "./testing/fake-adapter.js";
|
|
7
|
+
import { FakeAgent } from "./testing/fake-agent.js";
|
|
8
|
+
function setup(args) {
|
|
9
|
+
const adapter = new FakeAdapter({ platform: "intelligence" });
|
|
10
|
+
let iterations = 0;
|
|
11
|
+
const agent = new FakeAgent([
|
|
12
|
+
(subscriber) => {
|
|
13
|
+
iterations += 1;
|
|
14
|
+
subscriber.onToolCallEndEvent?.({
|
|
15
|
+
event: { toolCallId: "component-call-1" },
|
|
16
|
+
toolCallName: args.component.name,
|
|
17
|
+
toolCallArgs: args.toolArgs ?? {},
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
() => {
|
|
21
|
+
iterations += 1;
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
const channel = createChannel({
|
|
25
|
+
identifyUser: "platform",
|
|
26
|
+
adapters: [adapter],
|
|
27
|
+
agent: () => agent,
|
|
28
|
+
components: [args.component],
|
|
29
|
+
});
|
|
30
|
+
channel.onMessage(async ({ thread }) => {
|
|
31
|
+
await thread.runAgent();
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
adapter,
|
|
35
|
+
channel,
|
|
36
|
+
iterations: () => iterations,
|
|
37
|
+
async run() {
|
|
38
|
+
await channel.ɵruntime.start();
|
|
39
|
+
await adapter.getSink().onTurn({
|
|
40
|
+
conversationKey: "conversation-1",
|
|
41
|
+
replyTarget: {},
|
|
42
|
+
userText: "show the order",
|
|
43
|
+
platform: args.platform ?? "slack",
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
test("a channel component validates args, renders asynchronously, and posts through the thread", async () => {
|
|
49
|
+
const render = vi.fn(async (props, context) => {
|
|
50
|
+
await Promise.resolve();
|
|
51
|
+
return Section({
|
|
52
|
+
children: `${context.platform}:${props.orderId}:${context.signal.aborted}`,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
const component = defineChannelComponent({
|
|
56
|
+
name: "show_order",
|
|
57
|
+
description: "Show one order",
|
|
58
|
+
parameters: z.object({ orderId: z.string() }),
|
|
59
|
+
render,
|
|
60
|
+
});
|
|
61
|
+
const harness = setup({
|
|
62
|
+
component,
|
|
63
|
+
platform: "teams",
|
|
64
|
+
toolArgs: { orderId: "order-42" },
|
|
65
|
+
});
|
|
66
|
+
await harness.run();
|
|
67
|
+
expect(render).toHaveBeenCalledWith({ orderId: "order-42" }, { platform: "teams", signal: expect.any(AbortSignal) });
|
|
68
|
+
expect(harness.adapter.posted).toEqual([
|
|
69
|
+
[
|
|
70
|
+
{
|
|
71
|
+
type: "section",
|
|
72
|
+
props: {
|
|
73
|
+
children: [
|
|
74
|
+
{
|
|
75
|
+
type: "text",
|
|
76
|
+
props: { value: "teams:order-42:false" },
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
]);
|
|
83
|
+
expect(harness.iterations()).toBe(2);
|
|
84
|
+
});
|
|
85
|
+
test("invalid component arguments do not render or post", async () => {
|
|
86
|
+
const render = vi.fn(() => Section({ children: "should not render" }));
|
|
87
|
+
const component = defineChannelComponent({
|
|
88
|
+
name: "show_order",
|
|
89
|
+
description: "Show one order",
|
|
90
|
+
parameters: z.object({ orderId: z.string() }),
|
|
91
|
+
render,
|
|
92
|
+
});
|
|
93
|
+
const harness = setup({ component, toolArgs: { orderId: 42 } });
|
|
94
|
+
await harness.run();
|
|
95
|
+
expect(render).not.toHaveBeenCalled();
|
|
96
|
+
expect(harness.adapter.posted).toEqual([]);
|
|
97
|
+
expect(harness.iterations()).toBe(2);
|
|
98
|
+
});
|
|
99
|
+
test("channel start rejects component and tool name collisions", async () => {
|
|
100
|
+
const component = defineChannelComponent({
|
|
101
|
+
name: "show_order",
|
|
102
|
+
description: "Show one order",
|
|
103
|
+
parameters: z.object({}),
|
|
104
|
+
render: () => Section({ children: "order" }),
|
|
105
|
+
});
|
|
106
|
+
const channel = createChannel({
|
|
107
|
+
identifyUser: "platform",
|
|
108
|
+
adapters: [new FakeAdapter()],
|
|
109
|
+
components: [component],
|
|
110
|
+
tools: [
|
|
111
|
+
{
|
|
112
|
+
name: "show_order",
|
|
113
|
+
description: "Conflicting tool",
|
|
114
|
+
parameters: z.object({}),
|
|
115
|
+
handler: () => "conflict",
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
await expect(channel.ɵruntime.start()).rejects.toThrow('duplicate channel tool or component name "show_order"');
|
|
120
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-reaction-recovery.test.d.ts","sourceRoot":"","sources":["../src/component-reaction-recovery.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { Message } from "@copilotkit/channels-ui";
|
|
3
|
+
import { ActionRegistry } from "./action-registry.js";
|
|
4
|
+
import { InMemoryActionStore } from "./action-store.js";
|
|
5
|
+
const reactionHandler = () => undefined;
|
|
6
|
+
test("registered component reactions recover with their source platform", async () => {
|
|
7
|
+
const store = new InMemoryActionStore();
|
|
8
|
+
const renderPlatforms = [];
|
|
9
|
+
const register = (registry) => {
|
|
10
|
+
registry.registerComponent("deployment", (_props, context) => {
|
|
11
|
+
renderPlatforms.push(context.platform);
|
|
12
|
+
return Message({ children: "done", onReaction: reactionHandler });
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
const first = new ActionRegistry({ store });
|
|
16
|
+
register(first);
|
|
17
|
+
const bound = await first.bindRegisteredRenderable("deployment", {}, "conversation-1", undefined, { platform: "teams", signal: new AbortController().signal });
|
|
18
|
+
expect(bound.onReaction).toBe(reactionHandler);
|
|
19
|
+
await first.persistMessageReaction("message-1", {
|
|
20
|
+
component: "deployment",
|
|
21
|
+
props: {},
|
|
22
|
+
conversationKey: "conversation-1",
|
|
23
|
+
platform: "teams",
|
|
24
|
+
});
|
|
25
|
+
const restarted = new ActionRegistry({ store });
|
|
26
|
+
register(restarted);
|
|
27
|
+
expect(await restarted.resolveMessageReaction("message-1")).toBe(reactionHandler);
|
|
28
|
+
expect(renderPlatforms).toEqual(["teams", "teams"]);
|
|
29
|
+
});
|
package/dist/create-channel.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { Transcripts } from "./transcripts.js";
|
|
|
10
10
|
import type { TranscriptsConfig } from "./transcripts.js";
|
|
11
11
|
import type { ChannelIdentifyUser } from "./identity.js";
|
|
12
12
|
import type { StandardSchemaV1, InferSchemaOutput } from "./standard-schema.js";
|
|
13
|
+
import type { ChannelComponentDefinition } from "./channel-component.js";
|
|
13
14
|
export type LockConflictDecision = "drop" | "force";
|
|
14
15
|
/**
|
|
15
16
|
* How overlapping turns on the same `conversationKey` are handled.
|
|
@@ -43,7 +44,8 @@ export type ChannelConcurrency = "parallel" | "serial" | "drop";
|
|
|
43
44
|
*
|
|
44
45
|
* Fails loud on all three ways cloning can fail to isolate: a missing `clone()`,
|
|
45
46
|
* a `clone()` that hands back the same object, and a `clone()` that silently
|
|
46
|
-
* drops subclass state (see {@link
|
|
47
|
+
* drops subclass state (see {@link warnOnCloneDroppedOwnFields}, which reports
|
|
48
|
+
* rather than refuses).
|
|
47
49
|
*/
|
|
48
50
|
export declare function isolateAgentInstance(prototype: AbstractAgent, threadId: string): AbstractAgent;
|
|
49
51
|
/**
|
|
@@ -62,6 +64,7 @@ export declare function resolveChannelConcurrency(cfg: {
|
|
|
62
64
|
* with the props persisted in the store, so the specific shape isn't needed here.
|
|
63
65
|
*/
|
|
64
66
|
export type ChannelComponent = (props: never) => ReturnType<ComponentFn>;
|
|
67
|
+
export type ChannelComponentRegistration = ChannelComponent | ChannelComponentDefinition;
|
|
65
68
|
export type ChannelHandler<TState = unknown> = (ctx: {
|
|
66
69
|
thread: StatefulThread<TState>;
|
|
67
70
|
message: ChannelMessage;
|
|
@@ -237,12 +240,11 @@ export interface CreateChannelOptions<TStateSchema extends StandardSchemaV1 | un
|
|
|
237
240
|
tools?: ChannelTool[];
|
|
238
241
|
context?: ContextEntry[];
|
|
239
242
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* restart degrades to "action expired".
|
|
243
|
+
* Agent-rendered component definitions or legacy named JSX components.
|
|
244
|
+
* Definitions become tools. Both forms let the channel recover keyed handlers
|
|
245
|
+
* after a restart when the configured store is durable.
|
|
244
246
|
*/
|
|
245
|
-
components?:
|
|
247
|
+
components?: ChannelComponentRegistration[] | Record<string, ChannelComponent>;
|
|
246
248
|
/** Slash commands. Forwarded to adapters that support them; ignored elsewhere. */
|
|
247
249
|
commands?: ChannelCommand[];
|
|
248
250
|
/** Persistence, per-thread state schema, transcripts, and lock/dedup tuning. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-channel.d.ts","sourceRoot":"","sources":["../src/create-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAUf,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,eAAe,EACf,UAAU,EAGV,WAAW,EACX,UAAU,
|
|
1
|
+
{"version":3,"file":"create-channel.d.ts","sourceRoot":"","sources":["../src/create-channel.ts"],"names":[],"mappings":"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"}
|
package/dist/create-channel.js
CHANGED
|
@@ -8,6 +8,7 @@ import { sanitizeAgentEventStream } from "./sanitize-agent-events.js";
|
|
|
8
8
|
import { normalizeEmoji, toCanonicalEmoji, renderToIR, } from "@copilotkit/channels-ui";
|
|
9
9
|
import { Transcripts } from "./transcripts.js";
|
|
10
10
|
import { resolveChannelUser } from "./identity.js";
|
|
11
|
+
import { isChannelComponentDefinition } from "./channel-component.js";
|
|
11
12
|
import { ChannelTelemetry } from "./telemetry/channel-telemetry.js";
|
|
12
13
|
import { errorClass, normalizePlatform } from "./telemetry/sanitize-error.js";
|
|
13
14
|
import { createRequire } from "node:module";
|
|
@@ -58,7 +59,8 @@ function isEmojiPlatform(platform) {
|
|
|
58
59
|
*
|
|
59
60
|
* Fails loud on all three ways cloning can fail to isolate: a missing `clone()`,
|
|
60
61
|
* a `clone()` that hands back the same object, and a `clone()` that silently
|
|
61
|
-
* drops subclass state (see {@link
|
|
62
|
+
* drops subclass state (see {@link warnOnCloneDroppedOwnFields}, which reports
|
|
63
|
+
* rather than refuses).
|
|
62
64
|
*/
|
|
63
65
|
export function isolateAgentInstance(prototype, threadId) {
|
|
64
66
|
if (typeof prototype.clone !== "function") {
|
|
@@ -70,7 +72,7 @@ export function isolateAgentInstance(prototype, threadId) {
|
|
|
70
72
|
if (cloned == null || cloned === prototype) {
|
|
71
73
|
throw new Error("createChannel: agent.clone() must return a distinct instance for concurrent turns");
|
|
72
74
|
}
|
|
73
|
-
|
|
75
|
+
warnOnCloneDroppedOwnFields(prototype, cloned);
|
|
74
76
|
cloned.threadId = threadId;
|
|
75
77
|
// `clone()` copies `isRunning` from the source, and a source that has already
|
|
76
78
|
// run can be mid-run at the moment it is cloned. A fresh turn is not.
|
|
@@ -90,7 +92,7 @@ export function isolateAgentInstance(prototype, threadId) {
|
|
|
90
92
|
return cloned;
|
|
91
93
|
}
|
|
92
94
|
/**
|
|
93
|
-
*
|
|
95
|
+
* Report — but do not refuse — a `clone()` that drops subclass state.
|
|
94
96
|
*
|
|
95
97
|
* `AbstractAgent.prototype.clone()` copies a fixed field list, so a subclass
|
|
96
98
|
* that declares its own fields (an auth client, config, a cache) gets them back
|
|
@@ -98,27 +100,49 @@ export function isolateAgentInstance(prototype, threadId) {
|
|
|
98
100
|
* exists and returns a correctly-typed instance, nothing else surfaces it. The
|
|
99
101
|
* agent just runs gutted.
|
|
100
102
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
103
|
+
* This used to throw. It no longer does, because whether a dropped field matters
|
|
104
|
+
* depends on what the field HOLDS, and from here the two cases are
|
|
105
|
+
* indistinguishable:
|
|
106
|
+
*
|
|
107
|
+
* - **Config** (`orchestrationAgentUrl`, an auth client) is read during the run
|
|
108
|
+
* and never rewritten, so losing it does gut the agent.
|
|
109
|
+
* - **Per-run scratch state** is re-initialized at the start of every run, so
|
|
110
|
+
* losing it changes nothing. `LangGraphAgent`'s `emittedToolCallStartIds` and
|
|
111
|
+
* `eventsStreamActive` are exactly this: both are reset when a run binds its
|
|
112
|
+
* subscriber, before anything reads them.
|
|
113
|
+
*
|
|
114
|
+
* Throwing on the second case took a Channel that works and refused every turn
|
|
115
|
+
* — while the identical clone happens on every ordinary runtime request
|
|
116
|
+
* (`agent-utils.ts` clones per request) with no ill effect at all, which is why
|
|
117
|
+
* it had never been noticed outside Channels. A warning keeps the signal for the
|
|
118
|
+
* config case without failing the harmless one.
|
|
119
|
+
*
|
|
120
|
+
* Deliberately NOT done: copying the dropped fields onto the clone. That would
|
|
121
|
+
* share one mutable object across concurrent turns, which is the exact hazard
|
|
122
|
+
* this isolation exists to prevent.
|
|
123
|
+
*
|
|
124
|
+
* Comparing own enumerable keys stays quiet for the agents that override
|
|
125
|
+
* `clone()` fully (`HttpAgent`, `BuiltInAgent`, `IntelligenceAgent`).
|
|
126
|
+
* Symbol-keyed and non-enumerable fields are not covered.
|
|
105
127
|
*
|
|
106
128
|
* Own *functions* are deliberately exempt. Assigning a method on the instance is
|
|
107
129
|
* how spies and instrumentation wrap an agent, and losing that wrapper leaves
|
|
108
130
|
* the class's prototype method intact — the clone still behaves correctly, it
|
|
109
131
|
* just isn't wrapped. Only dropped state leaves an agent genuinely gutted.
|
|
110
132
|
*/
|
|
111
|
-
function
|
|
133
|
+
function warnOnCloneDroppedOwnFields(prototype, cloned) {
|
|
112
134
|
const source = prototype;
|
|
113
135
|
const dropped = Object.keys(prototype).filter((key) => typeof source[key] !== "function" &&
|
|
114
136
|
!Object.prototype.hasOwnProperty.call(cloned, key));
|
|
115
137
|
if (dropped.length === 0)
|
|
116
138
|
return;
|
|
117
139
|
const name = prototype.constructor?.name ?? "the configured agent";
|
|
118
|
-
|
|
140
|
+
console.warn(`createChannel: ${name}.clone() dropped ${dropped.join(", ")}. ` +
|
|
119
141
|
"Every turn runs on a clone, and AbstractAgent's clone() only copies its " +
|
|
120
|
-
|
|
121
|
-
|
|
142
|
+
"own fixed field list, so those fields read as undefined on the clone. " +
|
|
143
|
+
"That is harmless for state a run re-initializes, and gutting for anything " +
|
|
144
|
+
`read as configuration — override clone() on ${name} to carry them if it is ` +
|
|
145
|
+
"the latter (HttpAgent.clone() is the reference).");
|
|
122
146
|
}
|
|
123
147
|
/**
|
|
124
148
|
* Resolve effective turn concurrency from `store.concurrency` and legacy
|
|
@@ -256,8 +280,59 @@ export function createChannel(opts) {
|
|
|
256
280
|
return run;
|
|
257
281
|
}
|
|
258
282
|
const toolMap = new Map();
|
|
259
|
-
|
|
260
|
-
|
|
283
|
+
let componentConfigurationError;
|
|
284
|
+
for (const tool of opts.tools ?? []) {
|
|
285
|
+
if (toolMap.has(tool.name)) {
|
|
286
|
+
componentConfigurationError = new Error(`duplicate channel tool or component name "${tool.name}"`);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
toolMap.set(tool.name, tool);
|
|
290
|
+
}
|
|
291
|
+
const componentEntries = [];
|
|
292
|
+
const configuredComponents = Array.isArray(opts.components)
|
|
293
|
+
? opts.components.map((component) => ({
|
|
294
|
+
name: component.name,
|
|
295
|
+
component,
|
|
296
|
+
}))
|
|
297
|
+
: Object.entries(opts.components ?? {}).map(([name, component]) => ({
|
|
298
|
+
name,
|
|
299
|
+
component,
|
|
300
|
+
}));
|
|
301
|
+
const componentNames = new Set();
|
|
302
|
+
for (const { name, component } of configuredComponents) {
|
|
303
|
+
if (!name)
|
|
304
|
+
continue;
|
|
305
|
+
if (componentNames.has(name) || toolMap.has(name)) {
|
|
306
|
+
componentConfigurationError = new Error(`duplicate channel tool or component name "${name}"`);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
componentNames.add(name);
|
|
310
|
+
if (isChannelComponentDefinition(component)) {
|
|
311
|
+
componentEntries.push({
|
|
312
|
+
name,
|
|
313
|
+
requireKeys: true,
|
|
314
|
+
render: (props, renderContext) => component.render(props, renderContext),
|
|
315
|
+
});
|
|
316
|
+
toolMap.set(name, {
|
|
317
|
+
name,
|
|
318
|
+
description: component.description,
|
|
319
|
+
parameters: component.parameters,
|
|
320
|
+
async handler(args, toolContext) {
|
|
321
|
+
await toolContext.thread.postRegisteredComponent(name, args, {
|
|
322
|
+
platform: toolContext.platform,
|
|
323
|
+
signal: toolContext.signal ?? new AbortController().signal,
|
|
324
|
+
});
|
|
325
|
+
return `Rendered component "${name}".`;
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
componentEntries.push({
|
|
331
|
+
name,
|
|
332
|
+
requireKeys: false,
|
|
333
|
+
render: (props) => component(props),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
261
336
|
const context = opts.context ?? [];
|
|
262
337
|
const mentionHandlers = [];
|
|
263
338
|
const messageHandlers = [];
|
|
@@ -681,6 +756,8 @@ export function createChannel(opts) {
|
|
|
681
756
|
// and real adapters would connect/port-bind twice.
|
|
682
757
|
if (started)
|
|
683
758
|
return;
|
|
759
|
+
if (componentConfigurationError)
|
|
760
|
+
throw componentConfigurationError;
|
|
684
761
|
if (adapters.some((adapter) => adapter.capabilities.supportsMessageEvents) &&
|
|
685
762
|
mentionHandlers.length === 0 &&
|
|
686
763
|
messageHandlers.length === 0) {
|
|
@@ -706,20 +783,22 @@ export function createChannel(opts) {
|
|
|
706
783
|
retentionMs: cfg.actionRetentionMs ?? 7 * 24 * 60 * 60 * 1000,
|
|
707
784
|
});
|
|
708
785
|
registry = registryInstance;
|
|
709
|
-
for (const
|
|
710
|
-
if (!
|
|
786
|
+
for (const component of componentEntries) {
|
|
787
|
+
if (!component.name) {
|
|
711
788
|
console.warn("[channel] createChannel: skipping anonymous component — give it a name to enable durable actions after restart.");
|
|
712
789
|
continue;
|
|
713
790
|
}
|
|
714
|
-
registryInstance.registerComponent(
|
|
791
|
+
registryInstance.registerComponent(component.name, component.render, {
|
|
792
|
+
requireKeys: component.requireKeys,
|
|
793
|
+
});
|
|
715
794
|
}
|
|
716
795
|
toolDescriptors = toAgentToolDescriptors([...toolMap.values()]);
|
|
717
796
|
tel.capture("oss.channel.configured", {
|
|
718
797
|
platforms: adapters.map((a) => normalizePlatform(a.platform)),
|
|
719
798
|
adapterCount: adapters.length,
|
|
720
799
|
store: storeKind(backend),
|
|
721
|
-
hasComponents:
|
|
722
|
-
componentsCount:
|
|
800
|
+
hasComponents: componentEntries.length > 0,
|
|
801
|
+
componentsCount: componentEntries.length,
|
|
723
802
|
toolsCount: toolMap.size,
|
|
724
803
|
commandsCount: commandHandlers.size,
|
|
725
804
|
contextCount: context.length,
|
|
@@ -985,8 +985,9 @@ describe("createChannel", () => {
|
|
|
985
985
|
// Nothing ever runs on the configured object, so it stays pristine.
|
|
986
986
|
expect(shared.messages).toEqual([]);
|
|
987
987
|
});
|
|
988
|
-
it("agent whose clone() drops subclass state
|
|
988
|
+
it("agent whose clone() drops subclass state warns and still runs the turn", async () => {
|
|
989
989
|
const state = new MemoryStore();
|
|
990
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
990
991
|
// Inherits `FakeAgent.clone()`, which builds a plain `FakeAgent` and so
|
|
991
992
|
// cannot carry this field — the same shape as a subclass inheriting
|
|
992
993
|
// `AbstractAgent.prototype.clone()`, which copies a fixed field list.
|
|
@@ -1006,13 +1007,46 @@ describe("createChannel", () => {
|
|
|
1006
1007
|
});
|
|
1007
1008
|
await channel.ɵruntime.start();
|
|
1008
1009
|
const sink = fake.getSink();
|
|
1010
|
+
// Reports it, and does not refuse the turn: whether a dropped field matters
|
|
1011
|
+
// depends on what it holds, and this cannot tell config from per-run state.
|
|
1009
1012
|
await expect(sink.onTurn({
|
|
1010
1013
|
conversationKey: "c1",
|
|
1011
1014
|
replyTarget: {},
|
|
1012
1015
|
userText: "hi",
|
|
1013
1016
|
platform: "fake",
|
|
1014
1017
|
eventId: "E1",
|
|
1015
|
-
})).
|
|
1018
|
+
})).resolves.not.toThrow();
|
|
1019
|
+
expect(warn).toHaveBeenCalledWith(expect.stringMatching(/StatefulAgent\.clone\(\) dropped authClient/));
|
|
1020
|
+
warn.mockRestore();
|
|
1021
|
+
});
|
|
1022
|
+
it("the dropped-state warning names every field, once per turn", async () => {
|
|
1023
|
+
const state = new MemoryStore();
|
|
1024
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1025
|
+
class TwoFieldAgent extends FakeAgent {
|
|
1026
|
+
authClient = { token: "secret" };
|
|
1027
|
+
cache = new Map();
|
|
1028
|
+
}
|
|
1029
|
+
const fake = new FakeAdapter();
|
|
1030
|
+
const channel = createChannel({
|
|
1031
|
+
identifyUser: "platform",
|
|
1032
|
+
adapters: [fake],
|
|
1033
|
+
agent: () => new TwoFieldAgent(),
|
|
1034
|
+
store: { adapter: state },
|
|
1035
|
+
});
|
|
1036
|
+
channel.onMention(async ({ thread }) => {
|
|
1037
|
+
await thread.runAgent({ prompt: "hi" });
|
|
1038
|
+
});
|
|
1039
|
+
await channel.ɵruntime.start();
|
|
1040
|
+
await fake.getSink().onTurn({
|
|
1041
|
+
conversationKey: "c1",
|
|
1042
|
+
replyTarget: {},
|
|
1043
|
+
userText: "hi",
|
|
1044
|
+
platform: "fake",
|
|
1045
|
+
eventId: "E1",
|
|
1046
|
+
});
|
|
1047
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
1048
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("authClient, cache"));
|
|
1049
|
+
warn.mockRestore();
|
|
1016
1050
|
});
|
|
1017
1051
|
it("does not fail loud when clone() drops an instance-patched method", async () => {
|
|
1018
1052
|
const state = new MemoryStore();
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ export { createChannel } from "./create-channel.js";
|
|
|
2
2
|
export { isolateAgentInstance, resolveChannelConcurrency, } from "./create-channel.js";
|
|
3
3
|
export { sanitizeAgentEventStream } from "./sanitize-agent-events.js";
|
|
4
4
|
export { HttpAgent } from "@ag-ui/client";
|
|
5
|
-
export type { Channel, CreateChannelOptions, ReplyContinuationOptions, ChannelHandler, WelcomeHandler, ThreadStartHandler, ReactionEvent, ReactionHandler, ModalSubmitEvent, ModalSubmitHandler, ModalCloseEvent, ModalCloseHandler, StoreConfig, LockConflictDecision, ChannelConcurrency, StatefulThread, ChannelComponent, } from "./create-channel.js";
|
|
5
|
+
export type { Channel, CreateChannelOptions, ReplyContinuationOptions, ChannelHandler, WelcomeHandler, ThreadStartHandler, ReactionEvent, ReactionHandler, ModalSubmitEvent, ModalSubmitHandler, ModalCloseEvent, ModalCloseHandler, StoreConfig, LockConflictDecision, ChannelConcurrency, StatefulThread, ChannelComponent, ChannelComponentRegistration, } from "./create-channel.js";
|
|
6
|
+
export { defineChannelComponent } from "./channel-component.js";
|
|
7
|
+
export type { ChannelComponentDefinition, ChannelComponentPlatform, ChannelComponentRenderContext, } from "./channel-component.js";
|
|
6
8
|
export { ChannelIdentityResolutionError, ChannelIdentityResultError, resolveChannelUser, } from "./identity.js";
|
|
7
9
|
export type { ChannelConversation, ChannelEvent, ChannelIdentifyUser, ChannelIdentityContext, ChannelInstallation, ChannelTenant, IngressIdentityContext, } from "./identity.js";
|
|
8
10
|
export { Thread } from "./thread.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACL,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,YAAY,EACV,OAAO,EACP,oBAAoB,EACpB,wBAAwB,EACxB,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { sanitizeAgentEventStream } from "./sanitize-agent-events.js";
|
|
|
8
8
|
// The usual `agent` for a Channel is an AG-UI agent over HTTP. Re-exported so
|
|
9
9
|
// wiring one up needs no second import (any `AbstractAgent` still works).
|
|
10
10
|
export { HttpAgent } from "@ag-ui/client";
|
|
11
|
+
export { defineChannelComponent } from "./channel-component.js";
|
|
11
12
|
export { ChannelIdentityResolutionError, ChannelIdentityResultError, resolveChannelUser, } from "./identity.js";
|
|
12
13
|
// Thread
|
|
13
14
|
export { Thread } from "./thread.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyed-action-recovery.test.d.ts","sourceRoot":"","sources":["../src/keyed-action-recovery.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@copilotkit/channels-core/jsx-runtime";
|
|
2
|
+
import { createNativeNode } from "@copilotkit/channels-ui";
|
|
3
|
+
import { expect, test, vi } from "vitest";
|
|
4
|
+
import { ActionRegistry } from "./action-registry.js";
|
|
5
|
+
import { InMemoryActionStore } from "./action-store.js";
|
|
6
|
+
function SlackButton(props) {
|
|
7
|
+
return createNativeNode("slack", "element", "button", props);
|
|
8
|
+
}
|
|
9
|
+
async function unusedChoice() {
|
|
10
|
+
throw new Error("awaitChoice is not used by this fixture");
|
|
11
|
+
}
|
|
12
|
+
async function emptyState() {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
function interactionContext(id) {
|
|
16
|
+
const thread = {
|
|
17
|
+
platform: "slack",
|
|
18
|
+
post: async () => ({ id: "message-1" }),
|
|
19
|
+
update: async (ref) => ref,
|
|
20
|
+
delete: async () => undefined,
|
|
21
|
+
awaitChoice: unusedChoice,
|
|
22
|
+
runAgent: async () => undefined,
|
|
23
|
+
resume: async () => undefined,
|
|
24
|
+
stream: async () => ({ id: "message-1" }),
|
|
25
|
+
postFile: async () => ({ ok: true, fileId: "file-1" }),
|
|
26
|
+
getMessages: async () => [],
|
|
27
|
+
lookupUser: async () => undefined,
|
|
28
|
+
setSuggestedPrompts: async () => ({ ok: true }),
|
|
29
|
+
setTitle: async () => ({ ok: true }),
|
|
30
|
+
react: async () => ({ ok: true }),
|
|
31
|
+
unreact: async () => ({ ok: true }),
|
|
32
|
+
postEphemeral: async () => null,
|
|
33
|
+
subscribe: async () => undefined,
|
|
34
|
+
unsubscribe: async () => undefined,
|
|
35
|
+
isSubscribed: async () => false,
|
|
36
|
+
setState: async () => undefined,
|
|
37
|
+
state: emptyState,
|
|
38
|
+
};
|
|
39
|
+
const actor = { id: "user-1", kind: "human" };
|
|
40
|
+
return {
|
|
41
|
+
thread,
|
|
42
|
+
message: {
|
|
43
|
+
text: "",
|
|
44
|
+
user: null,
|
|
45
|
+
actor,
|
|
46
|
+
ref: { id: "message-1" },
|
|
47
|
+
platform: "slack",
|
|
48
|
+
},
|
|
49
|
+
action: { id, value: { decision: "provider-mutated" } },
|
|
50
|
+
values: {},
|
|
51
|
+
user: null,
|
|
52
|
+
actor,
|
|
53
|
+
platform: "slack",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function actionId(tree, key) {
|
|
57
|
+
const elements = tree[0]?.props.elements;
|
|
58
|
+
if (!Array.isArray(elements)) {
|
|
59
|
+
throw new Error("expected native action elements");
|
|
60
|
+
}
|
|
61
|
+
const node = elements.find((candidate) => typeof candidate === "object" &&
|
|
62
|
+
candidate !== null &&
|
|
63
|
+
"key" in candidate &&
|
|
64
|
+
candidate.key === key);
|
|
65
|
+
if (!node || typeof node !== "object" || !("props" in node)) {
|
|
66
|
+
throw new Error(`expected action with key ${key}`);
|
|
67
|
+
}
|
|
68
|
+
const handler = node.props.onClick;
|
|
69
|
+
if (typeof handler !== "object" ||
|
|
70
|
+
handler === null ||
|
|
71
|
+
!("id" in handler) ||
|
|
72
|
+
typeof handler.id !== "string") {
|
|
73
|
+
throw new Error(`expected bound action with key ${key}`);
|
|
74
|
+
}
|
|
75
|
+
return handler.id;
|
|
76
|
+
}
|
|
77
|
+
test("cold recovery finds the same keyed handler after async sibling reorder", async () => {
|
|
78
|
+
const store = new InMemoryActionStore();
|
|
79
|
+
const handled = vi.fn();
|
|
80
|
+
let reordered = false;
|
|
81
|
+
const render = async () => {
|
|
82
|
+
await Promise.resolve();
|
|
83
|
+
const approve = (_jsx(SlackButton, { text: "Approve", value: { decision: reordered ? "changed" : "approve" }, onClick: (context) => handled("approve", context.action.value) }, "approve-order"));
|
|
84
|
+
const reject = (_jsx(SlackButton, { text: "Reject", value: { decision: "reject" }, onClick: (context) => handled("reject", context.action.value) }, "reject-order"));
|
|
85
|
+
const unrelated = (_jsx(SlackButton, { text: "Inspect", value: { decision: "inspect" }, onClick: (context) => handled("inspect", context.action.value) }, "inspect-order"));
|
|
86
|
+
return createNativeNode("slack", "block", "actions", {
|
|
87
|
+
elements: reordered ? [unrelated, reject, approve] : [approve, reject],
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const first = new ActionRegistry({ store });
|
|
91
|
+
first.registerComponent("order_actions", render, { requireKeys: true });
|
|
92
|
+
const tree = await first.bindTree("order_actions", {}, "conversation-1", undefined, { platform: "slack", signal: new AbortController().signal });
|
|
93
|
+
const id = actionId(tree, "approve-order");
|
|
94
|
+
reordered = true;
|
|
95
|
+
const restarted = new ActionRegistry({ store });
|
|
96
|
+
restarted.registerComponent("order_actions", render, {
|
|
97
|
+
requireKeys: true,
|
|
98
|
+
});
|
|
99
|
+
const value = await restarted.dispatch(id, interactionContext(id));
|
|
100
|
+
expect(value).toEqual({ decision: "approve" });
|
|
101
|
+
expect(handled).toHaveBeenCalledOnce();
|
|
102
|
+
expect(handled).toHaveBeenCalledWith("approve", { decision: "approve" });
|
|
103
|
+
});
|
|
104
|
+
test("component tools reject a handler without a stable JSX key", async () => {
|
|
105
|
+
const registry = new ActionRegistry({ store: new InMemoryActionStore() });
|
|
106
|
+
registry.registerComponent("missing_key", () => createNativeNode("slack", "element", "button", {
|
|
107
|
+
text: "Approve",
|
|
108
|
+
onClick: () => undefined,
|
|
109
|
+
}), { requireKeys: true });
|
|
110
|
+
await expect(registry.bindTree("missing_key", {}, "conversation-1")).rejects.toThrow("missing_key[0].onClick requires a non-empty JSX key");
|
|
111
|
+
});
|
|
112
|
+
test("component tools reject duplicate interactive keys", async () => {
|
|
113
|
+
const registry = new ActionRegistry({ store: new InMemoryActionStore() });
|
|
114
|
+
registry.registerComponent("duplicate_key", () => createNativeNode("slack", "block", "actions", {
|
|
115
|
+
elements: [
|
|
116
|
+
_jsx(SlackButton, { text: "Approve", value: { decision: "approve" }, onClick: () => undefined }, "decision"),
|
|
117
|
+
_jsx(SlackButton, { text: "Reject", value: { decision: "reject" }, onClick: () => undefined }, "decision"),
|
|
118
|
+
],
|
|
119
|
+
}), { requireKeys: true });
|
|
120
|
+
await expect(registry.bindTree("duplicate_key", {}, "conversation-1")).rejects.toThrow('duplicate interactive JSX key "decision"');
|
|
121
|
+
});
|
package/dist/thread.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { AbstractAgent } from "@ag-ui/client";
|
|
|
7
7
|
import type { StateStore } from "./state/state-store.js";
|
|
8
8
|
import type { StandardSchemaV1 } from "./standard-schema.js";
|
|
9
9
|
import type { MemoryGrant } from "./memory.js";
|
|
10
|
+
import type { ChannelComponentRenderContext } from "./channel-component.js";
|
|
10
11
|
/** A Channel run requested Memory without an attached Intelligence backend. */
|
|
11
12
|
export declare class ChannelMemoryUnavailableError extends Error {
|
|
12
13
|
readonly code = "channel_memory_unavailable";
|
|
@@ -98,6 +99,8 @@ export declare class Thread implements ThreadInterface {
|
|
|
98
99
|
*/
|
|
99
100
|
private bindReaction;
|
|
100
101
|
post(ui: Renderable): Promise<MessageRef>;
|
|
102
|
+
/** @internal Post a registered component through the normal bind and adapter path. */
|
|
103
|
+
postRegisteredComponent(componentName: string, props: Record<string, unknown>, renderContext: ChannelComponentRenderContext): Promise<MessageRef>;
|
|
101
104
|
update(ref: MessageRef, ui: Renderable): Promise<MessageRef>;
|
|
102
105
|
delete(ref: MessageRef): Promise<void>;
|
|
103
106
|
stream(src: string | AsyncIterable<string>): Promise<MessageRef>;
|
package/dist/thread.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;AAK3D,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,EACf,MAAM,IAAI,eAAe,EACzB,UAAU,EACV,eAAe,EAChB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EACV,WAAW,EAEX,YAAY,EACZ,mBAAmB,EACpB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAyB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../src/thread.ts"],"names":[],"mappings":"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"}
|
package/dist/thread.js
CHANGED
|
@@ -64,7 +64,10 @@ export class Thread {
|
|
|
64
64
|
this.store = deps.state;
|
|
65
65
|
}
|
|
66
66
|
async bindForPost(ui) {
|
|
67
|
-
return this.deps.registry.bindRenderable(ui, this.deps.conversationKey, this.activeContinuation
|
|
67
|
+
return this.deps.registry.bindRenderable(ui, this.deps.conversationKey, this.activeContinuation, {
|
|
68
|
+
platform: this.platform,
|
|
69
|
+
signal: new AbortController().signal,
|
|
70
|
+
});
|
|
68
71
|
}
|
|
69
72
|
trackOperation(operation) {
|
|
70
73
|
if (!this.deps.adapter.trackThreadOperation) {
|
|
@@ -107,6 +110,15 @@ export class Thread {
|
|
|
107
110
|
return ref;
|
|
108
111
|
});
|
|
109
112
|
}
|
|
113
|
+
/** @internal Post a registered component through the normal bind and adapter path. */
|
|
114
|
+
postRegisteredComponent(componentName, props, renderContext) {
|
|
115
|
+
return this.trackOperation(async () => {
|
|
116
|
+
const bound = await this.deps.registry.bindRegisteredRenderable(componentName, props, this.deps.conversationKey, this.activeContinuation, renderContext);
|
|
117
|
+
const ref = await this.deps.adapter.post(this.deps.replyTarget, bound.root);
|
|
118
|
+
await this.bindReaction(ref.id, bound);
|
|
119
|
+
return ref;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
110
122
|
update(ref, ui) {
|
|
111
123
|
return this.trackOperation(async () => {
|
|
112
124
|
const bound = await this.bindForPost(ui);
|
|
@@ -478,6 +490,7 @@ export class Thread {
|
|
|
478
490
|
message: this.deps.message,
|
|
479
491
|
user: this.deps.user,
|
|
480
492
|
actor: this.deps.actor,
|
|
493
|
+
signal: session.agent.abortController?.signal ?? new AbortController().signal,
|
|
481
494
|
platform: this.platform,
|
|
482
495
|
}),
|
|
483
496
|
handleInterrupt: async (interrupt) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/channels-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2-canary.1785779327",
|
|
4
4
|
"description": "Platform-agnostic JSX channel engine for CopilotKit (createChannel, Thread, PlatformAdapter, ActionStore).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@ag-ui/client": "0.0.57",
|
|
52
52
|
"@ag-ui/core": "0.0.57",
|
|
53
|
-
"
|
|
54
|
-
"@copilotkit/core": "
|
|
55
|
-
"@copilotkit/shared": "
|
|
56
|
-
"
|
|
53
|
+
"@copilotkit/channels-ui": "0.6.2-canary.1785779327",
|
|
54
|
+
"@copilotkit/core": "1.65.1-canary.1785779327",
|
|
55
|
+
"@copilotkit/shared": "1.65.1-canary.1785779327",
|
|
56
|
+
"zod-to-json-schema": "^3.24.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^22.10.0",
|