@botpress/evals 1.19.0-beta.9 → 2.0.0-alpha.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 (85) hide show
  1. package/dist/client.d.ts +21 -9
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +103 -36
  4. package/dist/client.js.map +4 -4
  5. package/dist/definition.d.ts +9 -2
  6. package/dist/definition.d.ts.map +1 -1
  7. package/dist/definition.js.map +2 -2
  8. package/dist/graders/index.js +21 -74
  9. package/dist/graders/index.js.map +2 -2
  10. package/dist/graders/llm.d.ts +12 -12
  11. package/dist/graders/llm.d.ts.map +1 -1
  12. package/dist/graders/llm.js +30 -75
  13. package/dist/graders/llm.js.map +2 -2
  14. package/dist/graders/response.js +16 -65
  15. package/dist/graders/response.js.map +2 -2
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +10 -0
  19. package/dist/index.js.map +3 -3
  20. package/dist/loader.js.map +2 -2
  21. package/dist/manifest.d.ts +13 -0
  22. package/dist/manifest.d.ts.map +1 -0
  23. package/dist/runner.d.ts +5 -1
  24. package/dist/runner.d.ts.map +1 -1
  25. package/dist/runner.js +316 -183
  26. package/dist/runner.js.map +4 -4
  27. package/dist/spans/index.d.ts +5 -0
  28. package/dist/spans/index.d.ts.map +1 -0
  29. package/dist/spans/index.js +725 -0
  30. package/dist/spans/index.js.map +7 -0
  31. package/dist/spans/local-span-source.d.ts +19 -0
  32. package/dist/spans/local-span-source.d.ts.map +1 -0
  33. package/dist/spans/local-span-source.js +543 -0
  34. package/dist/spans/local-span-source.js.map +7 -0
  35. package/dist/spans/span-source.d.ts +40 -0
  36. package/dist/spans/span-source.d.ts.map +1 -0
  37. package/dist/spans/span-source.js +1 -0
  38. package/dist/{sse-collector.d.ts → spans/sse-collector.d.ts} +44 -5
  39. package/dist/spans/sse-collector.d.ts.map +1 -0
  40. package/dist/{sse-collector.js → spans/sse-collector.js} +97 -20
  41. package/dist/spans/sse-collector.js.map +7 -0
  42. package/dist/spans/trace.d.ts.map +1 -0
  43. package/dist/spans/trace.js.map +7 -0
  44. package/dist/spans/vortex-span-source.d.ts +42 -0
  45. package/dist/spans/vortex-span-source.d.ts.map +1 -0
  46. package/dist/spans/vortex-span-source.js +184 -0
  47. package/dist/spans/vortex-span-source.js.map +7 -0
  48. package/dist/stores/eval-definition-loader.d.ts +4 -0
  49. package/dist/stores/eval-definition-loader.d.ts.map +1 -0
  50. package/dist/stores/eval-definition-loader.js +142 -0
  51. package/dist/stores/eval-definition-loader.js.map +7 -0
  52. package/dist/stores/eval-store.d.ts +61 -0
  53. package/dist/stores/eval-store.d.ts.map +1 -0
  54. package/dist/stores/eval-store.js +1 -0
  55. package/dist/stores/eval-store.js.map +7 -0
  56. package/dist/stores/index.d.ts +8 -0
  57. package/dist/stores/index.d.ts.map +1 -0
  58. package/dist/stores/index.js +1156 -0
  59. package/dist/stores/index.js.map +7 -0
  60. package/dist/stores/local-eval-store.d.ts +66 -0
  61. package/dist/stores/local-eval-store.d.ts.map +1 -0
  62. package/dist/stores/local-eval-store.js +595 -0
  63. package/dist/stores/local-eval-store.js.map +7 -0
  64. package/dist/stores/vortex-eval-store-entry.d.ts +6 -0
  65. package/dist/stores/vortex-eval-store-entry.d.ts.map +1 -0
  66. package/dist/stores/vortex-eval-store-entry.js +702 -0
  67. package/dist/stores/vortex-eval-store-entry.js.map +7 -0
  68. package/dist/stores/vortex-eval-store.d.ts +78 -0
  69. package/dist/stores/vortex-eval-store.d.ts.map +1 -0
  70. package/dist/stores/vortex-eval-store.js +562 -0
  71. package/dist/stores/vortex-eval-store.js.map +7 -0
  72. package/dist/transformer.d.ts +2 -2
  73. package/dist/transformer.d.ts.map +1 -1
  74. package/dist/transformer.js +1 -1
  75. package/dist/transformer.js.map +2 -2
  76. package/dist/types.d.ts +38 -33
  77. package/dist/types.d.ts.map +1 -1
  78. package/dist/types.js.map +2 -2
  79. package/package.json +66 -6
  80. package/dist/sse-collector.d.ts.map +0 -1
  81. package/dist/sse-collector.js.map +0 -7
  82. package/dist/trace.d.ts.map +0 -1
  83. /package/dist/{trace.js.map → spans/span-source.js.map} +0 -0
  84. /package/dist/{trace.d.ts → spans/trace.d.ts} +0 -0
  85. /package/dist/{trace.js → spans/trace.js} +0 -0
package/dist/client.d.ts CHANGED
@@ -5,31 +5,36 @@
5
5
  * This is a send-only client. Observation (bot responses, tool calls, state)
6
6
  * is handled by the SSECollector via trace spans — not via WebSocket listeners.
7
7
  */
8
+ import { Client as BpClient } from '@botpress/client';
8
9
  import type { ChatClient, EvalLogger } from './types';
9
- export declare function setDefaultChatClient(chatClient: ChatClient | null): void;
10
10
  /**
11
11
  * A send-only chat session that maintains a single client connection across turns.
12
12
  * Messages and events are fire-and-forget; the SSECollector observes results via traces.
13
13
  */
14
14
  export declare class ChatSession {
15
- private webhookId;
16
- private chatClient?;
15
+ private bpClient;
16
+ private botId;
17
+ private _chatWebhookId?;
18
+ private _chatBaseUrl?;
19
+ private _chatClient?;
17
20
  private client;
18
21
  private conversationId;
19
- constructor(webhookId: string, chatClient?: ChatClient);
22
+ constructor(bpClient: BpClient, botId: string, _chatWebhookId?: string, _chatBaseUrl?: string, _chatClient?: ChatClient);
20
23
  connect(): Promise<void>;
21
24
  /** Invariant guard — calling any session method before connect() is a runner bug. */
22
25
  private assertConnected;
23
26
  get userId(): string;
24
27
  ensureConversation(): Promise<string>;
25
28
  /**
26
- * Send a user message. Fire-and-forget — observation is via SSECollector.
29
+ * Open a fresh conversation under the same user (the user is fixed at
30
+ * connect()) and make it active. Returns the new conversation id.
27
31
  */
28
- sendMessage(message: string): Promise<void>;
32
+ newConversation(): Promise<string>;
29
33
  /**
30
- * Send an event. Fire-and-forget — observation is via SSECollector.
34
+ * Send a user message. Fire-and-forget — observation is via SSECollector.
31
35
  */
32
- sendEvent(type: string, payload: Record<string, unknown>): Promise<void>;
36
+ sendMessage(message: string): Promise<void>;
37
+ sendEvent(payload: Record<string, unknown>): Promise<void>;
33
38
  }
34
39
  /**
35
40
  * Discover the chat integration's webhookId from a bot.
@@ -37,7 +42,14 @@ export declare class ChatSession {
37
42
  * Evals talk to the bot through the `chat` integration (via @botpress/chat).
38
43
  * If the bot doesn't have that integration installed, evals can't run.
39
44
  */
