stitchkit 0.77.0 → 0.79.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 (99) hide show
  1. package/dist/agent-runtime-browser.js +53 -581
  2. package/dist/agent-runtime-coding-tools.js +6 -5
  3. package/dist/agent-runtime-harness.js +20 -93
  4. package/dist/agent-runtime-openrouter.js +1 -1
  5. package/dist/agent-runtime.js +65 -61
  6. package/dist/application/decisions.d.ts +28 -25
  7. package/dist/application/decisions.d.ts.map +1 -1
  8. package/dist/application/kernel.d.ts +2 -0
  9. package/dist/application/kernel.d.ts.map +1 -1
  10. package/dist/application/keyspace.d.ts +11 -1
  11. package/dist/application/keyspace.d.ts.map +1 -1
  12. package/dist/application/schedule.d.ts.map +1 -1
  13. package/dist/application/schemas.d.ts +2 -0
  14. package/dist/application/schemas.d.ts.map +1 -1
  15. package/dist/application/server-resource.d.ts.map +1 -1
  16. package/dist/application/watch-hub.d.ts.map +1 -1
  17. package/dist/application-diagnostic-journal.d.ts +21 -0
  18. package/dist/application-diagnostic-journal.d.ts.map +1 -0
  19. package/dist/application-diagnostic-journal.js +632 -0
  20. package/dist/application-opentelemetry.js +3 -2
  21. package/dist/application-schemas.js +14 -98
  22. package/dist/application.d.ts +1 -2
  23. package/dist/application.d.ts.map +1 -1
  24. package/dist/application.js +155 -882
  25. package/dist/cli.js +13 -11
  26. package/dist/contract/index.js +15 -10
  27. package/dist/files/boundary.d.ts.map +1 -1
  28. package/dist/files.js +1 -1
  29. package/dist/{index-7b188kmz.js → index-2qzskm8h.js} +15 -1
  30. package/dist/{index-k2zczx1g.js → index-2rwwmr7v.js} +3 -3
  31. package/dist/{index-8eywc9zv.js → index-3gye9wzb.js} +6 -3
  32. package/dist/index-4fpa9dy2.js +51 -0
  33. package/dist/{index-7qy2ex0m.js → index-5mtc969c.js} +2 -2
  34. package/dist/index-5s3zajp8.js +192 -0
  35. package/dist/{index-3cwck0rm.js → index-6s7n2v50.js} +60 -374
  36. package/dist/{index-s4c8wy8m.js → index-758zx2ha.js} +11 -90
  37. package/dist/{index-qyrqwr4c.js → index-77fekveh.js} +4 -1
  38. package/dist/{index-58jzmnn4.js → index-7ag81v2f.js} +355 -34
  39. package/dist/index-7gnkny4z.js +326 -0
  40. package/dist/index-7zbps32p.js +89 -0
  41. package/dist/index-85vfqd7m.js +135 -0
  42. package/dist/{index-m668wzyc.js → index-9s6z8vgf.js} +17 -141
  43. package/dist/{index-bfcpjw20.js → index-actkayzn.js} +7 -3
  44. package/dist/index-b9aefrqd.js +20 -0
  45. package/dist/index-dre2ywck.js +48 -0
  46. package/dist/{index-nemjkxjp.js → index-dwf1f2ap.js} +16 -12
  47. package/dist/{index-wwst0td5.js → index-dx9xehwt.js} +1 -1
  48. package/dist/{index-ezmn6ac6.js → index-dxesg10d.js} +4 -4
  49. package/dist/{index-aczggrty.js → index-dxjjv399.js} +2 -2
  50. package/dist/{index-jqtsc9mj.js → index-egaabxkz.js} +3 -3
  51. package/dist/{index-vc1b0b1b.js → index-evcnmsc3.js} +49 -8
  52. package/dist/{index-nt1mp8km.js → index-fxz0v7vg.js} +84 -84
  53. package/dist/index-h4pj6fta.js +87 -0
  54. package/dist/{index-1rxswfbv.js → index-hb5pf3s4.js} +1 -1
  55. package/dist/{index-t8qyvrvg.js → index-k9ay6v6d.js} +21 -272
  56. package/dist/index-kzfs85xp.js +9 -0
  57. package/dist/{index-vbf2p6me.js → index-m9kzax52.js} +91 -89
  58. package/dist/{index-22by16v6.js → index-mgtxfe6b.js} +1 -1
  59. package/dist/index-p7hgdyhb.js +22 -0
  60. package/dist/index-pq95qkbf.js +0 -0
  61. package/dist/index-t0z5p5w6.js +318 -0
  62. package/dist/{index-s2rchahr.js → index-vwp69871.js} +13 -11
  63. package/dist/index-wc4x00bn.js +103 -0
  64. package/dist/{index-da1aqnhb.js → index-xcetq6j3.js} +8 -6
  65. package/dist/{index-f6n5n7nz.js → index-y01f4223.js} +33 -126
  66. package/dist/{index-35z5h2ty.js → index-ywd0y6m1.js} +18 -218
  67. package/dist/index.js +65 -1588
  68. package/dist/internal/deadline.d.ts +17 -0
  69. package/dist/internal/deadline.d.ts.map +1 -0
  70. package/dist/live.js +15 -89
  71. package/dist/node.js +17 -15
  72. package/dist/observability/context.d.ts.map +1 -1
  73. package/dist/observability/index.js +7 -6
  74. package/dist/primitives.js +5 -2
  75. package/dist/react.js +1 -1
  76. package/dist/remote.js +7 -6
  77. package/dist/server/event-bus.d.ts.map +1 -1
  78. package/dist/server/index.js +26 -38
  79. package/dist/telegram.js +1 -1
  80. package/dist/testing.js +86 -97
  81. package/dist/tool-invoker.js +11 -9
  82. package/dist/tools/async-operation-contract.d.ts +61 -0
  83. package/dist/tools/async-operation-contract.d.ts.map +1 -0
  84. package/dist/tools/async-operation.d.ts +2 -47
  85. package/dist/tools/async-operation.d.ts.map +1 -1
  86. package/dist/tools/view-file-contract.d.ts +98 -0
  87. package/dist/tools/view-file-contract.d.ts.map +1 -0
  88. package/dist/tools/view-file.d.ts +4 -83
  89. package/dist/tools/view-file.d.ts.map +1 -1
  90. package/dist/tools-contract.d.ts +29 -0
  91. package/dist/tools-contract.d.ts.map +1 -0
  92. package/dist/tools-contract.js +24 -0
  93. package/dist/tools.d.ts +3 -3
  94. package/dist/tools.d.ts.map +1 -1
  95. package/dist/tools.js +59 -95
  96. package/llms-full.txt +146 -14
  97. package/package.json +14 -7
  98. package/dist/index-ksp6e2ye.js +0 -24
  99. package/dist/index-vj3vvpaa.js +0 -307
