@automatalabs/acp-agents 2.0.0 → 3.0.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 +53 -17
- package/dist/acp-client.d.ts +10 -4
- package/dist/acp-client.d.ts.map +1 -1
- package/dist/acp-client.js +11 -8
- package/dist/agent/acp-agent.d.ts +84 -26
- package/dist/agent/acp-agent.d.ts.map +1 -1
- package/dist/agent/acp-agent.js +458 -170
- package/dist/agent/errors.d.ts +12 -2
- package/dist/agent/errors.d.ts.map +1 -1
- package/dist/agent/errors.js +38 -9
- package/dist/agent/fork.d.ts +7 -5
- package/dist/agent/fork.d.ts.map +1 -1
- package/dist/agent/fork.js +7 -5
- package/dist/agent/messages.d.ts +42 -0
- package/dist/agent/messages.d.ts.map +1 -0
- package/dist/agent/messages.js +225 -0
- package/dist/agent/probe.d.ts +1 -1
- package/dist/agent/probe.d.ts.map +1 -1
- package/dist/agent/probe.js +5 -1
- package/dist/agent/queue.js +2 -2
- package/dist/agent/routing.d.ts +19 -1
- package/dist/agent/routing.d.ts.map +1 -1
- package/dist/agent/routing.js +41 -4
- package/dist/agent/stream.d.ts +21 -0
- package/dist/agent/stream.d.ts.map +1 -0
- package/dist/agent/stream.js +93 -0
- package/dist/agent/structured.d.ts +7 -3
- package/dist/agent/structured.d.ts.map +1 -1
- package/dist/agent/structured.js +24 -14
- package/dist/agent/tool-host.d.ts +34 -0
- package/dist/agent/tool-host.d.ts.map +1 -0
- package/dist/agent/tool-host.js +138 -0
- package/dist/agent/tools.d.ts +26 -0
- package/dist/agent/tools.d.ts.map +1 -0
- package/dist/agent/tools.js +62 -0
- package/dist/agent/turn.d.ts +6 -3
- package/dist/agent/turn.d.ts.map +1 -1
- package/dist/agent/turn.js +15 -67
- package/dist/agent/types.d.ts +174 -25
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/backend.d.ts +4 -3
- package/dist/backend.d.ts.map +1 -1
- package/dist/backends/codex.d.ts +2 -1
- package/dist/backends/codex.d.ts.map +1 -1
- package/dist/backends/codex.js +3 -2
- package/dist/config-catalog.d.ts +4 -0
- package/dist/config-catalog.d.ts.map +1 -1
- package/dist/config-catalog.js +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/local-mcp-host.d.ts +30 -0
- package/dist/local-mcp-host.d.ts.map +1 -0
- package/dist/local-mcp-host.js +151 -0
- package/dist/protocol-coverage.d.ts +8 -6
- package/dist/protocol-coverage.d.ts.map +1 -1
- package/dist/protocol-coverage.js +3 -2
- package/dist/registry.d.ts +3 -3
- package/dist/registry.d.ts.map +1 -1
- package/dist/runner.d.ts +5 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +7 -4
- package/dist/structured-output.d.ts +18 -0
- package/dist/structured-output.d.ts.map +1 -1
- package/dist/structured-output.js +19 -1
- package/dist/structured-tool.d.ts +7 -9
- package/dist/structured-tool.d.ts.map +1 -1
- package/dist/structured-tool.js +14 -102
- package/dist/traits.d.ts +51 -0
- package/dist/traits.d.ts.map +1 -0
- package/dist/traits.js +88 -0
- package/package.json +4 -4
package/dist/agent/errors.d.ts
CHANGED
|
@@ -2,12 +2,22 @@ import { WorkflowError } from "@automatalabs/shared-types";
|
|
|
2
2
|
import { type ErrorMapContext } from "../errors-map.js";
|
|
3
3
|
import type { TypedSessionFailure } from "../typed-failures.js";
|
|
4
4
|
import type { AcpAgentTurn, AcpAgentTurnError } from "./types.js";
|
|
5
|
-
/** A deterministic caller error (bad option, misuse):
|
|
5
|
+
/** A deterministic caller error (bad option, misuse): INVALID_ARGUMENT, non-recoverable. */
|
|
6
6
|
export declare function agentValidationError(message: string, label?: string): WorkflowError;
|
|
7
7
|
/** Every method except `close()`/`cancel()`/getters after the agent closed, aborted, or its process died. */
|
|
8
8
|
export declare function agentClosedError(label: string | undefined, backendId: string, detail?: string): WorkflowError;
|
|
9
|
+
/** Re-code a shared validator's SCRIPT_VALIDATION_ERROR as the SDK's INVALID_ARGUMENT, keeping the
|
|
10
|
+
* message, `recoverable=false`, `agentLabel`, and `details`. Anything else is returned untouched
|
|
11
|
+
* — the runner and `InteractiveSession` keep SCRIPT_VALIDATION_ERROR; only the AcpAgent boundary
|
|
12
|
+
* speaks INVALID_ARGUMENT. */
|
|
13
|
+
export declare function asAgentArgumentError(error: unknown): unknown;
|
|
14
|
+
/** Run a synchronous validation block; a shared validator's SCRIPT_VALIDATION_ERROR surfaces as
|
|
15
|
+
* INVALID_ARGUMENT, everything else propagates as thrown. */
|
|
16
|
+
export declare function validateArguments<T>(validate: () => T): T;
|
|
9
17
|
/** Map a thrown wire/lifecycle error onto the seam contract — unless `signal` aborted, in which
|
|
10
|
-
* case the error is returned untouched so the caller can rethrow `signal.reason` itself.
|
|
18
|
+
* case the error is returned untouched so the caller can rethrow `signal.reason` itself. A
|
|
19
|
+
* validation error the client raised on the agent's behalf (mode selection, a lifecycle
|
|
20
|
+
* capability the agent does not advertise) is re-coded as INVALID_ARGUMENT. */
|
|
11
21
|
export declare function mapAgentError(error: unknown, ctx: ErrorMapContext, signal?: AbortSignal): unknown;
|
|
12
22
|
/** The `prompt()` rejection for a walled turn: `mapTypedSessionFailure` (codes/recoverable/details
|
|
13
23
|
* unchanged) with the complete `AcpAgentTurn` attached as `error.turn`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/agent/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/agent/errors.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAsC,MAAM,4BAA4B,CAAC;AAC/F,OAAO,EAA0C,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAElE,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,aAAa,CAKnF;AAED,6GAA6G;AAC7G,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAM7G;AAED;;;+BAG+B;AAC/B,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO5D;AAED;8DAC8D;AAC9D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAMzD;AAED;;;gFAGgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAGjG;AAED;4EAC4E;AAC5E,wBAAgB,cAAc,CAC5B,OAAO,EAAE,mBAAmB,EAC5B,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,eAAe,GACnB,iBAAiB,CAInB;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAM9E"}
|
package/dist/agent/errors.js
CHANGED
|
@@ -1,26 +1,55 @@
|
|
|
1
|
-
// AcpAgent error helpers: SDK misuse is uniformly a WorkflowError(
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// AcpAgent error helpers: SDK misuse is uniformly a WorkflowError(INVALID_ARGUMENT) — the SDK's
|
|
2
|
+
// own guards raise it directly, and a shared validator's SCRIPT_VALIDATION_ERROR (cwd, the reserved
|
|
3
|
+
// "model" config id, the registry, client handlers, systemPrompt support, prompt images, the
|
|
4
|
+
// client's mode-selection and lifecycle-capability gates) is re-coded at the agent boundary with
|
|
5
|
+
// its message, `recoverable=false`, `agentLabel`, and `details` preserved; wire failures go through
|
|
6
|
+
// the same `mapThrownError` ladder the runner uses; an abort is never mapped (the affected promise
|
|
7
|
+
// rejects with `signal.reason` untouched); a typed session failure rejects with the runner's
|
|
8
|
+
// mapped error carrying the complete turn.
|
|
5
9
|
import { WorkflowError, WorkflowErrorCode, isWorkflowError } from "@automatalabs/shared-types";
|
|
6
10
|
import { mapThrownError, mapTypedSessionFailure } from "../errors-map.js";
|
|
7
|
-
/** A deterministic caller error (bad option, misuse):
|
|
11
|
+
/** A deterministic caller error (bad option, misuse): INVALID_ARGUMENT, non-recoverable. */
|
|
8
12
|
export function agentValidationError(message, label) {
|
|
9
|
-
return new WorkflowError(message, WorkflowErrorCode.
|
|
13
|
+
return new WorkflowError(message, WorkflowErrorCode.INVALID_ARGUMENT, {
|
|
10
14
|
recoverable: false,
|
|
11
15
|
agentLabel: label,
|
|
12
16
|
});
|
|
13
17
|
}
|
|
14
18
|
/** Every method except `close()`/`cancel()`/getters after the agent closed, aborted, or its process died. */
|
|
15
19
|
export function agentClosedError(label, backendId, detail) {
|
|
16
|
-
return new WorkflowError(`AcpAgent (${backendId}) is closed${detail ? `: ${detail}` : ""}`, WorkflowErrorCode.
|
|
20
|
+
return new WorkflowError(`AcpAgent (${backendId}) is closed${detail ? `: ${detail}` : ""}`, WorkflowErrorCode.INVALID_ARGUMENT, { recoverable: false, agentLabel: label });
|
|
21
|
+
}
|
|
22
|
+
/** Re-code a shared validator's SCRIPT_VALIDATION_ERROR as the SDK's INVALID_ARGUMENT, keeping the
|
|
23
|
+
* message, `recoverable=false`, `agentLabel`, and `details`. Anything else is returned untouched
|
|
24
|
+
* — the runner and `InteractiveSession` keep SCRIPT_VALIDATION_ERROR; only the AcpAgent boundary
|
|
25
|
+
* speaks INVALID_ARGUMENT. */
|
|
26
|
+
export function asAgentArgumentError(error) {
|
|
27
|
+
if (!isWorkflowError(error) || error.code !== WorkflowErrorCode.SCRIPT_VALIDATION_ERROR)
|
|
28
|
+
return error;
|
|
29
|
+
return new WorkflowError(error.message, WorkflowErrorCode.INVALID_ARGUMENT, {
|
|
30
|
+
recoverable: false,
|
|
31
|
+
agentLabel: error.agentLabel,
|
|
32
|
+
details: error.details,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Run a synchronous validation block; a shared validator's SCRIPT_VALIDATION_ERROR surfaces as
|
|
36
|
+
* INVALID_ARGUMENT, everything else propagates as thrown. */
|
|
37
|
+
export function validateArguments(validate) {
|
|
38
|
+
try {
|
|
39
|
+
return validate();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw asAgentArgumentError(error);
|
|
43
|
+
}
|
|
17
44
|
}
|
|
18
45
|
/** Map a thrown wire/lifecycle error onto the seam contract — unless `signal` aborted, in which
|
|
19
|
-
* case the error is returned untouched so the caller can rethrow `signal.reason` itself.
|
|
46
|
+
* case the error is returned untouched so the caller can rethrow `signal.reason` itself. A
|
|
47
|
+
* validation error the client raised on the agent's behalf (mode selection, a lifecycle
|
|
48
|
+
* capability the agent does not advertise) is re-coded as INVALID_ARGUMENT. */
|
|
20
49
|
export function mapAgentError(error, ctx, signal) {
|
|
21
50
|
if (signal?.aborted)
|
|
22
51
|
return error;
|
|
23
|
-
return mapThrownError(error, ctx);
|
|
52
|
+
return asAgentArgumentError(mapThrownError(error, ctx));
|
|
24
53
|
}
|
|
25
54
|
/** The `prompt()` rejection for a walled turn: `mapTypedSessionFailure` (codes/recoverable/details
|
|
26
55
|
* unchanged) with the complete `AcpAgentTurn` attached as `error.turn`. */
|
package/dist/agent/fork.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AcpSessionOptions, PooledConnection, SessionHandle } from "../acp-client.js";
|
|
1
|
+
import type { AcpSessionOptions, PooledConnection, ReattachPreference, SessionHandle } from "../acp-client.js";
|
|
2
2
|
import type { Backend } from "../backend.js";
|
|
3
3
|
import { type ForkSessionTraitRow } from "../protocol-coverage.js";
|
|
4
4
|
import type { BackendRegistry } from "../registry.js";
|
|
@@ -18,10 +18,12 @@ export interface AcquiredFork {
|
|
|
18
18
|
* - `live`: the fork response IS the session.
|
|
19
19
|
* - `id-only`: the response names a persisted copy that is not live; release the fork handle
|
|
20
20
|
* with `keepOpen` (unregister only — no `session/close`, so the reattach's `register()` never
|
|
21
|
-
* replaces a live state and the slot count stays at one), then reattach it
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* replaces a live state and the slot count stays at one), then reattach it by `reattach` —
|
|
22
|
+
* `resume` preferred with `load` as the fallback (the live `agent.fork()`, whose child is
|
|
23
|
+
* seeded from the parent's snapshot), or `load` preferred with `resume` as the fallback (the
|
|
24
|
+
* cold `AcpAgent.fork(ref)`, which has no parent to seed from and wants the replay). The
|
|
25
|
+
* catalog/modes come from the reattach response; the bare fork response is never read for them.
|
|
24
26
|
* `prepare` is re-evaluated for the reattach so it sees the same options the fork carried.
|
|
25
27
|
*/
|
|
26
|
-
export declare function acquireForkedSession(connection: PooledConnection, sourceSessionId: string, opts: AcpSessionOptions, trait: ForkSessionTraitRow): Promise<AcquiredFork>;
|
|
28
|
+
export declare function acquireForkedSession(connection: PooledConnection, sourceSessionId: string, opts: AcpSessionOptions, trait: ForkSessionTraitRow, reattach?: ReattachPreference): Promise<AcquiredFork>;
|
|
27
29
|
//# sourceMappingURL=fork.d.ts.map
|
package/dist/agent/fork.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fork.d.ts","sourceRoot":"","sources":["../../src/agent/fork.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"fork.d.ts","sourceRoot":"","sources":["../../src/agent/fork.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC/G,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD;;0FAE0F;AAC1F,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,GAAG,mBAAmB,CAG7F;AAED,MAAM,WAAW,YAAY;IAC3B,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC7C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,gBAAgB,EAC5B,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,mBAAmB,EAC1B,QAAQ,GAAE,kBAA6B,GACtC,OAAO,CAAC,YAAY,CAAC,CAOvB"}
|
package/dist/agent/fork.js
CHANGED
|
@@ -12,17 +12,19 @@ export function forkTraitFor(backend, registry) {
|
|
|
12
12
|
* - `live`: the fork response IS the session.
|
|
13
13
|
* - `id-only`: the response names a persisted copy that is not live; release the fork handle
|
|
14
14
|
* with `keepOpen` (unregister only — no `session/close`, so the reattach's `register()` never
|
|
15
|
-
* replaces a live state and the slot count stays at one), then reattach it
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* replaces a live state and the slot count stays at one), then reattach it by `reattach` —
|
|
16
|
+
* `resume` preferred with `load` as the fallback (the live `agent.fork()`, whose child is
|
|
17
|
+
* seeded from the parent's snapshot), or `load` preferred with `resume` as the fallback (the
|
|
18
|
+
* cold `AcpAgent.fork(ref)`, which has no parent to seed from and wants the replay). The
|
|
19
|
+
* catalog/modes come from the reattach response; the bare fork response is never read for them.
|
|
18
20
|
* `prepare` is re-evaluated for the reattach so it sees the same options the fork carried.
|
|
19
21
|
*/
|
|
20
|
-
export async function acquireForkedSession(connection, sourceSessionId, opts, trait) {
|
|
22
|
+
export async function acquireForkedSession(connection, sourceSessionId, opts, trait, reattach = "resume") {
|
|
21
23
|
const forkHandle = await connection.forkSession(sourceSessionId, opts);
|
|
22
24
|
if (trait.disposition !== "id-only")
|
|
23
25
|
return { handle: forkHandle, method: "fork" };
|
|
24
26
|
const forkedId = forkHandle.sessionId;
|
|
25
27
|
await forkHandle.release({ keepOpen: true });
|
|
26
|
-
const { handle, method } = await connection.openPreparedReattachedSession(forkedId, () => opts);
|
|
28
|
+
const { handle, method } = await connection.openPreparedReattachedSession(forkedId, () => opts, undefined, reattach);
|
|
27
29
|
return { handle, method };
|
|
28
30
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind } from "@agentclientprotocol/sdk";
|
|
2
|
+
import type { AcpSessionUpdate } from "../events.js";
|
|
3
|
+
import type { AcpAgentMessage, AcpAgentToolCall, AcpAgentUpdateRecord } from "./types.js";
|
|
4
|
+
export interface MutableToolCall {
|
|
5
|
+
toolCallId: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
title: string;
|
|
8
|
+
kind?: ToolKind;
|
|
9
|
+
status: ToolCallStatus;
|
|
10
|
+
rawInput?: unknown;
|
|
11
|
+
rawOutput?: unknown;
|
|
12
|
+
content?: ToolCallContent[];
|
|
13
|
+
locations?: ToolCallLocation[];
|
|
14
|
+
meta?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
type ToolCallUpdate = Extract<AcpSessionUpdate, {
|
|
17
|
+
sessionUpdate: "tool_call" | "tool_call_update";
|
|
18
|
+
}>;
|
|
19
|
+
/** Fold one `tool_call` / `tool_call_update` into `existing` (a fresh entry when undefined):
|
|
20
|
+
* every field the update carries replaces the last seen one, `_meta` merges shallowly, and
|
|
21
|
+
* `status` stays `pending` until the agent sends one. */
|
|
22
|
+
export declare function foldToolCall(existing: MutableToolCall | undefined, update: ToolCallUpdate): MutableToolCall;
|
|
23
|
+
/** A fresh, shallow copy of a message (blocks and tool calls copied one level deep). */
|
|
24
|
+
export declare function copyMessage(message: AcpAgentMessage): AcpAgentMessage;
|
|
25
|
+
export declare class MessageFolder {
|
|
26
|
+
#private;
|
|
27
|
+
/** Mirror `SessionState.beginTurn()`: a boundary, no message in progress, and — like the
|
|
28
|
+
* accumulator under `retainSessionLog: false` — a cleared transcript when `retain` is false.
|
|
29
|
+
* A thought left over from the previous turn becomes its own assistant message first. */
|
|
30
|
+
beginTurn(retain: boolean): void;
|
|
31
|
+
apply(update: AcpSessionUpdate, receivedAt: number): void;
|
|
32
|
+
/** Every folded tool call in first-seen order (copies) — the flattening of the messages'
|
|
33
|
+
* `toolCalls`, which is the same order because a call attaches to the message in progress. */
|
|
34
|
+
get toolCalls(): AcpAgentToolCall[];
|
|
35
|
+
/** The messages so far (copies). A thought still waiting for the content it precedes is
|
|
36
|
+
* reported as a trailing assistant message with no text. */
|
|
37
|
+
snapshot(): AcpAgentMessage[];
|
|
38
|
+
}
|
|
39
|
+
/** Fold verbatim update records (a turn's `updates`, a `replay`) into messages. */
|
|
40
|
+
export declare function foldMessages(records: readonly AcpAgentUpdateRecord[]): AcpAgentMessage[];
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/agent/messages.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAgB,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAC1H,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE1F,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,KAAK,cAAc,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,aAAa,EAAE,WAAW,GAAG,kBAAkB,CAAA;CAAE,CAAC,CAAC;AAgBrG;;0DAE0D;AAC1D,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,EAAE,MAAM,EAAE,cAAc,GAAG,eAAe,CAiB3G;AAYD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe,CAQrE;AAED,qBAAa,aAAa;;IAgBxB;;8FAE0F;IAC1F,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAchC,KAAK,CAAC,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA6DzD;mGAC+F;IAC/F,IAAI,SAAS,IAAI,gBAAgB,EAAE,CAElC;IAED;iEAC6D;IAC7D,QAAQ,IAAI,eAAe,EAAE;CAmE9B;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,oBAAoB,EAAE,GAAG,eAAe,EAAE,CAIxF"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
function record(value) {
|
|
2
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
3
|
+
? value
|
|
4
|
+
: undefined;
|
|
5
|
+
}
|
|
6
|
+
/** Fold one `tool_call` / `tool_call_update` into `existing` (a fresh entry when undefined):
|
|
7
|
+
* every field the update carries replaces the last seen one, `_meta` merges shallowly, and
|
|
8
|
+
* `status` stays `pending` until the agent sends one. */
|
|
9
|
+
export function foldToolCall(existing, update) {
|
|
10
|
+
const meta = record(update._meta);
|
|
11
|
+
const entry = existing ?? {
|
|
12
|
+
toolCallId: update.toolCallId,
|
|
13
|
+
title: typeof update.title === "string" ? update.title : "",
|
|
14
|
+
status: "pending",
|
|
15
|
+
};
|
|
16
|
+
if (typeof update.title === "string")
|
|
17
|
+
entry.title = update.title;
|
|
18
|
+
if (typeof update.name === "string")
|
|
19
|
+
entry.name = update.name;
|
|
20
|
+
if (update.kind !== undefined && update.kind !== null)
|
|
21
|
+
entry.kind = update.kind;
|
|
22
|
+
if (update.status !== undefined && update.status !== null)
|
|
23
|
+
entry.status = update.status;
|
|
24
|
+
if (update.rawInput !== undefined)
|
|
25
|
+
entry.rawInput = update.rawInput;
|
|
26
|
+
if (update.rawOutput !== undefined)
|
|
27
|
+
entry.rawOutput = update.rawOutput;
|
|
28
|
+
if (update.content !== undefined && update.content !== null)
|
|
29
|
+
entry.content = update.content;
|
|
30
|
+
if (update.locations !== undefined && update.locations !== null)
|
|
31
|
+
entry.locations = update.locations;
|
|
32
|
+
if (meta)
|
|
33
|
+
entry.meta = { ...(entry.meta ?? {}), ...meta };
|
|
34
|
+
return entry;
|
|
35
|
+
}
|
|
36
|
+
/** Append `block` to `blocks`, concatenating a text block onto a trailing text block. */
|
|
37
|
+
function appendBlock(blocks, block) {
|
|
38
|
+
const last = blocks[blocks.length - 1];
|
|
39
|
+
if (block.type === "text" && last?.type === "text") {
|
|
40
|
+
blocks[blocks.length - 1] = { ...last, text: last.text + block.text };
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
blocks.push({ ...block });
|
|
44
|
+
}
|
|
45
|
+
/** A fresh, shallow copy of a message (blocks and tool calls copied one level deep). */
|
|
46
|
+
export function copyMessage(message) {
|
|
47
|
+
return {
|
|
48
|
+
role: message.role,
|
|
49
|
+
content: message.content.map((block) => ({ ...block })),
|
|
50
|
+
toolCalls: message.toolCalls.map((call) => ({ ...call })),
|
|
51
|
+
thoughts: message.thoughts.map((block) => ({ ...block })),
|
|
52
|
+
receivedAt: message.receivedAt,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export class MessageFolder {
|
|
56
|
+
#messages = [];
|
|
57
|
+
/** The assistant message tool calls and non-text blocks attach to; unset after a user message or
|
|
58
|
+
* a turn boundary. */
|
|
59
|
+
#assistant;
|
|
60
|
+
/** The open run of `user_message_chunk`s. */
|
|
61
|
+
#user;
|
|
62
|
+
/** Thoughts waiting for the assistant content they precede. */
|
|
63
|
+
#pendingThoughts = [];
|
|
64
|
+
#pendingThoughtsAt;
|
|
65
|
+
/** `SessionState.assistantMessageBoundaryPending`: the next text chunk opens a new message. */
|
|
66
|
+
#boundaryPending = true;
|
|
67
|
+
/** `SessionState.activeAssistantMessageId`: a changed ACP `messageId` is a boundary. */
|
|
68
|
+
#activeMessageId;
|
|
69
|
+
#toolCallsById = new Map();
|
|
70
|
+
/** Mirror `SessionState.beginTurn()`: a boundary, no message in progress, and — like the
|
|
71
|
+
* accumulator under `retainSessionLog: false` — a cleared transcript when `retain` is false.
|
|
72
|
+
* A thought left over from the previous turn becomes its own assistant message first. */
|
|
73
|
+
beginTurn(retain) {
|
|
74
|
+
if (retain) {
|
|
75
|
+
this.#flushPendingThoughts();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.#messages.length = 0;
|
|
79
|
+
this.#toolCallsById.clear();
|
|
80
|
+
this.#pendingThoughts = [];
|
|
81
|
+
this.#pendingThoughtsAt = undefined;
|
|
82
|
+
}
|
|
83
|
+
this.#assistant = undefined;
|
|
84
|
+
this.#user = undefined;
|
|
85
|
+
this.#markBoundary();
|
|
86
|
+
}
|
|
87
|
+
apply(update, receivedAt) {
|
|
88
|
+
switch (update.sessionUpdate) {
|
|
89
|
+
case "agent_message_chunk": {
|
|
90
|
+
this.#user = undefined;
|
|
91
|
+
this.#applyMessageId(update.messageId);
|
|
92
|
+
// A pending boundary opens a new message for ANY block. The text fold consumes it only on
|
|
93
|
+
// a text chunk, but a non-text block consuming it here never moves a text chunk between
|
|
94
|
+
// messages: the message it opens holds no earlier text, so the next text chunk is its
|
|
95
|
+
// first either way.
|
|
96
|
+
const target = this.#boundaryPending || !this.#assistant ? this.#openAssistant(receivedAt) : this.#assistant;
|
|
97
|
+
this.#boundaryPending = false;
|
|
98
|
+
appendBlock(target.content, update.content);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
case "agent_thought_chunk": {
|
|
102
|
+
this.#user = undefined;
|
|
103
|
+
this.#markBoundary();
|
|
104
|
+
appendBlock(this.#pendingThoughts, update.content);
|
|
105
|
+
this.#pendingThoughtsAt ??= receivedAt;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
case "tool_call":
|
|
109
|
+
case "tool_call_update": {
|
|
110
|
+
this.#user = undefined;
|
|
111
|
+
this.#markBoundary();
|
|
112
|
+
const existing = this.#toolCallsById.get(update.toolCallId);
|
|
113
|
+
if (existing) {
|
|
114
|
+
foldToolCall(existing, update);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const target = this.#assistant ?? this.#openAssistant(receivedAt);
|
|
118
|
+
this.#adoptPendingThoughts(target);
|
|
119
|
+
const entry = foldToolCall(undefined, update);
|
|
120
|
+
target.toolCalls.push(entry);
|
|
121
|
+
this.#toolCallsById.set(entry.toolCallId, entry);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
case "user_message_chunk": {
|
|
125
|
+
this.#markBoundary();
|
|
126
|
+
this.#assistant = undefined;
|
|
127
|
+
this.#flushPendingThoughts();
|
|
128
|
+
if (!this.#user) {
|
|
129
|
+
this.#user = { role: "user", content: [], toolCalls: [], thoughts: [], receivedAt };
|
|
130
|
+
this.#messages.push(this.#user);
|
|
131
|
+
}
|
|
132
|
+
appendBlock(this.#user.content, update.content);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
case "plan":
|
|
136
|
+
case "plan_update":
|
|
137
|
+
case "plan_removed": {
|
|
138
|
+
this.#user = undefined;
|
|
139
|
+
this.#markBoundary();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Every folded tool call in first-seen order (copies) — the flattening of the messages'
|
|
147
|
+
* `toolCalls`, which is the same order because a call attaches to the message in progress. */
|
|
148
|
+
get toolCalls() {
|
|
149
|
+
return this.#messages.flatMap((message) => message.toolCalls.map((call) => ({ ...call })));
|
|
150
|
+
}
|
|
151
|
+
/** The messages so far (copies). A thought still waiting for the content it precedes is
|
|
152
|
+
* reported as a trailing assistant message with no text. */
|
|
153
|
+
snapshot() {
|
|
154
|
+
const messages = this.#messages.map(copyMessage);
|
|
155
|
+
if (this.#pendingThoughts.length > 0) {
|
|
156
|
+
messages.push({
|
|
157
|
+
role: "assistant",
|
|
158
|
+
content: [],
|
|
159
|
+
toolCalls: [],
|
|
160
|
+
thoughts: this.#pendingThoughts.map((block) => ({ ...block })),
|
|
161
|
+
receivedAt: this.#pendingThoughtsAt,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return messages;
|
|
165
|
+
}
|
|
166
|
+
#markBoundary() {
|
|
167
|
+
this.#boundaryPending = true;
|
|
168
|
+
this.#activeMessageId = undefined;
|
|
169
|
+
}
|
|
170
|
+
/** `SessionState.beginAssistantMessageChunk`'s id rule: a chunk whose `messageId` differs from
|
|
171
|
+
* the active one marks a boundary; an id is remembered whether or not the chunk carries text. */
|
|
172
|
+
#applyMessageId(messageId) {
|
|
173
|
+
const id = messageId ?? undefined;
|
|
174
|
+
if (id !== undefined && this.#activeMessageId !== undefined && id !== this.#activeMessageId) {
|
|
175
|
+
this.#boundaryPending = true;
|
|
176
|
+
}
|
|
177
|
+
if (id !== undefined)
|
|
178
|
+
this.#activeMessageId = id;
|
|
179
|
+
}
|
|
180
|
+
/** Open a new assistant message (adopting any pending thoughts, which then date it). The
|
|
181
|
+
* boundary flag is the caller's: a text or non-text block consumes it, a tool call does not
|
|
182
|
+
* (text after a tool call is always a new message, exactly like the text fold). */
|
|
183
|
+
#openAssistant(receivedAt) {
|
|
184
|
+
const message = {
|
|
185
|
+
role: "assistant",
|
|
186
|
+
content: [],
|
|
187
|
+
toolCalls: [],
|
|
188
|
+
thoughts: [],
|
|
189
|
+
receivedAt: this.#pendingThoughtsAt ?? receivedAt,
|
|
190
|
+
};
|
|
191
|
+
this.#adoptPendingThoughts(message);
|
|
192
|
+
this.#messages.push(message);
|
|
193
|
+
this.#assistant = message;
|
|
194
|
+
return message;
|
|
195
|
+
}
|
|
196
|
+
#adoptPendingThoughts(message) {
|
|
197
|
+
if (this.#pendingThoughts.length === 0)
|
|
198
|
+
return;
|
|
199
|
+
message.thoughts.push(...this.#pendingThoughts);
|
|
200
|
+
this.#pendingThoughts = [];
|
|
201
|
+
this.#pendingThoughtsAt = undefined;
|
|
202
|
+
}
|
|
203
|
+
/** A thought that no assistant content followed (a user message or a turn boundary came first)
|
|
204
|
+
* is an assistant message of its own; it is never the attach target for later content. */
|
|
205
|
+
#flushPendingThoughts() {
|
|
206
|
+
if (this.#pendingThoughts.length === 0)
|
|
207
|
+
return;
|
|
208
|
+
this.#messages.push({
|
|
209
|
+
role: "assistant",
|
|
210
|
+
content: [],
|
|
211
|
+
toolCalls: [],
|
|
212
|
+
thoughts: this.#pendingThoughts,
|
|
213
|
+
receivedAt: this.#pendingThoughtsAt,
|
|
214
|
+
});
|
|
215
|
+
this.#pendingThoughts = [];
|
|
216
|
+
this.#pendingThoughtsAt = undefined;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** Fold verbatim update records (a turn's `updates`, a `replay`) into messages. */
|
|
220
|
+
export function foldMessages(records) {
|
|
221
|
+
const folder = new MessageFolder();
|
|
222
|
+
for (const { update, receivedAt } of records)
|
|
223
|
+
folder.apply(update, receivedAt);
|
|
224
|
+
return folder.snapshot();
|
|
225
|
+
}
|
package/dist/agent/probe.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { AcpAgentCatalog, AcpAgentProbeOptions } from "./types.js";
|
|
|
8
8
|
export declare function createAgentProbeRunner(backends: Record<string, CustomBackendConfig> | undefined): ValidateProbeRunner;
|
|
9
9
|
/** The catalog behind `AcpAgent.probe` (see the file header). Per-target failures never throw
|
|
10
10
|
* (`probed: false` with a redacted `error`); a bad `modelFilter`, `probeTimeoutMs`, or
|
|
11
|
-
* `probeConcurrency` throws a TypeError and a malformed registry
|
|
11
|
+
* `probeConcurrency` throws a TypeError and a malformed registry an INVALID_ARGUMENT — all
|
|
12
12
|
* before any process spawns. */
|
|
13
13
|
export declare function probeCatalog(options?: AcpAgentProbeOptions): Promise<AcpAgentCatalog>;
|
|
14
14
|
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/agent/probe.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/agent/probe.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAKnF,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAExE;;;wEAGwE;AACxE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG,SAAS,GAAG,mBAAmB,CA0CrH;AAED;;;iCAGiC;AACjC,wBAAsB,YAAY,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,eAAe,CAAC,CA8B/F"}
|
package/dist/agent/probe.js
CHANGED
|
@@ -3,6 +3,7 @@ import { buildHarnessModelsView, buildModelFilter, probeHarnessConfig, } from ".
|
|
|
3
3
|
import { mapThrownError } from "../errors-map.js";
|
|
4
4
|
import { registryWithRunBackends } from "../registry.js";
|
|
5
5
|
import { resolveModelRoute } from "../routing.js";
|
|
6
|
+
import { describeBackendTraits } from "../traits.js";
|
|
6
7
|
import { releaseOnExit, retainOnExit } from "./process-registry.js";
|
|
7
8
|
import { resolveAgentRegistry } from "./routing.js";
|
|
8
9
|
/** A `ValidateProbeRunner` over a dedicated connection per probe: spawn, initialize, one no-prompt
|
|
@@ -34,6 +35,9 @@ export function createAgentProbeRunner(backends) {
|
|
|
34
35
|
...(route.backend.defaultModeId === undefined ? {} : { defaultModeId: route.backend.defaultModeId }),
|
|
35
36
|
options: handle.advertisedConfigOptions,
|
|
36
37
|
modes: handle.modes ?? null,
|
|
38
|
+
// The live refinement: pi and the Codex fork advertise their system-prompt channel and the
|
|
39
|
+
// two vendor extensions at initialize; Claude advertises nothing and reports the tables.
|
|
40
|
+
traits: describeBackendTraits(route.backend, registry, connection.capabilities),
|
|
37
41
|
};
|
|
38
42
|
}
|
|
39
43
|
finally {
|
|
@@ -54,7 +58,7 @@ export function createAgentProbeRunner(backends) {
|
|
|
54
58
|
}
|
|
55
59
|
/** The catalog behind `AcpAgent.probe` (see the file header). Per-target failures never throw
|
|
56
60
|
* (`probed: false` with a redacted `error`); a bad `modelFilter`, `probeTimeoutMs`, or
|
|
57
|
-
* `probeConcurrency` throws a TypeError and a malformed registry
|
|
61
|
+
* `probeConcurrency` throws a TypeError and a malformed registry an INVALID_ARGUMENT — all
|
|
58
62
|
* before any process spawns. */
|
|
59
63
|
export async function probeCatalog(options = {}) {
|
|
60
64
|
// Validation only: a bad regex must surface BEFORE any spawn (the MCP handler does the same).
|
package/dist/agent/queue.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// The per-agent FIFO. `prompt`, `fork`, `setMode`, `setConfigOptions`, `close` and the
|
|
2
|
-
// open all run through one SerialQueue so no two of them ever overlap (a second concurrent
|
|
1
|
+
// The per-agent FIFO. `prompt`, `fork`, `setModel`, `setMode`, `setConfigOptions`, `close` and the
|
|
2
|
+
// implicit open all run through one SerialQueue so no two of them ever overlap (a second concurrent
|
|
3
3
|
// `SessionHandle.prompt` would clobber the handle's active turn; pi rejects a fork while the
|
|
4
4
|
// source has a turn in flight; Claude would copy a partial transcript). Pure: no ACP imports.
|
|
5
5
|
export class SerialQueue {
|
package/dist/agent/routing.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { type BackendRegistry, type CustomBackendConfig } from "../registry.js";
|
|
|
4
4
|
import { type ModelRoute } from "../routing.js";
|
|
5
5
|
import type { AcpAgentOptions } from "./types.js";
|
|
6
6
|
/** The custom-backend registry for an agent: `backends` merged over `AGENTPRISM_BACKENDS`. A
|
|
7
|
-
* malformed registry is a caller error (
|
|
7
|
+
* malformed registry is a caller error (INVALID_ARGUMENT; the runner's wrap says
|
|
8
|
+
* SCRIPT_VALIDATION_ERROR for the same condition). */
|
|
8
9
|
export declare function resolveAgentRegistry(backends: Record<string, CustomBackendConfig> | undefined, label?: string): BackendRegistry;
|
|
9
10
|
/** The runner's routing grammar applied to `new AcpAgent({ model })`. */
|
|
10
11
|
export declare function resolveAgentRoute(options: Pick<AcpAgentOptions, "model">, registry: BackendRegistry): ModelRoute;
|
|
@@ -17,6 +18,23 @@ export declare function resolveAgentRoute(options: Pick<AcpAgentOptions, "model"
|
|
|
17
18
|
* VERBATIM to the ref's backend (the same unrouted rule as the runner, applied to this backend).
|
|
18
19
|
*/
|
|
19
20
|
export declare function resolveRefRoute(ref: AgentSessionRef, model: string | undefined, registry: BackendRegistry, label?: string): ModelRoute;
|
|
21
|
+
/**
|
|
22
|
+
* The model rule `fork()`, `setModel()` and a per-turn `model` share: `spec` goes through the
|
|
23
|
+
* runner's grammar (`resolveModelRoute` — a registered name wins, an unrouted spec goes to the
|
|
24
|
+
* default backend) and the route must land on THIS agent's backend, poolKey-equal; otherwise
|
|
25
|
+
* INVALID_ARGUMENT naming both backends. The remainder is the verbatim model id (`undefined` for a
|
|
26
|
+
* backend-only spec — `fork()` reads that as "no selection"; a switch rejects it, see
|
|
27
|
+
* `resolveModelSwitch`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveSameBackendModel(spec: string, backend: Backend, registry: BackendRegistry, label: string | undefined, method: string): ModelRoute;
|
|
30
|
+
/** What a mid-session switch selects: `spec` resolved with `resolveSameBackendModel`, with a
|
|
31
|
+
* blank spec or a backend-only spec (`"claude"`, `"claude/"`) rejected — there is no wire form
|
|
32
|
+
* for "unselect", so a switch always names a model id. Returns the verbatim id to send and the
|
|
33
|
+
* routed `<backendId>/<id>` form the agent's `model` takes once it applied. */
|
|
34
|
+
export declare function resolveModelSwitch(spec: unknown, backend: Backend, registry: BackendRegistry, label: string | undefined, method: string): {
|
|
35
|
+
modelSpec: string;
|
|
36
|
+
model: string;
|
|
37
|
+
};
|
|
20
38
|
/** A fresh `Backend` instance with the same identity as `backend` (a registered name wins, as in
|
|
21
39
|
* routing; else the built-in of that id). A fork child must own its own instance — pooling identity
|
|
22
40
|
* is `poolKey ?? id`, never the object. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/agent/routing.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/agent/routing.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,EAA0B,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACxG,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAEnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;uDAEuD;AACvD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG,SAAS,EACzD,KAAK,CAAC,EAAE,MAAM,GACb,eAAe,CAMjB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,eAAe,GAAG,UAAU,CAEhH;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,eAAe,EACzB,KAAK,CAAC,EAAE,MAAM,GACb,UAAU,CA4BZ;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,GACb,UAAU,CAYZ;AAED;;;gFAGgF;AAChF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,GACb;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAYtC;AAED;;4CAE4C;AAC5C,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAEpF;AAQD;kFACkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,MAAM,CAiB/G"}
|
package/dist/agent/routing.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// Agent-facing routing over ../routing.js: the registry read (malformed →
|
|
1
|
+
// Agent-facing routing over ../routing.js: the registry read (malformed → INVALID_ARGUMENT),
|
|
2
2
|
// the runner's model-spec grammar for `new AcpAgent({ model })`, the ref-driven route the cold
|
|
3
|
-
// statics use (never the default backend),
|
|
4
|
-
//
|
|
3
|
+
// statics use (never the default backend), the same-backend rule a fork override and a
|
|
4
|
+
// mid-session model switch (`setModel`, a per-turn `model`) must satisfy, cwd validation that
|
|
5
|
+
// fails BEFORE a process spawns, and the fresh-Backend-instance rule a fork child needs.
|
|
5
6
|
import { isAbsolute } from "node:path";
|
|
6
7
|
import { statSync } from "node:fs";
|
|
7
8
|
import { builtinBackend } from "../backends/builtins.js";
|
|
@@ -10,7 +11,8 @@ import { resolveBackendRegistry } from "../registry.js";
|
|
|
10
11
|
import { asciiLowercase, resolveModelRoute } from "../routing.js";
|
|
11
12
|
import { agentValidationError } from "./errors.js";
|
|
12
13
|
/** The custom-backend registry for an agent: `backends` merged over `AGENTPRISM_BACKENDS`. A
|
|
13
|
-
* malformed registry is a caller error (
|
|
14
|
+
* malformed registry is a caller error (INVALID_ARGUMENT; the runner's wrap says
|
|
15
|
+
* SCRIPT_VALIDATION_ERROR for the same condition). */
|
|
14
16
|
export function resolveAgentRegistry(backends, label) {
|
|
15
17
|
try {
|
|
16
18
|
return resolveBackendRegistry(backends);
|
|
@@ -52,6 +54,41 @@ export function resolveRefRoute(ref, model, registry, label) {
|
|
|
52
54
|
}
|
|
53
55
|
return { backend, modelSpec: model };
|
|
54
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The model rule `fork()`, `setModel()` and a per-turn `model` share: `spec` goes through the
|
|
59
|
+
* runner's grammar (`resolveModelRoute` — a registered name wins, an unrouted spec goes to the
|
|
60
|
+
* default backend) and the route must land on THIS agent's backend, poolKey-equal; otherwise
|
|
61
|
+
* INVALID_ARGUMENT naming both backends. The remainder is the verbatim model id (`undefined` for a
|
|
62
|
+
* backend-only spec — `fork()` reads that as "no selection"; a switch rejects it, see
|
|
63
|
+
* `resolveModelSwitch`).
|
|
64
|
+
*/
|
|
65
|
+
export function resolveSameBackendModel(spec, backend, registry, label, method) {
|
|
66
|
+
const route = resolveModelRoute(spec, registry);
|
|
67
|
+
const routedPool = route.backend.poolKey ?? route.backend.id;
|
|
68
|
+
const ownPool = backend.poolKey ?? backend.id;
|
|
69
|
+
if (route.backend.id !== backend.id || routedPool !== ownPool) {
|
|
70
|
+
// Same id, different pool (a registry entry shadowing this built-in's name): say which pool.
|
|
71
|
+
const pooled = route.backend.id === backend.id;
|
|
72
|
+
const routed = `backend "${route.backend.id}"${pooled ? ` (pool "${routedPool}")` : ""}`;
|
|
73
|
+
const own = `backend "${backend.id}"${pooled ? ` (pool "${ownPool}")` : ""}`;
|
|
74
|
+
throw agentValidationError(`${method}: model "${spec}" routes to ${routed} but must stay on ${own}`, label);
|
|
75
|
+
}
|
|
76
|
+
return route;
|
|
77
|
+
}
|
|
78
|
+
/** What a mid-session switch selects: `spec` resolved with `resolveSameBackendModel`, with a
|
|
79
|
+
* blank spec or a backend-only spec (`"claude"`, `"claude/"`) rejected — there is no wire form
|
|
80
|
+
* for "unselect", so a switch always names a model id. Returns the verbatim id to send and the
|
|
81
|
+
* routed `<backendId>/<id>` form the agent's `model` takes once it applied. */
|
|
82
|
+
export function resolveModelSwitch(spec, backend, registry, label, method) {
|
|
83
|
+
if (typeof spec !== "string" || spec.trim() === "") {
|
|
84
|
+
throw agentValidationError(`${method} requires a non-empty model spec ("${backend.id}/<model id>")`, label);
|
|
85
|
+
}
|
|
86
|
+
const { modelSpec } = resolveSameBackendModel(spec, backend, registry, label, method);
|
|
87
|
+
if (modelSpec === undefined || modelSpec.trim() === "") {
|
|
88
|
+
throw agentValidationError(`${method}: model "${spec}" names backend "${backend.id}" but no model id; use "${backend.id}/<model id>"`, label);
|
|
89
|
+
}
|
|
90
|
+
return { modelSpec, model: `${backend.id}/${modelSpec}` };
|
|
91
|
+
}
|
|
55
92
|
/** A fresh `Backend` instance with the same identity as `backend` (a registered name wins, as in
|
|
56
93
|
* routing; else the built-in of that id). A fork child must own its own instance — pooling identity
|
|
57
94
|
* is `poolKey ?? id`, never the object. */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AcpAgentStream, AcpAgentStreamEvent } from "./types.js";
|
|
2
|
+
type Result = IteratorResult<AcpAgentStreamEvent, undefined>;
|
|
3
|
+
export declare class TurnStream implements AcpAgentStream {
|
|
4
|
+
#private;
|
|
5
|
+
/** `stop` aborts the turn and resolves once it settled; called at most once. */
|
|
6
|
+
constructor(stop: () => Promise<void>);
|
|
7
|
+
/** Buffer an event, or hand it straight to a waiting `next()`. Ignored once closed. */
|
|
8
|
+
push(event: AcpAgentStreamEvent): void;
|
|
9
|
+
/** The turn resolved; the terminal event is already buffered. */
|
|
10
|
+
end(): void;
|
|
11
|
+
/** The turn rejected: buffered events are still delivered, then `error` is thrown once. */
|
|
12
|
+
fail(error: unknown): void;
|
|
13
|
+
next(): Promise<Result>;
|
|
14
|
+
/** The consumer left early: abort the turn, wait for it to settle, then report `done`. */
|
|
15
|
+
return(): Promise<Result>;
|
|
16
|
+
/** Same as `return()`, then rethrow `error` to the caller (async-generator semantics). */
|
|
17
|
+
throw(error?: unknown): Promise<Result>;
|
|
18
|
+
[Symbol.asyncIterator](): AcpAgentStream;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/agent/stream.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtE,KAAK,MAAM,GAAG,cAAc,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC;AAS7D,qBAAa,UAAW,YAAW,cAAc;;IAU/C,gFAAgF;gBACpE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;IAIrC,uFAAuF;IACvF,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAOtC,iEAAiE;IACjE,GAAG,IAAI,IAAI;IAMX,2FAA2F;IAC3F,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAO1B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IASvB,0FAA0F;IACpF,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAK/B,0FAA0F;IACpF,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAK7C,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc;CAkCzC"}
|