@zapier/connectors-sdk 0.1.0-experimental.14 → 0.1.0-experimental.15

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.cjs CHANGED
@@ -92,12 +92,15 @@ var init_build_zapier_fetch = __esm({
92
92
  // src/index.ts
93
93
  var index_exports = {};
94
94
  __export(index_exports, {
95
+ ConnectorHttpError: () => ConnectorHttpError,
95
96
  defineBearerTokenResolver: () => defineBearerTokenResolver,
96
97
  defineConnectionResolver: () => defineConnectionResolver,
97
98
  defineConnector: () => defineConnector,
98
99
  defineTool: () => defineTool,
99
100
  handleIfScriptMain: () => handleIfScriptMain,
101
+ isConnectorHttpError: () => isConnectorHttpError,
100
102
  runDispatchCli: () => runDispatchCli,
103
+ throwForStatus: () => throwForStatus,
101
104
  toFunctions: () => toFunctions,
102
105
  zapierConnectionResolver: () => zapierConnectionResolver
103
106
  });
@@ -702,6 +705,143 @@ function defineTool(config) {
702
705
  return base;
703
706
  }
704
707
 
708
+ // src/errors.ts
709
+ var CONNECTOR_HTTP_ERROR_SYMBOL = /* @__PURE__ */ Symbol.for("connectors.http-error");
710
+ var ConnectorHttpError = class _ConnectorHttpError extends Error {
711
+ [CONNECTOR_HTTP_ERROR_SYMBOL] = true;
712
+ name = "ConnectorHttpError";
713
+ response;
714
+ constructor(message, options) {
715
+ super(message);
716
+ this.response = options.response;
717
+ Object.setPrototypeOf(this, new.target.prototype);
718
+ captureOriginStack(this, _ConnectorHttpError);
719
+ }
720
+ /**
721
+ * Multi-line, human-readable rendering for CLI/executable consumption. The
722
+ * single-line `.message` stays the summary; this adds the connector call site
723
+ * it was thrown from (the first stack frame, SDK frames elided), the HTTP
724
+ * `status` (always — so the message never has to restate it), the response
725
+ * headers (verbatim — they carry header-only signals like `Retry-After`), and
726
+ * the response body, so an operator (or agent) sees the full picture without
727
+ * digging into properties. A parsed JSON body is pretty-printed in full
728
+ * (error payloads are small); a non-JSON text body (HTML pages, stack dumps)
729
+ * is truncated.
730
+ */
731
+ toString() {
732
+ return formatConnectorHttpError(this);
733
+ }
734
+ /**
735
+ * Build a `ConnectorHttpError` from a `Response` whose body has already been
736
+ * read. This is the control path: a connector that wants its own message, or
737
+ * whose failure arrives as `200 { ok: false }` (so the body is read before
738
+ * the error is known), calls this directly. When `message` is omitted, a
739
+ * generic `HTTP <status> <statusText>` summary is used.
740
+ */
741
+ static fromResponseBody(res, body, opts = {}) {
742
+ const headers = res.headers;
743
+ const statusText = res.statusText ? ` ${res.statusText}` : "";
744
+ const err = new _ConnectorHttpError(
745
+ opts.message ?? `HTTP ${res.status}${statusText}`,
746
+ {
747
+ response: {
748
+ status: res.status,
749
+ statusText: res.statusText ?? "",
750
+ headers: headers ? headersToRecord(headers) : {},
751
+ body
752
+ }
753
+ }
754
+ );
755
+ captureOriginStack(err, _ConnectorHttpError.fromResponseBody);
756
+ return err;
757
+ }
758
+ };
759
+ function isConnectorHttpError(value) {
760
+ return Boolean(
761
+ value && typeof value === "object" && value[CONNECTOR_HTTP_ERROR_SYMBOL] === true
762
+ );
763
+ }
764
+ async function throwForStatus(res, message) {
765
+ if (res.ok) return res;
766
+ const origin = {};
767
+ captureOriginStack(origin, throwForStatus);
768
+ const err = ConnectorHttpError.fromResponseBody(res, await readBody(res), {
769
+ message
770
+ });
771
+ const firstNewline = origin.stack?.indexOf("\n") ?? -1;
772
+ if (origin.stack !== void 0 && firstNewline !== -1) {
773
+ err.stack = `${err.name}: ${err.message}${origin.stack.slice(firstNewline)}`;
774
+ }
775
+ throw err;
776
+ }
777
+ function captureOriginStack(target, sentinel) {
778
+ const capture = Error.captureStackTrace;
779
+ if (typeof capture === "function") capture(target, sentinel);
780
+ }
781
+ function headersToRecord(headers) {
782
+ const out = {};
783
+ headers.forEach((value, key) => {
784
+ out[key] = value;
785
+ });
786
+ return out;
787
+ }
788
+ async function readBody(res) {
789
+ let text;
790
+ try {
791
+ text = await res.text();
792
+ } catch {
793
+ return void 0;
794
+ }
795
+ if (text === "") return "";
796
+ try {
797
+ return JSON.parse(text);
798
+ } catch {
799
+ return text;
800
+ }
801
+ }
802
+ var MAX_BODY_CHARS = 2e3;
803
+ function formatConnectorHttpError(err) {
804
+ const lines = [`${err.name}: ${err.message}`];
805
+ const origin = originFrame(err.stack);
806
+ if (origin) lines.push(` ${origin}`);
807
+ const { status, statusText } = err.response;
808
+ lines.push(` status: ${status}${statusText ? ` ${statusText}` : ""}`);
809
+ const headers = formatHeaders(err.response.headers);
810
+ if (headers) lines.push(headers);
811
+ if (err.response.body !== void 0 && err.response.body !== "") {
812
+ lines.push(` body: ${summarizeBody(err.response.body)}`);
813
+ }
814
+ return lines.join("\n");
815
+ }
816
+ function formatHeaders(headers) {
817
+ const entries = Object.entries(headers);
818
+ if (entries.length === 0) return void 0;
819
+ const rendered = entries.map(([key, value]) => ` ${key}: ${value}`);
820
+ return ` headers:
821
+ ${rendered.join("\n")}`;
822
+ }
823
+ function originFrame(stack) {
824
+ if (!stack) return void 0;
825
+ for (const line of stack.split("\n")) {
826
+ const trimmed = line.trim();
827
+ if (trimmed.startsWith("at ")) return trimmed;
828
+ }
829
+ return void 0;
830
+ }
831
+ function summarizeBody(body) {
832
+ if (typeof body === "string") {
833
+ return body.length > MAX_BODY_CHARS ? `${body.slice(0, MAX_BODY_CHARS)}\u2026 (truncated)` : body;
834
+ }
835
+ return safeJsonStringify(body) ?? String(body);
836
+ }
837
+ function safeJsonStringify(value) {
838
+ try {
839
+ return JSON.stringify(value, null, 2);
840
+ } catch {
841
+ return void 0;
842
+ }
843
+ }
844
+
705
845
  // src/surfaces/handle-if-script-main.ts
706
846
  var import_zod3 = require("zod");
707
847
 
@@ -823,11 +963,31 @@ async function buildMcpServer(meta, connector, opts = {}) {
823
963
  // `input` because the registry walk is polymorphic over every
824
964
  // script's schema.
825
965
  async (input) => {
826
- const result = await script.run(input, runOpts);
827
- return {
828
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
829
- structuredContent: result
830
- };
966
+ try {
967
+ const result = await script.run(input, runOpts);
968
+ return {
969
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
970
+ structuredContent: result
971
+ };
972
+ } catch (err) {
973
+ if (isConnectorHttpError(err)) {
974
+ return {
975
+ content: [
976
+ { type: "text", text: err.toString() },
977
+ {
978
+ type: "text",
979
+ text: JSON.stringify(
980
+ { message: err.message, response: err.response },
981
+ null,
982
+ 2
983
+ )
984
+ }
985
+ ],
986
+ isError: true
987
+ };
988
+ }
989
+ throw err;
990
+ }
831
991
  }
832
992
  );
833
993
  }
@@ -915,7 +1075,7 @@ async function runDispatchCli(meta, connector) {
915
1075
  }
916
1076
  }
