dsh-codebuddy-llm 0.0.0-stage → 0.1.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/package.json CHANGED
@@ -1,6 +1,69 @@
1
1
  {
2
2
  "name": "dsh-codebuddy-llm",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Delegating CLI LLM adapter for the DeepSeek Harness: answers each model call by running the CodeBuddy CLI child that carries its own sign-in and agent loop, so a deployment runs without an API key",
5
+ "author": "saisaiwang <287442583@qq.com>",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ws199171/dsh-codebuddy-cli.git"
9
+ },
10
+ "homepage": "https://github.com/ws199171/dsh-codebuddy-cli#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/ws199171/dsh-codebuddy-cli/issues"
13
+ },
14
+ "type": "module",
15
+ "main": "index.js",
16
+ "types": "types/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./types/index.d.ts",
20
+ "default": "./index.js"
21
+ },
22
+ "./cordis.patch.yml": "./cordis.patch.yml",
23
+ "./package.json": "./package.json"
24
+ },
25
+ "files": [
26
+ "index.js",
27
+ "types/**/*.d.ts",
28
+ "cordis.patch.yml",
29
+ "README.md",
30
+ "README.zh.md",
31
+ "LICENSE"
32
+ ],
33
+ "keywords": [
34
+ "dsh",
35
+ "deepseek-harness",
36
+ "cordis",
37
+ "cordis-plugin",
38
+ "codebuddy",
39
+ "llm",
40
+ "cli",
41
+ "adapter"
42
+ ],
43
+ "license": "MIT",
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org/"
47
+ },
48
+ "engines": {
49
+ "dsh": "^0.2.1-rc.3",
50
+ "node": ">=24.9"
51
+ },
52
+ "peerDependencies": {
53
+ "@deepseek-ai/dsh-llm": "^0.2.1-rc.3"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "@deepseek-ai/dsh-llm": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "dependencies": {
61
+ "@deepseek-ai/schemastery": "^3.18.5"
62
+ },
63
+ "dsh": {
64
+ "manifestVersion": 1,
65
+ "bundle": {
66
+ "patch": "./cordis.patch.yml"
67
+ }
68
+ }
69
+ }
package/types/acp.d.ts ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * ACP transport: one long-lived CLI child that carries every delegated run of a
3
+ * route, driven over the Agent Client Protocol (NDJSON on stdin and stdout).
4
+ *
5
+ * The print transport starts a child per model call, so every call pays the
6
+ * CLI's cold start and the answer can only be read from that one run. This
7
+ * transport starts the child once, handshakes, and maps each model call to a
8
+ * `session/prompt` on the session its conversation owns; the answer arrives as
9
+ * `session/update` notifications, which is also where the CLI's own thinking
10
+ * becomes visible instead of staying inside the child.
11
+ *
12
+ * The child is addressed by JSON-RPC framing: requests carry an id this module
13
+ * correlates, notifications carry a session id it routes, and a request the CLI
14
+ * raises for itself — a tool permission — is answered by the configured policy
15
+ * rather than left hanging, because an unanswered request stalls the turn.
16
+ *
17
+ * @module dsh-codebuddy-llm/acp
18
+ */
19
+ import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
20
+ /** One incremental piece of a delegated answer. */
21
+ export type AcpTurnEvent = {
22
+ kind: 'text';
23
+ text: string;
24
+ } | {
25
+ kind: 'thought';
26
+ text: string;
27
+ };
28
+ /**
29
+ * How one delegated turn ended, as the CLI itself reported it. A turn the CLI
30
+ * refused or cancelled still answers `session/prompt`; only this outcome tells
31
+ * the caller that the empty answer is a failure rather than a short reply.
32
+ */
33
+ export interface AcpPromptOutcome {
34
+ /** The ACP stop reason the CLI reported for the turn. */
35
+ readonly stopReason: string;
36
+ /** The CLI's own failure detail reported beside the reason, when it gave one. */
37
+ readonly detail?: string;
38
+ }
39
+ /**
40
+ * The CLI session one conversation is bound to, and whether this child opened it
41
+ * fresh. A lease is what tells the caller whether the CLI already holds the
42
+ * conversation: a fresh session holds none, so the call has to carry the history.
43
+ */
44
+ export interface AcpSessionLease {
45
+ /** Session id the CLI itself assigned. */
46
+ sessionId: string;
47
+ /** Whether this child created the session (as opposed to adopting an existing one). */
48
+ created: boolean;
49
+ }
50
+ /** One model the CLI advertises when it opens a session. */
51
+ export interface AcpCatalogModel {
52
+ /** Model id the CLI accepts for `--model`. */
53
+ readonly id: string;
54
+ /** Display name the CLI reports, when it reports one. */
55
+ readonly name?: string;
56
+ /** Input capacity the CLI discloses for this id, when it discloses one. */
57
+ readonly contextWindow?: number;
58
+ }
59
+ /** One permission option the CLI offers for a tool call it wants to run. */
60
+ export interface AcpPermissionOption {
61
+ /** Identifier the answer selects. */
62
+ id: string;
63
+ /** Protocol-level kind, which is what a policy answers in terms of. */
64
+ kind: string;
65
+ }
66
+ /** What the transport needs to run one route's child. */
67
+ export interface AcpTransportOptions {
68
+ /** The subprocess seam's spawn operation. */
69
+ spawn: (spec: SubprocessSpawnSpec) => SubprocessHandle;
70
+ /** Executable and ACP arguments; a prompt never rides argv here. */
71
+ argv: readonly string[];
72
+ /** Working directory the child itself starts in. */
73
+ cwd: string;
74
+ /** Environment entries layered over the seam's base. */
75
+ env: Record<string, string>;
76
+ /** Grace in milliseconds for child process-tree termination. */
77
+ graceMs: number;
78
+ /**
79
+ * Answer one permission request: the option id to select, or undefined to
80
+ * refuse. Absent refuses every request, which is what a deployment that
81
+ * expects no tool activity wants.
82
+ */
83
+ selectPermission?: (options: readonly AcpPermissionOption[]) => string | undefined;
84
+ /**
85
+ * Ceiling in milliseconds for one prompted turn, or absent for none. The CLI
86
+ * runs its whole agent loop inside one prompt, so this is the only bound the
87
+ * client can put on a run that never converges: on expiry the turn is
88
+ * cancelled and the caller is told it outlived its deadline.
89
+ */
90
+ turnTimeoutMs?: number;
91
+ }
92
+ /** A delegated turn that outlived the connection's deadline. */
93
+ export declare class AcpTurnTimeoutError extends Error {
94
+ /** @param timeoutMs - the deadline the turn exceeded. */
95
+ constructor(timeoutMs: number);
96
+ }
97
+ /**
98
+ * One route's long-lived ACP connection.
99
+ *
100
+ * A transport is started lazily on first use and lives until {@link dispose};
101
+ * sessions are created per harness conversation and reused across its calls,
102
+ * so a second call to the same conversation pays neither a cold start nor a
103
+ * handshake.
104
+ */
105
+ export declare class AcpTransport {
106
+ private readonly options;
107
+ private child;
108
+ private starting;
109
+ private buffer;
110
+ private nextId;
111
+ private disposed;
112
+ private readonly pending;
113
+ private readonly turns;
114
+ private readonly sessions;
115
+ constructor(options: AcpTransportOptions);
116
+ /**
117
+ * The ACP session one conversation is bound to, opened on first use.
118
+ *
119
+ * A conversation this child already serves keeps its session. Otherwise the
120
+ * caller's `adopt` id — the session a previous child created for the same
121
+ * conversation — is loaded through `session/load`, which is what keeps the
122
+ * CLI's own history across a restarted harness or a replaced child. A failed
123
+ * load (the CLI forgot it, or does not support loading) falls back to a fresh
124
+ * session, and the returned lease says so, which is the caller's signal that
125
+ * this call must carry the conversation itself.
126
+ *
127
+ * @param key - the harness conversation this session belongs to.
128
+ * @param cwd - workspace the session runs in.
129
+ * @param adopt - CLI session id a previous child created for this conversation.
130
+ * @returns the session id and whether this child created it.
131
+ */
132
+ openSession(key: string, cwd: string, adopt?: string): Promise<AcpSessionLease>;
133
+ /**
134
+ * Read the model catalog this CLI advertises, by opening the session whose
135
+ * answer carries it. The connection is the caller's to dispose afterwards: a
136
+ * discovery probe wants the catalog and nothing of the session's history.
137
+ * @param cwd - workspace the probe session runs in.
138
+ * @returns one entry per advertised model, with the capacity it discloses.
139
+ */
140
+ describeModels(cwd: string): Promise<readonly AcpCatalogModel[]>;
141
+ /**
142
+ * Ask the child to resume a session a previous child created.
143
+ * @param cwd - workspace the session runs in.
144
+ * @param sessionId - CLI session id to load.
145
+ * @returns whether the child resumed it; a refusal is not this call's failure.
146
+ */
147
+ private loadSession;
148
+ /**
149
+ * Run one model call as a prompt on the session that owns the conversation.
150
+ * @param sessionId - the ACP session from {@link openSession}.
151
+ * @param text - the prompt text this call carries.
152
+ * @param signal - caller cancellation, forwarded as `session/cancel`.
153
+ * @returns the answer's incremental events, and how the CLI ended the turn
154
+ * once they are drained — `cancelled` and `refusal` are outcomes, not errors.
155
+ */
156
+ prompt(sessionId: string, text: string, signal?: AbortSignal): AsyncGenerator<AcpTurnEvent, AcpPromptOutcome>;
157
+ /**
158
+ * End the connection and the child behind it.
159
+ * @returns once the child's process tree is gone.
160
+ */
161
+ dispose(): Promise<void>;
162
+ /** Start the child and handshake once; a second caller joins the first attempt. */
163
+ private ensureStarted;
164
+ private start;
165
+ /** Route one parsed line: a response, a turn update, or a request to answer. */
166
+ private handleLine;
167
+ /** Answer one request the CLI raised, so the turn it belongs to can continue. */
168
+ private answerRequest;
169
+ /** Send one request and await its response. */
170
+ private request;
171
+ /** Send one notification, which expects no answer. */
172
+ private notify;
173
+ private write;
174
+ /** Fail every waiter: the child is gone, so no request can be answered. */
175
+ private failAll;
176
+ }
177
+ //# sourceMappingURL=acp.d.ts.map
@@ -0,0 +1,166 @@
1
+ /**
2
+ * `CliAdapter`: the delegating LLM adapter that answers one model call by
3
+ * running one CLI child process and translating its stream-json output into
4
+ * harness `StreamChunk`s. The configured CLI (CodeBuddy by default) carries its
5
+ * own authentication and runs its own agentic loop with its own tools, so the
6
+ * harness forwards conversation text and receives the final answer: no API key,
7
+ * no harness-side tool loop, and no harness tool vocabulary on the wire.
8
+ *
9
+ * The adapter is transport-only. Executable facts arrive through a thunk
10
+ * resolved once per operation, and the registering plugin owns validation,
11
+ * defaults, and configuration — so a settings change reaches the next call
12
+ * without re-registering this route.
13
+ *
14
+ * @module dsh-codebuddy-llm/adapter
15
+ */
16
+ import { LlmAdapter } from '@deepseek-ai/dsh-llm';
17
+ import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
18
+ import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
19
+ /** One optional model entry the CLI route advertises. */
20
+ export interface CliCatalogModel {
21
+ /** Model id accepted by {@link GenerateOptions.model} for this route. */
22
+ id: string;
23
+ /** Selector label; defaults to {@link id}. */
24
+ name?: string;
25
+ /**
26
+ * Input capacity this id accepts, when the deployment knows it. Compaction and
27
+ * switching need it, and a route that owns no endpoint cannot derive it; the
28
+ * ACP handshake supplies the CLI's own figure where it reports one.
29
+ */
30
+ contextWindow?: number;
31
+ }
32
+ /** The CLI permission modes its non-interactive print command accepts. */
33
+ export declare const CODEBUDDY_PERMISSION_MODES: readonly ["acceptEdits", "bypassPermissions", "default", "plan", "dontAsk", "auto"];
34
+ /** The delegated CLI's policy for approving tools inside its own agent loop. */
35
+ export type CodeBuddyPermissionMode = typeof CODEBUDDY_PERMISSION_MODES[number];
36
+ /** The reasoning effort levels the CLI's own flag accepts. */
37
+ export declare const CODEBUDDY_EFFORT_LEVELS: readonly ["minimal", "low", "medium", "high", "xhigh", "max"];
38
+ /** Reasoning effort the delegated CLI forwards to the model. */
39
+ export type CodeBuddyEffort = typeof CODEBUDDY_EFFORT_LEVELS[number];
40
+ /** The ways this route can reach the CLI. */
41
+ export declare const CLI_TRANSPORTS: readonly ["print", "acp"];
42
+ /**
43
+ * How one call reaches the CLI: `print` starts a child per call and reads its
44
+ * stream-json, `acp` keeps one child per route and prompts a session on it.
45
+ * `print` is the default because it needs nothing of the CLI but its own
46
+ * non-interactive mode.
47
+ */
48
+ export type CliTransport = typeof CLI_TRANSPORTS[number];
49
+ /** Transport a deployment gets without asking for one. */
50
+ export declare const DEFAULT_TRANSPORT: CliTransport;
51
+ /**
52
+ * Validated executable facts for one operation. The plugin's resolve step
53
+ * produces this shape; the adapter trusts it and re-reads it per operation.
54
+ */
55
+ export interface CliConnectionOptions {
56
+ /** Executable and base arguments; `argv[0]` is the program. */
57
+ argv: readonly string[];
58
+ /** Configured working directory, or the process fallback for the CLI child. */
59
+ cwd: string;
60
+ /** Whether a persistent session's own workspace replaces {@link cwd} when one is available. */
61
+ useSessionCwd: boolean;
62
+ /** Explicit environment entries layered over the subprocess seam's base. */
63
+ env: Record<string, string>;
64
+ /** Argument carrying the persistent session id, or undefined for stateless runs. */
65
+ sessionIdArg?: string;
66
+ /** How a call reaches the CLI; see {@link CliTransport}. */
67
+ transport: CliTransport;
68
+ /** The CLI's permission policy for tools its own loop executes. */
69
+ permissionMode: CodeBuddyPermissionMode;
70
+ /** Tool set the CLI restricts itself to; an empty value disables every built-in tool. Absent leaves its own default. */
71
+ tools?: string;
72
+ /** Cap on the CLI's own agentic turns; absent leaves its own default. */
73
+ maxTurns?: number;
74
+ /** Reasoning effort the CLI forwards to the model; absent leaves its own default. */
75
+ effort?: CodeBuddyEffort;
76
+ /** Grace in milliseconds for child process-tree termination. */
77
+ disposeGraceMs: number;
78
+ /**
79
+ * Ceiling in milliseconds for one delegated `acp` turn, or `0` for none. The
80
+ * CLI runs a whole agent loop inside one call, so without a ceiling a run that
81
+ * never converges holds the turn open for as long as the CLI keeps working.
82
+ */
83
+ turnTimeoutMs: number;
84
+ /** Executable and arguments that start the CLI as a long-lived ACP agent. */
85
+ acpArgv: readonly string[];
86
+ /** Executable and arguments that print the CLI's supported model ids. */
87
+ discoveryArgv: readonly string[];
88
+ /** Hard ceiling in milliseconds for one model-discovery child. */
89
+ discoveryTimeoutMs: number;
90
+ /** Advisory models exposed to discovery consumers. */
91
+ models: readonly CliCatalogModel[];
92
+ }
93
+ /** Constructor options for {@link CliAdapter}. */
94
+ export interface CliAdapterOptions {
95
+ /** Current validated executable facts; read once per operation. */
96
+ options: () => CliConnectionOptions;
97
+ /** The subprocess seam's spawn operation. */
98
+ spawn: (spec: SubprocessSpawnSpec) => SubprocessHandle;
99
+ /** Resolve the workspace selected for a persistent harness session. */
100
+ resolveSessionCwd?: (sessionId: NonNullable<GenerateOptions['sessionId']>) => string | undefined;
101
+ }
102
+ /**
103
+ * Delegating CLI adapter: one provider route whose model ids name the CLI's own
104
+ * model selection. Its default transport spawns a child per call, hands it the
105
+ * prompt as its final positional argument, and consumes stream-json on stdout;
106
+ * the ACP transport keeps one child per route and prompts a session on it, so
107
+ * the CLI's cold start is paid once per route instead of once per call.
108
+ */
109
+ export declare class CliAdapter extends LlmAdapter {
110
+ private readonly options;
111
+ private readonly spawn;
112
+ private readonly resolveSessionCwd;
113
+ /**
114
+ * The long-lived ACP connection of each workspace, replaced when what starts
115
+ * its child changes. The CLI takes its workspace from the directory the child
116
+ * starts in rather than from `session/new`, so a workspace needs its own child.
117
+ */
118
+ private readonly acp;
119
+ /**
120
+ * CLI sessions this route opened, by harness conversation. Held here rather
121
+ * than in the transport because a replaced child must be able to resume them:
122
+ * the id names a session in the CLI's own store, not in one child.
123
+ */
124
+ private readonly acpSessions;
125
+ /** Numbers the throwaway sessions a stateless call gets. */
126
+ private statelessCalls;
127
+ constructor(options: CliAdapterOptions);
128
+ providerInfo(provider: string): LlmProviderInfo;
129
+ listModels(provider: string): Promise<readonly LlmModelInfo[]>;
130
+ resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
131
+ stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
132
+ /**
133
+ * Answer one call through the route's long-lived ACP child. The session owns
134
+ * the conversation, so only this call's text is sent — which is also why the
135
+ * transport pays the CLI's cold start once per route instead of once per call.
136
+ * @param facts - resolved executable facts for this operation.
137
+ * @param options - the model call being answered.
138
+ * @param prompt - the text this call carries.
139
+ * @param recap - the conversation before this call's newest human text, sent
140
+ * instead of `prompt` when the CLI session is new and holds no history.
141
+ * @param cwd - workspace the conversation's session runs in.
142
+ * @param signal - caller cancellation, forwarded as a session cancel.
143
+ * @returns the call's chunks, ending with its terminal.
144
+ */
145
+ private streamAcp;
146
+ /**
147
+ * Release the route's long-lived children, if any were ever started. The
148
+ * plugin calls this from the effect that registered the route, so unloading
149
+ * the route unloads its children rather than leaving them to outlive the
150
+ * composition.
151
+ * @returns once every child's process tree is gone.
152
+ */
153
+ dispose(): Promise<void>;
154
+ /**
155
+ * The ACP connection one workspace runs on, started on first use and replaced
156
+ * when the facts that started it change. The model and the workspace ride the
157
+ * process: the CLI takes both as launch facts, so one child serves one model
158
+ * in one workspace.
159
+ * @param facts - resolved executable facts for this operation.
160
+ * @param model - model id this call selected.
161
+ * @param cwd - workspace the conversation's session runs in.
162
+ * @returns the connection to prompt on.
163
+ */
164
+ private acpTransport;
165
+ }
166
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Plugin configuration and the one resolve step from raw config to validated
3
+ * executable facts.
4
+ *
5
+ * Every field is `Volatile`, so a Models-page edit reaches the next request
6
+ * through the loader's volatile update without restarting anything. The resolve
7
+ * step re-judges every default and bound because programmatic construction and
8
+ * a settings snapshot both bypass Schemastery normalization.
9
+ *
10
+ * @module dsh-codebuddy-llm/config
11
+ */
12
+ import type { Volatile } from '@deepseek-ai/cordis';
13
+ import z from '@deepseek-ai/schemastery';
14
+ import type { CliCatalogModel, CliConnectionOptions, CliTransport, CodeBuddyEffort, CodeBuddyPermissionMode } from './adapter.js';
15
+ /** The default CLI executable; a bare name resolves on PATH. */
16
+ export declare const DEFAULT_COMMAND = "codebuddy";
17
+ /**
18
+ * Base arguments producing stream-json on stdout with a trailing positional
19
+ * prompt. Partial messages are requested too: without them the CLI reports a
20
+ * message only once it is complete, so a caller waits out the child's whole
21
+ * agent loop — tool rounds included — before the first character arrives.
22
+ */
23
+ export declare const DEFAULT_ARGS: readonly string[];
24
+ /** The argument that carries the persistent session id. */
25
+ export declare const DEFAULT_SESSION_ID_ARG = "--session-id";
26
+ /** Permission mode that makes a delegated, non-interactive CLI run usable by default. */
27
+ export declare const DEFAULT_PERMISSION_MODE: CodeBuddyPermissionMode;
28
+ /** Default graceful-termination window for a CLI child. */
29
+ export declare const DEFAULT_DISPOSE_GRACE_MS = 3000;
30
+ /** Arguments that start the CLI as a long-lived ACP agent on stdio. */
31
+ export declare const DEFAULT_ACP_ARGS: readonly string[];
32
+ /** Arguments that print the CLI's supported model ids without starting an agent session. */
33
+ export declare const DEFAULT_MODEL_DISCOVERY_ARGS: readonly string[];
34
+ /** Default ceiling for one model-discovery child. */
35
+ export declare const DEFAULT_MODEL_DISCOVERY_TIMEOUT_MS = 15000;
36
+ /**
37
+ * Default ceiling for one delegated `acp` turn. A turn is the CLI's whole agent
38
+ * loop, not one model round, so the bound is generous; it exists to end a run
39
+ * that never converges rather than to pace an ordinary one.
40
+ */
41
+ export declare const DEFAULT_TURN_TIMEOUT_MS = 600000;
42
+ /**
43
+ * Plugin config, validated by the same-named schemastery schema and doubling as
44
+ * the `llm-cli` user-settings section shape. Every field is optional in yml: a
45
+ * missing command falls back to `codebuddy`, and an empty section still
46
+ * registers the route with the defaults.
47
+ */
48
+ export interface Config {
49
+ /** CLI executable; a bare name resolves on PATH. */
50
+ command: Volatile<string>;
51
+ /** Arguments producing stream-json on stdout; the prompt is appended last. */
52
+ args: Volatile<string[]>;
53
+ /** Arguments that start the CLI as a long-lived ACP agent; used by the `acp` transport. */
54
+ acpArgs: Volatile<string[]>;
55
+ /** Arguments that print the CLI's supported model ids without starting an agent session. */
56
+ modelDiscoveryArgs: Volatile<string[]>;
57
+ /** Hard ceiling in milliseconds for one model-discovery child. */
58
+ modelDiscoveryTimeoutMs: Volatile<number>;
59
+ /**
60
+ * Ceiling in milliseconds for one delegated `acp` turn, or `0` for none. The
61
+ * CLI runs its whole agent loop inside one call, so an unbounded turn is a
62
+ * run that can hold the conversation open for as long as it keeps working.
63
+ */
64
+ turnTimeoutMs: Volatile<number>;
65
+ /** Advisory model ids offered beside the discovered ones. */
66
+ models: Volatile<CliCatalogModel[]>;
67
+ /** Child working directory; when absent, a persistent session's workspace wins. */
68
+ cwd: Volatile<string | undefined>;
69
+ /** Explicit environment entries layered over the subprocess seam's base. */
70
+ env: Volatile<Record<string, string>>;
71
+ /** Argument carrying the persistent session id; an empty value disables CLI-side sessions. */
72
+ sessionIdArg: Volatile<string>;
73
+ /**
74
+ * How a call reaches the CLI: `print` starts a child per call, `acp` keeps
75
+ * one child per route and prompts a session on it.
76
+ */
77
+ transport: Volatile<CliTransport>;
78
+ /** The CLI's own tool-approval policy for the delegated run. */
79
+ permissionMode: Volatile<CodeBuddyPermissionMode>;
80
+ /**
81
+ * Tool set the delegated run restricts itself to. An empty value disables
82
+ * every built-in tool, which reduces the CLI to a model call; absent leaves
83
+ * the CLI's own default set.
84
+ */
85
+ tools: Volatile<string | undefined>;
86
+ /**
87
+ * Cap on the CLI's own agentic turns. Absent leaves the CLI's own default,
88
+ * which is what lets it run a whole tool loop inside one call.
89
+ */
90
+ maxTurns: Volatile<number | undefined>;
91
+ /** Reasoning effort the CLI forwards to the model; absent leaves its own default. */
92
+ effort: Volatile<CodeBuddyEffort | undefined>;
93
+ /** Grace in milliseconds for child process-tree termination. */
94
+ disposeGraceMs: Volatile<number>;
95
+ }
96
+ /**
97
+ * Plain config accepted by the resolver: one detached value per option. Derived
98
+ * from {@link Config} so the two cannot drift; each value is a frozen snapshot
99
+ * of a volatile reference, read-only in practice.
100
+ */
101
+ export type Options = {
102
+ [K in keyof Config]?: Config[K] extends Volatile<infer T> ? Exclude<T, undefined> : never;
103
+ };
104
+ /**
105
+ * Read the current value behind every reference of a parsed {@link Config}.
106
+ * Every schema field is volatile, so each entry is a reference by construction.
107
+ * @param config - parsed plugin config.
108
+ * @returns one detached value per option.
109
+ */
110
+ export declare function plainOptions(config: Config): Options;
111
+ /** Fields shared by the plugin entry config and the `llm-cli` settings section. */
112
+ export declare const cliConfigFields: {
113
+ command: z<string, string, "volatile-defined">;
114
+ args: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
115
+ acpArgs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
116
+ modelDiscoveryArgs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
117
+ modelDiscoveryTimeoutMs: z<number, number, "volatile-defined">;
118
+ turnTimeoutMs: z<number, number, "volatile-defined">;
119
+ models: z<NoInfer<CliCatalogModel[]>, NoInfer<CliCatalogModel[]>, "volatile-defined">;
120
+ cwd: z<string, string, "volatile">;
121
+ env: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<string, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<string, string>>, "volatile-defined">;
122
+ sessionIdArg: z<string, string, "volatile-defined">;
123
+ transport: z<"print" | "acp", "print" | "acp", "volatile-defined">;
124
+ permissionMode: z<"acceptEdits" | "bypassPermissions" | "default" | "plan" | "dontAsk" | "auto", "acceptEdits" | "bypassPermissions" | "default" | "plan" | "dontAsk" | "auto", "volatile-defined">;
125
+ tools: z<string, string, "volatile">;
126
+ maxTurns: z<number, number, "volatile">;
127
+ effort: z<"minimal" | "low" | "medium" | "high" | "xhigh" | "max", "minimal" | "low" | "medium" | "high" | "xhigh" | "max", "volatile">;
128
+ disposeGraceMs: z<number, number, "volatile-defined">;
129
+ };
130
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
131
+ command: z<string, string, "volatile-defined">;
132
+ args: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
133
+ acpArgs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
134
+ modelDiscoveryArgs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
135
+ modelDiscoveryTimeoutMs: z<number, number, "volatile-defined">;
136
+ turnTimeoutMs: z<number, number, "volatile-defined">;
137
+ models: z<NoInfer<CliCatalogModel[]>, NoInfer<CliCatalogModel[]>, "volatile-defined">;
138
+ cwd: z<string, string, "volatile">;
139
+ env: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<string, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<string, string>>, "volatile-defined">;
140
+ sessionIdArg: z<string, string, "volatile-defined">;
141
+ transport: z<"print" | "acp", "print" | "acp", "volatile-defined">;
142
+ permissionMode: z<"acceptEdits" | "bypassPermissions" | "default" | "plan" | "dontAsk" | "auto", "acceptEdits" | "bypassPermissions" | "default" | "plan" | "dontAsk" | "auto", "volatile-defined">;
143
+ tools: z<string, string, "volatile">;
144
+ maxTurns: z<number, number, "volatile">;
145
+ effort: z<"minimal" | "low" | "medium" | "high" | "xhigh" | "max", "minimal" | "low" | "medium" | "high" | "xhigh" | "max", "volatile">;
146
+ disposeGraceMs: z<number, number, "volatile-defined">;
147
+ }>>, Schemastery.ObjectT<NoInfer<{
148
+ command: z<string, string, "volatile-defined">;
149
+ args: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
150
+ acpArgs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
151
+ modelDiscoveryArgs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
152
+ modelDiscoveryTimeoutMs: z<number, number, "volatile-defined">;
153
+ turnTimeoutMs: z<number, number, "volatile-defined">;
154
+ models: z<NoInfer<CliCatalogModel[]>, NoInfer<CliCatalogModel[]>, "volatile-defined">;
155
+ cwd: z<string, string, "volatile">;
156
+ env: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<string, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<string, string>>, "volatile-defined">;
157
+ sessionIdArg: z<string, string, "volatile-defined">;
158
+ transport: z<"print" | "acp", "print" | "acp", "volatile-defined">;
159
+ permissionMode: z<"acceptEdits" | "bypassPermissions" | "default" | "plan" | "dontAsk" | "auto", "acceptEdits" | "bypassPermissions" | "default" | "plan" | "dontAsk" | "auto", "volatile-defined">;
160
+ tools: z<string, string, "volatile">;
161
+ maxTurns: z<number, number, "volatile">;
162
+ effort: z<"minimal" | "low" | "medium" | "high" | "xhigh" | "max", "minimal" | "low" | "medium" | "high" | "xhigh" | "max", "volatile">;
163
+ disposeGraceMs: z<number, number, "volatile-defined">;
164
+ }>>, "plain">;
165
+ /** Validated executable facts for one operation, as the adapter reads them. */
166
+ export type ResolvedCliOptions = CliConnectionOptions;
167
+ /**
168
+ * Merge what the CLI last advertised with the ids the deployment declared. The
169
+ * advertised order leads — it is the CLI's own listing, which is what a picker
170
+ * should show — and a declared entry keeps the fields it declares there, so a
171
+ * curated label is not replaced by the bare id, while everything it leaves out
172
+ * still comes from the CLI. Declared ids the CLI did not report follow, so an
173
+ * advisory id stays selectable.
174
+ * @param configured - catalog entries the deployment declared.
175
+ * @param discovered - entries the last probe of the CLI reported.
176
+ * @returns one entry per id, advertised order first.
177
+ */
178
+ export declare function mergeCatalog(configured: readonly CliCatalogModel[], discovered: readonly CliCatalogModel[]): CliCatalogModel[];
179
+ /**
180
+ * The one explicit resolve step from raw config to validated executable facts.
181
+ * @param config - raw plugin config or a resolved settings snapshot.
182
+ * @param defaultCwd - fallback working directory for the CLI child.
183
+ * @returns validated executable facts for the adapter.
184
+ */
185
+ export declare function resolveAdapterOptions(config: Options, defaultCwd: string): ResolvedCliOptions;
186
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Host-side CLI model discovery. It runs the configured CLI's own listing
3
+ * command (default `--help`) through the shared subprocess seam and parses the
4
+ * model ids the CLI reports, so the standard Models page can offer the CLI's
5
+ * real catalog without this package owning a remote surface of its own.
6
+ *
7
+ * Every child here goes through the seam, so discovery inherits process-tree
8
+ * termination and environment scrubbing instead of managing a child itself.
9
+ *
10
+ * @module dsh-codebuddy-llm/discovery
11
+ */
12
+ import type { LlmDiscoveredModel } from '@deepseek-ai/dsh-llm';
13
+ import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
14
+ import type { CliCatalogModel } from './adapter.js';
15
+ /** The ACP connection a route that speaks ACP can be interrogated over. */
16
+ export interface CliAcpDiscovery {
17
+ /** Executable and arguments that start the CLI as a long-lived ACP agent. */
18
+ argv: readonly string[];
19
+ /** Environment entries layered over the seam's base. */
20
+ env: Record<string, string>;
21
+ /** Grace in milliseconds for terminating the probe's process tree. */
22
+ graceMs: number;
23
+ }
24
+ /** One discovery run's inputs, already validated by the resolver. */
25
+ export interface CliDiscoveryRequest {
26
+ /** Executable and arguments producing the model listing on stdout. */
27
+ argv: readonly string[];
28
+ /** Working directory for the discovery child. */
29
+ cwd: string;
30
+ /** Hard ceiling in milliseconds for the listing child. */
31
+ timeoutMs: number;
32
+ /** The route's advisory catalog, offered after the discovered ids. */
33
+ configured: readonly CliCatalogModel[];
34
+ /** The subprocess seam's spawn operation. */
35
+ spawn: (spec: SubprocessSpawnSpec) => SubprocessHandle;
36
+ /**
37
+ * The ACP connection to interrogate instead of the listing command, when the
38
+ * route reaches the CLI over ACP: that handshake carries the capacity of each
39
+ * advertised model, which a listing of ids cannot.
40
+ */
41
+ acp?: CliAcpDiscovery | undefined;
42
+ /** Caller cancellation, honored beside the timeout. */
43
+ signal?: AbortSignal | undefined;
44
+ }
45
+ /**
46
+ * Parse the model ids a CodeBuddy-style CLI lists for its `--model` option.
47
+ * @param help - the CLI's own help output.
48
+ * @returns the unique ids in the CLI's reported order, without display names.
49
+ */
50
+ export declare function parseCodeBuddyModels(help: string): LlmDiscoveredModel[];
51
+ /**
52
+ * Discover the models the configured CLI advertises.
53
+ * @param request - the listing invocation, its bounds, and the route's own catalog.
54
+ * @returns the discovered models first, then configured ids the CLI did not report.
55
+ */
56
+ export declare function discoverCliModels(request: CliDiscoveryRequest): Promise<readonly LlmDiscoveredModel[]>;
57
+ //# sourceMappingURL=discovery.d.ts.map