@ai-sdk/harness-acp 1.0.1 → 1.0.2
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.
- package/CHANGELOG.md +11 -0
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +74 -7
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +113 -1
- package/dist/index.js +90 -8
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/acp-harness.ts +19 -0
- package/src/acp-tool-call.ts +124 -0
- package/src/index.ts +18 -0
- package/src/v1/acp-v1-bridge-protocol.ts +17 -1
- package/src/v1/acp-v1-harness.ts +79 -3
- package/src/v1/acp-v1-settings.ts +8 -0
- package/src/v1/acp-v1-turn-start-config.ts +3 -0
- package/src/v1/bridge/create-emit-stream-event.ts +8 -1
- package/src/v1/bridge/index.ts +47 -0
- package/src/v1/bridge/stream-translator.ts +62 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-acp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"ws": "^8.21.0",
|
|
30
|
-
"@ai-sdk/harness": "1.0.
|
|
31
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
30
|
+
"@ai-sdk/harness": "1.0.65",
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.26"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"zod": "^3.25.76 || ^4.1.8"
|
package/src/acp-harness.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { HarnessV1 } from '@ai-sdk/harness';
|
|
|
2
2
|
import type { ToolSet } from '@ai-sdk/provider-utils';
|
|
3
3
|
import { z } from 'zod/v4';
|
|
4
4
|
import type { ACPClientApp } from './acp-auth';
|
|
5
|
+
import type { ACPToolCall } from './acp-tool-call';
|
|
5
6
|
import { createACPV1, type ACPV1Settings } from './v1';
|
|
6
7
|
import {
|
|
7
8
|
acpColdSessionStateSchema,
|
|
@@ -16,6 +17,12 @@ const ACP_CLIENT_APP = {
|
|
|
16
17
|
|
|
17
18
|
export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
|
|
18
19
|
readonly builtinTools?: TBuiltinTools;
|
|
20
|
+
/**
|
|
21
|
+
* MCP server definitions keyed by server name. Each definition uses the
|
|
22
|
+
* underlying runtime's native MCP server configuration format.
|
|
23
|
+
*/
|
|
24
|
+
readonly mcpServers?: Record<string, unknown>;
|
|
25
|
+
readonly isMcpToolCall?: (toolCall: ACPToolCall) => boolean;
|
|
19
26
|
readonly port?: number;
|
|
20
27
|
readonly startupTimeoutMs?: number;
|
|
21
28
|
readonly clientApp?: ACPClientApp;
|
|
@@ -32,6 +39,7 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
|
|
|
32
39
|
readonly modelId?: ACPV1Settings['modelId'];
|
|
33
40
|
readonly permissionModeMapping?: ACPV1Settings['permissionModeMapping'];
|
|
34
41
|
readonly session?: ACPV1Settings['session'];
|
|
42
|
+
readonly mintBridgeToken?: ACPV1Settings['mintBridgeToken'];
|
|
35
43
|
};
|
|
36
44
|
|
|
37
45
|
const ACP_BUILTIN_TOOLS = {} as const satisfies ToolSet;
|
|
@@ -85,6 +93,17 @@ const acpResumeStateSchema = z.object({
|
|
|
85
93
|
export function createACP<TBuiltinTools extends ToolSet = {}>(
|
|
86
94
|
settings: ACPHarnessSettings<TBuiltinTools>,
|
|
87
95
|
): HarnessV1<TBuiltinTools> {
|
|
96
|
+
if (
|
|
97
|
+
settings.mcpServers != null &&
|
|
98
|
+
Object.prototype.hasOwnProperty.call(
|
|
99
|
+
settings.mcpServers,
|
|
100
|
+
'ai-sdk-harness-tools',
|
|
101
|
+
)
|
|
102
|
+
) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
'ACP MCP server name "ai-sdk-harness-tools" is reserved for HarnessAgent tools.',
|
|
105
|
+
);
|
|
106
|
+
}
|
|
88
107
|
const version = (settings as { readonly version?: string }).version ?? 'v1';
|
|
89
108
|
switch (version) {
|
|
90
109
|
case 'v1': {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export type ACPMetadata = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export type ACPRole = 'assistant' | 'user';
|
|
4
|
+
|
|
5
|
+
export type ACPAnnotations = {
|
|
6
|
+
audience?: Array<ACPRole> | null;
|
|
7
|
+
lastModified?: string | null;
|
|
8
|
+
priority?: number | null;
|
|
9
|
+
_meta?: ACPMetadata | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ACPTextContent = {
|
|
13
|
+
annotations?: ACPAnnotations | null;
|
|
14
|
+
text: string;
|
|
15
|
+
_meta?: ACPMetadata | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ACPImageContent = {
|
|
19
|
+
annotations?: ACPAnnotations | null;
|
|
20
|
+
data: string;
|
|
21
|
+
mimeType: string;
|
|
22
|
+
uri?: string | null;
|
|
23
|
+
_meta?: ACPMetadata | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ACPAudioContent = {
|
|
27
|
+
annotations?: ACPAnnotations | null;
|
|
28
|
+
data: string;
|
|
29
|
+
mimeType: string;
|
|
30
|
+
_meta?: ACPMetadata | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ACPResourceLink = {
|
|
34
|
+
annotations?: ACPAnnotations | null;
|
|
35
|
+
description?: string | null;
|
|
36
|
+
mimeType?: string | null;
|
|
37
|
+
name: string;
|
|
38
|
+
size?: number | null;
|
|
39
|
+
title?: string | null;
|
|
40
|
+
uri: string;
|
|
41
|
+
_meta?: ACPMetadata | null;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ACPTextResourceContents = {
|
|
45
|
+
mimeType?: string | null;
|
|
46
|
+
text: string;
|
|
47
|
+
uri: string;
|
|
48
|
+
_meta?: ACPMetadata | null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type ACPBlobResourceContents = {
|
|
52
|
+
blob: string;
|
|
53
|
+
mimeType?: string | null;
|
|
54
|
+
uri: string;
|
|
55
|
+
_meta?: ACPMetadata | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ACPEmbeddedResource = {
|
|
59
|
+
annotations?: ACPAnnotations | null;
|
|
60
|
+
resource: ACPTextResourceContents | ACPBlobResourceContents;
|
|
61
|
+
_meta?: ACPMetadata | null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type ACPContentBlock =
|
|
65
|
+
| (ACPTextContent & { type: 'text' })
|
|
66
|
+
| (ACPImageContent & { type: 'image' })
|
|
67
|
+
| (ACPAudioContent & { type: 'audio' })
|
|
68
|
+
| (ACPResourceLink & { type: 'resource_link' })
|
|
69
|
+
| (ACPEmbeddedResource & { type: 'resource' });
|
|
70
|
+
|
|
71
|
+
export type ACPToolCallContent =
|
|
72
|
+
| {
|
|
73
|
+
type: 'content';
|
|
74
|
+
content: ACPContentBlock;
|
|
75
|
+
_meta?: ACPMetadata | null;
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
type: 'diff';
|
|
79
|
+
path: string;
|
|
80
|
+
oldText?: string | null;
|
|
81
|
+
newText: string;
|
|
82
|
+
_meta?: ACPMetadata | null;
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
type: 'terminal';
|
|
86
|
+
terminalId: string;
|
|
87
|
+
_meta?: ACPMetadata | null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type ACPToolKind =
|
|
91
|
+
| 'read'
|
|
92
|
+
| 'edit'
|
|
93
|
+
| 'delete'
|
|
94
|
+
| 'move'
|
|
95
|
+
| 'search'
|
|
96
|
+
| 'execute'
|
|
97
|
+
| 'think'
|
|
98
|
+
| 'fetch'
|
|
99
|
+
| 'switch_mode'
|
|
100
|
+
| 'other';
|
|
101
|
+
|
|
102
|
+
export type ACPToolCallStatus =
|
|
103
|
+
| 'pending'
|
|
104
|
+
| 'in_progress'
|
|
105
|
+
| 'completed'
|
|
106
|
+
| 'failed';
|
|
107
|
+
|
|
108
|
+
export type ACPToolCallLocation = {
|
|
109
|
+
path: string;
|
|
110
|
+
line?: number | null;
|
|
111
|
+
_meta?: ACPMetadata | null;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type ACPToolCall = {
|
|
115
|
+
toolCallId: string;
|
|
116
|
+
title: string;
|
|
117
|
+
kind?: ACPToolKind;
|
|
118
|
+
status?: ACPToolCallStatus;
|
|
119
|
+
content?: Array<ACPToolCallContent>;
|
|
120
|
+
locations?: Array<ACPToolCallLocation>;
|
|
121
|
+
rawInput?: unknown;
|
|
122
|
+
rawOutput?: unknown;
|
|
123
|
+
_meta?: ACPMetadata | null;
|
|
124
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
export { createACP } from './acp-harness';
|
|
2
2
|
export type { ACPAuthOptions, ACPClientApp } from './acp-auth';
|
|
3
3
|
export type { ACPHarnessSettings } from './acp-harness';
|
|
4
|
+
export type {
|
|
5
|
+
ACPAnnotations,
|
|
6
|
+
ACPAudioContent,
|
|
7
|
+
ACPBlobResourceContents,
|
|
8
|
+
ACPContentBlock,
|
|
9
|
+
ACPEmbeddedResource,
|
|
10
|
+
ACPImageContent,
|
|
11
|
+
ACPMetadata,
|
|
12
|
+
ACPResourceLink,
|
|
13
|
+
ACPRole,
|
|
14
|
+
ACPTextContent,
|
|
15
|
+
ACPTextResourceContents,
|
|
16
|
+
ACPToolCall,
|
|
17
|
+
ACPToolCallContent,
|
|
18
|
+
ACPToolCallLocation,
|
|
19
|
+
ACPToolCallStatus,
|
|
20
|
+
ACPToolKind,
|
|
21
|
+
} from './acp-tool-call';
|
|
4
22
|
export type {
|
|
5
23
|
ACPAuthentication,
|
|
6
24
|
ACPNpmLockedSource,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type HarnessV1ToolSpec,
|
|
12
12
|
} from '@ai-sdk/harness';
|
|
13
13
|
import { z } from 'zod/v4';
|
|
14
|
+
import type { ACPToolCall } from '../acp-tool-call';
|
|
14
15
|
import type { ACPPermissionModeMapping } from './acp-v1-settings';
|
|
15
16
|
import { acpTextContentBlockSchema } from './acp-v1-prompt';
|
|
16
17
|
|
|
@@ -126,11 +127,26 @@ const coldRestoreSchema = z.object({
|
|
|
126
127
|
acpSessionId: z.string(),
|
|
127
128
|
});
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
const acpToolCallCandidateSchema = z.object({
|
|
131
|
+
type: z.literal('acp-tool-call-candidate'),
|
|
132
|
+
toolCall: z.custom<ACPToolCall>(
|
|
133
|
+
value =>
|
|
134
|
+
value != null &&
|
|
135
|
+
typeof value === 'object' &&
|
|
136
|
+
typeof (value as { toolCallId?: unknown }).toolCallId === 'string' &&
|
|
137
|
+
typeof (value as { title?: unknown }).title === 'string',
|
|
138
|
+
),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export const outboundMessageSchema = z.union([
|
|
142
|
+
harnessV1BridgeOutboundMessageSchema,
|
|
143
|
+
acpToolCallCandidateSchema,
|
|
144
|
+
]);
|
|
130
145
|
export type OutboundMessage = z.infer<typeof outboundMessageSchema>;
|
|
131
146
|
|
|
132
147
|
export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
133
148
|
prompt: z.array(acpTextContentBlockSchema),
|
|
149
|
+
mcpServers: z.record(z.string(), z.unknown()).optional(),
|
|
134
150
|
tools: z.array(acpSerializableToolSpecSchema).optional(),
|
|
135
151
|
builtinTools: z.array(builtinToolSchema).readonly().default([]),
|
|
136
152
|
permissionModeMapping: permissionModeMappingSchema.optional(),
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
type ACPAuthenticationProfileIdentity,
|
|
41
41
|
type ACPClientApp,
|
|
42
42
|
} from '../acp-auth';
|
|
43
|
+
import type { ACPToolCall } from '../acp-tool-call';
|
|
43
44
|
import {
|
|
44
45
|
createACPV1Implementation,
|
|
45
46
|
createImplementationDescriptor,
|
|
@@ -126,6 +127,17 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
126
127
|
HarnessV1<TBuiltinTools>['lifecycleStateSchema']
|
|
127
128
|
>;
|
|
128
129
|
}): HarnessV1<TBuiltinTools> {
|
|
130
|
+
if (
|
|
131
|
+
settings.mcpServers != null &&
|
|
132
|
+
Object.prototype.hasOwnProperty.call(
|
|
133
|
+
settings.mcpServers,
|
|
134
|
+
'ai-sdk-harness-tools',
|
|
135
|
+
)
|
|
136
|
+
) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'ACP MCP server name "ai-sdk-harness-tools" is reserved for HarnessAgent tools.',
|
|
139
|
+
);
|
|
140
|
+
}
|
|
129
141
|
if (!HARNESS_ID_REGEXP.test(settings.harnessId)) {
|
|
130
142
|
throw new Error(
|
|
131
143
|
`ACP harnessId must be a stable kebab-case identifier; received ${JSON.stringify(settings.harnessId)}.`,
|
|
@@ -370,6 +382,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
370
382
|
builtinTools: builtinToolCatalog,
|
|
371
383
|
permissionMode,
|
|
372
384
|
permissionModeMapping,
|
|
385
|
+
mcpServers: settings.mcpServers,
|
|
386
|
+
isMcpToolCall: settings.isMcpToolCall,
|
|
373
387
|
initialGuidanceApplied: resolveACPInitialGuidanceApplied({
|
|
374
388
|
isResume: true,
|
|
375
389
|
lifecycleState: lifecycleData,
|
|
@@ -421,6 +435,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
421
435
|
sessionMeta: settings.session?.meta,
|
|
422
436
|
builtinTools: builtinToolCatalog,
|
|
423
437
|
permissionModeMapping,
|
|
438
|
+
mcpServers: settings.mcpServers,
|
|
424
439
|
});
|
|
425
440
|
respawnStrategy = {
|
|
426
441
|
mode: 'lossy-rerun',
|
|
@@ -450,6 +465,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
450
465
|
sessionMeta: settings.session?.meta,
|
|
451
466
|
builtinTools: builtinToolCatalog,
|
|
452
467
|
permissionModeMapping,
|
|
468
|
+
mcpServers: settings.mcpServers,
|
|
453
469
|
debug: startOptions.observability?.debug,
|
|
454
470
|
});
|
|
455
471
|
respawnStrategy = {
|
|
@@ -465,7 +481,10 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
465
481
|
override: portOverride,
|
|
466
482
|
harnessId: settings.harnessId,
|
|
467
483
|
});
|
|
468
|
-
const token =
|
|
484
|
+
const token =
|
|
485
|
+
settings.mintBridgeToken == null
|
|
486
|
+
? randomBytes(32).toString('hex')
|
|
487
|
+
: settings.mintBridgeToken(sandboxSession.id);
|
|
469
488
|
await sandbox.run({
|
|
470
489
|
command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
|
|
471
490
|
abortSignal: startOptions.abortSignal,
|
|
@@ -570,6 +589,9 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
570
589
|
builtinTools: builtinToolCatalog,
|
|
571
590
|
permissionMode,
|
|
572
591
|
permissionModeMapping,
|
|
592
|
+
...(settings.mcpServers == null
|
|
593
|
+
? {}
|
|
594
|
+
: { mcpServers: settings.mcpServers }),
|
|
573
595
|
tools: respawnStrategy.turnStartConfig.tools,
|
|
574
596
|
turnStartConfig: respawnStrategy.turnStartConfig,
|
|
575
597
|
recoveryMode: {
|
|
@@ -606,6 +628,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
606
628
|
builtinTools: builtinToolCatalog,
|
|
607
629
|
permissionMode,
|
|
608
630
|
permissionModeMapping,
|
|
631
|
+
mcpServers: settings.mcpServers,
|
|
632
|
+
isMcpToolCall: settings.isMcpToolCall,
|
|
609
633
|
initialGuidanceApplied: resolveACPInitialGuidanceApplied({
|
|
610
634
|
isResume,
|
|
611
635
|
lifecycleState: lifecycleData,
|
|
@@ -807,6 +831,8 @@ function createSession({
|
|
|
807
831
|
builtinTools,
|
|
808
832
|
permissionMode,
|
|
809
833
|
permissionModeMapping,
|
|
834
|
+
mcpServers,
|
|
835
|
+
isMcpToolCall,
|
|
810
836
|
initialGuidanceApplied: initialGuidanceAppliedAtStart,
|
|
811
837
|
skillCatalog,
|
|
812
838
|
skillsFingerprint,
|
|
@@ -835,6 +861,8 @@ function createSession({
|
|
|
835
861
|
builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
|
|
836
862
|
permissionMode: NonNullable<StartMessage['permissionMode']>;
|
|
837
863
|
permissionModeMapping: StartMessage['permissionModeMapping'];
|
|
864
|
+
mcpServers: Record<string, unknown> | undefined;
|
|
865
|
+
isMcpToolCall: ((toolCall: ACPToolCall) => boolean) | undefined;
|
|
838
866
|
initialGuidanceApplied: boolean;
|
|
839
867
|
skillCatalog: ReadonlyArray<ACPSkillCatalogEntry>;
|
|
840
868
|
skillsFingerprint: string;
|
|
@@ -889,6 +917,8 @@ function createSession({
|
|
|
889
917
|
let openBlock:
|
|
890
918
|
| { readonly type: 'text' | 'reasoning'; readonly id: string }
|
|
891
919
|
| undefined;
|
|
920
|
+
const dynamicToolCalls = new Map<string, boolean>();
|
|
921
|
+
const toolCallClassificationErrors = new Map<string, unknown>();
|
|
892
922
|
const subscriptions: Array<() => void> = [];
|
|
893
923
|
const forward = (event: HarnessV1StreamPart) => {
|
|
894
924
|
if (event.type === 'text-start' || event.type === 'reasoning-start') {
|
|
@@ -923,9 +953,7 @@ function createSession({
|
|
|
923
953
|
'reasoning-start',
|
|
924
954
|
'reasoning-delta',
|
|
925
955
|
'reasoning-end',
|
|
926
|
-
'tool-call',
|
|
927
956
|
'tool-approval-request',
|
|
928
|
-
'tool-result',
|
|
929
957
|
'file-change',
|
|
930
958
|
'finish-step',
|
|
931
959
|
'raw',
|
|
@@ -954,6 +982,45 @@ function createSession({
|
|
|
954
982
|
settle({ error: abortError });
|
|
955
983
|
}
|
|
956
984
|
};
|
|
985
|
+
subscriptions.push(
|
|
986
|
+
channel.on('acp-tool-call-candidate', event => {
|
|
987
|
+
try {
|
|
988
|
+
dynamicToolCalls.set(
|
|
989
|
+
event.toolCall.toolCallId,
|
|
990
|
+
isMcpToolCall?.(event.toolCall) === true,
|
|
991
|
+
);
|
|
992
|
+
} catch (error) {
|
|
993
|
+
toolCallClassificationErrors.set(event.toolCall.toolCallId, error);
|
|
994
|
+
}
|
|
995
|
+
}),
|
|
996
|
+
);
|
|
997
|
+
subscriptions.push(
|
|
998
|
+
channel.on('tool-call', event => {
|
|
999
|
+
if (toolCallClassificationErrors.has(event.toolCallId)) {
|
|
1000
|
+
const error = toolCallClassificationErrors.get(event.toolCallId);
|
|
1001
|
+
closeForwardedBlock();
|
|
1002
|
+
forward({ type: 'error', error });
|
|
1003
|
+
try {
|
|
1004
|
+
channel.send({ type: 'abort' });
|
|
1005
|
+
} catch {}
|
|
1006
|
+
settle({ error });
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
forward(
|
|
1010
|
+
dynamicToolCalls.get(event.toolCallId) === true
|
|
1011
|
+
? { ...event, dynamic: true }
|
|
1012
|
+
: event,
|
|
1013
|
+
);
|
|
1014
|
+
}),
|
|
1015
|
+
);
|
|
1016
|
+
subscriptions.push(
|
|
1017
|
+
channel.on('tool-result', event => {
|
|
1018
|
+
const dynamic = dynamicToolCalls.get(event.toolCallId) === true;
|
|
1019
|
+
dynamicToolCalls.delete(event.toolCallId);
|
|
1020
|
+
toolCallClassificationErrors.delete(event.toolCallId);
|
|
1021
|
+
forward(dynamic ? { ...event, dynamic: true } : event);
|
|
1022
|
+
}),
|
|
1023
|
+
);
|
|
957
1024
|
for (const type of eventTypes) {
|
|
958
1025
|
subscriptions.push(channel.on(type, event => forward(event)));
|
|
959
1026
|
}
|
|
@@ -1110,6 +1177,7 @@ function createSession({
|
|
|
1110
1177
|
builtinTools,
|
|
1111
1178
|
permissionMode,
|
|
1112
1179
|
permissionModeMapping,
|
|
1180
|
+
mcpServers,
|
|
1113
1181
|
debug,
|
|
1114
1182
|
authenticationProfile,
|
|
1115
1183
|
sessionMeta,
|
|
@@ -1137,6 +1205,7 @@ function createSession({
|
|
|
1137
1205
|
builtinTools,
|
|
1138
1206
|
permissionMode,
|
|
1139
1207
|
permissionModeMapping,
|
|
1208
|
+
...(mcpServers == null ? {} : { mcpServers }),
|
|
1140
1209
|
tools: options.tools == null ? undefined : turnStartConfig.tools,
|
|
1141
1210
|
turnStartConfig,
|
|
1142
1211
|
});
|
|
@@ -1174,6 +1243,7 @@ function createSession({
|
|
|
1174
1243
|
builtinTools: turnStartConfig.builtinTools,
|
|
1175
1244
|
permissionMode: turnStartConfig.permissionMode,
|
|
1176
1245
|
permissionModeMapping: turnStartConfig.permissionModeMapping,
|
|
1246
|
+
...(mcpServers == null ? {} : { mcpServers }),
|
|
1177
1247
|
tools: turnStartConfig.tools,
|
|
1178
1248
|
turnStartConfig,
|
|
1179
1249
|
recoveryMode: {
|
|
@@ -1338,12 +1408,14 @@ function validateACPTurnStartConfig({
|
|
|
1338
1408
|
sessionMeta,
|
|
1339
1409
|
builtinTools,
|
|
1340
1410
|
permissionModeMapping,
|
|
1411
|
+
mcpServers,
|
|
1341
1412
|
}: {
|
|
1342
1413
|
turnStartConfig: ACPTurnStartConfig;
|
|
1343
1414
|
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
1344
1415
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
1345
1416
|
builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
|
|
1346
1417
|
permissionModeMapping: ACPPermissionModeMapping | undefined;
|
|
1418
|
+
mcpServers: Record<string, unknown> | undefined;
|
|
1347
1419
|
}): void {
|
|
1348
1420
|
const current = createACPTurnStartConfig({
|
|
1349
1421
|
prompt: turnStartConfig.prompt,
|
|
@@ -1351,6 +1423,7 @@ function validateACPTurnStartConfig({
|
|
|
1351
1423
|
builtinTools,
|
|
1352
1424
|
permissionMode: turnStartConfig.permissionMode,
|
|
1353
1425
|
permissionModeMapping,
|
|
1426
|
+
mcpServers,
|
|
1354
1427
|
debug: turnStartConfig.debug,
|
|
1355
1428
|
authenticationProfile,
|
|
1356
1429
|
sessionMeta,
|
|
@@ -1373,6 +1446,7 @@ function validateACPColdSessionConfiguration({
|
|
|
1373
1446
|
sessionMeta,
|
|
1374
1447
|
builtinTools,
|
|
1375
1448
|
permissionModeMapping,
|
|
1449
|
+
mcpServers,
|
|
1376
1450
|
debug,
|
|
1377
1451
|
}: {
|
|
1378
1452
|
coldSession: ACPColdSessionState;
|
|
@@ -1382,6 +1456,7 @@ function validateACPColdSessionConfiguration({
|
|
|
1382
1456
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
1383
1457
|
builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
|
|
1384
1458
|
permissionModeMapping: ACPPermissionModeMapping | undefined;
|
|
1459
|
+
mcpServers: Record<string, unknown> | undefined;
|
|
1385
1460
|
debug: HarnessV1DebugConfig | undefined;
|
|
1386
1461
|
}): ACPTurnStartConfig {
|
|
1387
1462
|
const current = createACPTurnStartConfig({
|
|
@@ -1390,6 +1465,7 @@ function validateACPColdSessionConfiguration({
|
|
|
1390
1465
|
builtinTools,
|
|
1391
1466
|
permissionMode,
|
|
1392
1467
|
permissionModeMapping,
|
|
1468
|
+
mcpServers,
|
|
1393
1469
|
debug,
|
|
1394
1470
|
authenticationProfile,
|
|
1395
1471
|
sessionMeta,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HarnessV1PermissionMode } from '@ai-sdk/harness';
|
|
2
|
+
import type { ACPToolCall } from '../acp-tool-call';
|
|
2
3
|
|
|
3
4
|
export type ACPSerializablePrimitive = string | number | boolean | null;
|
|
4
5
|
|
|
@@ -77,6 +78,8 @@ export type ACPPermissionModeMapping = Readonly<
|
|
|
77
78
|
export type ACPV1Settings = {
|
|
78
79
|
readonly version?: 'v1';
|
|
79
80
|
readonly harnessId: string;
|
|
81
|
+
readonly mcpServers?: Record<string, unknown>;
|
|
82
|
+
readonly isMcpToolCall?: (toolCall: ACPToolCall) => boolean;
|
|
80
83
|
readonly auth?: ACPProviderAuthenticationMode;
|
|
81
84
|
readonly source: ACPSource;
|
|
82
85
|
readonly executable: string;
|
|
@@ -94,4 +97,9 @@ export type ACPV1Settings = {
|
|
|
94
97
|
readonly session?: {
|
|
95
98
|
readonly meta?: Readonly<Record<string, ACPSerializableValue>>;
|
|
96
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* Creates the authentication token used by the sandbox bridge. Defaults to
|
|
102
|
+
* a random 32-byte hexadecimal token.
|
|
103
|
+
*/
|
|
104
|
+
readonly mintBridgeToken?: (sandboxId: string) => string;
|
|
97
105
|
};
|
|
@@ -20,6 +20,7 @@ export function createACPTurnStartConfig({
|
|
|
20
20
|
builtinTools,
|
|
21
21
|
permissionMode,
|
|
22
22
|
permissionModeMapping,
|
|
23
|
+
mcpServers,
|
|
23
24
|
debug,
|
|
24
25
|
authenticationProfile,
|
|
25
26
|
sessionMeta,
|
|
@@ -29,6 +30,7 @@ export function createACPTurnStartConfig({
|
|
|
29
30
|
builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
|
|
30
31
|
permissionMode: NonNullable<StartMessage['permissionMode']>;
|
|
31
32
|
permissionModeMapping: ACPPermissionModeMapping | undefined;
|
|
33
|
+
mcpServers: Record<string, unknown> | undefined;
|
|
32
34
|
debug: HarnessV1DebugConfig | undefined;
|
|
33
35
|
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
34
36
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
@@ -42,6 +44,7 @@ export function createACPTurnStartConfig({
|
|
|
42
44
|
sessionMeta: sessionMeta ?? null,
|
|
43
45
|
builtinTools,
|
|
44
46
|
permissionModeMapping: permissionModeMapping ?? null,
|
|
47
|
+
mcpServers: mcpServers ?? null,
|
|
45
48
|
}),
|
|
46
49
|
)
|
|
47
50
|
.digest('hex'),
|
|
@@ -6,17 +6,20 @@ import type {
|
|
|
6
6
|
HarnessV1BridgeToolWire,
|
|
7
7
|
HarnessV1StreamPart,
|
|
8
8
|
} from '@ai-sdk/harness';
|
|
9
|
+
import type { ACPToolCall } from '../../acp-tool-call';
|
|
9
10
|
import type { ACPBuiltinToolMapping } from '../acp-v1-bridge-protocol';
|
|
10
11
|
import { createACPStreamTranslator } from './stream-translator';
|
|
11
12
|
import { createHostToolCorrelation } from './host-tool-correlation';
|
|
12
13
|
|
|
13
14
|
export function createEmitStreamEvent({
|
|
14
15
|
emit,
|
|
16
|
+
emitToolCallCandidate,
|
|
15
17
|
builtinTools,
|
|
16
18
|
hostToolServerName,
|
|
17
19
|
hostTools,
|
|
18
20
|
}: {
|
|
19
21
|
emit: (event: HarnessV1StreamPart) => void;
|
|
22
|
+
emitToolCallCandidate: (options: { toolCall: ACPToolCall }) => void;
|
|
20
23
|
builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
|
|
21
24
|
hostToolServerName: string;
|
|
22
25
|
hostTools: ReadonlyArray<HarnessV1BridgeToolWire>;
|
|
@@ -49,7 +52,11 @@ export function createEmitStreamEvent({
|
|
|
49
52
|
}) => void;
|
|
50
53
|
removeHostToolCorrelationInvocation: (options: { token: string }) => void;
|
|
51
54
|
} {
|
|
52
|
-
const translator = createACPStreamTranslator({
|
|
55
|
+
const translator = createACPStreamTranslator({
|
|
56
|
+
emit,
|
|
57
|
+
emitToolCallCandidate,
|
|
58
|
+
builtinTools,
|
|
59
|
+
});
|
|
53
60
|
const correlation = createHostToolCorrelation({
|
|
54
61
|
emitSemanticUpdate: ({ message, rawUpdate }) => {
|
|
55
62
|
translator.update({
|
package/src/v1/bridge/index.ts
CHANGED
|
@@ -230,6 +230,12 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
230
230
|
turn.emit({ type: 'stream-start' });
|
|
231
231
|
const emitStreamEvent = createEmitStreamEvent({
|
|
232
232
|
emit: event => turn.emit(event as BridgeEvent),
|
|
233
|
+
emitToolCallCandidate: ({ toolCall }) => {
|
|
234
|
+
turn.emit({
|
|
235
|
+
type: 'acp-tool-call-candidate',
|
|
236
|
+
toolCall,
|
|
237
|
+
});
|
|
238
|
+
},
|
|
233
239
|
builtinTools: start.builtinTools,
|
|
234
240
|
hostToolServerName: HOST_TOOL_MCP_SERVER_NAME,
|
|
235
241
|
hostTools: start.tools ?? [],
|
|
@@ -322,6 +328,7 @@ async function ensureSession({
|
|
|
322
328
|
sessionMeta: bridgeConfiguration.sessionMeta,
|
|
323
329
|
permissionMode: start.permissionMode,
|
|
324
330
|
permissionModeMapping: start.permissionModeMapping,
|
|
331
|
+
mcpServers: start.mcpServers,
|
|
325
332
|
});
|
|
326
333
|
if (session != null) {
|
|
327
334
|
if (catalogRefreshError != null) throw catalogRefreshError;
|
|
@@ -437,6 +444,10 @@ async function ensureSession({
|
|
|
437
444
|
authentication,
|
|
438
445
|
});
|
|
439
446
|
}
|
|
447
|
+
const externalMcpServers = createExternalMcpServers({
|
|
448
|
+
mcpServers: start.mcpServers,
|
|
449
|
+
initialization,
|
|
450
|
+
});
|
|
440
451
|
const tools = start.tools ?? [];
|
|
441
452
|
const catalogPath = `${bridgeStateDir}/host-tools.json`;
|
|
442
453
|
await writeFile(catalogPath, JSON.stringify(tools), { mode: 0o600 });
|
|
@@ -446,6 +457,7 @@ async function ensureSession({
|
|
|
446
457
|
});
|
|
447
458
|
|
|
448
459
|
const mcpServers: acp.McpServer[] = [
|
|
460
|
+
...externalMcpServers,
|
|
449
461
|
{
|
|
450
462
|
name: HOST_TOOL_MCP_SERVER_NAME,
|
|
451
463
|
command: process.execPath,
|
|
@@ -557,6 +569,41 @@ async function ensureSession({
|
|
|
557
569
|
sessionConfigurationFingerprint = fingerprint;
|
|
558
570
|
}
|
|
559
571
|
|
|
572
|
+
function createExternalMcpServers({
|
|
573
|
+
mcpServers,
|
|
574
|
+
initialization,
|
|
575
|
+
}: {
|
|
576
|
+
mcpServers: Record<string, unknown> | undefined;
|
|
577
|
+
initialization: acp.InitializeResponse;
|
|
578
|
+
}): acp.McpServer[] {
|
|
579
|
+
if (mcpServers == null) return [];
|
|
580
|
+
return Object.entries(mcpServers).map(([name, value]) => {
|
|
581
|
+
if (!isRecord(value)) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
`ACP MCP server ${JSON.stringify(name)} must be configured with an object value.`,
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
if (value.type === 'acp') {
|
|
587
|
+
throw new HarnessBridgeCapabilityUnsupportedError({
|
|
588
|
+
harnessId: bridgeType,
|
|
589
|
+
message:
|
|
590
|
+
'ACP-transport MCP servers require client-side mcp/connect handling, which this harness does not provide.',
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
const mcpCapabilities = initialization.agentCapabilities?.mcpCapabilities;
|
|
594
|
+
if (
|
|
595
|
+
(value.type === 'http' && mcpCapabilities?.http !== true) ||
|
|
596
|
+
(value.type === 'sse' && mcpCapabilities?.sse !== true)
|
|
597
|
+
) {
|
|
598
|
+
throw new HarnessBridgeCapabilityUnsupportedError({
|
|
599
|
+
harnessId: bridgeType,
|
|
600
|
+
message: `The ACP agent does not advertise support for ${value.type.toUpperCase()} MCP servers.`,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
return { ...value, name } as acp.McpServer;
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
560
607
|
function unknownUsage() {
|
|
561
608
|
return {
|
|
562
609
|
inputTokens: {
|