40
- export declare function discoverWebhookId(botId: string, token: string, apiUrl: string): Promise<string>;
45
+ export declare function discoverWebhookId(client: BpClient, botId: string): Promise<string>;
46
+ /**
47
+ * Verify the dev server's trace stream can be read before eval turns start.
48
+ *
49
+ * This only checks that `/api/traces/stream` is reachable and emits its initial
50
+ * snapshot. It does not prove the private runtime span-ingest path is healthy.
51
+ */
52
+ export declare function assertTraceStreamReadable(devServerUrl: string, headers?: Record<string, string>, timeoutMs?: number): Promise<void>;
41
53
  /**
42
54
  * Verify the bot has at least one Conversation bound to `chat.channel`.
43
55
  *
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAwB,UAAU,EAAE,MAAM,SAAS,CAAA;AAM3E,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAExE;AAiBD;;;GAGG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,UAAU,CAAC;IALrB,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,cAAc,CAAsB;gBAGlC,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,UAAU;IAG3B,OAAO;IAKb,qFAAqF;IACrF,OAAO,CAAC,eAAe;IAUvB,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEK,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAY3C;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAK/E;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBrG;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACpC,MAAM,GAAE,UAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAMrD;;;GAGG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,cAAc,CAAC;IACvB,OAAO,CAAC,YAAY,CAAC;IACrB,OAAO,CAAC,WAAW,CAAC;IARtB,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,cAAc,CAAsB;gBAGlC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,cAAc,CAAC,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,UAAU;IAG5B,OAAO;IAmBb,qFAAqF;IACrF,OAAO,CAAC,eAAe;IAUvB,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEK,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAY3C;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAOxC;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3C,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAKjE;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBxF;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACpC,SAAS,SAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CA+Df;AAoBD;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACpC,MAAM,GAAE,UAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
package/dist/client.js CHANGED
@@ -1,7 +1,3 @@
1
- // src/client.ts
2
- import { Client as BpClient } from "@botpress/client";
3
- import { createRequire } from "module";
4
-
5
1
  // src/types.ts
6
2
  var defaultLogger = console;
7
3
 
@@ -49,33 +45,27 @@ var EvalRunnerError = class extends AdkError {
49
45
  };
50
46
 
51
47
  // src/client.ts
52
- var defaultChatClient = null;
53
- function setDefaultChatClient(chatClient) {
54
- defaultChatClient = chatClient;
55
- }
56
- function getDefaultChatClient() {
57
- if (defaultChatClient) return defaultChatClient;
58
- const require2 = createRequire(import.meta.url);
59
- const mod = require2("@botpress/chat");
60
- const ChatClient = mod.default?.Client ?? mod.Client;
61
- if (!ChatClient) {
62
- throw new EvalRunnerError({
63
- code: "CHAT_CLIENT_MISSING",
64
- message: "@botpress/chat did not expose a Client export. Pass EvalRunnerConfig.chatClient explicitly."
65
- });
66
- }
67
- return ChatClient;
68
- }
48
+ var TRACE_STREAM_PREFLIGHT_TIMEOUT_MS = 1e4;
69
49
  var ChatSession = class {
70
- constructor(webhookId, chatClient) {
71
- this.webhookId = webhookId;
72
- this.chatClient = chatClient;
50
+ constructor(bpClient, botId, _chatWebhookId, _chatBaseUrl, _chatClient) {
51
+ this.bpClient = bpClient;
52
+ this.botId = botId;
53
+ this._chatWebhookId = _chatWebhookId;
54
+ this._chatBaseUrl = _chatBaseUrl;
55
+ this._chatClient = _chatClient;
73
56
  }
74
57
  client = null;
75
58
  conversationId = null;
76
59
  async connect() {
77
- const ChatClient = this.chatClient ?? getDefaultChatClient();
78
- this.client = await ChatClient.connect({ webhookId: this.webhookId });
60
+ const webhookId = this._chatWebhookId ?? await discoverWebhookId(this.bpClient, this.botId);
61
+ const ChatClientCtor = this._chatClient ?? (await import(
62
+ /* webpackIgnore: true */
63
+ "@botpress/chat"
64
+ )).Client;
65
+ this.client = await ChatClientCtor.connect({
66
+ webhookId,
67
+ ...this._chatBaseUrl ? { baseApiUrl: this._chatBaseUrl } : {}
68
+ });
79
69
  }
80
70
  /** Invariant guard — calling any session method before connect() is a runner bug. */
81
71
  assertConnected() {
@@ -99,6 +89,16 @@ var ChatSession = class {
99
89
  }
100
90
  return this.conversationId;
101
91
  }
92
+ /**
93
+ * Open a fresh conversation under the same user (the user is fixed at
94
+ * connect()) and make it active. Returns the new conversation id.
95
+ */
96
+ async newConversation() {
97
+ const client = this.assertConnected();
98
+ const conv = await client.createConversation({});
99
+ this.conversationId = conv.conversation.id;
100
+ return conv.conversation.id;
101
+ }
102
102
  /**
103
103
  * Send a user message. Fire-and-forget — observation is via SSECollector.
104
104
  */
@@ -107,17 +107,13 @@ var ChatSession = class {
107
107
  const conversationId = await this.ensureConversation();
108
108
  await client.createMessage({ conversationId, payload: { type: "text", text: message } });
109
109
  }
110
- /**
111
- * Send an event. Fire-and-forget — observation is via SSECollector.
112
- */
113
- async sendEvent(type, payload) {
110
+ async sendEvent(payload) {
114
111
  const client = this.assertConnected();
115
112
  const conversationId = await this.ensureConversation();
116
- await client.createEvent({ type, payload, conversationId });
113
+ await client.createEvent({ payload, conversationId });
117
114
  }
118
115
  };
