@zeroxyz/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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to `@zeroxyz/sdk` will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) — with the pre-1.0 caveat that minor versions may include breaking changes.
6
6
 
7
+ ## 0.6.0
8
+
9
+ ### Added
10
+
11
+ - **Envelope-aware `client.fetch()` routing.** When a request body is Zero's HTTP-transport envelope (`{ input: { type:"http", method, queryParams, body } }` — how many caps store their request contract), `fetch()` now normalizes it into a real wire request: the verb is **carried** (explicit `method` → the envelope's `input.method` → body-presence, in that order) rather than inferred from body-presence, and a GET's `queryParams` are moved onto the URL query string with the body dropped. This stops the SDK from POSTing the ~6,614 indexed GET capabilities that store an MPP-style envelope on an x402 GET endpoint (which 404'd with "Cannot POST"). The envelope is never a wire format, so the normalization is unconditional and protocol-agnostic. (#677)
12
+ - New pure exports for envelope/schema handling, lifted out of the CLI so SDK-direct consumers get them too: `extractInputEnvelope`, `unwrapTransportEnvelope`, `normalizeTransportEnvelopeRequest`, `asSchemaNode`, and the `JsonSchemaNode` type. (#677)
13
+ - **`client.runs.get(runId)`** — fetch a single run's detail. New exports `RunDetail` and `runDetailSchema`. (#667)
14
+
15
+ ## 0.5.0
16
+
17
+ ### Added
18
+
19
+ - Direct-URL `client.fetch()` calls now record a run via a server-side URL→capability resolve, so a bare URL that missed the local cache is still attributable without the caller supplying a `capabilityId`. The resolve runs concurrently with the probe (no added latency) and is fail-open. (#558, ZERO-335)
20
+
7
21
  ## 0.4.0
8
22
 
9
23
  ### Added
package/README.md CHANGED
@@ -104,6 +104,18 @@ Endpoints that don't charge pass straight through: a `200` on the first request
104
104
 
105
105
  **Building the request — search → get → fetch.** A `search()` result carries the `url` and `method`, so a no-body `GET` capability can go straight to `fetch()`. For anything that takes a request body, call `capabilities.get()` first: its `bodySchema`, `method`, and `headers` are what tell you how to construct the call. Don't guess the body from the search summary.
106
106
 
107
+ **The HTTP-transport envelope.** Some capabilities store their request contract as Zero's HTTP-transport envelope — `{ input: { type: "http", method, queryParams, body } }` — rather than a bare body. You don't have to special-case it: `fetch()` detects an envelope body and normalizes it to the real wire request — it **carries the method** (from the envelope's `input.method`, never inferring `POST` from body-presence) and moves a `GET`'s `queryParams` onto the URL query string. So passing the envelope straight through works:
108
+
109
+ ```ts
110
+ // A GET capability whose schema is the envelope — fetch() routes it to
111
+ // GET https://…/weather/current?city=tokyo (not a POST).
112
+ await client.fetch(cap.url, {
113
+ body: JSON.stringify({ input: { type: "http", method: "GET", queryParams: { city: "tokyo" } } }),
114
+ });
115
+ ```
116
+
117
+ If you'd rather inspect the schema yourself, the SDK exports the pure helpers it uses: `extractInputEnvelope(bodySchema, method)` (the query-param / body schema nodes), `unwrapTransportEnvelope(exampleRequest)` (the inner request from a stored example), and `normalizeTransportEnvelopeRequest(url, { method, body })` (the `{ url, method, body }` the wire request should use).
118
+
107
119
  ```ts
108
120
  const result = await client.fetch(url, {
109
121
  method: "POST",
@@ -126,6 +126,78 @@ var ZeroConfigurationError = class extends ZeroError {
126
126
  }
127
127
  };
128
128
 
129
+ // src/input-envelope.ts
130
+ var asSchemaNode = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
131
+ var extractInputEnvelope = (bodySchema, method) => {
132
+ const root = asSchemaNode(bodySchema);
133
+ if (!root) return {};
134
+ const props = asSchemaNode(root.properties);
135
+ if (!props) return {};
136
+ const inputProps = asSchemaNode(asSchemaNode(props.input)?.properties);
137
+ if (inputProps) {
138
+ return {
139
+ queryParams: asSchemaNode(inputProps.queryParams) ?? void 0,
140
+ body: asSchemaNode(inputProps.body) ?? void 0
141
+ };
142
+ }
143
+ const upperMethod = method.toUpperCase();
144
+ const isGetLike = upperMethod === "GET" || upperMethod === "DELETE";
145
+ return isGetLike ? { queryParams: root } : { body: root };
146
+ };
147
+ var unwrapTransportEnvelope = (request2) => {
148
+ const input = asSchemaNode(asSchemaNode(request2)?.input);
149
+ if (!input || input.type !== "http") return request2;
150
+ const body = input.body;
151
+ const hasBody = body !== void 0 && body !== null && !(typeof body === "object" && Object.keys(body).length === 0);
152
+ if (hasBody) return body;
153
+ const qp = asSchemaNode(input.queryParams);
154
+ if (qp && Object.keys(qp).length > 0) return qp;
155
+ return null;
156
+ };
157
+ var GET_LIKE = /* @__PURE__ */ new Set(["GET", "HEAD", "DELETE"]);
158
+ var appendQueryParams = (url, params) => {
159
+ const entries = Object.entries(params).filter(([, v]) => v != null);
160
+ if (entries.length === 0) return url;
161
+ try {
162
+ const u = new URL(url);
163
+ for (const [k, v] of entries) u.searchParams.set(k, String(v));
164
+ return u.toString();
165
+ } catch {
166
+ const qs = entries.map(
167
+ ([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`
168
+ ).join("&");
169
+ return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
170
+ }
171
+ };
172
+ var normalizeTransportEnvelopeRequest = (url, opts) => {
173
+ const { method, body } = opts;
174
+ if (typeof body !== "string") return { url, method, body };
175
+ let parsed;
176
+ try {
177
+ parsed = JSON.parse(body);
178
+ } catch {
179
+ return { url, method, body };
180
+ }
181
+ const input = asSchemaNode(asSchemaNode(parsed)?.input);
182
+ if (!input || input.type !== "http") return { url, method, body };
183
+ const innerBody = input.body;
184
+ const hasInnerBody = innerBody !== void 0 && innerBody !== null && !(typeof innerBody === "object" && Object.keys(innerBody).length === 0);
185
+ const verb = (method ?? (typeof input.method === "string" ? input.method : void 0) ?? (hasInnerBody ? "POST" : "GET")).toUpperCase();
186
+ if (GET_LIKE.has(verb)) {
187
+ const qp = asSchemaNode(input.queryParams);
188
+ return {
189
+ url: qp ? appendQueryParams(url, qp) : url,
190
+ method: verb,
191
+ body: void 0
192
+ };
193
+ }
194
+ return {
195
+ url,
196
+ method: verb,
197
+ body: hasInnerBody ? JSON.stringify(innerBody) : void 0
198
+ };
199
+ };
200
+
129
201
  // src/chains/eip712.ts
130
202
  var KNOWN_EIP712_DOMAINS = {
131
203
  // Base mainnet USDC. On-chain `name()` is "USD Coin" (NOT "USDC")
@@ -1144,7 +1216,7 @@ var Payments = class {
1144
1216
 
1145
1217
  // package.json
1146
1218
  var package_default = {
1147
- version: "0.4.0"};
1219
+ version: "0.6.0"};
1148
1220
 
1149
1221
  // src/version.ts
1150
1222
  var SDK_VERSION = package_default.version;
@@ -1801,6 +1873,11 @@ var capabilityResponseSchema = z.object({
1801
1873
  lastUsedAt: z.string().nullable().optional(),
1802
1874
  lastSuccessfullyRanAt: z.string().nullable().optional()
1803
1875
  });
1876
+ var resolveCapabilityResponseSchema = z.object({
1877
+ capabilityId: z.string(),
1878
+ capabilitySlug: z.string(),
1879
+ matchedVia: z.enum(["exact", "template"])
1880
+ });
1804
1881
 
1805
1882
  // src/namespaces/capabilities.ts
1806
1883
  var Capabilities = class {
@@ -1817,6 +1894,31 @@ var Capabilities = class {
1817
1894
  },
1818
1895
  capabilityResponseSchema
1819
1896
  );
1897
+ // Resolve a raw endpoint URL + method back to the capability that owns it,
1898
+ // for the `zero fetch <url>` case where the client has no local search
1899
+ // context to match against (a memoized/cross-session URL). Returns null on
1900
+ // 404 — i.e. no match or an ambiguous multi-match, both of which the server
1901
+ // reports as 404 (resolveByUrl fails closed rather than mis-attribute).
1902
+ // The URL travels in the POST body so it stays out of access logs and is
1903
+ // never stored; the caller uses the returned capabilityId to record an
1904
+ // attributed run without the URL ever touching the run row (ZERO-335).
1905
+ resolveByUrl = async (url, method, opts = {}) => {
1906
+ try {
1907
+ return await request(
1908
+ this.client,
1909
+ {
1910
+ method: "POST",
1911
+ path: "/v1/capabilities/resolve",
1912
+ body: { url, method },
1913
+ signal: opts.signal
1914
+ },
1915
+ resolveCapabilityResponseSchema
1916
+ );
1917
+ } catch (err) {
1918
+ if (err instanceof ZeroApiError && err.status === 404) return null;
1919
+ throw err;
1920
+ }
1921
+ };
1820
1922
  };
1821
1923
  var createRunResponseSchema = z.object({
1822
1924
  runId: z.string()
@@ -1842,6 +1944,30 @@ var listRunsResponseSchema = z.object({
1842
1944
  runs: z.array(runListItemSchema),
1843
1945
  nextCursor: z.string().nullable()
1844
1946
  });
1947
+ var runDetailSchema = z.object({
1948
+ runId: z.string(),
1949
+ status: z.number().nullable(),
1950
+ latencyMs: z.number().nullable(),
1951
+ errorClass: z.string().nullable(),
1952
+ createdAt: z.coerce.date(),
1953
+ cost: z.object({ amount: z.string(), asset: z.string().nullable() }).nullable(),
1954
+ payment: z.object({
1955
+ protocol: z.string(),
1956
+ chain: z.string().nullable(),
1957
+ txHash: z.string().nullable(),
1958
+ mode: z.string().nullable()
1959
+ }).nullable(),
1960
+ capability: z.object({
1961
+ uid: z.string(),
1962
+ slug: z.string(),
1963
+ name: z.string(),
1964
+ url: z.string(),
1965
+ method: z.string(),
1966
+ whatItDoes: z.string().nullable(),
1967
+ tags: z.array(z.string())
1968
+ }),
1969
+ reviewed: z.boolean()
1970
+ });
1845
1971
  var createReviewResponseSchema = z.object({
1846
1972
  reviewId: z.string(),
1847
1973
  recorded: z.boolean(),
@@ -1923,6 +2049,15 @@ var Runs = class {
1923
2049
  listRunsResponseSchema
1924
2050
  );
1925
2051
  };
2052
+ get = (runId, opts = {}) => request(
2053
+ this.client,
2054
+ {
2055
+ method: "GET",
2056
+ path: `/v1/runs/${runId}`,
2057
+ signal: opts.signal
2058
+ },
2059
+ runDetailSchema
2060
+ );
1926
2061
  review = (input, opts = {}) => request(
1927
2062
  this.client,
1928
2063
  {
@@ -2397,14 +2532,17 @@ var resolveRecordingClient = (client, opts) => {
2397
2532
  const canRecord = opts.account !== void 0 || credentials.kind === "account" || credentials.kind === "session";
2398
2533
  return { recordingClient, canRecord };
2399
2534
  };
2400
- var recordErrorFetchRun = async (client, opts, result) => {
2401
- if (opts.capabilityId === void 0) return;
2535
+ var recordErrorFetchRun = async (client, opts, result, capabilityIdOverride) => {
2536
+ const effectiveCapId = opts.capabilityId;
2537
+ if (effectiveCapId === void 0) return;
2402
2538
  const { recordingClient, canRecord } = resolveRecordingClient(client, opts);
2403
2539
  if (!canRecord) return;
2540
+ const fetchOriginForRecord = opts.capabilityId === void 0 ? "direct_url" : opts.fetchOrigin;
2404
2541
  try {
2405
2542
  const created = await recordingClient.runs.create({
2406
- capabilityId: opts.capabilityId,
2543
+ capabilityId: effectiveCapId,
2407
2544
  ...opts.searchId && { searchId: opts.searchId },
2545
+ ...fetchOriginForRecord && { fetchOrigin: fetchOriginForRecord },
2408
2546
  ...result.status !== null && { status: result.status },
2409
2547
  latencyMs: result.latencyMs,
2410
2548
  ...opts.requestSchema && { requestSchema: opts.requestSchema }
@@ -2414,7 +2552,7 @@ var recordErrorFetchRun = async (client, opts, result) => {
2414
2552
  } catch {
2415
2553
  }
2416
2554
  };
2417
- var recordTimeoutRun = async (client, opts, err, startedAt, capabilityIdRequested) => {
2555
+ var recordTimeoutRun = async (client, opts, err, startedAt, capabilityIdRequested, capabilityIdOverride) => {
2418
2556
  const result = errorFetchResult(
2419
2557
  startedAt,
2420
2558
  err.message,
@@ -2450,12 +2588,23 @@ var withDefaultContentType = (headers, body) => {
2450
2588
  var clientFetch = async (client, url, opts = {}) => {
2451
2589
  const startedAt = Date.now();
2452
2590
  const capabilityIdRequested = opts.capabilityId !== void 0;
2453
- const method = opts.method ?? (opts.body !== void 0 ? "POST" : "GET");
2454
- const probeHeaders = withDefaultContentType(opts.headers, opts.body);
2591
+ const normalized = normalizeTransportEnvelopeRequest(url, {
2592
+ method: opts.method,
2593
+ body: opts.body
2594
+ });
2595
+ const requestUrl = normalized.url;
2596
+ const requestBody = normalized.body;
2597
+ const method = normalized.method ?? (requestBody !== void 0 ? "POST" : "GET");
2598
+ const { canRecord: canRecordForResolve } = resolveRecordingClient(
2599
+ client,
2600
+ opts
2601
+ );
2602
+ const resolvePromise = !capabilityIdRequested && canRecordForResolve ? client.capabilities.resolveByUrl(redactUrl(requestUrl), method).catch(() => null) : Promise.resolve(null);
2603
+ const probeHeaders = withDefaultContentType(opts.headers, requestBody);
2455
2604
  const requestInit = {
2456
2605
  method,
2457
2606
  headers: probeHeaders,
2458
- body: opts.body,
2607
+ body: requestBody,
2459
2608
  signal: opts.signal
2460
2609
  };
2461
2610
  const configuredFetch = buildConfiguredFetch(client, {
@@ -2466,7 +2615,7 @@ var clientFetch = async (client, url, opts = {}) => {
2466
2615
  const skipReasons = [];
2467
2616
  const warnings = [];
2468
2617
  try {
2469
- response = await configuredFetch(url, requestInit);
2618
+ response = await configuredFetch(requestUrl, requestInit);
2470
2619
  } catch (err) {
2471
2620
  if (err instanceof ZeroTimeoutError) {
2472
2621
  await recordTimeoutRun(
@@ -2496,13 +2645,13 @@ var clientFetch = async (client, url, opts = {}) => {
2496
2645
  const payCall = requirement.protocol === "x402" ? client.payments.pay : client.payments.payMpp;
2497
2646
  try {
2498
2647
  const paid = await payCall({
2499
- url,
2648
+ url: requestUrl,
2500
2649
  method,
2501
2650
  // Fresh clone for the paid retry — same rationale as
2502
2651
  // the probe-side clone above. Re-derives the default
2503
2652
  // Content-Type so the paid leg matches the probe.
2504
- headers: withDefaultContentType(opts.headers, opts.body),
2505
- body: opts.body,
2653
+ headers: withDefaultContentType(opts.headers, requestBody),
2654
+ body: requestBody,
2506
2655
  maxPay: opts.maxPay,
2507
2656
  account: opts.account,
2508
2657
  signal: opts.signal,
@@ -2642,7 +2791,15 @@ var clientFetch = async (client, url, opts = {}) => {
2642
2791
  else if (closeFailed)
2643
2792
  skipReasons.push(FETCH_SKIP_REASONS.mppSessionCloseFailedNotRecorded);
2644
2793
  }
2645
- if (opts.capabilityId !== void 0 && !paidButStill402 && !closeFailed) {
2794
+ const resolvedCap = await resolvePromise;
2795
+ const effectiveCapabilityId = opts.capabilityId ?? resolvedCap?.capabilityId;
2796
+ const capabilityResolution = !capabilityIdRequested && canRecordForResolve ? resolvedCap !== null ? {
2797
+ resolved: true,
2798
+ capabilityId: resolvedCap.capabilityId,
2799
+ capabilitySlug: resolvedCap.capabilitySlug,
2800
+ matchedVia: resolvedCap.matchedVia
2801
+ } : { resolved: false } : void 0;
2802
+ if (effectiveCapabilityId !== void 0 && !paidButStill402 && !closeFailed) {
2646
2803
  const { recordingClient, canRecord } = resolveRecordingClient(client, opts);
2647
2804
  if (!canRecord) {
2648
2805
  skipReasons.push("no_credentials");
@@ -2662,8 +2819,11 @@ var clientFetch = async (client, url, opts = {}) => {
2662
2819
  }
2663
2820
  }
2664
2821
  const result2 = await recordingClient.runs.create({
2665
- capabilityId: opts.capabilityId,
2822
+ capabilityId: effectiveCapabilityId,
2666
2823
  searchId: opts.searchId,
2824
+ // Auto-resolved runs are always direct_url — they only fire when
2825
+ // no capabilityId was supplied (the direct_url path by definition).
2826
+ ...opts.capabilityId === void 0 ? { fetchOrigin: "direct_url" } : opts.fetchOrigin && { fetchOrigin: opts.fetchOrigin },
2667
2827
  status: response.status,
2668
2828
  latencyMs,
2669
2829
  ...opts.requestSchema && { requestSchema: opts.requestSchema },
@@ -2696,7 +2856,7 @@ var clientFetch = async (client, url, opts = {}) => {
2696
2856
  message: "Failed to record run for fetch",
2697
2857
  meta: {
2698
2858
  url: redactUrl(url),
2699
- capabilityId: opts.capabilityId,
2859
+ capabilityId: effectiveCapabilityId,
2700
2860
  error: errorMessage2,
2701
2861
  errorCode: code
2702
2862
  }
@@ -2724,6 +2884,8 @@ var clientFetch = async (client, url, opts = {}) => {
2724
2884
  result.runTrackingSkipped = skipReasons;
2725
2885
  }
2726
2886
  if (warnings.length > 0) result.warnings = warnings;
2887
+ if (capabilityResolution !== void 0)
2888
+ result.capabilityResolution = capabilityResolution;
2727
2889
  return result;
2728
2890
  };
2729
2891
  var searchResultSchema = z.object({
@@ -3137,6 +3299,6 @@ var ZeroClient = class _ZeroClient {
3137
3299
  };
3138
3300
  };
3139
3301
 
3140
- export { Auth, AuthDevice, BUG_REPORT_CATEGORIES, BugReports, Capabilities, DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, FETCH_SKIP_REASONS, FETCH_WARNINGS, Payments, Runs, SDK_VERSION, TEMPO_CHAIN_ID, TEMPO_TESTNET_CHAIN_ID, Wallet, ZeroApiError, ZeroAuthError, ZeroClient, ZeroConfigurationError, ZeroError, ZeroPaymentError, ZeroSessionCloseFailedError, ZeroTimeoutError, ZeroValidationError, ZeroWalletError, coerceTempoChainId, createManagedAccount, paymentHasAnchor, tempoChainLabelFromId };
3141
- //# sourceMappingURL=chunk-RPL7VC32.js.map
3142
- //# sourceMappingURL=chunk-RPL7VC32.js.map
3302
+ export { Auth, AuthDevice, BUG_REPORT_CATEGORIES, BugReports, Capabilities, DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, FETCH_SKIP_REASONS, FETCH_WARNINGS, Payments, Runs, SDK_VERSION, TEMPO_CHAIN_ID, TEMPO_TESTNET_CHAIN_ID, Wallet, ZeroApiError, ZeroAuthError, ZeroClient, ZeroConfigurationError, ZeroError, ZeroPaymentError, ZeroSessionCloseFailedError, ZeroTimeoutError, ZeroValidationError, ZeroWalletError, asSchemaNode, coerceTempoChainId, createManagedAccount, extractInputEnvelope, normalizeTransportEnvelopeRequest, paymentHasAnchor, tempoChainLabelFromId, unwrapTransportEnvelope };
3303
+ //# sourceMappingURL=chunk-3YKWO4B2.js.map
3304
+ //# sourceMappingURL=chunk-3YKWO4B2.js.map