@vibedgc/sdk 0.6.4

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/client.js ADDED
@@ -0,0 +1,472 @@
1
+ /** Public DGC client. Mirrors sdk/python/dgc_sdk/client.py. */
2
+ import { closeSync, constants as fsConstants, openSync, realpathSync, rmSync, statSync, unlinkSync, writeSync, } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { randomUUID } from "node:crypto";
6
+ import { AuditLog, rememberSecret } from "./audit.js";
7
+ import { DGCCommandRejectedError, DGCConfigError, DGCError, DGCProtocolError, DGCRuntimeError, publicError, } from "./errors.js";
8
+ import { Policy, compileSession, permissionSettings, sandboxPrecheck, sandboxRequirement, toPolicy, } from "./policy.js";
9
+ import { defaultRuntime, isolatedEnv } from "./runtime.js";
10
+ import { Session } from "./session.js";
11
+ import { configDrift, prepareStateDir, stateLock, writeSessionConfig } from "./state.js";
12
+ import { installTools } from "./tools.js";
13
+ import { Transport } from "./transport.js";
14
+ import { UsageLog } from "./usage.js";
15
+ import { PROTOCOL, REQUIRES_CLI, VERSION, } from "./types.js";
16
+ const CONFIG_ATTEMPTS = 3;
17
+ function newId(prefix) {
18
+ return `${prefix}-${randomUUID().replace(/-/g, "").slice(0, 8)}`;
19
+ }
20
+ function milliseconds(value, name, maximum, fallback) {
21
+ if (value === undefined)
22
+ return fallback;
23
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > maximum) {
24
+ throw new DGCConfigError(`${name} must be more than 0 and at most ${maximum} milliseconds`);
25
+ }
26
+ return value;
27
+ }
28
+ /**
29
+ * Write a provider key to a private (0600) file inside the 0700 stateDir. The runtime is handed
30
+ * the path (DGC_API_KEY_FILE), not the key, so the key never sits in the child's initial
31
+ * environment block, which stays readable in /proc/<pid>/environ. The CLI reads the file and
32
+ * deletes it at config load, before any tool runs.
33
+ */
34
+ function writeKeyFile(path, value) {
35
+ const fd = openSync(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_TRUNC, 0o600);
36
+ try {
37
+ writeSync(fd, value);
38
+ }
39
+ finally {
40
+ closeSync(fd);
41
+ }
42
+ }
43
+ function runtimeArgv(runtime) {
44
+ if (!Array.isArray(runtime))
45
+ throw new DGCConfigError("runtime must be an argv array such as ['dgc', 'serve']");
46
+ if (!runtime.length || runtime.some((item) => typeof item !== "string" || !item || item.includes("\u0000"))) {
47
+ throw new DGCConfigError("runtime must be a non-empty array of non-empty strings");
48
+ }
49
+ return [...runtime];
50
+ }
51
+ function existingDirs(items) {
52
+ const found = [];
53
+ for (const item of items) {
54
+ try {
55
+ const path = realpathSync(item.startsWith("~/") ? join(homedir(), item.slice(2)) : item);
56
+ if (statSync(path).isDirectory())
57
+ found.push(path);
58
+ }
59
+ catch { /* not there */ }
60
+ }
61
+ return found;
62
+ }
63
+ /** With inheritUserState these would have to be saved into your own ~/.dgc. */
64
+ function rejectInherited(options) {
65
+ const given = Object.entries(options)
66
+ .filter(([name, value]) => value !== undefined && value !== null
67
+ && !(typeof value === "object" && !Array.isArray(value) && !Object.keys(value).length)
68
+ && !(name === "thinking" && value === "off"))
69
+ .map(([name]) => name).sort();
70
+ if (given.length) {
71
+ throw new DGCConfigError(`inheritUserState runs with your own DGC settings, and DGC would save ${given.join(", ")} `
72
+ + "into ~/.dgc/config.json; set them with the dgc CLI, or use an isolated stateDir (inheritUserState: false)");
73
+ }
74
+ }
75
+ /** Refuse an inherited session whose settings differ from what the caller asked for. */
76
+ function checkInherited(ready, requested) {
77
+ const actual = {
78
+ model: String(ready.model || ""),
79
+ baseUrl: String(ready.base_url || "").replace(/\/+$/, ""),
80
+ mode: String(ready.mode || ""),
81
+ thinking: String(ready.think || ""),
82
+ };
83
+ const wrong = [];
84
+ for (const [name, value] of Object.entries(requested)) {
85
+ if (value === undefined)
86
+ continue;
87
+ const want = name === "baseUrl" ? value.replace(/\/+$/, "") : value;
88
+ if (want !== actual[name])
89
+ wrong.push(`${name}=${JSON.stringify(value)} (your DGC uses ${JSON.stringify(actual[name])})`);
90
+ }
91
+ if (wrong.length) {
92
+ throw new DGCConfigError("inheritUserState uses your own DGC settings and cannot change them without saving into "
93
+ + `~/.dgc: ${wrong.join("; ")}. Change them with the dgc CLI, or use an isolated stateDir`);
94
+ }
95
+ }
96
+ /**
97
+ * Own zero or more isolated DGC sessions. Does not talk to a provider on construction.
98
+ *
99
+ * `stateDir` holds this client's isolated HOME, audit and usage logs; it must be private, and
100
+ * left unset a fresh private temporary directory is used ({@link stateDir}). `inheritEnv` picks
101
+ * which host environment variables reach the runtime: false (default) passes only basic ones, a
102
+ * list of names adds those, true passes everything. `startTimeoutMs` bounds the runtime's startup
103
+ * handshake and `requestTimeoutMs` each control request.
104
+ */
105
+ export class DGC {
106
+ sessions = [];
107
+ closed = false;
108
+ options;
109
+ policy;
110
+ sandbox;
111
+ runtime;
112
+ startTimeoutMs;
113
+ requestTimeoutMs;
114
+ usageLog;
115
+ auditLog;
116
+ /** This client's private state directory (created when it was not given). */
117
+ stateDir;
118
+ ownsStateDir;
119
+ trustWorkspace;
120
+ constructor(options = {}) {
121
+ this.options = { ...options };
122
+ this.policy = toPolicy(options.policy);
123
+ if (options.inheritUserState) {
124
+ rejectInherited({
125
+ model: options.model, baseUrl: options.baseUrl, thinking: options.thinking, extraConfig: options.extraConfig,
126
+ });
127
+ }
128
+ if (options.inheritEnv !== undefined && typeof options.inheritEnv !== "boolean") {
129
+ if (!Array.isArray(options.inheritEnv))
130
+ throw new DGCConfigError("inheritEnv must be true, false, or a list of variable names");
131
+ for (const name of options.inheritEnv) {
132
+ if (typeof name !== "string" || !name || name.includes("=") || name.includes("\u0000")) {
133
+ throw new DGCConfigError(`inheritEnv has an invalid variable name: ${JSON.stringify(name)}`);
134
+ }
135
+ }
136
+ }
137
+ this.startTimeoutMs = milliseconds(options.startTimeoutMs, "startTimeoutMs", 3_600_000, 30_000);
138
+ this.requestTimeoutMs = milliseconds(options.requestTimeoutMs, "requestTimeoutMs", 86_400_000, 15_000);
139
+ this.runtime = options.runtime !== undefined ? runtimeArgv(options.runtime) : defaultRuntime();
140
+ this.sandbox = sandboxRequirement(options.sandbox);
141
+ sandboxPrecheck(this.sandbox);
142
+ this.stateDir = prepareStateDir(options.stateDir);
143
+ // Only a directory the SDK created for this client is removed on close; an explicit stateDir
144
+ // (the application's own directory) is always left alone.
145
+ this.ownsStateDir = options.stateDir === undefined && !options.keepStateDir;
146
+ this.trustWorkspace = Boolean(options.trustWorkspace);
147
+ if (options.apiKey)
148
+ rememberSecret(options.apiKey);
149
+ this.usageLog = new UsageLog(join(this.stateDir, "usage"));
150
+ this.auditLog = new AuditLog(join(this.stateDir, "audit"));
151
+ }
152
+ get version() {
153
+ return VERSION;
154
+ }
155
+ /** The `dgc serve` argv this client starts. */
156
+ get rawRuntime() {
157
+ return [...this.runtime];
158
+ }
159
+ async session(options) {
160
+ if (this.closed)
161
+ throw new DGCRuntimeError("this DGC client is closed");
162
+ if (!options || typeof options.cwd !== "string" || !options.cwd)
163
+ throw new DGCConfigError("cwd must be an existing directory");
164
+ let workspace;
165
+ try {
166
+ workspace = realpathSync(options.cwd);
167
+ if (!statSync(workspace).isDirectory())
168
+ throw new Error("not a directory");
169
+ }
170
+ catch {
171
+ throw new DGCConfigError("cwd must be an existing directory");
172
+ }
173
+ const inherit = Boolean(this.options.inheritUserState);
174
+ const [mode, unhandled] = permissionSettings(options.permissions, options.mode, this.options.mode ?? "default", options.onPermission);
175
+ const requestedMode = options.mode || options.permissions?.mode
176
+ || (this.options.mode && this.options.mode !== "default" ? this.options.mode : undefined);
177
+ if (inherit) {
178
+ rejectInherited({
179
+ maxTurns: options.maxTurns, verifyCommand: options.verifyCommand, turnBudgetS: options.turnBudgetS,
180
+ maxTokens: options.maxTokens,
181
+ });
182
+ }
183
+ const requirement = options.sandbox !== undefined ? sandboxRequirement(options.sandbox) : this.sandbox;
184
+ sandboxPrecheck(requirement);
185
+ const tools = options.tools ?? [];
186
+ // The RuntimePolicy and sandbox reach this session's runtime only through its environment
187
+ // (DGC_SESSION_POLICY); nothing is written to any config.json.
188
+ const plan = compileSession(this.policy, {
189
+ cwd: workspace, mode, sandbox: requirement, tools: tools.map((t) => t.name),
190
+ trustWorkspace: this.trustWorkspace, isolated: !inherit,
191
+ });
192
+ const key = options.apiKey ?? this.options.apiKey;
193
+ const extra = { ...(this.options.extraEnv || {}) };
194
+ let keyFile = null;
195
+ if (key) {
196
+ rememberSecret(key);
197
+ if (inherit) {
198
+ // inheritUserState runs as the user's own DGC on their own machine; the key goes in the
199
+ // environment as before (DGC never saves it into ~/.dgc).
200
+ extra.DGC_API_KEY = String(key);
201
+ }
202
+ else {
203
+ // An isolated child's environment block stays readable in /proc/<pid>/environ, so the key
204
+ // travels in a 0600 file inside the 0700 stateDir that the CLI reads and deletes.
205
+ keyFile = join(this.stateDir, `.apikey-${newId("key")}`);
206
+ extra.DGC_API_KEY_FILE = keyFile;
207
+ }
208
+ }
209
+ Object.assign(extra, plan.env);
210
+ let values = null;
211
+ if (!inherit) {
212
+ values = {
213
+ model: options.model || this.options.model,
214
+ base_url: options.baseUrl || this.options.baseUrl,
215
+ mode,
216
+ thinking: options.thinking || this.options.thinking || "off",
217
+ artifact_autostart: false,
218
+ artifact_in_plan: false,
219
+ suggest: false,
220
+ eta: false,
221
+ notify: "off",
222
+ monitor_wake: false,
223
+ mcp_servers: {},
224
+ hooks: {},
225
+ trusted_dirs: [workspace, ...existingDirs(this.policy?.extraReadDirs ?? [])],
226
+ sandbox: false,
227
+ sandbox_network: Boolean(this.policy && this.policy.network === "allow"),
228
+ ...(this.options.extraConfig || {}),
229
+ };
230
+ if (options.maxTurns !== undefined)
231
+ values.max_turns = Math.trunc(options.maxTurns);
232
+ if (options.turnBudgetS !== undefined)
233
+ values.turn_budget_s = Math.trunc(options.turnBudgetS);
234
+ if (options.maxTokens !== undefined)
235
+ values.max_tokens = Math.trunc(options.maxTokens);
236
+ if (options.verifyCommand) {
237
+ values.verify_command = options.verifyCommand;
238
+ values.verify_before_done = true;
239
+ }
240
+ }
241
+ let env;
242
+ try {
243
+ env = isolatedEnv(this.stateDir, extra, inherit, inherit ? undefined : workspace, this.options.inheritEnv ?? false);
244
+ }
245
+ catch (error) {
246
+ if (error instanceof DGCError)
247
+ throw error;
248
+ throw new DGCConfigError(`could not prepare the isolated runtime home: ${String(error)}`);
249
+ }
250
+ const lock = stateLock(this.stateDir);
251
+ // Config write, child startup and every save the SDK itself triggers happen under one lock,
252
+ // so concurrent sessions never read each other's options.
253
+ const session = await lock.hold(async () => {
254
+ let started;
255
+ try {
256
+ started = await this.start(workspace, env, values, key, keyFile);
257
+ }
258
+ finally {
259
+ // The CLI deletes the key file as it starts; remove any leftover (a failed or skipped
260
+ // start) so the key never lingers in the stateDir.
261
+ if (keyFile) {
262
+ try {
263
+ unlinkSync(keyFile);
264
+ }
265
+ catch { /* already gone */ }
266
+ }
267
+ }
268
+ const { transport, ready } = started;
269
+ let hub = null;
270
+ try {
271
+ const sandboxStatus = plan.confirm(ready);
272
+ if (inherit) {
273
+ checkInherited(ready, {
274
+ model: options.model || this.options.model,
275
+ baseUrl: options.baseUrl || this.options.baseUrl,
276
+ mode: requestedMode,
277
+ thinking: options.thinking || (this.options.thinking && this.options.thinking !== "off" ? this.options.thinking : undefined),
278
+ });
279
+ }
280
+ if (tools.length) {
281
+ hub = await installTools(transport, tools, {
282
+ requestId: newId("mcp"), timeoutMs: Math.max(20_000, this.requestTimeoutMs),
283
+ });
284
+ }
285
+ if ((mode === "acceptEdits" || mode === "auto") && ready.workspace_trusted !== true) {
286
+ try {
287
+ transport.send({ type: "set_mode", mode, acknowledge_workspace_trust: true, request_id: newId("trust") });
288
+ }
289
+ catch { /* the run reports a dead backend */ }
290
+ }
291
+ return new Session(transport, ready, {
292
+ options,
293
+ unhandled,
294
+ instructions: options.instructions ?? this.options.instructions ?? "",
295
+ toolHub: hub,
296
+ cwd: workspace,
297
+ policy: this.policy,
298
+ pricing: this.options.pricing,
299
+ department: this.options.department || "",
300
+ usageLog: this.usageLog,
301
+ auditLog: this.auditLog,
302
+ model: String(options.model || this.options.model || ""),
303
+ permissionMode: inherit ? String(ready.mode || mode) : mode,
304
+ isolated: !inherit,
305
+ maxTurns: options.maxTurns,
306
+ verifyCommand: options.verifyCommand || "",
307
+ stateLock: lock,
308
+ excludePaths: inherit ? [join(homedir(), ".dgc"), this.stateDir] : [this.stateDir],
309
+ requestTimeoutMs: this.requestTimeoutMs,
310
+ sandbox: sandboxStatus,
311
+ });
312
+ }
313
+ catch (error) {
314
+ // Nothing may outlive a failed setup: not the tool socket, not the dgc serve child.
315
+ hub?.close();
316
+ await transport.close();
317
+ throw error instanceof DGCError && !(error instanceof DGCCommandRejectedError)
318
+ ? error : publicError(error, "session setup failed");
319
+ }
320
+ });
321
+ try {
322
+ await session.bindIdentity();
323
+ }
324
+ catch { /* its transcript appears after the first run */ }
325
+ this.sessions.push(session);
326
+ return session;
327
+ }
328
+ /**
329
+ * Write this session's config from scratch, then start `dgc serve` on it. A DGC child saves its
330
+ * whole config when it persists anything; if another session's child did that between our write
331
+ * and our child's startup, start again.
332
+ */
333
+ async start(workspace, env, values, key, keyFile = null) {
334
+ const drifts = [];
335
+ const attempts = values !== null ? CONFIG_ATTEMPTS : 1;
336
+ for (let attempt = 0; attempt < attempts; attempt++) {
337
+ let written = null;
338
+ try {
339
+ // The CLI reads and deletes the key file at startup, so write it before every (re)start.
340
+ if (keyFile && key)
341
+ writeKeyFile(keyFile, String(key));
342
+ written = values !== null ? writeSessionConfig(this.stateDir, values) : null;
343
+ }
344
+ catch (error) {
345
+ throw new DGCConfigError(`could not prepare the isolated runtime home: ${String(error)}`);
346
+ }
347
+ const transport = new Transport(this.runtime, workspace, env);
348
+ let ready;
349
+ try {
350
+ ready = await transport.start(this.startTimeoutMs);
351
+ }
352
+ catch (error) {
353
+ await transport.close();
354
+ if (error instanceof DGCProtocolError)
355
+ throw this.protocolError(error);
356
+ throw new DGCRuntimeError(this.startFailure(error, transport, key), { cause: error });
357
+ }
358
+ const drift = written !== null ? configDrift(this.stateDir, written) : [];
359
+ const last = drifts[drifts.length - 1];
360
+ if (!drift.length || (last && last.join() === drift.join() && attempt === attempts - 1)) {
361
+ // The same difference every time is DGC normalizing its own file, not a race.
362
+ return { transport, ready };
363
+ }
364
+ drifts.push(drift);
365
+ process.emitWarning(`dgc sdk: isolated config changed while the session started (${drift.slice(0, 5).join(", ")}); `
366
+ + "starting again", { code: "DGC_SDK" });
367
+ await transport.close();
368
+ }
369
+ throw new DGCRuntimeError("another DGC process kept rewriting this stateDir's config while the session started; "
370
+ + "use a separate stateDir per process");
371
+ }
372
+ protocolError(error) {
373
+ const offered = error.offeredProtocol;
374
+ const runtime = this.runtime.join(" ");
375
+ if (offered === undefined) {
376
+ return new DGCProtocolError(`the DGC runtime broke protocol v${PROTOCOL}: ${error.message} (runtime: ${runtime})`, { cause: error });
377
+ }
378
+ const cli = error.backendVersion || "unknown";
379
+ const advice = typeof offered === "number" && offered > PROTOCOL
380
+ ? "Upgrade the SDK (@vibedgc/sdk) to drive this CLI."
381
+ : `Update the CLI (dgc update), or point DGC_PYTHON or runtime at a DGC that is ${REQUIRES_CLI} or newer.`;
382
+ return new DGCProtocolError(`DGC SDK ${VERSION} speaks protocol v${PROTOCOL} and needs CLI ${REQUIRES_CLI} or newer; the runtime is CLI `
383
+ + `${cli} with protocol v${String(offered)} (${runtime}). ${advice}`, { offeredProtocol: offered, backendVersion: error.backendVersion, cause: error });
384
+ }
385
+ startFailure(error, transport, key) {
386
+ let message = `the DGC runtime did not start: ${error instanceof Error ? error.message : String(error)} `
387
+ + `(runtime: ${this.runtime.join(" ")})`;
388
+ const tail = transport.stderrTail.trim();
389
+ if (tail)
390
+ message += "\nruntime stderr (last lines):\n" + tail.split("\n").slice(-12).join("\n").slice(-2000);
391
+ for (const secret of new Set([String(key || ""), String(this.options.apiKey || "")])) {
392
+ if (secret.length >= 4)
393
+ message = message.split(secret).join("[redacted]");
394
+ }
395
+ return message;
396
+ }
397
+ /** Open a session on a persisted transcript (`sessionId`, or `latest: true`). */
398
+ async resume(options) {
399
+ const session = await this.session(options);
400
+ const command = { type: "resume_session", request_id: newId("resume") };
401
+ let path = options.sessionId;
402
+ const target = options.latest || !options.sessionId ? "the latest session" : JSON.stringify(options.sessionId);
403
+ let event;
404
+ try {
405
+ if (options.latest || !options.sessionId) {
406
+ command.latest = true;
407
+ }
408
+ else {
409
+ const sessionId = options.sessionId;
410
+ const looksLikePath = sessionId.endsWith(".json") || sessionId.includes("/") || sessionId.includes("\\");
411
+ if (!looksLikePath) {
412
+ const match = (await session.listSessions()).find((item) => item.id === sessionId);
413
+ if (!match)
414
+ throw new DGCConfigError(`no persisted session ${JSON.stringify(sessionId)}`);
415
+ path = match.path;
416
+ }
417
+ command.path = path;
418
+ }
419
+ event = await session.transport.request(command, "session", { timeoutMs: this.requestTimeoutMs });
420
+ }
421
+ catch (error) {
422
+ await session.close();
423
+ const index = this.sessions.indexOf(session);
424
+ if (index >= 0)
425
+ this.sessions.splice(index, 1);
426
+ if (error instanceof DGCCommandRejectedError) {
427
+ throw new DGCConfigError(`could not resume ${target}: ${error.message}`, { cause: error });
428
+ }
429
+ if (error instanceof DGCError)
430
+ throw error;
431
+ throw publicError(error, `could not resume ${target}`);
432
+ }
433
+ session.sessionId = String(event.session_id || options.sessionId || session.sessionId);
434
+ session.sessionPath = String(event.path || path || session.sessionPath);
435
+ await session.discardIdle();
436
+ try {
437
+ await session.bindIdentity();
438
+ }
439
+ catch { /* keeps the path DGC reported */ }
440
+ return session;
441
+ }
442
+ /**
443
+ * Usage recorded under this client's stateDir (never the host ~/.dgc): runs, token totals over
444
+ * runs whose provider reported usage, `unknownUsageRuns` for the rest, cost, per department.
445
+ */
446
+ usageReport(department) {
447
+ return this.usageLog.query({ department });
448
+ }
449
+ /** Audit rows (redacted unless `redact: false`), for one session or all. */
450
+ exportAudit(sessionId, options = {}) {
451
+ return this.auditLog.export(sessionId, options.redact ?? true);
452
+ }
453
+ async close() {
454
+ this.closed = true;
455
+ const closing = [];
456
+ while (this.sessions.length) {
457
+ const session = this.sessions.pop();
458
+ if (session)
459
+ closing.push(session.close());
460
+ }
461
+ await Promise.all(closing);
462
+ if (this.ownsStateDir) {
463
+ // A stateDir the SDK created holds only this client's throwaway HOME, usage and audit logs;
464
+ // remove it so nothing (the audit rows included) is left on disk.
465
+ this.ownsStateDir = false;
466
+ try {
467
+ rmSync(this.stateDir, { recursive: true, force: true });
468
+ }
469
+ catch { /* best effort */ }
470
+ }
471
+ }
472
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Typed failures. Every error the SDK throws is a {@link DGCError}; mirrors the Python SDK's
3
+ * dgc_sdk.errors. A run that hits its own timeout does not throw: its result has
4
+ * `status: "failed"` and `reason: "timeout"`.
5
+ */
6
+ export declare class DGCError extends Error {
7
+ constructor(message: string, options?: {
8
+ cause?: unknown;
9
+ });
10
+ }
11
+ /** Session or client options are invalid. */
12
+ export declare class DGCConfigError extends DGCError {
13
+ }
14
+ /** The DGC runtime could not start, handshake, stay alive, or carry out a command. */
15
+ export declare class DGCRuntimeError extends DGCError {
16
+ }
17
+ /** The runtime speaks a protocol this SDK does not, or broke the one it offered. */
18
+ export declare class DGCProtocolError extends DGCRuntimeError {
19
+ /** The protocol a `ready` handshake offered, when that was the problem. */
20
+ readonly offeredProtocol?: unknown;
21
+ /** The CLI version the runtime reported, when known. */
22
+ readonly backendVersion?: string;
23
+ constructor(message: string, details?: {
24
+ offeredProtocol?: unknown;
25
+ backendVersion?: string;
26
+ cause?: unknown;
27
+ });
28
+ }
29
+ /**
30
+ * The runtime refused a command (`command_rejected`, or an `error` naming the request).
31
+ * `reason` is the runtime's machine-readable code when it sent one (for example
32
+ * `turn_in_progress` or `session_unavailable`); `command` is the refused command type.
33
+ */
34
+ export declare class DGCCommandRejectedError extends DGCRuntimeError {
35
+ readonly reason: string;
36
+ readonly command: string;
37
+ constructor(message: string, details?: {
38
+ reason?: string;
39
+ command?: string;
40
+ cause?: unknown;
41
+ });
42
+ }
43
+ /** A control request, the runtime handshake, or a wait on a run's result ran out of time. */
44
+ export declare class DGCTimeoutError extends DGCError {
45
+ }
46
+ /** A requested capability is not available on this host or runtime. */
47
+ export declare class DGCUnsupportedError extends DGCConfigError {
48
+ }
49
+ /**
50
+ * The SDK's own error for any failure, keeping its message and details. `context` prefixes the
51
+ * message. A DGCError keeps its class; anything else becomes a DGCRuntimeError.
52
+ */
53
+ export declare function publicError(error: unknown, context?: string): DGCError;
package/dist/errors.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Typed failures. Every error the SDK throws is a {@link DGCError}; mirrors the Python SDK's
3
+ * dgc_sdk.errors. A run that hits its own timeout does not throw: its result has
4
+ * `status: "failed"` and `reason: "timeout"`.
5
+ */
6
+ export class DGCError extends Error {
7
+ constructor(message, options) {
8
+ super(message, options);
9
+ this.name = new.target.name;
10
+ }
11
+ }
12
+ /** Session or client options are invalid. */
13
+ export class DGCConfigError extends DGCError {
14
+ }
15
+ /** The DGC runtime could not start, handshake, stay alive, or carry out a command. */
16
+ export class DGCRuntimeError extends DGCError {
17
+ }
18
+ /** The runtime speaks a protocol this SDK does not, or broke the one it offered. */
19
+ export class DGCProtocolError extends DGCRuntimeError {
20
+ /** The protocol a `ready` handshake offered, when that was the problem. */
21
+ offeredProtocol;
22
+ /** The CLI version the runtime reported, when known. */
23
+ backendVersion;
24
+ constructor(message, details = {}) {
25
+ super(message, details.cause === undefined ? undefined : { cause: details.cause });
26
+ this.offeredProtocol = details.offeredProtocol;
27
+ this.backendVersion = details.backendVersion;
28
+ }
29
+ }
30
+ /**
31
+ * The runtime refused a command (`command_rejected`, or an `error` naming the request).
32
+ * `reason` is the runtime's machine-readable code when it sent one (for example
33
+ * `turn_in_progress` or `session_unavailable`); `command` is the refused command type.
34
+ */
35
+ export class DGCCommandRejectedError extends DGCRuntimeError {
36
+ reason;
37
+ command;
38
+ constructor(message, details = {}) {
39
+ super(message, details.cause === undefined ? undefined : { cause: details.cause });
40
+ this.reason = details.reason || "";
41
+ this.command = details.command || "";
42
+ }
43
+ }
44
+ /** A control request, the runtime handshake, or a wait on a run's result ran out of time. */
45
+ export class DGCTimeoutError extends DGCError {
46
+ }
47
+ /** A requested capability is not available on this host or runtime. */
48
+ export class DGCUnsupportedError extends DGCConfigError {
49
+ }
50
+ /**
51
+ * The SDK's own error for any failure, keeping its message and details. `context` prefixes the
52
+ * message. A DGCError keeps its class; anything else becomes a DGCRuntimeError.
53
+ */
54
+ export function publicError(error, context = "") {
55
+ const base = error instanceof Error ? (error.message || error.name) : String(error);
56
+ const message = context ? `${context}: ${base}` : base;
57
+ if (error instanceof DGCCommandRejectedError) {
58
+ return new DGCCommandRejectedError(message, { reason: error.reason, command: error.command, cause: error });
59
+ }
60
+ if (error instanceof DGCProtocolError) {
61
+ return new DGCProtocolError(message, {
62
+ offeredProtocol: error.offeredProtocol, backendVersion: error.backendVersion, cause: error,
63
+ });
64
+ }
65
+ for (const Kind of [DGCUnsupportedError, DGCTimeoutError, DGCConfigError]) {
66
+ if (error instanceof Kind)
67
+ return new Kind(message, { cause: error });
68
+ }
69
+ return new DGCRuntimeError(message, { cause: error });
70
+ }
@@ -0,0 +1,10 @@
1
+ /** DGC SDK for Node (@vibedgc/sdk). Protocol v14; pair with DGC CLI 0.41.6. */
2
+ export { VERSION, PROTOCOL, REQUIRES_CLI } from "./types.ts";
3
+ export type { AbortSignalLike, AgentInfo, Artifact, Checkpoint, ClientOptions, Denial, FileChange, Goal, HookInfo, McpInputRequest, McpInputResponse, McpServerInfo, Monitor, PermissionAction, PermissionMode, PermissionRequest, PermissionRule, PlanAction, PlanRequest, Pricing, QuestionAnswers, QuestionRequest, ResumeOptions, RunEvent, RunOptions, RunResult, RunStatus, RuntimePolicy, SandboxRequirement, SandboxSetting, SandboxStatus, SessionInfo, SessionOptions, SkillInfo, TaskItem, ToolRecord, UnhandledPolicy, VerificationResult, } from "./types.ts";
4
+ export { DGC } from "./client.ts";
5
+ export { RunHandle, Session } from "./session.ts";
6
+ export { defineTool, type ToolSpec } from "./tools.ts";
7
+ export { assertSupported, extractJson, validate } from "./schema.ts";
8
+ export { DGCCommandRejectedError, DGCConfigError, DGCError, DGCProtocolError, DGCRuntimeError, DGCTimeoutError, DGCUnsupportedError, } from "./errors.ts";
9
+ export { redact, redactText } from "./audit.ts";
10
+ export { costUsd, type UsageReport } from "./usage.ts";
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /** DGC SDK for Node (@vibedgc/sdk). Protocol v14; pair with DGC CLI 0.41.6. */
2
+ export { VERSION, PROTOCOL, REQUIRES_CLI } from "./types.js";
3
+ export { DGC } from "./client.js";
4
+ export { RunHandle, Session } from "./session.js";
5
+ export { defineTool } from "./tools.js";
6
+ export { assertSupported, extractJson, validate } from "./schema.js";
7
+ export { DGCCommandRejectedError, DGCConfigError, DGCError, DGCProtocolError, DGCRuntimeError, DGCTimeoutError, DGCUnsupportedError, } from "./errors.js";
8
+ export { redact, redactText } from "./audit.js";
9
+ export { costUsd } from "./usage.js";
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * stdio <-> Unix-socket relay. DGC spawns it as the `app` MCP server; the SDK host owns the tools.
4
+ *
5
+ * The session's secret arrives in DGC_SDK_TOOL_TOKEN. The relay drops it from its own
6
+ * environment, proves it knows it with a first `{"dgc_sdk_bridge":1,"token":...}` line, then
7
+ * copies NDJSON in both directions. Standard library only.
8
+ */
9
+ import net from "node:net";
10
+ import process from "node:process";
11
+
12
+ const TOKEN_ENV = "DGC_SDK_TOOL_TOKEN";
13
+ const socketPath = process.argv[2];
14
+ if (!socketPath) {
15
+ process.stderr.write("usage: node mcp-bridge.mjs SOCKET (with DGC_SDK_TOOL_TOKEN set)\n");
16
+ process.exit(2);
17
+ }
18
+ const token = process.env[TOKEN_ENV] || "";
19
+ delete process.env[TOKEN_ENV];
20
+ if (!token) {
21
+ process.stderr.write(`dgc-sdk tool bridge: ${TOKEN_ENV} is not set\n`);
22
+ process.exit(2);
23
+ }
24
+
25
+ const sock = net.createConnection(socketPath);
26
+ sock.on("error", (error) => {
27
+ process.stderr.write(`dgc-sdk tool bridge: cannot reach the application: ${error.message || error}\n`);
28
+ process.exit(1);
29
+ });
30
+ sock.on("connect", () => {
31
+ sock.write(JSON.stringify({ dgc_sdk_bridge: 1, token }) + "\n");
32
+ process.stdin.pipe(sock);
33
+ sock.pipe(process.stdout);
34
+ });
35
+ sock.on("end", () => process.exit(0));
36
+ sock.on("close", () => process.exit(0));