917
1077
  function reportFatalErrorAndExit(err, stderr, exit) {
918
- const message = err instanceof Error ? err.stack ?? `${err.name}: ${err.message}` : String(err);
1078
+ const message = isConnectorHttpError(err) ? err.toString() : err instanceof Error ? err.stack ?? `${err.name}: ${err.message}` : String(err);
919
1079
  stderr.write(`${message}
920
1080
  `);
921
1081
  exit(1);
@@ -1350,12 +1510,15 @@ function toFunctions(connector) {
1350
1510
  }
1351
1511
  // Annotate the CommonJS export names for ESM import in node:
1352
1512
  0 && (module.exports = {
1513
+ ConnectorHttpError,
1353
1514
  defineBearerTokenResolver,
1354
1515
  defineConnectionResolver,
1355
1516
  defineConnector,
1356
1517
  defineTool,
1357
1518
  handleIfScriptMain,
1519
+ isConnectorHttpError,
1358
1520
  runDispatchCli,
1521
+ throwForStatus,
1359
1522
  toFunctions,
1360
1523
  zapierConnectionResolver
1361
1524
  });
package/dist/index.d.cts CHANGED
@@ -453,6 +453,116 @@ declare function defineTool<TIn extends z.ZodObject<z.ZodRawShape>, TOut extends
453
453
  declare function defineTool<TIn extends z.ZodObject<z.ZodRawShape>, TOut extends z.ZodObject<z.ZodRawShape>, const TName extends string = string, const TKey extends string = string>(config: DefineToolConfigSingle<TIn, TOut, TName, TKey>): ToolDefinitionSingle<TIn, TOut, TName, TKey>;
454
454
  declare function defineTool<TIn extends z.ZodObject<z.ZodRawShape>, TOut extends z.ZodObject<z.ZodRawShape>, const TName extends string = string, const TSlots extends string = string, const TKey extends string = string>(config: DefineToolConfigMulti<TIn, TOut, TName, TSlots, TKey>): ToolDefinitionMulti<TIn, TOut, TName, TSlots, TKey>;
455
455
 
456
+ /**
457
+ * `ConnectorHttpError` — the standardized error every connector throws on a
458
+ * failed API response, plus the `throwForStatus` helper that builds it.
459
+ *
460
+ * Goal (STAFF-4049): give agents and CLI consumers full, transparent context
461
+ * about a failed HTTP call by carrying the response that came back (status,
462
+ * headers, parsed body) — instead of a lossy `throw new Error(\`X 400:
463
+ * ${body}\`)` per connector. Everything agent-actionable an API surfaces
464
+ * (machine error codes like `missing_scope`, `Retry-After`, etc.) is already
465
+ * in `response.body` / `response.headers`; we don't promote derived fields off
466
+ * the response. Callers read what they need from `error.response`.
467
+ *
468
+ * We deliberately do NOT pull in kitcore's `adaptError` factory: that
469
+ * indirection exists to map kitcore's internal throws onto a head SDK's
470
+ * branded class across a plugin boundary; here we own both the throw site and
471
+ * the class.
472
+ *
473
+ * Two construction paths:
474
+ * - `throwForStatus(res, message?)` — reads the failed body for you and
475
+ * throws. The 90% case. The optional `message` names the call site (e.g.
476
+ * "Failed to read the source page"); the status never needs to be in it,
477
+ * since `toString()` always renders the `status: <code> <text>` line.
478
+ * - `ConnectorHttpError.fromResponseBody(res, body, { message? })` — for when
479
+ * the body is already in hand: a failure that arrives as `200 { ok: false }`
480
+ * (body read before the error is known), or a caller shaping the response
481
+ * itself.
482
+ *
483
+ * Recognition is brand-based (`Symbol.for` + `isConnectorHttpError`), not
484
+ * `instanceof`: `apps/*` connectors bundle the SDK standalone, so multiple
485
+ * copies of this module can coexist in one process and `instanceof` would miss
486
+ * cross-copy instances.
487
+ */
488
+ /** The response as it came back. `body` is parsed JSON when possible, else text. */
489
+ interface ConnectorHttpResponse {
490
+ status: number;
491
+ statusText: string;
492
+ headers: Record<string, string>;
493
+ body: unknown;
494
+ }
495
+ interface ConnectorHttpErrorOptions {
496
+ response: ConnectorHttpResponse;
497
+ }
498
+ /**
499
+ * Cross-realm/cross-bundle brand. `Symbol.for` reads the engine-global
500
+ * registry, so the same value resolves across multiple copies of this module
501
+ * (one bundled into each `apps/*` connector). Use `isConnectorHttpError` for
502
+ * recognition rather than `instanceof`.
503
+ */
504
+ declare const CONNECTOR_HTTP_ERROR_SYMBOL: unique symbol;
505
+ declare class ConnectorHttpError extends Error {
506
+ readonly [CONNECTOR_HTTP_ERROR_SYMBOL] = true;
507
+ readonly name = "ConnectorHttpError";
508
+ readonly response: ConnectorHttpResponse;
509
+ constructor(message: string, options: ConnectorHttpErrorOptions);
510
+ /**
511
+ * Multi-line, human-readable rendering for CLI/executable consumption. The
512
+ * single-line `.message` stays the summary; this adds the connector call site
513
+ * it was thrown from (the first stack frame, SDK frames elided), the HTTP
514
+ * `status` (always — so the message never has to restate it), the response
515
+ * headers (verbatim — they carry header-only signals like `Retry-After`), and
516
+ * the response body, so an operator (or agent) sees the full picture without
517
+ * digging into properties. A parsed JSON body is pretty-printed in full
518
+ * (error payloads are small); a non-JSON text body (HTML pages, stack dumps)
519
+ * is truncated.
520
+ */
521
+ toString(): string;
522
+ /**
523
+ * Build a `ConnectorHttpError` from a `Response` whose body has already been
524
+ * read. This is the control path: a connector that wants its own message, or
525
+ * whose failure arrives as `200 { ok: false }` (so the body is read before
526
+ * the error is known), calls this directly. When `message` is omitted, a
527
+ * generic `HTTP <status> <statusText>` summary is used.
528
+ */
529
+ static fromResponseBody(res: Pick<Response, "status" | "statusText" | "headers">, body: unknown, opts?: {
530
+ message?: string;
531
+ }): ConnectorHttpError;
532
+ }
533
+ /** Cross-bundle-safe recognizer. Prefer this over `instanceof`. */
534
+ declare function isConnectorHttpError(value: unknown): value is ConnectorHttpError;
535
+ /**
536
+ * Throw a `ConnectorHttpError` when `res` is not ok; otherwise return `res`
537
+ * unchanged (the success-path body is never consumed, so callers can still
538
+ * read it). A delegator: it reads the failed body and hands off to
539
+ * `ConnectorHttpError.fromResponseBody`. Named after the `zapier-platform`
540
+ * CLI's `response.throwForStatus()` idiom, but a standalone helper — it does
541
+ * not augment the `Response`.
542
+ *
543
+ * For the common path:
544
+ *
545
+ * const res = await ctx.fetch(url, init);
546
+ * await throwForStatus(res);
547
+ * return res.json();
548
+ *
549
+ * The optional `message` describes the call site for the rendered error and
550
+ * the thrown `.message` — useful when one script makes several requests and a
551
+ * bare `HTTP 404` wouldn't say which:
552
+ *
553
+ * await throwForStatus(readRes, "Failed to read the source page");
554
+ * await throwForStatus(createRes, "Failed to create the target page");
555
+ *
556
+ * The `message` need not mention the status: `toString()` always renders a
557
+ * `status: <code> <text>` line. When omitted, a generic `HTTP <status>
558
+ * <statusText>` summary is used.
559
+ *
560
+ * When the failure arrives in a 200 body (so the body must be read before the
561
+ * error is known), build the error directly with
562
+ * `ConnectorHttpError.fromResponseBody(res, body, { message? })` instead.
563
+ */
564
+ declare function throwForStatus(res: Response, message?: string): Promise<Response>;
565
+
456
566
  /**
457
567
  * `handleIfScriptMain(meta, definition, opts?)` — per-script CLI execution
458
568
  * surface (not the connector bin).
@@ -570,4 +680,4 @@ type NamedFunctions<TScripts extends AnyScripts> = {
570
680
  */
571
681
  declare function toFunctions<TScripts extends AnyScripts, TResolvers extends ConnectionResolversMap>(connector: ConnectorDefinition<TScripts, TResolvers>): NamedFunctions<ConnectorDefinition<TScripts, TResolvers>["scripts"]>;
572
682
 
573
- export { type AnyToolDefinition, type ConnectorDefinition, type RunOptions, defineBearerTokenResolver, defineConnectionResolver, defineConnector, defineTool, handleIfScriptMain, runDispatchCli, toFunctions, zapierConnectionResolver };
683
+ export { type AnyToolDefinition, type ConnectorDefinition, ConnectorHttpError, type ConnectorHttpErrorOptions, type ConnectorHttpResponse, type RunOptions, defineBearerTokenResolver, defineConnectionResolver, defineConnector, defineTool, handleIfScriptMain, isConnectorHttpError, runDispatchCli, throwForStatus, toFunctions, zapierConnectionResolver };
package/dist/index.d.ts CHANGED
@@ -453,6 +453,116 @@ declare function defineTool<TIn extends z.ZodObject<z.ZodRawShape>, TOut extends
453
453
  declare function defineTool<TIn extends z.ZodObject<z.ZodRawShape>, TOut extends z.ZodObject<z.ZodRawShape>, const TName extends string = string, const TKey extends string = string>(config: DefineToolConfigSingle<TIn, TOut, TName, TKey>): ToolDefinitionSingle<TIn, TOut, TName, TKey>;
454
454
  declare function defineTool<TIn extends z.ZodObject<z.ZodRawShape>, TOut extends z.ZodObject<z.ZodRawShape>, const TName extends string = string, const TSlots extends string = string, const TKey extends string = string>(config: DefineToolConfigMulti<TIn, TOut, TName, TSlots, TKey>): ToolDefinitionMulti<TIn, TOut, TName, TSlots, TKey>;
455
455
 
456
+ /**
457
+ * `ConnectorHttpError` — the standardized error every connector throws on a
458
+ * failed API response, plus the `throwForStatus` helper that builds it.
459
+ *
460
+ * Goal (STAFF-4049): give agents and CLI consumers full, transparent context
461
+ * about a failed HTTP call by carrying the response that came back (status,
462
+ * headers, parsed body) — instead of a lossy `throw new Error(\`X 400:
463
+ * ${body}\`)` per connector. Everything agent-actionable an API surfaces
464
+ * (machine error codes like `missing_scope`, `Retry-After`, etc.) is already
465
+ * in `response.body` / `response.headers`; we don't promote derived fields off
466
+ * the response. Callers read what they need from `error.response`.
467
+ *
468
+ * We deliberately do NOT pull in kitcore's `adaptError` factory: that
469
+ * indirection exists to map kitcore's internal throws onto a head SDK's
470
+ * branded class across a plugin boundary; here we own both the throw site and
471
+ * the class.
472
+ *
473
+ * Two construction paths:
474
+ * - `throwForStatus(res, message?)` — reads the failed body for you and
475
+ * throws. The 90% case. The optional `message` names the call site (e.g.
476
+ * "Failed to read the source page"); the status never needs to be in it,
477
+ * since `toString()` always renders the `status: <code> <text>` line.
478
+ * - `ConnectorHttpError.fromResponseBody(res, body, { message? })` — for when
479
+ * the body is already in hand: a failure that arrives as `200 { ok: false }`
480
+ * (body read before the error is known), or a caller shaping the response
481
+ * itself.
482
+ *
483
+ * Recognition is brand-based (`Symbol.for` + `isConnectorHttpError`), not
484
+ * `instanceof`: `apps/*` connectors bundle the SDK standalone, so multiple
485
+ * copies of this module can coexist in one process and `instanceof` would miss
486
+ * cross-copy instances.
487
+ */
488
+ /** The response as it came back. `body` is parsed JSON when possible, else text. */
489
+ interface ConnectorHttpResponse {
490
+ status: number;
491
+ statusText: string;
492
+ headers: Record<string, string>;
493
+ body: unknown;
494
+ }
495
+ interface ConnectorHttpErrorOptions {
496
+ response: ConnectorHttpResponse;
497
+ }
498
+ /**
499
+ * Cross-realm/cross-bundle brand. `Symbol.for` reads the engine-global
500
+ * registry, so the same value resolves across multiple copies of this module
501
+ * (one bundled into each `apps/*` connector). Use `isConnectorHttpError` for
502
+ * recognition rather than `instanceof`.
503
+ */
504
+ declare const CONNECTOR_HTTP_ERROR_SYMBOL: unique symbol;
505
+ declare class ConnectorHttpError extends Error {
506
+ readonly [CONNECTOR_HTTP_ERROR_SYMBOL] = true;
507
+ readonly name = "ConnectorHttpError";
508
+ readonly response: ConnectorHttpResponse;
509
+ constructor(message: string, options: ConnectorHttpErrorOptions);
510
+ /**
511
+ * Multi-line, human-readable rendering for CLI/executable consumption. The
512
+ * single-line `.message` stays the summary; this adds the connector call site
513
+ * it was thrown from (the first stack frame, SDK frames elided), the HTTP
514
+ * `status` (always — so the message never has to restate it), the response
515
+ * headers (verbatim — they carry header-only signals like `Retry-After`), and
516
+ * the response body, so an operator (or agent) sees the full picture without
517
+ * digging into properties. A parsed JSON body is pretty-printed in full
518
+ * (error payloads are small); a non-JSON text body (HTML pages, stack dumps)
519
+ * is truncated.
520
+ */
521
+ toString(): string;
522
+ /**
523
+ * Build a `ConnectorHttpError` from a `Response` whose body has already been
524
+ * read. This is the control path: a connector that wants its own message, or
525
+ * whose failure arrives as `200 { ok: false }` (so the body is read before
526
+ * the error is known), calls this directly. When `message` is omitted, a
527
+ * generic `HTTP <status> <statusText>` summary is used.
528
+ */
529
+ static fromResponseBody(res: Pick<Response, "status" | "statusText" | "headers">, body: unknown, opts?: {
530
+ message?: string;
531
+ }): ConnectorHttpError;
532
+ }
533
+ /** Cross-bundle-safe recognizer. Prefer this over `instanceof`. */
534
+ declare function isConnectorHttpError(value: unknown): value is ConnectorHttpError;
535
+ /**
536
+ * Throw a `ConnectorHttpError` when `res` is not ok; otherwise return `res`
537
+ * unchanged (the success-path body is never consumed, so callers can still
538
+ * read it). A delegator: it reads the failed body and hands off to
539
+ * `ConnectorHttpError.fromResponseBody`. Named after the `zapier-platform`
540
+ * CLI's `response.throwForStatus()` idiom, but a standalone helper — it does
541
+ * not augment the `Response`.
542
+ *
543
+ * For the common path:
544
+ *
545
+ * const res = await ctx.fetch(url, init);
546
+ * await throwForStatus(res);
547
+ * return res.json();
548
+ *
549
+ * The optional `message` describes the call site for the rendered error and
550
+ * the thrown `.message` — useful when one script makes several requests and a
551
+ * bare `HTTP 404` wouldn't say which:
552
+ *
553
+ * await throwForStatus(readRes, "Failed to read the source page");
554
+ * await throwForStatus(createRes, "Failed to create the target page");
555
+ *
556
+ * The `message` need not mention the status: `toString()` always renders a
557
+ * `status: <code> <text>` line. When omitted, a generic `HTTP <status>
558
+ * <statusText>` summary is used.
559
+ *
560
+ * When the failure arrives in a 200 body (so the body must be read before the
561
+ * error is known), build the error directly with
562
+ * `ConnectorHttpError.fromResponseBody(res, body, { message? })` instead.
563
+ */
564
+ declare function throwForStatus(res: Response, message?: string): Promise<Response>;
565
+
456
566
  /**
457
567
  * `handleIfScriptMain(meta, definition, opts?)` — per-script CLI execution
458
568
  * surface (not the connector bin).
@@ -570,4 +680,4 @@ type NamedFunctions<TScripts extends AnyScripts> = {
570
680
  */
571
681
  declare function toFunctions<TScripts extends AnyScripts, TResolvers extends ConnectionResolversMap>(connector: ConnectorDefinition<TScripts, TResolvers>): NamedFunctions<ConnectorDefinition<TScripts, TResolvers>["scripts"]>;
572
682
 
573
- export { type AnyToolDefinition, type ConnectorDefinition, type RunOptions, defineBearerTokenResolver, defineConnectionResolver, defineConnector, defineTool, handleIfScriptMain, runDispatchCli, toFunctions, zapierConnectionResolver };
683
+ export { type AnyToolDefinition, type ConnectorDefinition, ConnectorHttpError, type ConnectorHttpErrorOptions, type ConnectorHttpResponse, type RunOptions, defineBearerTokenResolver, defineConnectionResolver, defineConnector, defineTool, handleIfScriptMain, isConnectorHttpError, runDispatchCli, throwForStatus, toFunctions, zapierConnectionResolver };
package/dist/index.js CHANGED
@@ -596,6 +596,143 @@ function defineTool(config) {
596
596
  return base;
597
597
  }
598
598
 
599
+ // src/errors.ts
600
+ var CONNECTOR_HTTP_ERROR_SYMBOL = /* @__PURE__ */ Symbol.for("connectors.http-error");
601
+ var ConnectorHttpError = class _ConnectorHttpError extends Error {
602
+ [CONNECTOR_HTTP_ERROR_SYMBOL] = true;
603
+ name = "ConnectorHttpError";
604
+ response;
605
+ constructor(message, options) {
606
+ super(message);
607
+ this.response = options.response;
608
+ Object.setPrototypeOf(this, new.target.prototype);
609
+ captureOriginStack(this, _ConnectorHttpError);
610
+ }
611
+ /**
612
+ * Multi-line, human-readable rendering for CLI/executable consumption. The
613
+ * single-line `.message` stays the summary; this adds the connector call site
614
+ * it was thrown from (the first stack frame, SDK frames elided), the HTTP
615
+ * `status` (always — so the message never has to restate it), the response
616
+ * headers (verbatim — they carry header-only signals like `Retry-After`), and
617
+ * the response body, so an operator (or agent) sees the full picture without
618
+ * digging into properties. A parsed JSON body is pretty-printed in full
619
+ * (error payloads are small); a non-JSON text body (HTML pages, stack dumps)
620
+ * is truncated.
621
+ */
622
+ toString() {
623
+ return formatConnectorHttpError(this);
624
+ }
625
+ /**
626
+ * Build a `ConnectorHttpError` from a `Response` whose body has already been
627
+ * read. This is the control path: a connector that wants its own message, or
628
+ * whose failure arrives as `200 { ok: false }` (so the body is read before
629
+ * the error is known), calls this directly. When `message` is omitted, a
630
+ * generic `HTTP <status> <statusText>` summary is used.
631
+ */
632
+ static fromResponseBody(res, body, opts = {}) {
633
+ const headers = res.headers;
634
+ const statusText = res.statusText ? ` ${res.statusText}` : "";
635
+ const err = new _ConnectorHttpError(
636
+ opts.message ?? `HTTP ${res.status}${statusText}`,
637
+ {
638
+ response: {
639
+ status: res.status,
640
+ statusText: res.statusText ?? "",
641
+ headers: headers ? headersToRecord(headers) : {},
642
+ body
643
+ }
644
+ }
645
+ );
646
+ captureOriginStack(err, _ConnectorHttpError.fromResponseBody);
647
+ return err;
648
+ }
649
+ };
650
+ function isConnectorHttpError(value) {
651
+ return Boolean(
652
+ value && typeof value === "object" && value[CONNECTOR_HTTP_ERROR_SYMBOL] === true
653
+ );
654
+ }
655
+ async function throwForStatus(res, message) {
656
+ if (res.ok) return res;
657
+ const origin = {};
658
+ captureOriginStack(origin, throwForStatus);
659
+ const err = ConnectorHttpError.fromResponseBody(res, await readBody(res), {
660
+ message
661
+ });
662
+ const firstNewline = origin.stack?.indexOf("\n") ?? -1;
663
+ if (origin.stack !== void 0 && firstNewline !== -1) {
664
+ err.stack = `${err.name}: ${err.message}${origin.stack.slice(firstNewline)}`;
665
+ }
666
+ throw err;
667
+ }
668
+ function captureOriginStack(target, sentinel) {
669
+ const capture = Error.captureStackTrace;
670
+ if (typeof capture === "function") capture(target, sentinel);
671
+ }
672
+ function headersToRecord(headers) {
673
+ const out = {};
674
+ headers.forEach((value, key) => {
675
+ out[key] = value;
676
+ });
677
+ return out;
678
+ }
679
+ async function readBody(res) {
680
+ let text;
681
+ try {
682
+ text = await res.text();
683
+ } catch {
684
+ return void 0;
685
+ }
686
+ if (text === "") return "";
687
+ try {
688
+ return JSON.parse(text);
689
+ } catch {
690
+ return text;
691
+ }
692
+ }
693
+ var MAX_BODY_CHARS = 2e3;
694
+ function formatConnectorHttpError(err) {
695
+ const lines = [`${err.name}: ${err.message}`];
696
+ const origin = originFrame(err.stack);
697
+ if (origin) lines.push(` ${origin}`);
698
+ const { status, statusText } = err.response;
699
+ lines.push(` status: ${status}${statusText ? ` ${statusText}` : ""}`);
700
+ const headers = formatHeaders(err.response.headers);
701
+ if (headers) lines.push(headers);
702
+ if (err.response.body !== void 0 && err.response.body !== "") {
703
+ lines.push(` body: ${summarizeBody(err.response.body)}`);
704
+ }
705
+ return lines.join("\n");
706
+ }
707
+ function formatHeaders(headers) {
708
+ const entries = Object.entries(headers);
709
+ if (entries.length === 0) return void 0;
710
+ const rendered = entries.map(([key, value]) => ` ${key}: ${value}`);
711
+ return ` headers:
712
+ ${rendered.join("\n")}`;
713
+ }
714
+ function originFrame(stack) {
715
+ if (!stack) return void 0;
716
+ for (const line of stack.split("\n")) {
717
+ const trimmed = line.trim();
718
+ if (trimmed.startsWith("at ")) return trimmed;
719
+ }
720
+ return void 0;
721
+ }
722
+ function summarizeBody(body) {
723
+ if (typeof body === "string") {
724
+ return body.length > MAX_BODY_CHARS ? `${body.slice(0, MAX_BODY_CHARS)}\u2026 (truncated)` : body;
725
+ }
726
+ return safeJsonStringify(body) ?? String(body);
727
+ }
728
+ function safeJsonStringify(value) {
729
+ try {
730
+ return JSON.stringify(value, null, 2);
731
+ } catch {
732
+ return void 0;
733
+ }
734
+ }
735
+
599
736
  // src/surfaces/handle-if-script-main.ts
600
737
  import { z as z3 } from "zod";
601
738
 
@@ -717,11 +854,31 @@ async function buildMcpServer(meta, connector, opts = {}) {
717
854
  // `input` because the registry walk is polymorphic over every
718
855
  // script's schema.
719
856
  async (input) => {
720
- const result = await script.run(input, runOpts);
721
- return {
722
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
723
- structuredContent: result
724
- };
857
+ try {
858
+ const result = await script.run(input, runOpts);
859
+ return {
860
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
861
+ structuredContent: result
862
+ };
863
+ } catch (err) {
864
+ if (isConnectorHttpError(err)) {
865
+ return {
866
+ content: [
867
+ { type: "text", text: err.toString() },
868
+ {
869
+ type: "text",
870
+ text: JSON.stringify(
871
+ { message: err.message, response: err.response },
872
+ null,
873
+ 2
874
+ )
875
+ }
876
+ ],
877
+ isError: true
878
+ };
879
+ }
880
+ throw err;
881
+ }
725
882
  }
726
883
  );
727
884
  }
