@automatalabs/acp-agents 0.4.1 → 0.5.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/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 +19 -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 +18 -0
- package/dist/backends/custom.d.ts.map +1 -0
- package/dist/backends/custom.js +47 -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 +88 -18
- 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[];
|
|
@@ -22,11 +26,22 @@ export interface SessionMetaInputs {
|
|
|
22
26
|
}
|
|
23
27
|
export interface Backend {
|
|
24
28
|
readonly id: BackendId;
|
|
29
|
+
/** When true, the runner EMBEDS the JSON Schema in the prompt text on schema runs. For the
|
|
30
|
+
* built-ins the native constraint channel is authoritative and this stays unset; a CUSTOM
|
|
31
|
+
* backend sets it because its agent may ignore the `_meta.outputSchema` forward entirely —
|
|
32
|
+
* without the schema in the prompt, such an agent returns well-formed JSON with the WRONG
|
|
33
|
+
* KEYS and the repair ladder can never converge (it can fix prose, not unseen contracts). */
|
|
34
|
+
readonly embedSchemaInPrompt?: boolean;
|
|
25
35
|
/** How to launch this backend's ACP server over stdio. */
|
|
26
36
|
spawnConfig(): SpawnConfig;
|
|
27
|
-
/** `_meta` for session/new (
|
|
28
|
-
*
|
|
29
|
-
*
|
|
37
|
+
/** OPTIONAL backend-level `_meta` DEFAULTS for session/new (e.g. a custom registry entry's
|
|
38
|
+
* static `sessionMeta`). Lowest precedence: per-call RunOptions.meta merges OVER these,
|
|
39
|
+
* and sessionMeta()'s protocol-critical keys merge over both. */
|
|
40
|
+
sessionMetaDefaults?(): Record<string, unknown> | undefined;
|
|
41
|
+
/** PROTOCOL-CRITICAL `_meta` for session/new (undefined when this backend carries nothing
|
|
42
|
+
* there) — e.g. the Claude schema channel. Highest precedence below the runId stamp: these
|
|
43
|
+
* keys win over the generic user passthrough. `inputs` carries optional per-session extras
|
|
44
|
+
* (e.g. Codex base/developer instructions); a backend that has no use for them ignores it. */
|
|
30
45
|
sessionMeta(schema: TSchema | undefined, inputs?: SessionMetaInputs): Record<string, unknown> | undefined;
|
|
31
46
|
/** `_meta` for session/prompt (undefined when this backend carries the schema at session/new). */
|
|
32
47
|
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;;;;kGAI8F;IAC9F,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,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,18 @@
|
|
|
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
|
+
/** The agent may ignore the `_meta.outputSchema` forward, so the runner must also state the
|
|
8
|
+
* schema in the prompt — otherwise the model returns JSON with keys it invented and the
|
|
9
|
+
* repair ladder can never converge on a contract the model was never shown. */
|
|
10
|
+
readonly embedSchemaInPrompt = true;
|
|
11
|
+
constructor(config: RegisteredBackend);
|
|
12
|
+
spawnConfig(): SpawnConfig;
|
|
13
|
+
sessionMetaDefaults(): Record<string, unknown> | undefined;
|
|
14
|
+
sessionMeta(_schema: TSchema | undefined): Record<string, unknown> | undefined;
|
|
15
|
+
promptMeta(schema: TSchema | undefined): Record<string, unknown> | undefined;
|
|
16
|
+
nativeStructured(source: StructuredSource): unknown;
|
|
17
|
+
}
|
|
18
|
+
//# 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;IAOlC,OAAO,CAAC,QAAQ,CAAC,MAAM;IANnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;oFAEgF;IAChF,QAAQ,CAAC,mBAAmB,QAAQ;gBAEP,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,47 @@
|
|
|
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
|
+
/** The agent may ignore the `_meta.outputSchema` forward, so the runner must also state the
|
|
8
|
+
* schema in the prompt — otherwise the model returns JSON with keys it invented and the
|
|
9
|
+
* repair ladder can never converge on a contract the model was never shown. */
|
|
10
|
+
embedSchemaInPrompt = true;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.id = config.name;
|
|
14
|
+
}
|
|
15
|
+
spawnConfig() {
|
|
16
|
+
return {
|
|
17
|
+
command: this.config.command,
|
|
18
|
+
args: [...(this.config.args ?? [])],
|
|
19
|
+
// Registry-declared env merges OVER the inherited environment.
|
|
20
|
+
env: { ...process.env, ...(this.config.env ?? {}) },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
sessionMetaDefaults() {
|
|
24
|
+
// The registry entry's static `_meta` — DEFAULTS, so per-call RunOptions.meta overrides
|
|
25
|
+
// them. Return a copy so callers can merge without mutating config.
|
|
26
|
+
const staticMeta = this.config.sessionMeta;
|
|
27
|
+
if (!staticMeta || Object.keys(staticMeta).length === 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
return { ...staticMeta };
|
|
30
|
+
}
|
|
31
|
+
sessionMeta(_schema) {
|
|
32
|
+
// The schema rides the turn (see promptMeta); a custom backend has no protocol-critical
|
|
33
|
+
// session/new `_meta` of its own.
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
promptMeta(schema) {
|
|
37
|
+
if (!schema)
|
|
38
|
+
return undefined;
|
|
39
|
+
// Plain JSON Schema (NOT OpenAI-strict-normalized — strictness is a Codex/Responses-API
|
|
40
|
+
// constraint, not part of the generic dialect). Agents that ignore it are repaired by the
|
|
41
|
+
// runner's ladder.
|
|
42
|
+
return { [META_KEYS.outputSchema]: toJsonSchema(schema) };
|
|
43
|
+
}
|
|
44
|
+
nativeStructured(source) {
|
|
45
|
+
return parseFinalJson(source.currentTurnText());
|
|
46
|
+
}
|
|
47
|
+
}
|
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;AAQvG;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;AAmFD;;;;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,22 @@ 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";
|
|
27
|
+
import { toJsonSchema } from "./schema-strict.js";
|
|
25
28
|
import { resolveStructuredOutput } from "./structured-output.js";
|
|
26
29
|
export class AcpAgentRunner {
|
|
27
30
|
pool;
|
|
31
|
+
/** The resolved custom-backend registry (env + option, validated at construction). */
|
|
32
|
+
backends;
|
|
28
33
|
/** Typed bus carrying every ACP event from every pooled session. Beyond the AgentRunner seam
|
|
29
34
|
* (additive observability) — subscribing never affects a run and never enters the resume hash. */
|
|
30
35
|
events = new TypedEventEmitter();
|
|
31
36
|
emitEvent = (name, event) => this.events.emit(name, event);
|
|
32
37
|
constructor(options = {}) {
|
|
33
38
|
this.pool = new AcpAgentPool(options, { onEvent: this.emitEvent });
|
|
39
|
+
this.backends = resolveBackendRegistry(options.backends);
|
|
34
40
|
}
|
|
35
41
|
/**
|
|
36
42
|
* Listen in on the live ACP stream. `name` is an ACP `sessionUpdate` discriminant
|
|
@@ -60,7 +66,7 @@ export class AcpAgentRunner {
|
|
|
60
66
|
async run(prompt, options = {}) {
|
|
61
67
|
const opts = options;
|
|
62
68
|
const schema = opts.schema;
|
|
63
|
-
const backend = selectBackend(opts);
|
|
69
|
+
const backend = selectBackend(opts, this.backends);
|
|
64
70
|
const policy = { allow: opts.toolNames, deny: opts.disallowedToolNames };
|
|
65
71
|
const cwd = opts.cwd ?? process.cwd();
|
|
66
72
|
const session = await this.pool.acquire(backend, {
|
|
@@ -69,6 +75,9 @@ export class AcpAgentRunner {
|
|
|
69
75
|
policy,
|
|
70
76
|
signal: opts.signal,
|
|
71
77
|
mcpServers: opts.mcpServers,
|
|
78
|
+
// Generic session-scoped _meta passthrough (RunOptions.meta) — merged UNDER the
|
|
79
|
+
// backend-computed keys and the runId stamp in openSession. Additive; never hashed.
|
|
80
|
+
meta: opts.meta,
|
|
72
81
|
// Engine correlation id -> session/new _meta (META_KEYS.runId). Additive; never hashed.
|
|
73
82
|
runId: opts.runId,
|
|
74
83
|
// Stamped onto emitted ACP events as context (never sent on the wire).
|
|
@@ -80,9 +89,14 @@ export class AcpAgentRunner {
|
|
|
80
89
|
});
|
|
81
90
|
try {
|
|
82
91
|
opts.signal?.throwIfAborted();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
// For a CUSTOM backend chosen by its registered name, the name itself is routing, not a
|
|
93
|
+
// model id: "browser" selects nothing; "browser/foo" selects "foo". Built-ins get the
|
|
94
|
+
// full spec unchanged (their catalogs match provider-prefixed and bare ids).
|
|
95
|
+
await applyModelSelection(session, innerModelSpec(opts.model ?? opts.tier, backend), opts);
|
|
96
|
+
const text = buildPrompt(prompt, opts, schema, backend);
|
|
97
|
+
// Generic turn-scoped _meta passthrough merged UNDER the backend-computed keys (e.g. the
|
|
98
|
+
// outputSchema forward when a schema is set) — user meta never clobbers the schema channel.
|
|
99
|
+
const promptMeta = mergeTurnMeta(opts.promptMeta, backend.promptMeta(schema));
|
|
86
100
|
const response = await session.prompt(text, promptMeta);
|
|
87
101
|
opts.signal?.throwIfAborted();
|
|
88
102
|
// Inspect the turn's stop reason BEFORE the text/schema path: a refusal or truncation
|
|
@@ -153,8 +167,9 @@ export class AcpAgentRunner {
|
|
|
153
167
|
}
|
|
154
168
|
}
|
|
155
169
|
/** Factory the mcp-server composition root calls to inject the runner into the engine. The pool
|
|
156
|
-
* size
|
|
157
|
-
*
|
|
170
|
+
* size and the custom-backend registry are runner-level options — NOT RunOptions fields, so they
|
|
171
|
+
* never enter hashAgentCall / the resume identity. Env fallbacks: AGENTPRISM_ACP_POOL_SIZE for
|
|
172
|
+
* size, AGENTPRISM_BACKENDS (JSON) for backends. */
|
|
158
173
|
export function createAcpRunner(options) {
|
|
159
174
|
return new AcpAgentRunner(options);
|
|
160
175
|
}
|
|
@@ -188,9 +203,9 @@ function assertNormalStopReason(stopReason, label) {
|
|
|
188
203
|
return;
|
|
189
204
|
}
|
|
190
205
|
}
|
|
191
|
-
async function applyModelSelection(session, opts) {
|
|
192
|
-
// `
|
|
193
|
-
|
|
206
|
+
async function applyModelSelection(session, spec, opts) {
|
|
207
|
+
// `spec` is opts.model ?? opts.tier (`model` wins — frozen contract), with a custom
|
|
208
|
+
// backend's routing name already stripped by innerModelSpec.
|
|
194
209
|
if (!spec)
|
|
195
210
|
return;
|
|
196
211
|
const { matched, resolved, modifierFallbacks } = await session.selectModel(spec);
|
|
@@ -204,28 +219,75 @@ async function applyModelSelection(session, opts) {
|
|
|
204
219
|
for (const fallback of modifierFallbacks ?? [])
|
|
205
220
|
opts.onModelFallback?.(fallback);
|
|
206
221
|
}
|
|
207
|
-
function buildPrompt(prompt, opts,
|
|
222
|
+
function buildPrompt(prompt, opts, schema, backend) {
|
|
208
223
|
const parts = [];
|
|
209
224
|
if (opts.instructions)
|
|
210
225
|
parts.push(opts.instructions);
|
|
211
226
|
if (opts.label)
|
|
212
227
|
parts.push(`Task label: ${opts.label}`);
|
|
213
228
|
parts.push(prompt);
|
|
214
|
-
if (
|
|
215
|
-
|
|
229
|
+
if (schema) {
|
|
230
|
+
const contract = [
|
|
216
231
|
"Final output contract:",
|
|
217
232
|
"- Your FINAL message MUST be a single JSON object that conforms to the required output schema.",
|
|
218
233
|
"- Output ONLY that JSON object — no prose, no explanation, and no markdown code fences.",
|
|
219
234
|
"- If you need to inspect files or run commands first, do so, then emit the JSON object as your final message.",
|
|
220
|
-
]
|
|
235
|
+
];
|
|
236
|
+
if (backend.embedSchemaInPrompt) {
|
|
237
|
+
// The agent behind a custom backend may ignore the `_meta.outputSchema` forward, so the
|
|
238
|
+
// schema must be STATED, not just wired — otherwise the model invents its own keys and
|
|
239
|
+
// the repair ladder can never converge. Built-ins skip this: their native constraint
|
|
240
|
+
// channel is authoritative.
|
|
241
|
+
contract.push(`- The required output schema (JSON Schema):\n${JSON.stringify(toJsonSchema(schema))}`);
|
|
242
|
+
}
|
|
243
|
+
parts.push(contract.join("\n"));
|
|
221
244
|
}
|
|
222
245
|
return parts.join("\n\n");
|
|
223
246
|
}
|
|
224
|
-
/** Pick the backend by model/tier. Cross-provider routing = which ACP server to spawn.
|
|
225
|
-
|
|
226
|
-
|
|
247
|
+
/** Pick the backend by model/tier. Cross-provider routing = which ACP server to spawn.
|
|
248
|
+
* Registered CUSTOM names resolve FIRST (exact name, or `name/<inner-model>` prefix) so a
|
|
249
|
+
* registry entry is never shadowed by the built-in heuristics; then the claude/codex
|
|
250
|
+
* heuristics; then the default backend (AGENTPRISM_DEFAULT_BACKEND — which may itself name
|
|
251
|
+
* a registered custom backend). */
|
|
252
|
+
export function selectBackend(opts, registry) {
|
|
253
|
+
const custom = customBackendForSpec(opts.model, registry) ?? customBackendForSpec(opts.tier, registry);
|
|
254
|
+
if (custom)
|
|
255
|
+
return custom;
|
|
256
|
+
const id = backendIdForSpec(opts.model) ?? backendIdForSpec(opts.tier) ?? defaultBackendId(registry);
|
|
257
|
+
if (typeof id !== "string")
|
|
258
|
+
return id; // the default resolved to a registered custom backend
|
|
227
259
|
return id === "codex" ? new CodexBackend() : new ClaudeBackend();
|
|
228
260
|
}
|
|
261
|
+
/** Match a model/tier spec against the registry: the whole spec, or its `<name>/` prefix. */
|
|
262
|
+
function customBackendForSpec(spec, registry) {
|
|
263
|
+
if (!spec || !registry || registry.size === 0)
|
|
264
|
+
return undefined;
|
|
265
|
+
const lower = spec.toLowerCase();
|
|
266
|
+
const slash = lower.indexOf("/");
|
|
267
|
+
const name = slash > 0 ? lower.slice(0, slash) : lower;
|
|
268
|
+
const config = registry.get(name);
|
|
269
|
+
return config ? new CustomAcpBackend(config) : undefined;
|
|
270
|
+
}
|
|
271
|
+
/** Strip a custom backend's routing name off the model/tier spec: the spec `"name"` selects no
|
|
272
|
+
* inner model; `"name/foo"` selects `"foo"`. Built-in backends receive the spec unchanged, and
|
|
273
|
+
* a spec that reached a custom DEFAULT backend without naming it also passes through (the
|
|
274
|
+
* agent's own catalog may know it). */
|
|
275
|
+
function innerModelSpec(spec, backend) {
|
|
276
|
+
if (!spec || !(backend instanceof CustomAcpBackend))
|
|
277
|
+
return spec;
|
|
278
|
+
const lower = spec.toLowerCase();
|
|
279
|
+
if (lower === backend.id)
|
|
280
|
+
return undefined;
|
|
281
|
+
if (lower.startsWith(`${backend.id}/`))
|
|
282
|
+
return spec.slice(backend.id.length + 1) || undefined;
|
|
283
|
+
return spec;
|
|
284
|
+
}
|
|
285
|
+
/** Merge the generic turn-scoped meta passthrough UNDER the backend-computed turn meta. */
|
|
286
|
+
function mergeTurnMeta(user, backend) {
|
|
287
|
+
if (!user)
|
|
288
|
+
return backend;
|
|
289
|
+
return { ...user, ...(backend ?? {}) };
|
|
290
|
+
}
|
|
229
291
|
function backendIdForSpec(spec) {
|
|
230
292
|
if (!spec)
|
|
231
293
|
return undefined;
|
|
@@ -243,6 +305,14 @@ function backendIdForSpec(spec) {
|
|
|
243
305
|
return "claude";
|
|
244
306
|
return undefined;
|
|
245
307
|
}
|
|
246
|
-
|
|
247
|
-
|
|
308
|
+
/** Resolve the default backend: a registered custom name wins (returned as a Backend), else
|
|
309
|
+
* the built-in id. An unknown/unset value falls back to "claude" (the historical default). */
|
|
310
|
+
function defaultBackendId(registry) {
|
|
311
|
+
const name = process.env.AGENTPRISM_DEFAULT_BACKEND?.toLowerCase();
|
|
312
|
+
if (name && registry) {
|
|
313
|
+
const config = registry.get(name);
|
|
314
|
+
if (config)
|
|
315
|
+
return new CustomAcpBackend(config);
|
|
316
|
+
}
|
|
317
|
+
return name === "codex" ? "codex" : "claude";
|
|
248
318
|
}
|
|
@@ -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.1",
|
|
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",
|