@oxy-hq/sdk 2.12.0 → 2.16.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.
Files changed (47) hide show
  1. package/README.md +67 -0
  2. package/dist/{function-context-D8eyZuw_.d.cts → function-context-BNpL5bFb.d.cts} +223 -20
  3. package/dist/function-context-BNpL5bFb.d.cts.map +1 -0
  4. package/dist/{function-context-D8eyZuw_.d.mts → function-context-BNpL5bFb.d.mts} +223 -20
  5. package/dist/function-context-BNpL5bFb.d.mts.map +1 -0
  6. package/dist/index.cjs +80 -16
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +164 -3
  9. package/dist/index.d.cts.map +1 -1
  10. package/dist/index.d.mts +164 -3
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +78 -16
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/ops.d.cts +1 -1
  15. package/dist/ops.d.mts +1 -1
  16. package/dist/{react-DW7Z96sD.d.mts → react-CljeXJuw.d.cts} +142 -8
  17. package/dist/react-CljeXJuw.d.cts.map +1 -0
  18. package/dist/{react-DW7Z96sD.d.cts → react-CljeXJuw.d.mts} +142 -8
  19. package/dist/react-CljeXJuw.d.mts.map +1 -0
  20. package/dist/{react-DcT-mUPj.cjs → react-Dvkv2deI.cjs} +110 -59
  21. package/dist/react-Dvkv2deI.cjs.map +1 -0
  22. package/dist/{react-BXGyzgz0.mjs → react-OW1t_J0M.mjs} +103 -26
  23. package/dist/react-OW1t_J0M.mjs.map +1 -0
  24. package/dist/rolldown-runtime-KC0qvQup.cjs +34 -0
  25. package/dist/shell.cjs +38 -3
  26. package/dist/shell.cjs.map +1 -1
  27. package/dist/shell.d.cts +26 -3
  28. package/dist/shell.d.cts.map +1 -1
  29. package/dist/shell.d.mts +26 -3
  30. package/dist/shell.d.mts.map +1 -1
  31. package/dist/shell.mjs +34 -2
  32. package/dist/shell.mjs.map +1 -1
  33. package/dist/testing.cjs +4431 -0
  34. package/dist/testing.cjs.map +1 -0
  35. package/dist/testing.d.cts +656 -0
  36. package/dist/testing.d.cts.map +1 -0
  37. package/dist/testing.d.mts +656 -0
  38. package/dist/testing.d.mts.map +1 -0
  39. package/dist/testing.mjs +4395 -0
  40. package/dist/testing.mjs.map +1 -0
  41. package/package.json +22 -9
  42. package/dist/function-context-D8eyZuw_.d.cts.map +0 -1
  43. package/dist/function-context-D8eyZuw_.d.mts.map +0 -1
  44. package/dist/react-BXGyzgz0.mjs.map +0 -1
  45. package/dist/react-DW7Z96sD.d.cts.map +0 -1
  46. package/dist/react-DW7Z96sD.d.mts.map +0 -1
  47. package/dist/react-DcT-mUPj.cjs.map +0 -1
@@ -87,6 +87,25 @@ async function apiErrorFromResponse(resp) {
87
87
  message: snippet || `HTTP ${resp.status}`
88
88
  });
89
89
  }
90
+ /**
91
+ * Normalize a rejected request into the `Error` a hook reports.
92
+ *
93
+ * An `AbortError` reaching a hook's `.catch` is by construction an abort the
94
+ * hook did NOT cause — its own teardown is marked separately — so it is a
95
+ * fetcher's request timeout, a dropped socket, or a navigation. The platform's
96
+ * wording for those ("The user aborted a request.") is wrong on its face by
97
+ * the time a bundle renders it, so rephrase and keep the original as `cause`.
98
+ *
99
+ * The name is used only to WORD an error already being reported. Whether to
100
+ * report at all is the caller's teardown flag, never the name — that
101
+ * conflation is what left hooks loading forever.
102
+ */
103
+ function asReportableError(err) {
104
+ const name = typeof err === "object" && err !== null && "name" in err ? String(err.name) : "";
105
+ const e = err instanceof Error ? err : new Error(String(err));
106
+ if (name !== "AbortError" && e.name !== "AbortError") return e;
107
+ return Object.assign(/* @__PURE__ */ new Error("request was interrupted (the connection dropped or timed out)"), { cause: e });
108
+ }
90
109
  const ARCH_DOC = "internal-docs/customer-apps.md";
