@contractkit/plugin-typescript 0.30.0 → 0.31.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
@@ -1105,7 +1105,7 @@ function pascalToDotCase(name) {
1105
1105
  __name(pascalToDotCase, "pascalToDotCase");
1106
1106
 
1107
1107
  // src/codegen-operation.ts
1108
- import { resolveModifiers, resolveSecurity, SECURITY_NONE, classifyContentType } from "@contractkit/core";
1108
+ import { resolveModifiers, resolveSecurity, SECURITY_NONE, classifyContentType, emittedResponses } from "@contractkit/core";
1109
1109
  import { basename, dirname as dirname2, relative as relative2 } from "path";
1110
1110
  function bodyParserToken(contentType) {
1111
1111
  switch (classifyContentType(contentType)) {
@@ -1182,20 +1182,6 @@ function generateOp(root, options = {}) {
1182
1182
  const types = collectTypes(root, options.modelsWithInput, options.modelsWithOutput);
1183
1183
  const services = collectServices(root);
1184
1184
  const routerName = deriveRouterName(root.file);
1185
- const helpers = [];
1186
- if (opNeedsScalar(root, "binary")) {
1187
- helpers.push(`const _ZodBinary = z.custom<Buffer>((val) => Buffer.isBuffer(val), { error: 'Must be binary data' });`);
1188
- }
1189
- if (opNeedsScalar(root, "datetime")) {
1190
- helpers.push(`const _ZodDatetime = z.preprocess((val) => typeof val === 'string' ? DateTime.fromISO(val) : val, z.custom<DateTime>((val) => val instanceof DateTime && val.isValid, { message: 'Must be in ISO 8601 format' }));`);
1191
- }
1192
- if (opNeedsScalar(root, "interval")) {
1193
- helpers.push(`const _ZodInterval = z.preprocess((val) => typeof val === 'string' ? Interval.fromISO(val) : val, z.custom<Interval>((val) => val instanceof Interval && val.isValid, { message: 'Must be an ISO 8601 interval' })).transform(val => val.toISO()!);`);
1194
- }
1195
- if (opNeedsScalar(root, "json")) {
1196
- helpers.push(`type _JsonValue = string | number | boolean | null | _JsonValue[] | { [key: string]: _JsonValue };`);
1197
- helpers.push(`const _ZodJson: z.ZodType<_JsonValue> = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(_ZodJson), z.record(z.string(), _ZodJson)]));`);
1198
- }
1199
1185
  const lines = [];
1200
1186
  lines.push("");
1201
1187
  lines.push("/**");
@@ -1212,6 +1198,25 @@ function generateOp(root, options = {}) {
1212
1198
  lines.push("");
1213
1199
  }
1214
1200
  }
1201
+ const handlerBody = lines.join("\n");
1202
+ const references = /* @__PURE__ */ __name((symbol) => new RegExp(`\\b${symbol}\\b`).test(handlerBody), "references");
1203
+ const helpers = [];
1204
+ if (references("_ZodBinary")) {
1205
+ helpers.push(`const _ZodBinary = z.custom<Buffer>((val) => Buffer.isBuffer(val), { error: 'Must be binary data' });`);
1206
+ }
1207
+ if (references("_ZodDatetime")) {
1208
+ helpers.push(`const _ZodDatetime = z.preprocess((val) => typeof val === 'string' ? DateTime.fromISO(val) : val, z.custom<DateTime>((val) => val instanceof DateTime && val.isValid, { message: 'Must be in ISO 8601 format' }));`);
1209
+ }
1210
+ if (references("_ZodInterval")) {
1211
+ helpers.push(`const _ZodInterval = z.preprocess((val) => typeof val === 'string' ? Interval.fromISO(val) : val, z.custom<Interval>((val) => val instanceof Interval && val.isValid, { message: 'Must be an ISO 8601 interval' })).transform(val => val.toISO()!);`);
1212
+ }
1213
+ const needsZodJson = references("_ZodJson");
1214
+ if (needsZodJson || references("_JsonValue")) {
1215
+ helpers.push(`type _JsonValue = string | number | boolean | null | _JsonValue[] | { [key: string]: _JsonValue };`);
1216
+ }
1217
+ if (needsZodJson) {
1218
+ helpers.push(`const _ZodJson: z.ZodType<_JsonValue> = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(_ZodJson), z.record(z.string(), _ZodJson)]));`);
1219
+ }
1215
1220
  const generated = [
1216
1221
  ...helpers.length ? [
1217
1222
  "",
@@ -1336,50 +1341,161 @@ function generateHandler(route, op, root, options) {
1336
1341
  lines.push("");
1337
1342
  }
1338
1343
  }
1339
- const primaryResponse2 = op.responses.find((r) => r.bodyType) ?? op.responses[0];
1344
+ const emitted = emittedResponses(op);
1340
1345
  const serviceParts = inferService(op, route, file);
1341
- const respHeaders = primaryResponse2?.headers ?? [];
1346
+ const call = `await service.${serviceParts.methodName}(${buildArgs(route, op)})`;
1347
+ if (emitted.length > 1) {
1348
+ lines.push(...generateMultiStatusResult(emitted, serviceParts.className, call, options));
1349
+ } else {
1350
+ lines.push(...generateSingleStatusResult(emitted[0], op, serviceParts.className, call, options));
1351
+ }
1352
+ lines.push(`});`);
1353
+ return lines;
1354
+ }
1355
+ __name(generateHandler, "generateHandler");
1356
+ function generateSingleStatusResult(resp, op, className, call, options) {
1357
+ const lines = [];
1358
+ const bodies = resp ? resp.bodies : [];
1359
+ const respHeaders = resp?.headers ?? [];
1342
1360
  const hasRespHeaders = respHeaders.length > 0;
1343
- const headersAnnotation = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, options.modelsWithOutput, "server")}`).join("; ")} }` : "";
1344
- if (primaryResponse2?.bodyType) {
1345
- const { annotation, prelude } = formatTypeAnnotation(primaryResponse2.bodyType, options.modelsWithOutput);
1346
- if (prelude) {
1347
- lines.push(` ${prelude}`);
1348
- }
1349
- lines.push(` const service = ctx.container.get(${serviceParts.className});`);
1361
+ const headersAnnotation = hasRespHeaders ? renderHeadersAnnotation(respHeaders, options.modelsWithOutput) : "";
1362
+ if (bodies.length === 1) {
1363
+ const { annotation, prelude } = formatTypeAnnotation(bodies[0].bodyType, options.modelsWithOutput);
1364
+ if (prelude) lines.push(` ${prelude}`);
1365
+ lines.push(` const service = ctx.container.get(${className});`);
1350
1366
  if (hasRespHeaders) {
1351
- lines.push(` const result: { body: ${annotation}; headers: ${headersAnnotation} } = await service.${serviceParts.methodName}(${buildArgs(route, op)});`);
1367
+ lines.push(` const result: { body: ${annotation}; headers: ${headersAnnotation} } = ${call};`);
1352
1368
  } else {
1353
- lines.push(` const result: ${annotation} = await service.${serviceParts.methodName}(${buildArgs(route, op)});`);
1369
+ lines.push(` const result: ${annotation} = ${call};`);
1354
1370
  }
1371
+ } else if (bodies.length > 1) {
1372
+ const { members, preludes } = renderResponseMembers(resp, options, {
1373
+ includeStatus: false,
1374
+ varPrefix: "result"
1375
+ });
1376
+ for (const prelude of preludes) lines.push(` ${prelude}`);
1377
+ lines.push(` const service = ctx.container.get(${className});`);
1378
+ lines.push(` const result: ${members.join(" | ")} = ${call};`);
1355
1379
  } else {
1356
- lines.push(` const service = ctx.container.get(${serviceParts.className});`);
1380
+ lines.push(` const service = ctx.container.get(${className});`);
1357
1381
  if (hasRespHeaders) {
1358
- lines.push(` const result: { headers: ${headersAnnotation} } = await service.${serviceParts.methodName}(${buildArgs(route, op)});`);
1382
+ lines.push(` const result: { headers: ${headersAnnotation} } = ${call};`);
1359
1383
  } else {
1360
- lines.push(` await service.${serviceParts.methodName}(${buildArgs(route, op)});`);
1384
+ lines.push(` ${call};`);
1361
1385
  }
1362
1386
  }
1363
1387
  lines.push("");
1364
- lines.push(` ctx.status = ${primaryResponse2?.statusCode ?? 200};`);
1365
- if (hasRespHeaders) {
1366
- for (const h of respHeaders) {
1367
- const accessor = `result.headers[${JSON.stringify(headerNameToProperty(h.name))}]`;
1368
- if (h.optional) {
1369
- lines.push(` if (${accessor} !== undefined) ctx.set('${h.name}', String(${accessor}));`);
1370
- } else {
1371
- lines.push(` ctx.set('${h.name}', String(${accessor}));`);
1372
- }
1373
- }
1374
- }
1375
- if (primaryResponse2?.bodyType && primaryResponse2.contentType) {
1376
- lines.push(` ctx.type = '${primaryResponse2.contentType}';`);
1388
+ lines.push(` ctx.status = ${resp?.statusCode ?? op.responses[0]?.statusCode ?? 200};`);
1389
+ lines.push(...headerSetLines(respHeaders, " "));
1390
+ if (bodies.length === 1) {
1391
+ lines.push(` ctx.type = '${bodies[0].contentType}';`);
1377
1392
  lines.push(` ctx.body = ${hasRespHeaders ? "result.body" : "result"};`);
1393
+ } else if (bodies.length > 1) {
1394
+ lines.push(` ctx.type = result.contentType;`);
1395
+ lines.push(` ctx.body = result.body;`);
1378
1396
  }
1379
- lines.push(`});`);
1380
1397
  return lines;
1381
1398
  }
1382
- __name(generateHandler, "generateHandler");
1399
+ __name(generateSingleStatusResult, "generateSingleStatusResult");
1400
+ function generateMultiStatusResult(emitted, className, call, options) {
1401
+ const lines = [];
1402
+ const members = [];
1403
+ const preludes = [];
1404
+ for (const resp of emitted) {
1405
+ const rendered = renderResponseMembers(resp, options, {
1406
+ includeStatus: true,
1407
+ varPrefix: `result${resp.statusCode}`
1408
+ });
1409
+ members.push(...rendered.members);
1410
+ preludes.push(...rendered.preludes);
1411
+ }
1412
+ for (const prelude of preludes) lines.push(` ${prelude}`);
1413
+ lines.push(` const service = ctx.container.get(${className});`);
1414
+ lines.push(` const result:`);
1415
+ for (const member of members) lines.push(` | ${member}`);
1416
+ lines.push(` = ${call};`);
1417
+ lines.push("");
1418
+ lines.push(` ctx.status = result.status;`);
1419
+ lines.push(` switch (result.status) {`);
1420
+ for (const resp of emitted) {
1421
+ lines.push(` case ${resp.statusCode}:`);
1422
+ lines.push(...headerSetLines(resp.headers ?? [], " "));
1423
+ if (resp.bodies.length > 0) {
1424
+ lines.push(` ctx.type = result.contentType;`);
1425
+ lines.push(` ctx.body = result.body;`);
1426
+ }
1427
+ lines.push(` break;`);
1428
+ }
1429
+ lines.push(` }`);
1430
+ return lines;
1431
+ }
1432
+ __name(generateMultiStatusResult, "generateMultiStatusResult");
1433
+ function renderResponseMembers(resp, options, opts) {
1434
+ const bodies = resp.bodies;
1435
+ const headers = resp.headers ?? [];
1436
+ const leading = opts.includeStatus ? [
1437
+ `status: ${resp.statusCode}`
1438
+ ] : [];
1439
+ const trailing = headers.length > 0 ? [
1440
+ `headers: ${renderHeadersAnnotation(headers, options.modelsWithOutput)}`
1441
+ ] : [];
1442
+ const preludes = [];
1443
+ if (bodies.length === 0) {
1444
+ return {
1445
+ members: [
1446
+ `{ ${[
1447
+ ...leading,
1448
+ ...trailing
1449
+ ].join("; ")} }`
1450
+ ],
1451
+ preludes
1452
+ };
1453
+ }
1454
+ const uniform = bodies.every((b) => bodyTypesStructurallyEqual(b.bodyType, bodies[0].bodyType));
1455
+ if (uniform) {
1456
+ const { annotation, prelude } = formatTypeAnnotation(bodies[0].bodyType, options.modelsWithOutput, `${opts.varPrefix}Type`);
1457
+ if (prelude) preludes.push(prelude);
1458
+ const contentType = bodies.map((b) => `'${b.contentType}'`).join(" | ");
1459
+ return {
1460
+ members: [
1461
+ `{ ${[
1462
+ ...leading,
1463
+ `contentType: ${contentType}`,
1464
+ `body: ${annotation}`,
1465
+ ...trailing
1466
+ ].join("; ")} }`
1467
+ ],
1468
+ preludes
1469
+ };
1470
+ }
1471
+ const members = bodies.map((b, i) => {
1472
+ const { annotation, prelude } = formatTypeAnnotation(b.bodyType, options.modelsWithOutput, `${opts.varPrefix}Type${i}`);
1473
+ if (prelude) preludes.push(prelude);
1474
+ return `{ ${[
1475
+ ...leading,
1476
+ `contentType: '${b.contentType}'`,
1477
+ `body: ${annotation}`,
1478
+ ...trailing
1479
+ ].join("; ")} }`;
1480
+ });
1481
+ return {
1482
+ members,
1483
+ preludes
1484
+ };
1485
+ }
1486
+ __name(renderResponseMembers, "renderResponseMembers");
1487
+ function renderHeadersAnnotation(headers, modelsWithOutput) {
1488
+ const fields = headers.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, modelsWithOutput, "server")}`);
1489
+ return `{ ${fields.join("; ")} }`;
1490
+ }
1491
+ __name(renderHeadersAnnotation, "renderHeadersAnnotation");
1492
+ function headerSetLines(headers, indent) {
1493
+ return headers.map((h) => {
1494
+ const accessor = `result.headers[${JSON.stringify(headerNameToProperty(h.name))}]`;
1495
+ return h.optional ? `${indent}if (${accessor} !== undefined) ctx.set('${h.name}', String(${accessor}));` : `${indent}ctx.set('${h.name}', String(${accessor}));`;
1496
+ });
1497
+ }
1498
+ __name(headerSetLines, "headerSetLines");
1383
1499
  function inferService(op, route, file) {
1384
1500
  if (op.service) {
1385
1501
  const [cls = "", method] = op.service.split(".");
@@ -1473,9 +1589,9 @@ function serverTsScalar(name) {
1473
1589
  }
1474
1590
  }
1475
1591
  __name(serverTsScalar, "serverTsScalar");
1476
- function formatTypeAnnotation(bodyType, modelsWithOutput) {
1592
+ function formatTypeAnnotation(bodyType, modelsWithOutput, varName = "resultType") {
1477
1593
  if (bodyType.kind === "array") {
1478
- const inner = formatTypeAnnotation(bodyType.item, modelsWithOutput);
1594
+ const inner = formatTypeAnnotation(bodyType.item, modelsWithOutput, varName);
1479
1595
  return {
1480
1596
  annotation: `${inner.annotation}[]`,
1481
1597
  prelude: inner.prelude
@@ -1492,8 +1608,8 @@ function formatTypeAnnotation(bodyType, modelsWithOutput) {
1492
1608
  };
1493
1609
  const schema = renderType(bodyType);
1494
1610
  return {
1495
- annotation: "z.infer<typeof resultType>",
1496
- prelude: `const resultType = ${schema};`
1611
+ annotation: `z.infer<typeof ${varName}>`,
1612
+ prelude: `const ${varName} = ${schema};`
1497
1613
  };
1498
1614
  }
1499
1615
  __name(formatTypeAnnotation, "formatTypeAnnotation");
@@ -1579,9 +1695,9 @@ function collectTypes(root, modelsWithInput, modelsWithOutput) {
1579
1695
  }
1580
1696
  }
1581
1697
  for (const resp of op.responses) {
1582
- if (resp.bodyType) {
1583
- collectTypeNodeRefs(resp.bodyType, types);
1584
- collectOutputTypeNodeRefs(resp.bodyType, types, modelsWithOutput);
1698
+ for (const body of resp.bodies) {
1699
+ collectTypeNodeRefs(body.bodyType, types);
1700
+ collectOutputTypeNodeRefs(body.bodyType, types, modelsWithOutput);
1585
1701
  }
1586
1702
  if (resp.headers) {
1587
1703
  for (const h of resp.headers) {
@@ -1724,17 +1840,6 @@ function collectTypeNodeRefs(type, out) {
1724
1840
  }
1725
1841
  }
1726
1842
  __name(collectTypeNodeRefs, "collectTypeNodeRefs");
1727
- function paramSourceNeedsScalar(source, name) {
1728
- if (!source) return false;
1729
- if (source.kind === "ref") return false;
1730
- if (source.kind === "params") return source.nodes.some((p) => typeNeedsScalar(p.type, name));
1731
- return typeNeedsScalar(source.node, name);
1732
- }
1733
- __name(paramSourceNeedsScalar, "paramSourceNeedsScalar");
1734
- function opNeedsScalar(root, name) {
1735
- return root.routes.some((route) => paramSourceNeedsScalar(route.params, name) || route.operations.some((op) => !!op.request?.bodies.some((b) => typeNeedsScalar(b.bodyType, name)) || op.responses.some((r) => r.bodyType && typeNeedsScalar(r.bodyType, name)) || paramSourceNeedsScalar(op.query, name) || paramSourceNeedsScalar(op.headers, name)));
1736
- }
1737
- __name(opNeedsScalar, "opNeedsScalar");
1738
1843
  function collectServices(root) {
1739
1844
  const services = /* @__PURE__ */ new Set();
1740
1845
  const inferredService = `${deriveBaseName(root.file)}Service`;
@@ -1788,7 +1893,7 @@ __name(deriveTypeImportPath, "deriveTypeImportPath");
1788
1893
  import { runIncrementalCodegen, parseIncrementalManifest, emptyIncrementalManifest, serializeIncrementalManifest, hashFingerprint, collectTransitiveModelRefs } from "@contractkit/core";
1789
1894
 
1790
1895
  // src/codegen-sdk.ts
1791
- import { resolveModifiers as resolveModifiers2, isJsonMime, classifyContentType as classifyContentType2 } from "@contractkit/core";
1896
+ import { resolveModifiers as resolveModifiers2, isJsonMime, classifyContentType as classifyContentType2, observableResponses, thrownResponses } from "@contractkit/core";
1792
1897
  import { basename as basename2, dirname as dirname3, relative as relative3 } from "path";
1793
1898
  function jsonOrFormSerialize(varName, contentType) {
1794
1899
  if (contentType === "application/x-www-form-urlencoded") {
@@ -1865,16 +1970,17 @@ function generateSdk(root, options = {}) {
1865
1970
  if (sdkNeedsBigIntReplacer(root, includeInternal)) valueImports.push("bigIntReplacer");
1866
1971
  if (sdkNeedsBigIntReviver(root, includeInternal)) valueImports.push("parseJson");
1867
1972
  if (sdkNeedsQueryString(root, includeInternal)) valueImports.push("buildQueryString");
1973
+ if (sdkNeedsReadContentType(root, includeInternal)) valueImports.push("readContentType");
1868
1974
  if (valueImports.length > 0) {
1869
1975
  lines.push(`import { ${valueImports.join(", ")} } from '${rel}';`);
1870
1976
  }
1871
1977
  } else {
1872
1978
  lines.push("");
1873
- lines.push("export class SdkError extends Error {");
1979
+ lines.push("export class SdkError<TBody = unknown> extends Error {");
1874
1980
  lines.push(" constructor(");
1875
1981
  lines.push(" public readonly status: number,");
1876
1982
  lines.push(" public readonly statusText: string,");
1877
- lines.push(" public readonly body: unknown,");
1983
+ lines.push(" public readonly body: TBody,");
1878
1984
  lines.push(" public readonly headers: Headers,");
1879
1985
  lines.push(" ) {");
1880
1986
  lines.push(" super(`${status} ${statusText}`);");
@@ -1882,7 +1988,16 @@ function generateSdk(root, options = {}) {
1882
1988
  lines.push(" }");
1883
1989
  lines.push("}");
1884
1990
  lines.push("");
1885
- lines.push("export type SdkFetch = (url: string, init: RequestInit) => Promise<Response>;");
1991
+ lines.push("export interface SdkRequestInit extends RequestInit {");
1992
+ lines.push(" /**");
1993
+ lines.push(" * Statuses this operation declares as values rather than errors \u2014 a 304 from");
1994
+ lines.push(" * conditional-GET middleware, or an error status the service returns deliberately.");
1995
+ lines.push(" * Anything else at or above 400 still throws SdkError.");
1996
+ lines.push(" */");
1997
+ lines.push(" expectStatuses?: number[];");
1998
+ lines.push("}");
1999
+ lines.push("");
2000
+ lines.push("export type SdkFetch = (url: string, init: SdkRequestInit) => Promise<Response>;");
1886
2001
  lines.push("");
1887
2002
  lines.push("export interface SdkOptions {");
1888
2003
  lines.push(" baseUrl: string;");
@@ -1892,9 +2007,13 @@ function generateSdk(root, options = {}) {
1892
2007
  lines.push(" requestIdFactory?: () => string;");
1893
2008
  lines.push("}");
1894
2009
  lines.push("");
2010
+ lines.push("export function readContentType(res: Response): string {");
2011
+ lines.push(" return res.headers.get('content-type')?.split(';')[0]?.trim() ?? '';");
2012
+ lines.push("}");
2013
+ lines.push("");
1895
2014
  lines.push("export function createSdkFetch(options: SdkOptions): SdkFetch {");
1896
2015
  lines.push(" const getRequestId = options.requestIdFactory ?? (() => crypto.randomUUID());");
1897
- lines.push(" return async (url: string, init: RequestInit): Promise<Response> => {");
2016
+ lines.push(" return async (url: string, init: SdkRequestInit): Promise<Response> => {");
1898
2017
  lines.push(" const baseHeaders = typeof options.headers === 'function'");
1899
2018
  lines.push(" ? await options.headers()");
1900
2019
  lines.push(" : options.headers ?? {};");
@@ -1902,7 +2021,7 @@ function generateSdk(root, options = {}) {
1902
2021
  lines.push(" ...init,");
1903
2022
  lines.push(" headers: { ...baseHeaders, 'X-Request-ID': getRequestId(), ...init.headers as Record<string, string> },");
1904
2023
  lines.push(" });");
1905
- lines.push(" if (!res.ok) {");
2024
+ lines.push(" if (!res.ok && !(init.expectStatuses ?? []).includes(res.status)) {");
1906
2025
  lines.push(" const text = await res.text();");
1907
2026
  lines.push(" let body: unknown;");
1908
2027
  lines.push(" try { body = JSON.parse(text); } catch { body = text; }");
@@ -1933,6 +2052,11 @@ function generateSdk(root, options = {}) {
1933
2052
  lines.push(JSON_VALUE_TYPE_DECL);
1934
2053
  }
1935
2054
  lines.push("");
2055
+ const errorAliases = generateErrorBodyAliases(root, options);
2056
+ if (errorAliases.length > 0) {
2057
+ lines.push(...errorAliases);
2058
+ lines.push("");
2059
+ }
1936
2060
  lines.push("/**");
1937
2061
  const relFile = options.outPath ? relative3(dirname3(options.outPath), root.file) : root.file;
1938
2062
  lines.push(` * generated from [${basename2(root.file)}](file://./${relFile})`);
@@ -1980,19 +2104,37 @@ function generateMethod(route, op, file, options) {
1980
2104
  const { modelsWithInput, modelsWithOutput } = options;
1981
2105
  const params = buildMethodParams(route, op, modelsWithInput);
1982
2106
  const paramStr = params.map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ");
1983
- const primaryResponse2 = op.responses.find((r) => r.bodyType) ?? op.responses[0];
1984
- const isVoid = !primaryResponse2?.bodyType;
1985
- const respCategory = primaryResponse2?.contentType ? classifyContentType2(primaryResponse2.contentType) : "json";
1986
- const dataType = isVoid ? "void" : respCategory === "text" ? "string" : respCategory === "binary" ? "Blob" : renderOutputTsType(primaryResponse2.bodyType, modelsWithOutput);
1987
- const respHeaders = primaryResponse2?.headers ?? [];
2107
+ const observable = observableResponses(op);
2108
+ const thrown = thrownResponses(op);
2109
+ const isMultiStatus = observable.length > 1;
2110
+ const primaryResponse = observable[0];
2111
+ const primaryBodies = primaryResponse ? primaryResponse.bodies : [];
2112
+ const isVoid = primaryBodies.length === 0;
2113
+ const respHeaders = primaryResponse?.headers ?? [];
1988
2114
  const hasRespHeaders = respHeaders.length > 0;
1989
- const headersShape = hasRespHeaders ? `{ ${respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, modelsWithOutput)}`).join("; ")} }` : "";
1990
- const returnType = hasRespHeaders ? isVoid ? `{ headers: ${headersShape} }` : `{ data: ${dataType}; headers: ${headersShape} }` : dataType;
2115
+ const headersShape = hasRespHeaders ? renderSdkHeadersShape(respHeaders, modelsWithOutput) : "";
2116
+ let returnMembers;
2117
+ let returnType = "";
2118
+ if (isMultiStatus) {
2119
+ returnMembers = observable.flatMap((r) => sdkResponseMembers(r, modelsWithOutput, true));
2120
+ } else if (primaryBodies.length > 1) {
2121
+ returnMembers = sdkResponseMembers(primaryResponse, modelsWithOutput, false);
2122
+ } else {
2123
+ const dataType = isVoid ? "void" : sdkDataType(primaryBodies[0], modelsWithOutput);
2124
+ returnType = hasRespHeaders ? isVoid ? `{ headers: ${headersShape} }` : `{ data: ${dataType}; headers: ${headersShape} }` : dataType;
2125
+ }
2126
+ if (returnMembers?.length === 1) {
2127
+ returnType = returnMembers[0];
2128
+ returnMembers = void 0;
2129
+ }
2130
+ const expectStatuses = observable.filter((r) => r.statusCode < 200 || r.statusCode >= 300).map((r) => r.statusCode);
1991
2131
  const desc = op.description ?? route.description;
1992
- if (op.name || desc) {
2132
+ const errorBodyName = thrown.some((r) => r.bodies.length > 0) ? errorBodyTypeName(route, op) : void 0;
2133
+ if (op.name || desc || errorBodyName) {
1993
2134
  const tags = [];
1994
2135
  if (op.name) tags.push(`@name ${op.name}`);
1995
2136
  if (desc) tags.push(`@description ${desc}`);
2137
+ if (errorBodyName) tags.push(`@throws {SdkError<${errorBodyName}>} on ${thrown.map((r) => r.statusCode).join(", ")}`);
1996
2138
  const contentLines = tags.flatMap((t) => escapeJsDocLines(t));
1997
2139
  if (contentLines.length === 1) {
1998
2140
  lines.push(` /** ${contentLines[0]} */`);
@@ -2002,7 +2144,13 @@ function generateMethod(route, op, file, options) {
2002
2144
  lines.push(` */`);
2003
2145
  }
2004
2146
  }
2005
- lines.push(` async ${methodName}(${paramStr}): Promise<${returnType}> {`);
2147
+ if (returnMembers) {
2148
+ lines.push(` async ${methodName}(${paramStr}): Promise<`);
2149
+ for (const member of returnMembers) lines.push(` | ${member}`);
2150
+ lines.push(` > {`);
2151
+ } else {
2152
+ lines.push(` async ${methodName}(${paramStr}): Promise<${returnType}> {`);
2153
+ }
2006
2154
  const urlExpr = buildUrlExpression(route.path, route.params);
2007
2155
  const hasQuery = !!op.query;
2008
2156
  let fetchUrl = urlExpr;
@@ -2062,7 +2210,9 @@ function generateMethod(route, op, file, options) {
2062
2210
  fetchArgs.push("headers: customHeaders");
2063
2211
  }
2064
2212
  }
2065
- const resultPrefix = isVoid && !hasRespHeaders ? "" : "const result = ";
2213
+ if (expectStatuses.length > 0) fetchArgs.push(`expectStatuses: [${expectStatuses.join(", ")}]`);
2214
+ const needsResult = isMultiStatus || !isVoid || hasRespHeaders;
2215
+ const resultPrefix = needsResult ? "const result = " : "";
2066
2216
  if (fetchArgs.length === 2 && !hasBody && !hasOpHeaders && !hasQuery) {
2067
2217
  lines.push(` ${resultPrefix}await this.fetch(\`${fetchUrl}\`, { method: '${httpMethod}' });`);
2068
2218
  } else {
@@ -2072,22 +2222,185 @@ function generateMethod(route, op, file, options) {
2072
2222
  }
2073
2223
  lines.push(` });`);
2074
2224
  }
2075
- const readBodyExpr = respCategory === "text" ? `await result.text()` : respCategory === "binary" ? `await result.blob()` : `await parseJson<${dataType}>(result)`;
2076
- if (hasRespHeaders) {
2077
- const headerEntries = respHeaders.map((h) => `${quoteKey(headerNameToProperty(h.name))}: result.headers.get('${h.name}') ?? undefined`).join(", ");
2225
+ if (isMultiStatus) {
2226
+ const [fallback, ...rest] = observable;
2227
+ lines.push(` switch (result.status) {`);
2228
+ for (const resp of rest) {
2229
+ lines.push(` case ${resp.statusCode}:`);
2230
+ lines.push(...sdkReturnLines(resp, modelsWithOutput, " ", true));
2231
+ }
2232
+ lines.push(` default:`);
2233
+ lines.push(...sdkReturnLines(fallback, modelsWithOutput, " ", true));
2234
+ lines.push(` }`);
2235
+ } else if (primaryBodies.length > 1) {
2236
+ lines.push(...sdkReturnLines(primaryResponse, modelsWithOutput, " ", false));
2237
+ } else if (hasRespHeaders) {
2238
+ const headerEntries = sdkHeaderEntries(respHeaders);
2078
2239
  if (isVoid) {
2079
2240
  lines.push(` return { headers: { ${headerEntries} } };`);
2080
2241
  } else {
2081
- lines.push(` const data = ${readBodyExpr};`);
2242
+ lines.push(` const data = ${sdkReadExpr(primaryBodies[0], modelsWithOutput)};`);
2082
2243
  lines.push(` return { data, headers: { ${headerEntries} } };`);
2083
2244
  }
2084
2245
  } else if (!isVoid) {
2085
- lines.push(` return ${readBodyExpr};`);
2246
+ lines.push(` return ${sdkReadExpr(primaryBodies[0], modelsWithOutput)};`);
2086
2247
  }
2087
2248
  lines.push(" }");
2088
2249
  return lines;
2089
2250
  }
2090
2251
  __name(generateMethod, "generateMethod");
2252
+ function sdkDataType(body, modelsWithOutput) {
2253
+ const category = classifyContentType2(body.contentType);
2254
+ if (category === "text") return "string";
2255
+ if (category === "binary") return "Blob";
2256
+ return renderOutputTsType(body.bodyType, modelsWithOutput);
2257
+ }
2258
+ __name(sdkDataType, "sdkDataType");
2259
+ function sdkReadExpr(body, modelsWithOutput) {
2260
+ const category = classifyContentType2(body.contentType);
2261
+ if (category === "text") return "await result.text()";
2262
+ if (category === "binary") return "await result.blob()";
2263
+ return `await parseJson<${renderOutputTsType(body.bodyType, modelsWithOutput)}>(result)`;
2264
+ }
2265
+ __name(sdkReadExpr, "sdkReadExpr");
2266
+ function renderSdkHeadersShape(headers, modelsWithOutput) {
2267
+ const fields = headers.map((h) => `${quoteKey(headerNameToProperty(h.name))}${h.optional ? "?" : ""}: ${renderOutputTsType(h.type, modelsWithOutput)}`);
2268
+ return `{ ${fields.join("; ")} }`;
2269
+ }
2270
+ __name(renderSdkHeadersShape, "renderSdkHeadersShape");
2271
+ function sdkHeaderEntries(headers) {
2272
+ return headers.map((h) => `${quoteKey(headerNameToProperty(h.name))}: result.headers.get('${h.name}') ?? undefined`).join(", ");
2273
+ }
2274
+ __name(sdkHeaderEntries, "sdkHeaderEntries");
2275
+ function sdkResponseMembers(resp, modelsWithOutput, includeStatus) {
2276
+ const bodies = resp.bodies;
2277
+ const headers = resp.headers ?? [];
2278
+ const leading = includeStatus ? [
2279
+ `status: ${resp.statusCode}`
2280
+ ] : [];
2281
+ const trailing = headers.length > 0 ? [
2282
+ `headers: ${renderSdkHeadersShape(headers, modelsWithOutput)}`
2283
+ ] : [];
2284
+ if (bodies.length === 0) {
2285
+ return [
2286
+ `{ ${[
2287
+ ...leading,
2288
+ ...trailing
2289
+ ].join("; ")} }`
2290
+ ];
2291
+ }
2292
+ const dataTypes = bodies.map((b) => sdkDataType(b, modelsWithOutput));
2293
+ if (dataTypes.every((t) => t === dataTypes[0])) {
2294
+ const contentType = bodies.map((b) => `'${b.contentType}'`).join(" | ");
2295
+ return [
2296
+ `{ ${[
2297
+ ...leading,
2298
+ `contentType: ${contentType}`,
2299
+ `data: ${dataTypes[0]}`,
2300
+ ...trailing
2301
+ ].join("; ")} }`
2302
+ ];
2303
+ }
2304
+ return bodies.map((b, i) => `{ ${[
2305
+ ...leading,
2306
+ `contentType: '${b.contentType}'`,
2307
+ `data: ${dataTypes[i]}`,
2308
+ ...trailing
2309
+ ].join("; ")} }`);
2310
+ }
2311
+ __name(sdkResponseMembers, "sdkResponseMembers");
2312
+ function sdkReturnLines(resp, modelsWithOutput, indent, includeStatus) {
2313
+ const bodies = resp.bodies;
2314
+ const headers = resp.headers ?? [];
2315
+ const leading = includeStatus ? [
2316
+ `status: ${resp.statusCode}`
2317
+ ] : [];
2318
+ const trailing = headers.length > 0 ? [
2319
+ `headers: { ${sdkHeaderEntries(headers)} }`
2320
+ ] : [];
2321
+ if (bodies.length === 0) {
2322
+ return [
2323
+ `${indent}return { ${[
2324
+ ...leading,
2325
+ ...trailing
2326
+ ].join(", ")} };`
2327
+ ];
2328
+ }
2329
+ if (bodies.length === 1) {
2330
+ const fields = [
2331
+ ...leading,
2332
+ `contentType: '${bodies[0].contentType}'`,
2333
+ `data: ${sdkReadExpr(bodies[0], modelsWithOutput)}`,
2334
+ ...trailing
2335
+ ];
2336
+ return [
2337
+ `${indent}return { ${fields.join(", ")} };`
2338
+ ];
2339
+ }
2340
+ const dataTypes = bodies.map((b) => sdkDataType(b, modelsWithOutput));
2341
+ if (dataTypes.every((t) => t === dataTypes[0])) {
2342
+ const cast = bodies.map((b) => `'${b.contentType}'`).join(" | ");
2343
+ const fields = [
2344
+ ...leading,
2345
+ `contentType: readContentType(result) as ${cast}`,
2346
+ `data: ${sdkReadExpr(bodies[0], modelsWithOutput)}`,
2347
+ ...trailing
2348
+ ];
2349
+ return [
2350
+ `${indent}return { ${fields.join(", ")} };`
2351
+ ];
2352
+ }
2353
+ const lines = [
2354
+ `${indent}switch (readContentType(result)) {`
2355
+ ];
2356
+ for (const body of bodies.slice(1)) {
2357
+ const fields = [
2358
+ ...leading,
2359
+ `contentType: '${body.contentType}'`,
2360
+ `data: ${sdkReadExpr(body, modelsWithOutput)}`,
2361
+ ...trailing
2362
+ ];
2363
+ lines.push(`${indent} case '${body.contentType}':`);
2364
+ lines.push(`${indent} return { ${fields.join(", ")} };`);
2365
+ }
2366
+ const first = bodies[0];
2367
+ const fallbackFields = [
2368
+ ...leading,
2369
+ `contentType: '${first.contentType}'`,
2370
+ `data: ${sdkReadExpr(first, modelsWithOutput)}`,
2371
+ ...trailing
2372
+ ];
2373
+ lines.push(`${indent} default:`);
2374
+ lines.push(`${indent} return { ${fallbackFields.join(", ")} };`);
2375
+ lines.push(`${indent}}`);
2376
+ return lines;
2377
+ }
2378
+ __name(sdkReturnLines, "sdkReturnLines");
2379
+ function errorBodyTypeName(route, op) {
2380
+ const method = deriveMethodName(op, route);
2381
+ return `${method.charAt(0).toUpperCase()}${method.slice(1)}ErrorBody`;
2382
+ }
2383
+ __name(errorBodyTypeName, "errorBodyTypeName");
2384
+ function generateErrorBodyAliases(root, options) {
2385
+ const includeInternal = options.includeInternal ?? false;
2386
+ const lines = [];
2387
+ for (const route of root.routes) {
2388
+ for (const op of route.operations) {
2389
+ const mods = resolveModifiers2(route, op);
2390
+ if (!includeInternal && mods.includes("internal")) continue;
2391
+ const types = /* @__PURE__ */ new Set();
2392
+ for (const resp of thrownResponses(op)) {
2393
+ for (const body of resp.bodies) types.add(sdkDataType(body, options.modelsWithOutput));
2394
+ }
2395
+ if (types.size === 0) continue;
2396
+ lines.push(`export type ${errorBodyTypeName(route, op)} = ${[
2397
+ ...types
2398
+ ].join(" | ")};`);
2399
+ }
2400
+ }
2401
+ return lines;
2402
+ }
2403
+ __name(generateErrorBodyAliases, "generateErrorBodyAliases");
2091
2404
  function buildUrlExpression(path, _) {
2092
2405
  return path.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_match, name) => {
2093
2406
  return `\${encodeURIComponent(${name})}`;
@@ -2324,9 +2637,9 @@ function collectTypes2(root, modelsWithInput, modelsWithOutput, includeInternal
2324
2637
  }
2325
2638
  }
2326
2639
  for (const resp of op.responses) {
2327
- if (resp.bodyType) {
2328
- collectTypeNodeRefs2(resp.bodyType, types);
2329
- collectOutputTypeNodeRefs2(resp.bodyType, types, modelsWithOutput);
2640
+ for (const body of resp.bodies) {
2641
+ collectTypeNodeRefs2(body.bodyType, types);
2642
+ collectOutputTypeNodeRefs2(body.bodyType, types, modelsWithOutput);
2330
2643
  }
2331
2644
  if (resp.headers) {
2332
2645
  for (const h of resp.headers) {
@@ -2428,6 +2741,16 @@ function sdkNeedsQueryString(root, includeInternal = false) {
2428
2741
  return false;
2429
2742
  }
2430
2743
  __name(sdkNeedsQueryString, "sdkNeedsQueryString");
2744
+ function sdkNeedsReadContentType(root, includeInternal = false) {
2745
+ for (const route of root.routes) {
2746
+ for (const op of route.operations) {
2747
+ if (!includeInternal && resolveModifiers2(route, op).includes("internal")) continue;
2748
+ if (observableResponses(op).some((r) => r.bodies.length > 1)) return true;
2749
+ }
2750
+ }
2751
+ return false;
2752
+ }
2753
+ __name(sdkNeedsReadContentType, "sdkNeedsReadContentType");
2431
2754
  function sdkNeedsBigIntReplacer(root, includeInternal = false) {
2432
2755
  for (const route of root.routes) {
2433
2756
  for (const op of route.operations) {
@@ -2442,10 +2765,7 @@ function sdkNeedsBigIntReviver(root, includeInternal = false) {
2442
2765
  for (const route of root.routes) {
2443
2766
  for (const op of route.operations) {
2444
2767
  if (!includeInternal && resolveModifiers2(route, op).includes("internal")) continue;
2445
- if (op.responses.some((r) => {
2446
- if (!r.bodyType) return false;
2447
- return !r.contentType || classifyContentType2(r.contentType) === "json";
2448
- })) {
2768
+ if (op.responses.some((r) => r.bodies.some((b) => classifyContentType2(b.contentType) === "json"))) {
2449
2769
  return true;
2450
2770
  }
2451
2771
  }
@@ -2462,7 +2782,7 @@ function sdkNeedsJson(root, includeInternal = false) {
2462
2782
  if (src.kind === "params") return src.nodes.some((p) => typeNeedsScalar(p.type, "json"));
2463
2783
  return typeNeedsScalar(src.node, "json");
2464
2784
  }, "check");
2465
- if (!!op.request?.bodies.some((b) => typeNeedsScalar(b.bodyType, "json")) || op.responses.some((r) => r.bodyType && typeNeedsScalar(r.bodyType, "json")) || check(op.query) || check(op.headers) || check(route.params)) return true;
2785
+ if (!!op.request?.bodies.some((b) => typeNeedsScalar(b.bodyType, "json")) || op.responses.some((r) => r.bodies.some((b) => typeNeedsScalar(b.bodyType, "json"))) || check(op.query) || check(op.headers) || check(route.params)) return true;
2466
2786
  }
2467
2787
  }
2468
2788
  return false;
@@ -2546,11 +2866,11 @@ function deriveTypeImportPath2(file, template) {
2546
2866
  __name(deriveTypeImportPath2, "deriveTypeImportPath");
2547
2867
  function generateSdkOptions() {
2548
2868
  return [
2549
- "export class SdkError extends Error {",
2869
+ "export class SdkError<TBody = unknown> extends Error {",
2550
2870
  " constructor(",
2551
2871
  " public readonly status: number,",
2552
2872
  " public readonly statusText: string,",
2553
- " public readonly body: unknown,",
2873
+ " public readonly body: TBody,",
2554
2874
  " public readonly headers: Headers,",
2555
2875
  " ) {",
2556
2876
  " super(`${status} ${statusText}`);",
@@ -2558,7 +2878,16 @@ function generateSdkOptions() {
2558
2878
  " }",
2559
2879
  "}",
2560
2880
  "",
2561
- "export type SdkFetch = (url: string, init: RequestInit) => Promise<Response>;",
2881
+ "export interface SdkRequestInit extends RequestInit {",
2882
+ " /**",
2883
+ " * Statuses this operation declares as values rather than errors \u2014 a 304 from",
2884
+ " * conditional-GET middleware, or an error status the service returns deliberately.",
2885
+ " * Anything else at or above 400 still throws SdkError.",
2886
+ " */",
2887
+ " expectStatuses?: number[];",
2888
+ "}",
2889
+ "",
2890
+ "export type SdkFetch = (url: string, init: SdkRequestInit) => Promise<Response>;",
2562
2891
  "",
2563
2892
  "export interface SdkOptions {",
2564
2893
  " baseUrl: string;",
@@ -2584,9 +2913,13 @@ function generateSdkOptions() {
2584
2913
  "",
2585
2914
  JSON_VALUE_TYPE_DECL,
2586
2915
  "",
2916
+ "export function readContentType(res: Response): string {",
2917
+ " return res.headers.get('content-type')?.split(';')[0]?.trim() ?? '';",
2918
+ "}",
2919
+ "",
2587
2920
  "export function createSdkFetch(options: SdkOptions): SdkFetch {",
2588
2921
  " const getRequestId = options.requestIdFactory ?? (() => crypto.randomUUID());",
2589
- " return async (url: string, init: RequestInit): Promise<Response> => {",
2922
+ " return async (url: string, init: SdkRequestInit): Promise<Response> => {",
2590
2923
  " const baseHeaders = typeof options.headers === 'function'",
2591
2924
  " ? await options.headers()",
2592
2925
  " : options.headers ?? {};",
@@ -2594,7 +2927,7 @@ function generateSdkOptions() {
2594
2927
  " ...init,",
2595
2928
  " headers: { ...baseHeaders, 'X-Request-ID': getRequestId(), ...init.headers as Record<string, string> },",
2596
2929
  " });",
2597
- " if (!res.ok) {",
2930
+ " if (!res.ok && !(init.expectStatuses ?? []).includes(res.status)) {",
2598
2931
  " const text = await res.text();",
2599
2932
  " let body: unknown;",
2600
2933
  " try { body = JSON.parse(text); } catch { body = text; }",
@@ -2693,6 +3026,7 @@ function generateAreaClient(input) {
2693
3026
  const { area, outPath, inlineFiles, subareaClients, sdkOptionsPath } = input;
2694
3027
  const className = deriveAreaClientClassName(area);
2695
3028
  const collectedMethodLines = [];
3029
+ const collectedErrorAliases = /* @__PURE__ */ new Set();
2696
3030
  const seenMethods = /* @__PURE__ */ new Set();
2697
3031
  const typesByImportPath = /* @__PURE__ */ new Map();
2698
3032
  const unresolvedTypes = /* @__PURE__ */ new Set();
@@ -2700,6 +3034,7 @@ function generateAreaClient(input) {
2700
3034
  let needsBigIntReplacer = false;
2701
3035
  let needsBigIntReviver = false;
2702
3036
  let needsQueryString = false;
3037
+ let needsReadContentType = false;
2703
3038
  for (const inline of inlineFiles) {
2704
3039
  const includeInternal = inline.codegenOptions.includeInternal ?? false;
2705
3040
  const { lines: methodLines, methodNames } = generateClientMethods(inline.root, inline.codegenOptions);
@@ -2710,10 +3045,12 @@ function generateAreaClient(input) {
2710
3045
  seenMethods.add(name);
2711
3046
  }
2712
3047
  collectedMethodLines.push(...methodLines);
3048
+ for (const alias of generateErrorBodyAliases(inline.root, inline.codegenOptions)) collectedErrorAliases.add(alias);
2713
3049
  if (sdkNeedsJson(inline.root, includeInternal)) needsJson = true;
2714
3050
  if (sdkNeedsBigIntReplacer(inline.root, includeInternal)) needsBigIntReplacer = true;
2715
3051
  if (sdkNeedsBigIntReviver(inline.root, includeInternal)) needsBigIntReviver = true;
2716
3052
  if (sdkNeedsQueryString(inline.root, includeInternal)) needsQueryString = true;
3053
+ if (sdkNeedsReadContentType(inline.root, includeInternal)) needsReadContentType = true;
2717
3054
  const typesForFile = collectTypes2(inline.root, inline.codegenOptions.modelsWithInput, inline.codegenOptions.modelsWithOutput, includeInternal);
2718
3055
  const { modelOutPaths } = inline.codegenOptions;
2719
3056
  if (modelOutPaths) {
@@ -2741,6 +3078,7 @@ function generateAreaClient(input) {
2741
3078
  if (needsBigIntReplacer) valueImports.push("bigIntReplacer");
2742
3079
  if (needsBigIntReviver) valueImports.push("parseJson");
2743
3080
  if (needsQueryString) valueImports.push("buildQueryString");
3081
+ if (needsReadContentType) valueImports.push("readContentType");
2744
3082
  if (valueImports.length > 0) {
2745
3083
  lines.push(`import { ${valueImports.join(", ")} } from '${sdkOptionsRel}';`);
2746
3084
  }
@@ -2765,6 +3103,10 @@ function generateAreaClient(input) {
2765
3103
  lines.push(`import { ${sc.client.className} } from '${sc.client.importPath}';`);
2766
3104
  }
2767
3105
  lines.push("");
3106
+ if (collectedErrorAliases.size > 0) {
3107
+ lines.push(...collectedErrorAliases);
3108
+ lines.push("");
3109
+ }
2768
3110
  lines.push(`export class ${className} {`);
2769
3111
  for (const sc of subareaClients) {
2770
3112
  lines.push(` readonly ${sc.propertyName}: ${sc.client.className};`);
@@ -3061,7 +3403,7 @@ function renderOutputField(field, outputCase, modelsWithOutput, target) {
3061
3403
  __name(renderOutputField, "renderOutputField");
3062
3404
 
3063
3405
  // src/codegen-mcp.ts
3064
- import { resolveModifiers as resolveModifiers3 } from "@contractkit/core";
3406
+ import { resolveModifiers as resolveModifiers3, emittedResponses as emittedResponses2 } from "@contractkit/core";
3065
3407
  import { basename as basename3, dirname as dirname5, relative as relative5 } from "path";
3066
3408
  function mcpConfig(op) {
3067
3409
  return op.mcp && typeof op.mcp === "object" ? op.mcp : void 0;
@@ -3187,12 +3529,15 @@ function argsSchemaExpr(props) {
3187
3529
  return `z.object({ ${fields} })`;
3188
3530
  }
3189
3531
  __name(argsSchemaExpr, "argsSchemaExpr");
3190
- function primaryResponse(op) {
3191
- return op.responses.find((r) => r.bodyType) ?? op.responses[0];
3532
+ function primaryResponseBody(op) {
3533
+ for (const resp of emittedResponses2(op)) {
3534
+ if (resp.bodies[0]) return resp.bodies[0].bodyType;
3535
+ }
3536
+ return void 0;
3192
3537
  }
3193
- __name(primaryResponse, "primaryResponse");
3538
+ __name(primaryResponseBody, "primaryResponseBody");
3194
3539
  function outputSchemaExpr(op) {
3195
- const body = primaryResponse(op)?.bodyType;
3540
+ const body = primaryResponseBody(op);
3196
3541
  if (!body) return void 0;
3197
3542
  if (body.kind === "ref") return body.name;
3198
3543
  if (body.kind === "inlineObject") return renderType(body);
@@ -3261,7 +3606,7 @@ function collectSchemaIds(ops, modelsWithInput) {
3261
3606
  }
3262
3607
  walkSourceRefs(op.query, ids, modelsWithInput);
3263
3608
  walkSourceRefs(op.headers, ids, modelsWithInput);
3264
- const body = primaryResponse(op)?.bodyType;
3609
+ const body = primaryResponseBody(op);
3265
3610
  if (body && (body.kind === "ref" || body.kind === "inlineObject")) walkTypeRefs(body, ids, "read");
3266
3611
  }
3267
3612
  return ids;
@@ -3365,7 +3710,7 @@ function renderToolClass(plan, file, options) {
3365
3710
  const props = buildArgsProps(route, op, options.modelsWithInput);
3366
3711
  const destructure = props.map((p) => p.key);
3367
3712
  const callArgs = buildArgs(route, op);
3368
- const isVoid = !primaryResponse(op)?.bodyType;
3713
+ const isVoid = !primaryResponseBody(op);
3369
3714
  const structured = !!outExpr;
3370
3715
  lines.push(" async handle(args: Record<string, unknown>, _context: McpToolContext): Promise<CallToolResult> {");
3371
3716
  if (destructure.length > 0) {
@@ -3777,7 +4122,7 @@ function collectOpRootRefs(root, modelMap) {
3777
4122
  for (const body of op.request.bodies) seeds.push(body.bodyType);
3778
4123
  }
3779
4124
  for (const resp of op.responses) {
3780
- if (resp.bodyType) seeds.push(resp.bodyType);
4125
+ for (const body of resp.bodies) seeds.push(body.bodyType);
3781
4126
  if (resp.headers) {
3782
4127
  for (const h of resp.headers) seeds.push(h.type);
3783
4128
  }