@copilotkit/bot 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +190 -0
- package/dist/action-registry.d.ts +20 -0
- package/dist/action-registry.d.ts.map +1 -0
- package/dist/action-registry.js +109 -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 +45 -0
- package/dist/action-store.d.ts +19 -0
- package/dist/action-store.d.ts.map +1 -0
- package/dist/action-store.js +22 -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/commands.d.ts +68 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +30 -0
- package/dist/create-bot.d.ts +44 -0
- package/dist/create-bot.d.ts.map +1 -0
- package/dist/create-bot.js +166 -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 +261 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -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/platform-adapter.d.ts +125 -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 +32 -0
- package/dist/run-loop.d.ts +38 -0
- package/dist/run-loop.d.ts.map +1 -0
- package/dist/run-loop.js +100 -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 +80 -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/testing/fake-adapter.d.ts +44 -0
- package/dist/testing/fake-adapter.d.ts.map +1 -0
- package/dist/testing/fake-adapter.js +123 -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/thread.d.ts +60 -0
- package/dist/thread.d.ts.map +1 -0
- package/dist/thread.js +109 -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/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-adapter.test.d.ts","sourceRoot":"","sources":["../src/platform-adapter.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { FakeAdapter } from "./testing/fake-adapter.js";
|
|
3
|
+
describe("FakeAdapter", () => {
|
|
4
|
+
it("records posts and drives ingress", async () => {
|
|
5
|
+
const a = new FakeAdapter();
|
|
6
|
+
let got;
|
|
7
|
+
await a.start({
|
|
8
|
+
onTurn: (t) => {
|
|
9
|
+
got = t.userText;
|
|
10
|
+
},
|
|
11
|
+
onInteraction: () => { },
|
|
12
|
+
onCommand: () => { },
|
|
13
|
+
});
|
|
14
|
+
a.emitTurn({ userText: "hi" });
|
|
15
|
+
expect(got).toBe("hi");
|
|
16
|
+
await a.post({}, [{ type: "text", props: { value: "x" } }]);
|
|
17
|
+
expect(a.posted.length).toBe(1);
|
|
18
|
+
});
|
|
19
|
+
it("delivers interactions to the sink", async () => {
|
|
20
|
+
const a = new FakeAdapter();
|
|
21
|
+
let id;
|
|
22
|
+
await a.start({
|
|
23
|
+
onTurn: () => { },
|
|
24
|
+
onInteraction: (e) => {
|
|
25
|
+
id = e.id;
|
|
26
|
+
},
|
|
27
|
+
onCommand: () => { },
|
|
28
|
+
});
|
|
29
|
+
a.emitInteraction({ id: "ck:abc" });
|
|
30
|
+
expect(id).toBe("ck:abc");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AbstractAgent } from "@ag-ui/client";
|
|
2
|
+
import type { RunRenderer, CapturedToolCall, CapturedInterrupt } from "./platform-adapter.js";
|
|
3
|
+
import type { BotTool, BotToolContext, AgentToolDescriptor, ContextEntry } from "./tools.js";
|
|
4
|
+
export interface RunLoopArgs {
|
|
5
|
+
agent: AbstractAgent;
|
|
6
|
+
renderer: RunRenderer;
|
|
7
|
+
tools: Map<string, BotTool>;
|
|
8
|
+
toolDescriptors: AgentToolDescriptor[];
|
|
9
|
+
context: ContextEntry[];
|
|
10
|
+
/** ctx passed to tool.handler (thread + platform). */
|
|
11
|
+
makeToolCtx: (call: CapturedToolCall) => BotToolContext;
|
|
12
|
+
/** Invoke the registered onInterrupt handler (posts a picker); the loop then ends. */
|
|
13
|
+
handleInterrupt?: (interrupt: CapturedInterrupt) => Promise<void> | void;
|
|
14
|
+
isAborted?: () => boolean;
|
|
15
|
+
/** Hard cap on loop iterations. Default 6. */
|
|
16
|
+
maxIterations?: number;
|
|
17
|
+
/** When re-entering via thread.resume, the resume command to replay. */
|
|
18
|
+
initialResume?: {
|
|
19
|
+
resume: unknown;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Drive the agent, executing frontend-tool calls and re-invoking until the
|
|
24
|
+
* agent stops calling them (or we hit the iteration cap). On a captured
|
|
25
|
+
* LangGraph-style interrupt the loop posts the picker via `handleInterrupt`
|
|
26
|
+
* and returns immediately ("ack-first") — `thread.resume` re-enters later
|
|
27
|
+
* with `initialResume` set.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runAgentLoop(args: RunLoopArgs): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* If the agent's latest message isn't already the assistant message that
|
|
32
|
+
* issued these tool calls, append one. AG-UI's agent middleware *should*
|
|
33
|
+
* populate this from the streamed events, but we defensively reconcile here
|
|
34
|
+
* so the next `runAgent` sees a valid transcript even on backends that don't.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ensureAssistantToolCallMessage(agent: AbstractAgent, calls: ReadonlyArray<CapturedToolCall>): void;
|
|
37
|
+
export declare function pushToolResult(agent: AbstractAgent, toolCallId: string, content: string): void;
|
|
38
|
+
//# sourceMappingURL=run-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-loop.d.ts","sourceRoot":"","sources":["../src/run-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,mBAAmB,EACnB,YAAY,EACb,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,sDAAsD;IACtD,WAAW,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,cAAc,CAAC;IACxD,sFAAsF;IACtF,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzE,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;IAC1B,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,aAAa,CAAC,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;CACrC;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoEnE;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,aAAa,CAAC,gBAAgB,CAAC,GACrC,IAAI,CA4BN;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,aAAa,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,IAAI,CAQN"}
|
package/dist/run-loop.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { parseToolArgs, stringifyHandlerResult } from "./tools.js";
|
|
2
|
+
/**
|
|
3
|
+
* Drive the agent, executing frontend-tool calls and re-invoking until the
|
|
4
|
+
* agent stops calling them (or we hit the iteration cap). On a captured
|
|
5
|
+
* LangGraph-style interrupt the loop posts the picker via `handleInterrupt`
|
|
6
|
+
* and returns immediately ("ack-first") — `thread.resume` re-enters later
|
|
7
|
+
* with `initialResume` set.
|
|
8
|
+
*/
|
|
9
|
+
export async function runAgentLoop(args) {
|
|
10
|
+
const { agent, renderer, tools, toolDescriptors, context, makeToolCtx, handleInterrupt, isAborted, initialResume, } = args;
|
|
11
|
+
const maxIterations = args.maxIterations ?? 6;
|
|
12
|
+
const executed = new Set();
|
|
13
|
+
let resume = initialResume;
|
|
14
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
15
|
+
if (resume) {
|
|
16
|
+
await agent.runAgent({ forwardedProps: { command: resume } }, renderer.subscriber);
|
|
17
|
+
resume = undefined;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
await agent.runAgent({ tools: toolDescriptors, context: context }, renderer.subscriber);
|
|
21
|
+
}
|
|
22
|
+
if (isAborted?.())
|
|
23
|
+
return;
|
|
24
|
+
const pending = renderer.getPendingInterrupt();
|
|
25
|
+
if (pending) {
|
|
26
|
+
renderer.clearPendingInterrupt();
|
|
27
|
+
if (handleInterrupt)
|
|
28
|
+
await handleInterrupt(pending);
|
|
29
|
+
return; // ack-first: picker posted; thread.resume re-enters later
|
|
30
|
+
}
|
|
31
|
+
const calls = renderer
|
|
32
|
+
.getCapturedToolCalls()
|
|
33
|
+
.filter((c) => tools.has(c.toolCallName) && !executed.has(c.toolCallId));
|
|
34
|
+
if (calls.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
ensureAssistantToolCallMessage(agent, calls);
|
|
37
|
+
for (const call of calls) {
|
|
38
|
+
const tool = tools.get(call.toolCallName);
|
|
39
|
+
let result;
|
|
40
|
+
const parsed = await parseToolArgs(tool.parameters, call.toolCallArgs);
|
|
41
|
+
if (!parsed.ok) {
|
|
42
|
+
result = JSON.stringify({
|
|
43
|
+
error: `invalid arguments: ${parsed.error}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
try {
|
|
48
|
+
result = stringifyHandlerResult(await tool.handler(parsed.value, makeToolCtx(call)));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
result = JSON.stringify({ error: err.message });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
pushToolResult(agent, call.toolCallId, result);
|
|
55
|
+
executed.add(call.toolCallId);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* If the agent's latest message isn't already the assistant message that
|
|
61
|
+
* issued these tool calls, append one. AG-UI's agent middleware *should*
|
|
62
|
+
* populate this from the streamed events, but we defensively reconcile here
|
|
63
|
+
* so the next `runAgent` sees a valid transcript even on backends that don't.
|
|
64
|
+
*/
|
|
65
|
+
export function ensureAssistantToolCallMessage(agent, calls) {
|
|
66
|
+
const messages = agent.messages;
|
|
67
|
+
const last = messages[messages.length - 1];
|
|
68
|
+
const lastIsAssistantWithCalls = last !== undefined &&
|
|
69
|
+
last.role === "assistant" &&
|
|
70
|
+
Array.isArray(last.toolCalls);
|
|
71
|
+
if (lastIsAssistantWithCalls) {
|
|
72
|
+
const existing = (last.toolCalls ?? []).map((tc) => tc.id);
|
|
73
|
+
const allPresent = calls.every((c) => existing.includes(c.toolCallId));
|
|
74
|
+
if (allPresent)
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const assistant = {
|
|
78
|
+
id: `${calls[0].toolCallId}-assistant`,
|
|
79
|
+
role: "assistant",
|
|
80
|
+
content: "",
|
|
81
|
+
toolCalls: calls.map((c) => ({
|
|
82
|
+
id: c.toolCallId,
|
|
83
|
+
type: "function",
|
|
84
|
+
function: {
|
|
85
|
+
name: c.toolCallName,
|
|
86
|
+
arguments: JSON.stringify(c.toolCallArgs),
|
|
87
|
+
},
|
|
88
|
+
})),
|
|
89
|
+
};
|
|
90
|
+
agent.addMessage(assistant);
|
|
91
|
+
}
|
|
92
|
+
export function pushToolResult(agent, toolCallId, content) {
|
|
93
|
+
const toolMessage = {
|
|
94
|
+
id: `${toolCallId}-result`,
|
|
95
|
+
role: "tool",
|
|
96
|
+
toolCallId,
|
|
97
|
+
content,
|
|
98
|
+
};
|
|
99
|
+
agent.addMessage(toolMessage);
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-loop.test.d.ts","sourceRoot":"","sources":["../src/run-loop.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { runAgentLoop } from "./run-loop.js";
|
|
4
|
+
import { makeFakeRunRenderer } from "./testing/fake-adapter.js";
|
|
5
|
+
import { FakeAgent } from "./testing/fake-agent.js";
|
|
6
|
+
const toolDescriptors = [];
|
|
7
|
+
const context = [];
|
|
8
|
+
describe("runAgentLoop", () => {
|
|
9
|
+
it("executes a frontend tool call and pushes the result, then terminates", async () => {
|
|
10
|
+
const renderer = makeFakeRunRenderer();
|
|
11
|
+
const recorded = [];
|
|
12
|
+
const echo = {
|
|
13
|
+
name: "echo",
|
|
14
|
+
description: "echo back",
|
|
15
|
+
parameters: z.object({ msg: z.string() }),
|
|
16
|
+
handler: (args) => {
|
|
17
|
+
recorded.push(args);
|
|
18
|
+
return { ok: true };
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
const tools = new Map([["echo", echo]]);
|
|
22
|
+
// Step 1: agent emits an `echo` tool call, then finishes.
|
|
23
|
+
// Step 2: agent just finishes (no further tool calls) -> loop terminates.
|
|
24
|
+
const agent = new FakeAgent([
|
|
25
|
+
(sub) => {
|
|
26
|
+
sub.onToolCallEndEvent?.({
|
|
27
|
+
event: { toolCallId: "t1" },
|
|
28
|
+
toolCallName: "echo",
|
|
29
|
+
toolCallArgs: { msg: "hi" },
|
|
30
|
+
});
|
|
31
|
+
sub.onRunFinishedEvent?.({ event: {} });
|
|
32
|
+
},
|
|
33
|
+
(sub) => {
|
|
34
|
+
sub.onRunFinishedEvent?.({ event: {} });
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
await runAgentLoop({
|
|
38
|
+
agent,
|
|
39
|
+
renderer,
|
|
40
|
+
tools,
|
|
41
|
+
toolDescriptors,
|
|
42
|
+
context,
|
|
43
|
+
makeToolCtx: () => ({ thread: {}, platform: "fake" }),
|
|
44
|
+
});
|
|
45
|
+
expect(recorded).toEqual([{ msg: "hi" }]);
|
|
46
|
+
expect(agent.runAgentCalls).toBe(2);
|
|
47
|
+
const toolResult = agent.messages.find((m) => m.role === "tool");
|
|
48
|
+
expect(toolResult).toBeDefined();
|
|
49
|
+
expect(toolResult.toolCallId).toBe("t1");
|
|
50
|
+
});
|
|
51
|
+
it("posts the picker via handleInterrupt and returns without running tools", async () => {
|
|
52
|
+
const renderer = makeFakeRunRenderer();
|
|
53
|
+
const tools = new Map();
|
|
54
|
+
const handleInterrupt = vi.fn();
|
|
55
|
+
const agent = new FakeAgent([
|
|
56
|
+
(sub) => {
|
|
57
|
+
sub.onCustomEvent?.({
|
|
58
|
+
event: { name: "on_interrupt", value: { q: 1 } },
|
|
59
|
+
});
|
|
60
|
+
sub.onRunFinishedEvent?.({ event: {} });
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
await runAgentLoop({
|
|
64
|
+
agent,
|
|
65
|
+
renderer,
|
|
66
|
+
tools,
|
|
67
|
+
toolDescriptors,
|
|
68
|
+
context,
|
|
69
|
+
makeToolCtx: () => ({ thread: {}, platform: "fake" }),
|
|
70
|
+
handleInterrupt,
|
|
71
|
+
});
|
|
72
|
+
expect(handleInterrupt).toHaveBeenCalledTimes(1);
|
|
73
|
+
expect(handleInterrupt).toHaveBeenCalledWith({
|
|
74
|
+
eventName: "on_interrupt",
|
|
75
|
+
value: { q: 1 },
|
|
76
|
+
});
|
|
77
|
+
expect(agent.runAgentCalls).toBe(1);
|
|
78
|
+
expect(agent.messages.some((m) => m.role === "tool")).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-library-agnostic helpers for the Slack SDK.
|
|
3
|
+
*
|
|
4
|
+
* The public API accepts any [Standard Schema](https://standardschema.dev)
|
|
5
|
+
* validator — Zod 3.24+, Valibot v1+, ArkType v2+, and anything else that
|
|
6
|
+
* implements the `~standard` protocol. We deliberately keep `zod` out of
|
|
7
|
+
* the public type surface so consumers aren't locked to a single library
|
|
8
|
+
* or a single Zod major (the v3 ↔ v4 type split otherwise leaks into
|
|
9
|
+
* every signature that mentions `z.ZodType`).
|
|
10
|
+
*
|
|
11
|
+
* These wrap the canonical primitives from `@copilotkit/shared` (the same
|
|
12
|
+
* ones `@copilotkit/core` uses for its `FrontendTool` parameters) plus a
|
|
13
|
+
* couple of Slack-local conveniences.
|
|
14
|
+
*/
|
|
15
|
+
import { type StandardSchemaV1, type InferSchemaOutput } from "@copilotkit/shared";
|
|
16
|
+
export type { StandardSchemaV1, InferSchemaOutput };
|
|
17
|
+
/**
|
|
18
|
+
* A Standard Schema whose validated output is an object record.
|
|
19
|
+
*
|
|
20
|
+
* Tool args, component props, HITL props, and interrupt payloads are all
|
|
21
|
+
* objects (and `@copilotkit/core` constrains tool args to
|
|
22
|
+
* `Record<string, unknown>`). Bounding those generics by `ObjectSchema`
|
|
23
|
+
* — rather than coercing a non-object output to `Record` after the fact —
|
|
24
|
+
* makes a primitive/array schema (e.g. `z.string()`) a compile error at
|
|
25
|
+
* the `parameters`/`props` field instead of silently widening it.
|
|
26
|
+
*/
|
|
27
|
+
export type ObjectSchema = StandardSchemaV1<unknown, Record<string, unknown>>;
|
|
28
|
+
/**
|
|
29
|
+
* Convert any Standard Schema to a JSON Schema object suitable for an LLM
|
|
30
|
+
* tool/parameter descriptor. Prefers a native JSON Schema (Standard JSON
|
|
31
|
+
* Schema for Valibot/ArkType, `toJSONSchema()` for Zod v4); falls back to
|
|
32
|
+
* `zod-to-json-schema` only for Zod v3 schemas, which don't emit JSON
|
|
33
|
+
* Schema themselves. `$ref`s are inlined (`$refStrategy: "none"`) because
|
|
34
|
+
* most LLM tool-call APIs reject composite `$ref` schemas.
|
|
35
|
+
*/
|
|
36
|
+
export declare function toJsonSchema(schema: StandardSchemaV1): Record<string, unknown>;
|
|
37
|
+
/** Discriminated result of validating a value against a Standard Schema. */
|
|
38
|
+
export type SchemaParseResult<T> = {
|
|
39
|
+
ok: true;
|
|
40
|
+
value: T;
|
|
41
|
+
} | {
|
|
42
|
+
ok: false;
|
|
43
|
+
error: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Validate a value against a Standard Schema, awaiting async validators.
|
|
47
|
+
* Returns a discriminated result with a path-qualified, human-readable
|
|
48
|
+
* error string on failure — the caller (turn-runner) turns the error into
|
|
49
|
+
* a JSON tool result so the agent can recover from a bad tool call.
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateSchema<S extends StandardSchemaV1>(schema: S, value: unknown): Promise<SchemaParseResult<InferSchemaOutput<S>>>;
|
|
52
|
+
//# sourceMappingURL=standard-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standard-schema.d.ts","sourceRoot":"","sources":["../src/standard-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE9E;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,gBAAgB,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB;AAED,4EAA4E;AAC5E,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACtB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,CAAC,SAAS,gBAAgB,EAC7D,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAWlD"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-library-agnostic helpers for the Slack SDK.
|
|
3
|
+
*
|
|
4
|
+
* The public API accepts any [Standard Schema](https://standardschema.dev)
|
|
5
|
+
* validator — Zod 3.24+, Valibot v1+, ArkType v2+, and anything else that
|
|
6
|
+
* implements the `~standard` protocol. We deliberately keep `zod` out of
|
|
7
|
+
* the public type surface so consumers aren't locked to a single library
|
|
8
|
+
* or a single Zod major (the v3 ↔ v4 type split otherwise leaks into
|
|
9
|
+
* every signature that mentions `z.ZodType`).
|
|
10
|
+
*
|
|
11
|
+
* These wrap the canonical primitives from `@copilotkit/shared` (the same
|
|
12
|
+
* ones `@copilotkit/core` uses for its `FrontendTool` parameters) plus a
|
|
13
|
+
* couple of Slack-local conveniences.
|
|
14
|
+
*/
|
|
15
|
+
import { schemaToJsonSchema as sharedSchemaToJsonSchema, } from "@copilotkit/shared";
|
|
16
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
17
|
+
/**
|
|
18
|
+
* Convert any Standard Schema to a JSON Schema object suitable for an LLM
|
|
19
|
+
* tool/parameter descriptor. Prefers a native JSON Schema (Standard JSON
|
|
20
|
+
* Schema for Valibot/ArkType, `toJSONSchema()` for Zod v4); falls back to
|
|
21
|
+
* `zod-to-json-schema` only for Zod v3 schemas, which don't emit JSON
|
|
22
|
+
* Schema themselves. `$ref`s are inlined (`$refStrategy: "none"`) because
|
|
23
|
+
* most LLM tool-call APIs reject composite `$ref` schemas.
|
|
24
|
+
*/
|
|
25
|
+
export function toJsonSchema(schema) {
|
|
26
|
+
return sharedSchemaToJsonSchema(schema, {
|
|
27
|
+
// Adapt `zod-to-json-schema`'s `(schema: ZodType, ...)` signature to
|
|
28
|
+
// shared's `(schema: unknown, ...)` injection point. Only invoked for
|
|
29
|
+
// Zod v3 inputs that don't emit Standard JSON Schema themselves.
|
|
30
|
+
zodToJsonSchema: (s, options) => zodToJsonSchema(s, options),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate a value against a Standard Schema, awaiting async validators.
|
|
35
|
+
* Returns a discriminated result with a path-qualified, human-readable
|
|
36
|
+
* error string on failure — the caller (turn-runner) turns the error into
|
|
37
|
+
* a JSON tool result so the agent can recover from a bad tool call.
|
|
38
|
+
*/
|
|
39
|
+
export async function validateSchema(schema, value) {
|
|
40
|
+
const raw = schema["~standard"].validate(value);
|
|
41
|
+
// The Standard Schema spec permits `validate` to return any thenable,
|
|
42
|
+
// not strictly a native `Promise` (e.g. a Promise from another realm or
|
|
43
|
+
// a library's custom async result). Detect thenables, not `Promise`
|
|
44
|
+
// instances, so async validators are always awaited.
|
|
45
|
+
const result = isThenable(raw) ? await raw : raw;
|
|
46
|
+
if (result.issues) {
|
|
47
|
+
return { ok: false, error: formatIssues(result.issues) };
|
|
48
|
+
}
|
|
49
|
+
return { ok: true, value: result.value };
|
|
50
|
+
}
|
|
51
|
+
function isThenable(value) {
|
|
52
|
+
return (value != null && typeof value.then === "function");
|
|
53
|
+
}
|
|
54
|
+
/** Format Standard Schema issues as `path: message; path: message`. */
|
|
55
|
+
function formatIssues(issues) {
|
|
56
|
+
return issues
|
|
57
|
+
.map((issue) => {
|
|
58
|
+
const path = (issue.path ?? [])
|
|
59
|
+
.map((segment) => typeof segment === "object" && segment !== null
|
|
60
|
+
? String(segment.key)
|
|
61
|
+
: String(segment))
|
|
62
|
+
.join(".");
|
|
63
|
+
return `${path || "(root)"}: ${issue.message}`;
|
|
64
|
+
})
|
|
65
|
+
.join("; ");
|
|
66
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { BotNode, MessageRef, PlatformUser, ThreadMessage } from "@copilotkit/bot-ui";
|
|
2
|
+
import type { PlatformAdapter, SurfaceCapabilities, IngressSink, IncomingTurn, InteractionEvent, IncomingCommand, RunRenderer, ReplyTarget, NativePayload, UserQuery, ConversationStore } from "../platform-adapter.js";
|
|
3
|
+
import type { CommandSpec } from "../commands.js";
|
|
4
|
+
/** A RunRenderer whose subscriber captures tool-call-end and custom (interrupt) events — used by run-loop tests. */
|
|
5
|
+
export declare function makeFakeRunRenderer(): RunRenderer;
|
|
6
|
+
export declare class FakeAdapter implements PlatformAdapter {
|
|
7
|
+
readonly platform = "fake";
|
|
8
|
+
readonly capabilities: SurfaceCapabilities;
|
|
9
|
+
readonly ackDeadlineMs = 3000;
|
|
10
|
+
readonly conversationStore: ConversationStore;
|
|
11
|
+
posted: BotNode[][];
|
|
12
|
+
updated: {
|
|
13
|
+
ref: MessageRef;
|
|
14
|
+
ir: BotNode[];
|
|
15
|
+
}[];
|
|
16
|
+
interactionsSeen: InteractionEvent[];
|
|
17
|
+
lastRunRenderer?: RunRenderer;
|
|
18
|
+
/** History returned by getMessages(); override in tests. */
|
|
19
|
+
messages: ThreadMessage[];
|
|
20
|
+
/** User returned by lookupUser(); override in tests. */
|
|
21
|
+
user?: PlatformUser;
|
|
22
|
+
private sink?;
|
|
23
|
+
private counter;
|
|
24
|
+
start(sink: IngressSink): Promise<void>;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
render(ir: BotNode[]): NativePayload;
|
|
27
|
+
post(_target: ReplyTarget, ir: BotNode[]): Promise<MessageRef>;
|
|
28
|
+
update(ref: MessageRef, ir: BotNode[]): Promise<void>;
|
|
29
|
+
stream(_target: ReplyTarget, chunks: AsyncIterable<string>): Promise<MessageRef>;
|
|
30
|
+
delete(_ref: MessageRef): Promise<void>;
|
|
31
|
+
createRunRenderer(_target: ReplyTarget): RunRenderer;
|
|
32
|
+
decodeInteraction(raw: unknown): InteractionEvent | undefined;
|
|
33
|
+
lookupUser(_q: UserQuery): Promise<PlatformUser | undefined>;
|
|
34
|
+
getMessages(_target: ReplyTarget): Promise<ThreadMessage[]>;
|
|
35
|
+
emitTurn(partial: Partial<IncomingTurn>): void;
|
|
36
|
+
emitInteraction(partial: Partial<InteractionEvent>): void;
|
|
37
|
+
emitCommand(partial: Partial<IncomingCommand> & {
|
|
38
|
+
command: string;
|
|
39
|
+
}): Promise<void> | void;
|
|
40
|
+
/** Commands handed to the adapter via `registerCommands`; asserts the capability hook fires. */
|
|
41
|
+
registeredCommands?: readonly CommandSpec[];
|
|
42
|
+
registerCommands(commands: readonly CommandSpec[]): void;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=fake-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-adapter.d.ts","sourceRoot":"","sources":["../../src/testing/fake-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,YAAY,EACZ,aAAa,EACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,WAAW,EAGX,WAAW,EACX,aAAa,EACb,SAAS,EACT,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,oHAAoH;AACpH,wBAAgB,mBAAmB,IAAI,WAAW,CAyBjD;AAED,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,QAAQ,UAAU;IAC3B,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAKxC;IACF,QAAQ,CAAC,aAAa,QAAQ;IAC9B,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAI3C;IAEF,MAAM,EAAE,OAAO,EAAE,EAAE,CAAM;IACzB,OAAO,EAAE;QAAE,GAAG,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,OAAO,EAAE,CAAA;KAAE,EAAE,CAAM;IACnD,gBAAgB,EAAE,gBAAgB,EAAE,CAAM;IAC1C,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,EAAE,aAAa,EAAE,CAAM;IAC/B,wDAAwD;IACxD,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,OAAO,CAAC,IAAI,CAAC,CAAc;IAC3B,OAAO,CAAC,OAAO,CAAK;IAEd,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAGvC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAE3B,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,aAAa;IAG9B,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAI9D,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAGrD,MAAM,CACV,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAC5B,OAAO,CAAC,UAAU,CAAC;IAMhB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAE7C,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW;IAKpD,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS;IAGvD,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAG5D,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAKjE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAS9C,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI;IAUzD,WAAW,CACT,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GACtD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAUvB,gGAAgG;IAChG,kBAAkB,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC5C,gBAAgB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI;CAGzD"}
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
return {
|
|
20
|
+
subscriber,
|
|
21
|
+
async markInterrupted() { },
|
|
22
|
+
getCapturedToolCalls: () => toolCalls,
|
|
23
|
+
getPendingInterrupt: () => pending,
|
|
24
|
+
clearPendingInterrupt: () => {
|
|
25
|
+
pending = undefined;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export class FakeAdapter {
|
|
30
|
+
platform = "fake";
|
|
31
|
+
capabilities = {
|
|
32
|
+
supportsModals: false,
|
|
33
|
+
supportsTyping: false,
|
|
34
|
+
supportsReactions: false,
|
|
35
|
+
supportsStreaming: true,
|
|
36
|
+
};
|
|
37
|
+
ackDeadlineMs = 3000;
|
|
38
|
+
conversationStore = {
|
|
39
|
+
async getOrCreate(conversationKey, _replyTarget, makeAgent) {
|
|
40
|
+
return { agent: makeAgent(conversationKey) };
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
posted = [];
|
|
44
|
+
updated = [];
|
|
45
|
+
interactionsSeen = [];
|
|
46
|
+
lastRunRenderer;
|
|
47
|
+
/** History returned by getMessages(); override in tests. */
|
|
48
|
+
messages = [];
|
|
49
|
+
/** User returned by lookupUser(); override in tests. */
|
|
50
|
+
user;
|
|
51
|
+
sink;
|
|
52
|
+
counter = 0;
|
|
53
|
+
async start(sink) {
|
|
54
|
+
this.sink = sink;
|
|
55
|
+
}
|
|
56
|
+
async stop() { }
|
|
57
|
+
render(ir) {
|
|
58
|
+
return ir;
|
|
59
|
+
}
|
|
60
|
+
async post(_target, ir) {
|
|
61
|
+
this.posted.push(ir);
|
|
62
|
+
return { id: `msg-${++this.counter}` };
|
|
63
|
+
}
|
|
64
|
+
async update(ref, ir) {
|
|
65
|
+
this.updated.push({ ref, ir });
|
|
66
|
+
}
|
|
67
|
+
async stream(_target, chunks) {
|
|
68
|
+
let s = "";
|
|
69
|
+
for await (const c of chunks)
|
|
70
|
+
s += c;
|
|
71
|
+
this.posted.push([{ type: "text", props: { value: s } }]);
|
|
72
|
+
return { id: `msg-${++this.counter}` };
|
|
73
|
+
}
|
|
74
|
+
async delete(_ref) { }
|
|
75
|
+
createRunRenderer(_target) {
|
|
76
|
+
const r = makeFakeRunRenderer();
|
|
77
|
+
this.lastRunRenderer = r;
|
|
78
|
+
return r;
|
|
79
|
+
}
|
|
80
|
+
decodeInteraction(raw) {
|
|
81
|
+
return raw;
|
|
82
|
+
}
|
|
83
|
+
async lookupUser(_q) {
|
|
84
|
+
return this.user;
|
|
85
|
+
}
|
|
86
|
+
async getMessages(_target) {
|
|
87
|
+
return this.messages;
|
|
88
|
+
}
|
|
89
|
+
// --- test helpers ---
|
|
90
|
+
emitTurn(partial) {
|
|
91
|
+
void this.sink?.onTurn({
|
|
92
|
+
conversationKey: "c",
|
|
93
|
+
replyTarget: {},
|
|
94
|
+
userText: "",
|
|
95
|
+
platform: "fake",
|
|
96
|
+
...partial,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
emitInteraction(partial) {
|
|
100
|
+
const evt = {
|
|
101
|
+
id: "",
|
|
102
|
+
conversationKey: "c",
|
|
103
|
+
replyTarget: {},
|
|
104
|
+
...partial,
|
|
105
|
+
};
|
|
106
|
+
this.interactionsSeen.push(evt);
|
|
107
|
+
void this.sink?.onInteraction(evt);
|
|
108
|
+
}
|
|
109
|
+
emitCommand(partial) {
|
|
110
|
+
return this.sink?.onCommand({
|
|
111
|
+
text: "",
|
|
112
|
+
conversationKey: "c",
|
|
113
|
+
replyTarget: {},
|
|
114
|
+
platform: "fake",
|
|
115
|
+
...partial,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** Commands handed to the adapter via `registerCommands`; asserts the capability hook fires. */
|
|
119
|
+
registeredCommands;
|
|
120
|
+
registerCommands(commands) {
|
|
121
|
+
this.registeredCommands = commands;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -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
|
+
}
|