@botbotgo/agent-harness 0.0.81 → 0.0.83

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.
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.80";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.82";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.80";
1
+ export const AGENT_HARNESS_VERSION = "0.0.82";
@@ -1,5 +1,5 @@
1
1
  import type { ArtifactListing, ArtifactRecord, DelegationRecord, HarnessEvent, InternalApprovalRecord, RunSummary, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
2
- import type { PersistedRunRequest, PersistedRunControlRecord, PersistedRunQueueRecord, PersistenceLifecycle as Lifecycle, RuntimePersistence, RecoveryIntent, PersistenceRunMeta as RunMeta, PersistenceThreadMeta as ThreadMeta } from "./types.js";
2
+ import type { ApprovalFilter, PersistedRunRequest, PersistedRunControlRecord, PersistedRunQueueRecord, PersistenceLifecycle as Lifecycle, RuntimePersistence, RecoveryIntent, PersistenceRunMeta as RunMeta, RunSummaryFilter, ThreadSummaryFilter, PersistenceThreadMeta as ThreadMeta } from "./types.js";
3
3
  type RunIndexRecord = {
4
4
  runId: string;
5
5
  threadId: string;
@@ -36,16 +36,16 @@ export declare class FilePersistence implements RuntimePersistence {
36
36
  }): Promise<void>;
37
37
  setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
38
38
  appendEvent(event: HarnessEvent): Promise<void>;
39
- listSessions(): Promise<ThreadSummary[]>;
39
+ listSessions(filter?: ThreadSummaryFilter): Promise<ThreadSummary[]>;
40
40
  listRunIndexes(): Promise<RunIndexRecord[]>;
41
41
  private readRunSummary;
42
- listRuns(): Promise<RunSummary[]>;
42
+ listRuns(filter?: RunSummaryFilter): Promise<RunSummary[]>;
43
43
  getRun(runId: string): Promise<RunSummary | null>;
44
44
  getSession(threadId: string): Promise<ThreadSummary | null>;
45
45
  getThreadMeta(threadId: string): Promise<ThreadMeta | null>;
46
46
  listThreadRuns(threadId: string): Promise<ThreadRunRecord[]>;
47
47
  listRunEvents(threadId: string, runId: string): Promise<HarnessEvent[]>;
48
- listApprovals(): Promise<InternalApprovalRecord[]>;
48
+ listApprovals(filter?: ApprovalFilter): Promise<InternalApprovalRecord[]>;
49
49
  getApproval(approvalId: string): Promise<InternalApprovalRecord | null>;
50
50
  getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
51
51
  getRunMeta(threadId: string, runId: string): Promise<RunMeta>;
@@ -182,7 +182,7 @@ export class FilePersistence {
182
182
  const sequenceId = String(event.sequence).padStart(6, "0");
183
183
  await writeJson(path.join(this.runDir(event.threadId, event.runId), "events", `${sequenceId}.json`), event);
184
184
  }
185
- async listSessions() {
185
+ async listSessions(filter = {}) {
186
186
  const threadIndexDir = path.join(this.runRoot, "indexes", "threads");
187
187
  if (!(await fileExists(threadIndexDir))) {
188
188
  return [];
@@ -200,7 +200,9 @@ export class FilePersistence {
200
200
  status: index.status,
201
201
  };
202
202
  }));
203
- return records.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
203
+ return records
204
+ .filter((record) => !filter.agentId || record.agentId === filter.agentId)
205
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
204
206
  }
205
207
  async listRunIndexes() {
206
208
  const runIndexDir = path.join(this.runRoot, "indexes", "runs");
@@ -229,10 +231,23 @@ export class FilePersistence {
229
231
  resumable: lifecycle.resumable,
230
232
  };
231
233
  }
