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/README.md CHANGED
@@ -65,6 +65,7 @@ surface.
65
65
  | `receipt` | `get` | `GET /v1/receipt` |
66
66
  | `feedback` | `submit` | `POST /v1/feedback` |
67
67
  | `phoneModels` | `list` | `GET /v1/phone/models` |
68
+ | `resolve` | `submit`, `get`, `accept`, `attestation`, `health`, `waitFor` | `https://resolve.codai.ro/v1/resolve/*` (own host) |
68
69
 
69
70
  Every method takes an optional last argument `{ ext, signal }` where `ext` is a
70
71
  typed bag of `X-Codai-*` extension headers (see below).
@@ -158,6 +159,73 @@ const receipt = await codai.receipt.get({ sessionId: 'my-project' });
158
159
  receipt.cost_usd;
159
160
  ```
160
161
 
162
+ ## Resolve — verified bug fixes
163
+
164
+ `client.resolve` talks to the separate Resolve service (`https://resolve.codai.ro`,
165
+ override with `resolveBaseUrl`) with the same API key. You submit a public GitHub
166
+ repo + issue, triage quotes a tier (or declines), you accept, and only a verified
167
+ fix is billed — with a public attestation. Amounts are USD micros.
168
+
169
+ ```ts
170
+ import { ResolveInsufficientFundsError } from 'codai-sdk';
171
+
172
+ const q = await codai.resolve.submit({
173
+ repo_url: 'https://github.com/psf/requests',
174
+ issue_url: 'https://github.com/psf/requests/issues/6000', // or issue_text
175
+ test_command: 'python -m pytest -q tests/test_x.py::test_y', // optional
176
+ });
177
+ if (q.status === 'declined') throw new Error(q.decline_reason);
178
+
179
+ if (q.status === 'quoted') {
180
+ try {
181
+ await codai.resolve.accept(q.id); // free-tier jobs are auto-accepted
182
+ } catch (err) {
183
+ if (err instanceof ResolveInsufficientFundsError) console.log('top up:', err.topupUrl);
184
+ throw err;
185
+ }
186
+ }
187
+
188
+ const job = await codai.resolve.waitFor(q.id, { intervalMs: 5_000, timeoutMs: 30 * 60_000 });
189
+ if (job.status === 'resolved' && job.attestation_url) {
190
+ const proof = await codai.resolve.attestation(job.attestation_url);
191
+ console.log(proof.patch);
192
+ }
193
+ ```
194
+
195
+ Errors are `ResolveError` (a `CodaiError` with `status` and `detail` = the flat
196
+ `error` string or Zod details); a `402` on `accept` is `ResolveInsufficientFundsError`
197
+ with `balance`, `required`, `currency: 'micro_eur'` and `topupUrl`. `submit` and
198
+ `accept` are never retried. `waitFor` stops at `resolved | failed | declined | error`
199
+ (or `until`), and throws `ResolveError` with `status: 0` on timeout.
200
+
201
+ ### Webhooks instead of polling
202
+
203
+ Pass `callback_url` (https, public host) on `submit`; the response carries
204
+ `callback_secret` **once** — store it. Resolve then POSTs `job.quoted`,
205
+ `job.declined`, `job.resolved`, `job.failed` and `job.error` events signed with
206
+ `x-codai-signature: t=<unix>,v1=<hex HMAC-SHA256>`. Verify against the raw body
207
+ with `verifyResolveWebhook` (server-side, uses `node:crypto`; 300 s tolerance by
208
+ default). Up to 3 attempts on `5xx`/`429`/network errors; deduplicate on
209
+ `x-codai-delivery`; delivery never changes job state — `get(id)` stays the truth.
210
+
211
+ ```ts
212
+ import { verifyResolveWebhook, type ResolveWebhookEvent } from 'codai-sdk';
213
+
214
+ const q = await codai.resolve.submit({
215
+ repo_url: 'https://github.com/psf/requests',
216
+ issue_url: 'https://github.com/psf/requests/issues/6000',
217
+ callback_url: 'https://ci.example.com/hooks/codai-resolve',
218
+ });
219
+ await saveSecret(q.id, q.callback_secret!); // shown once
220
+
221
+ // in your webhook handler (Hono / Next route / express.raw):
222
+ const raw = await req.text();
223
+ if (!verifyResolveWebhook(secret, raw, req.headers.get('x-codai-signature'))) {
224
+ return new Response('bad signature', { status: 401 });
225
+ }
226
+ const evt = JSON.parse(raw) as ResolveWebhookEvent; // evt.event, evt.job.attestation_url
227
+ ```
228
+
161
229
  ## Shared sessions, devices and hosts
162
230
 
163
231
  Shared-sessions and hosts calls need a device UUID (`x-codai-device`). Set it once
@@ -240,6 +308,7 @@ const models = await codai.models();
240
308
  const codai = new Codai({
241
309
  apiKey: process.env.CODAI_API_KEY!,
242
310
  baseUrl: 'https://ai.codai.ro', // default
311
+ resolveBaseUrl: 'https://resolve.codai.ro', // default (client.resolve)
243
312
  sessionId: 'my-project', // enables session memory + stickiness
244
313
  device: '<uuid>', // shared sessions / hosts (x-codai-device)
245
314
  client: 'my-app/1.2.0', // x-codai-client surface tag
@@ -287,8 +356,12 @@ import type { components, Task, SessionStreamEvent } from 'codai-sdk';
287
356
  type Receipt = components['schemas']['AccountReceipt'];
288
357
  ```
289
358
 
290
- Regenerate after a spec change with `pnpm gen`; `operations.test.ts` fails when
291
- the spec and the client drift.
359
+ Resolve types are exported as `ResolvePaths`, `ResolveComponents` and
360
+ `ResolveOperations` (plus aliases such as `ResolveJob`, `ResolveQuote`).
361
+
362
+ Regenerate after a spec change with `pnpm gen` (gateway + resolve);
363
+ `operations.test.ts` and `operations.resolve.test.ts` fail when a spec and the
364
+ client drift.
292
365
 
293
366
  ## Migrating from the OpenAI SDK
294
367