91
110
  /** Interpret a thrown error as a structured report for UI display. */
92
111
  function interpretCustomAppError(err) {
@@ -299,7 +318,7 @@ const FUNCTION_NAME_RE = /^[a-z][a-z0-9-]{0,63}$/;
299
318
  *
300
319
  * This runs at manifest LOAD (app boot / `pnpm dev`), not at `oxy build` — the
301
320
  * build-time gate is the vite plugin's `validateManifest`, which now checks
302
- * function names too. `oxy publish` also validates them locally before esbuild,
321
+ * function names too. `oxyc publish` also validates them locally before esbuild,
303
322
  * so a bad name fails before the upload regardless.
304
323
  */
305
324
  function validateFunctions(raw) {
@@ -338,6 +357,10 @@ function validateFunctions(raw) {
338
357
  if (typeof t !== "number" || !Number.isInteger(t) || t < 1 || t > 300) throw new Error(`oxy-app.json: function "${fnName}" \`timeoutSeconds\` must be an integer in [1, 300]`);
339
358
  fn.timeoutSeconds = t;
340
359
  }
360
+ if (value.check !== void 0) {
361
+ if (typeof value.check !== "boolean") throw new Error(`oxy-app.json: function "${fnName}" \`check\` must be a boolean`);
362
+ fn.check = value.check;
363
+ }
341
364
  if (value.cache !== void 0) {
342
365
  const c = value.cache;
343
366
  if (!isRecord(c)) throw new Error(`oxy-app.json: function "${fnName}" \`cache\` must be an object`);
@@ -364,10 +387,25 @@ function validateFunctions(raw) {
364
387
  }
365
388
  fn.retries = retries;
366
389
  }
390
+ if (value.webhook !== void 0) {
391
+ const w = value.webhook;
392
+ if (!isRecord(w)) throw new Error(`oxy-app.json: function "${fnName}" \`webhook\` must be an object`);
393
+ for (const key of ["secretVar", "signatureHeader"]) if (typeof w[key] !== "string" || !w[key].trim()) throw new Error(`oxy-app.json: function "${fnName}" \`webhook.${key}\` must be a non-empty string`);
394
+ const webhook = {
395
+ secretVar: w.secretVar,
396
+ signatureHeader: w.signatureHeader
397
+ };
398
+ if (w.encoding !== void 0) {
399
+ if (w.encoding !== "hex" && w.encoding !== "base64") throw new Error(`oxy-app.json: function "${fnName}" \`webhook.encoding\` must be "hex" or "base64"`);
400
+ webhook.encoding = w.encoding;
401
+ }
402
+ fn.webhook = webhook;
403
+ }
367
404
  if (value.inputExample !== void 0) fn.inputExample = value.inputExample;
368
405
  const hasSchedule = fn.schedule !== void 0;
369
406
  const hasAirway = fn.airwayStep !== void 0;
370
- if (!(fn.route ?? !(hasSchedule || hasAirway)) && !hasSchedule && !hasAirway) throw new Error(`oxy-app.json: function "${fnName}" must enable at least one of route/schedule/airwayStep`);
407
+ const hasWebhook = fn.webhook !== void 0;
408
+ if (!(fn.route ?? !(hasSchedule || hasAirway || hasWebhook)) && !hasSchedule && !hasAirway && !hasWebhook) throw new Error(`oxy-app.json: function "${fnName}" must enable at least one of route/schedule/airwayStep/webhook`);
371
409
  out[fnName] = fn;
372
410
  }
373
411
  return out;
@@ -621,6 +659,44 @@ async function sharedQuery(fetcher, projectId, sql, db, opts = {}) {
621
659
  return p;
622
660
  }
623
661
 
662
+ //#endregion
663
+ //#region src/custom-app/traceparent.ts
664
+ const HEX = "0123456789abcdef";
665
+ function randomHex(bytes) {
666
+ const buf = new Uint8Array(bytes);
667
+ const c = globalThis.crypto;
668
+ if (c && typeof c.getRandomValues === "function") c.getRandomValues(buf);
669
+ else for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);
670
+ let out = "";
671
+ for (let i = 0; i < bytes; i++) out += HEX[buf[i] >> 4] + HEX[buf[i] & 15];
672
+ return out;
673
+ }
674
+ /** Mint a fresh, sampled `traceparent`. All-zero ids are invalid per the spec;
675
+ * the loop guards the astronomically unlikely draw. */
676
+ function newTraceparent() {
677
+ let traceId = randomHex(16);
678
+ while (/^0+$/.test(traceId)) traceId = randomHex(16);
679
+ let spanId = randomHex(8);
680
+ while (/^0+$/.test(spanId)) spanId = randomHex(8);
681
+ return {
682
+ header: `00-${traceId}-${spanId}-01`,
683
+ traceId
684
+ };
685
+ }
686
+ /**
687
+ * Stamp the ids of a failed invoke onto whatever was thrown, so the app (and
688
+ * the platform's error beacon, which reads `traceId`) can name the trace and
689
+ * the server-minted request id. Non-objects are returned untouched.
690
+ */
691
+ function withInvocationIds(err, traceId, requestId) {
692
+ if (err && typeof err === "object") {
693
+ const target = err;
694
+ if (!target.traceId) target.traceId = traceId;
695
+ if (requestId && !target.requestId) target.requestId = requestId;
696
+ }
697
+ return err;
698
+ }
699
+
624
700
  //#endregion