232
- async listRuns() {
234
+ async listRuns(filter = {}) {
233
235
  const indexes = await this.listRunIndexes();
234
236
  const runs = await Promise.all(indexes.map((record) => this.readRunSummary(record.threadId, record.runId)));
235
- return runs.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
237
+ return runs
238
+ .filter((run) => {
239
+ if (filter.agentId && run.agentId !== filter.agentId) {
240
+ return false;
241
+ }
242
+ if (filter.threadId && run.threadId !== filter.threadId) {
243
+ return false;
244
+ }
245
+ if (filter.state && run.state !== filter.state) {
246
+ return false;
247
+ }
248
+ return true;
249
+ })
250
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
236
251
  }
237
252
  async getRun(runId) {
238
253
  const indexPath = this.runIndexPath(runId);
@@ -284,13 +299,25 @@ export class FilePersistence {
284
299
  const entries = (await readdir(eventsDir)).sort();
285
300
  return Promise.all(entries.map((entry) => readJson(path.join(eventsDir, entry))));
286
301
  }
287
- async listApprovals() {
302
+ async listApprovals(filter = {}) {
288
303
  const approvalsDir = path.join(this.runRoot, "indexes", "approvals");
289
304
  if (!(await fileExists(approvalsDir))) {
290
305
  return [];
291
306
  }
292
307
  const entries = (await readdir(approvalsDir)).sort();
293
- return Promise.all(entries.map((entry) => readJson(path.join(approvalsDir, entry))));
308
+ const approvals = await Promise.all(entries.map((entry) => readJson(path.join(approvalsDir, entry))));
309
+ return approvals.filter((approval) => {
310
+ if (filter.status && approval.status !== filter.status) {
311
+ return false;
312
+ }
313
+ if (filter.threadId && approval.threadId !== filter.threadId) {
314
+ return false;
315
+ }
316
+ if (filter.runId && approval.runId !== filter.runId) {
317
+ return false;
318
+ }
319
+ return true;
320
+ });
294
321
  }
295
322
  async getApproval(approvalId) {
296
323
  const approvalPath = path.join(this.runRoot, "indexes", "approvals", `${approvalId}.json`);
@@ -1,5 +1,5 @@
1
1
  import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, RunState, RunSummary, ThreadRunRecord, ThreadSummary, TranscriptMessage } from "../contracts/types.js";
2
- import type { PersistedRunRequest, PersistedRunControlRecord, PersistedRunQueueRecord, PersistenceLifecycle, PersistenceRunMeta, PersistenceThreadMeta, RecoveryIntent, RuntimePersistence } from "./types.js";
2
+ import type { PersistedRunRequest, PersistedRunControlRecord, PersistedRunQueueRecord, PersistenceLifecycle, PersistenceRunMeta, PersistenceThreadMeta, RecoveryIntent, RuntimePersistence, ApprovalFilter, RunSummaryFilter, ThreadSummaryFilter } from "./types.js";
3
3
  export declare function listProtectedCheckpointThreadIds(dbPath: string): Promise<Set<string>>;
4
4
  export declare class SqlitePersistence implements RuntimePersistence {
5
5
  private readonly runRoot;
@@ -39,14 +39,14 @@ export declare class SqlitePersistence implements RuntimePersistence {
39
39
  }): Promise<void>;
40
40
  setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
41
41
  appendEvent(event: HarnessEvent): Promise<void>;
42
- listSessions(): Promise<ThreadSummary[]>;
43
- listRuns(): Promise<RunSummary[]>;
42
+ listSessions(filter?: ThreadSummaryFilter): Promise<ThreadSummary[]>;
43
+ listRuns(filter?: RunSummaryFilter): Promise<RunSummary[]>;
44
44
  getRun(runId: string): Promise<RunSummary | null>;
45
45
  getSession(threadId: string): Promise<ThreadSummary | null>;
46
46
  getThreadMeta(threadId: string): Promise<PersistenceThreadMeta | null>;
47
47
  listThreadRuns(threadId: string): Promise<ThreadRunRecord[]>;
48
48
  listRunEvents(threadId: string, runId: string): Promise<HarnessEvent[]>;
49
- listApprovals(): Promise<InternalApprovalRecord[]>;
49
+ listApprovals(filter?: ApprovalFilter): Promise<InternalApprovalRecord[]>;
50
50
  getApproval(approvalId: string): Promise<InternalApprovalRecord | null>;
51
51
  getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
52
52
  getRunMeta(threadId: string, runId: string): Promise<PersistenceRunMeta>;
@@ -25,6 +25,16 @@ function toSqliteUrl(filePath) {
25
25
  function nowIso() {
26
26
  return new Date(Date.now()).toISOString();
27
27
  }
28
+ function buildWhereClause(filters) {
29
+ const active = filters.filter(([, value]) => value !== undefined);
30
+ if (active.length === 0) {
31
+ return { clause: "", args: [] };
32
+ }
33
+ return {
34
+ clause: ` WHERE ${active.map(([sql]) => sql).join(" AND ")}`,
35
+ args: active.map(([, value]) => value),
36
+ };
37
+ }
28
38
  async function selectProtectedThreadIds(dbPath) {
29
39
  if (!(await fileExists(dbPath))) {
30
40
  return [];
@@ -377,16 +387,24 @@ export class SqlitePersistence {
377
387
  (thread_id, run_id, sequence, event_json, created_at)
378
388
  VALUES (?, ?, ?, ?, ?)`, [event.threadId, event.runId, event.sequence, JSON.stringify(event), event.timestamp]);
379
389
  }
380
- async listSessions() {
390
+ async listSessions(filter = {}) {
391
+ const { clause, args } = buildWhereClause([
392
+ ["entry_agent_id = ?", filter.agentId],
393
+ ]);
381
394
  const rows = await this.selectAll(`SELECT thread_id, entry_agent_id, latest_run_id, created_at, updated_at, status
382
- FROM threads
383
- ORDER BY updated_at DESC`);
395
+ FROM threads${clause}
396
+ ORDER BY updated_at DESC`, args);
384
397
  return rows.map((row) => this.mapThreadSummary(row));
385
398
  }
386
- async listRuns() {
399
+ async listRuns(filter = {}) {
400
+ const { clause, args } = buildWhereClause([
401
+ ["agent_id = ?", filter.agentId],
402
+ ["thread_id = ?", filter.threadId],
403
+ ["state = ?", filter.state],
404
+ ]);
387
405
  const rows = await this.selectAll(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
388
- FROM runs
389
- ORDER BY updated_at DESC`);
406
+ FROM runs${clause}
407
+ ORDER BY updated_at DESC`, args);
390
408
  return rows.map((row) => this.mapRunSummary(row));
391
409
  }
392
410
  async getRun(runId) {
@@ -430,8 +448,13 @@ export class SqlitePersistence {
430
448
  ORDER BY sequence ASC`, [threadId, runId]);
431
449
  return rows.map((row) => parseJson(row.event_json));
432
450
  }
433
- async listApprovals() {
434
- const rows = await this.selectAll("SELECT * FROM approvals ORDER BY requested_at ASC, approval_id ASC");
451
+ async listApprovals(filter = {}) {
452
+ const { clause, args } = buildWhereClause([
453
+ ["status = ?", filter.status],
454
+ ["thread_id = ?", filter.threadId],
455
+ ["run_id = ?", filter.runId],
456
+ ]);
457
+ const rows = await this.selectAll(`SELECT * FROM approvals${clause} ORDER BY requested_at ASC, approval_id ASC`, args);
435
458
  return rows.map((row) => this.mapApproval(row));
436
459
  }
437
460
  async getApproval(approvalId) {
@@ -59,6 +59,19 @@ export type PersistedRunControlRecord = {
59
59
  workerId: string | null;
60
60
  workerStartedAt: string | null;
61
61
  };
62
+ export type ThreadSummaryFilter = {
63
+ agentId?: string;
64
+ };
65
+ export type RunSummaryFilter = {
66
+ agentId?: string;
67
+ threadId?: string;
68
+ state?: RunSummary["state"];
69
+ };
70
+ export type ApprovalFilter = {
71
+ status?: InternalApprovalRecord["status"];
72
+ threadId?: string;
73
+ runId?: string;
74
+ };
62
75
  export interface RuntimePersistence {
63
76
  initialize(): Promise<void>;
64
77
  createThread(input: {
@@ -78,13 +91,13 @@ export interface RuntimePersistence {
78
91
  }): Promise<void>;
79
92
  setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
80
93
  appendEvent(event: HarnessEvent): Promise<void>;
81
- listSessions(): Promise<ThreadSummary[]>;
82
- listRuns(): Promise<RunSummary[]>;
94
+ listSessions(filter?: ThreadSummaryFilter): Promise<ThreadSummary[]>;
95
+ listRuns(filter?: RunSummaryFilter): Promise<RunSummary[]>;
83
96
  getRun(runId: string): Promise<RunSummary | null>;
84
97
  getSession(threadId: string): Promise<ThreadSummary | null>;
85
98
  getThreadMeta(threadId: string): Promise<PersistenceThreadMeta | null>;
86
99
  listThreadRuns(threadId: string): Promise<ThreadRunRecord[]>;
87
- listApprovals(): Promise<InternalApprovalRecord[]>;
100
+ listApprovals(filter?: ApprovalFilter): Promise<InternalApprovalRecord[]>;
88
101
  getApproval(approvalId: string): Promise<InternalApprovalRecord | null>;
89
102
  getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
90
103
  getRunMeta(threadId: string, runId: string): Promise<PersistenceRunMeta>;
@@ -21,7 +21,10 @@ export declare function materializeDeepAgentSkillSourcePaths(options: {
21
21
  }): Promise<string[] | undefined>;
22
22
  export declare class AgentRuntimeAdapter {
23
23
  private readonly options;
24
+ private readonly modelCache;
25
+ private readonly runnableCache;
24
26
  constructor(options?: RuntimeAdapterOptions);
27
+ private getModelCacheKey;
25
28
  private resolveBindingTimeout;
26
29
  private resolveStreamIdleTimeout;
27
30
  private resolveProviderRetryPolicy;
@@ -48,11 +51,15 @@ export declare class AgentRuntimeAdapter {
48
51
  private resolveBuiltinMiddlewareBackend;
49
52
  private invokeBuiltinTaskTool;
50
53
  private resolveBuiltinMiddlewareTools;
54
+ private canReplayToolCallsLocally;
55
+ private resolveAutomaticSummarizationMiddleware;
51
56
  private resolveLangChainAutomaticMiddleware;
57
+ private resolveDeepAgentAutomaticMiddleware;
52
58
  private resolveMiddleware;
53
59
  private resolveCheckpointer;
54
60
  private buildRouteSystemPrompt;
55
61
  private resolveSubagents;
62
+ private createRunnable;
56
63
  create(binding: CompiledAgentBinding): Promise<RunnableLike>;
57
64
  route(input: MessageContent, primaryBinding: CompiledAgentBinding, secondaryBinding: CompiledAgentBinding, options?: {
58
65
  systemPrompt?: string;
@@ -200,6 +200,9 @@ function hasConfiguredSubagentSupport(binding) {
200
200
  }
201
201
  return (params.subagents?.length ?? 0) > 0 || params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
202
202
  }
203
+ function hasConfiguredMiddlewareKind(binding, kind) {
204
+ return getBindingMiddlewareConfigs(binding)?.some((entry) => entry.kind === kind) ?? false;
205
+ }
203
206
  function instantiateProviderTool(compiledTool) {
204
207
  const providerTool = asRecord(compiledTool.config?.providerTool);
205
208
  const provider = typeof providerTool?.provider === "string" ? providerTool.provider.trim().toLowerCase() : "";
@@ -471,9 +474,19 @@ function asStructuredExecutableTool(resolvedTool, modelFacingName, description)
471
474
  }
472
475
  export class AgentRuntimeAdapter {
473
476
  options;
477
+ modelCache = new Map();
478
+ runnableCache = new WeakMap();
474
479
  constructor(options = {}) {
475
480
  this.options = options;
476
481
  }
482
+ getModelCacheKey(model) {
483
+ return JSON.stringify({
484
+ id: model.id,
485
+ runtimeValue: model.runtimeValue,
486
+ init: model.init,
487
+ clientRef: model.clientRef,
488
+ });
489
+ }
477
490
  resolveBindingTimeout(binding) {
478
491
  return resolveTimeoutMs(getBindingModelInit(binding)?.timeout);
479
492
  }
@@ -686,25 +699,40 @@ export class AgentRuntimeAdapter {
686
699
  return sanitizeVisibleText(extractVisibleOutput(synthesized));
687
700
  }
688
701
  async resolveModel(model) {
689
- if (this.options.modelResolver) {
690
- return wrapResolvedModel(await this.options.modelResolver(model.id));
691
- }
692
- if (model.provider === "ollama") {
693
- return wrapResolvedModel(new ChatOllama({ model: model.model, ...model.init }));
694
- }
695
- if (model.provider === "openai-compatible") {
696
- return wrapResolvedModel(new ChatOpenAI({ model: model.model, ...normalizeOpenAICompatibleInit(model.init) }));
697
- }
698
- if (model.provider === "openai") {
699
- return wrapResolvedModel(new ChatOpenAI({ model: model.model, ...model.init }));
702
+ const cacheKey = this.getModelCacheKey(model);
703
+ const cached = this.modelCache.get(cacheKey);
704
+ if (cached) {
705
+ return cached;
700
706
  }
701
- if (model.provider === "anthropic") {
702
- return wrapResolvedModel(new ChatAnthropic({ model: model.model, ...model.init }));
707
+ const pending = (async () => {
708
+ if (this.options.modelResolver) {
709
+ return wrapResolvedModel(await this.options.modelResolver(model.id));
710
+ }
711
+ if (model.provider === "ollama") {
712
+ return wrapResolvedModel(new ChatOllama({ model: model.model, ...model.init }));
713
+ }
714
+ if (model.provider === "openai-compatible") {
715
+ return wrapResolvedModel(new ChatOpenAI({ model: model.model, ...normalizeOpenAICompatibleInit(model.init) }));
716
+ }
717
+ if (model.provider === "openai") {
718
+ return wrapResolvedModel(new ChatOpenAI({ model: model.model, ...model.init }));
719
+ }
720
+ if (model.provider === "anthropic") {
721
+ return wrapResolvedModel(new ChatAnthropic({ model: model.model, ...model.init }));
722
+ }
723
+ if (model.provider === "google" || model.provider === "google-genai" || model.provider === "gemini") {
724
+ return wrapResolvedModel(new ChatGoogle({ model: model.model, ...model.init }));
725
+ }
726
+ return wrapResolvedModel(await initChatModel(model.model, { modelProvider: model.provider, ...model.init }));
727
+ })();
728
+ this.modelCache.set(cacheKey, pending);
729
+ try {
730
+ return await pending;
703
731
  }
704
- if (model.provider === "google" || model.provider === "google-genai" || model.provider === "gemini") {
705
- return wrapResolvedModel(new ChatGoogle({ model: model.model, ...model.init }));
732
+ catch (error) {
733
+ this.modelCache.delete(cacheKey);
734
+ throw error;
706
735
  }
707
- return wrapResolvedModel(await initChatModel(model.model, { modelProvider: model.provider, ...model.init }));
708
736
  }
709
737
  buildToolNameMapping(tools) {
710
738
  return buildToolNameMapping(tools);
@@ -1065,6 +1093,43 @@ export class AgentRuntimeAdapter {
1065
1093
  }
1066
1094
  return tools;
1067
1095
  }
1096
+ canReplayToolCallsLocally(binding, toolCalls, primaryTools, toolNameMapping, executableTools, builtinExecutableTools) {
1097
+ if (toolCalls.length === 0) {
1098
+ return false;
1099
+ }
1100
+ if (isLangChainBinding(binding)) {
1101
+ return true;
1102
+ }
1103
+ return toolCalls.every((toolCall) => {
1104
+ const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
1105
+ const executable = executableTools.get(toolCall.name) ?? executableTools.get(resolvedToolName);
1106
+ if (executable) {
1107
+ return false;
1108
+ }
1109
+ const builtinExecutable = builtinExecutableTools.get(toolCall.name) ??
1110
+ builtinExecutableTools.get(resolvedToolName) ??
1111
+ createModelFacingToolNameLookupCandidates(toolCall.name)
1112
+ .map((candidate) => builtinExecutableTools.get(candidate))
1113
+ .find((candidate) => candidate !== undefined);
1114
+ return builtinExecutable !== undefined;
1115
+ });
1116
+ }
1117
+ async resolveAutomaticSummarizationMiddleware(binding) {
1118
+ if (hasConfiguredMiddlewareKind(binding, "summarization")) {
1119
+ return [];
1120
+ }
1121
+ const primaryModel = getBindingPrimaryModel(binding);
1122
+ if (!primaryModel) {
1123
+ return [];
1124
+ }
1125
+ return resolveDeclaredMiddleware([{ kind: "summarization", model: primaryModel }], {
1126
+ resolveModel: (model) => this.resolveModel(model),
1127
+ resolveBackend: (resolvedBinding) => this.options.backendResolver?.(resolvedBinding ?? binding),
1128
+ resolveFilesystemBackend: (resolvedBinding) => this.resolveFilesystemBackend(resolvedBinding ?? binding),
1129
+ resolveCustom: this.options.declaredMiddlewareResolver,
1130
+ binding,
1131
+ });
1132
+ }
1068
1133
  async resolveLangChainAutomaticMiddleware(binding) {
1069
1134
  const params = getBindingLangChainParams(binding);
1070
1135
  if (!params) {
@@ -1072,6 +1137,7 @@ export class AgentRuntimeAdapter {
1072
1137
  }
1073
1138
  const automaticMiddleware = [];
1074
1139
  automaticMiddleware.push(createPatchToolCallsMiddleware());
1140
+ automaticMiddleware.push(...(await this.resolveAutomaticSummarizationMiddleware(binding)));
1075
1141
  if ((params.skills?.length ?? 0) > 0) {
1076
1142
  automaticMiddleware.push(createSkillsMiddleware({
1077
1143
  backend: this.resolveFilesystemBackend(binding),
@@ -1096,6 +1162,12 @@ export class AgentRuntimeAdapter {
1096
1162
  }
1097
1163
  return automaticMiddleware;
1098
1164
  }
1165
+ async resolveDeepAgentAutomaticMiddleware(binding) {
1166
+ if (!isDeepAgentBinding(binding)) {
1167
+ return [];
1168
+ }
1169
+ return this.resolveAutomaticSummarizationMiddleware(binding);
1170
+ }
1099
1171
  async resolveMiddleware(binding, interruptOn) {
1100
1172
  const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding), {
1101
1173
  resolveModel: (model) => this.resolveModel(model),
@@ -1106,7 +1178,7 @@ export class AgentRuntimeAdapter {
1106
1178
  });
1107
1179
  const automaticMiddleware = isLangChainBinding(binding)
1108
1180
  ? await this.resolveLangChainAutomaticMiddleware(binding)
1109
- : [];
1181
+ : await this.resolveDeepAgentAutomaticMiddleware(binding);
1110
1182
  const middleware = [
1111
1183
  ...declarativeMiddleware,
1112
1184
  ...automaticMiddleware,
@@ -1170,7 +1242,7 @@ export class AgentRuntimeAdapter {
1170
1242
  })),
1171
1243
  })));
1172
1244
  }
1173
- async create(binding) {
1245
+ async createRunnable(binding) {
1174
1246
  if (isLangChainBinding(binding)) {
1175
1247
  const params = getBindingLangChainParams(binding);
1176
1248
  const interruptOn = this.resolveInterruptOn(binding);
@@ -1226,6 +1298,21 @@ export class AgentRuntimeAdapter {
1226
1298
  };
1227
1299
  return createDeepAgent(deepAgentConfig);
1228
1300
  }
1301
+ async create(binding) {
1302
+ const cached = this.runnableCache.get(binding);
1303
+ if (cached) {
1304
+ return cached;
1305
+ }
1306
+ const pending = this.createRunnable(binding);
1307
+ this.runnableCache.set(binding, pending);
1308
+ try {
1309
+ return await pending;
1310
+ }
1311
+ catch (error) {
1312
+ this.runnableCache.delete(binding);
1313
+ throw error;
1314
+ }
1315
+ }
1229
1316
  async route(input, primaryBinding, secondaryBinding, options = {}) {
1230
1317
  const routeModelConfig = getBindingPrimaryModel(primaryBinding) ??
1231
1318
  getBindingPrimaryModel(secondaryBinding);
@@ -1323,12 +1410,17 @@ export class AgentRuntimeAdapter {
1323
1410
  let activeRequest = request;
1324
1411
  let currentMessages = Array.isArray(activeRequest.messages) ? [...activeRequest.messages] : [];
1325
1412
  const maxToolIterations = 8;
1413
+ let pendingResult;
1326
1414
  for (let iteration = 0; iteration < maxToolIterations; iteration += 1) {
1327
- result = await callRuntimeWithToolParseRecovery(activeRequest);
1415
+ result = pendingResult ?? await callRuntimeWithToolParseRecovery(activeRequest);
1416
+ pendingResult = undefined;
1328
1417
  const toolCalls = extractToolCallsFromResult(result);
1329
1418
  if (toolCalls.length === 0) {
1330
1419
  break;
1331
1420
  }
1421
+ if (!this.canReplayToolCallsLocally(binding, toolCalls, primaryTools, toolNameMapping, executableTools, builtinExecutableTools)) {
1422
+ break;
1423
+ }
1332
1424
  if (iteration + 1 === maxToolIterations) {
1333
1425
  throw new Error(`Tool-calling loop exceeded the maximum of ${maxToolIterations} iterations`);
1334
1426
  }
@@ -261,26 +261,10 @@ export class AgentHarnessRuntime {
261
261
  return tools.every((tool) => tool.retryable === true);
262
262
  }
263
263
  async listThreads(filter) {
264
- const threadSummaries = await this.persistence.listSessions();
265
- if (!filter?.agentId) {
266
- return threadSummaries;
267
- }
268
- return threadSummaries.filter((thread) => thread.agentId === filter.agentId);
264
+ return this.persistence.listSessions(filter);
269
265
  }
270
266
  async listRuns(filter) {
271
- const runs = await this.persistence.listRuns();
272
- return runs.filter((run) => {
273
- if (filter?.agentId && run.agentId !== filter.agentId) {
274
- return false;
275
- }
276
- if (filter?.threadId && run.threadId !== filter.threadId) {
277
- return false;
278
- }
279
- if (filter?.state && run.state !== filter.state) {
280
- return false;
281
- }
282
- return true;
283
- });
267
+ return this.persistence.listRuns(filter);
284
268
  }
285
269
  async getRun(runId) {
286
270
  return this.persistence.getRun(runId);
@@ -324,21 +308,8 @@ export class AgentHarnessRuntime {
324
308
  };
325
309
  }
326
310
  async listApprovals(filter) {
327
- const approvals = filter?.threadId && filter?.runId
328
- ? await this.persistence.getRunApprovals(filter.threadId, filter.runId)
329
- : await this.persistence.listApprovals();
330
- return approvals.filter((approval) => {
331
- if (filter?.status && approval.status !== filter.status) {
332
- return false;
333
- }
334
- if (filter?.threadId && approval.threadId !== filter.threadId) {
335
- return false;
336
- }
337
- if (filter?.runId && approval.runId !== filter.runId) {
338
- return false;
339
- }
340
- return true;
341
- }).map((approval) => this.toPublicApprovalRecord(approval));
311
+ const approvals = await this.persistence.listApprovals(filter);
312
+ return approvals.map((approval) => this.toPublicApprovalRecord(approval));
342
313
  }
343
314
  async getApproval(approvalId) {
344
315
  const approval = await this.persistence.getApproval(approvalId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.81",
3
+ "version": "0.0.83",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",