@covia/covia-sdk 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,18 +8,18 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
8
8
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
9
9
 
10
10
  // src/types.ts
11
- var RunStatus = /* @__PURE__ */ ((RunStatus2) => {
12
- RunStatus2["COMPLETE"] = "COMPLETE";
13
- RunStatus2["FAILED"] = "FAILED";
14
- RunStatus2["PENDING"] = "PENDING";
15
- RunStatus2["STARTED"] = "STARTED";
16
- RunStatus2["CANCELLED"] = "CANCELLED";
17
- RunStatus2["TIMEOUT"] = "TIMEOUT";
18
- RunStatus2["REJECTED"] = "REJECTED";
19
- RunStatus2["INPUT_REQUIRED"] = "INPUT_REQUIRED";
20
- RunStatus2["AUTH_REQUIRED"] = "AUTH_REQUIRED";
21
- RunStatus2["PAUSED"] = "PAUSED";
22
- return RunStatus2;
11
+ var RunStatus = /* @__PURE__ */ ((RunStatus3) => {
12
+ RunStatus3["COMPLETE"] = "COMPLETE";
13
+ RunStatus3["FAILED"] = "FAILED";
14
+ RunStatus3["PENDING"] = "PENDING";
15
+ RunStatus3["STARTED"] = "STARTED";
16
+ RunStatus3["CANCELLED"] = "CANCELLED";
17
+ RunStatus3["TIMEOUT"] = "TIMEOUT";
18
+ RunStatus3["REJECTED"] = "REJECTED";
19
+ RunStatus3["INPUT_REQUIRED"] = "INPUT_REQUIRED";
20
+ RunStatus3["AUTH_REQUIRED"] = "AUTH_REQUIRED";
21
+ RunStatus3["PAUSED"] = "PAUSED";
22
+ return RunStatus3;
23
23
  })(RunStatus || {});
24
24
  var JobStatus = RunStatus;
25
25
  var AgentStatus = /* @__PURE__ */ ((AgentStatus2) => {
@@ -72,6 +72,13 @@ var JobFailedError = class extends CoviaError {
72
72
  this.jobData = jobData;
73
73
  }
74
74
  };
75
+ var RateLimitError = class extends GridError {
76
+ constructor(message, retryAfterSeconds, body) {
77
+ super(429, message, body);
78
+ this.retryAfterSeconds = retryAfterSeconds;
79
+ this.name = "RateLimitError";
80
+ }
81
+ };
75
82
  var NotFoundError = class extends GridError {
76
83
  constructor(message) {
77
84
  super(404, message);
@@ -1309,22 +1316,55 @@ function wrapError(error) {
1309
1316
  }
1310
1317
  return new CoviaError(`Request failed: ${msg}`);
1311
1318
  }
1319
+ var RETRY_MAX_ATTEMPTS = 4;
1320
+ var RETRY_BASE_MS = 200;
1321
+ var RETRY_MAX_DELAY_MS = 1e4;
1322
+ var RETRY_BUDGET_MS = 3e4;
1323
+ function parseRetryAfterMs(header, nowMs) {
1324
+ if (!header) return 0;
1325
+ const secs = Number(header);
1326
+ if (Number.isFinite(secs)) return Math.max(0, secs * 1e3);
1327
+ const date = Date.parse(header);
1328
+ return Number.isFinite(date) ? Math.max(0, date - nowMs) : 0;
1329
+ }
1330
+ function retryDelayMs(attempt, retryAfterMs, remainingBudgetMs, random) {
1331
+ if (attempt >= RETRY_MAX_ATTEMPTS) return -1;
1332
+ const backoff = Math.min(RETRY_MAX_DELAY_MS, RETRY_BASE_MS * 2 ** (attempt - 1));
1333
+ const delay = Math.max(retryAfterMs, Math.floor(random * backoff));
1334
+ return delay > remainingBudgetMs ? -1 : delay;
1335
+ }
1336
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1312
1337
  async function fetchWithError(url, options) {
1313
1338
  const method = options?.method ?? "GET";
1314
- logger.debug(`${method} ${url}`);
1315
- let response;
1316
- try {
1317
- response = await fetch(url, options);
1318
- } catch (error) {
1319
- const msg = error.message ?? String(error);
1320
- logger.debug(`Connection failed: ${method} ${url} \u2014 ${msg}`);
1321
- throw wrapError(error);
1322
- }
1323
- logger.debug(`${method} ${url} \u2192 ${response.status}`);
1324
- if (!response.ok) {
1325
- await throwHttpError(response);
1339
+ const deadline = Date.now() + RETRY_BUDGET_MS;
1340
+ for (let attempt = 1; ; attempt++) {
1341
+ logger.debug(`${method} ${url}`);
1342
+ let response;
1343
+ try {
1344
+ response = await fetch(url, options);
1345
+ } catch (error) {
1346
+ const msg = error.message ?? String(error);
1347
+ logger.debug(`Connection failed: ${method} ${url} \u2014 ${msg}`);
1348
+ throw wrapError(error);
1349
+ }
1350
+ logger.debug(`${method} ${url} \u2192 ${response.status}`);
1351
+ if (response.status === 429) {
1352
+ const now = Date.now();
1353
+ const retryAfterMs = parseRetryAfterMs(response.headers?.get?.("Retry-After") ?? null, now);
1354
+ const delay = retryDelayMs(attempt, retryAfterMs, deadline - now, Math.random());
1355
+ if (delay < 0) {
1356
+ const { message, body } = await parseErrorBody(response);
1357
+ throw new RateLimitError(message, Math.max(1, Math.ceil(retryAfterMs / 1e3)), body);
1358
+ }
1359
+ logger.debug(`429 from ${url}; retrying in ${delay}ms (attempt ${attempt})`);
1360
+ await sleep(delay);
1361
+ continue;
1362
+ }
1363
+ if (!response.ok) {
1364
+ await throwHttpError(response);
1365
+ }
1366
+ return response.json();
1326
1367
  }
1327
- return response.json();
1328
1368
  }
1329
1369
  async function fetchStreamWithError(url, options) {
1330
1370
  const method = options?.method ?? "GET";
@@ -1459,10 +1499,96 @@ async function* parseSSEStream(response) {
1459
1499
  }
1460
1500
  }
1461
1501
 
1502
+ // src/AdapterManager.ts
1503
+ var ADAPTERS_PATH = "v/info/adapters";
1504
+ var LIST_LIMIT = 1e3;
1505
+ function toAdapterInfo(name, value) {
1506
+ return {
1507
+ name: value?.name ?? name ?? "",
1508
+ description: value?.description,
1509
+ operations: Array.isArray(value?.operations) ? value.operations : []
1510
+ };
1511
+ }
1512
+ var AdapterManager = class {
1513
+ constructor(venue) {
1514
+ this.venue = venue;
1515
+ }
1516
+ /**
1517
+ * List the adapters registered on the venue, with their descriptions and
1518
+ * invocable operation catalog paths. Registered-but-inactive or zero-op
1519
+ * adapters are included — this is the true registry, not an inference
1520
+ * from the operations catalog.
1521
+ */
1522
+ async list() {
1523
+ const r = await this.venue.workspace.slice(ADAPTERS_PATH, 0, LIST_LIMIT);
1524
+ if (!r.exists || !Array.isArray(r.values)) return [];
1525
+ const out = [];
1526
+ for (const entry of r.values) {
1527
+ if (!entry || typeof entry !== "object") continue;
1528
+ const key = "key" in entry && typeof entry.key === "string" ? entry.key : void 0;
1529
+ const value = "value" in entry ? entry.value : entry;
1530
+ out.push(toAdapterInfo(key, value));
1531
+ }
1532
+ return out;
1533
+ }
1534
+ /**
1535
+ * Get a single adapter's summary by name.
1536
+ * @param name - Adapter name, e.g. "mcp"
1537
+ * @returns {Promise<AdapterInfo | null>} `null` if no adapter with that name is registered
1538
+ */
1539
+ async get(name) {
1540
+ const r = await this.venue.workspace.read(`${ADAPTERS_PATH}/${encodeURIComponent(name)}`);
1541
+ if (!r.exists) return null;
1542
+ return toAdapterInfo(name, r.value);
1543
+ }
1544
+ };
1545
+
1462
1546
  // src/AgentManager.ts
1547
+ function versionAtLeast(version, major, minor) {
1548
+ const m = version?.match(/^(\d+)\.(\d+)/);
1549
+ if (!m) return true;
1550
+ const [maj, min] = [Number(m[1]), Number(m[2])];
1551
+ return maj > major || maj === major && min >= minor;
1552
+ }
1463
1553
  var AgentManager = class {
1464
1554
  constructor(venue) {
1465
1555
  this.venue = venue;
1556
+ // Whether this venue serves GET /api/v1/agents — flipped on the first 404 so
1557
+ // pre-0.4 venues pay the probe once, not one failed GET per read (covia#180).
1558
+ this.agentsGetSupported = true;
1559
+ }
1560
+ _headers() {
1561
+ const headers = { "Content-Type": "application/json" };
1562
+ this.venue.auth.apply(headers, this.venue.venueId);
1563
+ return headers;
1564
+ }
1565
+ /** Whether the venue serves `GET /api/v1/agents`: no if a probe already
1566
+ * 404'd, or if the venue's last known status identifies it as pre-0.4. */
1567
+ supportsAgentsGet() {
1568
+ if (!this.agentsGetSupported) return false;
1569
+ const status = this.venue.lastKnownStatus;
1570
+ if (status && (!status.version || !versionAtLeast(status.version, 0, 4))) {
1571
+ this.agentsGetSupported = false;
1572
+ }
1573
+ return this.agentsGetSupported;
1574
+ }
1575
+ /** A job-free agents GET, falling back to the invoke path on pre-0.4 venues. */
1576
+ async _agentsOr(path, params, fallback) {
1577
+ if (this.supportsAgentsGet()) {
1578
+ try {
1579
+ const qs = new URLSearchParams();
1580
+ for (const [k, v] of Object.entries(params)) if (v !== void 0) qs.set(k, String(v));
1581
+ const q = qs.toString();
1582
+ return await fetchWithError(
1583
+ `${this.venue.baseUrl}/api/v1/agents${path}${q ? `?${q}` : ""}`,
1584
+ { headers: this._headers() }
1585
+ );
1586
+ } catch (e) {
1587
+ if (!(e instanceof NotFoundError)) throw e;
1588
+ this.agentsGetSupported = false;
1589
+ }
1590
+ }
1591
+ return fallback();
1466
1592
  }
1467
1593
  async create(input) {
1468
1594
  return this.venue.operations.run("v/ops/agent/create", input);
@@ -1495,8 +1621,13 @@ var AgentManager = class {
1495
1621
  async trigger(agentId) {
1496
1622
  return this.venue.operations.run("v/ops/agent/trigger", { agentId });
1497
1623
  }
1624
+ /**
1625
+ * List the caller's agents. **Job-free** on covia ≥ 0.4: goes through
1626
+ * `GET /api/v1/agents` (covia#180) — synchronous, no Job persisted. Older
1627
+ * venues transparently fall back to the invoke path (one probe, remembered).
1628
+ */
1498
1629
  async list(includeTerminated) {
1499
- return this.venue.operations.run("v/ops/agent/list", { includeTerminated });
1630
+ return this._agentsOr("", { includeTerminated }, () => this.venue.operations.run("v/ops/agent/list", { includeTerminated }));
1500
1631
  }
1501
1632
  async delete(agentId, remove) {
1502
1633
  return this.venue.operations.run("v/ops/agent/delete", { agentId, remove });
@@ -1513,8 +1644,10 @@ var AgentManager = class {
1513
1644
  async cancelTask(agentId, taskId) {
1514
1645
  return this.venue.operations.run("v/ops/agent/cancel-task", { agentId, taskId });
1515
1646
  }
1647
+ /** Agent info. **Job-free** on covia ≥ 0.4 (`GET /api/v1/agents/{id}`,
1648
+ * covia#180); older venues fall back to the invoke path. */
1516
1649
  async info(agentId) {
1517
- return this.venue.operations.run("v/ops/agent/info", { agentId });
1650
+ return this._agentsOr(`/${encodeURIComponent(agentId)}`, {}, () => this.venue.operations.run("v/ops/agent/info", { agentId }));
1518
1651
  }
1519
1652
  async fork(input) {
1520
1653
  return this.venue.operations.run("v/ops/agent/fork", input);
@@ -2015,9 +2148,47 @@ var OperationManager = class {
2015
2148
  * @param options - Invoke options (e.g., ucans)
2016
2149
  */
2017
2150
  async run(assetId, input, options) {
2151
+ if (this.venue.privateJobs) {
2152
+ return this._runPrivate(assetId, input, options);
2153
+ }
2018
2154
  const job = await this.invoke(assetId, input, options);
2019
2155
  return await job.result();
2020
2156
  }
2157
+ /**
2158
+ * Private-mode execution (covia #192): a memory-only job whose result is
2159
+ * collected through the invoke `wait` window — a completed private job is
2160
+ * immediately forgotten by the venue, so polling cannot be used. If the
2161
+ * operation outlives the venue's wait cap, polling continues while the job
2162
+ * runs; a job that completes between polls is unobservable and surfaces as
2163
+ * a clear error rather than a confusing 404.
2164
+ */
2165
+ async _runPrivate(assetId, input, options) {
2166
+ const payload = {
2167
+ operation: assetId,
2168
+ input,
2169
+ private: true,
2170
+ wait: true
2171
+ };
2172
+ if (options?.ucans) payload.ucans = options.ucans;
2173
+ const rec = await fetchWithError(`${this.venue.baseUrl}/api/v1/invoke`, {
2174
+ method: "POST",
2175
+ headers: this._buildHeaders(),
2176
+ body: JSON.stringify(payload)
2177
+ });
2178
+ const status = rec.status;
2179
+ if (isJobComplete(status)) return rec.output;
2180
+ if (isJobFinished(status)) {
2181
+ throw new JobFailedError(rec);
2182
+ }
2183
+ try {
2184
+ const job = new Job(rec.id ?? "", this.venue, rec);
2185
+ return await job.result();
2186
+ } catch (e) {
2187
+ throw new CoviaError(
2188
+ `Private job completed while unobserved \u2014 its record is already forgotten, so the result could not be collected. Private jobs that outlive the venue wait window cannot be reliably collected; consider a non-private run for long operations. (${e.message})`
2189
+ );
2190
+ }
2191
+ }
2021
2192
  /**
2022
2193
  * Execute an operation and return a Job for tracking
2023
2194
  * @param assetId - Operation asset ID or named operation
@@ -2025,6 +2196,11 @@ var OperationManager = class {
2025
2196
  * @param options - Invoke options (e.g., ucans)
2026
2197
  */
2027
2198
  async invoke(assetId, input, options) {
2199
+ if (this.venue.privateJobs) {
2200
+ throw new CoviaError(
2201
+ "Private-jobs mode requires run(): a completed private job is immediately forgotten by the venue, so a poll-style Job cannot collect its result."
2202
+ );
2203
+ }
2028
2204
  const payload = {
2029
2205
  operation: assetId,
2030
2206
  input
@@ -2047,7 +2223,7 @@ var OperationManager = class {
2047
2223
  };
2048
2224
 
2049
2225
  // src/WorkspaceManager.ts
2050
- function versionAtLeast(version, major, minor) {
2226
+ function versionAtLeast2(version, major, minor) {
2051
2227
  const m = version?.match(/^(\d+)\.(\d+)/);
2052
2228
  if (!m) return true;
2053
2229
  const [maj, min] = [Number(m[1]), Number(m[2])];
@@ -2080,7 +2256,7 @@ var WorkspaceManager = class {
2080
2256
  supportsValues() {
2081
2257
  if (!this.valuesSupported) return false;
2082
2258
  const status = this.venue.lastKnownStatus;
2083
- if (status && (!status.version || !versionAtLeast(status.version, 0, 3))) {
2259
+ if (status && (!status.version || !versionAtLeast2(status.version, 0, 3))) {
2084
2260
  this.valuesSupported = false;
2085
2261
  }
2086
2262
  return this.valuesSupported;
@@ -2163,6 +2339,16 @@ var UCANManager = class {
2163
2339
  async issue(aud, att, exp) {
2164
2340
  return this.venue.operations.run("v/ops/ucan/issue", { aud, att, exp });
2165
2341
  }
2342
+ /**
2343
+ * Verify a UCAN against the venue's trust policy and get the verdict
2344
+ * explained — validity with a diagnosable reason, chain depth and root
2345
+ * issuer, per-capability root-authority (owner / venue / refused), and,
2346
+ * when `check` is supplied, whether the token would authorise that request
2347
+ * here. The diagnostic counterpart to an enforcement "Access denied".
2348
+ */
2349
+ async verify(token, check) {
2350
+ return this.venue.operations.run("v/ops/ucan/verify", { token, ...check });
2351
+ }
2166
2352
  };
2167
2353
 
2168
2354
  // src/SecretManager.ts
@@ -2299,6 +2485,34 @@ function venueBaseUrlCandidates(venueId) {
2299
2485
  return schemelessVenueCandidates(venueId);
2300
2486
  }
2301
2487
  var Venue = class _Venue {
2488
+ constructor(options = {}) {
2489
+ /** Connection-level private-jobs mode — see {@link setPrivate}. */
2490
+ this.privateJobs = false;
2491
+ this.baseUrl = options.baseUrl || "";
2492
+ this.venueId = options.venueId || "";
2493
+ this.auth = options.auth || new NoAuth();
2494
+ this.lastKnownStatus = options.status;
2495
+ this.metadata = {
2496
+ name: options.name || "default",
2497
+ description: options.description || ""
2498
+ };
2499
+ }
2500
+ /**
2501
+ * Puts this connection in **private-jobs mode**: every subsequent
2502
+ * `operations.run(...)` executes as a memory-only job (covia #192) — never
2503
+ * persisted to the venue's job index, no durable record, gone on venue
2504
+ * restart. Requires `enablePrivateJobs` on the venue.
2505
+ *
2506
+ * Because a completed private job is immediately forgotten, results are
2507
+ * collected through the invoke `wait` window rather than polling — so
2508
+ * private mode works with `run()`; a poll-style `invoke()` throws.
2509
+ */
2510
+ setPrivate(enabled) {
2511
+ this.privateJobs = enabled;
2512
+ }
2513
+ get adapters() {
2514
+ return this._adapters ?? (this._adapters = new AdapterManager(this));
2515
+ }
2302
2516
  get agents() {
2303
2517
  return this._agents ?? (this._agents = new AgentManager(this));
2304
2518
  }
@@ -2320,16 +2534,6 @@ var Venue = class _Venue {
2320
2534
  get secrets() {
2321
2535
  return this._secrets ?? (this._secrets = new SecretManager(this));
2322
2536
  }
2323
- constructor(options = {}) {
2324
- this.baseUrl = options.baseUrl || "";
2325
- this.venueId = options.venueId || "";
2326
- this.auth = options.auth || new NoAuth();
2327
- this.lastKnownStatus = options.status;
2328
- this.metadata = {
2329
- name: options.name || "default",
2330
- description: options.description || ""
2331
- };
2332
- }
2333
2537
  /**
2334
2538
  * Connect to a venue, validating it with `GET {base}/api/v1/status`. If the
2335
2539
  * venue is auth-gated (status answers 401/403 — public access disabled), the
@@ -2561,12 +2765,45 @@ var Grid = class {
2561
2765
  return connectedVenue;
2562
2766
  }
2563
2767
  };
2768
+
2769
+ // src/crypto/ucan.ts
2770
+ var encoder2 = new TextEncoder();
2771
+ var VENUE_RELAY = "venue/relay";
2772
+ function didFor(privateKey) {
2773
+ return `did:key:${encodePublicKey(getPublicKey2(privateKey))}`;
2774
+ }
2775
+ function createUCANJWT(privateKey, audienceDID, att, lifetimeSeconds, proofs = []) {
2776
+ const multikey = encodePublicKey(getPublicKey2(privateKey));
2777
+ const nowSecs = Math.floor(Date.now() / 1e3);
2778
+ const header = JSON.stringify({ alg: "EdDSA", typ: "JWT", kid: multikey });
2779
+ const claims = {
2780
+ iss: `did:key:${multikey}`,
2781
+ aud: audienceDID,
2782
+ att,
2783
+ exp: nowSecs + lifetimeSeconds
2784
+ };
2785
+ if (proofs.length) claims.prf = proofs;
2786
+ const signingInput = `${base64UrlEncode(encoder2.encode(header))}.${base64UrlEncode(encoder2.encode(JSON.stringify(claims)))}`;
2787
+ const signature = sign(encoder2.encode(signingInput), privateKey);
2788
+ return `${signingInput}.${base64UrlEncode(signature)}`;
2789
+ }
2790
+ function identityToken(privateKey, venueDID, lifetimeSeconds = 300) {
2791
+ return createUCANJWT(privateKey, venueDID, [], lifetimeSeconds);
2792
+ }
2793
+ function grant(ownerPrivateKey, audienceDID, withResource, can, lifetimeSeconds) {
2794
+ return createUCANJWT(ownerPrivateKey, audienceDID, [{ with: withResource, can }], lifetimeSeconds);
2795
+ }
2796
+ function relayDelegation(privateKey, venueDID, lifetimeSeconds, caps = []) {
2797
+ const att = [{ with: didFor(privateKey), can: VENUE_RELAY }, ...caps];
2798
+ return createUCANJWT(privateKey, venueDID, att, lifetimeSeconds);
2799
+ }
2564
2800
  /*! Bundled license information:
2565
2801
 
2566
2802
  @noble/ed25519/index.js:
2567
2803
  (*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) *)
2568
2804
  */
2569
2805
 
2806
+ exports.AdapterManager = AdapterManager;
2570
2807
  exports.Agent = Agent;
2571
2808
  exports.AgentManager = AgentManager;
2572
2809
  exports.AgentStatus = AgentStatus;
@@ -2596,14 +2833,18 @@ exports.NoAuth = NoAuth;
2596
2833
  exports.NotFoundError = NotFoundError;
2597
2834
  exports.Operation = Operation;
2598
2835
  exports.OperationManager = OperationManager;
2836
+ exports.RateLimitError = RateLimitError;
2599
2837
  exports.RunStatus = RunStatus;
2600
2838
  exports.SecretManager = SecretManager;
2601
2839
  exports.UCANManager = UCANManager;
2840
+ exports.VENUE_RELAY = VENUE_RELAY;
2602
2841
  exports.Venue = Venue;
2603
2842
  exports.WorkspaceManager = WorkspaceManager;
2604
2843
  exports.assetHash = assetHash;
2605
2844
  exports.createSSEEvent = createSSEEvent;
2845
+ exports.createUCANJWT = createUCANJWT;
2606
2846
  exports.decodePublicKey = decodePublicKey;
2847
+ exports.didFor = didFor;
2607
2848
  exports.didFromPublicKey = didFromPublicKey;
2608
2849
  exports.didMethod = didMethod;
2609
2850
  exports.didUrl = didUrl;
@@ -2614,13 +2855,18 @@ exports.generateKeyPair = generateKeyPair;
2614
2855
  exports.getAssetIdFromPath = getAssetIdFromPath;
2615
2856
  exports.getAssetIdFromVenueId = getAssetIdFromVenueId;
2616
2857
  exports.getParsedAssetId = getParsedAssetId;
2858
+ exports.grant = grant;
2617
2859
  exports.hexToPrivateKey = hexToPrivateKey;
2860
+ exports.identityToken = identityToken;
2618
2861
  exports.isDid = isDid;
2619
2862
  exports.isJobComplete = isJobComplete;
2620
2863
  exports.isJobFinished = isJobFinished;
2621
2864
  exports.isJobPaused = isJobPaused;
2622
2865
  exports.logger = logger;
2623
2866
  exports.parseDidUrl = parseDidUrl;
2867
+ exports.parseRetryAfterMs = parseRetryAfterMs;
2624
2868
  exports.parseSSEStream = parseSSEStream;
2625
2869
  exports.privateKeyToHex = privateKeyToHex;
2870
+ exports.relayDelegation = relayDelegation;
2871
+ exports.retryDelayMs = retryDelayMs;
2626
2872
  exports.venueBaseUrlCandidates = venueBaseUrlCandidates;