@@ -3,17 +3,29 @@ import {
3
3
  inputIsQuery,
4
4
  parseSSE,
5
5
  readBoundedUtf8Lines
6
- } from "./index-1rxswfbv.js";
6
+ } from "./index-hb5pf3s4.js";
7
+ import {
8
+ createTraceContext,
9
+ formatTraceparent
10
+ } from "./index-fxz0v7vg.js";
7
11
  import {
8
12
  ContractStreamFrameSchema,
9
13
  DEFAULT_CONTRACT_STREAM_FRAME_BYTES,
10
14
  parseTrailingWildcard
11
- } from "./index-vj3vvpaa.js";
15
+ } from "./index-y01f4223.js";
12
16
  import {
13
17
  isRecord,
14
18
  mapObject,
19
+ transportResult,
15
20
  typedEntries
16
- } from "./index-qyrqwr4c.js";
21
+ } from "./index-77fekveh.js";
22
+
23
+ // src/browser/http.ts
24
+ import ky, {
25
+ isHTTPError,
26
+ isNetworkError,
27
+ isTimeoutError
28
+ } from "ky";
17
29
 
18
30
  // src/browser/cancellation.ts
19
31
  class RequestCancellationError extends Error {
@@ -72,13 +84,6 @@ function cancellationError(cause) {
72
84
  return new RequestCancellationError(cause);
73
85
  }
74
86
 
75
- // src/browser/http.ts
76
- import ky, {
77
- isHTTPError,
78
- isNetworkError,
79
- isTimeoutError
80
- } from "ky";
81
-
82
87
  // src/browser/request-id.ts
83
88
  var REQUEST_ID_HEADER = "x-request-id";
84
89
  function responseTraceId(response) {
@@ -114,6 +119,15 @@ class ApiError extends Error {
114
119
  function refuseLocally(path, message) {
115
120
  return new ApiError("VALIDATION_ERROR", 0, { issues: [{ path, code: "invalid_type", message }] }, message);
116
121
  }
122
+ function shouldRetryBunNetworkError(error) {
123
+ if (ApiError.is(error) || isHTTPError(error) || isNetworkError(error) || isTimeoutError(error) || error instanceof RequestCancellationError || error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError")) {
124
+ return;
125
+ }
126
+ if (!(error instanceof Error))
127
+ return;
128
+ const code = Object.getOwnPropertyDescriptor(error, "code");
129
+ return code && "value" in code && code.value === "ConnectionRefused" ? true : undefined;
130
+ }
117
131
  function parseApiErrorBody(body) {
118
132
  if (!isRecord(body) || !isRecord(body.error))
119
133
  return null;
@@ -127,6 +141,221 @@ function parseApiErrorBody(body) {
127
141
  hint: typeof error.hint === "string" ? error.hint : undefined
128
142
  };
129
143
  }
144
+ function createRetryAwareFetch(transportFetch, unix) {
145
+ const runtimeFetch = transportFetch;
146
+ let attempt = 0;
147
+ return (input, init) => {
148
+ attempt += 1;
149
+ if (unix === undefined && attempt === 1) {
150
+ return runtimeFetch(input, init);
151
+ }
152
+ if (!(input instanceof Request)) {
153
+ if (unix === undefined)
154
+ return runtimeFetch(input, init);
155
+ const unixInit = { ...init, unix };
156
+ return runtimeFetch(input, unixInit);
157
+ }
158
+ const streamedBody = input.body ? { body: input.body, duplex: "half" } : {};
159
+ const materialized = {
160
+ ...init,
161
+ ...unix !== undefined && { unix },
162
+ method: input.method,
163
+ headers: input.headers,
164
+ ...streamedBody,
165
+ cache: input.cache,
166
+ credentials: input.credentials,
167
+ integrity: input.integrity,
168
+ keepalive: input.keepalive,
169
+ mode: input.mode,
170
+ redirect: input.redirect,
171
+ referrer: input.referrer,
172
+ referrerPolicy: input.referrerPolicy,
173
+ signal: input.signal
174
+ };
175
+ return runtimeFetch(input.url, materialized);
176
+ };
177
+ }
178
+ function createHttpClient(config) {
179
+ if (config.fetch && config.unix) {
180
+ throw new TypeError("HttpClientConfig.fetch and unix are mutually exclusive");
181
+ }
182
+ if (config.unix !== undefined) {
183
+ if (!config.unix.startsWith("/") || config.unix.includes("\x00")) {
184
+ throw new TypeError("HttpClientConfig.unix must be an absolute Unix socket path");
185
+ }
186
+ if (typeof Reflect.get(globalThis, "Bun") !== "object") {
187
+ throw new TypeError("HttpClientConfig.unix requires Bun; on Bun or Node use createUnixClientTransport().fetch for an explicit portable transport");
188
+ }
189
+ }
190
+ let ssrCookies = null;
191
+ let isLoggedOut = false;
192
+ const listeners = new Set;
193
+ const suppressUnauthorizedFor = config.suppressUnauthorizedFor ?? [];
194
+ const parseError = config.parseError ?? parseApiErrorBody;
195
+ function emit(event) {
196
+ for (const fn of listeners) {
197
+ try {
198
+ fn(event);
199
+ } catch {}
200
+ }
201
+ }
202
+ const client = ky.create({
203
+ prefix: config.baseUrl,
204
+ credentials: config.credentials ?? "include",
205
+ timeout: false,
206
+ retry: {
207
+ limit: config.retry?.limit ?? 2,
208
+ methods: config.retry?.methods ?? ["get"],
209
+ statusCodes: config.retry?.statusCodes ?? [],
210
+ shouldRetry: ({ error }) => shouldRetryBunNetworkError(error)
211
+ },
212
+ hooks: {
213
+ beforeRequest: [
214
+ ({ request: request2 }) => {
215
+ if (ssrCookies) {
216
+ request2.headers.set("Cookie", ssrCookies);
217
+ }
218
+ const extra = typeof config.headers === "function" ? config.headers() : config.headers;
219
+ if (extra) {
220
+ for (const [key, value] of Object.entries(extra)) {
221
+ request2.headers.set(key, value);
222
+ }
223
+ }
224
+ if (config.trace && !request2.headers.has("traceparent")) {
225
+ request2.headers.set("traceparent", formatTraceparent(createTraceContext()));
226
+ }
227
+ }
228
+ ],
229
+ afterResponse: [
230
+ async ({ request: request2, response }) => {
231
+ if (response.status === 401) {
232
+ const url = new URL(request2.url).pathname;
233
+ if (!isLoggedOut && !suppressUnauthorizedFor.some((matches) => matches(url))) {
234
+ isLoggedOut = true;
235
+ emit({ type: "unauthorized" });
236
+ }
237
+ }
238
+ if (!response.ok) {
239
+ const body = await response.clone().json().catch(() => null);
240
+ if (body) {
241
+ const parsed = parseError(body);
242
+ if (parsed) {
243
+ throw new ApiError(parsed.code, response.status, parsed.details, parsed.message, parsed.hint, responseTraceId(response));
244
+ }
245
+ }
246
+ }
247
+ }
248
+ ]
249
+ }
250
+ });
251
+ async function request(method, url, data, options = {}) {
252
+ const cancellation = createRequestCancellation(options.signal, options.timeout ?? config.timeout ?? 30000);
253
+ const kyOptions = {
254
+ fetch: createRetryAwareFetch(config.fetch ?? globalThis.fetch.bind(globalThis), config.unix),
255
+ timeout: false,
256
+ signal: cancellation.signal
257
+ };
258
+ if (options.params) {
259
+ const searchParams = new URLSearchParams;
260
+ for (const [key, value] of Object.entries(options.params)) {
261
+ if (value === undefined)
262
+ continue;
263
+ if (Array.isArray(value)) {
264
+ for (const item of value)
265
+ searchParams.append(key, String(item));
266
+ } else {
267
+ searchParams.set(key, String(value));
268
+ }
269
+ }
270
+ if (searchParams.size > 0) {
271
+ kyOptions.searchParams = searchParams;
272
+ }
273
+ }
274
+ if (data instanceof FormData) {
275
+ kyOptions.body = data;
276
+ } else if (data !== undefined) {
277
+ kyOptions.json = data;
278
+ }
279
+ try {
280
+ return await cancellation.run(async () => {
281
+ if (options.responseType === "blob") {
282
+ return transportResult(await client[method](url, kyOptions).blob());
283
+ }
284
+ if (options.responseType === "response") {
285
+ return transportResult(await client[method](url, kyOptions));
286
+ }
287
+ const response = await client[method](url, kyOptions);
288
+ if (options.responseType === "void") {
289
+ const text = await response.text();
290
+ if (text.length > 0) {
291
+ throw new Error("Server returned data for an endpoint with no output contract");
292
+ }
293
+ }
294
+ if (options.responseType === "void" || response.status === 204 || response.headers.get("content-length") === "0") {
295
+ return transportResult(undefined);
296
+ }
297
+ return await response.json();
298
+ });
299
+ } catch (error) {
300
+ if (error instanceof RequestCancellationError) {
301
+ throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
302
+ }
303
+ if (ApiError.is(error))
304
+ throw error;
305
+ emit({ type: "network_error" });
306
+ const response = isHTTPError(error) ? error.response : undefined;
307
+ const status = response?.status ?? 0;
308
+ const msg = error instanceof Error ? error.message : undefined;
309
+ throw new ApiError("UNKNOWN_ERROR", status, msg ? { message: msg } : undefined, msg, undefined, responseTraceId(response), { cause: error });
310
+ }
311
+ }
312
+ return {
313
+ baseUrl: config.baseUrl,
314
+ get: (url, options) => request("get", url, undefined, options),
315
+ head: (url, options) => request("head", url, undefined, options),
316
+ post: (url, data, options) => request("post", url, data, options),
317
+ put: (url, data, options) => request("put", url, data, options),
318
+ patch: (url, data, options) => request("patch", url, data, options),
319
+ delete: (url, options) => request("delete", url, undefined, options),
320
+ setServerContext(cookies) {
321
+ ssrCookies = cookies;
322
+ },
323
+ subscribe(listener) {
324
+ listeners.add(listener);
325
+ return () => listeners.delete(listener);
326
+ },
327
+ logout() {
328
+ isLoggedOut = true;
329
+ emit({ type: "logout" });
330
+ },
331
+ resetLogoutState() {
332
+ isLoggedOut = false;
333
+ }
334
+ };
335
+ }
336
+ // src/browser/stream.ts
337
+ function parseFailure(raw, error, onParseError) {
338
+ const failure = error instanceof Error ? error : new Error(String(error));
339
+ if (!onParseError)
340
+ throw failure;
341
+ onParseError(raw, failure);
342
+ }
343
+ async function* parseNDJSON(response, options) {
344
+ try {
345
+ for await (const rawLine of readBoundedUtf8Lines(response, options?.maxLineBytes ?? DEFAULT_STREAM_LINE_BYTES, options?.finalLine === "require-newline")) {
346
+ const line = (rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine).trim();
347
+ if (line === "")
348
+ continue;
349
+ try {
350
+ yield JSON.parse(line);
351
+ } catch (error) {
352
+ parseFailure(line, error, options?.onParseError);
353
+ }
354
+ }
355
+ } catch (error) {
356
+ parseFailure("", error, options?.onParseError);
357
+ }
358
+ }
130
359
 
131
360
  // src/browser/client-multipart.ts
132
361
  function isFileDescriptor(value) {
@@ -217,6 +446,52 @@ function resolvePathPrefix(config, args) {
217
446
  }
218
447
  return config.pathPrefix(args);
219
448
  }
449
+ function escapeRegex(value) {
450
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
451
+ }
452
+ function decodePathSegment(value) {
453
+ try {
454
+ return decodeURIComponent(value);
455
+ } catch {
456
+ return value;
457
+ }
458
+ }
459
+ function createClientRouteMatcher(endpoint, contractPrefix, config) {
460
+ if (typeof config?.pathPrefix === "function" && (!config.stripPrefixKeys || config.stripPrefixKeys.length === 0)) {
461
+ throw new Error("Dynamic pathPrefix matchers require stripPrefixKeys");
462
+ }
463
+ const markerByKey = {};
464
+ for (const [index, key] of (config?.stripPrefixKeys ?? []).entries()) {
465
+ markerByKey[key] = `__stitch_scope_${index}__`;
466
+ }
467
+ const pathPrefix = resolvePathPrefix(config, markerByKey);
468
+ const route = [pathPrefix, contractPrefix, endpoint.path === "/" ? "" : endpoint.path].filter(Boolean).join("/");
469
+ const patternSegments = route.split("/").filter(Boolean);
470
+ const wildcard = patternSegments.at(-1)?.startsWith("*") === true;
471
+ const fixedCount = wildcard ? patternSegments.length - 1 : patternSegments.length;
472
+ const markers = Object.values(markerByKey);
473
+ return (pathname) => {
474
+ const actualSegments = pathname.split("/").filter(Boolean).map(decodePathSegment);
475
+ if (wildcard ? actualSegments.length < fixedCount : actualSegments.length !== fixedCount) {
476
+ return false;
477
+ }
478
+ for (let index = 0;index < fixedCount; index += 1) {
479
+ const pattern = patternSegments[index];
480
+ const actual = actualSegments[index];
481
+ if (!pattern || actual === undefined)
482
+ return false;
483
+ if (pattern.startsWith(":"))
484
+ continue;
485
+ let source = escapeRegex(pattern);
486
+ for (const marker of markers) {
487
+ source = source.replaceAll(escapeRegex(marker), "[^/]+");
488
+ }
489
+ if (!new RegExp(`^${source}$`).test(actual))
490
+ return false;
491
+ }
492
+ return true;
493
+ };
494
+ }
220
495
  function extractParamNames(path) {
221
496
  const matches = path.match(/:(\w+)/g);
222
497
  const names = matches ? matches.map((match) => match.slice(1)) : [];
@@ -290,29 +565,6 @@ function joinClientBaseUrl(baseUrl, relativeUrl) {
290
565
  const path = relativeUrl.startsWith("/") ? relativeUrl : `/${relativeUrl}`;
291
566
  return `${base}${path}`;
292
567
  }
293
- // src/browser/stream.ts
294
- function parseFailure(raw, error, onParseError) {
295
- const failure = error instanceof Error ? error : new Error(String(error));
296
- if (!onParseError)
297
- throw failure;
298
- onParseError(raw, failure);
299
- }
300
- async function* parseNDJSON(response, options) {
301
- try {
302
- for await (const rawLine of readBoundedUtf8Lines(response, options?.maxLineBytes ?? DEFAULT_STREAM_LINE_BYTES, options?.finalLine === "require-newline")) {
303
- const line = (rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine).trim();
304
- if (line === "")
305
- continue;
306
- try {
307
- yield JSON.parse(line);
308
- } catch (error) {
309
- parseFailure(line, error, options?.onParseError);
310
- }
311
- }
312
- } catch (error) {
313
- parseFailure("", error, options?.onParseError);
314
- }
315
- }
316
568
 
317
569
  // src/browser/contract-stream.ts
318
570
  function sourceFor(response, descriptor) {
@@ -448,6 +700,25 @@ function withOutput(endpoint, result, abortStream) {
448
700
  return schema.parse(value);
449
701
  });
450
702
  }
703
+ function contractEndpointMatchers(contract, endpointNames, contractConfig) {
704
+ const selected = endpointNames ? new Set(endpointNames) : null;
705
+ const matchers = [];
706
+ for (const [key, endpoint] of typedEntries(contract.endpoints)) {
707
+ if (selected && !selected.has(key))
708
+ continue;
709
+ if (endpoint.expose && !endpoint.expose.includes("HTTP")) {
710
+ if (selected) {
711
+ throw new Error(`Cannot create an HTTP route matcher for non-HTTP endpoint: ${String(key)}`);
712
+ }
713
+ continue;
714
+ }
715
+ matchers.push(createClientRouteMatcher(endpoint, contract.meta.prefix, contractConfig));
716
+ }
717
+ if (selected && matchers.length !== selected.size) {
718
+ throw new Error("Cannot create an HTTP route matcher for an unknown endpoint");
719
+ }
720
+ return matchers;
721
+ }
451
722
  function createClient(contract, configOrClient, contractConfig) {
452
723
  const client = {};
453
724
  const makeExecutor = isHttpAdapter(configOrClient) ? (endpoint) => createHttpExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig);
@@ -461,6 +732,34 @@ function createClient(contract, configOrClient, contractConfig) {
461
732
  function createClients(contracts, configOrClient, contractConfig) {
462
733
  return mapObject(contracts, (_key, contract) => createClient(contract, configOrClient, contractConfig));
463
734
  }
735
+ function buildScopedRegistry(contracts, scopeConfigs, surfaceName, build) {
736
+ const registry = {};
737
+ for (const [namespace, value] of Object.entries(contracts)) {
738
+ const list = Array.isArray(value) ? value : [value];
739
+ const surface = {};
740
+ for (const contract of list) {
741
+ const scope = contract.meta.scope;
742
+ if (!scope) {
743
+ throw new Error(`Contract in ${surfaceName} namespace "${namespace}" has no scope`);
744
+ }
745
+ const scopeConfig = scopeConfigs[scope];
746
+ if (!scopeConfig) {
747
+ throw new Error(`Missing ${surfaceName} config for scope: ${scope}`);
748
+ }
749
+ for (const [methodName, method] of Object.entries(build(contract, scopeConfig))) {
750
+ if (Object.hasOwn(surface, methodName)) {
751
+ throw new Error(`${surfaceName[0]?.toUpperCase()}${surfaceName.slice(1)} namespace "${namespace}" has duplicate method: ${methodName}`);
752
+ }
753
+ surface[methodName] = method;
754
+ }
755
+ }
756
+ registry[namespace] = surface;
757
+ }
758
+ return registry;
759
+ }
760
+ function createScopedClients(contracts, configOrClient, scopeConfigs) {
761
+ return buildScopedRegistry(contracts, scopeConfigs, "client", (contract, config) => createClient(contract, configOrClient, config));
762
+ }
464
763
  function isHttpAdapter(value) {
465
764
  return typeof value === "object" && "get" in value && typeof value.get === "function";
466
765
  }
@@ -633,5 +932,27 @@ async function throwForErrorResponse(res, config, fallbackBody) {
633
932
  }
634
933
  throw new ApiError("HTTP_ERROR", res.status, { body }, undefined, undefined, responseTraceId(res));
635
934
  }
935
+ function createUrlBuilder(contract, source, contractConfig) {
936
+ const builder = {};
937
+ for (const [key, endpoint] of typedEntries(contract.endpoints)) {
938
+ if (endpoint.expose && !endpoint.expose.includes("HTTP"))
939
+ continue;
940
+ setClientMethod(builder, key, (args) => {
941
+ const plan = planClientRequest(endpoint, contract.meta.prefix, args ?? {}, contractConfig);
942
+ if (endpoint.method !== "GET" && endpoint.method !== "DELETE" && Object.keys(plan.remainingArgs).length > 0) {
943
+ const fields = Object.keys(plan.remainingArgs).join(", ");
944
+ throw new Error(`URL builder for ${endpoint.method} ${endpoint.path} received non-URL fields: ${fields}`);
945
+ }
946
+ return joinClientBaseUrl(source.baseUrl, plan.relativeUrl);
947
+ });
948
+ }
949
+ return builder;
950
+ }
951
+ function createUrlBuilders(contracts, source, contractConfig) {
952
+ return mapObject(contracts, (_key, contract) => createUrlBuilder(contract, source, contractConfig));
953
+ }
954
+ function createScopedUrlBuilders(contracts, source, scopeConfigs) {
955
+ return buildScopedRegistry(contracts, scopeConfigs, "URL builder", (contract, config) => createUrlBuilder(contract, source, config));
956
+ }
636
957
 
637
- export { ApiError, createClient, createClients };
958
+ export { ApiError, createHttpClient, parseNDJSON, contractEndpointMatchers, createClient, createClients, createScopedClients, createUrlBuilder, createUrlBuilders, createScopedUrlBuilders };