119
- async function discoverWebhookId(botId, token, apiUrl) {
120
- const client = new BpClient({ token, botId, apiUrl });
116
+ async function discoverWebhookId(client, botId) {
121
117
  const { bot } = await client.getBot({ id: botId });
122
118
  const integrations = bot.integrations || {};
123
119
  const chat = Object.values(integrations).find((int) => int.name === "chat");
@@ -129,7 +125,7 @@ async function discoverWebhookId(botId, token, apiUrl) {
129
125
  "The `chat` integration is not installed on this bot \u2014 evals require it.",
130
126
  "",
131
127
  "To fix:",
132
- " 1. Install it: adk add chat",
128
+ " 1. Install it: adk integrations add chat",
133
129
  " 2. Redeploy: adk dev (or: adk deploy)"
134
130
  ].join("\n"),
135
131
  expected: true
@@ -137,6 +133,77 @@ async function discoverWebhookId(botId, token, apiUrl) {
137
133
  }
138
134
  return webhookId;
139
135
  }
136
+ async function assertTraceStreamReadable(devServerUrl, headers = {}, timeoutMs = TRACE_STREAM_PREFLIGHT_TIMEOUT_MS) {
137
+ const url = `${devServerUrl.replace(/\/$/, "")}/api/traces/stream?count=1`;
138
+ const abortController = new AbortController();
139
+ let timedOut = false;
140
+ let reader = null;
141
+ const timer = setTimeout(() => {
142
+ timedOut = true;
143
+ abortController.abort();
144
+ }, timeoutMs);
145
+ try {
146
+ const res = await fetch(url, {
147
+ headers: { ...headers, Accept: "text/event-stream" },
148
+ signal: abortController.signal
149
+ });
150
+ if (!res.ok) {
151
+ throw traceStreamPreflightError(`Trace stream pre-flight failed: ${res.status} ${res.statusText}`);
152
+ }
153
+ if (!res.body) {
154
+ throw traceStreamPreflightError("Trace stream pre-flight failed: response had no body.");
155
+ }
156
+ reader = res.body.getReader();
157
+ const decoder = new TextDecoder();
158
+ let buffer = "";
159
+ while (true) {
160
+ const { done, value } = await reader.read();
161
+ if (done) {
162
+ throw traceStreamPreflightError("Trace stream pre-flight failed: stream closed before snapshot.");
163
+ }
164
+ buffer += decoder.decode(value, { stream: true });
165
+ const parts = buffer.split("\n\n");
166
+ buffer = parts.pop() ?? "";
167
+ for (const part of parts) {
168
+ if (parseSSEEventName(part) === "snapshot") {
169
+ return;
170
+ }
171
+ }
172
+ }
173
+ } catch (err) {
174
+ if (err instanceof EvalRunnerError) {
175
+ throw err;
176
+ }
177
+ if (timedOut) {
178
+ throw traceStreamPreflightError(
179
+ `Trace stream pre-flight timed out after ${timeoutMs}ms before receiving a snapshot.`
180
+ );
181
+ }
182
+ throw traceStreamPreflightError(
183
+ `Trace stream pre-flight failed: ${err instanceof Error ? err.message : String(err)}`
184
+ );
185
+ } finally {
186
+ clearTimeout(timer);
187
+ reader?.cancel().catch(() => {
188
+ });
189
+ }
190
+ }
191
+ function parseSSEEventName(block) {
192
+ for (const line of block.split("\n")) {
193
+ if (line.startsWith("event:")) {
194
+ return line.slice("event:".length).trim();
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+ function traceStreamPreflightError(message) {
200
+ return new EvalRunnerError({
201
+ code: "SSE_CONNECT_FAILED",
202
+ message,
203
+ expected: true,
204
+ suggestion: "Make sure the dev server is running (`adk dev`) and trace streaming is available."
205
+ });
206
+ }
140
207
  async function assertChatChannelBound(devServerUrl, headers = {}, logger = defaultLogger) {
141
208
  let conversations = [];
142
209
  try {
@@ -176,7 +243,7 @@ async function assertChatChannelBound(devServerUrl, headers = {}, logger = defau
176
243
  export {
177
244
  ChatSession,
178
245
  assertChatChannelBound,
179
- discoverWebhookId,
180
- setDefaultChatClient
246
+ assertTraceStreamReadable,
247
+ discoverWebhookId
181
248
  };
182
249
  //# sourceMappingURL=client.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/client.ts", "../src/types.ts", "../../analytics/src/errors.ts", "../src/errors.ts"],
4
- "sourcesContent": ["/**\n * Chat client for eval conversations.\n * Drives conversations against a running ADK bot via @botpress/chat.\n *\n * This is a send-only client. Observation (bot responses, tool calls, state)\n * is handled by the SSECollector via trace spans \u2014 not via WebSocket listeners.\n */\n\nimport { Client as BpClient } from '@botpress/client'\nimport { createRequire } from 'module'\nimport type { ChatClient, ChatClientConnection, EvalLogger } from './types'\nimport { defaultLogger } from './types'\nimport { EvalRunnerError } from './errors'\n\nlet defaultChatClient: ChatClient | null = null\n\nexport function setDefaultChatClient(chatClient: ChatClient | null): void {\n defaultChatClient = chatClient\n}\n\nfunction getDefaultChatClient(): ChatClient {\n if (defaultChatClient) return defaultChatClient\n\n const require = createRequire(import.meta.url)\n const mod = require('@botpress/chat') as { Client?: ChatClient; default?: { Client?: ChatClient } }\n const ChatClient = mod.default?.Client ?? mod.Client\n if (!ChatClient) {\n throw new EvalRunnerError({\n code: 'CHAT_CLIENT_MISSING',\n message: '@botpress/chat did not expose a Client export. Pass EvalRunnerConfig.chatClient explicitly.',\n })\n }\n return ChatClient\n}\n\n/**\n * A send-only chat session that maintains a single client connection across turns.\n * Messages and events are fire-and-forget; the SSECollector observes results via traces.\n */\nexport class ChatSession {\n private client: ChatClientConnection | null = null\n private conversationId: string | null = null\n\n constructor(\n private webhookId: string,\n private chatClient?: ChatClient\n ) {}\n\n async connect() {\n const ChatClient = this.chatClient ?? getDefaultChatClient()\n this.client = await ChatClient.connect({ webhookId: this.webhookId })\n }\n\n /** Invariant guard \u2014 calling any session method before connect() is a runner bug. */\n private assertConnected(): ChatClientConnection {\n if (!this.client) {\n throw new EvalRunnerError({\n code: 'CHAT_NOT_CONNECTED',\n message: 'ChatSession not connected. Call connect() first.',\n })\n }\n return this.client\n }\n\n get userId(): string {\n return this.assertConnected().user.id\n }\n\n async ensureConversation(): Promise<string> {\n const client = this.assertConnected()\n\n if (!this.conversationId) {\n const conv = await client.createConversation({})\n this.conversationId = conv.conversation.id\n return conv.conversation.id\n }\n\n return this.conversationId\n }\n\n /**\n * Send a user message. Fire-and-forget \u2014 observation is via SSECollector.\n */\n async sendMessage(message: string): Promise<void> {\n const client = this.assertConnected()\n const conversationId = await this.ensureConversation()\n await client.createMessage({ conversationId, payload: { type: 'text', text: message } })\n }\n\n /**\n * Send an event. Fire-and-forget \u2014 observation is via SSECollector.\n */\n async sendEvent(type: string, payload: Record<string, unknown>): Promise<void> {\n const client = this.assertConnected()\n const conversationId = await this.ensureConversation()\n await client.createEvent({ type, payload, conversationId })\n }\n}\n\n/**\n * Discover the chat integration's webhookId from a bot.\n *\n * Evals talk to the bot through the `chat` integration (via @botpress/chat).\n * If the bot doesn't have that integration installed, evals can't run.\n */\nexport async function discoverWebhookId(botId: string, token: string, apiUrl: string): Promise<string> {\n const client = new BpClient({ token, botId, apiUrl })\n const { bot } = await client.getBot({ id: botId })\n const integrations = bot.integrations || {}\n\n const chat = Object.values(integrations).find((int) => (int as Record<string, unknown>).name === 'chat')\n const webhookId = (chat as Record<string, unknown> | undefined)?.webhookId\n\n if (!webhookId || typeof webhookId !== 'string') {\n throw new EvalRunnerError({\n code: 'CHAT_INTEGRATION_MISSING',\n message: [\n 'The `chat` integration is not installed on this bot \u2014 evals require it.',\n '',\n 'To fix:',\n ' 1. Install it: adk add chat',\n ' 2. Redeploy: adk dev (or: adk deploy)',\n ].join('\\n'),\n expected: true,\n })\n }\n\n return webhookId\n}\n\n/**\n * Verify the bot has at least one Conversation bound to `chat.channel`.\n *\n * The `chat` integration can be installed without any handler listening on its\n * channel \u2014 in that case messages sent via @botpress/chat produce no response\n * and the caller times out silently. Fail fast with actionable guidance instead.\n *\n * Uses the dev server's `/api/agent` endpoint, so this is a no-op against\n * production bots (no devServerUrl).\n */\nexport async function assertChatChannelBound(\n devServerUrl: string,\n headers: Record<string, string> = {},\n logger: EvalLogger = defaultLogger\n): Promise<void> {\n type ConversationSummary = { channel?: string | string[] }\n let conversations: ConversationSummary[] = []\n try {\n const res = await fetch(`${devServerUrl}/api/agent`, { headers })\n if (!res.ok) return // Dev server unreachable \u2014 skip, let downstream errors surface\n const agent = (await res.json()) as { conversations?: ConversationSummary[] }\n conversations = agent.conversations ?? []\n } catch (err) {\n // Soft pre-flight only \u2014 a missing dev server is normal (e.g. prod bots),\n // but log the skip so a *broken* dev server (malformed /api/agent JSON)\n // isn't invisible while every eval afterwards times out mysteriously.\n logger?.warn(\n `Skipping chat.channel binding pre-flight \u2014 could not query ${devServerUrl}/api/agent: ${err instanceof Error ? err.message : String(err)}`\n )\n return\n }\n\n if (conversations.length === 0) return // Bot has no conversations at all \u2014 different problem\n\n // Mirrors the runtime's matchesChannel semantics: a handler matches `chat.channel`\n // if it is the wildcard '*', the literal string 'chat.channel', or an array\n // containing either.\n const matchesChatChannel = (ch: string | string[] | undefined): boolean => {\n if (!ch) return false\n if (ch === '*' || ch === 'chat.channel') return true\n return Array.isArray(ch) && (ch.includes('chat.channel') || ch.includes('*'))\n }\n\n const bound = conversations.some((c) => matchesChatChannel(c.channel))\n if (bound) return\n\n throw new EvalRunnerError({\n code: 'CHAT_CHANNEL_UNBOUND',\n message: [\n 'No Conversation is bound to `chat.channel` \u2014 messages via `@botpress/chat` will time out silently.',\n '',\n 'To fix, bind a Conversation in `src/conversations/` to `chat.channel` (or `*`):',\n \" channel: 'chat.channel', // explicit\",\n \" channel: '*', // wildcard \u2014 matches every channel\",\n \" channel: ['chat.channel', ...], // array form\",\n '',\n 'The `chat` integration is how evals and `adk chat --single` send messages programmatically.',\n ].join('\\n'),\n expected: true,\n })\n}\n", "/**\n * Eval type definitions.\n *\n * User-facing types live in @botpress/evals so eval authors can update eval\n * tooling without tying the authoring API to the agent runtime package.\n */\n\nexport {\n Eval,\n type EvalDefinition,\n type EvalSetup,\n type ConversationTurn,\n type TurnAssertions,\n type OutcomeAssertions,\n type ResponseAssertion,\n type ToolAssertion,\n type StateAssertion,\n type WorkflowAssertion,\n type TimingAssertion,\n type MatchOperator,\n type NumericOperator,\n} from './definition'\n\nexport type { Span, SpanStatus } from './trace'\n\nexport interface ChatClientConnection {\n user: { id: string }\n createConversation: (props: Record<string, unknown>) => Promise<{ conversation: { id: string } }>\n // Broad parameter types keep this compatible with @botpress/chat's narrower\n // generated payload unions while avoiding a dependency on those definitions.\n createMessage: (props: any) => Promise<unknown>\n createEvent: (props: any) => Promise<unknown>\n}\n\nexport interface ChatClient {\n connect: (props: { webhookId: string }) => Promise<ChatClientConnection>\n}\n\n// --- Grader result (universal currency for all graders) ---\n\nexport interface GraderResult {\n assertion: string\n pass: boolean\n expected: string\n actual: string\n}\n\n// --- Tool call extracted from traces ---\n\nexport interface ToolCall {\n name: string\n input: Record<string, unknown>\n output: string\n status: string\n}\n\n// --- Turn report ---\n\nexport interface TurnReport {\n turnNumber: number\n userMessage: string\n botResponse: string\n assertions: GraderResult[]\n pass: boolean\n /** Time spent waiting for the bot to respond (ms) */\n botDuration: number\n /** Time spent fetching traces + running graders (ms) */\n evalDuration: number\n}\n\n// --- Single eval report ---\n\nexport interface EvalReport {\n name: string\n description?: string\n type?: 'capability' | 'regression'\n tags?: string[]\n turns: TurnReport[]\n outcomeAssertions: GraderResult[]\n pass: boolean\n duration: number\n error?: string\n /** Stable EvalErrorCode when `error` came from a typed EvalRunnerError \u2014 lets consumers distinguish setup/config failures from bot failures. */\n errorCode?: string\n}\n\n// --- Progress event for real-time UI updates ---\n\nexport type EvalProgressEvent =\n | { type: 'suite_start'; totalEvals: number }\n | { type: 'eval_start'; evalName: string; index: number; totalTurns: number }\n | {\n type: 'turn_start'\n evalName: string\n evalIndex: number\n turnNumber: number\n totalTurns: number\n userMessage: string\n }\n | {\n type: 'turn_complete'\n evalName: string\n evalIndex: number\n turnNumber: number\n totalTurns: number\n turnReport: TurnReport\n }\n | { type: 'eval_complete'; evalName: string; index: number; report: EvalReport }\n | { type: 'suite_complete'; report: EvalRunReport; error?: string }\n\n// --- Eval run report (full suite) ---\n\nexport interface EvalRunReport {\n id: string\n timestamp: string\n evals: EvalReport[]\n passed: number\n failed: number\n total: number\n duration: number\n filter?: {\n names?: string[]\n tags?: string[]\n type?: 'capability' | 'regression'\n }\n /**\n * True when the suite was aborted mid-run (e.g. user clicked Stop). The\n * persisted report is partial \u2014 `evals` reflects only the completed evals,\n * and `total` matches that count, not the original suite size.\n */\n aborted?: boolean\n}\n\n// --- Bot connection info ---\n\nexport interface BotConnection {\n webhookId: string\n botId: string\n token: string\n apiUrl: string\n}\n\n// --- Logger interface ---\n\nexport interface EvalLogger {\n info: (message: string) => void\n warn: (message: string) => void\n error: (message: string) => void\n}\n\n/**\n * Fallback when no logger is injected (programmatic use outside the CLI).\n * console rather than a no-op so warnings stay visible \u2014 a silent default\n * would hide judge failures and skipped pre-flights, the exact swallow\n * anti-pattern docs/ERROR-HANDLING.md bans. The CLI always injects its own\n * structured logger via EvalRunnerConfig.logger.\n */\nexport const defaultLogger: EvalLogger = console\n\n// --- Eval runner configuration ---\n\nexport interface EvalRunnerConfig {\n credentials: {\n token: string\n apiUrl: string\n botId: string\n workspaceId?: string\n webhookId?: string\n }\n agentPath: string\n /** URL of the dev server providing traces and config. Defaults to the discovered DevConsole URL. */\n devServerUrl?: string\n /** Headers for dev-server requests, e.g. X-Agent-Path when routed through the singleton proxy. */\n devServerHeaders?: Record<string, string>\n /**\n * Optional host-provided chat client. The CLI passes its bundled chat client\n * here so the eval engine does not depend on @botpress/adk.\n */\n chatClient?: ChatClient\n onProgress?: (event: EvalProgressEvent) => void\n /** Logger for warnings and errors. Defaults to console. */\n logger?: EvalLogger\n /**\n * Called for unexpected (internal-bug) eval failures that runEval folds\n * into report.error \u2014 they never propagate to the CLI's command boundary,\n * so without this hook they'd be invisible to error tracking. The CLI binds\n * this to telemetry.captureException; evals itself stays telemetry-free.\n */\n onException?: (error: unknown, properties?: Record<string, unknown>) => void\n /**\n * Optional abort signal \u2014 when fired, the suite stops starting new evals.\n * The currently running eval continues to completion (LLM calls in flight\n * cannot always be cancelled), but no further evals are started.\n */\n signal?: AbortSignal\n /** Bot-level eval options from agent.config.ts. Cascades: eval > agent config > default. */\n evalOptions?: {\n idleTimeout?: number\n /** LLM judge pass threshold (integer, 1-5). Values outside this range are clamped. Cascades: eval > agent config > default (3). */\n judgePassThreshold?: number\n /** Model to use for llm_judge assertions (e.g. 'openai:gpt-4o'). Defaults to 'fast'. */\n judgeModel?: string\n }\n}\n\n// --- Eval suite filter ---\n\nexport interface EvalFilter {\n names?: string[]\n tags?: string[]\n type?: 'capability' | 'regression'\n}\n\n// --- Transformer types (trace-driven eval engine) ---\n\nexport interface StateMutation {\n type: string // 'bot', 'user', 'conversation'\n changedKeys: string[]\n previous: unknown\n current: unknown\n swappedToFile: boolean // data['swapped_to_file'] === true\n}\n\nexport interface WorkflowSpan {\n name: string\n status: 'running' | 'ok' | 'error'\n statusFinal?: string // 'completed', 'continue', 'failed'\n}\n\nexport interface TurnData {\n messages: string[]\n toolCalls: ToolCall[]\n stateMutations: StateMutation[]\n workflowSpans: WorkflowSpan[]\n handlerDuration: number\n handlerStatus: 'ok' | 'error'\n}\n", "/**\n * Shared base error for ADK packages. Lives in `@botpress/analytics` because\n * it's the dependency-light package every surface already shares (cli, adk,\n * ui) \u2014 so all typed errors can genuinely inherit one base instead of\n * mirroring its shape.\n *\n * The policy lives in docs/ERROR-HANDLING.md. The short version: every error\n * we *intend* to throw should carry a stable `code` (so PostHog error\n * tracking can slice issues by code) and an `expected` flag separating\n * user/environment conditions (bad input, not logged in, network down \u2014 render\n * a friendly message, low alert priority) from internal bugs (invariant\n * violations \u2014 full stack, page someone).\n *\n * The runtime package is out of scope and keeps its own `Errors.AbstractError`\n * hierarchy. The adk typed families (`DependencyError`, `Agent0*Error`) extend\n * this base; new code should start here.\n */\nexport class AdkError<Code extends string = string> extends Error {\n /**\n * Marker for cross-module-instance checks \u2014 `instanceof` breaks when a\n * package is duplicated in the dependency graph.\n * @internal\n */\n public static readonly __IS_ADK_BASE_ERROR = true\n\n /**\n * Stable SCREAMING_SNAKE_CASE identifier, e.g. 'BOT_NOT_FOUND'. Subclasses\n * pin `Code` to a literal union (`class EvalRunnerError extends\n * AdkError<EvalErrorCode>`) so typos are compile errors and `code` narrows\n * in switches.\n */\n readonly code: Code\n /**\n * True for user/environment conditions the developer can fix themselves\n * (validation, auth, connectivity). False for internal bugs. Defaults to\n * false so unclassified errors surface as bugs until someone triages them.\n */\n readonly expected: boolean\n /** Structured context. Sanitized before any telemetry leaves the machine. */\n readonly details?: Record<string, unknown>\n /** Actionable next step shown to the user, e.g. \"Run 'adk login' first.\" */\n readonly suggestion?: string\n\n constructor(opts: {\n code: Code\n message: string\n expected?: boolean\n details?: Record<string, unknown>\n suggestion?: string\n cause?: unknown\n }) {\n super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined)\n this.name = this.constructor.name\n this.code = opts.code\n this.expected = opts.expected ?? false\n // Conditional assignment keeps the optional fields absent-not-undefined,\n // which downstream tsconfigs with exactOptionalPropertyTypes require.\n if (opts.details !== undefined) {\n this.details = opts.details\n }\n if (opts.suggestion !== undefined) {\n this.suggestion = opts.suggestion\n }\n }\n}\n\n/**\n * Duck-typed guard that survives duplicated module instances in the\n * dependency graph, where `instanceof AdkError` returns false for an error\n * constructed from another copy of this module. No manual prototype walking\n * needed: static properties are inherited through the constructor chain, so\n * a subclass's constructor resolves the marker in one lookup.\n */\nexport function isAdkError(error: unknown): error is AdkError {\n if (error instanceof AdkError) return true\n if (!(error instanceof Error)) return false\n return (error.constructor as unknown as Record<string, unknown> | undefined)?.__IS_ADK_BASE_ERROR === true\n}\n", "import { AdkError } from '@botpress/analytics'\n\n/**\n * Stable error codes for the eval engine. Typed as a literal union so a typo'd\n * code is a compile error and PostHog issue-slicing stays consistent.\n * Conventions per docs/ERROR-HANDLING.md.\n */\nexport const EVAL_ERROR_CODES = [\n 'EVAL_LOAD_FAILED',\n 'EVAL_FILE_EMPTY',\n 'EVAL_DUPLICATE_NAME',\n 'EVAL_SEED_NO_CONVERSATION',\n 'EVAL_NO_CONVERSATION_ID',\n 'EVAL_TURN_CONFIG_INVALID',\n 'SSE_CONNECT_FAILED',\n 'SSE_NO_BODY',\n 'CHAT_CLIENT_MISSING',\n 'CHAT_NOT_CONNECTED',\n 'CHAT_INTEGRATION_MISSING',\n 'CHAT_CHANNEL_UNBOUND',\n] as const\n\nexport type EvalErrorCode = (typeof EVAL_ERROR_CODES)[number]\n\n/**\n * Typed error for the eval engine. Named EvalRunnerError (not EvalError) to\n * avoid shadowing the JS built-in `EvalError`.\n */\nexport class EvalRunnerError extends AdkError<EvalErrorCode> {}\n"],
5
- "mappings": "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",
6
- "names": ["require"]
3
+ "sources": ["../src/types.ts", "../../analytics/src/errors.ts", "../src/errors.ts", "../src/client.ts"],
4
+ "sourcesContent": ["/**\n * Eval type definitions.\n *\n * User-facing types live in @botpress/evals so eval authors can update eval\n * tooling without tying the authoring API to the agent runtime package.\n */\n\nimport type { EvalDefinition as _EvalDefinition } from './definition'\n\nexport {\n Eval,\n type EvalDefinition,\n type EvalSetup,\n type ConversationTurn,\n type TurnAssertions,\n type OutcomeAssertions,\n type ResponseAssertion,\n type ToolAssertion,\n type StateAssertion,\n type WorkflowAssertion,\n type TimingAssertion,\n type MatchOperator,\n type NumericOperator,\n} from './definition'\n\nexport type { Span, SpanStatus } from './spans/trace'\nexport type { WaitOptions, WorkflowWaitOptions } from './spans/span-source'\nimport type { SpanSource } from './spans/span-source'\nexport type { SpanSource }\n\n/**\n * The @botpress/chat Client *class* (constructor) \u2014 not an instance. Callers\n * inject it (the CLI passes its bundled chat client) and the runner calls the\n * static `.connect()`. Typed via `typeof import(...)` so this types-only module\n * doesn't pull in a value import.\n */\nexport type ChatClient = typeof import('@botpress/chat').Client\n\n// --- Grader result (universal currency for all graders) ---\n\nexport interface GraderResult {\n assertion: string\n pass: boolean\n expected: string\n actual: string\n /**\n * True when the assertion could not actually be evaluated (e.g. the llm_judge had no credentials,\n * returned an empty response, or errored). Distinguishes a genuine pass from a \"didn't run\" pass.\n * Callers that demand a trustworthy verdict (e.g. the benchmark) should treat `skipped` as a failure.\n */\n skipped?: boolean\n}\n\n// --- Tool call extracted from traces ---\n\nexport interface ToolCall {\n name: string\n input: Record<string, unknown>\n output: string\n status: string\n}\n\n// --- Turn report ---\n\nexport interface TurnReport {\n turnIndex: number\n userMessage: string\n botResponse: string\n assertions: GraderResult[]\n pass: boolean\n /** Time spent waiting for the bot to respond (ms) */\n botDuration: number\n /** Time spent fetching traces + running graders (ms) */\n evalDuration: number\n}\n\n// --- Single eval report ---\n\nexport interface EvalReport {\n name: string\n description?: string\n type?: 'capability' | 'regression'\n tags?: string[]\n turns: TurnReport[]\n outcomeAssertions: GraderResult[]\n pass: boolean\n duration: number\n error?: string\n /** Stable EvalErrorCode when `error` came from a typed EvalRunnerError \u2014 lets consumers distinguish setup/config failures from bot failures. */\n errorCode?: string\n}\n\n// --- Progress event for real-time UI updates ---\n\nexport type EvalProgressEvent =\n | { type: 'suite_start'; totalEvals: number }\n | { type: 'eval_start'; evalName: string; index: number; totalTurns: number }\n | {\n type: 'turn_start'\n evalName: string\n evalIndex: number\n turnIndex: number\n totalTurns: number\n userMessage: string\n }\n | {\n type: 'turn_complete'\n evalName: string\n evalIndex: number\n turnIndex: number\n totalTurns: number\n turnReport: TurnReport\n }\n | { type: 'eval_complete'; evalName: string; index: number; report: EvalReport }\n | { type: 'suite_complete'; report: EvalRunReport; error?: string }\n\n// --- Eval run report (full suite) ---\n\nexport interface EvalRunReport {\n id: string\n timestamp: string\n evals: EvalReport[]\n passed: number\n failed: number\n total: number\n duration: number\n filter?: {\n names?: string[]\n tags?: string[]\n type?: 'capability' | 'regression'\n }\n /**\n * True when the suite was aborted mid-run (e.g. user clicked Stop). The\n * persisted report is partial \u2014 `evals` reflects only the completed evals,\n * and `total` matches that count, not the original suite size.\n */\n aborted?: boolean\n}\n\n// --- Bot connection info ---\n\nexport interface BotConnection {\n client: import('@botpress/client').Client\n botId: string\n}\n\n// --- Logger interface ---\n\nexport interface EvalLogger {\n info: (message: string) => void\n warn: (message: string) => void\n error: (message: string) => void\n}\n\n/**\n * Fallback when no logger is injected (programmatic use outside the CLI).\n * console rather than a no-op so warnings stay visible \u2014 a silent default\n * would hide judge failures and skipped pre-flights, the exact swallow\n * anti-pattern docs/ERROR-HANDLING.md bans. The CLI always injects its own\n * structured logger via EvalRunnerConfig.logger.\n */\nexport const defaultLogger: EvalLogger = console\n\n// --- Eval runner configuration ---\n\nexport interface EvalRunnerConfig {\n /** Authenticated Botpress client. Used for state seeding, workflow triggers, webhook discovery, and LLM judge. */\n client: import('@botpress/client').Client\n botId: string\n /** Path to the agent directory. Used to discover eval files from `<agentPath>/evals/`. Not needed when `definitions` is provided. */\n agentPath?: string\n /** Pre-loaded eval definitions. When provided, `agentPath` is not used for eval discovery. */\n definitions?: _EvalDefinition[]\n /** URL of the dev server providing traces and config. Defaults to the discovered DevConsole URL. */\n devServerUrl?: string\n /** Headers for dev-server requests, e.g. X-Agent-Path when routed through the singleton proxy. */\n devServerHeaders?: Record<string, string>\n /**\n * Optional host-provided chat client. The CLI passes its bundled chat client\n * here so the eval engine does not depend on @botpress/adk.\n */\n chatClient?: ChatClient\n onProgress?: (event: EvalProgressEvent) => void | Promise<void>\n /** Logger for warnings and errors. Defaults to console. */\n logger?: EvalLogger\n /**\n * Called for unexpected (internal-bug) eval failures that runEval folds\n * into report.error \u2014 they never propagate to the CLI's command boundary,\n * so without this hook they'd be invisible to error tracking. The CLI binds\n * this to telemetry.captureException; evals itself stays telemetry-free.\n */\n onException?: (error: unknown, properties?: Record<string, unknown>) => void\n /**\n * Optional abort signal \u2014 when fired, the suite stops starting new evals.\n * The currently running eval continues to completion (LLM calls in flight\n * cannot always be cancelled), but no further evals are started.\n */\n signal?: AbortSignal\n /** Bot-level eval options from agent.config.ts. Cascades: eval > agent config > default. */\n evalOptions?: {\n idleTimeout?: number\n /** LLM judge pass threshold (integer, 1-5). Values outside this range are clamped. Cascades: eval > agent config > default (3). */\n judgePassThreshold?: number\n /** Model to use for llm_judge assertions (e.g. 'openai:gpt-4o'). Defaults to 'fast'. */\n judgeModel?: string\n }\n /** Factory for creating span sources. Each eval gets its own instance. Defaults to LocalSpanSource (SSE from dev server). */\n createSpanSource?: () => SpanSource\n /** Externally-provided run ID. When set, the runner uses this instead of generating its own. */\n runId?: string\n /** Pre-resolved chat integration webhook ID. Skips the getBot() discovery call when provided. */\n chatWebhookId?: string\n /** Base URL for the chat service (e.g. https://chat.botpress.dev). Defaults to https://chat.botpress.cloud. */\n chatBaseUrl?: string\n}\n\n// --- Eval suite filter ---\n\nexport interface EvalFilter {\n names?: string[]\n tags?: string[]\n type?: 'capability' | 'regression'\n}\n\n// --- Transformer types (trace-driven eval engine) ---\n\nexport interface StateMutation {\n type: string // 'bot', 'user', 'conversation'\n changedKeys: string[]\n previous: unknown\n current: unknown\n swappedToFile: boolean // data['swapped_to_file'] === true\n}\n\nexport interface WorkflowSpan {\n name: string\n status: 'running' | 'ok' | 'error'\n statusFinal?: string // 'completed', 'continue', 'failed'\n}\n\nexport interface TurnData {\n messages: string[]\n toolCalls: ToolCall[]\n stateMutations: StateMutation[]\n workflowSpans: WorkflowSpan[]\n handlerDuration: number\n handlerStatus: 'ok' | 'error'\n}\n", "/**\n * Shared base error for ADK packages. Lives in `@botpress/analytics` because\n * it's the dependency-light package every surface already shares (cli, adk,\n * ui) \u2014 so all typed errors can genuinely inherit one base instead of\n * mirroring its shape.\n *\n * The policy lives in docs/ERROR-HANDLING.md. The short version: every error\n * we *intend* to throw should carry a stable `code` (so PostHog error\n * tracking can slice issues by code) and an `expected` flag separating\n * user/environment conditions (bad input, not logged in, network down \u2014 render\n * a friendly message, low alert priority) from internal bugs (invariant\n * violations \u2014 full stack, page someone).\n *\n * The runtime package is out of scope and keeps its own `Errors.AbstractError`\n * hierarchy. The adk typed families (`DependencyError`, `Agent0*Error`) extend\n * this base; new code should start here.\n */\nexport class AdkError<Code extends string = string> extends Error {\n /**\n * Marker for cross-module-instance checks \u2014 `instanceof` breaks when a\n * package is duplicated in the dependency graph.\n * @internal\n */\n public static readonly __IS_ADK_BASE_ERROR = true\n\n /**\n * Stable SCREAMING_SNAKE_CASE identifier, e.g. 'BOT_NOT_FOUND'. Subclasses\n * pin `Code` to a literal union (`class EvalRunnerError extends\n * AdkError<EvalErrorCode>`) so typos are compile errors and `code` narrows\n * in switches.\n */\n readonly code: Code\n /**\n * True for user/environment conditions the developer can fix themselves\n * (validation, auth, connectivity). False for internal bugs. Defaults to\n * false so unclassified errors surface as bugs until someone triages them.\n */\n readonly expected: boolean\n /** Structured context. Sanitized before any telemetry leaves the machine. */\n readonly details?: Record<string, unknown>\n /** Actionable next step shown to the user, e.g. \"Run 'adk login' first.\" */\n readonly suggestion?: string\n\n constructor(opts: {\n code: Code\n message: string\n expected?: boolean\n details?: Record<string, unknown>\n suggestion?: string\n cause?: unknown\n }) {\n super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined)\n this.name = this.constructor.name\n this.code = opts.code\n this.expected = opts.expected ?? false\n // Conditional assignment keeps the optional fields absent-not-undefined,\n // which downstream tsconfigs with exactOptionalPropertyTypes require.\n if (opts.details !== undefined) {\n this.details = opts.details\n }\n if (opts.suggestion !== undefined) {\n this.suggestion = opts.suggestion\n }\n }\n}\n\n/**\n * Duck-typed guard that survives duplicated module instances in the\n * dependency graph, where `instanceof AdkError` returns false for an error\n * constructed from another copy of this module. No manual prototype walking\n * needed: static properties are inherited through the constructor chain, so\n * a subclass's constructor resolves the marker in one lookup.\n */\nexport function isAdkError(error: unknown): error is AdkError {\n if (error instanceof AdkError) return true\n if (!(error instanceof Error)) return false\n return (error.constructor as unknown as Record<string, unknown> | undefined)?.__IS_ADK_BASE_ERROR === true\n}\n", "import { AdkError } from '@botpress/analytics'\n\n/**\n * Stable error codes for the eval engine. Typed as a literal union so a typo'd\n * code is a compile error and PostHog issue-slicing stays consistent.\n * Conventions per docs/ERROR-HANDLING.md.\n */\nexport const EVAL_ERROR_CODES = [\n 'EVAL_LOAD_FAILED',\n 'EVAL_FILE_EMPTY',\n 'EVAL_DUPLICATE_NAME',\n 'EVAL_SEED_NO_CONVERSATION',\n 'EVAL_NO_CONVERSATION_ID',\n 'EVAL_TURN_CONFIG_INVALID',\n 'SSE_CONNECT_FAILED',\n 'SSE_NO_BODY',\n 'CHAT_CLIENT_MISSING',\n 'CHAT_NOT_CONNECTED',\n 'CHAT_INTEGRATION_MISSING',\n 'CHAT_CHANNEL_UNBOUND',\n] as const\n\nexport type EvalErrorCode = (typeof EVAL_ERROR_CODES)[number]\n\n/**\n * Typed error for the eval engine. Named EvalRunnerError (not EvalError) to\n * avoid shadowing the JS built-in `EvalError`.\n */\nexport class EvalRunnerError extends AdkError<EvalErrorCode> {}\n", "/**\n * Chat client for eval conversations.\n * Drives conversations against a running ADK bot via @botpress/chat.\n *\n * This is a send-only client. Observation (bot responses, tool calls, state)\n * is handled by the SSECollector via trace spans \u2014 not via WebSocket listeners.\n */\n\nimport { Client as BpClient } from '@botpress/client'\nimport type { AuthenticatedClient as AuthedChatClient } from '@botpress/chat'\nimport type { ChatClient, EvalLogger } from './types'\nimport { defaultLogger } from './types'\nimport { EvalRunnerError } from './errors'\n\nconst TRACE_STREAM_PREFLIGHT_TIMEOUT_MS = 10_000\n\n/**\n * A send-only chat session that maintains a single client connection across turns.\n * Messages and events are fire-and-forget; the SSECollector observes results via traces.\n */\nexport class ChatSession {\n private client: AuthedChatClient | null = null\n private conversationId: string | null = null\n\n constructor(\n private bpClient: BpClient,\n private botId: string,\n private _chatWebhookId?: string,\n private _chatBaseUrl?: string,\n private _chatClient?: ChatClient\n ) {}\n\n async connect() {\n const webhookId = this._chatWebhookId ?? (await discoverWebhookId(this.bpClient, this.botId))\n\n // Prefer the host-injected client. The CLI passes its bundled CJS chat client\n // (working node http adapter); only fall back to a dynamic import when nothing\n // was injected \u2014 e.g. the cloud runtime, where @botpress/chat resolves to a\n // real node_modules dep. In a bun-compiled CLI binary the dynamic import\n // resolves to the browser ESM bundle whose axios http adapter is null\n // (\"Adapter 'http' is not available in the build\"), so the injected client is\n // required there.\n const ChatClientCtor =\n this._chatClient ?? (await import(/* webpackIgnore: true */ '@botpress/chat' as string)).Client\n\n this.client = await ChatClientCtor.connect({\n webhookId,\n ...(this._chatBaseUrl ? { baseApiUrl: this._chatBaseUrl } : {}),\n })\n }\n\n /** Invariant guard \u2014 calling any session method before connect() is a runner bug. */\n private assertConnected(): AuthedChatClient {\n if (!this.client) {\n throw new EvalRunnerError({\n code: 'CHAT_NOT_CONNECTED',\n message: 'ChatSession not connected. Call connect() first.',\n })\n }\n return this.client\n }\n\n get userId(): string {\n return this.assertConnected().user.id\n }\n\n async ensureConversation(): Promise<string> {\n const client = this.assertConnected()\n\n if (!this.conversationId) {\n const conv = await client.createConversation({})\n this.conversationId = conv.conversation.id\n return conv.conversation.id\n }\n\n return this.conversationId\n }\n\n /**\n * Open a fresh conversation under the same user (the user is fixed at\n * connect()) and make it active. Returns the new conversation id.\n */\n async newConversation(): Promise<string> {\n const client = this.assertConnected()\n const conv = await client.createConversation({})\n this.conversationId = conv.conversation.id\n return conv.conversation.id\n }\n\n /**\n * Send a user message. Fire-and-forget \u2014 observation is via SSECollector.\n */\n async sendMessage(message: string): Promise<void> {\n const client = this.assertConnected()\n const conversationId = await this.ensureConversation()\n await client.createMessage({ conversationId, payload: { type: 'text', text: message } })\n }\n\n async sendEvent(payload: Record<string, unknown>): Promise<void> {\n const client = this.assertConnected()\n const conversationId = await this.ensureConversation()\n await client.createEvent({ payload, conversationId })\n }\n}\n\n/**\n * Discover the chat integration's webhookId from a bot.\n *\n * Evals talk to the bot through the `chat` integration (via @botpress/chat).\n * If the bot doesn't have that integration installed, evals can't run.\n */\nexport async function discoverWebhookId(client: BpClient, botId: string): Promise<string> {\n const { bot } = await client.getBot({ id: botId })\n const integrations = bot.integrations || {}\n\n const chat = Object.values(integrations).find((int) => (int as Record<string, unknown>).name === 'chat')\n const webhookId = (chat as Record<string, unknown> | undefined)?.webhookId\n\n if (!webhookId || typeof webhookId !== 'string') {\n throw new EvalRunnerError({\n code: 'CHAT_INTEGRATION_MISSING',\n message: [\n 'The `chat` integration is not installed on this bot \u2014 evals require it.',\n '',\n 'To fix:',\n ' 1. Install it: adk integrations add chat',\n ' 2. Redeploy: adk dev (or: adk deploy)',\n ].join('\\n'),\n expected: true,\n })\n }\n\n return webhookId\n}\n\n/**\n * Verify the dev server's trace stream can be read before eval turns start.\n *\n * This only checks that `/api/traces/stream` is reachable and emits its initial\n * snapshot. It does not prove the private runtime span-ingest path is healthy.\n */\nexport async function assertTraceStreamReadable(\n devServerUrl: string,\n headers: Record<string, string> = {},\n timeoutMs = TRACE_STREAM_PREFLIGHT_TIMEOUT_MS\n): Promise<void> {\n const url = `${devServerUrl.replace(/\\/$/, '')}/api/traces/stream?count=1`\n const abortController = new AbortController()\n let timedOut = false\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null\n\n const timer = setTimeout(() => {\n timedOut = true\n abortController.abort()\n }, timeoutMs)\n\n try {\n const res = await fetch(url, {\n headers: { ...headers, Accept: 'text/event-stream' },\n signal: abortController.signal,\n })\n\n if (!res.ok) {\n throw traceStreamPreflightError(`Trace stream pre-flight failed: ${res.status} ${res.statusText}`)\n }\n\n if (!res.body) {\n throw traceStreamPreflightError('Trace stream pre-flight failed: response had no body.')\n }\n\n reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n throw traceStreamPreflightError('Trace stream pre-flight failed: stream closed before snapshot.')\n }\n\n buffer += decoder.decode(value, { stream: true })\n const parts = buffer.split('\\n\\n')\n buffer = parts.pop() ?? ''\n\n for (const part of parts) {\n if (parseSSEEventName(part) === 'snapshot') {\n return\n }\n }\n }\n } catch (err) {\n if (err instanceof EvalRunnerError) {\n throw err\n }\n\n if (timedOut) {\n throw traceStreamPreflightError(\n `Trace stream pre-flight timed out after ${timeoutMs}ms before receiving a snapshot.`\n )\n }\n\n throw traceStreamPreflightError(\n `Trace stream pre-flight failed: ${err instanceof Error ? err.message : String(err)}`\n )\n } finally {\n clearTimeout(timer)\n reader?.cancel().catch(() => {})\n }\n}\n\nfunction parseSSEEventName(block: string): string | null {\n for (const line of block.split('\\n')) {\n if (line.startsWith('event:')) {\n return line.slice('event:'.length).trim()\n }\n }\n return null\n}\n\nfunction traceStreamPreflightError(message: string): EvalRunnerError {\n return new EvalRunnerError({\n code: 'SSE_CONNECT_FAILED',\n message,\n expected: true,\n suggestion: 'Make sure the dev server is running (`adk dev`) and trace streaming is available.',\n })\n}\n\n/**\n * Verify the bot has at least one Conversation bound to `chat.channel`.\n *\n * The `chat` integration can be installed without any handler listening on its\n * channel \u2014 in that case messages sent via @botpress/chat produce no response\n * and the caller times out silently. Fail fast with actionable guidance instead.\n *\n * Uses the dev server's `/api/agent` endpoint, so this is a no-op against\n * production bots (no devServerUrl).\n */\nexport async function assertChatChannelBound(\n devServerUrl: string,\n headers: Record<string, string> = {},\n logger: EvalLogger = defaultLogger\n): Promise<void> {\n type ConversationSummary = { channel?: string | string[] }\n let conversations: ConversationSummary[] = []\n try {\n const res = await fetch(`${devServerUrl}/api/agent`, { headers })\n if (!res.ok) return // Dev server unreachable \u2014 skip, let downstream errors surface\n const agent = (await res.json()) as { conversations?: ConversationSummary[] }\n conversations = agent.conversations ?? []\n } catch (err) {\n // Soft pre-flight only \u2014 a missing dev server is normal (e.g. prod bots),\n // but log the skip so a *broken* dev server (malformed /api/agent JSON)\n // isn't invisible while every eval afterwards times out mysteriously.\n logger?.warn(\n `Skipping chat.channel binding pre-flight \u2014 could not query ${devServerUrl}/api/agent: ${err instanceof Error ? err.message : String(err)}`\n )\n return\n }\n\n if (conversations.length === 0) return // Bot has no conversations at all \u2014 different problem\n\n // Mirrors the runtime's matchesChannel semantics: a handler matches `chat.channel`\n // if it is the wildcard '*', the literal string 'chat.channel', or an array\n // containing either.\n const matchesChatChannel = (ch: string | string[] | undefined): boolean => {\n if (!ch) return false\n if (ch === '*' || ch === 'chat.channel') return true\n return Array.isArray(ch) && (ch.includes('chat.channel') || ch.includes('*'))\n }\n\n const bound = conversations.some((c) => matchesChatChannel(c.channel))\n if (bound) return\n\n throw new EvalRunnerError({\n code: 'CHAT_CHANNEL_UNBOUND',\n message: [\n 'No Conversation is bound to `chat.channel` \u2014 messages via `@botpress/chat` will time out silently.',\n '',\n 'To fix, bind a Conversation in `src/conversations/` to `chat.channel` (or `*`):',\n \" channel: 'chat.channel', // explicit\",\n \" channel: '*', // wildcard \u2014 matches every channel\",\n \" channel: ['chat.channel', ...], // array form\",\n '',\n 'The `chat` integration is how evals and `adk chat --single` send messages programmatically.',\n ].join('\\n'),\n expected: true,\n })\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
7
  }
@@ -62,11 +62,16 @@ export interface TurnAssertions {
62
62
  export interface ConversationTurn {
63
63
  user?: string;
64
64
  event?: {
65
- type: string;
66
- payload?: Record<string, unknown>;
65
+ payload: Record<string, unknown>;
67
66
  };
68
67
  expectSilence?: boolean;
69
68
  assert?: TurnAssertions;
69
+ /**
70
+ * Run this turn in a NEW conversation under the same user. The prior
71
+ * transcript is absent, so recall here can only come from user-scoped
72
+ * persistence, not context. Ignored on the first turn.
73
+ */
74
+ newConversation?: boolean;
70
75
  }
71
76
  export interface OutcomeAssertions {
72
77
  state?: StateAssertion[];
@@ -93,6 +98,7 @@ export interface EvalDefinition {
93
98
  outcome?: OutcomeAssertions;
94
99
  options?: {
95
100
  idleTimeout?: number;
101
+ /** @deprecated No-op: the LLM judge is boolean (zai.check); there is no score threshold. */
96
102
  judgePassThreshold?: number;
97
103
  };
98
104
  }
@@ -106,6 +112,7 @@ export declare class Eval implements EvalDefinition {
106
112
  readonly outcome?: OutcomeAssertions;
107
113
  readonly options?: {
108
114
  idleTimeout?: number;
115
+ /** @deprecated No-op: the LLM judge is boolean (zai.check); there is no score threshold. */
109
116
  judgePassThreshold?: number;
110
117
  };
111
118
  constructor(def: EvalDefinition);
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../src/definition.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../src/definition.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/definition.ts"],
4
- "sourcesContent": ["export type MatchOperator =\n | string\n | { equals: unknown }\n | { contains: string }\n | { not_contains: string }\n | { matches: string }\n | { in: unknown[] }\n | { exists: boolean }\n | { gte: number }\n | { lte: number }\n\nexport type ResponseAssertion =\n | { contains: string }\n | { not_contains: string }\n | { matches: string }\n | { llm_judge: string }\n// | { similar_to: string } TODO: uncomment this when it gets implemented\n\nexport type ToolAssertion =\n | { called: string; params?: Record<string, MatchOperator> }\n | { not_called: string }\n | { call_order: string[] }\n\nexport interface StateAssertion {\n path: string\n equals?: unknown\n changed?: boolean\n}\n\nexport interface WorkflowAssertion {\n name: string\n entered?: boolean\n completed?: boolean\n}\n\nexport type NumericOperator = { equals: number } | { gte: number } | { lte: number }\n\nexport interface TimingAssertion {\n response_time: NumericOperator\n}\n\nexport interface TurnAssertions {\n response?: ResponseAssertion[]\n tools?: ToolAssertion[]\n state?: StateAssertion[]\n workflow?: WorkflowAssertion[]\n timing?: TimingAssertion[]\n}\n\nexport interface ConversationTurn {\n user?: string\n event?: { type: string; payload?: Record<string, unknown> }\n expectSilence?: boolean\n assert?: TurnAssertions\n}\n\nexport interface OutcomeAssertions {\n state?: StateAssertion[]\n workflow?: WorkflowAssertion[]\n}\n\nexport interface EvalSetup {\n state?: {\n bot?: Record<string, unknown>\n user?: Record<string, unknown>\n conversation?: Record<string, unknown>\n }\n workflow?: {\n trigger: string\n input?: Record<string, unknown>\n }\n}\n\nexport interface EvalDefinition {\n name: string\n description?: string\n tags?: string[]\n type?: 'capability' | 'regression'\n setup?: EvalSetup\n conversation: ConversationTurn[]\n outcome?: OutcomeAssertions\n options?: {\n idleTimeout?: number\n judgePassThreshold?: number\n }\n}\n\nexport class Eval implements EvalDefinition {\n readonly name: string\n readonly description?: string\n readonly tags?: string[]\n readonly type?: 'capability' | 'regression'\n readonly setup?: EvalSetup\n readonly conversation: ConversationTurn[]\n readonly outcome?: OutcomeAssertions\n readonly options?: {\n idleTimeout?: number\n judgePassThreshold?: number\n }\n\n constructor(def: EvalDefinition) {\n this.name = def.name\n this.conversation = def.conversation\n if (def.description !== undefined) this.description = def.description\n if (def.tags !== undefined) this.tags = def.tags\n if (def.type !== undefined) this.type = def.type\n if (def.setup !== undefined) this.setup = def.setup\n if (def.outcome !== undefined) this.outcome = def.outcome\n if (def.options !== undefined) this.options = def.options\n }\n}\n"],
5
- "mappings": ";AAuFO,IAAM,OAAN,MAAqC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAKT,YAAY,KAAqB;AAC/B,SAAK,OAAO,IAAI;AAChB,SAAK,eAAe,IAAI;AACxB,QAAI,IAAI,gBAAgB,OAAW,MAAK,cAAc,IAAI;AAC1D,QAAI,IAAI,SAAS,OAAW,MAAK,OAAO,IAAI;AAC5C,QAAI,IAAI,SAAS,OAAW,MAAK,OAAO,IAAI;AAC5C,QAAI,IAAI,UAAU,OAAW,MAAK,QAAQ,IAAI;AAC9C,QAAI,IAAI,YAAY,OAAW,MAAK,UAAU,IAAI;AAClD,QAAI,IAAI,YAAY,OAAW,MAAK,UAAU,IAAI;AAAA,EACpD;AACF;",
4
+ "sourcesContent": ["export type MatchOperator =\n | string\n | { equals: unknown }\n | { contains: string }\n | { not_contains: string }\n | { matches: string }\n | { in: unknown[] }\n | { exists: boolean }\n | { gte: number }\n | { lte: number }\n\nexport type ResponseAssertion =\n | { contains: string }\n | { not_contains: string }\n | { matches: string }\n | { llm_judge: string }\n// | { similar_to: string } TODO: uncomment this when it gets implemented\n\nexport type ToolAssertion =\n | { called: string; params?: Record<string, MatchOperator> }\n | { not_called: string }\n | { call_order: string[] }\n\nexport interface StateAssertion {\n path: string\n equals?: unknown\n changed?: boolean\n}\n\nexport interface WorkflowAssertion {\n name: string\n entered?: boolean\n completed?: boolean\n}\n\nexport type NumericOperator = { equals: number } | { gte: number } | { lte: number }\n\nexport interface TimingAssertion {\n response_time: NumericOperator\n}\n\nexport interface TurnAssertions {\n response?: ResponseAssertion[]\n tools?: ToolAssertion[]\n state?: StateAssertion[]\n workflow?: WorkflowAssertion[]\n timing?: TimingAssertion[]\n}\n\nexport interface ConversationTurn {\n user?: string\n event?: { payload: Record<string, unknown> }\n expectSilence?: boolean\n assert?: TurnAssertions\n /**\n * Run this turn in a NEW conversation under the same user. The prior\n * transcript is absent, so recall here can only come from user-scoped\n * persistence, not context. Ignored on the first turn.\n */\n newConversation?: boolean\n}\n\nexport interface OutcomeAssertions {\n state?: StateAssertion[]\n workflow?: WorkflowAssertion[]\n}\n\nexport interface EvalSetup {\n state?: {\n bot?: Record<string, unknown>\n user?: Record<string, unknown>\n conversation?: Record<string, unknown>\n }\n workflow?: {\n trigger: string\n input?: Record<string, unknown>\n }\n}\n\nexport interface EvalDefinition {\n name: string\n description?: string\n tags?: string[]\n type?: 'capability' | 'regression'\n setup?: EvalSetup\n conversation: ConversationTurn[]\n outcome?: OutcomeAssertions\n options?: {\n idleTimeout?: number\n /** @deprecated No-op: the LLM judge is boolean (zai.check); there is no score threshold. */\n judgePassThreshold?: number\n }\n}\n\nexport class Eval implements EvalDefinition {\n readonly name: string\n readonly description?: string\n readonly tags?: string[]\n readonly type?: 'capability' | 'regression'\n readonly setup?: EvalSetup\n readonly conversation: ConversationTurn[]\n readonly outcome?: OutcomeAssertions\n readonly options?: {\n idleTimeout?: number\n /** @deprecated No-op: the LLM judge is boolean (zai.check); there is no score threshold. */\n judgePassThreshold?: number\n }\n\n constructor(def: EvalDefinition) {\n this.name = def.name\n this.conversation = def.conversation\n if (def.description !== undefined) this.description = def.description\n if (def.tags !== undefined) this.tags = def.tags\n if (def.type !== undefined) this.type = def.type\n if (def.setup !== undefined) this.setup = def.setup\n if (def.outcome !== undefined) this.outcome = def.outcome\n if (def.options !== undefined) this.options = def.options\n }\n}\n"],
5
+ "mappings": ";AA8FO,IAAM,OAAN,MAAqC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAMT,YAAY,KAAqB;AAC/B,SAAK,OAAO,IAAI;AAChB,SAAK,eAAe,IAAI;AACxB,QAAI,IAAI,gBAAgB,OAAW,MAAK,cAAc,IAAI;AAC1D,QAAI,IAAI,SAAS,OAAW,MAAK,OAAO,IAAI;AAC5C,QAAI,IAAI,SAAS,OAAW,MAAK,OAAO,IAAI;AAC5C,QAAI,IAAI,UAAU,OAAW,MAAK,QAAQ,IAAI;AAC9C,QAAI,IAAI,YAAY,OAAW,MAAK,UAAU,IAAI;AAClD,QAAI,IAAI,YAAY,OAAW,MAAK,UAAU,IAAI;AAAA,EACpD;AACF;",
6
6
  "names": []
7
7
  }
@@ -43,60 +43,31 @@ function operatorToString(operator) {
43
43
  }
44
44
 
45
45
  // src/graders/llm.ts
46
- import { Client } from "@botpress/client";
47
46
  import { Cognitive } from "@botpress/cognitive";
47
+ import { Zai } from "@botpress/zai";
48
48
 
49
49
  // src/types.ts
50
50
  var defaultLogger = console;
51
51
 
52
52
  // src/graders/llm.ts
53
- var DEFAULT_PASS_THRESHOLD = 3;
54
- function buildJudgeSystemPrompt(passThreshold) {
55
- return `You are an evaluation judge for a chatbot. You will be given:
56
- - The user's message
57
- - The bot's response
58
- - Grading criteria
59
-
60
- Your job is to determine if the bot's response meets the criteria.
61
-
62
- Respond with a JSON object:
63
- {
64
- "pass": true/false,
65
- "score": 1-5,
66
- "reason": "brief explanation"
67
- }
68
-
69
- Scoring guide:
70
- - 5: Fully meets criteria, excellent response
71
- - 4: Mostly meets criteria, minor issues
72
- - 3: Partially meets criteria, notable gaps
73
- - 2: Barely meets criteria, significant issues
74
- - 1: Does not meet criteria
75
-
76
- A score of ${passThreshold} or above is a pass.`;
77
- }
78
53
  var MODEL_ALIASES = ["fast", "best"];
79
- var _cognitive = null;
54
+ var _zai = null;
80
55
  var _availableModels = [];
81
56
  var _judgeModel = "fast";
57
+ var _failClosed = true;
82
58
  var _log = defaultLogger;
83
59
  function _isModelValid(model) {
84
60
  if (MODEL_ALIASES.includes(model)) return true;
85
61
  if (_availableModels.length === 0) return false;
86
62
  return _availableModels.includes(model);
87
63
  }
88
- async function initLLMJudge(credentials, options) {
64
+ async function initLLMJudge(client, options) {
89
65
  const log = options?.logger ?? defaultLogger;
90
66
  _log = log;
91
- const client = new Client({
92
- token: credentials.token,
93
- apiUrl: credentials.apiUrl,
94
- botId: credentials.botId,
95
- ...credentials.workspaceId !== void 0 ? { workspaceId: credentials.workspaceId } : {}
96
- });
97
- _cognitive = new Cognitive({ client, __experimental_beta: true });
67
+ _failClosed = options?.failClosed ?? true;
68
+ const cognitive = new Cognitive({ client, __experimental_beta: true });
98
69
  try {
99
- _availableModels = Array.from((await _cognitive.fetchRemoteModels()).keys());
70
+ _availableModels = Array.from((await cognitive.fetchRemoteModels()).keys());
100
71
  } catch (err) {
101
72
  log.warn(`Failed to fetch available models: ${err.message}. LLM judge will be unavailable.`);
102
73
  _availableModels = [];
@@ -107,66 +78,42 @@ async function initLLMJudge(credentials, options) {
107
78
  } else if (!_isModelValid(_judgeModel)) {
108
79
  log.warn(`Configured LLM judge model "${_judgeModel}" is invalid. Run "adk models" to list available models.`);
109
80
  }
81
+ _zai = new Zai({ client: cognitive, modelId: _judgeModel });
110
82
  }
111
83
  async function gradeLLMJudge(botResponse, criteria, context) {
112
- const threshold = Math.max(1, Math.min(5, context.passThreshold ?? DEFAULT_PASS_THRESHOLD));
84
+ const assertion = `llm_judge: "${criteria}"`;
113
85
  try {
114
- if (!_cognitive) {
86
+ if (!_zai) {
115
87
  _log.warn(`LLM judge unavailable for "${criteria}": no credentials configured (initLLMJudge not called)`);
116
88
  return {
117
- assertion: `llm_judge: "${criteria}"`,
118
- pass: false,
89
+ assertion,
90
+ pass: !_failClosed,
91
+ skipped: true,
119
92
  expected: criteria,
120
93
  actual: "FAILED \u2014 LLM judge unavailable: no credentials configured"
121
94
  };
122
95
  }
123
96
  if (!_isModelValid(_judgeModel)) {
124
97
  return {
125
- assertion: `llm_judge: "${criteria}"`,
98
+ assertion,
126
99
  pass: false,
127
100
  expected: criteria,
128
101
  actual: "FAILED \u2014 invalid LLM judge model"
129
102
  };
130
103
  }
131
- const { output } = await _cognitive.generateContent({
132
- model: _judgeModel,
133
- temperature: 0,
134
- responseFormat: "json_object",
135
- systemPrompt: buildJudgeSystemPrompt(threshold),
136
- messages: [
137
- {
138
- role: "user",
139
- content: `User message: ${context.userMessage}
140
-
141
- Bot response: ${botResponse}
142
-
143
- Criteria: ${criteria}`
144
- }
145
- ]
146
- });
147
- const rawContent = output?.choices?.[0]?.content;
148
- const content = typeof rawContent === "string" ? rawContent : null;
149
- if (!content) {
150
- _log.warn(`LLM judge returned an empty response for "${criteria}"`);
151
- return {
152
- assertion: `llm_judge: "${criteria}"`,
153
- pass: false,
154
- expected: criteria,
155
- actual: "FAILED \u2014 LLM judge returned empty response"
156
- };
157
- }
158
- const verdict = JSON.parse(content);
104
+ const { output } = await _zai.check({ userMessage: context.userMessage, botResponse }, criteria).result();
159
105
  return {
160
- assertion: `llm_judge: "${criteria}"`,
161
- pass: verdict.score >= threshold,
106
+ assertion,
107
+ pass: output.value,
162
108
  expected: criteria,
163
- actual: `Score ${verdict.score}/5 (threshold: ${threshold}) \u2014 ${verdict.reason}`
109
+ actual: `${output.value ? "Pass" : "Fail"} \u2014 ${output.explanation}`
164
110
  };
165
111
  } catch (err) {
166
112
  _log.warn(`LLM judge failed for "${criteria}": ${err.message}`);
167
113
  return {
168
- assertion: `llm_judge: "${criteria}"`,
169
- pass: false,
114
+ assertion,
115
+ pass: !_failClosed,
116
+ skipped: true,
170
117
  expected: criteria,
171
118
  actual: `FAILED \u2014 LLM judge unavailable: ${err.message}`
172
119
  };