625
701
  //#region src/custom-app/react.tsx
626
702
  function defaultFetcher(input, init) {
@@ -839,7 +915,7 @@ function useQuery(input, opts = {}) {
839
915
  setState((s) => ({
840
916
  ...s,
841
917
  loading: false,
842
- error: err instanceof Error ? err : new Error(String(err))
918
+ error: asReportableError(err)
843
919
  }));
844
920
  });
845
921
  return () => {
@@ -893,9 +969,11 @@ function useFunction(name) {
893
969
  error: null
894
970
  }));
895
971
  try {
972
+ const trace = newTraceparent();
896
973
  const headers = {
897
974
  "content-type": "application/json",
898
- accept: "text/event-stream"
975
+ accept: "text/event-stream",
976
+ traceparent: trace.header
899
977
  };
900
978
  if (opts?.idempotencyKey) headers["idempotency-key"] = opts.idempotencyKey;
901
979
  const result = await sharedFunctionInvoke(functionInvokeKey(name, body), async () => {
@@ -904,8 +982,13 @@ function useFunction(name) {
904
982
  headers,
905
983
  body: JSON.stringify(body ?? {})
906
984
  });
907
- if (!resp.ok && resp.status !== 200) throw await apiErrorFromResponse(resp);
908
- return readFunctionSseStream(resp);
985
+ const requestId = resp.headers?.get?.("x-oxy-request-id") ?? null;
986
+ if (!resp.ok && resp.status !== 200) throw withInvocationIds(await apiErrorFromResponse(resp), trace.traceId, requestId);
987
+ try {
988
+ return await readFunctionSseStream(resp);
989
+ } catch (err) {
990
+ throw withInvocationIds(err, trace.traceId, requestId);
991
+ }
909
992
  });
910
993
  setState({
911
994
  data: result.value,
@@ -1010,11 +1093,10 @@ function useSemanticQuery(input, opts = {}) {
1010
1093
  });
1011
1094
  }).catch((err) => {
1012
1095
  if (cancelled) return;
1013
- if (err instanceof DOMException && err.name === "AbortError") return;
1014
1096
  setState((s) => ({
1015
1097
  ...s,
1016
1098
  loading: false,
1017
- error: err instanceof Error ? err : new Error(String(err))
1099
+ error: asReportableError(err)
1018
1100
  }));
1019
1101
  });
