gaunt-sloth 2.0.0-alpha.2 → 2.0.0-alpha.21

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 (127) hide show
  1. package/README.md +55 -5
  2. package/cli.js +43 -10
  3. package/dist/cli.js +33 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/adkEvalRunner.d.ts +66 -0
  6. package/dist/commands/adkEvalRunner.js +69 -0
  7. package/dist/commands/adkEvalRunner.js.map +1 -0
  8. package/dist/commands/agUiEvalRunner.d.ts +73 -0
  9. package/dist/commands/agUiEvalRunner.js +216 -0
  10. package/dist/commands/agUiEvalRunner.js.map +1 -0
  11. package/dist/commands/askCommand.d.ts +3 -2
  12. package/dist/commands/askCommand.js +26 -7
  13. package/dist/commands/askCommand.js.map +1 -1
  14. package/dist/commands/batchCommand.d.ts +78 -0
  15. package/dist/commands/batchCommand.js +245 -0
  16. package/dist/commands/batchCommand.js.map +1 -0
  17. package/dist/commands/cliOptionParsers.d.ts +12 -0
  18. package/dist/commands/cliOptionParsers.js +18 -0
  19. package/dist/commands/cliOptionParsers.js.map +1 -0
  20. package/dist/commands/commandIntrospection.d.ts +6 -6
  21. package/dist/commands/commandIntrospection.js +12 -12
  22. package/dist/commands/commandIntrospection.js.map +1 -1
  23. package/dist/commands/commandUtils.d.ts +16 -16
  24. package/dist/commands/commandUtils.js +20 -20
  25. package/dist/commands/commandUtils.js.map +1 -1
  26. package/dist/commands/configCommand.d.ts +24 -0
  27. package/dist/commands/configCommand.js +117 -0
  28. package/dist/commands/configCommand.js.map +1 -0
  29. package/dist/commands/configSetup.d.ts +1 -1
  30. package/dist/commands/configSetup.js +17 -2
  31. package/dist/commands/configSetup.js.map +1 -1
  32. package/dist/commands/evalCommand.d.ts +41 -0
  33. package/dist/commands/evalCommand.js +483 -0
  34. package/dist/commands/evalCommand.js.map +1 -0
  35. package/dist/commands/execCommand.js +4 -1
  36. package/dist/commands/execCommand.js.map +1 -1
  37. package/dist/commands/firstRunDialog.d.ts +39 -5
  38. package/dist/commands/firstRunDialog.js +72 -10
  39. package/dist/commands/firstRunDialog.js.map +1 -1
  40. package/dist/commands/getCommand.js +7 -7
  41. package/dist/commands/getCommand.js.map +1 -1
  42. package/dist/commands/historyCommand.d.ts +17 -0
  43. package/dist/commands/historyCommand.js +102 -0
  44. package/dist/commands/historyCommand.js.map +1 -0
  45. package/dist/commands/initCommand.d.ts +3 -0
  46. package/dist/commands/initCommand.js +9 -4
  47. package/dist/commands/initCommand.js.map +1 -1
  48. package/dist/commands/insightsCommand.d.ts +8 -0
  49. package/dist/commands/insightsCommand.js +32 -0
  50. package/dist/commands/insightsCommand.js.map +1 -0
  51. package/dist/commands/modelsCommand.d.ts +25 -0
  52. package/dist/commands/modelsCommand.js +96 -0
  53. package/dist/commands/modelsCommand.js.map +1 -0
  54. package/dist/commands/prCommand.js +14 -15
  55. package/dist/commands/prCommand.js.map +1 -1
  56. package/dist/commands/prDiscovery.d.ts +2 -2
  57. package/dist/commands/prDiscovery.js +27 -29
  58. package/dist/commands/prDiscovery.js.map +1 -1
  59. package/dist/commands/reviewCommand.js +15 -10
  60. package/dist/commands/reviewCommand.js.map +1 -1
  61. package/dist/commands/workflowCommand.d.ts +19 -0
  62. package/dist/commands/workflowCommand.js +77 -0
  63. package/dist/commands/workflowCommand.js.map +1 -0
  64. package/dist/helpers/jira/jiraLogWork.js.map +1 -1
  65. package/dist/modules/startSession.js +9 -7
  66. package/dist/modules/startSession.js.map +1 -1
  67. package/dist/tools/gthJiraLogWorkTool.js +3 -3
  68. package/dist/tools/gthJiraLogWorkTool.js.map +1 -1
  69. package/dist/tui/components/App.js +321 -21
  70. package/dist/tui/components/App.js.map +1 -1
  71. package/dist/tui/components/ApprovalPrompt.d.ts +18 -0
  72. package/dist/tui/components/ApprovalPrompt.js +26 -0
  73. package/dist/tui/components/ApprovalPrompt.js.map +1 -0
  74. package/dist/tui/components/DebugPanel.d.ts +29 -9
  75. package/dist/tui/components/DebugPanel.js +46 -23
  76. package/dist/tui/components/DebugPanel.js.map +1 -1
  77. package/dist/tui/components/FetchProgress.d.ts +36 -0
  78. package/dist/tui/components/FetchProgress.js +39 -0
  79. package/dist/tui/components/FetchProgress.js.map +1 -0
  80. package/dist/tui/components/LiveTurn.d.ts +26 -4
  81. package/dist/tui/components/LiveTurn.js +104 -18
  82. package/dist/tui/components/LiveTurn.js.map +1 -1
  83. package/dist/tui/components/NoticeBar.d.ts +32 -0
  84. package/dist/tui/components/NoticeBar.js +39 -0
  85. package/dist/tui/components/NoticeBar.js.map +1 -0
  86. package/dist/tui/components/PromptInput.d.ts +26 -1
  87. package/dist/tui/components/PromptInput.js +93 -7
  88. package/dist/tui/components/PromptInput.js.map +1 -1
  89. package/dist/tui/components/Rule.js.map +1 -1
  90. package/dist/tui/components/SelectList.d.ts +98 -9
  91. package/dist/tui/components/SelectList.js +209 -26
  92. package/dist/tui/components/SelectList.js.map +1 -1
  93. package/dist/tui/components/SlashCommandMenu.d.ts +20 -0
  94. package/dist/tui/components/SlashCommandMenu.js +28 -0
  95. package/dist/tui/components/SlashCommandMenu.js.map +1 -0
  96. package/dist/tui/components/StatusBar.d.ts +7 -1
  97. package/dist/tui/components/StatusBar.js +7 -3
  98. package/dist/tui/components/StatusBar.js.map +1 -1
  99. package/dist/tui/components/Transcript.js +6 -1
  100. package/dist/tui/components/Transcript.js.map +1 -1
  101. package/dist/tui/debugRender.d.ts +41 -6
  102. package/dist/tui/debugRender.js +126 -12
  103. package/dist/tui/debugRender.js.map +1 -1
  104. package/dist/tui/debugSearch.d.ts +40 -0
  105. package/dist/tui/debugSearch.js +73 -0
  106. package/dist/tui/debugSearch.js.map +1 -0
  107. package/dist/tui/markdown.js +0 -0
  108. package/dist/tui/markdown.js.map +1 -1
  109. package/dist/tui/pasteParser.d.ts +21 -0
  110. package/dist/tui/pasteParser.js +24 -0
  111. package/dist/tui/pasteParser.js.map +1 -0
  112. package/dist/tui/selectCancelled.d.ts +13 -0
  113. package/dist/tui/selectCancelled.js +17 -0
  114. package/dist/tui/selectCancelled.js.map +1 -0
  115. package/dist/tui/slashCommands.d.ts +220 -1
  116. package/dist/tui/slashCommands.js +408 -1
  117. package/dist/tui/slashCommands.js.map +1 -1
  118. package/dist/tui/tuiSessionModule.js +249 -24
  119. package/dist/tui/tuiSessionModule.js.map +1 -1
  120. package/dist/tui/types.d.ts +96 -7
  121. package/dist/tui/viewModel.d.ts +34 -0
  122. package/dist/tui/viewModel.js +67 -0
  123. package/dist/tui/viewModel.js.map +1 -1
  124. package/dist/utils/stdinPolicy.d.ts +31 -0
  125. package/dist/utils/stdinPolicy.js +37 -0
  126. package/dist/utils/stdinPolicy.js.map +1 -0
  127. package/package.json +21 -17
