@oqtopus-team/qdash-client 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,803 @@
1
+ import {
2
+ QDashApiError,
3
+ QDashAuthError,
4
+ QDashClientError,
5
+ QDashConfigError,
6
+ QDashNotFoundError,
7
+ QDashTransportError,
8
+ QDashValidationError,
9
+ getQDashAPI
10
+ } from "./chunk-F75WR2JX.js";
11
+
12
+ // src/client.ts
13
+ import { setTimeout as sleep2 } from "timers/promises";
14
+
15
+ // src/config.ts
16
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
17
+ import { homedir } from "os";
18
+ import { dirname, join } from "path";
19
+ import ini from "ini";
20
+ var DEFAULT_RETRY = {
21
+ maxAttempts: 3,
22
+ baseDelaySeconds: 0.2,
23
+ maxDelaySeconds: 5
24
+ };
25
+ function optional(value) {
26
+ return typeof value === "string" && value.length > 0 ? value : void 0;
27
+ }
28
+ function positiveNumber(value, name, fallback) {
29
+ if (value === void 0 || value === null || value === "") return fallback;
30
+ const parsed = Number(value);
31
+ if (!Number.isFinite(parsed) || parsed <= 0) {
32
+ throw new QDashConfigError(`${name} must be a positive number`);
33
+ }
34
+ return parsed;
35
+ }
36
+ function nonNegativeNumber(value, name, fallback) {
37
+ if (value === void 0 || value === null || value === "") return fallback;
38
+ const parsed = Number(value);
39
+ if (!Number.isFinite(parsed) || parsed < 0) {
40
+ throw new QDashConfigError(`${name} must be a non-negative number`);
41
+ }
42
+ return parsed;
43
+ }
44
+ function booleanValue(value, name, fallback) {
45
+ if (value === void 0 || value === null || value === "") return fallback;
46
+ if (typeof value === "boolean") return value;
47
+ if (typeof value === "string") {
48
+ if (["1", "true", "yes", "on"].includes(value.toLowerCase())) return true;
49
+ if (["0", "false", "no", "off"].includes(value.toLowerCase())) return false;
50
+ }
51
+ throw new QDashConfigError(`${name} must be a boolean`);
52
+ }
53
+ function defaultConfigPath(env = process.env) {
54
+ return env.XDG_CONFIG_HOME ? join(env.XDG_CONFIG_HOME, "qdash", "config.ini") : join(homedir(), ".config", "qdash", "config.ini");
55
+ }
56
+ var QDashConfig = class _QDashConfig {
57
+ baseUrl;
58
+ username;
59
+ passwordEnv;
60
+ apiToken;
61
+ projectId;
62
+ cfAccessClientId;
63
+ cfAccessClientSecret;
64
+ timeoutSeconds;
65
+ verifyTls;
66
+ proxy;
67
+ userAgent;
68
+ retry;
69
+ constructor(options) {
70
+ const baseUrl = options.baseUrl?.replace(/\/+$/, "");
71
+ if (!baseUrl) throw new QDashConfigError("baseUrl is required");
72
+ try {
73
+ new URL(baseUrl);
74
+ } catch (cause) {
75
+ throw new QDashConfigError("baseUrl must be a valid URL", { cause });
76
+ }
77
+ this.baseUrl = baseUrl;
78
+ this.username = optional(options.username);
79
+ this.passwordEnv = optional(options.passwordEnv);
80
+ this.apiToken = optional(options.apiToken);
81
+ this.projectId = optional(options.projectId);
82
+ this.cfAccessClientId = optional(options.cfAccessClientId);
83
+ this.cfAccessClientSecret = optional(options.cfAccessClientSecret);
84
+ this.timeoutSeconds = positiveNumber(options.timeoutSeconds, "timeoutSeconds", 30);
85
+ this.verifyTls = options.verifyTls ?? true;
86
+ this.proxy = optional(options.proxy);
87
+ this.userAgent = optional(options.userAgent) ?? "qdash-client-ts/dev";
88
+ this.retry = {
89
+ maxAttempts: positiveNumber(
90
+ options.retry?.maxAttempts,
91
+ "retry.maxAttempts",
92
+ DEFAULT_RETRY.maxAttempts
93
+ ),
94
+ baseDelaySeconds: nonNegativeNumber(
95
+ options.retry?.baseDelaySeconds,
96
+ "retry.baseDelaySeconds",
97
+ DEFAULT_RETRY.baseDelaySeconds
98
+ ),
99
+ maxDelaySeconds: nonNegativeNumber(
100
+ options.retry?.maxDelaySeconds,
101
+ "retry.maxDelaySeconds",
102
+ DEFAULT_RETRY.maxDelaySeconds
103
+ )
104
+ };
105
+ }
106
+ static fromEnv(env = process.env) {
107
+ const baseUrl = env.QDASH_BASE_URL;
108
+ if (!baseUrl) {
109
+ throw new QDashConfigError("Environment variable QDASH_BASE_URL is required");
110
+ }
111
+ return new _QDashConfig({
112
+ baseUrl,
113
+ ...optional(env.QDASH_USERNAME) ? { username: env.QDASH_USERNAME } : {},
114
+ passwordEnv: env.QDASH_PASSWORD_ENV ?? "QDASH_PASSWORD",
115
+ ...optional(env.QDASH_API_TOKEN) ? { apiToken: env.QDASH_API_TOKEN } : {},
116
+ ...optional(env.QDASH_PROJECT_ID) ? { projectId: env.QDASH_PROJECT_ID } : {},
117
+ ...optional(env.QDASH_CF_ACCESS_CLIENT_ID) ? { cfAccessClientId: env.QDASH_CF_ACCESS_CLIENT_ID } : {},
118
+ ...optional(env.QDASH_CF_ACCESS_CLIENT_SECRET) ? { cfAccessClientSecret: env.QDASH_CF_ACCESS_CLIENT_SECRET } : {},
119
+ timeoutSeconds: positiveNumber(env.QDASH_TIMEOUT_SECONDS, "QDASH_TIMEOUT_SECONDS", 30),
120
+ verifyTls: booleanValue(env.QDASH_VERIFY_TLS, "QDASH_VERIFY_TLS", true),
121
+ ...optional(env.QDASH_PROXY) ? { proxy: env.QDASH_PROXY } : {},
122
+ userAgent: env.QDASH_USER_AGENT ?? "qdash-client-ts/dev",
123
+ retry: {
124
+ maxAttempts: positiveNumber(
125
+ env.QDASH_RETRY_MAX_ATTEMPTS,
126
+ "QDASH_RETRY_MAX_ATTEMPTS",
127
+ 3
128
+ ),
129
+ baseDelaySeconds: nonNegativeNumber(
130
+ env.QDASH_RETRY_BACKOFF_SECONDS,
131
+ "QDASH_RETRY_BACKOFF_SECONDS",
132
+ 0.2
133
+ ),
134
+ maxDelaySeconds: nonNegativeNumber(
135
+ env.QDASH_RETRY_MAX_BACKOFF_SECONDS,
136
+ "QDASH_RETRY_MAX_BACKOFF_SECONDS",
137
+ 5
138
+ )
139
+ }
140
+ });
141
+ }
142
+ static async fromFile(profile = "default", path = defaultConfigPath()) {
143
+ let contents;
144
+ try {
145
+ contents = await readFile(path, "utf8");
146
+ } catch (cause) {
147
+ throw new QDashConfigError(`Config file not found: ${path}`, { cause });
148
+ }
149
+ const section = ini.parse(contents)[profile];
150
+ if (!section) throw new QDashConfigError(`Config profile not found: ${profile}`);
151
+ const baseUrl = optional(section.base_url);
152
+ if (!baseUrl) throw new QDashConfigError(`base_url is required in profile '${profile}'`);
153
+ return new _QDashConfig({
154
+ baseUrl,
155
+ ...optional(section.username) ? { username: String(section.username) } : {},
156
+ ...optional(section.password_env) ? { passwordEnv: String(section.password_env) } : {},
157
+ ...optional(section.api_token) ? { apiToken: String(section.api_token) } : {},
158
+ ...optional(section.project_id) ? { projectId: String(section.project_id) } : {},
159
+ ...optional(section.cf_access_client_id) ? { cfAccessClientId: String(section.cf_access_client_id) } : {},
160
+ ...optional(section.cf_access_client_secret) ? { cfAccessClientSecret: String(section.cf_access_client_secret) } : {},
161
+ timeoutSeconds: positiveNumber(
162
+ section.timeout_seconds ?? section.timeout_sec,
163
+ "timeout_seconds",
164
+ 30
165
+ ),
166
+ verifyTls: booleanValue(section.verify_tls, "verify_tls", true),
167
+ ...optional(section.proxy) ? { proxy: String(section.proxy) } : {},
168
+ userAgent: optional(section.user_agent) ?? "qdash-client-ts/dev",
169
+ retry: {
170
+ maxAttempts: positiveNumber(section.retry_max_attempts, "retry_max_attempts", 3),
171
+ baseDelaySeconds: nonNegativeNumber(
172
+ section.retry_backoff_seconds ?? section.retry_base_delay_sec,
173
+ "retry_backoff_seconds",
174
+ 0.2
175
+ ),
176
+ maxDelaySeconds: nonNegativeNumber(
177
+ section.retry_max_backoff_seconds ?? section.retry_max_delay_sec,
178
+ "retry_max_backoff_seconds",
179
+ 5
180
+ )
181
+ }
182
+ });
183
+ }
184
+ async save(profile = "default", path = defaultConfigPath()) {
185
+ let parsed = {};
186
+ try {
187
+ parsed = ini.parse(await readFile(path, "utf8"));
188
+ } catch (cause) {
189
+ if (cause.code !== "ENOENT") throw cause;
190
+ }
191
+ parsed[profile] = {
192
+ base_url: this.baseUrl,
193
+ ...this.username ? { username: this.username } : {},
194
+ ...this.passwordEnv ? { password_env: this.passwordEnv } : {},
195
+ ...this.apiToken ? { api_token: this.apiToken } : {},
196
+ ...this.projectId ? { project_id: this.projectId } : {},
197
+ ...this.cfAccessClientId ? { cf_access_client_id: this.cfAccessClientId } : {},
198
+ ...this.cfAccessClientSecret ? { cf_access_client_secret: this.cfAccessClientSecret } : {},
199
+ timeout_seconds: this.timeoutSeconds,
200
+ verify_tls: this.verifyTls,
201
+ ...this.proxy ? { proxy: this.proxy } : {},
202
+ user_agent: this.userAgent,
203
+ retry_max_attempts: this.retry.maxAttempts,
204
+ retry_backoff_seconds: this.retry.baseDelaySeconds,
205
+ retry_max_backoff_seconds: this.retry.maxDelaySeconds
206
+ };
207
+ await mkdir(dirname(path), { recursive: true });
208
+ await writeFile(path, ini.stringify(parsed), { encoding: "utf8", mode: 384 });
209
+ await chmod(path, 384);
210
+ return path;
211
+ }
212
+ };
213
+
214
+ // src/transport.ts
215
+ import { setTimeout as sleep } from "timers/promises";
216
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
217
+ async function responsePayload(response) {
218
+ if (response.status === 204) return void 0;
219
+ const text = await response.text();
220
+ if (!text) return void 0;
221
+ try {
222
+ return JSON.parse(text);
223
+ } catch {
224
+ return text;
225
+ }
226
+ }
227
+ function errorMessage(response, payload) {
228
+ let detail;
229
+ if (typeof payload === "object" && payload !== null && "detail" in payload) {
230
+ detail = payload.detail;
231
+ } else if (typeof payload === "string") {
232
+ detail = payload;
233
+ }
234
+ const endpoint = response.url ? new URL(response.url).pathname : "<unknown>";
235
+ return `${response.status} ${endpoint}${detail ? `: ${String(detail)}` : ""}`;
236
+ }
237
+ var QDashTransport = class {
238
+ constructor(config, options = {}) {
239
+ this.config = config;
240
+ if ((config.proxy || !config.verifyTls) && !options.fetch) {
241
+ throw new QDashTransportError(
242
+ "proxy and verifyTls overrides require a custom fetch implementation"
243
+ );
244
+ }
245
+ this.baseFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
246
+ this.defaultHeaders = new Headers(options.defaultHeaders);
247
+ this.sleep = options.sleep ?? ((milliseconds) => sleep(milliseconds));
248
+ this.random = options.random ?? Math.random;
249
+ this.env = options.env ?? process.env;
250
+ this.token = config.apiToken;
251
+ this.fetch = this.authorizedFetch.bind(this);
252
+ }
253
+ config;
254
+ fetch;
255
+ token;
256
+ baseFetch;
257
+ defaultHeaders;
258
+ sleep;
259
+ random;
260
+ env;
261
+ async requestJson(method, path, options = {}) {
262
+ return this.request({
263
+ method,
264
+ url: path,
265
+ params: options.query,
266
+ data: options.body,
267
+ ...options.headers ? { headers: Object.fromEntries(new Headers(options.headers).entries()) } : {}
268
+ });
269
+ }
270
+ async request(config) {
271
+ const requestUrl = config.url ?? "";
272
+ const url = new URL(
273
+ requestUrl.startsWith("http://") || requestUrl.startsWith("https://") ? requestUrl : this.config.baseUrl + (requestUrl.startsWith("/") ? "" : "/") + requestUrl
274
+ );
275
+ for (const [key, value] of Object.entries(
276
+ config.params ?? {}
277
+ )) {
278
+ if (value === void 0 || value === null) continue;
279
+ if (Array.isArray(value)) {
280
+ for (const item of value) url.searchParams.append(key, item);
281
+ } else {
282
+ url.searchParams.set(key, String(value));
283
+ }
284
+ }
285
+ const headers = new Headers(config.headers);
286
+ let body;
287
+ if (config.data !== void 0) {
288
+ if (typeof config.data === "string" || config.data instanceof URLSearchParams || config.data instanceof FormData || config.data instanceof Blob || config.data instanceof ArrayBuffer) {
289
+ body = config.data;
290
+ } else {
291
+ if (!headers.has("Content-Type")) headers.set("Content-Type", "application/json");
292
+ body = JSON.stringify(config.data);
293
+ }
294
+ }
295
+ const response = await this.fetch(url, {
296
+ method: config.method ?? "GET",
297
+ headers,
298
+ ...body === void 0 ? {} : { body }
299
+ });
300
+ let payload;
301
+ if (config.responseType === "arraybuffer") payload = await response.arrayBuffer();
302
+ else if (config.responseType === "blob") payload = await response.blob();
303
+ else if (config.responseType === "text") payload = await response.text();
304
+ else payload = await responsePayload(response);
305
+ if (!response.ok) throw this.apiError(response, payload);
306
+ return payload;
307
+ }
308
+ async authorizedFetch(input, init) {
309
+ const request = new Request(input, init);
310
+ const headers = new Headers(this.defaultHeaders);
311
+ request.headers.forEach((value, key) => headers.set(key, value));
312
+ headers.set("Accept", "application/json");
313
+ headers.set("User-Agent", this.config.userAgent);
314
+ headers.set("Authorization", `Bearer ${await this.getToken()}`);
315
+ this.setGatewayHeaders(headers);
316
+ const authenticated = new Request(request, { headers });
317
+ const retryableMethod = authenticated.method === "GET" || authenticated.method === "HEAD";
318
+ const attempts = retryableMethod ? this.config.retry.maxAttempts : 1;
319
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
320
+ try {
321
+ const response = await this.fetchWithTimeout(authenticated.clone());
322
+ if (response.status === 401 && !this.config.apiToken && this.config.username) {
323
+ this.token = void 0;
324
+ }
325
+ if (!RETRYABLE_STATUSES.has(response.status) || attempt === attempts) return response;
326
+ await this.sleep(this.retryDelayMilliseconds(attempt, response));
327
+ } catch (cause) {
328
+ if (cause instanceof QDashApiError) throw cause;
329
+ if (attempt === attempts) {
330
+ throw new QDashTransportError("QDash request failed", { cause });
331
+ }
332
+ await this.sleep(this.retryDelayMilliseconds(attempt));
333
+ }
334
+ }
335
+ throw new QDashTransportError("QDash request exhausted all retries");
336
+ }
337
+ async fetchWithTimeout(request) {
338
+ const timeout = AbortSignal.timeout(this.config.timeoutSeconds * 1e3);
339
+ const signal = request.signal.aborted ? request.signal : AbortSignal.any([request.signal, timeout]);
340
+ return this.baseFetch(new Request(request, { signal }));
341
+ }
342
+ async getToken() {
343
+ if (this.token) return this.token;
344
+ if (!this.config.username) {
345
+ throw new QDashAuthError("No authentication method configured", { statusCode: 401 });
346
+ }
347
+ const passwordEnv = this.config.passwordEnv;
348
+ if (!passwordEnv) {
349
+ throw new QDashAuthError("passwordEnv is required for username/password authentication", {
350
+ statusCode: 401
351
+ });
352
+ }
353
+ const password = this.env[passwordEnv];
354
+ if (!password) {
355
+ throw new QDashAuthError(`Missing password in environment variable ${passwordEnv}`, {
356
+ statusCode: 401
357
+ });
358
+ }
359
+ const headers = new Headers({
360
+ Accept: "application/json",
361
+ "Content-Type": "application/x-www-form-urlencoded",
362
+ "User-Agent": this.config.userAgent
363
+ });
364
+ this.setGatewayHeaders(headers);
365
+ const response = await this.baseFetch(`${this.config.baseUrl}/auth/login`, {
366
+ method: "POST",
367
+ headers,
368
+ body: new URLSearchParams({ username: this.config.username, password }),
369
+ signal: AbortSignal.timeout(this.config.timeoutSeconds * 1e3)
370
+ });
371
+ const payload = await responsePayload(response);
372
+ if (!response.ok) throw this.apiError(response, payload);
373
+ const token = typeof payload === "object" && payload !== null && "access_token" in payload ? payload.access_token : void 0;
374
+ if (typeof token !== "string" || !token) {
375
+ throw new QDashAuthError("Login response did not include access_token", {
376
+ statusCode: 401,
377
+ payload
378
+ });
379
+ }
380
+ this.token = token;
381
+ return token;
382
+ }
383
+ setGatewayHeaders(headers) {
384
+ if (this.config.projectId) headers.set("X-Project-Id", this.config.projectId);
385
+ if (this.config.cfAccessClientId) {
386
+ headers.set("CF-Access-Client-Id", this.config.cfAccessClientId);
387
+ }
388
+ if (this.config.cfAccessClientSecret) {
389
+ headers.set("CF-Access-Client-Secret", this.config.cfAccessClientSecret);
390
+ }
391
+ }
392
+ retryDelayMilliseconds(attempt, response) {
393
+ const retryAfter = response?.headers.get("Retry-After");
394
+ if (retryAfter) {
395
+ const seconds = Number(retryAfter);
396
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
397
+ }
398
+ const base = this.config.retry.baseDelaySeconds;
399
+ const cap = this.config.retry.maxDelaySeconds;
400
+ const delay = Math.min(cap, base * 2 ** (attempt - 1));
401
+ return (delay + this.random() * delay * 0.1) * 1e3;
402
+ }
403
+ apiError(response, payload) {
404
+ const options = { statusCode: response.status, payload };
405
+ const message = errorMessage(response, payload);
406
+ if (response.status === 401 || response.status === 403) {
407
+ return new QDashAuthError(message, options);
408
+ }
409
+ if (response.status === 404) return new QDashNotFoundError(message, options);
410
+ if (response.status === 422) return new QDashValidationError(message, options);
411
+ return new QDashTransportError(message, options);
412
+ }
413
+ };
414
+
415
+ // src/client.ts
416
+ function pathPart(value) {
417
+ return encodeURIComponent(value);
418
+ }
419
+ function query(values) {
420
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value != null));
421
+ }
422
+ var QDashClient = class _QDashClient {
423
+ api;
424
+ config;
425
+ transport;
426
+ constructor(config, options = {}) {
427
+ this.config = config;
428
+ this.transport = new QDashTransport(config, options);
429
+ this.api = this.bindGeneratedApi(getQDashAPI());
430
+ }
431
+ static fromEnv(env = process.env, options = {}) {
432
+ return new _QDashClient(QDashConfig.fromEnv(env), { ...options, env });
433
+ }
434
+ static async fromProfile(profile = "default", path, options = {}) {
435
+ const config = path ? await QDashConfig.fromFile(profile, path) : await QDashConfig.fromFile(profile);
436
+ return new _QDashClient(config, options);
437
+ }
438
+ async listChips() {
439
+ return this.get("/chips");
440
+ }
441
+ async getDefaultChip() {
442
+ const { chips } = await this.listChips();
443
+ if (chips.length === 0) throw new QDashNotFoundError("No chips are available");
444
+ const active = chips.filter((chip) => chip.activity_status === "active");
445
+ const candidates = active.length > 0 ? active : chips;
446
+ return candidates.reduce((latest, chip) => {
447
+ const latestTime = latest.installed_at ? Date.parse(latest.installed_at) : 0;
448
+ const chipTime = chip.installed_at ? Date.parse(chip.installed_at) : 0;
449
+ return chipTime > latestTime ? chip : latest;
450
+ });
451
+ }
452
+ async getDefaultChipId() {
453
+ return (await this.getDefaultChip()).chip_id;
454
+ }
455
+ async getChipMetrics(chipId) {
456
+ return this.get(`/metrics/chips/${pathPart(chipId)}/metrics`);
457
+ }
458
+ async getMetricsConfig() {
459
+ return this.get("/metrics/config");
460
+ }
461
+ async listChipQubits(chipId, options = {}) {
462
+ return this.get(`/chips/${pathPart(chipId)}/qubits`, query({ limit: options.limit, offset: options.offset }));
463
+ }
464
+ async getChipQubit(chipId, qid) {
465
+ return this.get(`/chips/${pathPart(chipId)}/qubits/${pathPart(qid)}`);
466
+ }
467
+ async listChipCouplings(chipId, options = {}) {
468
+ return this.get(`/chips/${pathPart(chipId)}/couplings`, query({ limit: options.limit, offset: options.offset }));
469
+ }
470
+ async getChipCoupling(chipId, couplingId) {
471
+ return this.get(`/chips/${pathPart(chipId)}/couplings/${pathPart(couplingId)}`);
472
+ }
473
+ async getTaskResultsTimeseries(options) {
474
+ return this.get(
475
+ "/task-results/timeseries",
476
+ query({
477
+ chip_id: options.chipId,
478
+ parameter: options.parameter,
479
+ start_at: options.startAt,
480
+ end_at: options.endAt,
481
+ tag: options.tag,
482
+ qid: options.qid
483
+ })
484
+ );
485
+ }
486
+ async listTaskResults(options = {}) {
487
+ return this.get(
488
+ "/task-results",
489
+ query({
490
+ chip_id: options.chipId,
491
+ task_name: options.taskName,
492
+ qid: options.qid,
493
+ coupling_id: options.couplingId,
494
+ status: options.status,
495
+ start_at: options.startAt,
496
+ end_at: options.endAt,
497
+ limit: options.limit,
498
+ skip: options.skip
499
+ })
500
+ );
501
+ }
502
+ async getTaskResult(taskId) {
503
+ return this.get(`/tasks/${pathPart(taskId)}/result`);
504
+ }
505
+ async listTasks(backend) {
506
+ return this.get("/tasks", query({ backend }));
507
+ }
508
+ async listTaskKnowledge() {
509
+ return this.get("/task-knowledge");
510
+ }
511
+ async getTaskKnowledge(taskName) {
512
+ return this.get(`/tasks/${pathPart(taskName)}/knowledge`);
513
+ }
514
+ async getTaskKnowledgeMarkdown(taskName) {
515
+ return this.get(`/tasks/${pathPart(taskName)}/knowledge/markdown`);
516
+ }
517
+ async listProjects() {
518
+ return this.get("/projects");
519
+ }
520
+ async getProject(projectId) {
521
+ return this.get(`/projects/${pathPart(projectId)}`);
522
+ }
523
+ async getFilesTree() {
524
+ const payload = await this.get(
525
+ "/files/tree"
526
+ );
527
+ return Array.isArray(payload) ? payload : payload.tree ?? [];
528
+ }
529
+ async getFileContent(path) {
530
+ return this.get("/files/content", { path });
531
+ }
532
+ async saveFileContent(path, content) {
533
+ return this.put("/files/content", { path, content });
534
+ }
535
+ async getGitStatus() {
536
+ return this.get("/files/git/status");
537
+ }
538
+ async listFlows() {
539
+ return this.get("/flows");
540
+ }
541
+ async getFlow(name) {
542
+ return this.get(`/flows/${pathPart(name)}`);
543
+ }
544
+ async listFlowTemplates() {
545
+ return this.get("/flows/templates");
546
+ }
547
+ async getFlowTemplate(templateId) {
548
+ return this.get(`/flows/templates/${pathPart(templateId)}`);
549
+ }
550
+ async saveFlow(request) {
551
+ return this.post("/flows", request);
552
+ }
553
+ async executeFlow(name, parameters = {}) {
554
+ return this.post(`/flows/${pathPart(name)}/execute`, { parameters });
555
+ }
556
+ async listExecutions(options = {}) {
557
+ return this.get(
558
+ "/executions",
559
+ query({
560
+ flow_name: options.flowName,
561
+ status: options.status,
562
+ skip: options.skip,
563
+ limit: options.limit
564
+ })
565
+ );
566
+ }
567
+ async getExecution(executionId) {
568
+ return this.get(`/executions/${pathPart(executionId)}`);
569
+ }
570
+ async waitForExecution(executionId, options = {}) {
571
+ const timeoutSeconds = options.timeoutSeconds ?? 600;
572
+ const pollIntervalSeconds = options.pollIntervalSeconds ?? 0.5;
573
+ this.validatePollOptions(timeoutSeconds, pollIntervalSeconds);
574
+ const deadline = Date.now() + timeoutSeconds * 1e3;
575
+ const terminal = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "canceled", "crashed"]);
576
+ while (true) {
577
+ const execution = await this.getExecution(executionId);
578
+ if (terminal.has(execution.status.toLowerCase())) return execution;
579
+ if (Date.now() >= deadline) {
580
+ throw new Error(
581
+ `Execution '${executionId}' did not reach a terminal state within ${timeoutSeconds} seconds`
582
+ );
583
+ }
584
+ await sleep2(pollIntervalSeconds * 1e3);
585
+ }
586
+ }
587
+ async createAgentSession(options) {
588
+ return this.post("/agent-sessions", {
589
+ chip_id: options.chipId,
590
+ policy: options.policy,
591
+ expires_in_seconds: options.expiresInSeconds ?? 21600,
592
+ skill_name: options.skillName ?? "",
593
+ skill_version: options.skillVersion ?? "",
594
+ skill_hash: options.skillHash ?? "",
595
+ model_name: options.modelName ?? ""
596
+ });
597
+ }
598
+ async getAgentSession(sessionId) {
599
+ return this.get(`/agent-sessions/${pathPart(sessionId)}`);
600
+ }
601
+ async evaluateAgentCandidateGate(sessionId, parameterName, value) {
602
+ return this.post(`/agent-sessions/${pathPart(sessionId)}/candidate-gate`, {
603
+ parameter_name: parameterName,
604
+ value
605
+ });
606
+ }
607
+ async submitAgentAction(sessionId, options) {
608
+ return this.post(`/agent-sessions/${pathPart(sessionId)}/actions`, {
609
+ idempotency_key: options.idempotencyKey,
610
+ expected_state_version: options.expectedStateVersion,
611
+ action_type: options.actionType,
612
+ task_name: options.taskName ?? null,
613
+ qids: options.qids ?? [],
614
+ parameter_overrides: options.parameterOverrides ?? {},
615
+ diagnosis: options.diagnosis ?? ""
616
+ });
617
+ }
618
+ async executeAgentAction(sessionId, actionId, options) {
619
+ return this.post(
620
+ `/agent-sessions/${pathPart(sessionId)}/actions/${pathPart(actionId)}/execute`,
621
+ {
622
+ source_execution_id: options.sourceExecutionId,
623
+ update_params: options.updateParams ?? false,
624
+ reconfigure: options.reconfigure ?? false
625
+ }
626
+ );
627
+ }
628
+ async getAgentAction(sessionId, actionId) {
629
+ return this.get(`/agent-sessions/${pathPart(sessionId)}/actions/${pathPart(actionId)}`);
630
+ }
631
+ async listAgentActions(sessionId) {
632
+ const payload = await this.get(
633
+ `/agent-sessions/${pathPart(sessionId)}/actions`
634
+ );
635
+ return payload.items;
636
+ }
637
+ async waitForAgentAction(sessionId, actionId, options = {}) {
638
+ return this.pollAgentAction(sessionId, actionId, "operation_id", options);
639
+ }
640
+ async waitForAgentActionExecution(sessionId, actionId, options = {}) {
641
+ return this.pollAgentAction(
642
+ sessionId,
643
+ actionId,
644
+ "execution_id",
645
+ { timeoutSeconds: 600, ...options }
646
+ );
647
+ }
648
+ async listAgentActionCandidates(sessionId, actionId) {
649
+ const payload = await this.get(
650
+ `/agent-sessions/${pathPart(sessionId)}/actions/${pathPart(actionId)}/candidates`
651
+ );
652
+ return payload.items;
653
+ }
654
+ async commitAgentActionCandidate(sessionId, actionId, parameterName, options) {
655
+ return this.post(
656
+ `/agent-sessions/${pathPart(sessionId)}/actions/${pathPart(actionId)}/candidates/${pathPart(parameterName)}/commit`,
657
+ {
658
+ idempotency_key: options.idempotencyKey,
659
+ expected_state_version: options.expectedStateVersion,
660
+ task_id: options.taskId
661
+ }
662
+ );
663
+ }
664
+ async commitAgentCampaignCandidates(sessionId, candidates, options) {
665
+ return this.post(`/agent-sessions/${pathPart(sessionId)}/campaign-commits`, {
666
+ idempotency_key: options.idempotencyKey,
667
+ expected_state_version: options.expectedStateVersion,
668
+ candidates
669
+ });
670
+ }
671
+ async getAgentCampaignCommit(sessionId, commitId) {
672
+ return this.get(
673
+ `/agent-sessions/${pathPart(sessionId)}/campaign-commits/${pathPart(commitId)}`
674
+ );
675
+ }
676
+ async getAgentCandidateCommit(sessionId, commitId) {
677
+ return this.get(`/agent-sessions/${pathPart(sessionId)}/commits/${pathPart(commitId)}`);
678
+ }
679
+ async applyAgentCandidateCommit(sessionId, commitId, options) {
680
+ return this.post(
681
+ `/agent-sessions/${pathPart(sessionId)}/commits/${pathPart(commitId)}/apply`,
682
+ {
683
+ idempotency_key: options.idempotencyKey,
684
+ expected_state_version: options.expectedStateVersion,
685
+ push_to_github: options.pushToGithub ?? false
686
+ }
687
+ );
688
+ }
689
+ async waitForAgentCandidateApply(sessionId, commitId, options = {}) {
690
+ const timeoutSeconds = options.timeoutSeconds ?? 300;
691
+ const pollIntervalSeconds = options.pollIntervalSeconds ?? 0.5;
692
+ this.validatePollOptions(timeoutSeconds, pollIntervalSeconds);
693
+ const deadline = Date.now() + timeoutSeconds * 1e3;
694
+ while (true) {
695
+ const commit = await this.getAgentCandidateCommit(sessionId, commitId);
696
+ if (commit.backend_status === "applied" || commit.backend_status === "failed") return commit;
697
+ if (Date.now() >= deadline) {
698
+ throw new Error(
699
+ `Agent candidate commit '${commitId}' was not applied within ${timeoutSeconds} seconds`
700
+ );
701
+ }
702
+ await sleep2(pollIntervalSeconds * 1e3);
703
+ }
704
+ }
705
+ async listForumPosts(options = {}) {
706
+ return this.get(
707
+ "/forum/posts",
708
+ query({
709
+ category: options.category,
710
+ status: options.status,
711
+ chip_id: options.chipId,
712
+ target_type: options.targetType,
713
+ target_id: options.targetId,
714
+ skip: options.skip,
715
+ limit: options.limit
716
+ })
717
+ );
718
+ }
719
+ async createForumPost(request) {
720
+ return this.post("/forum/posts", request);
721
+ }
722
+ async getForumPost(postId) {
723
+ return this.get(`/forum/posts/${pathPart(postId)}`);
724
+ }
725
+ async updateForumPost(postId, request) {
726
+ return this.patch(`/forum/posts/${pathPart(postId)}`, request);
727
+ }
728
+ async getForumPostReplies(postId) {
729
+ return this.get(`/forum/posts/${pathPart(postId)}/replies`);
730
+ }
731
+ async getProvenanceLineage(entityId) {
732
+ return this.get(`/provenance/lineage/${pathPart(entityId)}`);
733
+ }
734
+ async getProvenanceImpact(entityId) {
735
+ return this.get(`/provenance/impact/${pathPart(entityId)}`);
736
+ }
737
+ async getProvenanceStats() {
738
+ return this.get("/provenance/stats");
739
+ }
740
+ bindGeneratedApi(api) {
741
+ const entries = Object.entries(api).map(([name, method]) => {
742
+ const generated = method;
743
+ const bound = (...args) => {
744
+ const optionIndex = Math.max(0, generated.length - 1);
745
+ const callArgs = [...args];
746
+ while (callArgs.length < optionIndex) callArgs.push(void 0);
747
+ const supplied = callArgs[optionIndex];
748
+ const options = typeof supplied === "object" && supplied !== null ? supplied : {};
749
+ callArgs[optionIndex] = { ...options, qdashTransport: this.transport };
750
+ return generated(...callArgs);
751
+ };
752
+ return [name, bound];
753
+ });
754
+ return Object.fromEntries(entries);
755
+ }
756
+ get(path, requestQuery) {
757
+ return this.transport.requestJson("GET", path, { query: requestQuery });
758
+ }
759
+ post(path, body) {
760
+ return this.transport.requestJson("POST", path, { body });
761
+ }
762
+ put(path, body) {
763
+ return this.transport.requestJson("PUT", path, { body });
764
+ }
765
+ patch(path, body) {
766
+ return this.transport.requestJson("PATCH", path, { body });
767
+ }
768
+ async pollAgentAction(sessionId, actionId, field, options) {
769
+ const timeoutSeconds = options.timeoutSeconds ?? 120;
770
+ const pollIntervalSeconds = options.pollIntervalSeconds ?? 0.5;
771
+ this.validatePollOptions(timeoutSeconds, pollIntervalSeconds);
772
+ const deadline = Date.now() + timeoutSeconds * 1e3;
773
+ while (true) {
774
+ const action = await this.getAgentAction(sessionId, actionId);
775
+ if (action.execution_status === "failed" || action[field] != null) return action;
776
+ if (Date.now() >= deadline) {
777
+ throw new Error(
778
+ `Agent action '${actionId}' did not produce ${field} within ${timeoutSeconds} seconds`
779
+ );
780
+ }
781
+ await sleep2(pollIntervalSeconds * 1e3);
782
+ }
783
+ }
784
+ validatePollOptions(timeoutSeconds, pollIntervalSeconds) {
785
+ if (timeoutSeconds < 0 || pollIntervalSeconds < 0) {
786
+ throw new RangeError("Polling timeout and interval must be non-negative");
787
+ }
788
+ }
789
+ };
790
+ export {
791
+ QDashApiError,
792
+ QDashAuthError,
793
+ QDashClient,
794
+ QDashClientError,
795
+ QDashConfig,
796
+ QDashConfigError,
797
+ QDashNotFoundError,
798
+ QDashTransportError,
799
+ QDashValidationError,
800
+ defaultConfigPath,
801
+ getQDashAPI
802
+ };
803
+ //# sourceMappingURL=index.js.map