codai-sdk 0.4.0 → 0.6.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
@@ -1219,6 +1219,213 @@ var Projects = class {
1219
1219
  }
1220
1220
  };
1221
1221
 
1222
+ // src/resources/resolve.ts
1223
+ function verifyResolveWebhook(secret, rawBody, signatureHeader, opts = {}) {
1224
+ if (!secret || !signatureHeader) return false;
1225
+ let t;
1226
+ const v1s = [];
1227
+ for (const part of signatureHeader.split(",")) {
1228
+ const eq = part.indexOf("=");
1229
+ if (eq === -1) continue;
1230
+ const k = part.slice(0, eq).trim();
1231
+ const v = part.slice(eq + 1).trim();
1232
+ if (k === "t") t = v;
1233
+ else if (k === "v1") v1s.push(v.toLowerCase());
1234
+ }
1235
+ if (!t || !/^\d+$/.test(t) || v1s.length === 0) return false;
1236
+ const tolerance = opts.toleranceSeconds ?? 300;
1237
+ const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
1238
+ if (Math.abs(now - Number(t)) > tolerance) return false;
1239
+ const { createHmac, timingSafeEqual } = nodeCrypto();
1240
+ const enc2 = new TextEncoder();
1241
+ const body = typeof rawBody === "string" ? enc2.encode(rawBody) : rawBody;
1242
+ const expected = createHmac("sha256", secret).update(enc2.encode(`${t}.`)).update(body).digest();
1243
+ return v1s.some((v1) => {
1244
+ if (!/^[0-9a-f]{64}$/.test(v1)) return false;
1245
+ return timingSafeEqual(hexToBytes(v1), expected);
1246
+ });
1247
+ }
1248
+ function nodeCrypto() {
1249
+ const proc = globalThis.process;
1250
+ const mod = proc?.getBuiltinModule?.("node:crypto");
1251
+ if (!mod) {
1252
+ throw new Error(
1253
+ "verifyResolveWebhook needs node:crypto (Node 20.16+/22.3+, Bun or Deno); run it server-side"
1254
+ );
1255
+ }
1256
+ return mod;
1257
+ }
1258
+ function hexToBytes(hex) {
1259
+ const out = new Uint8Array(hex.length / 2);
1260
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1261
+ return out;
1262
+ }
1263
+ var RESOLVE_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
1264
+ "resolved",
1265
+ "failed",
1266
+ "declined",
1267
+ "error"
1268
+ ]);
1269
+ var ResolveError = class extends CodaiError {
1270
+ /** The `error` field of the body: a message, or Zod `flatten()` details on a 400. */
1271
+ detail;
1272
+ constructor(message, status, body, meta = {}) {
1273
+ super(message, status, body, meta);
1274
+ this.name = "ResolveError";
1275
+ this.detail = extractDetail(body);
1276
+ }
1277
+ };
1278
+ var ResolveInsufficientFundsError = class extends ResolveError {
1279
+ /** Wallet balance, in `currency` units (EUR micros). */
1280
+ balance;
1281
+ /** Amount the hold needs, in `currency` units (EUR micros). */
1282
+ required;
1283
+ currency;
1284
+ /** pay.codai.ro checkout link for the shortfall — open it, then retry `accept()`. */
1285
+ topupUrl;
1286
+ constructor(message, body, meta = {}) {
1287
+ super(message, 402, body, meta);
1288
+ this.name = "ResolveInsufficientFundsError";
1289
+ this.balance = body.balance;
1290
+ this.required = body.required;
1291
+ this.currency = body.currency;
1292
+ this.topupUrl = body.topup_url;
1293
+ }
1294
+ };
1295
+ var Resolve = class {
1296
+ constructor(http) {
1297
+ this.http = http;
1298
+ }
1299
+ http;
1300
+ /**
1301
+ * `POST /v1/resolve/jobs` — submit a public GitHub repo + issue for triage.
1302
+ * Returns a quote (`status: 'quoted'`), a free-tier auto-accept
1303
+ * (`status: 'accepted'`) or a decline (`status: 'declined'`). Never retried:
1304
+ * a retry could create a second job.
1305
+ */
1306
+ submit(body, opts = {}) {
1307
+ if (!body.issue_url && !body.issue_text)
1308
+ throw new TypeError("resolve.submit: one of issue_url or issue_text is required");
1309
+ return this.call("/v1/resolve/jobs", {
1310
+ method: "POST",
1311
+ body,
1312
+ noRetry: true,
1313
+ signal: opts.signal
1314
+ });
1315
+ }
1316
+ /** `GET /v1/resolve/jobs/{id}` — status, quote, reasons, cost and attestation pointer. */
1317
+ get(id, opts = {}) {
1318
+ return this.call(`/v1/resolve/jobs/${encodeURIComponent(id)}`, {
1319
+ signal: opts.signal
1320
+ });
1321
+ }
1322
+ /**
1323
+ * `POST /v1/resolve/jobs/{id}/accept` — accept a paid quote and start the job.
1324
+ * Throws `ResolveInsufficientFundsError` (402, carries `topupUrl`) when the
1325
+ * wallet is short, `ResolveError` 409 when the job is not `quoted`.
1326
+ */
1327
+ accept(id, opts = {}) {
1328
+ return this.call(`/v1/resolve/jobs/${encodeURIComponent(id)}/accept`, {
1329
+ method: "POST",
1330
+ rawBody: "",
1331
+ // no body — sent with content-length: 0
1332
+ noRetry: true,
1333
+ signal: opts.signal
1334
+ });
1335
+ }
1336
+ /**
1337
+ * `GET /v1/resolve/attestations/{id}` — public execution proof (no auth needed
1338
+ * server-side). Accepts the attestation id or the job's `attestation_url`.
1339
+ */
1340
+ attestation(idOrUrl, opts = {}) {
1341
+ const id = idOrUrl.replace(/^.*\/v1\/resolve\/attestations\//, "");
1342
+ return this.call(`/v1/resolve/attestations/${encodeURIComponent(id)}`, {
1343
+ signal: opts.signal
1344
+ });
1345
+ }
1346
+ /** `GET /health` — liveness of the Resolve service. */
1347
+ health(opts = {}) {
1348
+ return this.call("/health", { signal: opts.signal });
1349
+ }
1350
+ /**
1351
+ * Poll `get(id)` until the job reaches a terminal status (`resolved`,
1352
+ * `failed`, `declined`, `error`) or one of `opts.until`. A `quoted` job
1353
+ * does not move until you `accept()` it.
1354
+ */
1355
+ async waitFor(id, opts = {}) {
1356
+ const intervalMs = opts.intervalMs ?? 5e3;
1357
+ const timeoutMs = opts.timeoutMs ?? 18e5;
1358
+ const until = new Set(opts.until ?? RESOLVE_TERMINAL_STATUSES);
1359
+ const deadline = Date.now() + timeoutMs;
1360
+ for (; ; ) {
1361
+ const job = await this.get(id, { signal: opts.signal });
1362
+ opts.onPoll?.(job);
1363
+ if (until.has(job.status)) return job;
1364
+ const left = deadline - Date.now();
1365
+ if (left <= 0)
1366
+ throw new ResolveError(
1367
+ `codai-resolve job ${id} still ${job.status} after ${timeoutMs} ms`,
1368
+ 0,
1369
+ job
1370
+ );
1371
+ await abortableSleep(Math.min(intervalMs, left), opts.signal);
1372
+ }
1373
+ }
1374
+ async call(path, init) {
1375
+ try {
1376
+ return await this.http.json(path, init);
1377
+ } catch (err) {
1378
+ throw toResolveError(path, err);
1379
+ }
1380
+ }
1381
+ };
1382
+ function extractDetail(body) {
1383
+ if (body && typeof body === "object" && "error" in body) {
1384
+ const e = body.error;
1385
+ if (typeof e === "string") return e;
1386
+ if (e && typeof e === "object") return e;
1387
+ }
1388
+ return null;
1389
+ }
1390
+ function isInsufficientFunds(body) {
1391
+ return !!body && typeof body === "object" && body.error === "insufficient_funds" && typeof body.topup_url === "string";
1392
+ }
1393
+ function toResolveError(path, err) {
1394
+ if (!(err instanceof CodaiError) || err instanceof ResolveError) return err;
1395
+ if (err.status === 0) return err;
1396
+ const meta = { requestId: err.requestId, retryAfter: err.retryAfter };
1397
+ if (err.status === 402 && isInsufficientFunds(err.body)) {
1398
+ const b = err.body;
1399
+ return new ResolveInsufficientFundsError(
1400
+ `codai-resolve ${path} \u2192 402: insufficient funds (balance ${b.balance}, required ${b.required} ${b.currency}); top up at ${b.topup_url}`,
1401
+ b,
1402
+ meta
1403
+ );
1404
+ }
1405
+ const detail = extractDetail(err.body);
1406
+ const text = detail === null ? "" : `: ${typeof detail === "string" ? detail : JSON.stringify(detail)}`;
1407
+ return new ResolveError(
1408
+ `codai-resolve ${path} \u2192 ${err.status}${text}`,
1409
+ err.status,
1410
+ err.body,
1411
+ meta
1412
+ );
1413
+ }
1414
+ function abortableSleep(ms, signal) {
1415
+ return new Promise((resolve, reject) => {
1416
+ if (signal?.aborted) return reject(signal.reason);
1417
+ const onAbort = () => {
1418
+ clearTimeout(t);
1419
+ reject(signal?.reason);
1420
+ };
1421
+ const t = setTimeout(() => {
1422
+ signal?.removeEventListener("abort", onAbort);
1423
+ resolve();
1424
+ }, ms);
1425
+ signal?.addEventListener("abort", onAbort, { once: true });
1426
+ });
1427
+ }
1428
+
1222
1429
  // src/resources/responses.ts
1223
1430
  var Responses = class {
1224
1431
  constructor(http) {
@@ -1663,6 +1870,14 @@ var Notifications = class {
1663
1870
  markRead(ids, opts = {}) {
1664
1871
  return this.http.json("/v1/notifications/read", { method: "POST", body: ids ? { ids } : {}, ext: opts.ext, signal: opts.signal });
1665
1872
  }
1873
+ /** `DELETE /v1/notifications/:id` — `{ deleted: 0 }` when the id is missing or not yours. */
1874
+ delete(id, opts = {}) {
1875
+ return this.http.json(`/v1/notifications/${encodeURIComponent(String(id))}`, { method: "DELETE", ext: opts.ext, signal: opts.signal });
1876
+ }
1877
+ /** `POST /v1/notifications/delete` — 1..200 ids; ids of other users are skipped. */
1878
+ deleteMany(ids, opts = {}) {
1879
+ return this.http.json("/v1/notifications/delete", { method: "POST", body: { ids }, ext: opts.ext, signal: opts.signal });
1880
+ }
1666
1881
  };
1667
1882
 
1668
1883
  // src/resources/tokens.ts
@@ -1756,6 +1971,8 @@ var OPERATION_METHODS = {
1756
1971
  receiveTriggerHook: "triggers.fireHook",
1757
1972
  listNotifications: "notifications.list",
1758
1973
  markNotificationsRead: "notifications.markRead",
1974
+ deleteNotification: "notifications.delete",
1975
+ deleteNotifications: "notifications.deleteMany",
1759
1976
  // Sessions
1760
1977
  createSession: "sessions.create",
1761
1978
  listSessions: "sessions.list",
@@ -1837,6 +2054,13 @@ var STREAMING_OPERATIONS = /* @__PURE__ */ new Set([
1837
2054
  "streamSession",
1838
2055
  "streamHost"
1839
2056
  ]);
2057
+ var RESOLVE_OPERATION_METHODS = {
2058
+ resolveHealth: "resolve.health",
2059
+ createResolveJob: "resolve.submit",
2060
+ getResolveJob: "resolve.get",
2061
+ acceptResolveJob: "resolve.accept",
2062
+ getResolveAttestation: "resolve.attestation"
2063
+ };
1840
2064
 
1841
2065
  // src/index.ts
1842
2066
  var Codai = class {
@@ -1869,6 +2093,8 @@ var Codai = class {
1869
2093
  receipt;
1870
2094
  feedback;
1871
2095
  phoneModels;
2096
+ /** codai Resolve — execution-verified bug fixes (separate host, `resolveBaseUrl`). */
2097
+ resolve;
1872
2098
  constructor(opts) {
1873
2099
  if (!opts.apiKey) throw new Error("Codai: apiKey is required");
1874
2100
  this.http = new HttpClient({
@@ -1907,6 +2133,16 @@ var Codai = class {
1907
2133
  this.account = new Account(http);
1908
2134
  this.receipt = new Receipts(http);
1909
2135
  this.phoneModels = new PhoneModels(http);
2136
+ this.resolve = new Resolve(
2137
+ new HttpClient({
2138
+ apiKey: opts.apiKey,
2139
+ baseUrl: opts.resolveBaseUrl ?? "https://resolve.codai.ro",
2140
+ timeoutMs: opts.timeoutMs ?? 12e4,
2141
+ maxRetries: opts.maxRetries ?? 2,
2142
+ fetch: opts.fetch,
2143
+ defaults: opts.client ? { client: opts.client } : {}
2144
+ })
2145
+ );
1910
2146
  this.chat = callable(
1911
2147
  new Chat(http),
1912
2148
  (o) => this.chat.completions.create(this.legacyBody(o), {
@@ -1991,6 +2227,6 @@ function allKeys(obj) {
1991
2227
  }
1992
2228
  var index_default = Codai;
1993
2229
 
1994
- export { Account, AgentRuns, Agents, Audio, Chat, ChatCompletions, Codai, CodaiError, Devices, Embeddings, EnvironmentMembers, EnvironmentPorts, EnvironmentSecrets, EnvironmentTasks, Environments, Feedback, Health, Hosts, HttpClient, Messages, Models, Notifications, OPERATION_METHODS, OrgMembers, Orgs, PhoneModels, Projects, Receipts, Responses, STREAMING_OPERATIONS, SessionControls, SessionEvents, SessionLease, SessionShares, Sessions, Tasks, Tokens, Tools, Triggers, index_default as default, extensionHeaders, frameJson, mergeToolCall, readSse };
2230
+ export { Account, AgentRuns, Agents, Audio, Chat, ChatCompletions, Codai, CodaiError, Devices, Embeddings, EnvironmentMembers, EnvironmentPorts, EnvironmentSecrets, EnvironmentTasks, Environments, Feedback, Health, Hosts, HttpClient, Messages, Models, Notifications, OPERATION_METHODS, OrgMembers, Orgs, PhoneModels, Projects, RESOLVE_OPERATION_METHODS, RESOLVE_TERMINAL_STATUSES, Receipts, Resolve, ResolveError, ResolveInsufficientFundsError, Responses, STREAMING_OPERATIONS, SessionControls, SessionEvents, SessionLease, SessionShares, Sessions, Tasks, Tokens, Tools, Triggers, index_default as default, extensionHeaders, frameJson, mergeToolCall, readSse, verifyResolveWebhook };
1995
2231
  //# sourceMappingURL=index.js.map
1996
2232
  //# sourceMappingURL=index.js.map