@ai-sdk/harness-acp 1.0.24 → 1.0.28

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.
@@ -2,6 +2,34 @@ import type { HarnessV1BridgeToolWire } from '@ai-sdk/harness';
2
2
  import { HarnessBridgeCapabilityUnsupportedError } from '@ai-sdk/harness/bridge';
3
3
  import type { HostToolRelay } from './host-tool-relay';
4
4
 
5
+ /*
6
+ * Some ACP agents start session MCP servers only after receiving the first
7
+ * prompt. Starting that prompt before waiting lets those agents discover the
8
+ * initial catalog, while the relay's revision checks still reject stale or
9
+ * unknown tool invocations.
10
+ */
11
+ export async function promptAndRefreshInitialHostToolCatalog({
12
+ startPrompt,
13
+ relay,
14
+ tools,
15
+ harnessId,
16
+ timeoutMs,
17
+ }: {
18
+ startPrompt: () => Promise<unknown>;
19
+ relay: Pick<HostToolRelay, 'updateCatalog' | 'waitForCatalogRefresh'>;
20
+ tools: ReadonlyArray<HarnessV1BridgeToolWire>;
21
+ harnessId: string;
22
+ timeoutMs: number;
23
+ }): Promise<void> {
24
+ void startPrompt();
25
+ await refreshHostToolCatalog({
26
+ relay,
27
+ tools,
28
+ harnessId,
29
+ timeoutMs,
30
+ });
31
+ }
32
+
5
33
  export async function refreshHostToolCatalog({
6
34
  relay,
7
35
  tools,
@@ -206,6 +206,11 @@ export function createACPStreamTranslator({
206
206
  const builtin = resolveBuiltinTool({
207
207
  programmaticName,
208
208
  metadata: state.values._meta,
209
+ title: getStringProperty({
210
+ value: state.values,
211
+ property: 'title',
212
+ }),
213
+ kind: isACPToolKind(state.values.kind) ? state.values.kind : undefined,
209
214
  rawInput: state.values.rawInput,
210
215
  builtinTools,
211
216
  builtinToolsByName,
@@ -478,12 +483,16 @@ function addBuiltinToolMatch({
478
483
  function resolveBuiltinTool({
479
484
  programmaticName,
480
485
  metadata,
486
+ title,
487
+ kind,
481
488
  rawInput,
482
489
  builtinTools,
483
490
  builtinToolsByName,
484
491
  }: {
485
492
  programmaticName: string | undefined;
486
493
  metadata: unknown;
494
+ title: string | undefined;
495
+ kind: ACPToolKind | undefined;
487
496
  rawInput: unknown;
488
497
  builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
489
498
  builtinToolsByName: ReadonlyMap<string, ACPBuiltinToolMapping>;
@@ -502,12 +511,68 @@ function resolveBuiltinTool({
502
511
  : undefined;
503
512
  }
504
513
 
514
+ const titleMatch = findBuiltinToolByTitle({
515
+ title,
516
+ kind,
517
+ builtinTools,
518
+ });
519
+ if (titleMatch != null) return titleMatch;
520
+
505
521
  const schemaMatches = builtinTools.filter(tool =>
506
522
  matchesBuiltinToolInput({ rawInput, inputSchema: tool.inputSchema }),
507
523
  );
508
524
  return schemaMatches.length === 1 ? schemaMatches[0] : undefined;
509
525
  }
510
526
 
527
+ function findBuiltinToolByTitle({
528
+ title,
529
+ kind,
530
+ builtinTools,
531
+ }: {
532
+ title: string | undefined;
533
+ kind: ACPToolKind | undefined;
534
+ builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
535
+ }): ACPBuiltinToolMapping | undefined {
536
+ if (title == null) return undefined;
537
+ const matches = builtinTools.filter(
538
+ tool =>
539
+ tool.title != null &&
540
+ title.startsWith(tool.title) &&
541
+ isCompatibleToolKind({ toolUseKind: tool.toolUseKind, kind }),
542
+ );
543
+ if (matches.length === 0) return undefined;
544
+ const longestPrefixLength = Math.max(
545
+ ...matches.map(tool => tool.title!.length),
546
+ );
547
+ const longestMatches = matches.filter(
548
+ tool => tool.title!.length === longestPrefixLength,
549
+ );
550
+ return longestMatches.length === 1 ? longestMatches[0] : undefined;
551
+ }
552
+
553
+ function isCompatibleToolKind({
554
+ toolUseKind,
555
+ kind,
556
+ }: {
557
+ toolUseKind: ACPBuiltinToolMapping['toolUseKind'];
558
+ kind: ACPToolKind | undefined;
559
+ }): boolean {
560
+ if (toolUseKind == null || kind == null) return true;
561
+ switch (toolUseKind) {
562
+ case 'readonly':
563
+ return (
564
+ kind === 'read' ||
565
+ kind === 'search' ||
566
+ kind === 'fetch' ||
567
+ kind === 'think'
568
+ );
569
+ case 'edit':
570
+ return kind === 'edit' || kind === 'delete' || kind === 'move';
571
+ case 'bash':
572
+ return kind === 'execute';
573
+ }
574
+ }
575
+
511
576
  function findBuiltinToolsInMetadata({
512
577
  metadata,
513
578
  builtinToolsByName,
@@ -60,6 +60,9 @@ export function validateACPV1Implementation(
60
60
  if (source.pnpmLockYaml.length === 0) {
61
61
  throw new Error('ACP source.pnpmLockYaml must not be empty.');
62
62
  }
63
+ if (source.pnpmWorkspaceYaml?.length === 0) {
64
+ throw new Error('ACP source.pnpmWorkspaceYaml must not be empty.');
65
+ }
63
66
  } else if (source.type === 'npm-simple') {
64
67
  validateNpmSimpleSource({ source });
65
68
  } else if (source.command.trim().length === 0) {
@@ -133,6 +136,15 @@ export function getImplementationLockfile({
133
136
  return source.type === 'npm-locked' ? source.pnpmLockYaml : undefined;
134
137
  }
135
138
 
139
+ export function getImplementationWorkspaceFile({
140
+ implementation,
141
+ }: {
142
+ implementation: ACPImplementation;
143
+ }): string | undefined {
144
+ const { source } = implementation;
145
+ return source.type === 'npm-locked' ? source.pnpmWorkspaceYaml : undefined;
146
+ }
147
+
136
148
  export function createImplementationDescriptor({
137
149
  implementation,
138
150
  }: {
@@ -177,6 +189,7 @@ export function createImplementationIdentity({
177
189
  type: source.type,
178
190
  packageJson: source.packageJson,
179
191
  pnpmLockYaml: source.pnpmLockYaml,
192
+ pnpmWorkspaceYaml: source.pnpmWorkspaceYaml,
180
193
  }
181
194
  : source.type === 'npm-simple'
182
195
  ? {