@copilotkit/bot 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/dist/action-registry.d.ts +20 -0
  4. package/dist/action-registry.d.ts.map +1 -0
  5. package/dist/action-registry.js +109 -0
  6. package/dist/action-registry.test.d.ts +2 -0
  7. package/dist/action-registry.test.d.ts.map +1 -0
  8. package/dist/action-registry.test.js +45 -0
  9. package/dist/action-store.d.ts +19 -0
  10. package/dist/action-store.d.ts.map +1 -0
  11. package/dist/action-store.js +22 -0
  12. package/dist/action-store.test.d.ts +2 -0
  13. package/dist/action-store.test.d.ts.map +1 -0
  14. package/dist/action-store.test.js +27 -0
  15. package/dist/commands.d.ts +68 -0
  16. package/dist/commands.d.ts.map +1 -0
  17. package/dist/commands.js +30 -0
  18. package/dist/create-bot.d.ts +44 -0
  19. package/dist/create-bot.d.ts.map +1 -0
  20. package/dist/create-bot.js +166 -0
  21. package/dist/create-bot.test.d.ts +2 -0
  22. package/dist/create-bot.test.d.ts.map +1 -0
  23. package/dist/create-bot.test.js +261 -0
  24. package/dist/index.d.ts +17 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +19 -0
  27. package/dist/mint-id.d.ts +3 -0
  28. package/dist/mint-id.d.ts.map +1 -0
  29. package/dist/mint-id.js +20 -0
  30. package/dist/mint-id.test.d.ts +2 -0
  31. package/dist/mint-id.test.d.ts.map +1 -0
  32. package/dist/mint-id.test.js +16 -0
  33. package/dist/platform-adapter.d.ts +125 -0
  34. package/dist/platform-adapter.d.ts.map +1 -0
  35. package/dist/platform-adapter.js +1 -0
  36. package/dist/platform-adapter.test.d.ts +2 -0
  37. package/dist/platform-adapter.test.d.ts.map +1 -0
  38. package/dist/platform-adapter.test.js +32 -0
  39. package/dist/run-loop.d.ts +38 -0
  40. package/dist/run-loop.d.ts.map +1 -0
  41. package/dist/run-loop.js +100 -0
  42. package/dist/run-loop.test.d.ts +2 -0
  43. package/dist/run-loop.test.d.ts.map +1 -0
  44. package/dist/run-loop.test.js +80 -0
  45. package/dist/standard-schema.d.ts +52 -0
  46. package/dist/standard-schema.d.ts.map +1 -0
  47. package/dist/standard-schema.js +66 -0
  48. package/dist/testing/fake-adapter.d.ts +44 -0
  49. package/dist/testing/fake-adapter.d.ts.map +1 -0
  50. package/dist/testing/fake-adapter.js +123 -0
  51. package/dist/testing/fake-agent.d.ts +28 -0
  52. package/dist/testing/fake-agent.d.ts.map +1 -0
  53. package/dist/testing/fake-agent.js +36 -0
  54. package/dist/thread.d.ts +60 -0
  55. package/dist/thread.d.ts.map +1 -0
  56. package/dist/thread.js +109 -0
  57. package/dist/tools.d.ts +67 -0
  58. package/dist/tools.d.ts.map +1 -0
  59. package/dist/tools.js +38 -0
  60. package/dist/tools.test.d.ts +2 -0
  61. package/dist/tools.test.d.ts.map +1 -0
  62. package/dist/tools.test.js +31 -0
  63. package/package.json +60 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=platform-adapter.test.d.ts.map
@@ -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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=run-loop.test.d.ts.map
@@ -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
+ }