@copilotkit/channels 0.1.0
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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/action-registry.d.ts +59 -0
- package/dist/action-registry.d.ts.map +1 -0
- package/dist/action-registry.js +217 -0
- package/dist/action-registry.test.d.ts +2 -0
- package/dist/action-registry.test.d.ts.map +1 -0
- package/dist/action-registry.test.js +115 -0
- package/dist/action-store.d.ts +21 -0
- package/dist/action-store.d.ts.map +1 -0
- package/dist/action-store.js +23 -0
- package/dist/action-store.test.d.ts +2 -0
- package/dist/action-store.test.d.ts.map +1 -0
- package/dist/action-store.test.js +27 -0
- package/dist/codec.d.ts +25 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +1 -0
- package/dist/commands.d.ts +73 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +38 -0
- package/dist/commands.test.d.ts +2 -0
- package/dist/commands.test.d.ts.map +1 -0
- package/dist/commands.test.js +23 -0
- package/dist/create-bot.d.ts +171 -0
- package/dist/create-bot.d.ts.map +1 -0
- package/dist/create-bot.foundations.test.d.ts +2 -0
- package/dist/create-bot.foundations.test.d.ts.map +1 -0
- package/dist/create-bot.foundations.test.js +147 -0
- package/dist/create-bot.js +576 -0
- package/dist/create-bot.test.d.ts +2 -0
- package/dist/create-bot.test.d.ts.map +1 -0
- package/dist/create-bot.test.js +936 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/mint-id.d.ts +3 -0
- package/dist/mint-id.d.ts.map +1 -0
- package/dist/mint-id.js +20 -0
- package/dist/mint-id.test.d.ts +2 -0
- package/dist/mint-id.test.d.ts.map +1 -0
- package/dist/mint-id.test.js +16 -0
- package/dist/modal-submit.test.d.ts +2 -0
- package/dist/modal-submit.test.d.ts.map +1 -0
- package/dist/modal-submit.test.js +68 -0
- package/dist/platform-adapter.d.ts +312 -0
- package/dist/platform-adapter.d.ts.map +1 -0
- package/dist/platform-adapter.js +1 -0
- package/dist/platform-adapter.test.d.ts +2 -0
- package/dist/platform-adapter.test.d.ts.map +1 -0
- package/dist/platform-adapter.test.js +40 -0
- package/dist/reactions.test.d.ts +2 -0
- package/dist/reactions.test.d.ts.map +1 -0
- package/dist/reactions.test.js +197 -0
- package/dist/run-loop.d.ts +41 -0
- package/dist/run-loop.d.ts.map +1 -0
- package/dist/run-loop.js +102 -0
- package/dist/run-loop.test.d.ts +2 -0
- package/dist/run-loop.test.d.ts.map +1 -0
- package/dist/run-loop.test.js +125 -0
- package/dist/standard-schema.d.ts +52 -0
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js +66 -0
- package/dist/state/duration.d.ts +3 -0
- package/dist/state/duration.d.ts.map +1 -0
- package/dist/state/duration.js +16 -0
- package/dist/state/duration.test.d.ts +2 -0
- package/dist/state/duration.test.d.ts.map +1 -0
- package/dist/state/duration.test.js +13 -0
- package/dist/state/kv-action-store.d.ts +7 -0
- package/dist/state/kv-action-store.d.ts.map +1 -0
- package/dist/state/kv-action-store.js +9 -0
- package/dist/state/kv-action-store.test.d.ts +2 -0
- package/dist/state/kv-action-store.test.d.ts.map +1 -0
- package/dist/state/kv-action-store.test.js +21 -0
- package/dist/state/memory-store.d.ts +41 -0
- package/dist/state/memory-store.d.ts.map +1 -0
- package/dist/state/memory-store.js +110 -0
- package/dist/state/memory-store.test.d.ts +2 -0
- package/dist/state/memory-store.test.d.ts.map +1 -0
- package/dist/state/memory-store.test.js +22 -0
- package/dist/state/state-conversation-store.d.ts +14 -0
- package/dist/state/state-conversation-store.d.ts.map +1 -0
- package/dist/state/state-conversation-store.js +22 -0
- package/dist/state/state-conversation-store.test.d.ts +2 -0
- package/dist/state/state-conversation-store.test.d.ts.map +1 -0
- package/dist/state/state-conversation-store.test.js +58 -0
- package/dist/state/state-store.d.ts +63 -0
- package/dist/state/state-store.d.ts.map +1 -0
- package/dist/state/state-store.js +1 -0
- package/dist/telemetry/bot-telemetry.d.ts +31 -0
- package/dist/telemetry/bot-telemetry.d.ts.map +1 -0
- package/dist/telemetry/bot-telemetry.js +70 -0
- package/dist/telemetry/bot-telemetry.test.d.ts +2 -0
- package/dist/telemetry/bot-telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/bot-telemetry.test.js +56 -0
- package/dist/telemetry/create-bot-telemetry.test.d.ts +2 -0
- package/dist/telemetry/create-bot-telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/create-bot-telemetry.test.js +71 -0
- package/dist/telemetry/e2e-telemetry.test.d.ts +2 -0
- package/dist/telemetry/e2e-telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/e2e-telemetry.test.js +56 -0
- package/dist/telemetry/events-catalog.test.d.ts +2 -0
- package/dist/telemetry/events-catalog.test.d.ts.map +1 -0
- package/dist/telemetry/events-catalog.test.js +8 -0
- package/dist/telemetry/install-id.d.ts +8 -0
- package/dist/telemetry/install-id.d.ts.map +1 -0
- package/dist/telemetry/install-id.js +43 -0
- package/dist/telemetry/install-id.test.d.ts +2 -0
- package/dist/telemetry/install-id.test.d.ts.map +1 -0
- package/dist/telemetry/install-id.test.js +43 -0
- package/dist/telemetry/platform-allowlist-coverage.test.d.ts +2 -0
- package/dist/telemetry/platform-allowlist-coverage.test.d.ts.map +1 -0
- package/dist/telemetry/platform-allowlist-coverage.test.js +45 -0
- package/dist/telemetry/sanitize-error.d.ts +3 -0
- package/dist/telemetry/sanitize-error.d.ts.map +1 -0
- package/dist/telemetry/sanitize-error.js +31 -0
- package/dist/telemetry/sanitize-error.test.d.ts +2 -0
- package/dist/telemetry/sanitize-error.test.d.ts.map +1 -0
- package/dist/telemetry/sanitize-error.test.js +33 -0
- package/dist/testing/fake-adapter.d.ts +137 -0
- package/dist/testing/fake-adapter.d.ts.map +1 -0
- package/dist/testing/fake-adapter.js +273 -0
- package/dist/testing/fake-agent.d.ts +28 -0
- package/dist/testing/fake-agent.d.ts.map +1 -0
- package/dist/testing/fake-agent.js +36 -0
- package/dist/testing/state-store-conformance.d.ts +4 -0
- package/dist/testing/state-store-conformance.d.ts.map +1 -0
- package/dist/testing/state-store-conformance.js +117 -0
- package/dist/thread-capabilities.test.d.ts +2 -0
- package/dist/thread-capabilities.test.d.ts.map +1 -0
- package/dist/thread-capabilities.test.js +77 -0
- package/dist/thread-ephemeral.test.d.ts +2 -0
- package/dist/thread-ephemeral.test.d.ts.map +1 -0
- package/dist/thread-ephemeral.test.js +45 -0
- package/dist/thread-reactions.test.d.ts +2 -0
- package/dist/thread-reactions.test.d.ts.map +1 -0
- package/dist/thread-reactions.test.js +38 -0
- package/dist/thread.d.ts +152 -0
- package/dist/thread.d.ts.map +1 -0
- package/dist/thread.js +329 -0
- package/dist/thread.test.d.ts +2 -0
- package/dist/thread.test.d.ts.map +1 -0
- package/dist/thread.test.js +83 -0
- package/dist/tools.d.ts +67 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +38 -0
- package/dist/tools.test.d.ts +2 -0
- package/dist/tools.test.d.ts.map +1 -0
- package/dist/tools.test.js +31 -0
- package/dist/transcripts.d.ts +52 -0
- package/dist/transcripts.d.ts.map +1 -0
- package/dist/transcripts.js +67 -0
- package/dist/transcripts.test.d.ts +2 -0
- package/dist/transcripts.test.d.ts.map +1 -0
- package/dist/transcripts.test.js +108 -0
- package/package.json +72 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/** A RunRenderer whose subscriber captures tool-call-end and custom (interrupt) events — used by run-loop tests. */
|
|
2
|
+
export function makeFakeRunRenderer() {
|
|
3
|
+
const toolCalls = [];
|
|
4
|
+
let pending;
|
|
5
|
+
const subscriber = {
|
|
6
|
+
onToolCallEndEvent(p) {
|
|
7
|
+
toolCalls.push({
|
|
8
|
+
toolCallId: p.event.toolCallId,
|
|
9
|
+
toolCallName: p.toolCallName,
|
|
10
|
+
toolCallArgs: (p.toolCallArgs ?? {}),
|
|
11
|
+
});
|
|
12
|
+
},
|
|
13
|
+
onCustomEvent(p) {
|
|
14
|
+
const e = p.event;
|
|
15
|
+
if (e.name)
|
|
16
|
+
pending = { eventName: e.name, value: e.value };
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
let finishCalls = 0;
|
|
20
|
+
return {
|
|
21
|
+
subscriber,
|
|
22
|
+
async markInterrupted() { },
|
|
23
|
+
getCapturedToolCalls: () => toolCalls,
|
|
24
|
+
getPendingInterrupt: () => pending,
|
|
25
|
+
clearPendingInterrupt: () => {
|
|
26
|
+
pending = undefined;
|
|
27
|
+
},
|
|
28
|
+
async finish() {
|
|
29
|
+
finishCalls++;
|
|
30
|
+
},
|
|
31
|
+
get finishCalls() {
|
|
32
|
+
return finishCalls;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export class FakeAdapter {
|
|
37
|
+
platform = "fake";
|
|
38
|
+
capabilities;
|
|
39
|
+
ackDeadlineMs = 3000;
|
|
40
|
+
/** When true, `start()` rejects (set via constructor `failStart`). */
|
|
41
|
+
failStart;
|
|
42
|
+
/** When true, `registerCommands()` rejects (set via constructor `failRegisterCommands`). */
|
|
43
|
+
failRegisterCommands;
|
|
44
|
+
/** When true, `stop()` rejects (set via constructor `failStop`). */
|
|
45
|
+
failStop;
|
|
46
|
+
/** Set once `start()` has run to completion; lets tests assert a healthy adapter still started. */
|
|
47
|
+
started = false;
|
|
48
|
+
/**
|
|
49
|
+
* @param fakeOpts.paneMethods When `false`, the optional
|
|
50
|
+
* `setSuggestedPrompts`/`setThreadTitle` methods are omitted (and the
|
|
51
|
+
* matching capability flags cleared) so tests can exercise the
|
|
52
|
+
* capability-gated `{ ok: false }` path. Defaults to present.
|
|
53
|
+
* @param fakeOpts.reactions When `false`, `addReaction`/`removeReaction` are
|
|
54
|
+
* omitted and `supportsReactions` is cleared. Defaults to present.
|
|
55
|
+
* @param fakeOpts.nativeEphemeral When `true`, `postEphemeral` reports a
|
|
56
|
+
* native success (`usedFallback: false`) and `supportsEphemeral` is set;
|
|
57
|
+
* otherwise DM-fallback / `null` semantics apply and `supportsEphemeral`
|
|
58
|
+
* is `false`.
|
|
59
|
+
* @param fakeOpts.modals When `false`, `renderModal`/`openModal` are omitted
|
|
60
|
+
* and `supportsModals` is `false`. Defaults to present.
|
|
61
|
+
*/
|
|
62
|
+
constructor(fakeOpts = {}) {
|
|
63
|
+
if (fakeOpts.platform)
|
|
64
|
+
this.platform = fakeOpts.platform;
|
|
65
|
+
this.failStart = fakeOpts.failStart === true;
|
|
66
|
+
this.failRegisterCommands = fakeOpts.failRegisterCommands === true;
|
|
67
|
+
this.failStop = fakeOpts.failStop === true;
|
|
68
|
+
const paneMethods = fakeOpts.paneMethods !== false;
|
|
69
|
+
this.capabilities = {
|
|
70
|
+
supportsModals: fakeOpts.modals !== false,
|
|
71
|
+
supportsTyping: false,
|
|
72
|
+
supportsReactions: fakeOpts.reactions !== false,
|
|
73
|
+
supportsStreaming: true,
|
|
74
|
+
supportsSuggestedPrompts: paneMethods,
|
|
75
|
+
supportsThreadTitle: paneMethods,
|
|
76
|
+
supportsEphemeral: fakeOpts.nativeEphemeral === true,
|
|
77
|
+
};
|
|
78
|
+
if (paneMethods) {
|
|
79
|
+
this.setSuggestedPrompts = async (target, prompts, opts) => {
|
|
80
|
+
this.suggestedPromptsCalls.push({ target, prompts, opts });
|
|
81
|
+
return { ok: true };
|
|
82
|
+
};
|
|
83
|
+
this.setThreadTitle = async (target, title) => {
|
|
84
|
+
this.threadTitleCalls.push({ target, title });
|
|
85
|
+
return { ok: true };
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (fakeOpts.reactions !== false) {
|
|
89
|
+
this.addReaction = async (_t, ref, e) => {
|
|
90
|
+
this.reactionsAdded.push({ ref, emoji: e });
|
|
91
|
+
return { ok: true };
|
|
92
|
+
};
|
|
93
|
+
this.removeReaction = async (_t, ref, e) => {
|
|
94
|
+
this.reactionsRemoved.push({ ref, emoji: e });
|
|
95
|
+
return { ok: true };
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Native ephemeral when nativeEphemeral === true; otherwise DM-fallback semantics.
|
|
99
|
+
this.postEphemeral = async (_t, user, ir, opts) => {
|
|
100
|
+
this.ephemeralPosts.push({ user, ir, opts });
|
|
101
|
+
if (this.capabilities.supportsEphemeral) {
|
|
102
|
+
return {
|
|
103
|
+
ok: true,
|
|
104
|
+
usedFallback: false,
|
|
105
|
+
ref: { id: `eph-${++this.counter}` },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (opts.fallbackToDM) {
|
|
109
|
+
return {
|
|
110
|
+
ok: true,
|
|
111
|
+
usedFallback: true,
|
|
112
|
+
ref: { id: `dm-${++this.counter}` },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
if (fakeOpts.modals !== false) {
|
|
118
|
+
this.renderModal = (ir) => ir;
|
|
119
|
+
this.openModal = async (_t, triggerId, ir) => {
|
|
120
|
+
this.openedModals.push({ triggerId, ir });
|
|
121
|
+
return { ok: true };
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
conversationStore = {
|
|
126
|
+
async getOrCreate(conversationKey, _replyTarget, makeAgent) {
|
|
127
|
+
return { agent: makeAgent(conversationKey) };
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
posted = [];
|
|
131
|
+
updated = [];
|
|
132
|
+
interactionsSeen = [];
|
|
133
|
+
lastRunRenderer;
|
|
134
|
+
/** History returned by getMessages(); override in tests. */
|
|
135
|
+
messages = [];
|
|
136
|
+
/** User returned by lookupUser(); override in tests. */
|
|
137
|
+
user;
|
|
138
|
+
/** Optional persistence backend the adapter provides (test-only); exercises createBot's store resolution. */
|
|
139
|
+
stateStore;
|
|
140
|
+
sink;
|
|
141
|
+
counter = 0;
|
|
142
|
+
/** Expose the registered sink so tests can invoke onTurn() directly for overlap/lock tests. */
|
|
143
|
+
getSink() {
|
|
144
|
+
if (!this.sink)
|
|
145
|
+
throw new Error("FakeAdapter: sink not set — call bot.start() first");
|
|
146
|
+
return this.sink;
|
|
147
|
+
}
|
|
148
|
+
async start(sink) {
|
|
149
|
+
if (this.failStart)
|
|
150
|
+
throw new Error("fake-adapter: start failed");
|
|
151
|
+
this.sink = sink;
|
|
152
|
+
this.started = true;
|
|
153
|
+
}
|
|
154
|
+
async stop() {
|
|
155
|
+
if (this.failStop)
|
|
156
|
+
throw new Error("fake-adapter: stop failed");
|
|
157
|
+
}
|
|
158
|
+
render(ir) {
|
|
159
|
+
return ir;
|
|
160
|
+
}
|
|
161
|
+
async post(_target, ir) {
|
|
162
|
+
this.posted.push(ir);
|
|
163
|
+
return { id: `msg-${++this.counter}` };
|
|
164
|
+
}
|
|
165
|
+
async update(ref, ir) {
|
|
166
|
+
this.updated.push({ ref, ir });
|
|
167
|
+
}
|
|
168
|
+
async stream(_target, chunks) {
|
|
169
|
+
let s = "";
|
|
170
|
+
for await (const c of chunks)
|
|
171
|
+
s += c;
|
|
172
|
+
this.posted.push([{ type: "text", props: { value: s } }]);
|
|
173
|
+
return { id: `msg-${++this.counter}` };
|
|
174
|
+
}
|
|
175
|
+
async delete(_ref) { }
|
|
176
|
+
createRunRenderer(_target) {
|
|
177
|
+
const r = makeFakeRunRenderer();
|
|
178
|
+
this.lastRunRenderer = r;
|
|
179
|
+
return r;
|
|
180
|
+
}
|
|
181
|
+
decodeInteraction(raw) {
|
|
182
|
+
return raw;
|
|
183
|
+
}
|
|
184
|
+
async lookupUser(_q) {
|
|
185
|
+
return this.user;
|
|
186
|
+
}
|
|
187
|
+
async getMessages(_target) {
|
|
188
|
+
return this.messages;
|
|
189
|
+
}
|
|
190
|
+
/** Suggested-prompt calls recorded by the capability-gated method (when present). */
|
|
191
|
+
suggestedPromptsCalls = [];
|
|
192
|
+
setSuggestedPrompts;
|
|
193
|
+
/** Thread-title calls recorded by the capability-gated method (when present). */
|
|
194
|
+
threadTitleCalls = [];
|
|
195
|
+
setThreadTitle;
|
|
196
|
+
// --- reactions ---
|
|
197
|
+
reactionsAdded = [];
|
|
198
|
+
reactionsRemoved = [];
|
|
199
|
+
addReaction;
|
|
200
|
+
removeReaction;
|
|
201
|
+
// --- ephemeral ---
|
|
202
|
+
ephemeralPosts = [];
|
|
203
|
+
postEphemeral;
|
|
204
|
+
// --- modals ---
|
|
205
|
+
openedModals = [];
|
|
206
|
+
renderModal;
|
|
207
|
+
openModal;
|
|
208
|
+
// --- test helpers ---
|
|
209
|
+
emitTurn(partial) {
|
|
210
|
+
void this.sink?.onTurn({
|
|
211
|
+
conversationKey: "c",
|
|
212
|
+
replyTarget: {},
|
|
213
|
+
userText: "",
|
|
214
|
+
platform: "fake",
|
|
215
|
+
...partial,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
emitThreadStarted(partial) {
|
|
219
|
+
return this.sink?.onThreadStarted({
|
|
220
|
+
conversationKey: "c",
|
|
221
|
+
replyTarget: {},
|
|
222
|
+
platform: "fake",
|
|
223
|
+
...partial,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
emitInteraction(partial) {
|
|
227
|
+
const evt = {
|
|
228
|
+
id: "",
|
|
229
|
+
conversationKey: "c",
|
|
230
|
+
replyTarget: {},
|
|
231
|
+
...partial,
|
|
232
|
+
};
|
|
233
|
+
this.interactionsSeen.push(evt);
|
|
234
|
+
void this.sink?.onInteraction(evt);
|
|
235
|
+
}
|
|
236
|
+
emitCommand(partial) {
|
|
237
|
+
return this.sink?.onCommand({
|
|
238
|
+
text: "",
|
|
239
|
+
conversationKey: "c",
|
|
240
|
+
replyTarget: {},
|
|
241
|
+
platform: "fake",
|
|
242
|
+
...partial,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
emitReaction(partial) {
|
|
246
|
+
return this.sink?.onReaction({
|
|
247
|
+
added: true,
|
|
248
|
+
conversationKey: "c",
|
|
249
|
+
replyTarget: {},
|
|
250
|
+
messageId: "m1",
|
|
251
|
+
raw: {},
|
|
252
|
+
...partial,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
emitModalSubmit(partial) {
|
|
256
|
+
return this.sink?.onModalSubmit({
|
|
257
|
+
values: {},
|
|
258
|
+
platform: "fake",
|
|
259
|
+
raw: {},
|
|
260
|
+
...partial,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
emitModalClose(partial) {
|
|
264
|
+
return this.sink?.onModalClose({ platform: "fake", raw: {}, ...partial });
|
|
265
|
+
}
|
|
266
|
+
/** Commands handed to the adapter via `registerCommands`; asserts the capability hook fires. */
|
|
267
|
+
registeredCommands;
|
|
268
|
+
async registerCommands(commands) {
|
|
269
|
+
if (this.failRegisterCommands)
|
|
270
|
+
throw new Error("fake-adapter: registerCommands failed");
|
|
271
|
+
this.registeredCommands = commands;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AbstractAgent } from "@ag-ui/client";
|
|
2
|
+
import type { AgentSubscriber, RunAgentParameters, RunAgentResult } from "@ag-ui/client";
|
|
3
|
+
import type { RunAgentInput } from "@ag-ui/core";
|
|
4
|
+
/**
|
|
5
|
+
* One scripted `runAgent` invocation. Each step receives the subscriber the
|
|
6
|
+
* run-loop passed into `runAgent` and simulates the agent's behaviour by
|
|
7
|
+
* calling the subscriber's callbacks (e.g. `onToolCallEndEvent`,
|
|
8
|
+
* `onCustomEvent`, `onRunFinishedEvent`).
|
|
9
|
+
*/
|
|
10
|
+
export type FakeAgentScriptStep = (subscriber: AgentSubscriber) => void | Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* A scriptable fake `AbstractAgent` for exercising the run/tool/interrupt
|
|
13
|
+
* loop without a real backend. The constructor (or `setScript`) supplies an
|
|
14
|
+
* ordered list of steps; each `runAgent` call shifts and runs the next step.
|
|
15
|
+
*/
|
|
16
|
+
export declare class FakeAgent extends AbstractAgent {
|
|
17
|
+
private script;
|
|
18
|
+
/** Number of `runAgent` invocations seen — handy for loop-termination asserts. */
|
|
19
|
+
runAgentCalls: number;
|
|
20
|
+
/** Flipped to true by `abortRun()`. */
|
|
21
|
+
aborted: boolean;
|
|
22
|
+
constructor(script?: FakeAgentScriptStep[]);
|
|
23
|
+
setScript(script: FakeAgentScriptStep[]): void;
|
|
24
|
+
run(_input: RunAgentInput): ReturnType<AbstractAgent["run"]>;
|
|
25
|
+
runAgent(_parameters?: RunAgentParameters, subscriber?: AgentSubscriber): Promise<RunAgentResult>;
|
|
26
|
+
abortRun(): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=fake-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-agent.d.ts","sourceRoot":"","sources":["../../src/testing/fake-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,cAAc,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAChC,UAAU,EAAE,eAAe,KACxB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,aAAa;IAC1C,OAAO,CAAC,MAAM,CAAwB;IACtC,kFAAkF;IAClF,aAAa,SAAK;IAClB,uCAAuC;IACvC,OAAO,UAAS;gBAEJ,MAAM,GAAE,mBAAmB,EAAO;IAK9C,SAAS,CAAC,MAAM,EAAE,mBAAmB,EAAE,GAAG,IAAI;IAM9C,GAAG,CAAC,MAAM,EAAE,aAAa,GAAG,UAAU,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAI7C,QAAQ,CACrB,WAAW,CAAC,EAAE,kBAAkB,EAChC,UAAU,CAAC,EAAE,eAAe,GAC3B,OAAO,CAAC,cAAc,CAAC;IASjB,QAAQ,IAAI,IAAI;CAG1B"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AbstractAgent } from "@ag-ui/client";
|
|
2
|
+
/**
|
|
3
|
+
* A scriptable fake `AbstractAgent` for exercising the run/tool/interrupt
|
|
4
|
+
* loop without a real backend. The constructor (or `setScript`) supplies an
|
|
5
|
+
* ordered list of steps; each `runAgent` call shifts and runs the next step.
|
|
6
|
+
*/
|
|
7
|
+
export class FakeAgent extends AbstractAgent {
|
|
8
|
+
script;
|
|
9
|
+
/** Number of `runAgent` invocations seen — handy for loop-termination asserts. */
|
|
10
|
+
runAgentCalls = 0;
|
|
11
|
+
/** Flipped to true by `abortRun()`. */
|
|
12
|
+
aborted = false;
|
|
13
|
+
constructor(script = []) {
|
|
14
|
+
super({ agentId: "fake" });
|
|
15
|
+
this.script = [...script];
|
|
16
|
+
}
|
|
17
|
+
setScript(script) {
|
|
18
|
+
this.script = [...script];
|
|
19
|
+
}
|
|
20
|
+
// The base class declares `run` abstract. It's never invoked here because
|
|
21
|
+
// `runAgent` is overridden to drive the script directly (no rxjs stream).
|
|
22
|
+
run(_input) {
|
|
23
|
+
throw new Error("FakeAgent.run unused; runAgent is overridden");
|
|
24
|
+
}
|
|
25
|
+
async runAgent(_parameters, subscriber) {
|
|
26
|
+
this.runAgentCalls += 1;
|
|
27
|
+
const step = this.script.shift();
|
|
28
|
+
if (step && subscriber) {
|
|
29
|
+
await step(subscriber);
|
|
30
|
+
}
|
|
31
|
+
return { result: undefined, newMessages: [] };
|
|
32
|
+
}
|
|
33
|
+
abortRun() {
|
|
34
|
+
this.aborted = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { StateStore } from "../state/state-store.js";
|
|
2
|
+
/** Registers the full StateStore contract as Vitest tests against a backend. */
|
|
3
|
+
export declare function runStateStoreConformance(name: string, make: () => StateStore | Promise<StateStore>, teardown?: (s: StateStore) => Promise<void>): void;
|
|
4
|
+
//# sourceMappingURL=state-store-conformance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-store-conformance.d.ts","sourceRoot":"","sources":["../../src/testing/state-store-conformance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,gFAAgF;AAChF,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,EAC5C,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAC1C,IAAI,CAwHN"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
/** Registers the full StateStore contract as Vitest tests against a backend. */
|
|
3
|
+
export function runStateStoreConformance(name, make, teardown) {
|
|
4
|
+
describe(`StateStore conformance: ${name}`, () => {
|
|
5
|
+
let s;
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
s = await make();
|
|
8
|
+
});
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await teardown?.(s);
|
|
11
|
+
});
|
|
12
|
+
describe("kv", () => {
|
|
13
|
+
it("set/get/delete round-trips", async () => {
|
|
14
|
+
await s.kv.set("a", { n: 1 });
|
|
15
|
+
expect(await s.kv.get("a")).toEqual({ n: 1 });
|
|
16
|
+
await s.kv.delete("a");
|
|
17
|
+
expect(await s.kv.get("a")).toBeUndefined();
|
|
18
|
+
});
|
|
19
|
+
it("missing key is undefined", async () => expect(await s.kv.get("nope")).toBeUndefined());
|
|
20
|
+
it("expires after ttl", async () => {
|
|
21
|
+
await s.kv.set("t", 1, 30);
|
|
22
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
23
|
+
expect(await s.kv.get("t")).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("list", () => {
|
|
27
|
+
it("appends oldest-first and ranges", async () => {
|
|
28
|
+
await s.list.append("L", "a");
|
|
29
|
+
await s.list.append("L", "b");
|
|
30
|
+
await s.list.append("L", "c");
|
|
31
|
+
expect(await s.list.range("L")).toEqual(["a", "b", "c"]);
|
|
32
|
+
expect(await s.list.range("L", 0, 1)).toEqual(["a", "b"]);
|
|
33
|
+
});
|
|
34
|
+
it("caps with maxLen on append (drops oldest)", async () => {
|
|
35
|
+
for (const v of ["a", "b", "c", "d"])
|
|
36
|
+
await s.list.append("C", v, { maxLen: 2 });
|
|
37
|
+
expect(await s.list.range("C")).toEqual(["c", "d"]);
|
|
38
|
+
});
|
|
39
|
+
it("trim keeps newest maxLen", async () => {
|
|
40
|
+
for (const v of ["a", "b", "c"])
|
|
41
|
+
await s.list.append("T", v);
|
|
42
|
+
await s.list.trim("T", 2);
|
|
43
|
+
expect(await s.list.range("T")).toEqual(["b", "c"]);
|
|
44
|
+
});
|
|
45
|
+
it("delete clears", async () => {
|
|
46
|
+
await s.list.append("D", "x");
|
|
47
|
+
await s.list.delete("D");
|
|
48
|
+
expect(await s.list.range("D")).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
it("mixed ttl/non-ttl append keeps whole-list expiry", async () => {
|
|
51
|
+
await s.list.append("M", "a", { ttlMs: 1000 });
|
|
52
|
+
await s.list.append("M", "b"); // no ttlMs: must not clobber the existing expiry
|
|
53
|
+
await new Promise((r) => setTimeout(r, 40));
|
|
54
|
+
expect((await s.list.range("M")).length).toBe(2);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("lock", () => {
|
|
58
|
+
it("acquire blocks a second acquire until release", async () => {
|
|
59
|
+
const a = await s.lock.acquire("k");
|
|
60
|
+
expect(a).not.toBeNull();
|
|
61
|
+
expect(await s.lock.acquire("k")).toBeNull();
|
|
62
|
+
await s.lock.release("k", a.token);
|
|
63
|
+
const b = await s.lock.acquire("k");
|
|
64
|
+
expect(b).not.toBeNull();
|
|
65
|
+
});
|
|
66
|
+
it("release with a stale token does not free a re-acquired lock", async () => {
|
|
67
|
+
const a = await s.lock.acquire("k", { ttlMs: 20 });
|
|
68
|
+
await new Promise((r) => setTimeout(r, 40)); // a expires
|
|
69
|
+
const b = await s.lock.acquire("k");
|
|
70
|
+
expect(b).not.toBeNull();
|
|
71
|
+
await s.lock.release("k", a.token); // stale — must NOT release b
|
|
72
|
+
expect(await s.lock.acquire("k")).toBeNull();
|
|
73
|
+
await s.lock.release("k", b.token);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("dedup", () => {
|
|
77
|
+
it("first seen false, second true, within ttl", async () => {
|
|
78
|
+
expect(await s.dedup.seen("e1", 1000)).toBe(false);
|
|
79
|
+
expect(await s.dedup.seen("e1", 1000)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
it("forgets after ttl", async () => {
|
|
82
|
+
expect(await s.dedup.seen("e2", 30)).toBe(false);
|
|
83
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
84
|
+
expect(await s.dedup.seen("e2", 30)).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("queue", () => {
|
|
88
|
+
it("FIFO enqueue/dequeue/depth", async () => {
|
|
89
|
+
await s.queue.enqueue("q", 1);
|
|
90
|
+
await s.queue.enqueue("q", 2);
|
|
91
|
+
expect(await s.queue.depth("q")).toBe(2);
|
|
92
|
+
expect(await s.queue.dequeue("q")).toBe(1);
|
|
93
|
+
expect(await s.queue.dequeue("q")).toBe(2);
|
|
94
|
+
expect(await s.queue.dequeue("q")).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
it("maxSize + drop-oldest evicts the head", async () => {
|
|
97
|
+
for (const v of [1, 2, 3])
|
|
98
|
+
await s.queue.enqueue("q2", v, { maxSize: 2, onFull: "drop-oldest" });
|
|
99
|
+
expect(await s.queue.dequeue("q2")).toBe(2);
|
|
100
|
+
expect(await s.queue.dequeue("q2")).toBe(3);
|
|
101
|
+
});
|
|
102
|
+
it("maxSize + drop-newest rejects the incoming", async () => {
|
|
103
|
+
for (const v of [1, 2, 3])
|
|
104
|
+
await s.queue.enqueue("q3", v, { maxSize: 2, onFull: "drop-newest" });
|
|
105
|
+
expect(await s.queue.dequeue("q3")).toBe(1);
|
|
106
|
+
expect(await s.queue.dequeue("q3")).toBe(2);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
it("kv and lock keyspaces do not collide", async () => {
|
|
110
|
+
await s.kv.set("x", { v: 1 });
|
|
111
|
+
const a = await s.lock.acquire("x");
|
|
112
|
+
expect(a).not.toBeNull();
|
|
113
|
+
expect(await s.kv.get("x")).toEqual({ v: 1 });
|
|
114
|
+
await s.lock.release("x", a.token);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-ephemeral.test.d.ts","sourceRoot":"","sources":["../src/thread-ephemeral.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createBot } from "./create-bot.js";
|
|
3
|
+
import { FakeAdapter } from "./testing/fake-adapter.js";
|
|
4
|
+
async function runOnMessage(fake, fn) {
|
|
5
|
+
const bot = createBot({ adapters: [fake] });
|
|
6
|
+
bot.onMessage(fn);
|
|
7
|
+
await bot.start();
|
|
8
|
+
fake.emitTurn({ userText: "hi", user: { id: "U1" } });
|
|
9
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
10
|
+
}
|
|
11
|
+
describe("Thread.postEphemeral", () => {
|
|
12
|
+
it("posts natively when the surface supports it (usedFallback=false)", async () => {
|
|
13
|
+
const fake = new FakeAdapter({ nativeEphemeral: true });
|
|
14
|
+
let res;
|
|
15
|
+
await runOnMessage(fake, async ({ thread, message }) => {
|
|
16
|
+
res = await thread.postEphemeral(message.user, "psst", {
|
|
17
|
+
fallbackToDM: false,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
expect(res).toMatchObject({ ok: true, usedFallback: false });
|
|
21
|
+
expect(fake.ephemeralPosts).toHaveLength(1);
|
|
22
|
+
// Renderable was bound to IR before reaching the adapter.
|
|
23
|
+
expect(Array.isArray(fake.ephemeralPosts[0].ir)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it("DM-falls-back when native is unsupported and fallbackToDM=true (usedFallback=true)", async () => {
|
|
26
|
+
const fake = new FakeAdapter({ nativeEphemeral: false });
|
|
27
|
+
let res;
|
|
28
|
+
await runOnMessage(fake, async ({ thread, message }) => {
|
|
29
|
+
res = await thread.postEphemeral(message.user, "psst", {
|
|
30
|
+
fallbackToDM: true,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
expect(res).toMatchObject({ ok: true, usedFallback: true });
|
|
34
|
+
});
|
|
35
|
+
it("returns null when native unsupported and fallbackToDM=false", async () => {
|
|
36
|
+
const fake = new FakeAdapter({ nativeEphemeral: false });
|
|
37
|
+
let res = "sentinel";
|
|
38
|
+
await runOnMessage(fake, async ({ thread, message }) => {
|
|
39
|
+
res = await thread.postEphemeral(message.user, "psst", {
|
|
40
|
+
fallbackToDM: false,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
expect(res).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-reactions.test.d.ts","sourceRoot":"","sources":["../src/thread-reactions.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { emoji } from "@copilotkit/channels-ui";
|
|
3
|
+
import { createBot } from "./create-bot.js";
|
|
4
|
+
import { FakeAdapter } from "./testing/fake-adapter.js";
|
|
5
|
+
describe("Thread.react / unreact", () => {
|
|
6
|
+
it("delegates to the adapter when supported", async () => {
|
|
7
|
+
const fake = new FakeAdapter();
|
|
8
|
+
const bot = createBot({ adapters: [fake] });
|
|
9
|
+
const results = [];
|
|
10
|
+
bot.onMessage(async ({ thread, message }) => {
|
|
11
|
+
results.push(await thread.react(message.ref, emoji.thumbs_up));
|
|
12
|
+
results.push(await thread.unreact(message.ref, emoji.thumbs_up));
|
|
13
|
+
});
|
|
14
|
+
await bot.start();
|
|
15
|
+
fake.emitTurn({ userText: "hi" });
|
|
16
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
17
|
+
expect(results).toEqual([{ ok: true }, { ok: true }]);
|
|
18
|
+
expect(fake.reactionsAdded).toEqual([
|
|
19
|
+
{ ref: { id: "" }, emoji: "thumbs_up" },
|
|
20
|
+
]);
|
|
21
|
+
expect(fake.reactionsRemoved).toEqual([
|
|
22
|
+
{ ref: { id: "" }, emoji: "thumbs_up" },
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
it("returns { ok: false } without throwing when unsupported", async () => {
|
|
26
|
+
const fake = new FakeAdapter({ reactions: false });
|
|
27
|
+
const bot = createBot({ adapters: [fake] });
|
|
28
|
+
let res;
|
|
29
|
+
bot.onMessage(async ({ thread, message }) => {
|
|
30
|
+
res = await thread.react(message.ref, emoji.heart);
|
|
31
|
+
});
|
|
32
|
+
await bot.start();
|
|
33
|
+
fake.emitTurn({ userText: "hi" });
|
|
34
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
35
|
+
expect(res.ok).toBe(false);
|
|
36
|
+
expect(res.error).toMatch(/does not support reactions/);
|
|
37
|
+
});
|
|
38
|
+
});
|