@qoder-ai/qoder-agent-sdk 1.0.54 → 1.0.56

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.
@@ -1,10 +1,25 @@
1
1
  import type { Readable, Writable } from "node:stream";
2
2
  import { type DaemonCommandResultMap, type DaemonCommandParamsMap, type DaemonControlRequestFrame, type DaemonQueryMethod, type DaemonWireSessionMessage, type JsonObject, type JsonValue, type OpenSessionParams, type RenameSessionResult } from "./protocol.js";
3
3
  import type { SdkAuthPayload } from "../types/auth.js";
4
+ import type { PluginValidationReport } from "../types/plugins.js";
4
5
  import { DaemonSessionTransport, type DaemonSessionTransportOptions } from "./daemon-session-transport.js";
5
6
  import type { DiagnosticsSink } from "../core/sdk-diagnostics.js";
6
7
  export declare const SESSION_STORE_FILE_CHECKPOINTING_CAPABILITY = "session_store_file_checkpointing_v1";
7
8
  export declare const SESSION_STORE_FORK_BARRIER_CAPABILITY = "session_store_fork_barrier_v1";
9
+ /**
10
+ * 心跳判死阈值:30s 无任何入站帧即判 daemon 失活。与 CLI 仓
11
+ * DAEMON_KEEP_ALIVE_INTERVAL_MS(5s 发送周期,本值 = 6 周期)镜像;能力常量
12
+ * DAEMON_KEEP_ALIVE_CAPABILITY 同为两仓镜像,靠握手协商门控——组合不匹配时
13
+ * 最多不武装看门狗,不会误判。
14
+ */
15
+ export declare const DAEMON_HEARTBEAT_TIMEOUT_MS = 30000;
16
+ export declare const DAEMON_KEEP_ALIVE_CAPABILITY = "daemon_keep_alive_v1";
17
+ /**
18
+ * daemon 会话 close 改成「关信道即回包、后台排干 teardown」的能力位(CLI 仓镜像)。
19
+ * 会话 initialize 回包声明它时,QueryRunner.close 不再隐式等 memory / evolution
20
+ * flush;旧 CLI 不声明则保持原有等待语义(版本错配保护)。
21
+ */
22
+ export declare const DAEMON_ASYNC_SESSION_TEARDOWN_CAPABILITY = "daemon_async_session_teardown_v1";
8
23
  export interface DaemonConnectionOptions {
9
24
  input: Writable;
10
25
  output: Readable;
@@ -27,6 +42,11 @@ export interface DaemonConnectionOptions {
27
42
  * 只打协议元数据(方法名、id、耗时、字节数),绝不打帧原文、凭据与 prompt 正文。
28
43
  */
29
44
  diagnostics?: DiagnosticsSink;
45
+ /**
46
+ * 心跳看门狗阈值(ms),缺省 DAEMON_HEARTBEAT_TIMEOUT_MS;0 = 关闭(逃生口)。
47
+ * 仅握手协商出 daemon_keep_alive_v1 时武装;单调钟判定,机器睡眠/唤醒不误杀。
48
+ */
49
+ heartbeatTimeoutMs?: number;
30
50
  }
31
51
  export interface DaemonInitializeOptions {
32
52
  /** Advertised in addition to the capability this SDK cannot work without. */
@@ -38,6 +58,17 @@ export interface DaemonInitializeOptions {
38
58
  * without it. Carries a plaintext credential — never log it.
39
59
  */
40
60
  auth?: SdkAuthPayload;
61
+ /**
62
+ * Process-wide proxy applied during the handshake (`daemon_proxy_v1`).
63
+ * An empty string clears a previously configured proxy.
64
+ */
65
+ proxy?: string;
66
+ /**
67
+ * Proxy bypass list applied during the handshake (`daemon_proxy_bypass_v1`).
68
+ * Replaces `NO_PROXY` / `no_proxy` in the daemon process; an empty string
69
+ * clears both.
70
+ */
71
+ noProxy?: string;
41
72
  }
42
73
  export type DaemonRequestHandler = (request: DaemonControlRequestFrame) => Promise<JsonValue>;
43
74
  export declare class DaemonRpcError extends Error {
@@ -62,13 +93,50 @@ export declare class DaemonRequestError extends Error {
62
93
  details?: JsonObject;
63
94
  });
64
95
  }
96
+ /**
97
+ * A second concurrent Query declared a session id that is already opening or
98
+ * open on this daemon connection. Thrown synchronously from `query()` so the
99
+ * caller sees the conflict before any frame is sent.
100
+ */
101
+ export declare class DaemonSessionAlreadyOpenError extends Error {
102
+ readonly sessionId: string;
103
+ readonly state: "opening" | "open";
104
+ readonly code = "SESSION_ALREADY_OPEN";
105
+ constructor(sessionId: string, state: "opening" | "open");
106
+ }
107
+ /** `close` 进行中的 id 不会拒绝新 Query;凭据里的 ready 在 close 出清后兑现。 */
108
+ export interface DaemonSessionIdClaim {
109
+ readonly sessionId: string;
110
+ readonly ready: Promise<void>;
111
+ }
112
+ /**
113
+ * 心跳看门狗判死:daemon「活着但静默」(事件循环卡死、管道半开、OOM 挂持)时
114
+ * 唯一的判定手段。message 以错误码开头——app 侧按约定取 message 前 120 字符做
115
+ * failureCode,码在开头才可检索。
116
+ */
117
+ export declare class DaemonHeartbeatTimeoutError extends Error {
118
+ readonly code = "DAEMON_HEARTBEAT_TIMEOUT";
119
+ readonly elapsedMs: number;
120
+ constructor(elapsedMs: number);
121
+ }
65
122
  export declare class DaemonConnection {
66
123
  private readonly input;
67
- private readonly requestHandler;
124
+ /**
125
+ * Daemon-level reverse-request handler (the `daemon.auth.*` host-callback bridge).
126
+ * Mutable: `Daemon.auth(payload)` can rebind it when a later payload switches the
127
+ * host-callback arm (jobToken / serviceAccount-token-provider), so the daemon's
128
+ * token fetch for the *new* credentials reaches the right host callback.
129
+ */
130
+ private requestHandler;
68
131
  private readonly pendingRequests;
69
132
  private readonly timedOutRequestIds;
70
133
  private readonly activeInboundRequestIds;
71
134
  private readonly sessions;
135
+ /**
136
+ * 声明 id 的占用表:query() 创建 transport 时同步占位,回包前就能拦住第二个
137
+ * 同 id Query。close 期间到达的 Query 排队等 close 出清,不误杀顺序重开。
138
+ */
139
+ private readonly sessionSlots;
72
140
  private readonly closedSessionIds;
73
141
  /**
74
142
  * 每个 daemon-bound 会话 open 时携带的工作目录(sessionId → cwd)。daemon 已无 cwd
@@ -93,10 +161,59 @@ export declare class DaemonConnection {
93
161
  private readonly failureListeners;
94
162
  private readonly describeUnexpectedClose;
95
163
  private readonly diagnostics;
164
+ /**
165
+ * 心跳看门狗:最近一次入站帧的单调钟时刻(任意入站帧都刷新——有正常会话流量
166
+ * 时天然免检,keep_alive 只是空闲期保底节拍)。单调钟使机器睡眠/唤醒后差值仍小,
167
+ * 不误杀。看门狗定时器与阈值见 armHeartbeatWatchdog。
168
+ */
169
+ private lastInboundAt;
170
+ private heartbeatWatchdog;
171
+ private readonly heartbeatTimeoutMs;
96
172
  constructor(options: DaemonConnectionOptions);
97
173
  initialize(sdkVersion: string, options?: DaemonInitializeOptions): Promise<void>;
98
174
  createSessionTransport(options: DaemonSessionTransportOptions): DaemonSessionTransport;
99
175
  supportsCapability(capability: string): boolean;
176
+ /**
177
+ * Switches the daemon-level (process-wide) proxy, or clears it with
178
+ * `undefined` / `null` / an empty string. Returns the normalized value.
179
+ *
180
+ * `noProxy` omitted leaves the current bypass list unchanged; `null`
181
+ * restores the value inherited at daemon startup; a string replaces both
182
+ * `NO_PROXY` and `no_proxy`.
183
+ */
184
+ setProxy(proxy?: string | null, noProxy?: string | null): Promise<string | null>;
185
+ /**
186
+ * Same as `setProxy`, but also returns the effective bypass list. Gated on
187
+ * `daemon_proxy_bypass_v1` when `noProxy` is supplied so an older CLI cannot
188
+ * silently ignore the field.
189
+ */
190
+ setProxySettings(proxy?: string | null, noProxy?: string | null): Promise<{
191
+ proxy: string | null;
192
+ noProxy: string | null;
193
+ }>;
194
+ /**
195
+ * 同步占用一个声明 id。opening/open 直接抛冲突;closing 则排队,ready 在
196
+ * 前一次 close 出清后兑现。未声明 id(daemon 现 mint)不会撞,调用方不占用。
197
+ */
198
+ claimSessionId(sessionId: string): DaemonSessionIdClaim;
199
+ markSessionOpen(sessionId: string): void;
200
+ markSessionClosing(sessionId: string): void;
201
+ /** open 失败/放弃时出清占位;不向等待者交接(close 才有资格交接)。 */
202
+ releaseSessionClaim(sessionId: string): void;
203
+ private completeSessionClose;
204
+ private advanceSessionSlot;
205
+ /**
206
+ * Statically validates local plugins inside the daemon. Gated on
207
+ * `daemon_validate_plugins_v1` so an older CLI rejects here instead of
208
+ * answering `METHOD_NOT_FOUND`; the caller falls back to the Worker path.
209
+ */
210
+ validatePlugins(paths: readonly string[], configDir?: string): Promise<PluginValidationReport[]>;
211
+ /**
212
+ * Rebinds the daemon-level reverse-request handler. Used by `Daemon.auth(payload)`
213
+ * when the new credentials carry a host-callback arm, so the daemon's token fetch
214
+ * for the overridden credentials is answered by the matching host callback.
215
+ */
216
+ setRequestHandler(handler: DaemonRequestHandler | undefined): void;
100
217
  onFailure(listener: (error: Error) => void): () => void;
101
218
  openSession(params: OpenSessionParams, transport: DaemonSessionTransport): Promise<string>;
102
219
  listSessions(cwd?: string): Promise<import("./protocol.js").DaemonSessionInfo[]>;
@@ -113,6 +230,20 @@ export declare class DaemonConnection {
113
230
  * fails that one query, not the connection.
114
231
  */
115
232
  query<Method extends DaemonQueryMethod>(method: Method, params: DaemonCommandParamsMap[Method]): Promise<DaemonCommandResultMap[Method]>;
233
+ /**
234
+ * Explicitly triggers the daemon's on-demand credential landing
235
+ * (`daemon.authenticate`). The handshake only validated the payload (and auth is
236
+ * optional there), so this is the host-facing way to land credentials up front.
237
+ *
238
+ * `payload` omitted → authenticate with the credentials the daemon already holds.
239
+ * `payload` present → override the held credentials, then authenticate. Idempotent
240
+ * on the CLI side for the no-payload form.
241
+ *
242
+ * Gated on `daemon_queries_v1` (always advertised by a CLI that speaks this
243
+ * protocol); an old CLI that lacks the method rejects here with a clear protocol
244
+ * error instead of a bare METHOD_NOT_FOUND frame.
245
+ */
246
+ authenticate(payload?: SdkAuthPayload): Promise<DaemonCommandResultMap["authenticate"]>;
116
247
  shutdown(timeoutMs?: number): Promise<void>;
117
248
  /**
118
249
  * EOF 落定前问一次进程侧:对端是不是已经退了、为什么退。
@@ -120,6 +251,14 @@ export declare class DaemonConnection {
120
251
  */
121
252
  private resolveUnexpectedClose;
122
253
  fail(error: unknown, notifyListeners?: boolean): void;
254
+ /**
255
+ * 心跳看门狗:每 5s 检查一次距最近入站帧的单调钟差值,超阈值即判 daemon 失活。
256
+ * 判定后走既有 fail() 全量传播(在途请求 reject、在途会话流 error 终止、后续调用
257
+ * 立即 reject、failureListeners 通知)——「对外抛错」不需要新机制。
258
+ * 定时器 unref:看门狗是宿主的判定器,不该替宿主保活进程。
259
+ */
260
+ private armHeartbeatWatchdog;
261
+ private disarmHeartbeatWatchdog;
123
262
  private request;
124
263
  private readOutput;
125
264
  private acceptFrame;
@@ -1,12 +1,14 @@
1
1
  import type { Transport } from '../core/transport.js';
2
2
  import type { StdoutMessage } from '../types/control.js';
3
3
  import type { DaemonWireSessionMessage } from './protocol.js';
4
- import type { DaemonConnection } from './daemon-connection.js';
4
+ import type { DaemonConnection, DaemonSessionIdClaim } from './daemon-connection.js';
5
5
  import type { DiagnosticsSink } from '../core/sdk-diagnostics.js';
6
6
  export type DaemonSessionTransportOptions = {
7
7
  workerArgs: string[];
8
8
  cwd?: string;
9
9
  configDir?: string;
10
+ /** query() 创建时按声明 id 占到的位置;close 进行中时先等 ready。 */
11
+ claim?: DaemonSessionIdClaim;
10
12
  cleanup?: () => void;
11
13
  /**
12
14
  * 由 DaemonConnection.createSessionTransport 注入,调用方不需要传。
@@ -2,11 +2,16 @@ import type { InternalAuthOptions } from '../types/auth.js';
2
2
  import { type Daemon } from './start-daemon.js';
3
3
  export interface InitDaemonOptions {
4
4
  auth: InternalAuthOptions;
5
- /** Idle Worker timeout in milliseconds. Set to 0 to disable reclamation. */
6
- workerIdleTimeoutMs?: number;
7
- cwd?: string;
8
5
  pathToQoderCLIExecutable?: string;
6
+ /** 语义同 `StartDaemonOptions.executable`。 */
7
+ executable?: string;
8
+ /** 语义同 `StartDaemonOptions.executableArgs`。 */
9
+ executableArgs?: string[];
9
10
  proxy?: string;
11
+ /** 语义同 `StartDaemonOptions.noProxy`。 */
12
+ noProxy?: string;
13
+ /** 语义同 `StartDaemonOptions.vpcEndpoint`。 */
14
+ vpcEndpoint?: string;
10
15
  env?: Record<string, string | undefined>;
11
16
  /** 语义同 `StartDaemonOptions.stderr`:daemon 子进程 stderr 的实时回调。 */
12
17
  stderr?: (data: string) => void;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * JSON.stringify leaves U+2028/U+2029 raw inside strings. They are valid JSON,
3
+ * but Node's readline treats them as line breaks, which splits one NDJSON frame
4
+ * into several. Escape them so every reader that only honours "\n" stays compatible.
5
+ */
6
+ export declare function escapeJsonLineSeparators(line: string): string;
7
+ /**
8
+ * Split an NDJSON stream on "\n" only. CRLF is accepted by stripping the
9
+ * trailing "\r"; U+2028/U+2029 remain content and are never treated as
10
+ * terminators.
11
+ */
12
+ export declare function readNdjsonLines(input: AsyncIterable<Buffer | string>): AsyncGenerator<string>;
@@ -5,15 +5,36 @@
5
5
  */
6
6
  import type { BYOKModelValidationInput, BYOKProviderInfo, ByokConfigCheckResult, ByokConfigInfo, ByokConfigReference, CheckByokConfigInput, CreateByokConfigInput, DeleteByokConfigInput, UpdateByokConfigInput } from "../protocol/control.js";
7
7
  import type { AccountInfo, ModelInfo, SessionCreditsUsage, UsageInfo } from "../protocol/common.js";
8
- import type { PluginDetails } from "../types/plugins.js";
8
+ import type { PluginDetails, PluginValidationReport } from "../types/plugins.js";
9
9
  import type { SdkAuthPayload } from "../types/auth.js";
10
10
  export declare const DAEMON_PROTOCOL_VERSION: 2;
11
11
  /**
12
12
  * Reported by the daemon in its initialize result once it accepted an `auth`
13
- * payload, i.e. when the account-level query plane is usable on this
14
- * connection. Absent for a daemon that only manages Sessions.
13
+ * payload, i.e. when the account-level query plane (and the explicit
14
+ * `daemon.authenticate` trigger) is available on this connection. Acceptance is
15
+ * shape validation only — the credentials land on demand, so queries before the
16
+ * first landing may answer `AUTH_REQUIRED`. Absent for a daemon that only
17
+ * manages Sessions.
15
18
  */
16
19
  export declare const DAEMON_QUERIES_CAPABILITY = "daemon_queries_v1";
20
+ /**
21
+ * The daemon can statically validate local plugins in-process
22
+ * (`daemon.validate_plugins`), so the SDK can skip spawning a Worker for
23
+ * plugin validation. Absent on older CLIs; callers must fall back.
24
+ */
25
+ export declare const DAEMON_VALIDATE_PLUGINS_CAPABILITY = "daemon_validate_plugins_v1";
26
+ /**
27
+ * The daemon can apply a process-wide proxy during the handshake (`initialize`)
28
+ * and switch it later via `daemon.setproxy`.
29
+ */
30
+ export declare const DAEMON_PROXY_CAPABILITY = "daemon_proxy_v1";
31
+ /**
32
+ * The daemon accepts a `no_proxy` bypass list alongside the process-wide proxy
33
+ * (`initialize` and `daemon.setproxy`), and applies it to its own environment.
34
+ * Older CLIs ignore the field, so callers must gate on this capability instead
35
+ * of assuming the bypass list was applied.
36
+ */
37
+ export declare const DAEMON_PROXY_BYPASS_CAPABILITY = "daemon_proxy_bypass_v1";
17
38
  export type JsonPrimitive = string | number | boolean | null;
18
39
  export type JsonValue = JsonPrimitive | JsonValue[] | {
19
40
  [key: string]: JsonValue;
@@ -64,12 +85,26 @@ export interface DaemonSessionUpdatedStatusEventFrame {
64
85
  export interface KeepAliveFrame {
65
86
  type: "keep_alive";
66
87
  }
88
+ /**
89
+ * Connection-level fatal signal emitted by the CLI when its NDJSON reader
90
+ * cannot recover the frame boundary (for example one user message split into
91
+ * multiple lines). It carries metadata only, never frame content.
92
+ */
93
+ export interface DaemonProtocolErrorFrame {
94
+ type: "protocol_error";
95
+ code: string;
96
+ message?: string;
97
+ malformed_lines?: number;
98
+ malformed_bytes?: number;
99
+ last_frame_type?: string;
100
+ last_frame_session_id?: string;
101
+ }
67
102
  /** A raw Query message carrying a daemon-local Session route. */
68
103
  export type DaemonWireSessionMessage = JsonObject & {
69
104
  type: string;
70
105
  session_id?: string;
71
106
  };
72
- export type DaemonProtocolFrame = DaemonControlRequestFrame | DaemonControlResponseFrame | DaemonSessionUpdatedStatusEventFrame | KeepAliveFrame | DaemonWireSessionMessage;
107
+ export type DaemonProtocolFrame = DaemonControlRequestFrame | DaemonControlResponseFrame | DaemonSessionUpdatedStatusEventFrame | KeepAliveFrame | DaemonProtocolErrorFrame | DaemonWireSessionMessage;
73
108
  export type InitializeDaemonParams = {
74
109
  daemon_protocol_version: typeof DAEMON_PROTOCOL_VERSION;
75
110
  sdk_version: string;
@@ -83,6 +118,17 @@ export type InitializeDaemonParams = {
83
118
  * message.
84
119
  */
85
120
  auth?: SdkAuthPayload;
121
+ /**
122
+ * Process-wide proxy applied during the handshake (`daemon_proxy_v1`). An
123
+ * empty string clears a previously configured proxy.
124
+ */
125
+ proxy?: string;
126
+ /**
127
+ * Proxy bypass list applied during the handshake (`daemon_proxy_bypass_v1`).
128
+ * Replaces `NO_PROXY` / `no_proxy` in the daemon process; an empty string
129
+ * clears both.
130
+ */
131
+ no_proxy?: string;
86
132
  };
87
133
  export type InitializeDaemonResult = {
88
134
  daemon_protocol_version: typeof DAEMON_PROTOCOL_VERSION;
@@ -138,6 +184,31 @@ export type DeleteSessionParams = {
138
184
  cwd?: string;
139
185
  };
140
186
  export type ShutdownParams = Record<string, never>;
187
+ /**
188
+ * Explicit auth trigger (`daemon.authenticate`, surfaced as `Daemon.auth(payload?)`).
189
+ * The initialize handshake only validates the credential payload (and auth is now
190
+ * optional there); the CLI lands it on demand — the first auth-needing consumer
191
+ * (this method, an account-level query, or a session's inference readiness) starts
192
+ * the login.
193
+ *
194
+ * `auth` is optional:
195
+ * - omitted → authenticate with the credentials the daemon already holds (from
196
+ * `startDaemon`); rejected with `AUTH_PAYLOAD_REQUIRED` if it holds none.
197
+ * - present → **override** the held credentials with this payload, then authenticate
198
+ * (supports supplying credentials to a daemon started without auth, and switching
199
+ * accounts on an already-authenticated daemon).
200
+ */
201
+ export type DaemonAuthenticateParams = {
202
+ auth?: SdkAuthPayload;
203
+ };
204
+ export type DaemonAuthenticateResult = {
205
+ /**
206
+ * False is a non-fatal "no credential" outcome (e.g. the read-only borrow of
207
+ * the local qodercli login found nothing). Rejected credentials arrive as an
208
+ * error frame instead, never as `authenticated: false`.
209
+ */
210
+ authenticated: boolean;
211
+ };
141
212
  /**
142
213
  * Account-level query plane (`daemon_queries_v1`).
143
214
  *
@@ -202,6 +273,32 @@ export type DaemonListPluginsParams = Record<string, never>;
202
273
  export type DaemonListPluginsResult = {
203
274
  plugins: PluginDetails[];
204
275
  };
276
+ /**
277
+ * Static, session-less plugin validation. `config_dir` is the base directory
278
+ * for relative paths, mirroring the Worker path's `QODER_CONFIG_DIR` context.
279
+ */
280
+ export type DaemonValidatePluginsParams = {
281
+ paths: string[];
282
+ config_dir?: string;
283
+ };
284
+ export type DaemonValidatePluginsResult = {
285
+ reports: PluginValidationReport[];
286
+ };
287
+ export type DaemonSetProxyParams = {
288
+ /** `null` clears the daemon-level proxy. */
289
+ proxy: string | null;
290
+ /**
291
+ * Replaces the daemon's `NO_PROXY` / `no_proxy`. `null` restores the value
292
+ * inherited at daemon startup; an empty string clears both. Omitted leaves
293
+ * the current bypass list unchanged.
294
+ */
295
+ no_proxy?: string | null;
296
+ };
297
+ export type DaemonSetProxyResult = {
298
+ proxy: string | null;
299
+ /** Present only when the request carried `no_proxy`. */
300
+ no_proxy?: string | null;
301
+ };
205
302
  export type DaemonGetUsageInfoParams = Record<string, never>;
206
303
  export type DaemonGetUsageInfoResult = {
207
304
  usage: UsageInfo | null;
@@ -213,7 +310,7 @@ export type DaemonAccountInfoParams = Record<string, never>;
213
310
  export type DaemonAccountInfoResult = {
214
311
  account: AccountInfo;
215
312
  };
216
- export type DaemonCommandMethod = "initialize" | "open_session" | "list_sessions" | "rename_session" | "close_session" | "delete_session" | "shutdown" | DaemonQueryMethod;
313
+ export type DaemonCommandMethod = "initialize" | "open_session" | "list_sessions" | "rename_session" | "close_session" | "delete_session" | "shutdown" | "authenticate" | "validate_plugins" | "setproxy" | DaemonQueryMethod;
217
314
  export interface DaemonCommandParamsMap {
218
315
  initialize: InitializeDaemonParams;
219
316
  open_session: OpenSessionParams;
@@ -222,6 +319,7 @@ export interface DaemonCommandParamsMap {
222
319
  close_session: CloseSessionParams;
223
320
  delete_session: DeleteSessionParams;
224
321
  shutdown: ShutdownParams;
322
+ authenticate: DaemonAuthenticateParams;
225
323
  get_models: DaemonGetModelsParams;
226
324
  get_byok_config: DaemonGetByokConfigParams;
227
325
  validate_byok_model: DaemonValidateByokModelParams;
@@ -231,10 +329,12 @@ export interface DaemonCommandParamsMap {
231
329
  delete_byok_config: DaemonDeleteByokConfigParams;
232
330
  check_byok_config: DaemonCheckByokConfigParams;
233
331
  list_plugins: DaemonListPluginsParams;
332
+ validate_plugins: DaemonValidatePluginsParams;
333
+ setproxy: DaemonSetProxyParams;
234
334
  get_usage_info: DaemonGetUsageInfoParams;
235
335
  account_info: DaemonAccountInfoParams;
236
336
  }
237
- export type DaemonCommandRequest = DaemonControlRequestFrame<"initialize", InitializeDaemonParams> | DaemonControlRequestFrame<"open_session", OpenSessionParams> | DaemonControlRequestFrame<"list_sessions", ListSessionsParams> | DaemonControlRequestFrame<"rename_session", RenameSessionParams> | DaemonControlRequestFrame<"close_session", CloseSessionParams> | DaemonControlRequestFrame<"delete_session", DeleteSessionParams> | DaemonControlRequestFrame<"shutdown", ShutdownParams> | DaemonControlRequestFrame<"get_models", DaemonGetModelsParams> | DaemonControlRequestFrame<"get_byok_config", DaemonGetByokConfigParams> | DaemonControlRequestFrame<"validate_byok_model", DaemonValidateByokModelParams> | DaemonControlRequestFrame<"list_byok_configs", DaemonListByokConfigsParams> | DaemonControlRequestFrame<"create_byok_config", DaemonCreateByokConfigParams> | DaemonControlRequestFrame<"update_byok_config", DaemonUpdateByokConfigParams> | DaemonControlRequestFrame<"delete_byok_config", DaemonDeleteByokConfigParams> | DaemonControlRequestFrame<"check_byok_config", DaemonCheckByokConfigParams> | DaemonControlRequestFrame<"list_plugins", DaemonListPluginsParams> | DaemonControlRequestFrame<"get_usage_info", DaemonGetUsageInfoParams> | DaemonControlRequestFrame<"account_info", DaemonAccountInfoParams>;
337
+ export type DaemonCommandRequest = DaemonControlRequestFrame<"initialize", InitializeDaemonParams> | DaemonControlRequestFrame<"open_session", OpenSessionParams> | DaemonControlRequestFrame<"list_sessions", ListSessionsParams> | DaemonControlRequestFrame<"rename_session", RenameSessionParams> | DaemonControlRequestFrame<"close_session", CloseSessionParams> | DaemonControlRequestFrame<"delete_session", DeleteSessionParams> | DaemonControlRequestFrame<"shutdown", ShutdownParams> | DaemonControlRequestFrame<"authenticate", DaemonAuthenticateParams> | DaemonControlRequestFrame<"get_models", DaemonGetModelsParams> | DaemonControlRequestFrame<"get_byok_config", DaemonGetByokConfigParams> | DaemonControlRequestFrame<"validate_byok_model", DaemonValidateByokModelParams> | DaemonControlRequestFrame<"list_byok_configs", DaemonListByokConfigsParams> | DaemonControlRequestFrame<"create_byok_config", DaemonCreateByokConfigParams> | DaemonControlRequestFrame<"update_byok_config", DaemonUpdateByokConfigParams> | DaemonControlRequestFrame<"delete_byok_config", DaemonDeleteByokConfigParams> | DaemonControlRequestFrame<"check_byok_config", DaemonCheckByokConfigParams> | DaemonControlRequestFrame<"list_plugins", DaemonListPluginsParams> | DaemonControlRequestFrame<"validate_plugins", DaemonValidatePluginsParams> | DaemonControlRequestFrame<"setproxy", DaemonSetProxyParams> | DaemonControlRequestFrame<"get_usage_info", DaemonGetUsageInfoParams> | DaemonControlRequestFrame<"account_info", DaemonAccountInfoParams>;
238
338
  export interface DaemonCommandResultMap {
239
339
  initialize: InitializeDaemonResult;
240
340
  open_session: OpenSessionResult;
@@ -243,6 +343,7 @@ export interface DaemonCommandResultMap {
243
343
  close_session: null;
244
344
  delete_session: null;
245
345
  shutdown: null;
346
+ authenticate: DaemonAuthenticateResult;
246
347
  get_models: DaemonGetModelsResult;
247
348
  get_byok_config: DaemonGetByokConfigResult;
248
349
  validate_byok_model: DaemonValidateByokModelResult;
@@ -252,6 +353,8 @@ export interface DaemonCommandResultMap {
252
353
  delete_byok_config: null;
253
354
  check_byok_config: DaemonCheckByokConfigResult;
254
355
  list_plugins: DaemonListPluginsResult;
356
+ validate_plugins: DaemonValidatePluginsResult;
357
+ setproxy: DaemonSetProxyResult;
255
358
  get_usage_info: DaemonGetUsageInfoResult;
256
359
  account_info: DaemonAccountInfoResult;
257
360
  }
@@ -4,7 +4,7 @@ import type { QueryTransportProvider } from '../core/transport.js';
4
4
  import type { DaemonGetModelsParams, DaemonValidateByokModelParams } from './protocol.js';
5
5
  import type { BYOKProviderInfo, ByokConfigCheckResult, ByokConfigInfo, ByokConfigReference, CheckByokConfigInput, CreateByokConfigInput, DeleteByokConfigInput, UpdateByokConfigInput } from '../protocol/control.js';
6
6
  import type { AccountInfo, ModelInfo, UsageInfo } from '../protocol/common.js';
7
- import type { PluginDetails } from '../types/plugins.js';
7
+ import type { PluginDetails, PluginValidationReport } from '../types/plugins.js';
8
8
  export type DaemonState = 'ready' | 'stopping' | 'stopped' | 'failed';
9
9
  export type SessionRuntimeState = 'pending' | 'ready' | 'inactive' | 'failed';
10
10
  export interface DaemonSessionInfo {
@@ -16,21 +16,58 @@ export interface DaemonSessionInfo {
16
16
  updatedAt: string;
17
17
  }
18
18
  export interface StartDaemonOptions {
19
- auth: InternalAuthOptions;
20
- /** Idle Worker timeout in milliseconds. Set to 0 to disable reclamation. */
21
- workerIdleTimeoutMs?: number;
19
+ /**
20
+ * Credentials for the daemon's own account plane and its in-process Sessions.
21
+ *
22
+ * **Optional.** When omitted the daemon starts in the "no credentials" state:
23
+ * the handshake does no identity work, and auth-needing calls answer
24
+ * `AUTH_PAYLOAD_REQUIRED` until you supply credentials via `daemon.auth(payload)`.
25
+ * When provided it is validated (shape) at the handshake and landed lazily on the
26
+ * first auth-needing consumer — never blocking `startDaemon` on the login network.
27
+ */
28
+ auth?: InternalAuthOptions;
22
29
  /**
23
30
  * Budget for the `daemon.initialize` round trip; defaults to 30s.
24
31
  *
25
- * The CLI lands the in-band credentials while handling initialize — token
26
- * exchange, user info, a possible host callback for the first token, and the
27
- * account tier prefill — so the wall time follows the host's network and the
28
- * CLI's own startup cost. Raise this when either is slow; initialize only
29
- * resolves once that work is complete.
32
+ * The handshake only validates the credential payload (when `auth` is given at
33
+ * all — it is optional) and installs the session plane; no auth network happens
34
+ * inside it. The CLI lands the credentials **on demand**: the first auth-needing
35
+ * call (an account-level query, a session's first inference, or an explicit
36
+ * `daemon.auth()`) starts the login. This budget therefore covers CLI startup and
37
+ * the handshake I/O only. Note that host-callback credential requests
38
+ * (`daemon.auth.fetch_job_token` / `fetch_service_account_token`) can arrive
39
+ * at any later point, whenever something first triggers the landing.
30
40
  */
31
41
  initializeTimeoutMs?: number;
32
42
  pathToQoderCLIExecutable?: string;
43
+ /**
44
+ * 运行 CLI 脚本入口的解释器(node/bun/deno 等),语义与 `query()` 的
45
+ * `Options.executable` 一致:只在解析出的 CLI 入口是脚本文件(.js/.mjs/.ts/...)
46
+ * 时生效,原生二进制忽略它。缺省用宿主 SDK 的 `process.execPath`——原生平台
47
+ * 把随包 Node 传在这里(`executable: runtime.interpreterPath` 配
48
+ * `pathToQoderCLIExecutable: runtime.executablePath`)。
49
+ */
50
+ executable?: string;
51
+ /** 解释器参数,插在 CLI 脚本路径之前;语义同 `Options.executableArgs`。 */
52
+ executableArgs?: string[];
53
+ /**
54
+ * daemon 进程级代理:握手时随 `daemon.initialize` 写入,作用于该 daemon 的
55
+ * 账号面与所有会话(`daemon_proxy_v1`)。运行期可用 `daemon.setProxy()` 切换。
56
+ */
33
57
  proxy?: string;
58
+ /**
59
+ * daemon 进程级代理绕过名单:握手时随 `daemon.initialize` 写入
60
+ * `NO_PROXY` / `no_proxy`(`daemon_proxy_bypass_v1`)。空串表示清空;
61
+ * 运行期可用 `daemon.setProxy(proxy, { noProxy })` 切换或恢复。
62
+ */
63
+ noProxy?: string;
64
+ /**
65
+ * VPC private-deployment endpoint for the daemon process (CN deployments only).
66
+ * Written to `QODERCN_VPC_ENDPOINT` / `QODER_VPC_ENDPOINT` before spawn; the CLI
67
+ * reads that environment variable ahead of `settings.json`'s vpcInstanceName.
68
+ * Sessions share the daemon process, so this is not a per-Query option.
69
+ */
70
+ vpcEndpoint?: string;
34
71
  env?: Record<string, string | undefined>;
35
72
  /**
36
73
  * daemon 子进程 stderr 的实时回调。
@@ -53,6 +90,11 @@ export interface StartDaemonOptions {
53
90
  }
54
91
  export interface Daemon {
55
92
  readonly state: DaemonState;
93
+ /**
94
+ * 订阅连接/进程级失败(与 DaemonImpl 内部 acceptDaemonFailure 同源)。全局槽位靠它
95
+ * 感知「活跃 daemon 已死」并触发重建;普通调用方不需要订阅。返回取消订阅句柄。
96
+ */
97
+ onFailure(listener: (error: Error) => void): () => void;
56
98
  /**
57
99
  * 列出会话。`cwd` 给定时只列该工作目录名下的会话;缺省时返回所有目录的全部会话
58
100
  * (daemon 无 cwd 概念,「不传目录」即「不过滤」,由 CLI 侧全量扫盘)。
@@ -66,10 +108,64 @@ export interface Daemon {
66
108
  /** 删除会话。`cwd` 语义同 renameSession。 */
67
109
  deleteSession(localSessionId: string, cwd?: string): Promise<void>;
68
110
  /**
69
- * Account-level queries, answered from the credentials the daemon was
70
- * started with — never from a Session's. They require a daemon that accepted
71
- * `StartDaemonOptions.auth` and reported `daemon_queries_v1`; otherwise they
72
- * reject.
111
+ * Set or clear the daemon-level (process-wide) proxy for every Session.
112
+ * `undefined` / `null` / an empty string clears it. Requires
113
+ * `daemon_proxy_v1`; resolves with the normalized proxy (or null).
114
+ *
115
+ * `options.noProxy` replaces the daemon's bypass list (`null` restores the
116
+ * value inherited at startup, an empty string clears it). Supplying it
117
+ * requires `daemon_proxy_bypass_v1`; omitting it leaves the current list
118
+ * unchanged.
119
+ */
120
+ setProxy(proxy?: string | null, options?: {
121
+ noProxy?: string | null;
122
+ }): Promise<string | null>;
123
+ /**
124
+ * The daemon-level proxy currently in effect (`null` when unset/cleared, or
125
+ * when the CLI does not support `daemon_proxy_v1`).
126
+ */
127
+ getProxy(): string | null;
128
+ /**
129
+ * The daemon-level proxy bypass list currently tracked by the SDK (`null`
130
+ * when unset/cleared or when the CLI does not support
131
+ * `daemon_proxy_bypass_v1`).
132
+ */
133
+ getNoProxy(): string | null;
134
+ /**
135
+ * Explicitly lands credentials and reports whether the daemon is authenticated.
136
+ *
137
+ * The `daemon.initialize` handshake only *validates* the credential payload (and
138
+ * `StartDaemonOptions.auth` is optional); the CLI lands it **on demand** — the
139
+ * first auth-needing call (an account-level query, or a session's first inference)
140
+ * triggers the login implicitly.
141
+ *
142
+ * - `auth()` (no argument): authenticate with the credentials the daemon already
143
+ * holds (from `startDaemon`). Rejects with `AUTH_PAYLOAD_REQUIRED` if it holds
144
+ * none. Idempotent: concurrent and repeated calls share one landing.
145
+ * - `auth(payload)`: **override** the held credentials with `payload`, then
146
+ * authenticate. Use it to supply credentials to a daemon started without auth,
147
+ * or to switch accounts on an already-authenticated daemon. A host-callback arm
148
+ * (`jobToken` / `serviceAccount` token provider) rebinds the reverse-request
149
+ * bridge so the daemon's token fetch reaches the new callback. The override also
150
+ * updates the credentials a later `query({ daemon })` reuses.
151
+ *
152
+ * Resolves `false` for a non-fatal "no credential" outcome (e.g. the read-only
153
+ * borrow of the local qodercli login found nothing). Rejected credentials reject
154
+ * this promise with a `DaemonRpcError` carrying the CLI's error code — never a
155
+ * silent `false`.
156
+ */
157
+ auth(payload?: InternalAuthOptions): Promise<boolean>;
158
+ /**
159
+ * Account-level queries, answered from the daemon's own credentials — never
160
+ * from a Session's. `daemon_queries_v1` is always negotiated, so these are
161
+ * always callable; each answer depends on the daemon's auth state:
162
+ * authenticated → the data; pending (holds credentials, not yet landed) →
163
+ * triggers the landing then answers; no credentials → `AUTH_PAYLOAD_REQUIRED`
164
+ * (call `auth(payload)` first).
165
+ *
166
+ * Auth-needing queries trigger the credential landing on demand, so the first
167
+ * one may be as slow as a login; `getModels`' cache path answers without
168
+ * credentials and does not trigger it.
73
169
  *
74
170
  * Unlike the per-`Query` equivalents these do not swallow failures into
75
171
  * `null`: capability is already negotiated here, so a rejection is a real
@@ -84,6 +180,19 @@ export interface Daemon {
84
180
  deleteByokConfig(target: DeleteByokConfigInput): Promise<void>;
85
181
  checkByokConfig(input: CheckByokConfigInput): Promise<ByokConfigCheckResult>;
86
182
  listPlugins(): Promise<PluginDetails[]>;
183
+ /**
184
+ * Whether the daemon advertised `capability` in the initialize result.
185
+ * Capabilities are negotiated once per connection and never change.
186
+ */
187
+ supportsCapability(capability: string): boolean;
188
+ /**
189
+ * Statically validates local plugins in-process (no Session, no auth).
190
+ * Requires `daemon_validate_plugins_v1`; older daemons reject with a
191
+ * `DaemonProtocolError` and callers should fall back to the Worker path.
192
+ */
193
+ validatePlugins(paths: readonly string[], options?: {
194
+ configDir?: string;
195
+ }): Promise<PluginValidationReport[]>;
87
196
  getUsageInfo(): Promise<UsageInfo | null>;
88
197
  accountInfo(): Promise<AccountInfo>;
89
198
  stop(): Promise<void>;
@@ -92,10 +201,33 @@ interface InternalDaemonRuntime {
92
201
  /** Local CLI launch context for feedback that bypasses the daemon. */
93
202
  readonly hostActionLaunch: {
94
203
  proxy?: string;
204
+ /**
205
+ * `undefined` uses the env as-is; `null` also uses the env (the CLI
206
+ * restored its startup value); a string overrides `NO_PROXY` / `no_proxy`
207
+ * for the one-shot CLI launch.
208
+ */
209
+ noProxy?: string | null;
95
210
  cliPath?: string;
96
211
  env?: Record<string, string | undefined>;
97
212
  };
98
- readonly auth: InternalAuthOptions;
213
+ /**
214
+ * Credentials a daemon-bound `query()` reuses for its Session. Optional: a daemon
215
+ * started without auth has none until `daemon.auth(payload)` supplies them — that
216
+ * call updates this field so a later `query({ daemon })` sees the credentials
217
+ * instead of tripping query()'s "auth is required" client-side gate. Mutable for
218
+ * exactly that reason.
219
+ */
220
+ auth?: InternalAuthOptions;
221
+ /**
222
+ * The daemon-level proxy currently applied by the CLI (`null` when unset or
223
+ * when the CLI ignored the handshake proxy). Updated by `setProxy`.
224
+ */
225
+ proxy: string | null;
226
+ /**
227
+ * The daemon-level proxy bypass list currently applied by the CLI (`null`
228
+ * when unset, or when the CLI does not support bypass updates).
229
+ */
230
+ noProxy: string | null;
99
231
  readonly transportProvider: QueryTransportProvider<ProcessTransportOptions>;
100
232
  }
101
233
  export declare function getDaemonRuntime(daemon: Daemon): InternalDaemonRuntime | undefined;