@automatalabs/acp-agents 0.4.1 → 0.5.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/README.md +5 -3
- package/dist/acp-client.d.ts +4 -0
- package/dist/acp-client.d.ts.map +1 -1
- package/dist/acp-client.js +15 -4
- package/dist/backend.d.ts +13 -4
- package/dist/backend.d.ts.map +1 -1
- package/dist/backends/codex.d.ts.map +1 -1
- package/dist/backends/codex.js +4 -22
- package/dist/backends/custom.d.ts +14 -0
- package/dist/backends/custom.d.ts.map +1 -0
- package/dist/backends/custom.js +43 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/registry.d.ts +26 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +82 -0
- package/dist/runner.d.ts +21 -6
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +74 -13
- package/dist/structured-output.d.ts +8 -0
- package/dist/structured-output.d.ts.map +1 -1
- package/dist/structured-output.js +28 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -86,8 +86,9 @@ From [`src/index.ts`](./src/index.ts):
|
|
|
86
86
|
|
|
87
87
|
- **`createAcpRunner(options?)`** — factory returning an `AcpAgentRunner` (this is what `@automatalabs/workflows` injects into the engine).
|
|
88
88
|
- **`AcpAgentRunner`** — the `AgentRunner` implementation; `run(prompt, options)` and `dispose()`.
|
|
89
|
-
- **`selectBackend({ model, tier })`** — the cross-provider routing rule: which backend a spec maps to.
|
|
90
|
-
- **`ClaudeBackend` / `CodexBackend`** — the two backend strategies (spawn config + per-backend schema wiring).
|
|
89
|
+
- **`selectBackend({ model, tier }, registry?)`** — the cross-provider routing rule: which backend a spec maps to (registered custom names match first, exact or `name/<inner-model>`).
|
|
90
|
+
- **`ClaudeBackend` / `CodexBackend`** — the two built-in backend strategies (spawn config + per-backend schema wiring).
|
|
91
|
+
- **`CustomAcpBackend` / `resolveBackendRegistry` / `BACKENDS_ENV`** — the custom-backend registry: run **any** ACP agent as a named backend via `createAcpRunner({ backends: { name: { command, args?, env?, sessionMeta? } } })` or the `AGENTPRISM_BACKENDS` env var (JSON, same shape; the option wins per name; `claude`/`codex` reserved). Custom backends carry a `schema` as turn-level `_meta.outputSchema` and read the result off the final message as JSON.
|
|
91
92
|
- **`toJsonSchema(schema)` / `toStrictJsonSchema(schema)`** — turn a typebox schema into the on-the-wire shapes: a plain JSON Schema for Claude `outputFormat`, and an OpenAI-strict-normalized schema for Codex `outputSchema`.
|
|
92
93
|
|
|
93
94
|
Also exported: `AcpAgentPool` / `resolvePoolSize`, `PooledConnection` / `SessionHandle`, `decidePermission`, `UsageAccumulator`, `resolveStructuredOutput` / `extractValidated` / `findJsonBlock` / `validateValue`, `errorText` / `mapThrownError`, and the event surface `TypedEventEmitter` / `AcpRunnerEventMap` / `AcpEventName` / `AcpEventListener` / `AcpEventContext` / `AcpSessionUpdate` (+ the per-event payload types), plus their associated types.
|
|
@@ -96,7 +97,8 @@ Also exported: `AcpAgentPool` / `resolvePoolSize`, `PooledConnection` / `Session
|
|
|
96
97
|
|
|
97
98
|
| Variable | Effect |
|
|
98
99
|
| --- | --- |
|
|
99
|
-
| `AGENTPRISM_DEFAULT_BACKEND` | Backend when `model`/`tier` don't pick one (`codex` selects Codex; anything else is Claude). |
|
|
100
|
+
| `AGENTPRISM_DEFAULT_BACKEND` | Backend when `model`/`tier` don't pick one (`codex` selects Codex; a registered custom name selects that backend; anything else is Claude). |
|
|
101
|
+
| `AGENTPRISM_BACKENDS` | Custom ACP backends as JSON: `{"<name>": {"command": "…", "args": […], "env": {…}, "sessionMeta": {…}}}`. |
|
|
100
102
|
| `AGENTPRISM_ACP_POOL_SIZE` | Long-lived processes to keep per backend (default `1`). |
|
|
101
103
|
| `AGENTPRISM_CLAUDE_ACP_CMD` / `AGENTPRISM_CLAUDE_ACP_ARGS` | Override the command (and args) used to spawn the Claude ACP server. |
|
|
102
104
|
| `AGENTPRISM_CODEX_ACP_CMD` / `AGENTPRISM_CODEX_ACP_ARGS` | Override the command (and args) used to spawn the Codex ACP server. |
|
package/dist/acp-client.d.ts
CHANGED
|
@@ -39,6 +39,10 @@ export interface AcpSessionOptions {
|
|
|
39
39
|
signal?: AbortSignal;
|
|
40
40
|
/** Client-provided MCP servers to attach at session/new. Omitted => `[]` (the default). */
|
|
41
41
|
mcpServers?: McpServerConfig[];
|
|
42
|
+
/** Generic session-scoped `_meta` passthrough (RunOptions.meta). Merged FIRST, under the
|
|
43
|
+
* backend-computed `_meta` and the runId stamp, so user keys never clobber the schema /
|
|
44
|
+
* correlation channels. Omitted => the request `_meta` is whatever the backend set. */
|
|
45
|
+
meta?: Record<string, unknown>;
|
|
42
46
|
/** Engine run id, stamped onto session/new `_meta` (META_KEYS.runId) as a correlation id.
|
|
43
47
|
* Omitted => no runId `_meta` is stamped (the request `_meta` is whatever the backend set). */
|
|
44
48
|
runId?: string;
|
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,EAAa,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACrG,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACzF,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiB9C,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;oFACoF;AACpF,cAAM,YAAY;IAUd,QAAQ,CAAC,MAAM,EAAE,UAAU;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IAXzB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAM;IACnC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAM;IAC3C,QAAQ,CAAC,KAAK,mBAA0B;IACxC,gBAAgB,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC/C,OAAO,CAAC,cAAc,CAAK;IAE3B;6EACyE;gBAE9D,MAAM,EAAE,UAAU,EAClB,KAAK,CAAC,EAAE,MAAM,YAAA,EACd,KAAK,CAAC,EAAE,MAAM,YAAA;IAGzB,+FAA+F;IAC/F,SAAS,IAAI,IAAI;IAKjB,eAAe,IAAI,MAAM;IAIzB,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GAAG,IAAI;IAoCxD,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI;CAK7D;
|
|
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
|
@@ -171,6 +171,14 @@ class MultiplexClient {
|
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
/** Shallow-merge `_meta` layers lowest-to-highest precedence, treating empty layers as absent
|
|
175
|
+
* so an unconfigured session keeps sending NO `_meta` at all. */
|
|
176
|
+
function layerMeta(...layers) {
|
|
177
|
+
const present = layers.filter((l) => Boolean(l && Object.keys(l).length > 0));
|
|
178
|
+
if (present.length === 0)
|
|
179
|
+
return undefined;
|
|
180
|
+
return Object.assign({}, ...present);
|
|
181
|
+
}
|
|
174
182
|
/** Merge the engine runId correlation stamp into a backend's session/new `_meta`. Returns the
|
|
175
183
|
* meta unchanged when no runId is given (so a backend that sends no `_meta` keeps sending none). */
|
|
176
184
|
function stampRunId(meta, runId) {
|
|
@@ -316,12 +324,15 @@ export class PooledConnection {
|
|
|
316
324
|
try {
|
|
317
325
|
await this.ready;
|
|
318
326
|
const state = new SessionState(opts.policy, opts.label, opts.runId);
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
|
|
327
|
+
// session/new `_meta`, layered lowest-to-highest precedence: the backend's static
|
|
328
|
+
// defaults (a custom registry entry's `sessionMeta`), then the generic user passthrough
|
|
329
|
+
// (opts.meta), then the backend's protocol-critical `_meta` (Claude schema channel;
|
|
330
|
+
// Codex base/developer instructions), then the engine runId correlation stamp. When no
|
|
331
|
+
// layer is present, no `_meta` is sent.
|
|
332
|
+
const meta = stampRunId(layerMeta(this.backend.sessionMetaDefaults?.(), opts.meta, this.backend.sessionMeta(opts.schema, {
|
|
322
333
|
baseInstructions: opts.baseInstructions,
|
|
323
334
|
developerInstructions: opts.developerInstructions,
|
|
324
|
-
}), opts.runId);
|
|
335
|
+
})), opts.runId);
|
|
325
336
|
const request = {
|
|
326
337
|
cwd: opts.cwd,
|
|
327
338
|
// Client-provided MCP servers (additive run input), else the default empty list.
|
package/dist/backend.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { TSchema } from "typebox";
|
|
2
|
-
|
|
2
|
+
/** The two built-in backends. Custom registry backends extend the id space beyond these. */
|
|
3
|
+
export type BuiltinBackendId = "claude" | "codex";
|
|
4
|
+
/** A backend id: one of the built-ins, or the registered name of a custom ACP backend
|
|
5
|
+
* (see registry.ts). The pool keys connections by this id, so ids must be stable. */
|
|
6
|
+
export type BackendId = string;
|
|
3
7
|
export interface SpawnConfig {
|
|
4
8
|
command: string;
|
|
5
9
|
args: string[];
|
|
@@ -24,9 +28,14 @@ export interface Backend {
|
|
|
24
28
|
readonly id: BackendId;
|
|
25
29
|
/** How to launch this backend's ACP server over stdio. */
|
|
26
30
|
spawnConfig(): SpawnConfig;
|
|
27
|
-
/** `_meta` for session/new (
|
|
28
|
-
*
|
|
29
|
-
*
|
|
31
|
+
/** OPTIONAL backend-level `_meta` DEFAULTS for session/new (e.g. a custom registry entry's
|
|
32
|
+
* static `sessionMeta`). Lowest precedence: per-call RunOptions.meta merges OVER these,
|
|
33
|
+
* and sessionMeta()'s protocol-critical keys merge over both. */
|
|
34
|
+
sessionMetaDefaults?(): Record<string, unknown> | undefined;
|
|
35
|
+
/** PROTOCOL-CRITICAL `_meta` for session/new (undefined when this backend carries nothing
|
|
36
|
+
* there) — e.g. the Claude schema channel. Highest precedence below the runId stamp: these
|
|
37
|
+
* keys win over the generic user passthrough. `inputs` carries optional per-session extras
|
|
38
|
+
* (e.g. Codex base/developer instructions); a backend that has no use for them ignores it. */
|
|
30
39
|
sessionMeta(schema: TSchema | undefined, inputs?: SessionMetaInputs): Record<string, unknown> | undefined;
|
|
31
40
|
/** `_meta` for session/prompt (undefined when this backend carries the schema at session/new). */
|
|
32
41
|
promptMeta(schema: TSchema | undefined): Record<string, unknown> | undefined;
|
package/dist/backend.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,4FAA4F;AAC5F,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAClD;sFACsF;AACtF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,8FAA8F;AAC9F,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,eAAe,IAAI,MAAM,CAAC;IAC1B,uGAAuG;IACvG,mBAAmB,IAAI,OAAO,CAAC;CAChC;AAED;+EAC+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sFAAsF;IACtF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,0DAA0D;IAC1D,WAAW,IAAI,WAAW,CAAC;IAC3B;;sEAEkE;IAClE,mBAAmB,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC5D;;;mGAG+F;IAC/F,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC1G,kGAAkG;IAClG,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC7E,oGAAoG;IACpG,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC;CACrD;AAED,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAE7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/backends/codex.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAO/F,qBAAa,YAAa,YAAW,OAAO;IAC1C,QAAQ,CAAC,EAAE,EAAG,OAAO,CAAU;IAE/B,WAAW,IAAI,WAAW;IAe1B,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAY1G,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAK5E,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/backends/codex.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAO/F,qBAAa,YAAa,YAAW,OAAO;IAC1C,QAAQ,CAAC,EAAE,EAAG,OAAO,CAAU;IAE/B,WAAW,IAAI,WAAW;IAe1B,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAY1G,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAK5E,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO;CAKpD"}
|
package/dist/backends/codex.js
CHANGED
|
@@ -10,7 +10,7 @@ import { createRequire } from "node:module";
|
|
|
10
10
|
import { CODEX_META_KEYS, META_KEYS } from "@automatalabs/shared-types";
|
|
11
11
|
import { splitArgs } from "../backend.js";
|
|
12
12
|
import { toStrictJsonSchema } from "../schema-strict.js";
|
|
13
|
-
import {
|
|
13
|
+
import { parseFinalJson } from "../structured-output.js";
|
|
14
14
|
const require = createRequire(import.meta.url);
|
|
15
15
|
export class CodexBackend {
|
|
16
16
|
id = "codex";
|
|
@@ -46,26 +46,8 @@ export class CodexBackend {
|
|
|
46
46
|
return { [META_KEYS.outputSchema]: toStrictJsonSchema(schema) };
|
|
47
47
|
}
|
|
48
48
|
nativeStructured(source) {
|
|
49
|
-
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
// The constrained final message is pure JSON; parse it directly, then fall back to a
|
|
53
|
-
// balanced-block extraction if the turn also emitted leading prose.
|
|
54
|
-
try {
|
|
55
|
-
return JSON.parse(text);
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// fall through to block extraction
|
|
59
|
-
}
|
|
60
|
-
const block = findJsonBlock(text);
|
|
61
|
-
if (block !== undefined) {
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(block);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// give up; the runner's ladder will re-prompt / extract.
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return undefined;
|
|
49
|
+
// The constrained final message is pure JSON; parse it directly, with a balanced-block
|
|
50
|
+
// fallback if the turn also emitted leading prose.
|
|
51
|
+
return parseFinalJson(source.currentTurnText());
|
|
70
52
|
}
|
|
71
53
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TSchema } from "typebox";
|
|
2
|
+
import type { Backend, SpawnConfig, StructuredSource } from "../backend.js";
|
|
3
|
+
import type { RegisteredBackend } from "../registry.js";
|
|
4
|
+
export declare class CustomAcpBackend implements Backend {
|
|
5
|
+
private readonly config;
|
|
6
|
+
readonly id: string;
|
|
7
|
+
constructor(config: RegisteredBackend);
|
|
8
|
+
spawnConfig(): SpawnConfig;
|
|
9
|
+
sessionMetaDefaults(): Record<string, unknown> | undefined;
|
|
10
|
+
sessionMeta(_schema: TSchema | undefined): Record<string, unknown> | undefined;
|
|
11
|
+
promptMeta(schema: TSchema | undefined): Record<string, unknown> | undefined;
|
|
12
|
+
nativeStructured(source: StructuredSource): unknown;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=custom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/backends/custom.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAIxD,qBAAa,gBAAiB,YAAW,OAAO;IAGlC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAES,MAAM,EAAE,iBAAiB;IAItD,WAAW,IAAI,WAAW;IAS1B,mBAAmB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAQ1D,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAM9E,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAQ5E,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO;CAGpD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { META_KEYS } from "@automatalabs/shared-types";
|
|
2
|
+
import { toJsonSchema } from "../schema-strict.js";
|
|
3
|
+
import { parseFinalJson } from "../structured-output.js";
|
|
4
|
+
export class CustomAcpBackend {
|
|
5
|
+
config;
|
|
6
|
+
id;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.id = config.name;
|
|
10
|
+
}
|
|
11
|
+
spawnConfig() {
|
|
12
|
+
return {
|
|
13
|
+
command: this.config.command,
|
|
14
|
+
args: [...(this.config.args ?? [])],
|
|
15
|
+
// Registry-declared env merges OVER the inherited environment.
|
|
16
|
+
env: { ...process.env, ...(this.config.env ?? {}) },
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
sessionMetaDefaults() {
|
|
20
|
+
// The registry entry's static `_meta` — DEFAULTS, so per-call RunOptions.meta overrides
|
|
21
|
+
// them. Return a copy so callers can merge without mutating config.
|
|
22
|
+
const staticMeta = this.config.sessionMeta;
|
|
23
|
+
if (!staticMeta || Object.keys(staticMeta).length === 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
return { ...staticMeta };
|
|
26
|
+
}
|
|
27
|
+
sessionMeta(_schema) {
|
|
28
|
+
// The schema rides the turn (see promptMeta); a custom backend has no protocol-critical
|
|
29
|
+
// session/new `_meta` of its own.
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
promptMeta(schema) {
|
|
33
|
+
if (!schema)
|
|
34
|
+
return undefined;
|
|
35
|
+
// Plain JSON Schema (NOT OpenAI-strict-normalized — strictness is a Codex/Responses-API
|
|
36
|
+
// constraint, not part of the generic dialect). Agents that ignore it are repaired by the
|
|
37
|
+
// runner's ladder.
|
|
38
|
+
return { [META_KEYS.outputSchema]: toJsonSchema(schema) };
|
|
39
|
+
}
|
|
40
|
+
nativeStructured(source) {
|
|
41
|
+
return parseFinalJson(source.currentTurnText());
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
export { AcpAgentRunner, createAcpRunner, selectBackend } from "./runner.js";
|
|
2
|
+
export type { AcpRunnerOptions } from "./runner.js";
|
|
3
|
+
export { BACKENDS_ENV, resolveBackendRegistry } from "./registry.js";
|
|
4
|
+
export type { BackendRegistry, CustomBackendConfig, RegisteredBackend } from "./registry.js";
|
|
2
5
|
export { PooledConnection, SessionHandle } from "./acp-client.js";
|
|
3
6
|
export type { AcpSessionOptions, PooledConnectionDeps } from "./acp-client.js";
|
|
4
7
|
export { AcpAgentPool, resolvePoolSize } from "./pool.js";
|
|
5
8
|
export type { AcpPoolOptions, AcpPoolDeps } from "./pool.js";
|
|
6
9
|
export { TypedEventEmitter, emitSessionUpdate } from "./events.js";
|
|
7
10
|
export type { AcpRunnerEventMap, AcpEventName, AcpEventListener, AcpEventContext, AcpEventSink, AcpSessionUpdate, AcpUpdateKind, AcpPermissionEvent, AcpRawMessageEvent, AcpBackendErrorEvent, } from "./events.js";
|
|
8
|
-
export type { Backend, BackendId, SessionMetaInputs, SpawnConfig, StructuredSource } from "./backend.js";
|
|
11
|
+
export type { Backend, BackendId, BuiltinBackendId, SessionMetaInputs, SpawnConfig, StructuredSource, } from "./backend.js";
|
|
9
12
|
export { ClaudeBackend } from "./backends/claude.js";
|
|
10
13
|
export { CodexBackend } from "./backends/codex.js";
|
|
14
|
+
export { CustomAcpBackend } from "./backends/custom.js";
|
|
11
15
|
export { decidePermission } from "./permissions.js";
|
|
12
16
|
export type { ToolPolicy } from "./permissions.js";
|
|
13
17
|
export { UsageAccumulator } from "./usage.js";
|
|
14
18
|
export { toJsonSchema, toStrictJsonSchema } from "./schema-strict.js";
|
|
15
|
-
export { extractValidated, findJsonBlock, resolveStructuredOutput, validateValue, } from "./structured-output.js";
|
|
19
|
+
export { extractValidated, findJsonBlock, parseFinalJson, resolveStructuredOutput, validateValue, } from "./structured-output.js";
|
|
16
20
|
export type { ResolveOptions, StructuredSession } from "./structured-output.js";
|
|
17
21
|
export { errorText, mapThrownError } from "./errors-map.js";
|
|
18
22
|
//# sourceMappingURL=index.d.ts.map
|
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;
|
|
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,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACrE,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;AAC/E,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
|
@@ -5,14 +5,17 @@
|
|
|
5
5
|
// @automatalabs/workflow-engine; the two siblings meet ONLY at AgentRunner, injected by the
|
|
6
6
|
// @automatalabs/workflows facade (which mcp-server builds on) via createAcpRunner().
|
|
7
7
|
export { AcpAgentRunner, createAcpRunner, selectBackend } from "./runner.js";
|
|
8
|
+
// The custom-backend registry: run ANY ACP agent as an agent() target.
|
|
9
|
+
export { BACKENDS_ENV, resolveBackendRegistry } from "./registry.js";
|
|
8
10
|
export { PooledConnection, SessionHandle } from "./acp-client.js";
|
|
9
11
|
export { AcpAgentPool, resolvePoolSize } from "./pool.js";
|
|
10
12
|
// The typed ACP event bus surfaced on AcpAgentRunner (`runner.on(name, evt => …)`).
|
|
11
13
|
export { TypedEventEmitter, emitSessionUpdate } from "./events.js";
|
|
12
14
|
export { ClaudeBackend } from "./backends/claude.js";
|
|
13
15
|
export { CodexBackend } from "./backends/codex.js";
|
|
16
|
+
export { CustomAcpBackend } from "./backends/custom.js";
|
|
14
17
|
export { decidePermission } from "./permissions.js";
|
|
15
18
|
export { UsageAccumulator } from "./usage.js";
|
|
16
19
|
export { toJsonSchema, toStrictJsonSchema } from "./schema-strict.js";
|
|
17
|
-
export { extractValidated, findJsonBlock, resolveStructuredOutput, validateValue, } from "./structured-output.js";
|
|
20
|
+
export { extractValidated, findJsonBlock, parseFinalJson, resolveStructuredOutput, validateValue, } from "./structured-output.js";
|
|
18
21
|
export { errorText, mapThrownError } from "./errors-map.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const BACKENDS_ENV = "AGENTPRISM_BACKENDS";
|
|
2
|
+
/** How to spawn (and optionally pre-configure) one custom ACP backend. */
|
|
3
|
+
export interface CustomBackendConfig {
|
|
4
|
+
/** The ACP server executable (absolute path or on PATH). */
|
|
5
|
+
command: string;
|
|
6
|
+
/** Arguments for the command. Default []. */
|
|
7
|
+
args?: string[];
|
|
8
|
+
/** Extra environment for the subprocess, merged OVER the inherited process.env. */
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
/** Static `_meta` sent on every session/new for this backend (backend-level defaults).
|
|
11
|
+
* Per-call RunOptions.meta merges over these; backend-computed keys win over both. */
|
|
12
|
+
sessionMeta?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
/** A validated registry entry: the (lowercased) name plus its config. */
|
|
15
|
+
export interface RegisteredBackend extends CustomBackendConfig {
|
|
16
|
+
name: string;
|
|
17
|
+
}
|
|
18
|
+
export type BackendRegistry = ReadonlyMap<string, RegisteredBackend>;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the custom-backend registry: env-declared backends first, programmatic `backends`
|
|
21
|
+
* merged over them (option wins per name). Throws on malformed JSON, invalid names/configs, or
|
|
22
|
+
* reserved names — a misconfigured registry should fail LOUDLY at construction, not silently
|
|
23
|
+
* misroute at run time.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveBackendRegistry(option?: Record<string, CustomBackendConfig>, env?: NodeJS.ProcessEnv): BackendRegistry;
|
|
26
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAKlD,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;2FACuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAErE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC5C,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,eAAe,CAwBjB"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The custom-backend REGISTRY — lets any ACP agent plug in as an agent() target, not just the
|
|
2
|
+
// built-in Claude/Codex pair. A registered backend is (name -> how to spawn it + optional static
|
|
3
|
+
// session `_meta`), resolved once at runner construction from:
|
|
4
|
+
// 1. the programmatic option: createAcpRunner({ backends: { name: config } }) (wins per name)
|
|
5
|
+
// 2. the env var AGENTPRISM_BACKENDS: a JSON object of the same shape
|
|
6
|
+
// Routing then matches `model`/`tier` specs against registered names FIRST (exact name, or
|
|
7
|
+
// `name/<inner-model>` prefix), before the built-in claude/codex heuristics — see runner.ts.
|
|
8
|
+
// Names are case-insensitive (stored lowercased); "claude" and "codex" are reserved because the
|
|
9
|
+
// built-ins own their ids (and their own AGENTPRISM_*_ACP_CMD override channel).
|
|
10
|
+
export const BACKENDS_ENV = "AGENTPRISM_BACKENDS";
|
|
11
|
+
const RESERVED_NAMES = new Set(["claude", "codex"]);
|
|
12
|
+
const NAME_PATTERN = /^[a-z][a-z0-9._-]*$/;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the custom-backend registry: env-declared backends first, programmatic `backends`
|
|
15
|
+
* merged over them (option wins per name). Throws on malformed JSON, invalid names/configs, or
|
|
16
|
+
* reserved names — a misconfigured registry should fail LOUDLY at construction, not silently
|
|
17
|
+
* misroute at run time.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveBackendRegistry(option, env = process.env) {
|
|
20
|
+
const registry = new Map();
|
|
21
|
+
const raw = env[BACKENDS_ENV];
|
|
22
|
+
if (raw !== undefined && raw.trim() !== "") {
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new Error(`${BACKENDS_ENV} is not valid JSON: ${error.message}`);
|
|
29
|
+
}
|
|
30
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
31
|
+
throw new Error(`${BACKENDS_ENV} must be a JSON object of { "<name>": { "command": … } }`);
|
|
32
|
+
}
|
|
33
|
+
for (const [name, config] of Object.entries(parsed)) {
|
|
34
|
+
registry.set(...validateEntry(name, config, BACKENDS_ENV));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const [name, config] of Object.entries(option ?? {})) {
|
|
38
|
+
registry.set(...validateEntry(name, config, "backends option"));
|
|
39
|
+
}
|
|
40
|
+
return registry;
|
|
41
|
+
}
|
|
42
|
+
function validateEntry(rawName, config, source) {
|
|
43
|
+
const name = rawName.toLowerCase();
|
|
44
|
+
if (!NAME_PATTERN.test(name)) {
|
|
45
|
+
throw new Error(`${source}: invalid backend name "${rawName}" (must match ${NAME_PATTERN}, matched case-insensitively)`);
|
|
46
|
+
}
|
|
47
|
+
if (RESERVED_NAMES.has(name)) {
|
|
48
|
+
throw new Error(`${source}: backend name "${rawName}" is reserved for the built-in backend (use AGENTPRISM_${name.toUpperCase()}_ACP_CMD to override how it spawns)`);
|
|
49
|
+
}
|
|
50
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) {
|
|
51
|
+
throw new Error(`${source}: backend "${rawName}" must be an object with at least { command }`);
|
|
52
|
+
}
|
|
53
|
+
const c = config;
|
|
54
|
+
if (typeof c.command !== "string" || c.command.trim() === "") {
|
|
55
|
+
throw new Error(`${source}: backend "${rawName}" needs a non-empty string "command"`);
|
|
56
|
+
}
|
|
57
|
+
if (c.args !== undefined && !(Array.isArray(c.args) && c.args.every((a) => typeof a === "string"))) {
|
|
58
|
+
throw new Error(`${source}: backend "${rawName}" "args" must be an array of strings`);
|
|
59
|
+
}
|
|
60
|
+
if (c.env !== undefined && !isStringRecord(c.env)) {
|
|
61
|
+
throw new Error(`${source}: backend "${rawName}" "env" must be an object of string values`);
|
|
62
|
+
}
|
|
63
|
+
if (c.sessionMeta !== undefined && (c.sessionMeta === null || typeof c.sessionMeta !== "object" || Array.isArray(c.sessionMeta))) {
|
|
64
|
+
throw new Error(`${source}: backend "${rawName}" "sessionMeta" must be an object`);
|
|
65
|
+
}
|
|
66
|
+
return [
|
|
67
|
+
name,
|
|
68
|
+
{
|
|
69
|
+
name,
|
|
70
|
+
command: c.command,
|
|
71
|
+
...(c.args !== undefined ? { args: c.args } : {}),
|
|
72
|
+
...(c.env !== undefined ? { env: c.env } : {}),
|
|
73
|
+
...(c.sessionMeta !== undefined ? { sessionMeta: c.sessionMeta } : {}),
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
function isStringRecord(value) {
|
|
78
|
+
return (value !== null &&
|
|
79
|
+
typeof value === "object" &&
|
|
80
|
+
!Array.isArray(value) &&
|
|
81
|
+
Object.values(value).every((v) => typeof v === "string"));
|
|
82
|
+
}
|
package/dist/runner.d.ts
CHANGED
|
@@ -3,13 +3,23 @@ import type { TSchema } from "typebox";
|
|
|
3
3
|
import { type AcpPoolOptions } from "./pool.js";
|
|
4
4
|
import { type AcpEventListener, type AcpEventName } from "./events.js";
|
|
5
5
|
import type { Backend } from "./backend.js";
|
|
6
|
+
import { type BackendRegistry, type CustomBackendConfig } from "./registry.js";
|
|
7
|
+
/** Constructor options for the runner: pool sizing PLUS the custom-backend registry.
|
|
8
|
+
* `backends` merges over (and wins against) env-declared AGENTPRISM_BACKENDS entries. */
|
|
9
|
+
export interface AcpRunnerOptions extends AcpPoolOptions {
|
|
10
|
+
/** Custom ACP backends, keyed by registered name (see registry.ts for the config shape
|
|
11
|
+
* and the routing rules). Names are case-insensitive; "claude"/"codex" are reserved. */
|
|
12
|
+
backends?: Record<string, CustomBackendConfig>;
|
|
13
|
+
}
|
|
6
14
|
export declare class AcpAgentRunner implements AgentRunner {
|
|
7
15
|
private readonly pool;
|
|
16
|
+
/** The resolved custom-backend registry (env + option, validated at construction). */
|
|
17
|
+
private readonly backends;
|
|
8
18
|
/** Typed bus carrying every ACP event from every pooled session. Beyond the AgentRunner seam
|
|
9
19
|
* (additive observability) — subscribing never affects a run and never enters the resume hash. */
|
|
10
20
|
private readonly events;
|
|
11
21
|
private readonly emitEvent;
|
|
12
|
-
constructor(options?:
|
|
22
|
+
constructor(options?: AcpRunnerOptions);
|
|
13
23
|
/**
|
|
14
24
|
* Listen in on the live ACP stream. `name` is an ACP `sessionUpdate` discriminant
|
|
15
25
|
* ("agent_message_chunk", "tool_call", "usage_update", …) or one of the cross-cutting events
|
|
@@ -31,12 +41,17 @@ export declare class AcpAgentRunner implements AgentRunner {
|
|
|
31
41
|
dispose(): Promise<void>;
|
|
32
42
|
}
|
|
33
43
|
/** Factory the mcp-server composition root calls to inject the runner into the engine. The pool
|
|
34
|
-
* size
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
* size and the custom-backend registry are runner-level options — NOT RunOptions fields, so they
|
|
45
|
+
* never enter hashAgentCall / the resume identity. Env fallbacks: AGENTPRISM_ACP_POOL_SIZE for
|
|
46
|
+
* size, AGENTPRISM_BACKENDS (JSON) for backends. */
|
|
47
|
+
export declare function createAcpRunner(options?: AcpRunnerOptions): AcpAgentRunner;
|
|
48
|
+
/** Pick the backend by model/tier. Cross-provider routing = which ACP server to spawn.
|
|
49
|
+
* Registered CUSTOM names resolve FIRST (exact name, or `name/<inner-model>` prefix) so a
|
|
50
|
+
* registry entry is never shadowed by the built-in heuristics; then the claude/codex
|
|
51
|
+
* heuristics; then the default backend (AGENTPRISM_DEFAULT_BACKEND — which may itself name
|
|
52
|
+
* a registered custom backend). */
|
|
38
53
|
export declare function selectBackend(opts: {
|
|
39
54
|
model?: string;
|
|
40
55
|
tier?: string;
|
|
41
|
-
}): Backend;
|
|
56
|
+
}, registry?: BackendRegistry): Backend;
|
|
42
57
|
//# sourceMappingURL=runner.d.ts.map
|
package/dist/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAGlB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAGlB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI5C,OAAO,EAA0B,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAOvG;0FAC0F;AAC1F,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD;6FACyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAChD;AAED,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C;uGACmG;IACnG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8C;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgE;gBAE9E,OAAO,GAAE,gBAAqB;IAK1C;;;;;;;;OAQG;IACH,EAAE,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAI9E,+EAA+E;IAC/E,IAAI,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAIhF,GAAG,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI;IAIzE,kBAAkB,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,IAAI;IAI7C,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAInC,GAAG,CAAC,CAAC,SAAS,OAAO,GAAG,SAAS,GAAG,SAAS,EACjD,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,UAAU,CAAC,CAAC,CAAM,GAC1B,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IA+F1B;qGACiG;IAC3F,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/B;AAED;;;qDAGqD;AACrD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,cAAc,CAE1E;AAwED;;;;oCAIoC;AACpC,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,OAAO,CAM1G"}
|
package/dist/runner.js
CHANGED
|
@@ -21,16 +21,21 @@ import { AcpAgentPool } from "./pool.js";
|
|
|
21
21
|
import { TypedEventEmitter, } from "./events.js";
|
|
22
22
|
import { ClaudeBackend } from "./backends/claude.js";
|
|
23
23
|
import { CodexBackend } from "./backends/codex.js";
|
|
24
|
+
import { CustomAcpBackend } from "./backends/custom.js";
|
|
25
|
+
import { resolveBackendRegistry } from "./registry.js";
|
|
24
26
|
import { mapThrownError } from "./errors-map.js";
|
|
25
27
|
import { resolveStructuredOutput } from "./structured-output.js";
|
|
26
28
|
export class AcpAgentRunner {
|
|
27
29
|
pool;
|
|
30
|
+
/** The resolved custom-backend registry (env + option, validated at construction). */
|
|
31
|
+
backends;
|
|
28
32
|
/** Typed bus carrying every ACP event from every pooled session. Beyond the AgentRunner seam
|
|
29
33
|
* (additive observability) — subscribing never affects a run and never enters the resume hash. */
|
|
30
34
|
events = new TypedEventEmitter();
|
|
31
35
|
emitEvent = (name, event) => this.events.emit(name, event);
|
|
32
36
|
constructor(options = {}) {
|
|
33
37
|
this.pool = new AcpAgentPool(options, { onEvent: this.emitEvent });
|
|
38
|
+
this.backends = resolveBackendRegistry(options.backends);
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* Listen in on the live ACP stream. `name` is an ACP `sessionUpdate` discriminant
|
|
@@ -60,7 +65,7 @@ export class AcpAgentRunner {
|
|
|
60
65
|
async run(prompt, options = {}) {
|
|
61
66
|
const opts = options;
|
|
62
67
|
const schema = opts.schema;
|
|
63
|
-
const backend = selectBackend(opts);
|
|
68
|
+
const backend = selectBackend(opts, this.backends);
|
|
64
69
|
const policy = { allow: opts.toolNames, deny: opts.disallowedToolNames };
|
|
65
70
|
const cwd = opts.cwd ?? process.cwd();
|
|
66
71
|
const session = await this.pool.acquire(backend, {
|
|
@@ -69,6 +74,9 @@ export class AcpAgentRunner {
|
|
|
69
74
|
policy,
|
|
70
75
|
signal: opts.signal,
|
|
71
76
|
mcpServers: opts.mcpServers,
|
|
77
|
+
// Generic session-scoped _meta passthrough (RunOptions.meta) — merged UNDER the
|
|
78
|
+
// backend-computed keys and the runId stamp in openSession. Additive; never hashed.
|
|
79
|
+
meta: opts.meta,
|
|
72
80
|
// Engine correlation id -> session/new _meta (META_KEYS.runId). Additive; never hashed.
|
|
73
81
|
runId: opts.runId,
|
|
74
82
|
// Stamped onto emitted ACP events as context (never sent on the wire).
|
|
@@ -80,9 +88,14 @@ export class AcpAgentRunner {
|
|
|
80
88
|
});
|
|
81
89
|
try {
|
|
82
90
|
opts.signal?.throwIfAborted();
|
|
83
|
-
|
|
91
|
+
// For a CUSTOM backend chosen by its registered name, the name itself is routing, not a
|
|
92
|
+
// model id: "browser" selects nothing; "browser/foo" selects "foo". Built-ins get the
|
|
93
|
+
// full spec unchanged (their catalogs match provider-prefixed and bare ids).
|
|
94
|
+
await applyModelSelection(session, innerModelSpec(opts.model ?? opts.tier, backend), opts);
|
|
84
95
|
const text = buildPrompt(prompt, opts, Boolean(schema));
|
|
85
|
-
|
|
96
|
+
// Generic turn-scoped _meta passthrough merged UNDER the backend-computed keys (e.g. the
|
|
97
|
+
// outputSchema forward when a schema is set) — user meta never clobbers the schema channel.
|
|
98
|
+
const promptMeta = mergeTurnMeta(opts.promptMeta, backend.promptMeta(schema));
|
|
86
99
|
const response = await session.prompt(text, promptMeta);
|
|
87
100
|
opts.signal?.throwIfAborted();
|
|
88
101
|
// Inspect the turn's stop reason BEFORE the text/schema path: a refusal or truncation
|
|
@@ -153,8 +166,9 @@ export class AcpAgentRunner {
|
|
|
153
166
|
}
|
|
154
167
|
}
|
|
155
168
|
/** Factory the mcp-server composition root calls to inject the runner into the engine. The pool
|
|
156
|
-
* size
|
|
157
|
-
*
|
|
169
|
+
* size and the custom-backend registry are runner-level options — NOT RunOptions fields, so they
|
|
170
|
+
* never enter hashAgentCall / the resume identity. Env fallbacks: AGENTPRISM_ACP_POOL_SIZE for
|
|
171
|
+
* size, AGENTPRISM_BACKENDS (JSON) for backends. */
|
|
158
172
|
export function createAcpRunner(options) {
|
|
159
173
|
return new AcpAgentRunner(options);
|
|
160
174
|
}
|
|
@@ -188,9 +202,9 @@ function assertNormalStopReason(stopReason, label) {
|
|
|
188
202
|
return;
|
|
189
203
|
}
|
|
190
204
|
}
|
|
191
|
-
async function applyModelSelection(session, opts) {
|
|
192
|
-
// `
|
|
193
|
-
|
|
205
|
+
async function applyModelSelection(session, spec, opts) {
|
|
206
|
+
// `spec` is opts.model ?? opts.tier (`model` wins — frozen contract), with a custom
|
|
207
|
+
// backend's routing name already stripped by innerModelSpec.
|
|
194
208
|
if (!spec)
|
|
195
209
|
return;
|
|
196
210
|
const { matched, resolved, modifierFallbacks } = await session.selectModel(spec);
|
|
@@ -221,11 +235,50 @@ function buildPrompt(prompt, opts, structured) {
|
|
|
221
235
|
}
|
|
222
236
|
return parts.join("\n\n");
|
|
223
237
|
}
|
|
224
|
-
/** Pick the backend by model/tier. Cross-provider routing = which ACP server to spawn.
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
/** Pick the backend by model/tier. Cross-provider routing = which ACP server to spawn.
|
|
239
|
+
* Registered CUSTOM names resolve FIRST (exact name, or `name/<inner-model>` prefix) so a
|
|
240
|
+
* registry entry is never shadowed by the built-in heuristics; then the claude/codex
|
|
241
|
+
* heuristics; then the default backend (AGENTPRISM_DEFAULT_BACKEND — which may itself name
|
|
242
|
+
* a registered custom backend). */
|
|
243
|
+
export function selectBackend(opts, registry) {
|
|
244
|
+
const custom = customBackendForSpec(opts.model, registry) ?? customBackendForSpec(opts.tier, registry);
|
|
245
|
+
if (custom)
|
|
246
|
+
return custom;
|
|
247
|
+
const id = backendIdForSpec(opts.model) ?? backendIdForSpec(opts.tier) ?? defaultBackendId(registry);
|
|
248
|
+
if (typeof id !== "string")
|
|
249
|
+
return id; // the default resolved to a registered custom backend
|
|
227
250
|
return id === "codex" ? new CodexBackend() : new ClaudeBackend();
|
|
228
251
|
}
|
|
252
|
+
/** Match a model/tier spec against the registry: the whole spec, or its `<name>/` prefix. */
|
|
253
|
+
function customBackendForSpec(spec, registry) {
|
|
254
|
+
if (!spec || !registry || registry.size === 0)
|
|
255
|
+
return undefined;
|
|
256
|
+
const lower = spec.toLowerCase();
|
|
257
|
+
const slash = lower.indexOf("/");
|
|
258
|
+
const name = slash > 0 ? lower.slice(0, slash) : lower;
|
|
259
|
+
const config = registry.get(name);
|
|
260
|
+
return config ? new CustomAcpBackend(config) : undefined;
|
|
261
|
+
}
|
|
262
|
+
/** Strip a custom backend's routing name off the model/tier spec: the spec `"name"` selects no
|
|
263
|
+
* inner model; `"name/foo"` selects `"foo"`. Built-in backends receive the spec unchanged, and
|
|
264
|
+
* a spec that reached a custom DEFAULT backend without naming it also passes through (the
|
|
265
|
+
* agent's own catalog may know it). */
|
|
266
|
+
function innerModelSpec(spec, backend) {
|
|
267
|
+
if (!spec || !(backend instanceof CustomAcpBackend))
|
|
268
|
+
return spec;
|
|
269
|
+
const lower = spec.toLowerCase();
|
|
270
|
+
if (lower === backend.id)
|
|
271
|
+
return undefined;
|
|
272
|
+
if (lower.startsWith(`${backend.id}/`))
|
|
273
|
+
return spec.slice(backend.id.length + 1) || undefined;
|
|
274
|
+
return spec;
|
|
275
|
+
}
|
|
276
|
+
/** Merge the generic turn-scoped meta passthrough UNDER the backend-computed turn meta. */
|
|
277
|
+
function mergeTurnMeta(user, backend) {
|
|
278
|
+
if (!user)
|
|
279
|
+
return backend;
|
|
280
|
+
return { ...user, ...(backend ?? {}) };
|
|
281
|
+
}
|
|
229
282
|
function backendIdForSpec(spec) {
|
|
230
283
|
if (!spec)
|
|
231
284
|
return undefined;
|
|
@@ -243,6 +296,14 @@ function backendIdForSpec(spec) {
|
|
|
243
296
|
return "claude";
|
|
244
297
|
return undefined;
|
|
245
298
|
}
|
|
246
|
-
|
|
247
|
-
|
|
299
|
+
/** Resolve the default backend: a registered custom name wins (returned as a Backend), else
|
|
300
|
+
* the built-in id. An unknown/unset value falls back to "claude" (the historical default). */
|
|
301
|
+
function defaultBackendId(registry) {
|
|
302
|
+
const name = process.env.AGENTPRISM_DEFAULT_BACKEND?.toLowerCase();
|
|
303
|
+
if (name && registry) {
|
|
304
|
+
const config = registry.get(name);
|
|
305
|
+
if (config)
|
|
306
|
+
return new CustomAcpBackend(config);
|
|
307
|
+
}
|
|
308
|
+
return name === "codex" ? "codex" : "claude";
|
|
248
309
|
}
|
|
@@ -5,6 +5,14 @@ import type { TSchema } from "typebox";
|
|
|
5
5
|
* raw JSON string, or undefined when none is found. (Ported VERBATIM from pi agent.ts.)
|
|
6
6
|
*/
|
|
7
7
|
export declare function findJsonBlock(text: string): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Parse a turn's final assistant text as JSON: direct parse first (a natively-constrained
|
|
10
|
+
* final message is pure JSON), then a balanced-block extraction when the turn also emitted
|
|
11
|
+
* leading prose. Returns undefined when no parseable JSON is present (the caller's ladder
|
|
12
|
+
* re-prompts). Shared by the Codex backend and every custom registry backend — the "read the
|
|
13
|
+
* structured result off the final message" convention is the generic ACP dialect.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseFinalJson(text: string): unknown;
|
|
8
16
|
/** Coerce an already-parsed value toward the schema and accept it only if it then validates. */
|
|
9
17
|
export declare function validateValue(value: unknown, schema: TSchema): unknown;
|
|
10
18
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structured-output.d.ts","sourceRoot":"","sources":["../src/structured-output.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIvC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAa9D;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAQtE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAUvE;AAED,sGAAsG;AACtG,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,+DAA+D;IAC/D,QAAQ,IAAI,MAAM,CAAC;IACnB,8FAA8F;IAC9F,SAAS,IAAI,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAQD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
|
|
1
|
+
{"version":3,"file":"structured-output.d.ts","sourceRoot":"","sources":["../src/structured-output.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIvC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAa9D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAiBpD;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAQtE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAUvE;AAED,sGAAsG;AACtG,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,+DAA+D;IAC/D,QAAQ,IAAI,MAAM,CAAC;IACnB,8FAA8F;IAC9F,SAAS,IAAI,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAQD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
|
|
@@ -23,6 +23,34 @@ export function findJsonBlock(text) {
|
|
|
23
23
|
}
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse a turn's final assistant text as JSON: direct parse first (a natively-constrained
|
|
28
|
+
* final message is pure JSON), then a balanced-block extraction when the turn also emitted
|
|
29
|
+
* leading prose. Returns undefined when no parseable JSON is present (the caller's ladder
|
|
30
|
+
* re-prompts). Shared by the Codex backend and every custom registry backend — the "read the
|
|
31
|
+
* structured result off the final message" convention is the generic ACP dialect.
|
|
32
|
+
*/
|
|
33
|
+
export function parseFinalJson(text) {
|
|
34
|
+
const trimmed = text.trim();
|
|
35
|
+
if (!trimmed)
|
|
36
|
+
return undefined;
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(trimmed);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// fall through to block extraction
|
|
42
|
+
}
|
|
43
|
+
const block = findJsonBlock(trimmed);
|
|
44
|
+
if (block !== undefined) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(block);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// give up; the runner's ladder will re-prompt / extract.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
26
54
|
/** Coerce an already-parsed value toward the schema and accept it only if it then validates. */
|
|
27
55
|
export function validateValue(value, schema) {
|
|
28
56
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automatalabs/acp-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@agentclientprotocol/claude-agent-acp": "0.53.0",
|
|
29
29
|
"@automatalabs/codex-acp": "1.2.0",
|
|
30
30
|
"typebox": "1.3.2",
|
|
31
|
-
"@automatalabs/shared-types": "0.
|
|
31
|
+
"@automatalabs/shared-types": "0.4.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsc -b",
|