1020
1102
  return () => {
@@ -1171,13 +1253,13 @@ function useProcedureRun(input, opts = {}) {
1171
1253
  return;
1172
1254
  }
1173
1255
  } catch (e) {
1174
- if (e instanceof DOMException && e.name === "AbortError") return;
1256
+ if (ctrl.signal.aborted) return;
1175
1257
  inflight.current.runId = void 0;
1176
1258
  setState({
1177
1259
  state: "failed",
1178
1260
  progress: null,
1179
1261
  result: null,
1180
- error: e instanceof Error ? e : new Error(String(e))
1262
+ error: asReportableError(e)
1181
1263
  });
1182
1264
  }
1183
1265
  })();
@@ -1278,6 +1360,8 @@ function useAgentRun(input) {
1278
1360
  if (ctrl.signal.aborted) return;
1279
1361
  if (terminated) return;
1280
1362
  attempts += 1;
1363
+ const idBeforeAttempt = lastEventId;
1364
+ let windowError;
1281
1365
  try {
1282
1366
  await consumeSseStream({
1283
1367
  url: `/api/projects/${projectId}/agents/runs/${encodeURIComponent(run_id)}/events`,
@@ -1327,36 +1411,29 @@ function useAgentRun(input) {
1327
1411
  }
1328
1412
  });
1329
1413
  } catch (err) {
1330
- if (err instanceof DOMException && err.name === "AbortError") return;
1331
- if (attempts >= 5) {
1332
- inflight.current.runId = void 0;
1333
- setState((s) => ({
1334
- ...s,
1335
- state: "failed",
1336
- error: err instanceof Error ? err : new Error(String(err))
1337
- }));
1338
- return;
1339
- }
1414
+ if (ctrl.signal.aborted) return;
1415
+ windowError = err;
1340
1416
  }
1341
1417
  if (terminated) return;
1342
- if (attempts >= 5) {
1418
+ if (lastEventId !== idBeforeAttempt) attempts = 0;
1419
+ else if (attempts >= 5) {
1343
1420
  inflight.current.runId = void 0;
1344
1421
  setState((s) => ({
1345
1422
  ...s,
1346
1423
  state: "failed",
1347
- error: /* @__PURE__ */ new Error("run event stream closed without a terminal event")
1424
+ error: windowError ? asReportableError(windowError) : /* @__PURE__ */ new Error("run event stream closed without a terminal event")
1348
1425
  }));
1349
1426
  return;
1350
1427
  }
1351
1428
  await sleep(1e3, ctrl.signal);
1352
1429
  }
1353
1430
  } catch (e) {
1354
- if (e instanceof DOMException && e.name === "AbortError") return;
1431
+ if (ctrl.signal.aborted) return;
1355
1432
  inflight.current.runId = void 0;
1356
1433
  setState((s) => ({
1357
1434
  ...s,
1358
1435
  state: "failed",
1359
- error: e instanceof Error ? e : new Error(String(e))
1436
+ error: asReportableError(e)
1360
1437
  }));
1361
1438
  }
1362
1439
  })();
@@ -2296,5 +2373,5 @@ const styles = {
2296
2373
  };
2297
2374
 
2298
2375
  //#endregion
2299
- export { interpretCustomAppError as _, useFunction as a, useQuery as c, useTrackEvent as d, _resetCustomAppManifestCacheForTest as f, apiErrorFromResponse as g, OxyApiError as h, useAgentRun as i, useResolvedManifest as l, readInjectedAppConfig as m, OxyAppProvider as n, useOxyApp as o, loadCustomAppManifest as p, OxyChat as r, useProcedureRun as s, OxyAnswer as t, useSemanticQuery as u, getOxyAppLogger as v, setOxyAppLogger as y };
2300
- //# sourceMappingURL=react-BXGyzgz0.mjs.map
2376
+ export { asReportableError as _, useFunction as a, setOxyAppLogger as b, useQuery as c, useTrackEvent as d, _resetCustomAppManifestCacheForTest as f, apiErrorFromResponse as g, OxyApiError as h, useAgentRun as i, useResolvedManifest as l, readInjectedAppConfig as m, OxyAppProvider as n, useOxyApp as o, loadCustomAppManifest as p, OxyChat as r, useProcedureRun as s, OxyAnswer as t, useSemanticQuery as u, interpretCustomAppError as v, getOxyAppLogger as y };
2377
+ //# sourceMappingURL=react-OW1t_J0M.mjs.map