@automatalabs/acp-agents 0.15.0 → 0.17.0
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/dist/acp-client.d.ts +19 -2
- package/dist/acp-client.d.ts.map +1 -1
- package/dist/acp-client.js +224 -9
- package/dist/capabilities.d.ts +21 -8
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +28 -9
- package/dist/client-handlers.d.ts +12 -3
- package/dist/client-handlers.d.ts.map +1 -1
- package/dist/client-handlers.js +22 -7
- package/dist/errors-map.d.ts +8 -1
- package/dist/errors-map.d.ts.map +1 -1
- package/dist/errors-map.js +23 -3
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +13 -1
- package/dist/protocol-coverage.d.ts +3 -1
- package/dist/protocol-coverage.d.ts.map +1 -1
- package/dist/protocol-coverage.js +11 -9
- package/dist/runner.d.ts +46 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +179 -31
- package/package.json +2 -2
package/dist/runner.js
CHANGED
|
@@ -98,6 +98,137 @@ export class AcpAgentRunner {
|
|
|
98
98
|
async openSession(opts) {
|
|
99
99
|
return this.createInteractiveSession(opts, "openSession", (connection, prepared) => connection.openSession(prepared.sessionOptions));
|
|
100
100
|
}
|
|
101
|
+
/** Return the selected backend's initialize-advertised authentication methods. */
|
|
102
|
+
async authMethods(opts = {}) {
|
|
103
|
+
if (this.disposed)
|
|
104
|
+
throw new Error("ACP agent runner is disposed");
|
|
105
|
+
const backend = selectBackend(opts, this.backends);
|
|
106
|
+
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
107
|
+
try {
|
|
108
|
+
if (this.disposed)
|
|
109
|
+
throw new Error("ACP agent runner is disposed");
|
|
110
|
+
return await connection.authMethods();
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await disposeBestEffort(connection);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Drive ACP authenticate on the selected backend. */
|
|
117
|
+
async authenticate(opts) {
|
|
118
|
+
if (this.disposed)
|
|
119
|
+
throw new Error("ACP agent runner is disposed");
|
|
120
|
+
validateRequiredString(opts.methodId, opts.label, "authenticate", "methodId");
|
|
121
|
+
opts.signal?.throwIfAborted();
|
|
122
|
+
const backend = selectBackend(opts, this.backends);
|
|
123
|
+
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
124
|
+
try {
|
|
125
|
+
const request = {
|
|
126
|
+
methodId: opts.methodId,
|
|
127
|
+
...(opts.meta ? { _meta: opts.meta } : {}),
|
|
128
|
+
};
|
|
129
|
+
const response = await connection.authenticate(request, opts.label);
|
|
130
|
+
opts.signal?.throwIfAborted();
|
|
131
|
+
if (this.disposed)
|
|
132
|
+
throw new Error("ACP agent runner is disposed");
|
|
133
|
+
return response;
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
await disposeBestEffort(connection);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** List configurable providers from the selected backend. */
|
|
140
|
+
async listProviders(opts = {}) {
|
|
141
|
+
if (this.disposed)
|
|
142
|
+
throw new Error("ACP agent runner is disposed");
|
|
143
|
+
opts.signal?.throwIfAborted();
|
|
144
|
+
const backend = selectBackend(opts, this.backends);
|
|
145
|
+
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
146
|
+
try {
|
|
147
|
+
const request = {
|
|
148
|
+
...(opts.meta ? { _meta: opts.meta } : {}),
|
|
149
|
+
};
|
|
150
|
+
const response = await connection.listProviders(request, opts.label);
|
|
151
|
+
opts.signal?.throwIfAborted();
|
|
152
|
+
if (this.disposed)
|
|
153
|
+
throw new Error("ACP agent runner is disposed");
|
|
154
|
+
return response;
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
await disposeBestEffort(connection);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Configure one provider on the selected backend. */
|
|
161
|
+
async setProvider(opts) {
|
|
162
|
+
if (this.disposed)
|
|
163
|
+
throw new Error("ACP agent runner is disposed");
|
|
164
|
+
validateRequiredString(opts.providerId, opts.label, "setProvider", "providerId");
|
|
165
|
+
validateRequiredString(opts.apiType, opts.label, "setProvider", "apiType");
|
|
166
|
+
validateRequiredString(opts.baseUrl, opts.label, "setProvider", "baseUrl");
|
|
167
|
+
opts.signal?.throwIfAborted();
|
|
168
|
+
const backend = selectBackend(opts, this.backends);
|
|
169
|
+
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
170
|
+
try {
|
|
171
|
+
const request = {
|
|
172
|
+
providerId: opts.providerId,
|
|
173
|
+
apiType: opts.apiType,
|
|
174
|
+
baseUrl: opts.baseUrl,
|
|
175
|
+
...(opts.headers ? { headers: opts.headers } : {}),
|
|
176
|
+
...(opts.meta ? { _meta: opts.meta } : {}),
|
|
177
|
+
};
|
|
178
|
+
const response = await connection.setProvider(request, opts.label);
|
|
179
|
+
opts.signal?.throwIfAborted();
|
|
180
|
+
if (this.disposed)
|
|
181
|
+
throw new Error("ACP agent runner is disposed");
|
|
182
|
+
return response;
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
await disposeBestEffort(connection);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Disable one provider on the selected backend. */
|
|
189
|
+
async disableProvider(opts) {
|
|
190
|
+
if (this.disposed)
|
|
191
|
+
throw new Error("ACP agent runner is disposed");
|
|
192
|
+
validateRequiredString(opts.providerId, opts.label, "disableProvider", "providerId");
|
|
193
|
+
opts.signal?.throwIfAborted();
|
|
194
|
+
const backend = selectBackend(opts, this.backends);
|
|
195
|
+
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
196
|
+
try {
|
|
197
|
+
const request = {
|
|
198
|
+
providerId: opts.providerId,
|
|
199
|
+
...(opts.meta ? { _meta: opts.meta } : {}),
|
|
200
|
+
};
|
|
201
|
+
const response = await connection.disableProvider(request, opts.label);
|
|
202
|
+
opts.signal?.throwIfAborted();
|
|
203
|
+
if (this.disposed)
|
|
204
|
+
throw new Error("ACP agent runner is disposed");
|
|
205
|
+
return response;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
await disposeBestEffort(connection);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Logout through the selected backend. */
|
|
212
|
+
async logout(opts = {}) {
|
|
213
|
+
if (this.disposed)
|
|
214
|
+
throw new Error("ACP agent runner is disposed");
|
|
215
|
+
opts.signal?.throwIfAborted();
|
|
216
|
+
const backend = selectBackend(opts, this.backends);
|
|
217
|
+
const connection = this.createDedicatedConnection(backend, () => undefined);
|
|
218
|
+
try {
|
|
219
|
+
const request = {
|
|
220
|
+
...(opts.meta ? { _meta: opts.meta } : {}),
|
|
221
|
+
};
|
|
222
|
+
const response = await connection.logout(request, opts.label);
|
|
223
|
+
opts.signal?.throwIfAborted();
|
|
224
|
+
if (this.disposed)
|
|
225
|
+
throw new Error("ACP agent runner is disposed");
|
|
226
|
+
return response;
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
await disposeBestEffort(connection);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
101
232
|
/** List persisted ACP sessions from the selected backend. */
|
|
102
233
|
async listSessions(opts = {}) {
|
|
103
234
|
if (this.disposed)
|
|
@@ -180,22 +311,24 @@ export class AcpAgentRunner {
|
|
|
180
311
|
registry,
|
|
181
312
|
signal: opts.signal,
|
|
182
313
|
});
|
|
183
|
-
|
|
314
|
+
let session;
|
|
184
315
|
try {
|
|
316
|
+
session = await this.pool.acquire(prepared.backend, prepared.sessionOptions);
|
|
317
|
+
const activeSession = session;
|
|
185
318
|
opts.signal?.throwIfAborted();
|
|
186
319
|
// For a CUSTOM backend chosen by its registered name, the name itself is routing, not a
|
|
187
320
|
// model id: "browser" selects nothing; "browser/foo" selects "foo". Built-ins get the
|
|
188
321
|
// full spec unchanged (their catalogs match provider-prefixed and bare ids).
|
|
189
|
-
await applyModelSelection(
|
|
322
|
+
await applyModelSelection(activeSession, prepared.modelSpec, opts);
|
|
190
323
|
opts.signal?.throwIfAborted();
|
|
191
324
|
if (opts.mode)
|
|
192
|
-
await
|
|
325
|
+
await activeSession.setMode(opts.mode);
|
|
193
326
|
const text = buildRunPrompt(prompt, opts, schema, prepared.backend);
|
|
194
327
|
const initialPrompt = opts.images && opts.images.length > 0 ? promptWithImages(text, opts.images) : text;
|
|
195
328
|
// Generic turn-scoped _meta passthrough merged UNDER the backend-computed keys (e.g. the
|
|
196
329
|
// outputSchema forward when a schema is set) — user meta never clobbers the schema channel.
|
|
197
330
|
const promptMeta = mergeTurnMeta(opts.promptMeta, prepared.backend.promptMeta(schema));
|
|
198
|
-
const response = await
|
|
331
|
+
const response = await activeSession.prompt(initialPrompt, promptMeta);
|
|
199
332
|
opts.signal?.throwIfAborted();
|
|
200
333
|
// Inspect the turn's stop reason BEFORE the text/schema path: a refusal or truncation
|
|
201
334
|
// must surface distinctly here, never be misread as empty output or burned through the
|
|
@@ -204,13 +337,13 @@ export class AcpAgentRunner {
|
|
|
204
337
|
if (schema) {
|
|
205
338
|
const structuredSession = {
|
|
206
339
|
prompt: async (repromptText) => {
|
|
207
|
-
const repromptResponse = await
|
|
340
|
+
const repromptResponse = await activeSession.prompt(repromptText, promptMeta);
|
|
208
341
|
// A repair turn that refuses / truncates / cancels must also surface distinctly
|
|
209
342
|
// instead of silently continuing the ladder.
|
|
210
343
|
assertNormalStopReason(repromptResponse.stopReason, opts.label);
|
|
211
344
|
},
|
|
212
|
-
lastText: () =>
|
|
213
|
-
tryNative: () => prepared.backend.nativeStructured(
|
|
345
|
+
lastText: () => activeSession.currentTurnText(),
|
|
346
|
+
tryNative: () => prepared.backend.nativeStructured(activeSession),
|
|
214
347
|
};
|
|
215
348
|
const result = await resolveStructuredOutput(structuredSession, schema, {
|
|
216
349
|
maxSchemaRetries: opts.maxSchemaRetries,
|
|
@@ -219,7 +352,7 @@ export class AcpAgentRunner {
|
|
|
219
352
|
});
|
|
220
353
|
return result;
|
|
221
354
|
}
|
|
222
|
-
const finalText =
|
|
355
|
+
const finalText = activeSession.currentTurnText().trim();
|
|
223
356
|
if (!finalText) {
|
|
224
357
|
throw new WorkflowError("Subagent produced no assistant output", WorkflowErrorCode.AGENT_EMPTY_OUTPUT, {
|
|
225
358
|
recoverable: true,
|
|
@@ -232,28 +365,34 @@ export class AcpAgentRunner {
|
|
|
232
365
|
// Abort is the engine's concern (throwIfAborted before/after the call) — re-throw it raw.
|
|
233
366
|
if (opts.signal?.aborted)
|
|
234
367
|
throw error;
|
|
235
|
-
throw mapThrownError(error,
|
|
368
|
+
throw mapThrownError(error, {
|
|
369
|
+
label: opts.label,
|
|
370
|
+
backendId: prepared.backend.id,
|
|
371
|
+
authMethods: session?.capabilities?.authMethods,
|
|
372
|
+
});
|
|
236
373
|
}
|
|
237
374
|
finally {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
375
|
+
if (session) {
|
|
376
|
+
// Read real usage on BOTH success and error so partial usage is never lost.
|
|
377
|
+
try {
|
|
378
|
+
opts.onUsage?.(session.usage.toAgentUsage());
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
// usage is best-effort; never let it mask the real result/error.
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
opts.onHistory?.(session.history);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// history is diagnostic only.
|
|
388
|
+
}
|
|
389
|
+
// Release the SESSION (best-effort session/close) WITHOUT killing the pooled process.
|
|
390
|
+
try {
|
|
391
|
+
await session.release();
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
// release is best-effort (session already untracked); never mask the real result/error.
|
|
395
|
+
}
|
|
257
396
|
}
|
|
258
397
|
}
|
|
259
398
|
}
|
|
@@ -320,7 +459,13 @@ export class AcpAgentRunner {
|
|
|
320
459
|
// best-effort: lifecycle setup failed, so teardown must never mask the real error.
|
|
321
460
|
}
|
|
322
461
|
await disposeBestEffort(connection);
|
|
323
|
-
|
|
462
|
+
if (opts.signal?.aborted)
|
|
463
|
+
throw error;
|
|
464
|
+
throw mapThrownError(error, {
|
|
465
|
+
label: opts.label,
|
|
466
|
+
backendId: prepared.backend.id,
|
|
467
|
+
authMethods: connection.capabilities?.authMethods,
|
|
468
|
+
});
|
|
324
469
|
}
|
|
325
470
|
}
|
|
326
471
|
createDedicatedConnection(backend, onDead) {
|
|
@@ -491,8 +636,11 @@ function validateOptionalLifecycleCwd(cwd, label, methodName) {
|
|
|
491
636
|
validateInteractiveCwd(cwd, label, methodName);
|
|
492
637
|
}
|
|
493
638
|
function validateLifecycleSessionId(sessionId, label, methodName) {
|
|
494
|
-
|
|
495
|
-
|
|
639
|
+
validateRequiredString(sessionId, label, methodName, "sessionId");
|
|
640
|
+
}
|
|
641
|
+
function validateRequiredString(value, label, methodName, fieldName) {
|
|
642
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
643
|
+
throw new WorkflowError(`${methodName} requires ${fieldName} to be a non-empty string`, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false, agentLabel: label });
|
|
496
644
|
}
|
|
497
645
|
}
|
|
498
646
|
async function disposeBestEffort(connection) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automatalabs/acp-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@agentclientprotocol/claude-agent-acp": "0.56.0",
|
|
32
32
|
"@automatalabs/codex-acp": "1.4.0",
|
|
33
33
|
"typebox": "1.3.2",
|
|
34
|
-
"@automatalabs/shared-types": "0.
|
|
34
|
+
"@automatalabs/shared-types": "0.11.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc -b",
|