@ai-sdk/harness-acp 1.0.0 → 1.0.1
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/README.md +5 -6
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +71 -32
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +25 -19
- package/dist/index.js +98 -103
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/acp-harness.ts +7 -60
- package/src/index.ts +3 -3
- package/src/v1/acp-v1-harness.ts +32 -18
- package/src/v1/acp-v1-settings.ts +19 -20
- package/src/v1/bridge/acp-diagnostics.ts +27 -1
- package/src/v1/bridge/agent-stderr-monitor.ts +52 -0
- package/src/v1/bridge/index.ts +19 -34
- package/src/v1/implementation.ts +73 -47
- package/src/v1/index.ts +3 -3
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.1",
|
|
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.64",
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.25"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"zod": "^3.25.76 || ^4.1.8"
|
package/src/acp-harness.ts
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
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
|
-
import {
|
|
5
|
-
createACPAuthenticationProfileIdentity,
|
|
6
|
-
resolveACPProviderAuthenticationCompatibility,
|
|
7
|
-
type ACPAuthenticationProfileIdentity,
|
|
8
|
-
type ACPClientApp,
|
|
9
|
-
} from './acp-auth';
|
|
4
|
+
import type { ACPClientApp } from './acp-auth';
|
|
10
5
|
import { createACPV1, type ACPV1Settings } from './v1';
|
|
11
|
-
import { createImplementationIdentity } from './v1/implementation';
|
|
12
6
|
import {
|
|
13
7
|
acpColdSessionStateSchema,
|
|
14
8
|
acpTurnStartConfigSchema,
|
|
15
|
-
type ACPColdSessionState,
|
|
16
|
-
type ACPTurnStartConfig,
|
|
17
9
|
} from './v1/acp-v1-bridge-protocol';
|
|
18
10
|
import { VERSION } from './version';
|
|
19
11
|
|
|
@@ -30,7 +22,11 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
|
|
|
30
22
|
readonly version?: ACPV1Settings['version'];
|
|
31
23
|
readonly harnessId: ACPV1Settings['harnessId'];
|
|
32
24
|
readonly auth?: ACPV1Settings['auth'];
|
|
33
|
-
readonly
|
|
25
|
+
readonly source: ACPV1Settings['source'];
|
|
26
|
+
readonly executable: ACPV1Settings['executable'];
|
|
27
|
+
readonly args?: ACPV1Settings['args'];
|
|
28
|
+
readonly forwardEnv?: ACPV1Settings['forwardEnv'];
|
|
29
|
+
readonly env?: ACPV1Settings['env'];
|
|
34
30
|
readonly authentication?: ACPV1Settings['authentication'];
|
|
35
31
|
readonly providerAuthentication?: ACPV1Settings['providerAuthentication'];
|
|
36
32
|
readonly modelId?: ACPV1Settings['modelId'];
|
|
@@ -86,8 +82,6 @@ const acpResumeStateSchema = z.object({
|
|
|
86
82
|
skillsFingerprint: z.string().optional(),
|
|
87
83
|
});
|
|
88
84
|
|
|
89
|
-
type ACPBridgeCoords = z.infer<typeof acpBridgeCoordsSchema>;
|
|
90
|
-
|
|
91
85
|
export function createACP<TBuiltinTools extends ToolSet = {}>(
|
|
92
86
|
settings: ACPHarnessSettings<TBuiltinTools>,
|
|
93
87
|
): HarnessV1<TBuiltinTools> {
|
|
@@ -95,50 +89,6 @@ export function createACP<TBuiltinTools extends ToolSet = {}>(
|
|
|
95
89
|
switch (version) {
|
|
96
90
|
case 'v1': {
|
|
97
91
|
const clientApp = settings.clientApp ?? ACP_CLIENT_APP;
|
|
98
|
-
const providerAuthenticationCompatibility =
|
|
99
|
-
resolveACPProviderAuthenticationCompatibility({
|
|
100
|
-
auth: settings.auth,
|
|
101
|
-
providerAuthentication: settings.providerAuthentication,
|
|
102
|
-
env: process.env,
|
|
103
|
-
});
|
|
104
|
-
const implementationIdentity = createImplementationIdentity({
|
|
105
|
-
harnessId: settings.harnessId,
|
|
106
|
-
acpVersion: version,
|
|
107
|
-
implementation: settings.implementation,
|
|
108
|
-
clientApp,
|
|
109
|
-
providerAuthentication: providerAuthenticationCompatibility,
|
|
110
|
-
permissionModeMapping: settings.permissionModeMapping,
|
|
111
|
-
});
|
|
112
|
-
const authenticationProfile = createACPAuthenticationProfileIdentity({
|
|
113
|
-
authentication: settings.authentication,
|
|
114
|
-
providerAuthenticationCompatibility,
|
|
115
|
-
});
|
|
116
|
-
const lifecycleStateSchema: z.ZodType<{
|
|
117
|
-
readonly implementationIdentity: string;
|
|
118
|
-
readonly authenticationProfile?: ACPAuthenticationProfileIdentity;
|
|
119
|
-
readonly acpSessionId?: string;
|
|
120
|
-
readonly bridge?: ACPBridgeCoords;
|
|
121
|
-
readonly coldSession?: ACPColdSessionState;
|
|
122
|
-
readonly turnStartConfig?: ACPTurnStartConfig;
|
|
123
|
-
readonly recovery?: {
|
|
124
|
-
readonly mode: 'disk-replay' | 'lossy-rerun';
|
|
125
|
-
readonly reason: string;
|
|
126
|
-
};
|
|
127
|
-
readonly restoration?: {
|
|
128
|
-
readonly method: 'resume' | 'load';
|
|
129
|
-
};
|
|
130
|
-
readonly initialGuidanceApplied?: boolean;
|
|
131
|
-
readonly skillsMaterialized?: boolean;
|
|
132
|
-
readonly skillsFingerprint?: string;
|
|
133
|
-
}> = acpResumeStateSchema.extend({
|
|
134
|
-
implementationIdentity: z.literal(implementationIdentity),
|
|
135
|
-
authenticationProfile: acpResumeStateSchema.shape.authenticationProfile
|
|
136
|
-
.unwrap()
|
|
137
|
-
.extend({
|
|
138
|
-
digest: z.literal(authenticationProfile.digest),
|
|
139
|
-
})
|
|
140
|
-
.optional(),
|
|
141
|
-
});
|
|
142
92
|
return createACPV1({
|
|
143
93
|
settings,
|
|
144
94
|
builtinTools:
|
|
@@ -146,10 +96,7 @@ export function createACP<TBuiltinTools extends ToolSet = {}>(
|
|
|
146
96
|
port: settings.port,
|
|
147
97
|
startupTimeoutMs: settings.startupTimeoutMs,
|
|
148
98
|
clientApp,
|
|
149
|
-
|
|
150
|
-
authenticationProfile,
|
|
151
|
-
providerAuthenticationCompatibility,
|
|
152
|
-
lifecycleStateSchema,
|
|
99
|
+
lifecycleStateSchema: acpResumeStateSchema,
|
|
153
100
|
});
|
|
154
101
|
}
|
|
155
102
|
default:
|
package/src/index.ts
CHANGED
|
@@ -3,8 +3,8 @@ export type { ACPAuthOptions, ACPClientApp } from './acp-auth';
|
|
|
3
3
|
export type { ACPHarnessSettings } from './acp-harness';
|
|
4
4
|
export type {
|
|
5
5
|
ACPAuthentication,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
ACPNpmLockedSource,
|
|
7
|
+
ACPNpmSimpleSource,
|
|
8
8
|
ACPPermissionModeMapping,
|
|
9
9
|
ACPPermissionModeTarget,
|
|
10
10
|
ACPProfileValue,
|
|
@@ -12,7 +12,7 @@ export type {
|
|
|
12
12
|
ACPProviderAuthenticationMode,
|
|
13
13
|
ACPSerializablePrimitive,
|
|
14
14
|
ACPSerializableValue,
|
|
15
|
-
|
|
15
|
+
ACPSource,
|
|
16
16
|
ACPV1Settings,
|
|
17
17
|
ACPValueSource,
|
|
18
18
|
} from './v1';
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -34,13 +34,16 @@ import {
|
|
|
34
34
|
} from '@ai-sdk/provider-utils';
|
|
35
35
|
import { WebSocket } from 'ws';
|
|
36
36
|
import {
|
|
37
|
+
createACPAuthenticationProfileIdentity,
|
|
37
38
|
resolveACPProviderAuthentication,
|
|
39
|
+
resolveACPProviderAuthenticationCompatibility,
|
|
38
40
|
type ACPAuthenticationProfileIdentity,
|
|
39
41
|
type ACPClientApp,
|
|
40
|
-
type ACPProviderAuthenticationCompatibility,
|
|
41
42
|
} from '../acp-auth';
|
|
42
43
|
import {
|
|
44
|
+
createACPV1Implementation,
|
|
43
45
|
createImplementationDescriptor,
|
|
46
|
+
createImplementationIdentity,
|
|
44
47
|
createImplementationInstallCommand,
|
|
45
48
|
createImplementationManifest,
|
|
46
49
|
getImplementationLockfile,
|
|
@@ -112,9 +115,6 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
112
115
|
port: portOverride,
|
|
113
116
|
startupTimeoutMs,
|
|
114
117
|
clientApp,
|
|
115
|
-
implementationIdentity,
|
|
116
|
-
authenticationProfile,
|
|
117
|
-
providerAuthenticationCompatibility,
|
|
118
118
|
lifecycleStateSchema,
|
|
119
119
|
}: {
|
|
120
120
|
settings: ACPV1Settings;
|
|
@@ -122,11 +122,6 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
122
122
|
port?: number;
|
|
123
123
|
startupTimeoutMs?: number;
|
|
124
124
|
clientApp: ACPClientApp;
|
|
125
|
-
implementationIdentity: string;
|
|
126
|
-
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
127
|
-
providerAuthenticationCompatibility:
|
|
128
|
-
| ACPProviderAuthenticationCompatibility
|
|
129
|
-
| undefined;
|
|
130
125
|
lifecycleStateSchema: NonNullable<
|
|
131
126
|
HarnessV1<TBuiltinTools>['lifecycleStateSchema']
|
|
132
127
|
>;
|
|
@@ -136,7 +131,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
136
131
|
`ACP harnessId must be a stable kebab-case identifier; received ${JSON.stringify(settings.harnessId)}.`,
|
|
137
132
|
);
|
|
138
133
|
}
|
|
139
|
-
|
|
134
|
+
const implementation = createACPV1Implementation({ settings });
|
|
135
|
+
validateACPV1Implementation(implementation);
|
|
140
136
|
|
|
141
137
|
const BOOTSTRAP_DIR = `.harness-bootstrap/${settings.harnessId}`;
|
|
142
138
|
|
|
@@ -164,7 +160,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
164
160
|
readBridgeAsset({ name: 'host-tool-mcp.mjs' }),
|
|
165
161
|
]);
|
|
166
162
|
const implementationLock = getImplementationLockfile({
|
|
167
|
-
implementation
|
|
163
|
+
implementation,
|
|
168
164
|
});
|
|
169
165
|
cachedBootstrap = {
|
|
170
166
|
harnessId: settings.harnessId,
|
|
@@ -186,14 +182,13 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
186
182
|
{
|
|
187
183
|
path: `${BOOTSTRAP_DIR}/implementation/package.json`,
|
|
188
184
|
content: createImplementationManifest({
|
|
189
|
-
implementation
|
|
185
|
+
implementation,
|
|
190
186
|
}),
|
|
191
187
|
},
|
|
192
188
|
{
|
|
193
189
|
path: `${BOOTSTRAP_DIR}/implementation/implementation.json`,
|
|
194
190
|
content: createImplementationDescriptor({
|
|
195
|
-
implementation
|
|
196
|
-
implementationIdentity,
|
|
191
|
+
implementation,
|
|
197
192
|
}),
|
|
198
193
|
},
|
|
199
194
|
...(implementationLock == null
|
|
@@ -213,7 +208,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
213
208
|
command: createImplementationInstallCommand({
|
|
214
209
|
implementationDir: 'implementation',
|
|
215
210
|
storeDir: '../.pnpm-store',
|
|
216
|
-
implementation
|
|
211
|
+
implementation,
|
|
217
212
|
}),
|
|
218
213
|
},
|
|
219
214
|
],
|
|
@@ -229,6 +224,25 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
229
224
|
});
|
|
230
225
|
}
|
|
231
226
|
const permissionMode = startOptions.permissionMode ?? 'allow-all';
|
|
227
|
+
const env = { ...process.env };
|
|
228
|
+
const providerAuthenticationCompatibility =
|
|
229
|
+
resolveACPProviderAuthenticationCompatibility({
|
|
230
|
+
auth: settings.auth,
|
|
231
|
+
providerAuthentication: settings.providerAuthentication,
|
|
232
|
+
env,
|
|
233
|
+
});
|
|
234
|
+
const implementationIdentity = createImplementationIdentity({
|
|
235
|
+
harnessId: settings.harnessId,
|
|
236
|
+
acpVersion: 'v1',
|
|
237
|
+
implementation,
|
|
238
|
+
clientApp,
|
|
239
|
+
providerAuthentication: providerAuthenticationCompatibility,
|
|
240
|
+
permissionModeMapping: settings.permissionModeMapping,
|
|
241
|
+
});
|
|
242
|
+
const authenticationProfile = createACPAuthenticationProfileIdentity({
|
|
243
|
+
authentication: settings.authentication,
|
|
244
|
+
providerAuthenticationCompatibility,
|
|
245
|
+
});
|
|
232
246
|
|
|
233
247
|
const resolvedProviderAuthentication = resolveACPProviderAuthentication({
|
|
234
248
|
auth: {
|
|
@@ -236,7 +250,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
236
250
|
providerAuthentication: settings.providerAuthentication,
|
|
237
251
|
clientApp,
|
|
238
252
|
},
|
|
239
|
-
env
|
|
253
|
+
env,
|
|
240
254
|
compatibility: providerAuthenticationCompatibility,
|
|
241
255
|
});
|
|
242
256
|
const sandboxSession = startOptions.sandboxSession;
|
|
@@ -472,8 +486,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
472
486
|
` --bridge-type ${shellQuote(settings.harnessId)}`,
|
|
473
487
|
env: {
|
|
474
488
|
...resolveImplementationEnvironment({
|
|
475
|
-
implementation
|
|
476
|
-
env
|
|
489
|
+
implementation,
|
|
490
|
+
env,
|
|
477
491
|
}),
|
|
478
492
|
...createACPBridgeEnvironment({
|
|
479
493
|
authentication: settings.authentication,
|
|
@@ -26,33 +26,24 @@ export type ACPProfileValue =
|
|
|
26
26
|
| ReadonlyArray<ACPProfileValue>
|
|
27
27
|
| { readonly [key: string]: ACPProfileValue };
|
|
28
28
|
|
|
29
|
-
type
|
|
30
|
-
readonly type: 'npm';
|
|
31
|
-
readonly
|
|
32
|
-
readonly args?: ReadonlyArray<string>;
|
|
33
|
-
readonly forwardEnv?: ReadonlyArray<string>;
|
|
29
|
+
export type ACPNpmSimpleSource = {
|
|
30
|
+
readonly type: 'npm-simple';
|
|
31
|
+
readonly packageName: string;
|
|
34
32
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* Exact version to install. When omitted, the package's `latest` dist-tag is
|
|
34
|
+
* installed and no version takes part in the implementation identity, so a
|
|
35
|
+
* new upstream release does not invalidate existing lifecycle state.
|
|
37
36
|
*/
|
|
38
|
-
readonly
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export type ACPSimpleNpmImplementation = ACPNpmLaunchSettings & {
|
|
42
|
-
readonly mode: 'simple';
|
|
43
|
-
readonly packageName: string;
|
|
44
|
-
readonly version: string;
|
|
37
|
+
readonly packageVersion?: string;
|
|
45
38
|
};
|
|
46
39
|
|
|
47
|
-
export type
|
|
48
|
-
readonly
|
|
40
|
+
export type ACPNpmLockedSource = {
|
|
41
|
+
readonly type: 'npm-locked';
|
|
49
42
|
readonly packageJson: string;
|
|
50
43
|
readonly pnpmLockYaml: string;
|
|
51
44
|
};
|
|
52
45
|
|
|
53
|
-
export type
|
|
54
|
-
| ACPSimpleNpmImplementation
|
|
55
|
-
| ACPLockedNpmImplementation;
|
|
46
|
+
export type ACPSource = ACPNpmSimpleSource | ACPNpmLockedSource;
|
|
56
47
|
|
|
57
48
|
export type ACPAuthentication = {
|
|
58
49
|
readonly methodId: string;
|
|
@@ -87,7 +78,15 @@ export type ACPV1Settings = {
|
|
|
87
78
|
readonly version?: 'v1';
|
|
88
79
|
readonly harnessId: string;
|
|
89
80
|
readonly auth?: ACPProviderAuthenticationMode;
|
|
90
|
-
readonly
|
|
81
|
+
readonly source: ACPSource;
|
|
82
|
+
readonly executable: string;
|
|
83
|
+
readonly args?: ReadonlyArray<string>;
|
|
84
|
+
readonly forwardEnv?: ReadonlyArray<string>;
|
|
85
|
+
/**
|
|
86
|
+
* Runtime environment values that are safe to persist in bootstrap and
|
|
87
|
+
* lifecycle compatibility identity.
|
|
88
|
+
*/
|
|
89
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
91
90
|
readonly authentication?: ACPAuthentication;
|
|
92
91
|
readonly providerAuthentication?: ACPProviderAuthentication;
|
|
93
92
|
readonly modelId?: string;
|
|
@@ -39,11 +39,37 @@ export function createACPBridgeError({
|
|
|
39
39
|
| 'prompt update stream';
|
|
40
40
|
cause: unknown;
|
|
41
41
|
}): Error {
|
|
42
|
-
const
|
|
42
|
+
const causeMessage = getErrorMessage({ error: cause });
|
|
43
|
+
const error = new Error(
|
|
44
|
+
causeMessage == null
|
|
45
|
+
? `ACP ${stage} failed.`
|
|
46
|
+
: `ACP ${stage} failed: ${causeMessage}`,
|
|
47
|
+
);
|
|
43
48
|
(error as Error & { cause?: unknown }).cause = cause;
|
|
44
49
|
return error;
|
|
45
50
|
}
|
|
46
51
|
|
|
52
|
+
function getErrorMessage({ error }: { error: unknown }): string | undefined {
|
|
53
|
+
if (error instanceof Error) {
|
|
54
|
+
return error.message;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
error != null &&
|
|
59
|
+
typeof error === 'object' &&
|
|
60
|
+
'message' in error &&
|
|
61
|
+
typeof error.message === 'string'
|
|
62
|
+
) {
|
|
63
|
+
return error.message;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof error === 'string') {
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
function stripMetadata({ value }: { value: unknown }): unknown {
|
|
48
74
|
if (Array.isArray(value)) {
|
|
49
75
|
return value.map(item => stripMetadata({ value: item }));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
3
|
+
|
|
4
|
+
export function monitorACPAgentStderr({
|
|
5
|
+
stderr,
|
|
6
|
+
onStderrLine,
|
|
7
|
+
}: {
|
|
8
|
+
stderr: Readable;
|
|
9
|
+
onStderrLine: (line: string) => void;
|
|
10
|
+
}): Promise<never> {
|
|
11
|
+
let rejectFailure!: (error: unknown) => void;
|
|
12
|
+
const failure = new Promise<never>((_, reject) => {
|
|
13
|
+
rejectFailure = reject;
|
|
14
|
+
});
|
|
15
|
+
void failure.catch(() => {});
|
|
16
|
+
|
|
17
|
+
void (async () => {
|
|
18
|
+
stderr.setEncoding('utf8');
|
|
19
|
+
let pending = '';
|
|
20
|
+
for await (const chunk of stderr) {
|
|
21
|
+
pending += chunk;
|
|
22
|
+
const lines = pending.split('\n');
|
|
23
|
+
pending = lines.pop() ?? '';
|
|
24
|
+
for (const line of lines) handleStderrLine({ line });
|
|
25
|
+
}
|
|
26
|
+
if (pending.length > 0) handleStderrLine({ line: pending });
|
|
27
|
+
})().catch(error => rejectFailure(error));
|
|
28
|
+
|
|
29
|
+
return failure;
|
|
30
|
+
|
|
31
|
+
function handleStderrLine({ line }: { line: string }): void {
|
|
32
|
+
if (line.length === 0) return;
|
|
33
|
+
onStderrLine(line);
|
|
34
|
+
const normalizedLine = stripVTControlCharacters(line);
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* An ACP agent can log a response-stream decoding failure without rejecting
|
|
38
|
+
* its pending JSON-RPC prompt. Once the provider stream cannot be decoded,
|
|
39
|
+
* no valid prompt completion can reach the ACP client, so the bridge must
|
|
40
|
+
* fail the turn instead of waiting indefinitely.
|
|
41
|
+
*/
|
|
42
|
+
if (
|
|
43
|
+
normalizedLine
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.includes('failed to deserialize responsestreamevent from stream')
|
|
46
|
+
) {
|
|
47
|
+
rejectFailure(
|
|
48
|
+
new Error('ACP agent failed to deserialize a streamed response.'),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/v1/bridge/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
createACPBridgeError,
|
|
31
31
|
createACPInitializationDiagnostic,
|
|
32
32
|
} from './acp-diagnostics';
|
|
33
|
+
import { monitorACPAgentStderr } from './agent-stderr-monitor';
|
|
33
34
|
import { createEmitStreamEvent } from './create-emit-stream-event';
|
|
34
35
|
import {
|
|
35
36
|
startHostToolRelay,
|
|
@@ -79,6 +80,7 @@ const implementation = await readImplementationDescriptor({
|
|
|
79
80
|
const bridgeConfiguration = await readACPBridgeEnvironment({ env: processEnv });
|
|
80
81
|
|
|
81
82
|
let child: ChildProcessWithoutNullStreams | undefined;
|
|
83
|
+
let agentResponseStreamFailure: Promise<never> | undefined;
|
|
82
84
|
let connection: acp.ClientConnection | undefined;
|
|
83
85
|
let session: ACPActiveSession | undefined;
|
|
84
86
|
let recoveredSessionUpdates:
|
|
@@ -139,6 +141,12 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
139
141
|
if (activeSession == null) {
|
|
140
142
|
throw new Error('ACP session initialization did not produce a session.');
|
|
141
143
|
}
|
|
144
|
+
const activeAgentResponseStreamFailure = agentResponseStreamFailure;
|
|
145
|
+
if (activeAgentResponseStreamFailure == null) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'ACP session initialization did not start stderr monitoring.',
|
|
148
|
+
);
|
|
149
|
+
}
|
|
142
150
|
if (start.recoveryMode?.type === 'lossy-rerun') {
|
|
143
151
|
const marker = {
|
|
144
152
|
type: 'acp-recovery',
|
|
@@ -260,6 +268,7 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
260
268
|
message = await Promise.race([
|
|
261
269
|
activeSession.nextUpdate(),
|
|
262
270
|
cancellationFailure,
|
|
271
|
+
activeAgentResponseStreamFailure,
|
|
263
272
|
]);
|
|
264
273
|
} catch (error) {
|
|
265
274
|
for (const rawValue of streamCapture?.drainRawValues() ?? []) {
|
|
@@ -361,7 +370,16 @@ async function ensureSession({
|
|
|
361
370
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
362
371
|
},
|
|
363
372
|
);
|
|
364
|
-
|
|
373
|
+
agentResponseStreamFailure = monitorACPAgentStderr({
|
|
374
|
+
stderr: child.stderr,
|
|
375
|
+
onStderrLine: line => {
|
|
376
|
+
turn.bridgeLog({
|
|
377
|
+
level: 'warn',
|
|
378
|
+
subsystem: 'acp.agent.stderr',
|
|
379
|
+
message: line,
|
|
380
|
+
});
|
|
381
|
+
},
|
|
382
|
+
});
|
|
365
383
|
|
|
366
384
|
const input = Writable.toWeb(child.stdin);
|
|
367
385
|
const output = Readable.toWeb(child.stdout) as ReadableStream<
|
|
@@ -631,39 +649,6 @@ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
|
631
649
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
632
650
|
}
|
|
633
651
|
|
|
634
|
-
function forwardAgentStderr({
|
|
635
|
-
child,
|
|
636
|
-
turn,
|
|
637
|
-
}: {
|
|
638
|
-
child: ChildProcessWithoutNullStreams;
|
|
639
|
-
turn: BridgeTurn;
|
|
640
|
-
}): void {
|
|
641
|
-
void (async () => {
|
|
642
|
-
child.stderr.setEncoding('utf8');
|
|
643
|
-
let pending = '';
|
|
644
|
-
for await (const chunk of child.stderr) {
|
|
645
|
-
pending += chunk;
|
|
646
|
-
const lines = pending.split('\n');
|
|
647
|
-
pending = lines.pop() ?? '';
|
|
648
|
-
for (const line of lines) {
|
|
649
|
-
if (line.length === 0) continue;
|
|
650
|
-
turn.bridgeLog({
|
|
651
|
-
level: 'warn',
|
|
652
|
-
subsystem: 'acp.agent.stderr',
|
|
653
|
-
message: line,
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
if (pending.length > 0) {
|
|
658
|
-
turn.bridgeLog({
|
|
659
|
-
level: 'warn',
|
|
660
|
-
subsystem: 'acp.agent.stderr',
|
|
661
|
-
message: pending,
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
})();
|
|
665
|
-
}
|
|
666
|
-
|
|
667
652
|
async function readImplementationDescriptor({
|
|
668
653
|
path,
|
|
669
654
|
}: {
|