@botbotgo/agent-harness 0.0.42 → 0.0.44

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.
@@ -8,6 +8,152 @@ export function escapeHtml(value) {
8
8
  .replaceAll('"', """)
9
9
  .replaceAll("'", "'");
10
10
  }
11
+ /** CSS class for anchors that should open in the host app embedded browser (Wallee). */
12
+ export const WALLEE_OUTPUT_LINK_CLASS = "wallee-output-link";
13
+ /** `data-wallee-url` — target URL for the embedded browser (http/https only). */
14
+ export const WALLEE_BROWSER_URL_ATTR = "data-wallee-url";
15
+ export function isAllowedWalleeBrowserUrl(url) {
16
+ try {
17
+ const u = new URL(url);
18
+ return u.protocol === "http:" || u.protocol === "https:";
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ function walleeOutputAnchor(url, labelEscaped) {
25
+ return `<a class="${WALLEE_OUTPUT_LINK_CLASS}" ${WALLEE_BROWSER_URL_ATTR}="${escapeHtml(url)}" href="#">${labelEscaped}</a>`;
26
+ }
27
+ /**
28
+ * Escape plain text and wrap http(s) URLs in Wallee output anchors (open in host embedded browser).
29
+ */
30
+ export function linkifyPlainTextForWalleeBrowser(text) {
31
+ const urlRe = /\bhttps?:\/\/[^\s<>"']+/g;
32
+ const parts = [];
33
+ let last = 0;
34
+ let m;
35
+ while ((m = urlRe.exec(text)) !== null) {
36
+ parts.push(escapeHtml(text.slice(last, m.index)));
37
+ const raw = m[0];
38
+ const trimmed = raw.replace(/[.,;:!?)\]]+$/u, "");
39
+ const rest = raw.slice(trimmed.length);
40
+ if (isAllowedWalleeBrowserUrl(trimmed)) {
41
+ parts.push(walleeOutputAnchor(trimmed, escapeHtml(trimmed)));
42
+ if (rest) {
43
+ parts.push(escapeHtml(rest));
44
+ }
45
+ }
46
+ else {
47
+ parts.push(escapeHtml(raw));
48
+ }
49
+ last = m.index + raw.length;
50
+ }
51
+ parts.push(escapeHtml(text.slice(last)));
52
+ return parts.join("");
53
+ }
54
+ function applyBasicInlineMarkdown(escaped) {
55
+ return escaped
56
+ .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
57
+ .replace(/\*(.+?)\*/g, "<em>$1</em>")
58
+ .replace(/`([^`]+)`/g, "<code>$1</code>");
59
+ }
60
+ function linkifyPlainTextSegmentWithWalleeMarkdown(text) {
61
+ const mdLinkPattern = /\[([^\]]*)\]\((https?:\/\/[^)\s]+)\)/g;
62
+ const segments = [];
63
+ let lastIndex = 0;
64
+ let m;
65
+ while ((m = mdLinkPattern.exec(text)) !== null) {
66
+ if (m.index > lastIndex) {
67
+ segments.push({ type: "text", text: text.slice(lastIndex, m.index) });
68
+ }
69
+ segments.push({ type: "mdlink", label: m[1], url: m[2] });
70
+ lastIndex = m.index + m[0].length;
71
+ }
72
+ if (lastIndex < text.length) {
73
+ segments.push({ type: "text", text: text.slice(lastIndex) });
74
+ }
75
+ if (segments.length === 0) {
76
+ segments.push({ type: "text", text });
77
+ }
78
+ return segments
79
+ .map((seg) => {
80
+ if (seg.type === "mdlink") {
81
+ if (!isAllowedWalleeBrowserUrl(seg.url)) {
82
+ return `${escapeHtml(seg.label)} (${escapeHtml(seg.url)})`;
83
+ }
84
+ return walleeOutputAnchor(seg.url, escapeHtml(seg.label));
85
+ }
86
+ return linkifyBareUrlsWithInlineMarkdown(seg.text);
87
+ })
88
+ .join("");
89
+ }
90
+ function linkifyBareUrlsWithInlineMarkdown(text) {
91
+ const urlRe = /\bhttps?:\/\/[^\s<>"']+/g;
92
+ const parts = [];
93
+ let last = 0;
94
+ let m;
95
+ while ((m = urlRe.exec(text)) !== null) {
96
+ parts.push(applyBasicInlineMarkdown(escapeHtml(text.slice(last, m.index))));
97
+ const raw = m[0];
98
+ const trimmed = raw.replace(/[.,;:!?)\]]+$/u, "");
99
+ const rest = raw.slice(trimmed.length);
100
+ if (isAllowedWalleeBrowserUrl(trimmed)) {
101
+ parts.push(walleeOutputAnchor(trimmed, escapeHtml(trimmed)));
102
+ if (rest) {
103
+ parts.push(applyBasicInlineMarkdown(escapeHtml(rest)));
104
+ }
105
+ }
106
+ else {
107
+ parts.push(applyBasicInlineMarkdown(escapeHtml(raw)));
108
+ }
109
+ last = m.index + raw.length;
110
+ }
111
+ parts.push(applyBasicInlineMarkdown(escapeHtml(text.slice(last))));
112
+ return parts.join("");
113
+ }
114
+ /**
115
+ * Like {@link markdownToHtml} but inline http(s) URLs and markdown links `[label](https://…)` render as
116
+ * Wallee embedded-browser anchors (`wallee-output-link` + `data-wallee-url`).
117
+ */
118
+ export function markdownToWalleeOutputHtml(markdown) {
119
+ const normalized = markdown.replace(/\r\n/g, "\n");
120
+ const blocks = normalized.split(/\n\n+/);
121
+ const html = [];
122
+ for (const block of blocks) {
123
+ const trimmed = block.trim();
124
+ if (!trimmed) {
125
+ continue;
126
+ }
127
+ if (trimmed.startsWith("```") && trimmed.endsWith("```")) {
128
+ const lines = trimmed.split("\n");
129
+ const language = lines[0]?.slice(3).trim();
130
+ const code = lines.slice(1, -1).join("\n");
131
+ html.push(`<pre class="ah-code"><code${language ? ` data-language="${escapeHtml(language)}"` : ""}>${escapeHtml(code)}</code></pre>`);
132
+ continue;
133
+ }
134
+ if (/^#{1,6}\s/.test(trimmed)) {
135
+ const match = trimmed.match(/^(#{1,6})\s+(.*)$/);
136
+ const level = match?.[1].length ?? 1;
137
+ const content = linkifyPlainTextSegmentWithWalleeMarkdown(match?.[2] ?? trimmed);
138
+ html.push(`<h${level}>${content}</h${level}>`);
139
+ continue;
140
+ }
141
+ if (trimmed.split("\n").every((line) => /^[-*]\s+/.test(line))) {
142
+ const items = trimmed
143
+ .split("\n")
144
+ .map((line) => line.replace(/^[-*]\s+/, ""))
145
+ .map((line) => `<li>${linkifyPlainTextSegmentWithWalleeMarkdown(line)}</li>`)
146
+ .join("");
147
+ html.push(`<ul>${items}</ul>`);
148
+ continue;
149
+ }
150
+ html.push(`<p>${trimmed
151
+ .split("\n")
152
+ .map((line) => linkifyPlainTextSegmentWithWalleeMarkdown(line))
153
+ .join("<br />")}</p>`);
154
+ }
155
+ return html.join("");
156
+ }
11
157
  function renderInlineMarkdown(text) {
12
158
  const escaped = escapeHtml(text);
13
159
  return escaped
@@ -27,6 +27,7 @@ export declare class AgentRuntimeAdapter {
27
27
  private buildToolNameMapping;
28
28
  private buildAgentMessages;
29
29
  private buildInvocationRequest;
30
+ private buildStateSnapshot;
30
31
  private buildRawModelMessages;
31
32
  private resolveTools;
32
33
  private normalizeInterruptPolicy;
@@ -12,8 +12,9 @@ import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, ex
12
12
  import { wrapToolForExecution } from "./tool-hitl.js";
13
13
  import { resolveDeclaredMiddleware } from "./declared-middleware.js";
14
14
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
15
+ import { getBindingDeepAgentParams, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
15
16
  function countConfiguredTools(binding) {
16
- return binding.langchainAgentParams?.tools.length ?? binding.deepAgentParams?.tools.length ?? 0;
17
+ return getBindingPrimaryTools(binding).length;
17
18
  }
18
19
  const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
19
20
  const MODEL_SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
@@ -142,6 +143,11 @@ function hasCallableToolHandler(value) {
142
143
  const typed = value;
143
144
  return typeof typed.invoke === "function" || typeof typed.call === "function" || typeof typed.func === "function";
144
145
  }
146
+ function asRecord(value) {
147
+ return typeof value === "object" && value !== null && !Array.isArray(value)
148
+ ? { ...value }
149
+ : undefined;
150
+ }
145
151
  function normalizeResolvedToolSchema(resolvedTool) {
146
152
  const schema = resolvedTool.schema;
147
153
  if (schema && typeof schema.parse === "function" && "_def" in schema) {
@@ -172,10 +178,10 @@ export class AgentRuntimeAdapter {
172
178
  this.options = options;
173
179
  }
174
180
  resolveBindingTimeout(binding) {
175
- return resolveTimeoutMs(binding.langchainAgentParams?.model.init.timeout ?? binding.deepAgentParams?.model.init.timeout);
181
+ return resolveTimeoutMs(getBindingModelInit(binding)?.timeout);
176
182
  }
177
183
  resolveStreamIdleTimeout(binding) {
178
- const configuredIdleTimeout = resolveTimeoutMs(binding.langchainAgentParams?.model.init.streamIdleTimeout ?? binding.deepAgentParams?.model.init.streamIdleTimeout);
184
+ const configuredIdleTimeout = resolveTimeoutMs(getBindingModelInit(binding)?.streamIdleTimeout);
179
185
  if (configuredIdleTimeout) {
180
186
  return configuredIdleTimeout;
181
187
  }
@@ -284,35 +290,38 @@ export class AgentRuntimeAdapter {
284
290
  };
285
291
  }
286
292
  applyStrictToolJsonInstruction(binding) {
287
- if (binding.langchainAgentParams) {
293
+ if (isLangChainBinding(binding)) {
294
+ const params = getBindingLangChainParams(binding);
288
295
  return {
289
296
  ...binding,
290
297
  langchainAgentParams: {
291
- ...binding.langchainAgentParams,
292
- systemPrompt: [binding.langchainAgentParams.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
298
+ ...params,
299
+ systemPrompt: [params.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
293
300
  },
294
301
  };
295
302
  }
296
- if (binding.deepAgentParams) {
303
+ if (isDeepAgentBinding(binding)) {
304
+ const params = getBindingDeepAgentParams(binding);
297
305
  return {
298
306
  ...binding,
299
307
  deepAgentParams: {
300
- ...binding.deepAgentParams,
301
- systemPrompt: [binding.deepAgentParams.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
308
+ ...params,
309
+ systemPrompt: [params.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
302
310
  },
303
311
  };
304
312
  }
305
313
  return binding;
306
314
  }
307
315
  async synthesizeDeepAgentAnswer(binding, input, result) {
308
- if (!binding.deepAgentParams) {
316
+ const params = getBindingDeepAgentParams(binding);
317
+ if (!params) {
309
318
  return "";
310
319
  }
311
320
  const toolContext = extractToolFallbackContext(result);
312
321
  if (!toolContext) {
313
322
  return "";
314
323
  }
315
- const model = (await this.resolveModel(binding.deepAgentParams.model));
324
+ const model = (await this.resolveModel(params.model));
316
325
  if (!model?.invoke) {
317
326
  return "";
318
327
  }
@@ -365,6 +374,14 @@ export class AgentRuntimeAdapter {
365
374
  messages: this.buildAgentMessages(history, input),
366
375
  };
367
376
  }
377
+ buildStateSnapshot(result) {
378
+ const snapshot = { ...result };
379
+ delete snapshot.messages;
380
+ delete snapshot.__interrupt__;
381
+ delete snapshot.structuredResponse;
382
+ delete snapshot.files;
383
+ return Object.keys(snapshot).length > 0 ? snapshot : undefined;
384
+ }
368
385
  buildRawModelMessages(systemPrompt, history, input) {
369
386
  const messages = [];
370
387
  if (systemPrompt) {
@@ -424,14 +441,10 @@ export class AgentRuntimeAdapter {
424
441
  return compiled.size > 0 ? Object.fromEntries(compiled.entries()) : undefined;
425
442
  }
426
443
  resolveInterruptOn(binding) {
427
- if (binding.deepAgentParams) {
428
- return this.compileInterruptOn(binding.deepAgentParams.tools, binding.deepAgentParams.interruptOn);
429
- }
430
- return this.compileInterruptOn(binding.langchainAgentParams?.tools ?? [], binding.agent.langchainAgentConfig?.interruptOn);
444
+ return this.compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
431
445
  }
432
446
  async resolveMiddleware(binding, interruptOn) {
433
- const declarativeMiddleware = await resolveDeclaredMiddleware(binding.langchainAgentParams?.middleware ??
434
- binding.deepAgentParams?.middleware, {
447
+ const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding), {
435
448
  resolveModel: (model) => this.resolveModel(model),
436
449
  resolveCustom: this.options.declaredMiddlewareResolver,
437
450
  binding,
@@ -484,30 +497,31 @@ export class AgentRuntimeAdapter {
484
497
  })));
485
498
  }
486
499
  async create(binding) {
487
- if (binding.langchainAgentParams) {
500
+ if (isLangChainBinding(binding)) {
501
+ const params = getBindingLangChainParams(binding);
488
502
  const interruptOn = this.resolveInterruptOn(binding);
489
- const model = (await this.resolveModel(binding.langchainAgentParams.model));
490
- const tools = this.resolveTools(binding.langchainAgentParams.tools, binding);
503
+ const model = (await this.resolveModel(params.model));
504
+ const tools = this.resolveTools(params.tools, binding);
491
505
  if (tools.length > 0 && typeof model.bindTools !== "function") {
492
- throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${binding.langchainAgentParams.model.id} does not support tool binding.`);
506
+ throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
493
507
  }
494
508
  return createAgent({
495
509
  model: model,
496
510
  tools: tools,
497
- systemPrompt: binding.langchainAgentParams.systemPrompt,
498
- stateSchema: binding.langchainAgentParams.stateSchema,
499
- responseFormat: binding.langchainAgentParams.responseFormat,
500
- contextSchema: binding.langchainAgentParams.contextSchema,
511
+ systemPrompt: params.systemPrompt,
512
+ stateSchema: params.stateSchema,
513
+ responseFormat: params.responseFormat,
514
+ contextSchema: params.contextSchema,
501
515
  middleware: (await this.resolveMiddleware(binding, interruptOn)),
502
516
  checkpointer: this.resolveCheckpointer(binding),
503
517
  store: this.options.storeResolver?.(binding),
504
- includeAgentName: binding.langchainAgentParams.includeAgentName,
505
- version: binding.langchainAgentParams.version,
506
- name: binding.langchainAgentParams.name,
507
- description: binding.langchainAgentParams.description,
518
+ includeAgentName: params.includeAgentName,
519
+ version: params.version,
520
+ name: params.name,
521
+ description: params.description,
508
522
  });
509
523
  }
510
- const params = binding.deepAgentParams;
524
+ const params = getBindingDeepAgentParams(binding);
511
525
  if (!params) {
512
526
  throw new Error(`Agent ${binding.agent.id} has no runnable params`);
513
527
  }
@@ -532,10 +546,8 @@ export class AgentRuntimeAdapter {
532
546
  return createDeepAgent(deepAgentConfig);
533
547
  }
534
548
  async route(input, primaryBinding, secondaryBinding, options = {}) {
535
- const routeModelConfig = primaryBinding.langchainAgentParams?.model ??
536
- primaryBinding.deepAgentParams?.model ??
537
- secondaryBinding.langchainAgentParams?.model ??
538
- secondaryBinding.deepAgentParams?.model;
549
+ const routeModelConfig = getBindingPrimaryModel(primaryBinding) ??
550
+ getBindingPrimaryModel(secondaryBinding);
539
551
  if (!routeModelConfig) {
540
552
  throw new Error("No router model configuration available");
541
553
  }
@@ -587,13 +599,21 @@ export class AgentRuntimeAdapter {
587
599
  throw new Error(emptyAssistantMessageFailure);
588
600
  }
589
601
  const output = visibleOutput || synthesizedOutput || toolFallback || JSON.stringify(result, null, 2);
602
+ const finalMessageText = sanitizeVisibleText(output);
590
603
  return {
591
604
  threadId,
592
605
  runId,
593
606
  agentId: binding.agent.id,
594
607
  state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? "waiting_for_approval" : "completed",
595
608
  interruptContent,
596
- output: sanitizeVisibleText(output),
609
+ output: finalMessageText,
610
+ finalMessageText,
611
+ metadata: {
612
+ ...(result.structuredResponse !== undefined ? { structuredResponse: result.structuredResponse } : {}),
613
+ ...(asRecord(result.files) ? { files: asRecord(result.files) } : {}),
614
+ ...(this.buildStateSnapshot(result) ? { stateSnapshot: this.buildStateSnapshot(result) } : {}),
615
+ upstreamResult: result,
616
+ },
597
617
  };
598
618
  }
599
619
  async *stream(binding, input, threadId, history = [], options = {}) {
@@ -601,9 +621,9 @@ export class AgentRuntimeAdapter {
601
621
  const invokeTimeoutMs = this.resolveBindingTimeout(binding);
602
622
  const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
603
623
  const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
604
- if (binding.langchainAgentParams) {
605
- const langchainParams = binding.langchainAgentParams;
606
- const resolvedModel = (await this.resolveModel(binding.langchainAgentParams.model));
624
+ if (isLangChainBinding(binding)) {
625
+ const langchainParams = getBindingLangChainParams(binding);
626
+ const resolvedModel = (await this.resolveModel(langchainParams.model));
607
627
  const tools = this.resolveTools(langchainParams.tools, binding);
608
628
  const canUseDirectModelStream = tools.length === 0 || typeof resolvedModel.bindTools !== "function";
609
629
  const model = canUseDirectModelStream
@@ -615,7 +635,7 @@ export class AgentRuntimeAdapter {
615
635
  // agent loop and only adds an extra model round-trip before the runnable path.
616
636
  if (canUseDirectModelStream && typeof model.stream === "function") {
617
637
  let emitted = false;
618
- const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(langchainParams.systemPrompt, history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
638
+ const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(getBindingSystemPrompt(binding), history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
619
639
  for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream", streamDeadlineAt, invokeTimeoutMs)) {
620
640
  const delta = readStreamDelta(chunk);
621
641
  if (delta) {
@@ -637,7 +657,7 @@ export class AgentRuntimeAdapter {
637
657
  const request = this.buildInvocationRequest(history, input, options);
638
658
  if (typeof runnable.streamEvents === "function") {
639
659
  const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
640
- const allowVisibleStreamDeltas = Boolean(binding.langchainAgentParams);
660
+ const allowVisibleStreamDeltas = isLangChainBinding(binding);
641
661
  let emittedOutput = "";
642
662
  let emittedToolError = false;
643
663
  const seenTerminalOutputs = new Set();
@@ -662,7 +682,7 @@ export class AgentRuntimeAdapter {
662
682
  }
663
683
  }
664
684
  }
665
- if (binding.deepAgentParams) {
685
+ if (isDeepAgentBinding(binding)) {
666
686
  const stateStreamOutput = extractStateStreamOutput(event);
667
687
  if (stateStreamOutput) {
668
688
  const nextOutput = computeIncrementalOutput(emittedOutput, sanitizeVisibleText(stateStreamOutput));
@@ -702,7 +722,7 @@ export class AgentRuntimeAdapter {
702
722
  return;
703
723
  }
704
724
  }
705
- if (binding.langchainAgentParams && typeof runnable.stream === "function") {
725
+ if (isLangChainBinding(binding) && typeof runnable.stream === "function") {
706
726
  const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
707
727
  let emitted = false;
708
728
  for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs)) {
@@ -24,6 +24,8 @@ export declare class AgentHarnessRuntime {
24
24
  private readonly concurrencyConfig;
25
25
  private activeRunSlots;
26
26
  private readonly pendingRunSlots;
27
+ private toPublicApprovalRecord;
28
+ private normalizeInvocationEnvelope;
27
29
  private listHostBindings;
28
30
  private defaultRunRoot;
29
31
  private heuristicRoute;
@@ -15,6 +15,7 @@ import { FileBackedStore } from "./store.js";
15
15
  import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
16
16
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
17
17
  import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
18
+ import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig } from "./support/compiled-binding.js";
18
19
  export class AgentHarnessRuntime {
19
20
  workspace;
20
21
  runtimeAdapterOptions;
@@ -39,6 +40,19 @@ export class AgentHarnessRuntime {
39
40
  concurrencyConfig;
40
41
  activeRunSlots = 0;
41
42
  pendingRunSlots = [];
43
+ toPublicApprovalRecord(approval) {
44
+ const { toolCallId: _toolCallId, checkpointRef: _checkpointRef, eventRefs: _eventRefs, ...publicApproval } = approval;
45
+ return publicApproval;
46
+ }
47
+ normalizeInvocationEnvelope(options) {
48
+ const invocation = options.invocation;
49
+ return {
50
+ context: invocation?.context ?? options.context,
51
+ state: invocation?.inputs ?? options.state,
52
+ files: invocation?.attachments ?? options.files,
53
+ invocation,
54
+ };
55
+ }
42
56
  listHostBindings() {
43
57
  return inferRoutingBindings(this.workspace).hostBindings;
44
58
  }
@@ -79,7 +93,7 @@ export class AgentHarnessRuntime {
79
93
  return requestedAgentId;
80
94
  }
81
95
  resolveStore(binding) {
82
- const storeConfig = binding?.deepAgentParams?.store ?? binding?.harnessRuntime.store;
96
+ const storeConfig = binding ? getBindingStoreConfig(binding) : undefined;
83
97
  const cacheKey = storeConfig ? JSON.stringify(storeConfig) : undefined;
84
98
  if (!storeConfig) {
85
99
  return this.defaultStore;
@@ -184,7 +198,7 @@ export class AgentHarnessRuntime {
184
198
  if (!binding) {
185
199
  throw new Error(`Unknown agent ${agentId}`);
186
200
  }
187
- return binding.langchainAgentParams?.tools ?? binding.deepAgentParams?.tools ?? [];
201
+ return getBindingPrimaryTools(binding);
188
202
  }
189
203
  resolveAgentTools(agentId) {
190
204
  const binding = this.getBinding(agentId);
@@ -259,10 +273,11 @@ export class AgentHarnessRuntime {
259
273
  return false;
260
274
  }
261
275
  return true;
262
- });
276
+ }).map((approval) => this.toPublicApprovalRecord(approval));
263
277
  }
264
278
  async getApproval(approvalId) {
265
- return this.persistence.getApproval(approvalId);
279
+ const approval = await this.persistence.getApproval(approvalId);
280
+ return approval ? this.toPublicApprovalRecord(approval) : null;
266
281
  }
267
282
  async createToolMcpServer(options) {
268
283
  const tools = this.resolveAgentTools(options.agentId).map(({ compiledTool, resolvedTool }) => ({
@@ -340,7 +355,8 @@ export class AgentHarnessRuntime {
340
355
  threadId,
341
356
  runId,
342
357
  agentId: binding.agent.id,
343
- executionMode: binding.agent.executionMode,
358
+ executionMode: getBindingAdapterKind(binding),
359
+ adapterKind: getBindingAdapterKind(binding),
344
360
  createdAt,
345
361
  });
346
362
  return { threadId, runId, createdAt };
@@ -500,6 +516,7 @@ export class AgentHarnessRuntime {
500
516
  }
501
517
  async dispatchRunListeners(stream, listeners) {
502
518
  let latestEvent;
519
+ let latestResult;
503
520
  let output = "";
504
521
  for await (const item of stream) {
505
522
  if (item.type === "event") {
@@ -507,6 +524,10 @@ export class AgentHarnessRuntime {
507
524
  await this.notifyListener(listeners.onEvent, item.event);
508
525
  continue;
509
526
  }
527
+ if (item.type === "result") {
528
+ latestResult = item.result;
529
+ continue;
530
+ }
510
531
  if (item.type === "content") {
511
532
  output += item.content;
512
533
  await this.notifyListener(listeners.onChunk, item.content);
@@ -531,6 +552,13 @@ export class AgentHarnessRuntime {
531
552
  if (!latestEvent) {
532
553
  throw new Error("run did not emit any events");
533
554
  }
555
+ if (latestResult) {
556
+ return {
557
+ ...latestResult,
558
+ output: latestResult.output || output,
559
+ finalMessageText: latestResult.finalMessageText ?? latestResult.output ?? output,
560
+ };
561
+ }
534
562
  const thread = await this.getThread(latestEvent.threadId);
535
563
  if (!thread) {
536
564
  throw new Error(`Unknown thread ${latestEvent.threadId}`);
@@ -561,6 +589,7 @@ export class AgentHarnessRuntime {
561
589
  }
562
590
  const releaseRunSlot = await this.acquireRunSlot();
563
591
  try {
592
+ const invocation = this.normalizeInvocationEnvelope(options);
564
593
  const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
565
594
  const binding = this.workspace.bindings.get(selectedAgentId);
566
595
  if (!binding) {
@@ -575,13 +604,13 @@ export class AgentHarnessRuntime {
575
604
  agentId: binding.agent.id,
576
605
  requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
577
606
  selectedAgentId,
578
- executionMode: binding.agent.executionMode,
607
+ executionMode: getBindingAdapterKind(binding),
579
608
  });
580
609
  try {
581
610
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId, undefined, {
582
- context: options.context,
583
- state: options.state,
584
- files: options.files,
611
+ context: invocation.context,
612
+ state: invocation.state,
613
+ files: invocation.files,
585
614
  });
586
615
  const finalized = await this.finalizeContinuedRun(threadId, runId, options.input, actual, {
587
616
  previousState: null,
@@ -615,6 +644,7 @@ export class AgentHarnessRuntime {
615
644
  async *streamEvents(options) {
616
645
  const releaseRunSlot = await this.acquireRunSlot();
617
646
  try {
647
+ const invocation = this.normalizeInvocationEnvelope(options);
618
648
  const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
619
649
  const binding = this.workspace.bindings.get(selectedAgentId);
620
650
  if (!binding) {
@@ -644,9 +674,9 @@ export class AgentHarnessRuntime {
644
674
  let assistantOutput = "";
645
675
  const toolErrors = [];
646
676
  for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory, {
647
- context: options.context,
648
- state: options.state,
649
- files: options.files,
677
+ context: invocation.context,
678
+ state: invocation.state,
679
+ files: invocation.files,
650
680
  })) {
651
681
  if (chunk) {
652
682
  const normalizedChunk = typeof chunk === "string"
@@ -669,6 +699,20 @@ export class AgentHarnessRuntime {
669
699
  type: "event",
670
700
  event: approvalRequest.event,
671
701
  };
702
+ yield {
703
+ type: "result",
704
+ result: {
705
+ threadId,
706
+ runId,
707
+ agentId: selectedAgentId,
708
+ state: "waiting_for_approval",
709
+ output: assistantOutput,
710
+ finalMessageText: assistantOutput,
711
+ interruptContent: normalizedChunk.content,
712
+ approvalId: approvalRequest.approval.approvalId,
713
+ pendingActionId: approvalRequest.approval.pendingActionId,
714
+ },
715
+ };
672
716
  return;
673
717
  }
674
718
  if (normalizedChunk.kind === "reasoning") {
@@ -728,6 +772,17 @@ export class AgentHarnessRuntime {
728
772
  }
729
773
  }
730
774
  await this.appendAssistantMessage(threadId, runId, assistantOutput);
775
+ yield {
776
+ type: "result",
777
+ result: {
778
+ threadId,
779
+ runId,
780
+ agentId: selectedAgentId,
781
+ state: "completed",
782
+ output: assistantOutput,
783
+ finalMessageText: assistantOutput,
784
+ },
785
+ };
731
786
  yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
732
787
  previousState: null,
733
788
  }) };
@@ -753,6 +808,17 @@ export class AgentHarnessRuntime {
753
808
  agentId: selectedAgentId,
754
809
  content: renderRuntimeFailure(error),
755
810
  };
811
+ yield {
812
+ type: "result",
813
+ result: {
814
+ threadId,
815
+ runId,
816
+ agentId: selectedAgentId,
817
+ state: "failed",
818
+ output: renderRuntimeFailure(error),
819
+ finalMessageText: renderRuntimeFailure(error),
820
+ },
821
+ };
756
822
  return;
757
823
  }
758
824
  try {
@@ -761,6 +827,15 @@ export class AgentHarnessRuntime {
761
827
  if (actual.output) {
762
828
  yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
763
829
  }
830
+ yield {
831
+ type: "result",
832
+ result: {
833
+ ...actual,
834
+ threadId,
835
+ runId,
836
+ agentId: selectedAgentId,
837
+ },
838
+ };
764
839
  yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
765
840
  previousState: null,
766
841
  }) };
@@ -779,6 +854,17 @@ export class AgentHarnessRuntime {
779
854
  agentId: selectedAgentId,
780
855
  content: renderRuntimeFailure(invokeError),
781
856
  };
857
+ yield {
858
+ type: "result",
859
+ result: {
860
+ threadId,
861
+ runId,
862
+ agentId: selectedAgentId,
863
+ state: "failed",
864
+ output: renderRuntimeFailure(invokeError),
865
+ finalMessageText: renderRuntimeFailure(invokeError),
866
+ },
867
+ };
782
868
  return;
783
869
  }
784
870
  }
@@ -1,4 +1,5 @@
1
1
  import { readSkillMetadata } from "./support/skill-metadata.js";
2
+ import { getBindingPrimaryTools } from "./support/compiled-binding.js";
2
3
  function listHostBindings(workspace) {
3
4
  return Array.from(workspace.bindings.values()).filter((binding) => binding.harnessRuntime.hostFacing);
4
5
  }
@@ -31,7 +32,7 @@ export function listAgentTools(workspace, agentId) {
31
32
  if (!binding) {
32
33
  return [];
33
34
  }
34
- return dedupeTools(binding.langchainAgentParams?.tools ?? binding.deepAgentParams?.tools ?? []);
35
+ return dedupeTools(getBindingPrimaryTools(binding));
35
36
  }
36
37
  export function listAgentSkills(workspace, agentId) {
37
38
  const binding = findAgentBinding(workspace, agentId);