package/README.md CHANGED
@@ -39,11 +39,61 @@ This is the only package in the workspace that pulls in AI vendor dependencies d
39
39
  npm install -g gaunt-sloth
40
40
  ```
41
41
 
42
- For full usage documentation see the [root README](../../README.md) and [docs/COMMANDS.md](../../docs/COMMANDS.md).
42
+ For full usage documentation see the [root README](https://github.com/pukeko-robotics/gaunt-sloth/blob/main/README.md) and [docs/COMMANDS.md](https://github.com/pukeko-robotics/gaunt-sloth/blob/main/docs/COMMANDS.md).
43
+
44
+ ## ACP server (editor integration)
45
+
46
+ Gaunt Sloth can run as an [Agent Client Protocol](https://agentclientprotocol.com/) (ACP)
47
+ server, so an ACP host — Zed, JetBrains, a future Pukeko client — can spawn it as a coding
48
+ agent. It speaks ACP JSON-RPC over **stdio** (no port, no flags beyond the switch below); the
49
+ host launches it as a subprocess.
50
+
51
+ Run it through this package:
52
+
53
+ ```bash
54
+ npm install -g gaunt-sloth@alpha
55
+ gaunt-sloth --acp-agent
56
+ ```
57
+
58
+ **Install the app (`gaunt-sloth`), not `@gaunt-sloth/agent`.** The agent package also ships a
59
+ standalone `gaunt-sloth-acp` binary, but the LLM providers (`@langchain/anthropic`, `openai`,
60
+ `google`, …) are `peerDependencies` of `@gaunt-sloth/core` that **only this app package
61
+ declares as real dependencies**. A bare `@gaunt-sloth/agent` install leaves those peers unmet,
62
+ so its ACP server has no provider to build a model from. `gaunt-sloth --acp-agent` runs the
63
+ exact same ACP server but resolves providers out of the app's dependency tree, so every
64
+ configured provider works. (`stdout` is the protocol channel and is kept clean — gsloth's
65
+ status/config output is redirected to `stderr`.)
66
+
67
+ Provider credentials and model selection come from your usual gsloth config
68
+ (`.gsloth.config.*` / env vars such as `ANTHROPIC_API_KEY`); the ACP server reads them via the
69
+ same `initConfig` path as the CLI. Run the host from your project directory (or set its `cwd`)
70
+ so config and the per-session workspace resolve correctly.
71
+
72
+ ### Zed
73
+
74
+ Add to Zed `settings.json` (or Settings -> External Agents -> Add Agent -> Custom Agent):
75
+
76
+ ```json
77
+ {
78
+ "agent_servers": {
79
+ "Gaunt Sloth": {
80
+ "type": "custom",
81
+ "command": "gaunt-sloth",
82
+ "args": ["--acp-agent"],
83
+ "env": {}
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ Point `command` at the global `gaunt-sloth` binary (after `npm install -g gaunt-sloth@alpha`),
90
+ or at an absolute path to `cli.js` for a local build. Other ACP hosts (JetBrains, etc.) take
91
+ the same `command` + `args` pair.
43
92
 
44
93
  ## Related packages
45
94
 
46
- - [`@gaunt-sloth/core`](../core) — Core utilities, config, and agent infrastructure
47
- - [`@gaunt-sloth/agent`](../agent) — Agent runtime, built-in tools, API/AG-UI/ACP server
48
- - [`@gaunt-sloth/review`](../review) — Review and Q&A modules with standalone CLI
49
- - [`gaunt-sloth`](../app) — Main CLI application (this package)
95
+ - [`@gaunt-sloth/core`](https://www.npmjs.com/package/@gaunt-sloth/core) — Core utilities, config, and agent infrastructure ([source](https://github.com/pukeko-robotics/gaunt-sloth/tree/main/packages/core))
96
+ - [`@gaunt-sloth/agent`](https://www.npmjs.com/package/@gaunt-sloth/agent) — Agent runtime, built-in tools, API/AG-UI/ACP server ([source](https://github.com/pukeko-robotics/gaunt-sloth/tree/main/packages/agent))
97
+ - [`@gaunt-sloth/review`](https://www.npmjs.com/package/@gaunt-sloth/review) — Review and Q&A modules with standalone CLI ([source](https://github.com/pukeko-robotics/gaunt-sloth/tree/main/packages/review))
98
+ - [`@gaunt-sloth/batch`](https://www.npmjs.com/package/@gaunt-sloth/batch) — Batch / eval / workflow runtime ([source](https://github.com/pukeko-robotics/gaunt-sloth/tree/main/packages/batch))
99
+ - [`gaunt-sloth`](https://www.npmjs.com/package/gaunt-sloth) — Main CLI application (this package) ([source](https://github.com/pukeko-robotics/gaunt-sloth/tree/main/packages/app))
package/cli.js CHANGED
@@ -9,15 +9,48 @@ process.on('warning', (warning) => {
9
9
  console.warn(warning);
10
10
  });
11
11
 
12
- // This is a minimalistic entry point that sets the installDir in systemUtils
13
- // and delegates to the compiled TypeScript code in dist/cli.js
14
- import { setEntryPoint } from './dist/utils/systemUtils.js';
12
+ // --- ACP server bypass -----------------------------------------------------
13
+ // `gaunt-sloth --acp-agent` runs the Agent Client Protocol (ACP) server instead
14
+ // of the normal CLI, so an ACP host (Zed, JetBrains, a future Pukeko client) can
15
+ // spawn the fat `gaunt-sloth` package as a coding-agent subprocess.
16
+ //
17
+ // Why route ACP through the app rather than the standalone `gaunt-sloth-acp`
18
+ // binary in @gaunt-sloth/agent: the LLM providers (@langchain/anthropic, openai,
19
+ // google, …) are peerDependencies of @gaunt-sloth/core, and only this app package
20
+ // declares them as real dependencies. A bare `@gaunt-sloth/agent` install leaves
21
+ // those peers unmet, so its ACP server has no providers to construct a model from.
22
+ // Starting the same ACP server from here resolves providers out of the app's tree.
23
+ //
24
+ // ACP speaks JSON-RPC over stdio, so stdout MUST stay a clean protocol channel:
25
+ // redirect console.log/info (used by initConfig/status output) to stderr BEFORE
26
+ // touching config, exactly as the standalone `gaunt-sloth-acp` entry does.
27
+ if (process.argv.includes('--acp-agent')) {
28
+ for (const method of ['log', 'info']) {
29
+ console[method] = (...args) => process.stderr.write(args.join(' ') + '\n');
30
+ }
31
+ const [{ initConfig }, { displayError }, { startAcpServer }] = await Promise.all([
32
+ import('@gaunt-sloth/core/config.js'),
33
+ import('@gaunt-sloth/core/utils/consoleUtils.js'),
34
+ import('@gaunt-sloth/agent'),
35
+ ]);
36
+ try {
37
+ const config = await initConfig({});
38
+ await startAcpServer(config);
39
+ } catch (err) {
40
+ displayError(`ACP server failed: ${err instanceof Error ? err.message : String(err)}`);
41
+ process.exit(1);
42
+ }
43
+ } else {
44
+ // This is a minimalistic entry point that sets the installDir in systemUtils
45
+ // and delegates to the compiled TypeScript code in dist/cli.js
46
+ const { setEntryPoint } = await import('./dist/utils/systemUtils.js');
15
47
 
16
- // Set the installation directory in systemUtils
17
- setEntryPoint(import.meta.url);
48
+ // Set the installation directory in systemUtils
49
+ setEntryPoint(import.meta.url);
18
50
 
19
- // Import and run the compiled TypeScript code
20
- import('./dist/cli.js').catch((err) => {
21
- console.error('Failed to load application:', err);
22
- process.exit(1);
23
- });
51
+ // Import and run the compiled TypeScript code
52
+ import('./dist/cli.js').catch((err) => {
53
+ console.error('Failed to load application:', err);
54
+ process.exit(1);
55
+ });
56
+ }
package/dist/cli.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { Command, Option } from 'commander';
2
2
  import { askCommand } from '#src/commands/askCommand.js';
3
3
  import { execCommand } from '#src/commands/execCommand.js';
4
+ import { batchCommand } from '#src/commands/batchCommand.js';
5
+ import { evalCommand } from '#src/commands/evalCommand.js';
6
+ import { workflowCommand } from '#src/commands/workflowCommand.js';
4
7
  import { initCommand } from '#src/commands/initCommand.js';
5
8
  import { reviewCommand } from '#src/commands/reviewCommand.js';
6
9
  import { prCommand } from '#src/commands/prCommand.js';
@@ -8,12 +11,17 @@ import { chatCommand } from '#src/commands/chatCommand.js';
8
11
  import { codeCommand } from '#src/commands/codeCommand.js';
9
12
  import { apiCommand } from '#src/commands/apiCommand.js';
10
13
  import { getCommand } from '#src/commands/getCommand.js';
14
+ import { configCommand } from '#src/commands/configCommand.js';
15
+ import { historyCommand } from '#src/commands/historyCommand.js';
16
+ import { insightsCommand } from '#src/commands/insightsCommand.js';
17
+ import { modelsCommand } from '#src/commands/modelsCommand.js';
11
18
  import { argv, getSlothVersion, readStdin } from '@gaunt-sloth/core/utils/systemUtils.js';
19
+ import { commandSkipsStdin, resolveInvokedCommandName } from '#src/utils/stdinPolicy.js';
12
20
  import { coerceBooleanOrString } from '@gaunt-sloth/core/utils/consoleUtils.js';
13
21
  const program = new Command();
14
22
  program
15
23
  .name('gsloth')
16
- .description('Gaunt Sloth Assistant reviewing your PRs')
24
+ .description('Gaunt Sloth reviewing your PRs')
17
25
  .version(getSlothVersion())
18
26
  .option('--verbose', 'Set LangChain/LangGraph to verbose mode, ' +
19
27
  'causing LangChain/LangGraph to log many details to the console. ' +
@@ -23,7 +31,8 @@ program
23
31
  .option('-w, --write-output-to-file <value>', 'Write output to file. Accepts true/false or a filename. Shortcuts: -wn or -w0 for false.')
24
32
  .option('--tui', 'Force the interactive Ink TUI for chat/code sessions (overrides CI auto-off)')
25
33
  .option('--no-tui', 'Force the plain readline session for chat/code (disable the TUI)')
26
- .addOption(new Option('--nopipe').hideHelp(true));
34
+ .addOption(new Option('--nopipe').hideHelp(true))
35
+ .addOption(new Option('--no-pipe').hideHelp(true));
27
36
  const cliConfigOverrides = {};
28
37
  // Parse global options before binding any commands
29
38
  program.parseOptions(argv);
@@ -62,9 +71,31 @@ reviewCommand(program, cliConfigOverrides);
62
71
  prCommand(program, cliConfigOverrides);
63
72
  askCommand(program, cliConfigOverrides);
64
73
  execCommand(program, cliConfigOverrides);
74
+ batchCommand(program, cliConfigOverrides);
75
+ evalCommand(program, cliConfigOverrides);
76
+ workflowCommand(program, cliConfigOverrides);
65
77
  chatCommand(program, cliConfigOverrides);
66
78
  codeCommand(program, cliConfigOverrides);
67
79
  apiCommand(program, cliConfigOverrides);
68
80
  getCommand(program, cliConfigOverrides);
81
+ configCommand(program, cliConfigOverrides);
82
+ // GS2-7 (B20) — read-only, local history/insights surfaces. They resolve their own DB path (global
83
+ // default or --db) and do not build the LLM, so they stay decoupled from config/provider setup.
84
+ historyCommand(program);
85
+ insightsCommand(program);
86
+ // GS2-6 (B16) — model catalog: lists providers/models enriched with models.dev cost/limit metadata.
87
+ // Read-only; enrichment never gates what `/v1/models` reports as callable.
88
+ modelsCommand(program);
89
+ // BATCH-11 (#405 gotcha #5): `eval`/`batch` never consume piped stdin, so they must not block
90
+ // waiting for stdin EOF before dispatch — a scripted/CI `gth eval suite.yaml` inherits a non-TTY,
91
+ // non-closing stdin and would otherwise hang until EOF (or need `</dev/null`). Resolve the invoked
92
+ // subcommand from argv via commander's own operand parsing (so a `-c <path>` value or a file
93
+ // argument is never mistaken for the command name) and, for those commands, imply the existing
94
+ // `--no-pipe` fast path in readStdin. ask/review/pr etc. are untouched and still block-and-read a
95
+ // piped diff.
96
+ const invokedCommand = resolveInvokedCommandName(program.commands.map((command) => command.name()), program.parseOptions(argv).operands);
97
+ if (commandSkipsStdin(invokedCommand)) {
98
+ program.setOptionValue('nopipe', true);
99
+ }
69
100
  await readStdin(program);
70
101
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @module adkEvalRunner
3
+ * BATCH-14 — the ADK (A2A) target's production runner builders for `gth eval`. The eval runner
4
+ * ({@link @gaunt-sloth/batch#runEvalSuite}) is target-agnostic: it consumes an injected
5
+ * {@link RunCellFn} (single-shot) and {@link RunConversationFn} (multi-turn) and grades whatever
6
+ * `answer` they produce with the SAME assertion surface used for the `gth-agent` target. This module
7
+ * builds those two functions for an EXTERNAL Google ADK agent, driving it over the A2A protocol via
8
+ * {@link A2AClientWrapper} (the existing `@a2a-js/sdk` wrapper) — the analogue of `batchCommand.ts`'s
9
+ * `buildProductionRunCell`/`buildProductionRunConversation` for the in-process gth agent.
10
+ *
11
+ * The A2A client is INJECTABLE (like `RunCellFn`/`RunConversationFn` themselves) so unit tests drive
12
+ * these builders against a FAKE client with no network and no live ADK agent — the live end-to-end
13
+ * validation is a separate node (BATCH-16).
14
+ *
15
+ * Honest boundaries (BATCH-14 design point 4): A2A exposes text (and file/data) parts plus task
16
+ * status/artifact events, but NOT the agent's intermediate tool/function calls — so the ADK outcomes
17
+ * never populate `tools`, and `must_call`/`must_not_call` are rejected at suite-parse time rather
18
+ * than silently passed. A2A likewise carries no token accounting, so `tokensInput`/`tokensOutput`
19
+ * are left unset (undefined), unlike the gth-agent path.
20
+ */
21
+ import type { AdkAgentTarget, RunCellFn, RunConversationFn } from '@gaunt-sloth/batch';
22
+ /** One A2A turn's result as the ADK runner needs it: the answer text plus the continuity handles to
23
+ * thread into follow-up turns. */
24
+ export interface AdkA2AResponse {
25
+ text: string;
26
+ contextId?: string;
27
+ taskId?: string;
28
+ }
29
+ /**
30
+ * The minimal A2A client the ADK runner depends on — a single `sendMessage(text, context?)` that
31
+ * returns text + continuity handles. Production wraps {@link A2AClientWrapper.sendMessageWithContext};
32
+ * tests inject a fake implementing exactly this. Kept deliberately narrow so a fake is trivial and
33
+ * the runner never reaches into the full SDK surface.
34
+ */
35
+ export interface AdkA2AClient {
36
+ sendMessage(text: string, context?: {
37
+ contextId?: string;
38
+ taskId?: string;
39
+ }): Promise<AdkA2AResponse>;
40
+ }
41
+ /** Builds an {@link AdkA2AClient} for a target — the injection seam for tests. */
42
+ export type AdkClientFactory = (target: AdkAgentTarget) => AdkA2AClient;
43
+ /** Production factory: a real {@link A2AClientWrapper} over the target's A2A endpoint, adapted to the
44
+ * narrow {@link AdkA2AClient} shape via its context-threading {@link A2AClientWrapper.sendMessageWithContext}. */
45
+ export declare const defaultAdkClientFactory: AdkClientFactory;
46
+ /**
47
+ * Build the injectable single-shot {@link RunCellFn} that drives ONE ADK agent turn over A2A: send
48
+ * the cell's prompt as the A2A message text and return the agent's text as the cell `answer`. A
49
+ * transport/agent error is contained as a failed cell (`ok:false`) so one bad case can never take the
50
+ * whole suite down — matching `buildProductionRunCell`'s discipline for the gth-agent path.
51
+ *
52
+ * `tools`/tokens are intentionally unset (A2A exposes neither); content assertions grade the answer.
53
+ */
54
+ export declare function buildAdkRunCell(target: AdkAgentTarget, createClient?: AdkClientFactory): RunCellFn;
55
+ /**
56
+ * Build the injectable multi-turn {@link RunConversationFn} that drives a whole scripted conversation
57
+ * against the ADK agent over A2A, threading the A2A `contextId` across turns so turn N sees turn
58
+ * N-1's context (an ADK agent keeps conversational memory by `contextId`). ONE client is built for
59
+ * the conversation; the first turn sends no context, and each subsequent turn carries the `contextId`
60
+ * the previous response returned.
61
+ *
62
+ * Returns one {@link TurnRunOutcome} per turn attempted. A turn that throws is recorded as a failed
63
+ * turn and ABORTS the conversation (the returned array is short) — the runner fails the un-run turns
64
+ * with a clear reason, exactly as it does for a gth-agent conversation that ended early.
65
+ */
66
+ export declare function buildAdkRunConversation(target: AdkAgentTarget, createClient?: AdkClientFactory): RunConversationFn;
@@ -0,0 +1,69 @@
1
+ import { A2AClientWrapper } from '#src/modules/a2a/A2AClientWrapper.js';
2
+ /** Production factory: a real {@link A2AClientWrapper} over the target's A2A endpoint, adapted to the
3
+ * narrow {@link AdkA2AClient} shape via its context-threading {@link A2AClientWrapper.sendMessageWithContext}. */
4
+ export const defaultAdkClientFactory = (target) => {
5
+ const wrapper = new A2AClientWrapper({
6
+ agentId: target.agentId ?? 'adk-agent',
7
+ agentUrl: target.url,
8
+ });
9
+ return {
10
+ sendMessage: (text, context) => wrapper.sendMessageWithContext(text, context),
11
+ };
12
+ };
13
+ /**
14
+ * Build the injectable single-shot {@link RunCellFn} that drives ONE ADK agent turn over A2A: send
15
+ * the cell's prompt as the A2A message text and return the agent's text as the cell `answer`. A
16
+ * transport/agent error is contained as a failed cell (`ok:false`) so one bad case can never take the
17
+ * whole suite down — matching `buildProductionRunCell`'s discipline for the gth-agent path.
18
+ *
19
+ * `tools`/tokens are intentionally unset (A2A exposes neither); content assertions grade the answer.
20
+ */
21
+ export function buildAdkRunCell(target, createClient = defaultAdkClientFactory) {
22
+ return async (cell) => {
23
+ try {
24
+ const client = createClient(target);
25
+ const { text } = await client.sendMessage(cell.content);
26
+ return { ok: true, answer: text };
27
+ }
28
+ catch (error) {
29
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
30
+ }
31
+ };
32
+ }
33
+ /**
34
+ * Build the injectable multi-turn {@link RunConversationFn} that drives a whole scripted conversation
35
+ * against the ADK agent over A2A, threading the A2A `contextId` across turns so turn N sees turn
36
+ * N-1's context (an ADK agent keeps conversational memory by `contextId`). ONE client is built for
37
+ * the conversation; the first turn sends no context, and each subsequent turn carries the `contextId`
38
+ * the previous response returned.
39
+ *
40
+ * Returns one {@link TurnRunOutcome} per turn attempted. A turn that throws is recorded as a failed
41
+ * turn and ABORTS the conversation (the returned array is short) — the runner fails the un-run turns
42
+ * with a clear reason, exactly as it does for a gth-agent conversation that ended early.
43
+ */
44
+ export function buildAdkRunConversation(target, createClient = defaultAdkClientFactory) {
45
+ return async (userMessages) => {
46
+ const client = createClient(target);
47
+ const outcomes = [];
48
+ let contextId;
49
+ for (const message of userMessages) {
50
+ try {
51
+ const response = await client.sendMessage(message, contextId ? { contextId } : undefined);
52
+ // Carry the server's contextId forward so the next turn continues the same conversation.
53
+ if (response.contextId)
54
+ contextId = response.contextId;
55
+ outcomes.push({ ok: true, answer: response.text });
56
+ }
57
+ catch (error) {
58
+ outcomes.push({
59
+ ok: false,
60
+ error: error instanceof Error ? error.message : String(error),
61
+ });
62
+ // A failed turn aborts the conversation — the runner marks the remaining turns FAILed.
63
+ break;
64
+ }
65
+ }
66
+ return outcomes;
67
+ };
68
+ }
69
+ //# sourceMappingURL=adkEvalRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adkEvalRunner.js","sourceRoot":"","sources":["../../src/commands/adkEvalRunner.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AA0BxE;kHACkH;AAClH,MAAM,CAAC,MAAM,uBAAuB,GAAqB,CAAC,MAAM,EAAE,EAAE;IAClE,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC;QACnC,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,WAAW;QACtC,QAAQ,EAAE,MAAM,CAAC,GAAG;KACrB,CAAC,CAAC;IACH,OAAO;QACL,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC;KAC9E,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAsB,EACtB,YAAY,GAAqB,uBAAuB;IAExD,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE;QACpB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACxD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACtF,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAsB,EACtB,YAAY,GAAqB,uBAAuB;IAExD,OAAO,KAAK,EAAE,YAAY,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAqB,EAAE,CAAC;QACtC,IAAI,SAA6B,CAAC;QAElC,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;YACnC,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBAC1F,yFAAyF;gBACzF,IAAI,QAAQ,CAAC,SAAS;oBAAE,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;gBACvD,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACrD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CAAC;oBACZ,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;iBAC9D,CAAC,CAAC;gBACH,uFAAuF;gBACvF,MAAM;YACR,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,73 @@
1
+ import type { AgUiAgentTarget, RunCellFn, RunConversationFn } from '@gaunt-sloth/batch';
2
+ /** One AG-UI message as sent in the `RunAgentInput.messages` array. The eval only ever sends `user`
3
+ * turns and, for conversation continuity, the `assistant` answers it assembled — the minimal shape
4
+ * the reference server's `convertMessage` consumes (`{ id, role, content }`). */
5
+ export interface AgUiMessage {
6
+ id: string;
7
+ role: 'user' | 'assistant';
8
+ content: string;
9
+ }
10
+ /** The `RunAgentInput` fields the runner drives per request. `threadId` provides conversation
11
+ * continuity (stable across the turns of ONE conversation); `runId` is fresh per request. */
12
+ export interface AgUiRunInput {
13
+ threadId: string;
14
+ runId: string;
15
+ messages: AgUiMessage[];
16
+ }
17
+ /** One AG-UI run's decoded result: the answer assembled from `TEXT_MESSAGE_CONTENT` deltas and the
18
+ * tool names captured from `TOOL_CALL_START` events (empty array when the agent called no tools —
19
+ * NOT undefined, so `must_not_call` grades against a real, present trace). */
20
+ export interface AgUiRunResult {
21
+ answer: string;
22
+ tools: string[];
23
+ }
24
+ /**
25
+ * The minimal AG-UI client the runner depends on — a single `run(input)` that POSTs one
26
+ * `RunAgentInput` and returns the decoded answer + tools. Production wraps {@link createAgUiClient};
27
+ * tests inject a fake implementing exactly this. Kept deliberately narrow so a fake is trivial and
28
+ * the runner never reaches into transport details.
29
+ */
30
+ export interface AgUiClient {
31
+ run(input: AgUiRunInput): Promise<AgUiRunResult>;
32
+ }
33
+ /** Builds an {@link AgUiClient} for a target — the injection seam for tests. */
34
+ export type AgUiClientFactory = (target: AgUiAgentTarget) => AgUiClient;
35
+ /** The `fetch` seam — global `fetch` in production, a fake in tests (so the real POST-body
36
+ * construction + SSE decode are exercised with no network). */
37
+ export type FetchLike = typeof fetch;
38
+ /**
39
+ * Create a real {@link AgUiClient} that drives the target's AG-UI run endpoint over HTTP/SSE. Each
40
+ * `run` POSTs a `RunAgentInput` to `{url}/agents/{agentId}/run` and decodes the SSE response via
41
+ * {@link decodeAgUiStream}. A non-2xx response, an absent body, a `RUN_ERROR` event, or a truncated
42
+ * stream all throw — the runner builders contain that into a failed cell (`ok:false`).
43
+ *
44
+ * `fetchImpl` defaults to global `fetch`; tests pass a fake returning a synthetic SSE `Response` so
45
+ * the body construction + decode are exercised without a network or a live server.
46
+ */
47
+ export declare function createAgUiClient(target: AgUiAgentTarget, fetchImpl?: FetchLike): AgUiClient;
48
+ /** Production factory: a real HTTP/SSE {@link AgUiClient} over the target's AG-UI endpoint. */
49
+ export declare const defaultAgUiClientFactory: AgUiClientFactory;
50
+ /**
51
+ * Build the injectable single-shot {@link RunCellFn} that drives ONE AG-UI run: send the cell's
52
+ * prompt as the sole `user` message (fresh `threadId`/`runId`) and return the agent's assembled text
53
+ * as the cell `answer` plus the tool names captured from the stream as `tools`. A transport/stream
54
+ * error is contained as a failed cell (`ok:false`) so one bad case can never take the whole suite
55
+ * down — matching `buildProductionRunCell`'s discipline for the gth-agent path.
56
+ *
57
+ * `tools` is ALWAYS populated (possibly `[]`) — the AG-UI wire exposes the trace — so
58
+ * `must_call`/`must_not_call` grade normally, the key difference from the ADK target.
59
+ */
60
+ export declare function buildAgUiRunCell(target: AgUiAgentTarget, createClient?: AgUiClientFactory): RunCellFn;
61
+ /**
62
+ * Build the injectable multi-turn {@link RunConversationFn} that drives a whole scripted conversation
63
+ * against the AG-UI agent, threading continuity via the `messages` array + a STABLE `threadId` across
64
+ * the turns of ONE conversation: each turn appends its `user` message, POSTs the accumulated history
65
+ * under the same `threadId`, then appends the assistant's answer so the next turn's request carries
66
+ * it. ONE `threadId` for the whole conversation; a fresh `runId` per turn.
67
+ *
68
+ * Returns one {@link TurnRunOutcome} per turn attempted (answer + per-turn tool trace). A turn that
69
+ * throws is recorded as a failed turn and ABORTS the conversation (the returned array is short) — the
70
+ * runner fails the un-run turns with a clear reason, exactly as it does for a gth-agent conversation
71
+ * that ended early.
72
+ */
73
+ export declare function buildAgUiRunConversation(target: AgUiAgentTarget, createClient?: AgUiClientFactory): RunConversationFn;
@@ -0,0 +1,216 @@
1
+ /**
2
+ * @module agUiEvalRunner
3
+ * BATCH-15 — the AG-UI target's production runner builders for `gth eval`. The eval runner
4
+ * ({@link @gaunt-sloth/batch#runEvalSuite}) is target-agnostic: it consumes an injected
5
+ * {@link RunCellFn} (single-shot) and {@link RunConversationFn} (multi-turn) and grades whatever
6
+ * `answer` (and `tools`) they produce with the SAME assertion surface used for the `gth-agent`
7
+ * target. This module builds those two functions for an EXTERNAL agent exposed over the AG-UI
8
+ * protocol, driving its HTTP/SSE run endpoint — the analogue of `batchCommand.ts`'s
9
+ * `buildProductionRunCell`/`buildProductionRunConversation` (in-process gth agent) and of
10
+ * `adkEvalRunner.ts` (external ADK agent over A2A).
11
+ *
12
+ * The transport is INJECTABLE (like `RunCellFn`/`RunConversationFn` themselves) so unit tests drive
13
+ * these builders against a FAKE client — or the real decoder against a FAKE `fetch` — with no
14
+ * network and no live AG-UI server. The live end-to-end validation against a running `gth api ag-ui`
15
+ * server is a separate node (BATCH-17).
16
+ *
17
+ * KEY DIFFERENCE from the ADK (A2A) runner: the AG-UI wire DOES stream the agent's tool calls
18
+ * (`TOOL_CALL_START`). So this runner CAPTURES each `TOOL_CALL_START`'s `toolCallName` into the
19
+ * outcome's `tools`, and `must_call`/`must_not_call` grade normally — unlike the adk-agent target,
20
+ * where the tool trace is invisible and those assertions are rejected at parse time. AG-UI carries
21
+ * no token accounting either, so `tokensInput`/`tokensOutput` are left unset (undefined).
22
+ *
23
+ * The wire contract (see the reference endpoint, `@gaunt-sloth/api`'s `apiAgUiModule.ts`):
24
+ * - Request: `POST {url}/agents/{agentId}/run` with a `RunAgentInput` body
25
+ * `{ threadId, runId, messages, tools, forwardedProps }`.
26
+ * - Response: an SSE stream of AG-UI events. We decode `TEXT_MESSAGE_CONTENT` deltas into the
27
+ * `answer`, `TOOL_CALL_START` names into `tools`, and treat `RUN_ERROR` (or a non-200, or a stream
28
+ * that ends without the terminal `RUN_FINISHED`) as a failed run — a gradeable `ok:false`, never
29
+ * an uncaught throw that aborts the whole suite.
30
+ */
31
+ import { randomUUID } from 'node:crypto';
32
+ import { EventType } from '@ag-ui/core';
33
+ /**
34
+ * Decode an AG-UI SSE stream into an {@link AgUiRunResult}. The reference encoder frames each event
35
+ * as `data: <json>\n\n` (`@ag-ui/encoder`), so we split on the blank-line delimiter, JSON-parse each
36
+ * frame's `data:` payload, and fold the relevant event types:
37
+ * - `TEXT_MESSAGE_CONTENT` → append `delta` to the answer.
38
+ * - `TOOL_CALL_START` → capture `toolCallName` into `tools`.
39
+ * - `RUN_ERROR` → remember the message; the run FAILED.
40
+ * - `RUN_FINISHED` → the terminal success signal.
41
+ *
42
+ * Throws on `RUN_ERROR` OR on a stream that ends without a terminal `RUN_FINISHED` (a truncated /
43
+ * malformed stream is a failed run, not an "empty answer" success). Non-JSON frames (SSE comments /
44
+ * keep-alives) are ignored. The throw is contained by the runner builders below into `ok:false`.
45
+ */
46
+ async function decodeAgUiStream(body) {
47
+ const decoder = new TextDecoder();
48
+ let buffer = '';
49
+ let answer = '';
50
+ const tools = [];
51
+ let runError;
52
+ let sawRunFinished = false;
53
+ const handleFrame = (frame) => {
54
+ const payload = frame
55
+ .split(/\r?\n/)
56
+ .filter((line) => line.startsWith('data:'))
57
+ .map((line) => line.slice('data:'.length).trim())
58
+ .join('\n');
59
+ if (!payload)
60
+ return;
61
+ let event;
62
+ try {
63
+ event = JSON.parse(payload);
64
+ }
65
+ catch {
66
+ // Non-JSON frame (SSE comment / keep-alive) — ignore.
67
+ return;
68
+ }
69
+ switch (event.type) {
70
+ case EventType.TEXT_MESSAGE_CONTENT:
71
+ if (typeof event.delta === 'string')
72
+ answer += event.delta;
73
+ break;
74
+ case EventType.TOOL_CALL_START:
75
+ if (typeof event.toolCallName === 'string')
76
+ tools.push(event.toolCallName);
77
+ break;
78
+ case EventType.RUN_ERROR:
79
+ runError = typeof event.message === 'string' ? event.message : 'unknown run error';
80
+ break;
81
+ case EventType.RUN_FINISHED:
82
+ sawRunFinished = true;
83
+ break;
84
+ }
85
+ };
86
+ for await (const chunk of body) {
87
+ buffer += decoder.decode(chunk, { stream: true });
88
+ let sep;
89
+ while ((sep = buffer.indexOf('\n\n')) !== -1) {
90
+ handleFrame(buffer.slice(0, sep));
91
+ buffer = buffer.slice(sep + 2);
92
+ }
93
+ }
94
+ buffer += decoder.decode();
95
+ if (buffer.trim().length > 0)
96
+ handleFrame(buffer);
97
+ if (runError !== undefined) {
98
+ throw new Error(`AG-UI run error: ${runError}`);
99
+ }
100
+ if (!sawRunFinished) {
101
+ throw new Error('AG-UI stream ended without a terminal RUN_FINISHED event (truncated or malformed stream).');
102
+ }
103
+ return { answer, tools };
104
+ }
105
+ /**
106
+ * Create a real {@link AgUiClient} that drives the target's AG-UI run endpoint over HTTP/SSE. Each
107
+ * `run` POSTs a `RunAgentInput` to `{url}/agents/{agentId}/run` and decodes the SSE response via
108
+ * {@link decodeAgUiStream}. A non-2xx response, an absent body, a `RUN_ERROR` event, or a truncated
109
+ * stream all throw — the runner builders contain that into a failed cell (`ok:false`).
110
+ *
111
+ * `fetchImpl` defaults to global `fetch`; tests pass a fake returning a synthetic SSE `Response` so
112
+ * the body construction + decode are exercised without a network or a live server.
113
+ */
114
+ export function createAgUiClient(target, fetchImpl = fetch) {
115
+ const base = target.url.replace(/\/+$/, '');
116
+ const endpoint = `${base}/agents/${encodeURIComponent(target.agentId)}/run`;
117
+ return {
118
+ async run(input) {
119
+ const response = await fetchImpl(endpoint, {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
122
+ body: JSON.stringify({
123
+ threadId: input.threadId,
124
+ runId: input.runId,
125
+ messages: input.messages,
126
+ // The eval declares no client-fulfilled frontend tools; an empty array makes the server
127
+ // serve the run from its own statically-configured agent.
128
+ tools: [],
129
+ forwardedProps: {},
130
+ }),
131
+ });
132
+ if (!response.ok) {
133
+ throw new Error(`AG-UI server responded ${response.status} ${response.statusText || ''}`.trim());
134
+ }
135
+ if (!response.body) {
136
+ throw new Error('AG-UI server returned no response body to stream.');
137
+ }
138
+ return decodeAgUiStream(response.body);
139
+ },
140
+ };
141
+ }
142
+ /** Production factory: a real HTTP/SSE {@link AgUiClient} over the target's AG-UI endpoint. */
143
+ export const defaultAgUiClientFactory = (target) => createAgUiClient(target);
144
+ /**
145
+ * Build the injectable single-shot {@link RunCellFn} that drives ONE AG-UI run: send the cell's
146
+ * prompt as the sole `user` message (fresh `threadId`/`runId`) and return the agent's assembled text
147
+ * as the cell `answer` plus the tool names captured from the stream as `tools`. A transport/stream
148
+ * error is contained as a failed cell (`ok:false`) so one bad case can never take the whole suite
149
+ * down — matching `buildProductionRunCell`'s discipline for the gth-agent path.
150
+ *
151
+ * `tools` is ALWAYS populated (possibly `[]`) — the AG-UI wire exposes the trace — so
152
+ * `must_call`/`must_not_call` grade normally, the key difference from the ADK target.
153
+ */
154
+ export function buildAgUiRunCell(target, createClient = defaultAgUiClientFactory) {
155
+ return async (cell) => {
156
+ try {
157
+ const client = createClient(target);
158
+ const result = await client.run({
159
+ threadId: randomUUID(),
160
+ runId: randomUUID(),
161
+ messages: [{ id: randomUUID(), role: 'user', content: cell.content }],
162
+ });
163
+ return { ok: true, answer: result.answer, tools: result.tools };
164
+ }
165
+ catch (error) {
166
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
167
+ }
168
+ };
169
+ }
170
+ /**
171
+ * Build the injectable multi-turn {@link RunConversationFn} that drives a whole scripted conversation
172
+ * against the AG-UI agent, threading continuity via the `messages` array + a STABLE `threadId` across
173
+ * the turns of ONE conversation: each turn appends its `user` message, POSTs the accumulated history
174
+ * under the same `threadId`, then appends the assistant's answer so the next turn's request carries
175
+ * it. ONE `threadId` for the whole conversation; a fresh `runId` per turn.
176
+ *
177
+ * Returns one {@link TurnRunOutcome} per turn attempted (answer + per-turn tool trace). A turn that
178
+ * throws is recorded as a failed turn and ABORTS the conversation (the returned array is short) — the
179
+ * runner fails the un-run turns with a clear reason, exactly as it does for a gth-agent conversation
180
+ * that ended early.
181
+ */
182
+ export function buildAgUiRunConversation(target, createClient = defaultAgUiClientFactory) {
183
+ return async (userMessages) => {
184
+ const client = createClient(target);
185
+ const outcomes = [];
186
+ // One stable thread for the whole conversation (continuity), plus the accumulated history the
187
+ // server replays each turn (its `add_messages` reducer dedupes by id, so re-sending is safe).
188
+ const threadId = randomUUID();
189
+ const messages = [];
190
+ for (const userMessage of userMessages) {
191
+ messages.push({ id: randomUUID(), role: 'user', content: userMessage });
192
+ try {
193
+ const result = await client.run({
194
+ threadId,
195
+ runId: randomUUID(),
196
+ // Snapshot the history so the caller/test sees exactly what THIS turn sent, unaffected by
197
+ // later mutation.
198
+ messages: [...messages],
199
+ });
200
+ // Thread the assistant's answer into the history so the next turn carries it (memory).
201
+ messages.push({ id: randomUUID(), role: 'assistant', content: result.answer });
202
+ outcomes.push({ ok: true, answer: result.answer, tools: result.tools });
203
+ }
204
+ catch (error) {
205
+ outcomes.push({
206
+ ok: false,
207
+ error: error instanceof Error ? error.message : String(error),
208
+ });
209
+ // A failed turn aborts the conversation — the runner marks the remaining turns FAILed.
210
+ break;
211
+ }
212
+ }
213
+ return outcomes;
214
+ };
215
+ }
216
+ //# sourceMappingURL=agUiEvalRunner.js.map