@automatalabs/acp-agents 0.6.0 → 0.7.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 +9 -1
- package/dist/acp-client.d.ts.map +1 -1
- package/dist/acp-client.js +51 -9
- package/dist/capabilities.d.ts +46 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +89 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/package.json +5 -5
package/dist/acp-client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ClientSideConnection, type PromptResponse, type SessionConfigOption, ty
|
|
|
2
2
|
import type { TSchema } from "typebox";
|
|
3
3
|
import { type AgentHistoryEntry, type McpServerConfig } from "@automatalabs/shared-types";
|
|
4
4
|
import type { Backend, BackendId, StructuredSource } from "./backend.js";
|
|
5
|
+
import { type NegotiatedCapabilities } from "./capabilities.js";
|
|
5
6
|
import { type AcpEventSink } from "./events.js";
|
|
6
7
|
import { type ToolPolicy } from "./permissions.js";
|
|
7
8
|
import { UsageAccumulator } from "./usage.js";
|
|
@@ -83,7 +84,8 @@ export declare class PooledConnection {
|
|
|
83
84
|
private readonly whenDead;
|
|
84
85
|
private resolveDead;
|
|
85
86
|
private deathError;
|
|
86
|
-
|
|
87
|
+
/** Set from the one-time initialize handshake; undefined until it completes (or if it failed). */
|
|
88
|
+
private negotiated;
|
|
87
89
|
private _alive;
|
|
88
90
|
private _activeSessions;
|
|
89
91
|
private stderrTail;
|
|
@@ -93,6 +95,12 @@ export declare class PooledConnection {
|
|
|
93
95
|
static create(backend: Backend, deps: PooledConnectionDeps): PooledConnection;
|
|
94
96
|
get alive(): boolean;
|
|
95
97
|
get activeSessions(): number;
|
|
98
|
+
/** The capabilities negotiated on this connection's one-time initialize handshake, or undefined
|
|
99
|
+
* until it completes — derived-state-behind-a-getter, like `alive`/`activeSessions`. */
|
|
100
|
+
get capabilities(): NegotiatedCapabilities | undefined;
|
|
101
|
+
/** Drop the fork's custom bare `_meta` keys the connected agent did not advertise support for
|
|
102
|
+
* (see gateCustomMeta). Applied to BOTH session/new and session/prompt `_meta`. */
|
|
103
|
+
gateCustomMeta(meta: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
|
96
104
|
/** Mark this connection dead exactly once, then ask the pool to evict it. Idempotent. */
|
|
97
105
|
private die;
|
|
98
106
|
private stderrSuffix;
|
package/dist/acp-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-client.d.ts","sourceRoot":"","sources":["../src/acp-client.ts"],"names":[],"mappings":"AAwBA,OAAO,EACL,oBAAoB,EAOpB,KAAK,cAAc,EAGnB,KAAK,mBAAmB,EAGxB,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,
|
|
1
|
+
{"version":3,"file":"acp-client.d.ts","sourceRoot":"","sources":["../src/acp-client.ts"],"names":[],"mappings":"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"}
|
package/dist/acp-client.js
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
import { spawn } from "node:child_process";
|
|
24
24
|
import { Readable, Writable } from "node:stream";
|
|
25
25
|
import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
|
|
26
|
-
import { META_KEYS } from "@automatalabs/shared-types";
|
|
26
|
+
import { META_KEYS, WorkflowError, WorkflowErrorCode, } from "@automatalabs/shared-types";
|
|
27
|
+
import { gateCustomMeta, isSupportedProtocolVersion, negotiateCapabilities, unsupportedMcpServer, } from "./capabilities.js";
|
|
27
28
|
import { emitSessionUpdate } from "./events.js";
|
|
28
29
|
import { decidePermission } from "./permissions.js";
|
|
29
30
|
import { UsageAccumulator } from "./usage.js";
|
|
@@ -232,7 +233,8 @@ export class PooledConnection {
|
|
|
232
233
|
whenDead;
|
|
233
234
|
resolveDead;
|
|
234
235
|
deathError;
|
|
235
|
-
|
|
236
|
+
/** Set from the one-time initialize handshake; undefined until it completes (or if it failed). */
|
|
237
|
+
negotiated;
|
|
236
238
|
_alive = true;
|
|
237
239
|
_activeSessions = 0;
|
|
238
240
|
stderrTail = "";
|
|
@@ -289,6 +291,16 @@ export class PooledConnection {
|
|
|
289
291
|
get activeSessions() {
|
|
290
292
|
return this._activeSessions;
|
|
291
293
|
}
|
|
294
|
+
/** The capabilities negotiated on this connection's one-time initialize handshake, or undefined
|
|
295
|
+
* until it completes — derived-state-behind-a-getter, like `alive`/`activeSessions`. */
|
|
296
|
+
get capabilities() {
|
|
297
|
+
return this.negotiated;
|
|
298
|
+
}
|
|
299
|
+
/** Drop the fork's custom bare `_meta` keys the connected agent did not advertise support for
|
|
300
|
+
* (see gateCustomMeta). Applied to BOTH session/new and session/prompt `_meta`. */
|
|
301
|
+
gateCustomMeta(meta) {
|
|
302
|
+
return gateCustomMeta(meta, this.negotiated?.customMetaSupport);
|
|
303
|
+
}
|
|
292
304
|
/** Mark this connection dead exactly once, then ask the pool to evict it. Idempotent. */
|
|
293
305
|
die(error) {
|
|
294
306
|
if (!this._alive)
|
|
@@ -337,12 +349,27 @@ export class PooledConnection {
|
|
|
337
349
|
const response = await Promise.race([
|
|
338
350
|
this.race(this.rpc.initialize({
|
|
339
351
|
protocolVersion: PROTOCOL_VERSION,
|
|
352
|
+
// Truthful advertisement: MultiplexClient implements NONE of the client-side
|
|
353
|
+
// fs/terminal methods, so we advertise none (the ACP rule is "advertise only what you
|
|
354
|
+
// implement"). Everything omitted here is treated as unsupported by the agent.
|
|
340
355
|
clientCapabilities: {},
|
|
341
356
|
clientInfo: { ...CLIENT_INFO },
|
|
342
357
|
})),
|
|
343
358
|
deadline,
|
|
344
359
|
]);
|
|
345
|
-
|
|
360
|
+
const negotiated = negotiateCapabilities(response);
|
|
361
|
+
// Version negotiation: the agent replies with the version it chose (our requested version if
|
|
362
|
+
// it supports it, else its own latest). If this client cannot speak it, the ACP spec says
|
|
363
|
+
// CLOSE the connection and inform the user — kill the process (so the pool evicts it) and
|
|
364
|
+
// surface a legible error instead of proceeding on an unspoken protocol. Non-recoverable: a
|
|
365
|
+
// deterministic protocol incompatibility must fail fast, not be retried as a transient
|
|
366
|
+
// AGENT_EXECUTION_ERROR.
|
|
367
|
+
if (!isSupportedProtocolVersion(negotiated.protocolVersion)) {
|
|
368
|
+
this.killNow();
|
|
369
|
+
throw new WorkflowError(`ACP agent (${this.backendId}) selected protocol version ${negotiated.protocolVersion}, which ` +
|
|
370
|
+
`this client (protocol v${PROTOCOL_VERSION}) does not support — closing the connection.${this.stderrSuffix()}`, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false });
|
|
371
|
+
}
|
|
372
|
+
this.negotiated = negotiated;
|
|
346
373
|
}
|
|
347
374
|
finally {
|
|
348
375
|
clearTimeout(timer);
|
|
@@ -357,16 +384,28 @@ export class PooledConnection {
|
|
|
357
384
|
this._activeSessions += 1;
|
|
358
385
|
try {
|
|
359
386
|
await this.ready;
|
|
387
|
+
// Capability gate: reject a client-provided MCP server whose transport the connected agent
|
|
388
|
+
// does not advertise (http/sse gated on mcpCapabilities; stdio is always serviceable).
|
|
389
|
+
// Fail-fast and non-recoverable — re-running the same incompatible transport can never
|
|
390
|
+
// succeed. Lenient for agents that advertise no mcpCapabilities (the legacy passthrough).
|
|
391
|
+
const unsupported = this.negotiated
|
|
392
|
+
? unsupportedMcpServer(opts.mcpServers, this.negotiated.agent)
|
|
393
|
+
: undefined;
|
|
394
|
+
if (unsupported) {
|
|
395
|
+
throw new WorkflowError(`MCP server "${unsupported.name}" uses the "${unsupported.transport}" transport, which the ` +
|
|
396
|
+
`${this.backendId} agent does not support`, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false, agentLabel: opts.label });
|
|
397
|
+
}
|
|
360
398
|
const state = new SessionState(opts.policy, opts.label, opts.runId);
|
|
361
399
|
// session/new `_meta`, layered lowest-to-highest precedence: the backend's static
|
|
362
400
|
// defaults (a custom registry entry's `sessionMeta`), then the generic user passthrough
|
|
363
401
|
// (opts.meta), then the backend's protocol-critical `_meta` (Claude schema channel;
|
|
364
|
-
// Codex base/developer instructions), then the engine runId correlation stamp.
|
|
365
|
-
//
|
|
366
|
-
|
|
402
|
+
// Codex base/developer instructions), then the engine runId correlation stamp. The result
|
|
403
|
+
// is gated against the agent's advertised custom capabilities (a Codex instruction key the
|
|
404
|
+
// agent said it does not honor is dropped). When no layer survives, no `_meta` is sent.
|
|
405
|
+
const meta = this.gateCustomMeta(stampRunId(layerMeta(this.backend.sessionMetaDefaults?.(), opts.meta, this.backend.sessionMeta(opts.schema, {
|
|
367
406
|
baseInstructions: opts.baseInstructions,
|
|
368
407
|
developerInstructions: opts.developerInstructions,
|
|
369
|
-
})), opts.runId);
|
|
408
|
+
})), opts.runId));
|
|
370
409
|
const request = {
|
|
371
410
|
cwd: opts.cwd,
|
|
372
411
|
// Client-provided MCP servers (additive run input), else the default empty list.
|
|
@@ -402,7 +441,7 @@ export class PooledConnection {
|
|
|
402
441
|
this.client.unregister(sessionId);
|
|
403
442
|
if (this._activeSessions > 0)
|
|
404
443
|
this._activeSessions -= 1;
|
|
405
|
-
if (!this.supportsClose || !this._alive)
|
|
444
|
+
if (!this.negotiated?.supportsClose || !this._alive)
|
|
406
445
|
return;
|
|
407
446
|
try {
|
|
408
447
|
await this.race(withTimeout(this.rpc.closeSession({ sessionId }), CLOSE_SESSION_TIMEOUT_MS));
|
|
@@ -588,10 +627,13 @@ export class SessionHandle {
|
|
|
588
627
|
this.opts.signal?.throwIfAborted();
|
|
589
628
|
this.state.beginTurn();
|
|
590
629
|
const prompt = [{ type: "text", text }];
|
|
630
|
+
// Gate the turn `_meta` against the agent's advertised custom capabilities: the Codex
|
|
631
|
+
// outputSchema forward is dropped when the connected agent said it does not honor it.
|
|
632
|
+
const gatedMeta = this.pooled.gateCustomMeta(promptMeta);
|
|
591
633
|
const request = {
|
|
592
634
|
sessionId: this.sessionId,
|
|
593
635
|
prompt,
|
|
594
|
-
...(
|
|
636
|
+
...(gatedMeta ? { _meta: gatedMeta } : {}),
|
|
595
637
|
};
|
|
596
638
|
const response = await this.pooled.race(this.pooled.rpc.prompt(request));
|
|
597
639
|
this.state.usage.recordPromptUsage(response.usage);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type AgentCapabilities, type Implementation, type InitializeResponse } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { type McpServerConfig } from "@automatalabs/shared-types";
|
|
3
|
+
/** The bare `_meta` keys whose emission is gated by the fork's custom-capability advertisement.
|
|
4
|
+
* Each is named EXACTLY like the advertised flag that gates it, so `support[key] === true` is the
|
|
5
|
+
* whole test. session/new carries baseInstructions/developerInstructions and session/prompt carries
|
|
6
|
+
* outputSchema — one list covers both (a key absent from a given `_meta` is simply skipped). */
|
|
7
|
+
export declare const GATED_CUSTOM_META_KEYS: readonly string[];
|
|
8
|
+
/** The capability state a pooled connection derives from its initialize response. */
|
|
9
|
+
export interface NegotiatedCapabilities {
|
|
10
|
+
/** The protocol version the agent selected (echoes the client's when supported, else the agent's
|
|
11
|
+
* latest). Validated by isSupportedProtocolVersion before the connection is used. */
|
|
12
|
+
protocolVersion: number;
|
|
13
|
+
/** The agent's full advertised capabilities — an empty object when the agent sent none (every
|
|
14
|
+
* capability is then UNSUPPORTED per the ACP spec). */
|
|
15
|
+
agent: AgentCapabilities;
|
|
16
|
+
/** The agent's self-identification, when it sent agentInfo. */
|
|
17
|
+
agentInfo: Implementation | undefined;
|
|
18
|
+
/** Whether session/close is advertised (gates the best-effort release-time close). */
|
|
19
|
+
supportsClose: boolean;
|
|
20
|
+
/** The parsed @automatalabs/codex-acp custom-capability block (the namespaced `_meta` object), or
|
|
21
|
+
* undefined when the agent did not advertise the namespace at all — the legacy passthrough. */
|
|
22
|
+
customMetaSupport: Record<string, unknown> | undefined;
|
|
23
|
+
}
|
|
24
|
+
/** Parse an initialize response into the connection's derived capability state. */
|
|
25
|
+
export declare function negotiateCapabilities(response: InitializeResponse): NegotiatedCapabilities;
|
|
26
|
+
/** True only when the agent selected EXACTLY PROTOCOL_VERSION. This client implements that one wire
|
|
27
|
+
* version and adapts its behavior to no other, so any other selected version — older or newer —
|
|
28
|
+
* means close the connection per the ACP spec's SHOULD-close rule. Per the spec the agent echoes
|
|
29
|
+
* the requested version when it supports it, else its own latest; we do NOT accept older versions
|
|
30
|
+
* (we cannot speak them) — the equality is the whole test. */
|
|
31
|
+
export declare function isSupportedProtocolVersion(version: number): boolean;
|
|
32
|
+
/** Remove the fork's custom bare `_meta` keys the connected agent did NOT advertise support for.
|
|
33
|
+
* A no-op when the agent advertised no namespace (`support` undefined => legacy => every key
|
|
34
|
+
* passes) or the meta is empty/undefined. Never mutates its input; collapses to undefined if
|
|
35
|
+
* gating empties the object (so no `_meta` is sent at all). */
|
|
36
|
+
export declare function gateCustomMeta(meta: Record<string, unknown> | undefined, support: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
|
37
|
+
/** The first client-provided MCP server whose transport the agent did NOT advertise, or undefined
|
|
38
|
+
* when every server is serviceable. stdio is ALWAYS serviceable (the baseline transport); http/sse
|
|
39
|
+
* are gated on mcpCapabilities.{http,sse}. Lenient for legacy agents: when the agent advertised no
|
|
40
|
+
* mcpCapabilities at all we cannot know its transports, so we do not gate (preserving today's
|
|
41
|
+
* send-and-let-the-agent-decide behavior for minimal/custom servers). */
|
|
42
|
+
export declare function unsupportedMcpServer(servers: McpServerConfig[] | undefined, agent: AgentCapabilities): {
|
|
43
|
+
name: string;
|
|
44
|
+
transport: "http" | "sse";
|
|
45
|
+
} | undefined;
|
|
46
|
+
//# sourceMappingURL=capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAeA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AAEpC;;;iGAGiG;AACjG,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAInD,CAAC;AAEF,qFAAqF;AACrF,MAAM,WAAW,sBAAsB;IACrC;0FACsF;IACtF,eAAe,EAAE,MAAM,CAAC;IACxB;4DACwD;IACxD,KAAK,EAAE,iBAAiB,CAAC;IACzB,+DAA+D;IAC/D,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,sFAAsF;IACtF,aAAa,EAAE,OAAO,CAAC;IACvB;oGACgG;IAChG,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACxD;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,sBAAsB,CAS1F;AAUD;;;;+DAI+D;AAC/D,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED;;;gEAGgE;AAChE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC3C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAWrC;AAED;;;;0EAI0E;AAC1E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,EAAE,GAAG,SAAS,EACtC,KAAK,EAAE,iBAAiB,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GAAG,SAAS,CAYzD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Negotiated ACP capabilities for one pooled connection — the derived state the connection acts on
|
|
2
|
+
// after its ONE-TIME `initialize` handshake. Parses the InitializeResponse into: the agent's chosen
|
|
3
|
+
// protocolVersion (validated against what this client speaks), its full advertised agentCapabilities
|
|
4
|
+
// + agentInfo, whether session/close is supported, and the @automatalabs/codex-acp fork's custom
|
|
5
|
+
// capability advertisement (the namespaced `_meta` block gating which bare `_meta` inputs the client
|
|
6
|
+
// may send).
|
|
7
|
+
//
|
|
8
|
+
// GATING PHILOSOPHY — lenient for legacy agents. An agent that advertises NOTHING is treated as
|
|
9
|
+
// today's behavior (every gated key still sent), because the currently-published fork
|
|
10
|
+
// (@automatalabs/codex-acp ≤ 1.2.0) and arbitrary custom ACP servers honor these inputs WITHOUT
|
|
11
|
+
// advertising them; a strict "omitted => unsupported" reading would silently regress them. Only once
|
|
12
|
+
// an agent DOES advertise the fork namespace (opts into negotiation) is each bare key gated on its
|
|
13
|
+
// same-named flag — and, symmetrically, only once an agent advertises mcpCapabilities is an
|
|
14
|
+
// unsupported MCP transport rejected. Truthfully-advertised absence still gates (per spec) WITHIN an
|
|
15
|
+
// advertised capability; total silence is the legacy passthrough.
|
|
16
|
+
import { PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
|
|
17
|
+
import { CODEX_CUSTOM_CAPABILITY_NAMESPACE, CODEX_META_KEYS, META_KEYS, } from "@automatalabs/shared-types";
|
|
18
|
+
/** The bare `_meta` keys whose emission is gated by the fork's custom-capability advertisement.
|
|
19
|
+
* Each is named EXACTLY like the advertised flag that gates it, so `support[key] === true` is the
|
|
20
|
+
* whole test. session/new carries baseInstructions/developerInstructions and session/prompt carries
|
|
21
|
+
* outputSchema — one list covers both (a key absent from a given `_meta` is simply skipped). */
|
|
22
|
+
export const GATED_CUSTOM_META_KEYS = [
|
|
23
|
+
META_KEYS.outputSchema,
|
|
24
|
+
CODEX_META_KEYS.baseInstructions,
|
|
25
|
+
CODEX_META_KEYS.developerInstructions,
|
|
26
|
+
];
|
|
27
|
+
/** Parse an initialize response into the connection's derived capability state. */
|
|
28
|
+
export function negotiateCapabilities(response) {
|
|
29
|
+
const agent = response.agentCapabilities ?? {};
|
|
30
|
+
return {
|
|
31
|
+
protocolVersion: response.protocolVersion,
|
|
32
|
+
agent,
|
|
33
|
+
agentInfo: response.agentInfo ?? undefined,
|
|
34
|
+
supportsClose: Boolean(agent.sessionCapabilities?.close),
|
|
35
|
+
customMetaSupport: readCustomNamespace(agent._meta),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function readCustomNamespace(meta) {
|
|
39
|
+
if (!meta || typeof meta !== "object")
|
|
40
|
+
return undefined;
|
|
41
|
+
const block = meta[CODEX_CUSTOM_CAPABILITY_NAMESPACE];
|
|
42
|
+
return block && typeof block === "object" && !Array.isArray(block)
|
|
43
|
+
? block
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
/** True only when the agent selected EXACTLY PROTOCOL_VERSION. This client implements that one wire
|
|
47
|
+
* version and adapts its behavior to no other, so any other selected version — older or newer —
|
|
48
|
+
* means close the connection per the ACP spec's SHOULD-close rule. Per the spec the agent echoes
|
|
49
|
+
* the requested version when it supports it, else its own latest; we do NOT accept older versions
|
|
50
|
+
* (we cannot speak them) — the equality is the whole test. */
|
|
51
|
+
export function isSupportedProtocolVersion(version) {
|
|
52
|
+
return version === PROTOCOL_VERSION;
|
|
53
|
+
}
|
|
54
|
+
/** Remove the fork's custom bare `_meta` keys the connected agent did NOT advertise support for.
|
|
55
|
+
* A no-op when the agent advertised no namespace (`support` undefined => legacy => every key
|
|
56
|
+
* passes) or the meta is empty/undefined. Never mutates its input; collapses to undefined if
|
|
57
|
+
* gating empties the object (so no `_meta` is sent at all). */
|
|
58
|
+
export function gateCustomMeta(meta, support) {
|
|
59
|
+
if (!meta || !support)
|
|
60
|
+
return meta;
|
|
61
|
+
let gated;
|
|
62
|
+
for (const key of GATED_CUSTOM_META_KEYS) {
|
|
63
|
+
if (key in meta && support[key] !== true) {
|
|
64
|
+
gated ??= { ...meta };
|
|
65
|
+
delete gated[key];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const result = gated ?? meta;
|
|
69
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
70
|
+
}
|
|
71
|
+
/** The first client-provided MCP server whose transport the agent did NOT advertise, or undefined
|
|
72
|
+
* when every server is serviceable. stdio is ALWAYS serviceable (the baseline transport); http/sse
|
|
73
|
+
* are gated on mcpCapabilities.{http,sse}. Lenient for legacy agents: when the agent advertised no
|
|
74
|
+
* mcpCapabilities at all we cannot know its transports, so we do not gate (preserving today's
|
|
75
|
+
* send-and-let-the-agent-decide behavior for minimal/custom servers). */
|
|
76
|
+
export function unsupportedMcpServer(servers, agent) {
|
|
77
|
+
const mcp = agent.mcpCapabilities;
|
|
78
|
+
if (!mcp || !servers)
|
|
79
|
+
return undefined;
|
|
80
|
+
for (const server of servers) {
|
|
81
|
+
if ("type" in server && server.type === "http" && mcp.http !== true) {
|
|
82
|
+
return { name: server.name, transport: "http" };
|
|
83
|
+
}
|
|
84
|
+
if ("type" in server && server.type === "sse" && mcp.sse !== true) {
|
|
85
|
+
return { name: server.name, transport: "sse" };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { BACKENDS_ENV, registryWithRunBackends, resolveBackendRegistry } from ".
|
|
|
4
4
|
export type { BackendRegistry, CustomBackendConfig, RegisteredBackend } from "./registry.js";
|
|
5
5
|
export { PooledConnection, SessionHandle } from "./acp-client.js";
|
|
6
6
|
export type { AcpSessionOptions, PooledConnectionDeps } from "./acp-client.js";
|
|
7
|
+
export { GATED_CUSTOM_META_KEYS, gateCustomMeta, isSupportedProtocolVersion, negotiateCapabilities, unsupportedMcpServer, } from "./capabilities.js";
|
|
8
|
+
export type { NegotiatedCapabilities } from "./capabilities.js";
|
|
7
9
|
export { AcpAgentPool, resolvePoolSize } from "./pool.js";
|
|
8
10
|
export type { AcpPoolOptions, AcpPoolDeps } from "./pool.js";
|
|
9
11
|
export { TypedEventEmitter, emitSessionUpdate } from "./events.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC9F,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE7F,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAClE,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC9F,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE7F,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAClE,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAI/E,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC1D,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnE,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,9 @@ export { AcpAgentRunner, createAcpRunner, selectBackend } from "./runner.js";
|
|
|
8
8
|
// The custom-backend registry: run ANY ACP agent as an agent() target.
|
|
9
9
|
export { BACKENDS_ENV, registryWithRunBackends, resolveBackendRegistry } from "./registry.js";
|
|
10
10
|
export { PooledConnection, SessionHandle } from "./acp-client.js";
|
|
11
|
+
// ACP capability negotiation: parse/validate the initialize response and gate what the client
|
|
12
|
+
// sends (custom `_meta` keys, MCP transports) on what the connected agent advertised.
|
|
13
|
+
export { GATED_CUSTOM_META_KEYS, gateCustomMeta, isSupportedProtocolVersion, negotiateCapabilities, unsupportedMcpServer, } from "./capabilities.js";
|
|
11
14
|
export { AcpAgentPool, resolvePoolSize } from "./pool.js";
|
|
12
15
|
// The typed ACP event bus surfaced on AcpAgentRunner (`runner.on(name, evt => …)`).
|
|
13
16
|
export { TypedEventEmitter, emitSessionUpdate } from "./events.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automatalabs/acp-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@agentclientprotocol/sdk": "1.
|
|
28
|
-
"@agentclientprotocol/claude-agent-acp": "0.
|
|
29
|
-
"@automatalabs/codex-acp": "1.
|
|
27
|
+
"@agentclientprotocol/sdk": "1.1.0",
|
|
28
|
+
"@agentclientprotocol/claude-agent-acp": "0.55.0",
|
|
29
|
+
"@automatalabs/codex-acp": "1.3.0",
|
|
30
30
|
"typebox": "1.3.2",
|
|
31
|
-
"@automatalabs/shared-types": "0.
|
|
31
|
+
"@automatalabs/shared-types": "0.6.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsc -b",
|