@@ -809,7 +966,7 @@ async function runDispatchCli(meta, connector) {
809
966
  }
810
967
  }
811
968
  function reportFatalErrorAndExit(err, stderr, exit) {
812
- const message = err instanceof Error ? err.stack ?? `${err.name}: ${err.message}` : String(err);
969
+ const message = isConnectorHttpError(err) ? err.toString() : err instanceof Error ? err.stack ?? `${err.name}: ${err.message}` : String(err);
813
970
  stderr.write(`${message}
814
971
  `);
815
972
  exit(1);
@@ -1243,12 +1400,15 @@ function toFunctions(connector) {
1243
1400
  return out;
1244
1401
  }
1245
1402
  export {
1403
+ ConnectorHttpError,
1246
1404
  defineBearerTokenResolver,
1247
1405
  defineConnectionResolver,
1248
1406
  defineConnector,
1249
1407
  defineTool,
1250
1408
  handleIfScriptMain,
1409
+ isConnectorHttpError,
1251
1410
  runDispatchCli,
1411
+ throwForStatus,
1252
1412
  toFunctions,
1253
1413
  zapierConnectionResolver
1254
1414
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/connectors-sdk",
3
- "version": "0.1.0-experimental.14",
3
+ "version": "0.1.0-experimental.15",
4
4
  "description": "SDK for building Zapier connectors. Provides the authoring primitives and execution surfaces for connector scripts.",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",