@pwrdrvr/agent-acp 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +72 -9
- package/dist/index.js +158 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,16 @@ interface AcpJsonRpcTransport {
|
|
|
14
14
|
* drives. Injectable (via `createConnection`) so the adapter can be unit-tested
|
|
15
15
|
* without spawning a real agent. The library's `ClientSideConnection` satisfies
|
|
16
16
|
* this structurally (its methods take typed schema params; we pass records that
|
|
17
|
-
* match at runtime).
|
|
17
|
+
* match at runtime).
|
|
18
|
+
*
|
|
19
|
+
* Every entry here MUST exist on the real `ClientSideConnection`. It is
|
|
20
|
+
* attached with an `as unknown as` cast (see `createConnection`), so a name
|
|
21
|
+
* that is merely plausible type-checks and then throws
|
|
22
|
+
* "conn.<name> is not a function" at runtime, on the wire, against a user's
|
|
23
|
+
* agent. `setSessionModel` was such an entry: ACP 1.3 has no `session/set_model`
|
|
24
|
+
* at all, so every model selection died before it was sent. Anything the SDK
|
|
25
|
+
* does not expose as a typed method belongs in `dispatch`'s default arm, which
|
|
26
|
+
* sends it verbatim through `request()`. */
|
|
18
27
|
interface AcpAgentConnection {
|
|
19
28
|
initialize(params: Record<string, unknown>): Promise<unknown>;
|
|
20
29
|
newSession(params: Record<string, unknown>): Promise<unknown>;
|
|
@@ -22,11 +31,14 @@ interface AcpAgentConnection {
|
|
|
22
31
|
prompt(params: Record<string, unknown>): Promise<unknown>;
|
|
23
32
|
cancel(params: Record<string, unknown>): Promise<void>;
|
|
24
33
|
setSessionMode(params: Record<string, unknown>): Promise<unknown>;
|
|
25
|
-
setSessionModel(params: Record<string, unknown>): Promise<unknown>;
|
|
26
34
|
authenticate(params: Record<string, unknown>): Promise<unknown>;
|
|
27
35
|
request(method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
28
36
|
notify(method: string, params: Record<string, unknown>): Promise<void>;
|
|
29
37
|
}
|
|
38
|
+
/** Prefix on the rejection when a request outlived its budget. Lets a caller
|
|
39
|
+
* tell "the agent did not answer in time" apart from "the agent answered with
|
|
40
|
+
* an error" — the two want different recovery (retry / restart vs. surface). */
|
|
41
|
+
declare const ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX = "acp request timed out";
|
|
30
42
|
/** Built connection + a teardown handle (kills the child / closes streams). */
|
|
31
43
|
type AcpAgentConnectionHandle = {
|
|
32
44
|
connection: AcpAgentConnection;
|
|
@@ -55,7 +67,8 @@ declare class AcpConnection implements AcpJsonRpcTransport {
|
|
|
55
67
|
private ensureConnected;
|
|
56
68
|
private connect;
|
|
57
69
|
private defaultCreateConnection;
|
|
58
|
-
request(method: string, params?: Record<string, unknown>,
|
|
70
|
+
request(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
|
|
71
|
+
private dispatch;
|
|
59
72
|
notify(method: string, params?: Record<string, unknown>): Promise<void>;
|
|
60
73
|
close(): Promise<void>;
|
|
61
74
|
onNotification(listener: (method: string, params: Record<string, unknown>) => void): () => void;
|
|
@@ -66,7 +79,14 @@ type LocalAcpProbeResult = {
|
|
|
66
79
|
stdout?: string | Buffer;
|
|
67
80
|
stderr?: string | Buffer;
|
|
68
81
|
};
|
|
69
|
-
type
|
|
82
|
+
type LocalAcpProbeOptions = {
|
|
83
|
+
/** Budget for this probe spawn. An injected probe should honor it so a slow
|
|
84
|
+
* CLI is reported as "did not answer in time", not as "not installed". */
|
|
85
|
+
timeoutMs?: number | undefined;
|
|
86
|
+
/** Fires when discovery is abandoned mid-probe. */
|
|
87
|
+
signal?: AbortSignal | undefined;
|
|
88
|
+
};
|
|
89
|
+
type LocalAcpAgentProbe = (command: string, args: string[], options?: LocalAcpProbeOptions) => Promise<LocalAcpProbeResult>;
|
|
70
90
|
/** How a strategy decides it is installed + ACP-capable on this machine. */
|
|
71
91
|
type AcpDiscoveryProbe = {
|
|
72
92
|
/** Args that print a version (probed first; failure = not installed). */
|
|
@@ -289,7 +309,11 @@ type AcpPromptContentBlock = {
|
|
|
289
309
|
mimeType: string;
|
|
290
310
|
data: string;
|
|
291
311
|
};
|
|
292
|
-
type AcpRuntimeOptionSource = "mode" | "model" | "configOption"
|
|
312
|
+
type AcpRuntimeOptionSource = "mode" | "model" | "configOption"
|
|
313
|
+
/** A model write that travels as `session/set_config_option` because the
|
|
314
|
+
* agent exposes model choice as a config option rather than via the legacy
|
|
315
|
+
* top-level `models` capability. See `setModel`. */
|
|
316
|
+
| "modelConfigOption";
|
|
293
317
|
type AcpAgentClientOptions = {
|
|
294
318
|
/** The transport (real stdio or a fake). */
|
|
295
319
|
transport: AcpJsonRpcTransport;
|
|
@@ -377,8 +401,9 @@ declare class AcpAgentClient implements AgentBackend {
|
|
|
377
401
|
* Public `AgentBackend.startThread`: accepts NEUTRAL `AgentStartThreadOptions`
|
|
378
402
|
* and maps them onto an ACP `session/new`. ACP supports:
|
|
379
403
|
* • `cwd` → `session/new.cwd`.
|
|
380
|
-
* • `model` → applied
|
|
381
|
-
* the
|
|
404
|
+
* • `model` → applied after the session opens, by whichever route the agent
|
|
405
|
+
* advertises: the `model` config option, or legacy `session/set_model`
|
|
406
|
+
* (best-effort; debug-logged if not). See `setModel`.
|
|
382
407
|
* • `instructions` is NOT injected here — ACP `session/new` has no base-
|
|
383
408
|
* instructions slot, matching the adapter's existing behavior. A host that
|
|
384
409
|
* wants system framing sends it as leading turn text.
|
|
@@ -468,6 +493,25 @@ declare class AcpAgentClient implements AgentBackend {
|
|
|
468
493
|
* releasing here never blocks resume. */
|
|
469
494
|
archiveThread(threadId: string): Promise<void>;
|
|
470
495
|
setMode(threadId: string, modeId: string): Promise<void>;
|
|
496
|
+
/** Apply a model selection by whichever route the agent actually advertises.
|
|
497
|
+
*
|
|
498
|
+
* ACP 1.3 has NO `session/set_model` — it is absent from `AGENT_METHODS`.
|
|
499
|
+
* An agent that offers model choice does it through a config option with
|
|
500
|
+
* `category: "model"` (Kimi's `session/new` returns exactly that, and nothing
|
|
501
|
+
* under `models`). Some agents still accept `session/set_model` as a vendor
|
|
502
|
+
* extension, and the `models.availableModels` shape is still honoured for
|
|
503
|
+
* them, but it is not what the protocol defines any more.
|
|
504
|
+
*
|
|
505
|
+
* Two reasons to prefer the config option when the agent offers it. The READ
|
|
506
|
+
* path already draws this distinction — `modelIdFromCapabilities` and
|
|
507
|
+
* `modelsFromCapabilities` both fall back to the config option — and the two
|
|
508
|
+
* disagreeing lets a host list models it cannot then select. And the config
|
|
509
|
+
* option's REPLY carries the agent's full refreshed config set, where
|
|
510
|
+
* `session/set_model` answers `{}` and refreshes only via an async
|
|
511
|
+
* notification. On Kimi the model DETERMINES which thought levels exist —
|
|
512
|
+
* `K2.7 Coding` offers only `on`, `K3` offers `low`/`high`/`max` — and
|
|
513
|
+
* `applyReasoning` picks from `runtimeCapabilities` on the following
|
|
514
|
+
* `startTurn`, so the refresh this write leaves behind is what it reads. */
|
|
471
515
|
setModel(threadId: string, modelId: string): Promise<void>;
|
|
472
516
|
setConfigOption(threadId: string, optionId: string, value: string): Promise<void>;
|
|
473
517
|
close(): Promise<void>;
|
|
@@ -707,7 +751,10 @@ type RejectedAcpAgentInstance = {
|
|
|
707
751
|
/** Parsed from successful `--version` output when available. */
|
|
708
752
|
version?: string;
|
|
709
753
|
source: AcpAgentInstanceSource;
|
|
710
|
-
|
|
754
|
+
/** `probe-timed-out` means the CLI did not answer within the probe budget —
|
|
755
|
+
* UNLIKE the other reasons it is not a verdict on the CLI, and a host may
|
|
756
|
+
* re-probe on a larger `probeTimeoutMs` instead of reporting it unusable. */
|
|
757
|
+
reason: "version-probe-failed" | "acp-probe-failed" | "acp-help-mismatch" | "probe-timed-out";
|
|
711
758
|
};
|
|
712
759
|
/** Every installed instance of one agent found on this machine. */
|
|
713
760
|
type DiscoveredAcpAgentGroup = {
|
|
@@ -755,7 +802,23 @@ type LocalAcpDiscoveryOptions = {
|
|
|
755
802
|
/** Include candidates known to exist but rejected by a discovery probe.
|
|
756
803
|
* Defaults false to preserve the legacy installed-only result. */
|
|
757
804
|
includeRejectedCandidates?: boolean;
|
|
805
|
+
/** Budget for ONE probe spawn (the default probe only — an injected `probe`
|
|
806
|
+
* owns its own timing). Defaults to `DEFAULT_ACP_PROBE_TIMEOUT_MS`. */
|
|
807
|
+
probeTimeoutMs?: number;
|
|
808
|
+
/** Abandon discovery in flight (re-triggered discovery, app quitting).
|
|
809
|
+
* Already-probed groups are still returned. */
|
|
810
|
+
signal?: AbortSignal;
|
|
758
811
|
};
|
|
812
|
+
/**
|
|
813
|
+
* Budget for one ACP discovery probe spawn, in ms.
|
|
814
|
+
*
|
|
815
|
+
* Unchanged from the value this module has always used. Kept lower than
|
|
816
|
+
* codex-discovery's version-probe budget on purpose: `discoverStrategyInstances`
|
|
817
|
+
* walks its candidates SERIALLY, so the worst case here is (candidates x
|
|
818
|
+
* budget) rather than a single budget. Hosts that scan machines with many
|
|
819
|
+
* installed copies can raise it knowingly via `probeTimeoutMs`.
|
|
820
|
+
*/
|
|
821
|
+
declare const DEFAULT_ACP_PROBE_TIMEOUT_MS = 5000;
|
|
759
822
|
/**
|
|
760
823
|
* Discover every installed instance of each agent (all `PATH` matches +
|
|
761
824
|
* fallbacks + override that pass the probe), grouped per strategy. Groups with
|
|
@@ -890,4 +953,4 @@ declare class AcpRegistryService {
|
|
|
890
953
|
declare function evaluateAcpDistributionPolicy(distribution: AcpRegistryDistribution, allowlist: AcpAllowlistDecision): AcpDistributionPolicy;
|
|
891
954
|
declare function normalizeRegistry(raw: unknown): AcpRegistryAgent[];
|
|
892
955
|
|
|
893
|
-
export { ACP_REGISTRY_URL, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentConnection, type AcpAgentConnectionHandle, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, AcpConnection, type AcpConnectionOptions, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpHttpMcpServerConfig, type AcpJsonRpcTransport, type AcpLoadThreadOptions, type AcpMcpKeyValue, type AcpMcpKeyValueCollection, type AcpMcpServerConfig, type AcpMcpServerWireConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMcpCapabilities, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpSseMcpServerConfig, type AcpStartThreadOptions, type AcpStartTurnOptions, type AcpStdioMcpServerConfig, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeResult, type RejectedAcpAgentInstance, acpRuntimeSupportsHttpMcp, acpRuntimeSupportsSessionLoad, acpRuntimeSupportsSseMcp, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, modelConfigOption, modelIdFromCapabilities, modelsFromCapabilities, normalizeAcpMcpServerConfig, normalizeAcpMcpServerConfigs, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, reasoningValueForThoughtLevel, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
|
|
956
|
+
export { ACP_REGISTRY_URL, ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentConnection, type AcpAgentConnectionHandle, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, AcpConnection, type AcpConnectionOptions, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpHttpMcpServerConfig, type AcpJsonRpcTransport, type AcpLoadThreadOptions, type AcpMcpKeyValue, type AcpMcpKeyValueCollection, type AcpMcpServerConfig, type AcpMcpServerWireConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMcpCapabilities, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpSseMcpServerConfig, type AcpStartThreadOptions, type AcpStartTurnOptions, type AcpStdioMcpServerConfig, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, DEFAULT_ACP_PROBE_TIMEOUT_MS, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeOptions, type LocalAcpProbeResult, type RejectedAcpAgentInstance, acpRuntimeSupportsHttpMcp, acpRuntimeSupportsSessionLoad, acpRuntimeSupportsSseMcp, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, modelConfigOption, modelIdFromCapabilities, modelsFromCapabilities, normalizeAcpMcpServerConfig, normalizeAcpMcpServerConfigs, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, reasoningValueForThoughtLevel, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,27 @@ import {
|
|
|
10
10
|
createCommandInvocation,
|
|
11
11
|
prependCommandDirToPath
|
|
12
12
|
} from "@pwrdrvr/agent-transport";
|
|
13
|
+
var ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX = "acp request timed out";
|
|
14
|
+
function withAcpRequestTimeout(method, timeoutMs, run) {
|
|
15
|
+
if (timeoutMs === void 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
16
|
+
return run();
|
|
17
|
+
}
|
|
18
|
+
const budget = Math.max(1, Math.floor(timeoutMs));
|
|
19
|
+
const call = run();
|
|
20
|
+
void call.catch(() => void 0);
|
|
21
|
+
let timer;
|
|
22
|
+
const expiry = new Promise((_, reject) => {
|
|
23
|
+
timer = setTimeout(
|
|
24
|
+
() => reject(
|
|
25
|
+
new Error(`${ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX} after ${budget}ms: ${method}`)
|
|
26
|
+
),
|
|
27
|
+
budget
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
return Promise.race([call, expiry]).finally(() => {
|
|
31
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
13
34
|
var AcpConnection = class {
|
|
14
35
|
constructor(options) {
|
|
15
36
|
this.options = options;
|
|
@@ -101,10 +122,17 @@ var AcpConnection = class {
|
|
|
101
122
|
};
|
|
102
123
|
};
|
|
103
124
|
}
|
|
104
|
-
async request(method, params = {},
|
|
125
|
+
async request(method, params = {}, timeoutMs) {
|
|
105
126
|
await this.ensureConnected();
|
|
106
127
|
const conn = this.connection;
|
|
107
128
|
if (!conn) throw new Error("acp connection unavailable");
|
|
129
|
+
return await withAcpRequestTimeout(
|
|
130
|
+
method,
|
|
131
|
+
timeoutMs,
|
|
132
|
+
() => this.dispatch(conn, method, params)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
async dispatch(conn, method, params) {
|
|
108
136
|
switch (method) {
|
|
109
137
|
case "initialize":
|
|
110
138
|
return await conn.initialize(params);
|
|
@@ -116,8 +144,9 @@ var AcpConnection = class {
|
|
|
116
144
|
return await conn.prompt(params);
|
|
117
145
|
case "session/set_mode":
|
|
118
146
|
return await conn.setSessionMode(params);
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
// `session/set_model` is deliberately NOT cased here: SDK 1.3 has no
|
|
148
|
+
// typed `setSessionModel`, so it goes out through the default arm's
|
|
149
|
+
// generic `request()` for agents still speaking the older method.
|
|
121
150
|
case "session/cancel":
|
|
122
151
|
await conn.cancel(params);
|
|
123
152
|
return {};
|
|
@@ -1033,8 +1062,9 @@ var AcpAgentClient = class {
|
|
|
1033
1062
|
* Public `AgentBackend.startThread`: accepts NEUTRAL `AgentStartThreadOptions`
|
|
1034
1063
|
* and maps them onto an ACP `session/new`. ACP supports:
|
|
1035
1064
|
* • `cwd` → `session/new.cwd`.
|
|
1036
|
-
* • `model` → applied
|
|
1037
|
-
* the
|
|
1065
|
+
* • `model` → applied after the session opens, by whichever route the agent
|
|
1066
|
+
* advertises: the `model` config option, or legacy `session/set_model`
|
|
1067
|
+
* (best-effort; debug-logged if not). See `setModel`.
|
|
1038
1068
|
* • `instructions` is NOT injected here — ACP `session/new` has no base-
|
|
1039
1069
|
* instructions slot, matching the adapter's existing behavior. A host that
|
|
1040
1070
|
* wants system framing sends it as leading turn text.
|
|
@@ -1412,7 +1442,32 @@ var AcpAgentClient = class {
|
|
|
1412
1442
|
async setMode(threadId, modeId) {
|
|
1413
1443
|
await this.setRuntimeOption(threadId, "mode", modeId, modeId);
|
|
1414
1444
|
}
|
|
1445
|
+
/** Apply a model selection by whichever route the agent actually advertises.
|
|
1446
|
+
*
|
|
1447
|
+
* ACP 1.3 has NO `session/set_model` — it is absent from `AGENT_METHODS`.
|
|
1448
|
+
* An agent that offers model choice does it through a config option with
|
|
1449
|
+
* `category: "model"` (Kimi's `session/new` returns exactly that, and nothing
|
|
1450
|
+
* under `models`). Some agents still accept `session/set_model` as a vendor
|
|
1451
|
+
* extension, and the `models.availableModels` shape is still honoured for
|
|
1452
|
+
* them, but it is not what the protocol defines any more.
|
|
1453
|
+
*
|
|
1454
|
+
* Two reasons to prefer the config option when the agent offers it. The READ
|
|
1455
|
+
* path already draws this distinction — `modelIdFromCapabilities` and
|
|
1456
|
+
* `modelsFromCapabilities` both fall back to the config option — and the two
|
|
1457
|
+
* disagreeing lets a host list models it cannot then select. And the config
|
|
1458
|
+
* option's REPLY carries the agent's full refreshed config set, where
|
|
1459
|
+
* `session/set_model` answers `{}` and refreshes only via an async
|
|
1460
|
+
* notification. On Kimi the model DETERMINES which thought levels exist —
|
|
1461
|
+
* `K2.7 Coding` offers only `on`, `K3` offers `low`/`high`/`max` — and
|
|
1462
|
+
* `applyReasoning` picks from `runtimeCapabilities` on the following
|
|
1463
|
+
* `startTurn`, so the refresh this write leaves behind is what it reads. */
|
|
1415
1464
|
async setModel(threadId, modelId) {
|
|
1465
|
+
const advertisesModelsCapability = (this.runtimeCapabilities?.models?.availableModels?.length ?? 0) > 0;
|
|
1466
|
+
const option = modelConfigOption(this.runtimeCapabilities);
|
|
1467
|
+
if (!advertisesModelsCapability && option !== void 0) {
|
|
1468
|
+
await this.setRuntimeOption(threadId, "modelConfigOption", option.id, modelId);
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1416
1471
|
await this.setRuntimeOption(threadId, "model", modelId, modelId);
|
|
1417
1472
|
}
|
|
1418
1473
|
async setConfigOption(threadId, optionId, value) {
|
|
@@ -1436,6 +1491,8 @@ var AcpAgentClient = class {
|
|
|
1436
1491
|
}
|
|
1437
1492
|
async initialize() {
|
|
1438
1493
|
if (this.initialized) return;
|
|
1494
|
+
this.unsubscribeNotification?.();
|
|
1495
|
+
this.unsubscribeRequest?.();
|
|
1439
1496
|
this.unsubscribeNotification = this.transport.onNotification((method, params) => {
|
|
1440
1497
|
this.handleNotification(method, params);
|
|
1441
1498
|
});
|
|
@@ -1567,7 +1624,16 @@ var AcpAgentClient = class {
|
|
|
1567
1624
|
value
|
|
1568
1625
|
);
|
|
1569
1626
|
const runtimeCapabilities = this.captureRuntimeCapabilities("session-load", result);
|
|
1570
|
-
const requested = source === "configOption" ? { configValues: { [optionId]: value }, updatedAt: this.now() } : source === "mode" ? { currentModeId: value, updatedAt: this.now() } :
|
|
1627
|
+
const requested = source === "configOption" ? { configValues: { [optionId]: value }, updatedAt: this.now() } : source === "mode" ? { currentModeId: value, updatedAt: this.now() } : source === "modelConfigOption" ? (
|
|
1628
|
+
// Record BOTH: it IS the config option's value, and it IS the
|
|
1629
|
+
// session's model. A consumer reading `currentModelId` must not
|
|
1630
|
+
// have to know which route the agent happened to expose.
|
|
1631
|
+
{
|
|
1632
|
+
currentModelId: value,
|
|
1633
|
+
configValues: { [optionId]: value },
|
|
1634
|
+
updatedAt: this.now()
|
|
1635
|
+
}
|
|
1636
|
+
) : { currentModelId: value, updatedAt: this.now() };
|
|
1571
1637
|
session.runtimeState = mergeAcpRuntimeState(session.runtimeState, requested);
|
|
1572
1638
|
const effectiveCapabilities = runtimeCapabilities ?? this.runtimeCapabilities;
|
|
1573
1639
|
if (effectiveCapabilities) {
|
|
@@ -1580,7 +1646,7 @@ var AcpAgentClient = class {
|
|
|
1580
1646
|
this.emitThreadSettings(session, effectiveCapabilities, session.runtimeState);
|
|
1581
1647
|
}
|
|
1582
1648
|
async setRuntimeOptionOnTransport(protocolSessionId, source, optionId, value) {
|
|
1583
|
-
if (source === "configOption") {
|
|
1649
|
+
if (source === "configOption" || source === "modelConfigOption") {
|
|
1584
1650
|
return await this.transport.request("session/set_config_option", {
|
|
1585
1651
|
sessionId: protocolSessionId,
|
|
1586
1652
|
configId: optionId,
|
|
@@ -2314,12 +2380,17 @@ import {
|
|
|
2314
2380
|
prependCommandDirToPath as prependCommandDirToPath2
|
|
2315
2381
|
} from "@pwrdrvr/agent-transport";
|
|
2316
2382
|
var execFile = promisify(execFileCallback);
|
|
2383
|
+
var DEFAULT_ACP_PROBE_TIMEOUT_MS = 5e3;
|
|
2317
2384
|
async function discoverLocalAcpAgentInstances(options = {}) {
|
|
2318
2385
|
const strategies = options.strategies ?? BUILT_IN_ACP_STRATEGIES;
|
|
2319
2386
|
const env = options.env ?? process.env;
|
|
2320
2387
|
const probe = options.probe ?? makeDefaultProbe(env);
|
|
2321
2388
|
const listExecutables = options.listExecutables ?? defaultListExecutables;
|
|
2322
2389
|
const now = options.now ?? Date.now;
|
|
2390
|
+
const probeOptions = {
|
|
2391
|
+
timeoutMs: options.probeTimeoutMs ?? DEFAULT_ACP_PROBE_TIMEOUT_MS,
|
|
2392
|
+
signal: options.signal
|
|
2393
|
+
};
|
|
2323
2394
|
const groups = await Promise.all(
|
|
2324
2395
|
strategies.map(
|
|
2325
2396
|
(strategy) => discoverStrategyInstances(
|
|
@@ -2329,7 +2400,8 @@ async function discoverLocalAcpAgentInstances(options = {}) {
|
|
|
2329
2400
|
env,
|
|
2330
2401
|
listExecutables,
|
|
2331
2402
|
options.overrides?.[strategy.id],
|
|
2332
|
-
options.includeRejectedCandidates === true
|
|
2403
|
+
options.includeRejectedCandidates === true,
|
|
2404
|
+
probeOptions
|
|
2333
2405
|
)
|
|
2334
2406
|
)
|
|
2335
2407
|
);
|
|
@@ -2353,22 +2425,27 @@ async function discoverLocalAcpAgents(options = {}) {
|
|
|
2353
2425
|
return [agent];
|
|
2354
2426
|
});
|
|
2355
2427
|
}
|
|
2356
|
-
async function discoverStrategyInstances(strategy, probe, now, env, listExecutables, override, includeRejectedCandidates = false) {
|
|
2428
|
+
async function discoverStrategyInstances(strategy, probe, now, env, listExecutables, override, includeRejectedCandidates = false, probeOptions = {}) {
|
|
2357
2429
|
const candidates = candidateCommands(strategy, env, listExecutables, override);
|
|
2358
2430
|
const instances = [];
|
|
2359
2431
|
const rejectedInstances = [];
|
|
2360
2432
|
for (const candidate of candidates) {
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
runProbe(probe, candidate.command, strategy.discoveryProbe.
|
|
2433
|
+
if (probeOptions.signal?.aborted) break;
|
|
2434
|
+
const [versionAttempt, helpAttempt] = await Promise.all([
|
|
2435
|
+
runProbe(probe, candidate.command, strategy.discoveryProbe.versionArgs, probeOptions),
|
|
2436
|
+
runProbe(probe, candidate.command, strategy.discoveryProbe.helpArgs, probeOptions)
|
|
2364
2437
|
]);
|
|
2438
|
+
if (probeOptions.signal?.aborted) break;
|
|
2439
|
+
const versionResult = versionAttempt.ok ? versionAttempt.result : void 0;
|
|
2440
|
+
const helpResult = helpAttempt.ok ? helpAttempt.result : void 0;
|
|
2365
2441
|
const version = versionResult ? parseCliVersion(resultText(versionResult)) : void 0;
|
|
2366
2442
|
if (!versionResult || !helpResult) {
|
|
2367
|
-
|
|
2443
|
+
const timedOut = !versionAttempt.ok && versionAttempt.timedOut || !helpAttempt.ok && helpAttempt.timedOut;
|
|
2444
|
+
if (candidate.detected && (timedOut || includeRejectedCandidates)) {
|
|
2368
2445
|
const rejected = {
|
|
2369
2446
|
command: candidate.command,
|
|
2370
2447
|
source: candidate.source,
|
|
2371
|
-
reason: versionResult ? "acp-probe-failed" : "version-probe-failed"
|
|
2448
|
+
reason: timedOut ? "probe-timed-out" : versionResult ? "acp-probe-failed" : "version-probe-failed"
|
|
2372
2449
|
};
|
|
2373
2450
|
if (version !== void 0) rejected.version = version;
|
|
2374
2451
|
rejectedInstances.push(rejected);
|
|
@@ -2531,25 +2608,78 @@ function ensureArgs(args, ensure) {
|
|
|
2531
2608
|
}
|
|
2532
2609
|
return result;
|
|
2533
2610
|
}
|
|
2611
|
+
var AcpProbeTimeoutError = class extends Error {
|
|
2612
|
+
constructor(command, timeoutMs) {
|
|
2613
|
+
super(`acp probe did not answer within ${timeoutMs}ms: ${command}`);
|
|
2614
|
+
this.name = "AcpProbeTimeoutError";
|
|
2615
|
+
}
|
|
2616
|
+
};
|
|
2617
|
+
function isProbeTimeout(error) {
|
|
2618
|
+
if (error instanceof AcpProbeTimeoutError) return true;
|
|
2619
|
+
const name = error?.name;
|
|
2620
|
+
return error?.killed === true || name === "AbortError" || name === "TimeoutError";
|
|
2621
|
+
}
|
|
2622
|
+
function normalizeProbeTimeoutMs(timeoutMs) {
|
|
2623
|
+
if (timeoutMs === void 0 || !Number.isFinite(timeoutMs)) {
|
|
2624
|
+
return DEFAULT_ACP_PROBE_TIMEOUT_MS;
|
|
2625
|
+
}
|
|
2626
|
+
return Math.max(1, Math.floor(timeoutMs));
|
|
2627
|
+
}
|
|
2534
2628
|
function makeDefaultProbe(env) {
|
|
2535
|
-
return async (command, args) => {
|
|
2629
|
+
return async (command, args, options = {}) => {
|
|
2630
|
+
const timeoutMs = normalizeProbeTimeoutMs(options.timeoutMs);
|
|
2536
2631
|
const launchEnv = prependCommandDirToPath2(command, env);
|
|
2537
2632
|
const invocation = createCommandInvocation2({ command, args, env: launchEnv });
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2633
|
+
const controller = new AbortController();
|
|
2634
|
+
let timedOut = false;
|
|
2635
|
+
const onAbort = () => controller.abort();
|
|
2636
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2637
|
+
const timer = setTimeout(() => {
|
|
2638
|
+
timedOut = true;
|
|
2639
|
+
controller.abort();
|
|
2640
|
+
}, timeoutMs);
|
|
2641
|
+
const expired = new Promise((resolve) => {
|
|
2642
|
+
controller.signal.addEventListener("abort", () => resolve("expired"), {
|
|
2643
|
+
once: true
|
|
2644
|
+
});
|
|
2545
2645
|
});
|
|
2646
|
+
try {
|
|
2647
|
+
const execution = execFile(invocation.command, invocation.args, {
|
|
2648
|
+
timeout: timeoutMs,
|
|
2649
|
+
signal: controller.signal,
|
|
2650
|
+
maxBuffer: 1024 * 1024,
|
|
2651
|
+
// A Windows `.cmd` shim runs through `cmd.exe`; without this every
|
|
2652
|
+
// discovery pass flashes console windows over the host app.
|
|
2653
|
+
windowsHide: true,
|
|
2654
|
+
// Prepend the candidate's own dir so a node-script CLI (e.g. an nvm-
|
|
2655
|
+
// installed `qwen`) finds its sibling `node` during the probe.
|
|
2656
|
+
env: launchEnv,
|
|
2657
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
2658
|
+
}).then(
|
|
2659
|
+
(result) => ({ ok: true, result }),
|
|
2660
|
+
(error) => ({ ok: false, error })
|
|
2661
|
+
);
|
|
2662
|
+
const settled = await Promise.race([execution, expired]);
|
|
2663
|
+
if (settled === "expired") {
|
|
2664
|
+
if (timedOut) throw new AcpProbeTimeoutError(command, timeoutMs);
|
|
2665
|
+
throw new Error(`acp probe aborted: ${command}`);
|
|
2666
|
+
}
|
|
2667
|
+
if (!settled.ok) {
|
|
2668
|
+
if (timedOut) throw new AcpProbeTimeoutError(command, timeoutMs);
|
|
2669
|
+
throw settled.error;
|
|
2670
|
+
}
|
|
2671
|
+
return settled.result;
|
|
2672
|
+
} finally {
|
|
2673
|
+
clearTimeout(timer);
|
|
2674
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
2675
|
+
}
|
|
2546
2676
|
};
|
|
2547
2677
|
}
|
|
2548
|
-
async function runProbe(probe, command, args) {
|
|
2678
|
+
async function runProbe(probe, command, args, options) {
|
|
2549
2679
|
try {
|
|
2550
|
-
return await probe(command, args);
|
|
2551
|
-
} catch {
|
|
2552
|
-
return
|
|
2680
|
+
return { ok: true, result: await probe(command, args, options) };
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
return { ok: false, timedOut: isProbeTimeout(error) };
|
|
2553
2683
|
}
|
|
2554
2684
|
}
|
|
2555
2685
|
function resultText(result) {
|
|
@@ -2894,6 +3024,7 @@ function stringRecord(value) {
|
|
|
2894
3024
|
}
|
|
2895
3025
|
export {
|
|
2896
3026
|
ACP_REGISTRY_URL,
|
|
3027
|
+
ACP_REQUEST_TIMEOUT_MESSAGE_PREFIX,
|
|
2897
3028
|
AcpAgentAllowlist,
|
|
2898
3029
|
AcpAgentClient,
|
|
2899
3030
|
AcpAgentClientPool,
|
|
@@ -2904,6 +3035,7 @@ export {
|
|
|
2904
3035
|
BANNED_ACP_REGISTRY_IDS,
|
|
2905
3036
|
BUILT_IN_ACP_STRATEGIES,
|
|
2906
3037
|
DEFAULT_ACP_AGENT_ALLOWLIST,
|
|
3038
|
+
DEFAULT_ACP_PROBE_TIMEOUT_MS,
|
|
2907
3039
|
acpRuntimeSupportsHttpMcp,
|
|
2908
3040
|
acpRuntimeSupportsSessionLoad,
|
|
2909
3041
|
acpRuntimeSupportsSseMcp,
|