@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 +169 -6
- package/dist/index.d.cts +111 -1
- package/dist/index.d.ts +111 -1
- package/dist/index.js +166 -6
- package/package.json +1 -1
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
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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.
|
|
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",
|