@zapier/connectors-sdk 0.1.0-experimental.13 → 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 +175 -15
- package/dist/index.d.cts +111 -1
- package/dist/index.d.ts +111 -1
- package/dist/index.js +172 -15
- 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);
|
|
@@ -1164,18 +1324,14 @@ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
|
|
|
1164
1324
|
} else if (arg === "--filter") {
|
|
1165
1325
|
const value = args[i + 1];
|
|
1166
1326
|
if (value === void 0 || value.length === 0) {
|
|
1167
|
-
throw new Error(
|
|
1168
|
-
`\`--filter\` requires a jq expression, e.g. \`--filter '.results | length'\`.`
|
|
1169
|
-
);
|
|
1327
|
+
throw new Error(`\`--filter\` requires a jq expression argument.`);
|
|
1170
1328
|
}
|
|
1171
1329
|
filter = value;
|
|
1172
1330
|
i++;
|
|
1173
1331
|
} else if (arg.startsWith("--filter=")) {
|
|
1174
1332
|
const value = arg.slice("--filter=".length);
|
|
1175
1333
|
if (value.length === 0) {
|
|
1176
|
-
throw new Error(
|
|
1177
|
-
`\`--filter\` requires a jq expression, e.g. \`--filter '.results | length'\`.`
|
|
1178
|
-
);
|
|
1334
|
+
throw new Error(`\`--filter\` requires a jq expression argument.`);
|
|
1179
1335
|
}
|
|
1180
1336
|
filter = value;
|
|
1181
1337
|
} else if (arg.startsWith("--")) {
|
|
@@ -1257,10 +1413,11 @@ function buildHelpText(definition, connectionResolvers, opts = {}) {
|
|
|
1257
1413
|
}
|
|
1258
1414
|
lines.push("");
|
|
1259
1415
|
lines.push(
|
|
1260
|
-
" --filter <jq> Transform the JSON output through a jq expression
|
|
1416
|
+
" --filter <jq> Transform the JSON output through a jq expression,"
|
|
1417
|
+
);
|
|
1418
|
+
lines.push(
|
|
1419
|
+
" e.g. to trim large results. See the output schema below."
|
|
1261
1420
|
);
|
|
1262
|
-
lines.push(" Useful for trimming large outputs, e.g.");
|
|
1263
|
-
lines.push(" --filter '.results | length'.");
|
|
1264
1421
|
lines.push("");
|
|
1265
1422
|
if (opts.invocation) {
|
|
1266
1423
|
lines.push("Example:");
|
|
@@ -1353,12 +1510,15 @@ function toFunctions(connector) {
|
|
|
1353
1510
|
}
|
|
1354
1511
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1355
1512
|
0 && (module.exports = {
|
|
1513
|
+
ConnectorHttpError,
|
|
1356
1514
|
defineBearerTokenResolver,
|
|
1357
1515
|
defineConnectionResolver,
|
|
1358
1516
|
defineConnector,
|
|
1359
1517
|
defineTool,
|
|
1360
1518
|
handleIfScriptMain,
|
|
1519
|
+
isConnectorHttpError,
|
|
1361
1520
|
runDispatchCli,
|
|
1521
|
+
throwForStatus,
|
|
1362
1522
|
toFunctions,
|
|
1363
1523
|
zapierConnectionResolver
|
|
1364
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);
|
|
@@ -1058,18 +1215,14 @@ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
|
|
|
1058
1215
|
} else if (arg === "--filter") {
|
|
1059
1216
|
const value = args[i + 1];
|
|
1060
1217
|
if (value === void 0 || value.length === 0) {
|
|
1061
|
-
throw new Error(
|
|
1062
|
-
`\`--filter\` requires a jq expression, e.g. \`--filter '.results | length'\`.`
|
|
1063
|
-
);
|
|
1218
|
+
throw new Error(`\`--filter\` requires a jq expression argument.`);
|
|
1064
1219
|
}
|
|
1065
1220
|
filter = value;
|
|
1066
1221
|
i++;
|
|
1067
1222
|
} else if (arg.startsWith("--filter=")) {
|
|
1068
1223
|
const value = arg.slice("--filter=".length);
|
|
1069
1224
|
if (value.length === 0) {
|
|
1070
|
-
throw new Error(
|
|
1071
|
-
`\`--filter\` requires a jq expression, e.g. \`--filter '.results | length'\`.`
|
|
1072
|
-
);
|
|
1225
|
+
throw new Error(`\`--filter\` requires a jq expression argument.`);
|
|
1073
1226
|
}
|
|
1074
1227
|
filter = value;
|
|
1075
1228
|
} else if (arg.startsWith("--")) {
|
|
@@ -1151,10 +1304,11 @@ function buildHelpText(definition, connectionResolvers, opts = {}) {
|
|
|
1151
1304
|
}
|
|
1152
1305
|
lines.push("");
|
|
1153
1306
|
lines.push(
|
|
1154
|
-
" --filter <jq> Transform the JSON output through a jq expression
|
|
1307
|
+
" --filter <jq> Transform the JSON output through a jq expression,"
|
|
1308
|
+
);
|
|
1309
|
+
lines.push(
|
|
1310
|
+
" e.g. to trim large results. See the output schema below."
|
|
1155
1311
|
);
|
|
1156
|
-
lines.push(" Useful for trimming large outputs, e.g.");
|
|
1157
|
-
lines.push(" --filter '.results | length'.");
|
|
1158
1312
|
lines.push("");
|
|
1159
1313
|
if (opts.invocation) {
|
|
1160
1314
|
lines.push("Example:");
|
|
@@ -1246,12 +1400,15 @@ function toFunctions(connector) {
|
|
|
1246
1400
|
return out;
|
|
1247
1401
|
}
|
|
1248
1402
|
export {
|
|
1403
|
+
ConnectorHttpError,
|
|
1249
1404
|
defineBearerTokenResolver,
|
|
1250
1405
|
defineConnectionResolver,
|
|
1251
1406
|
defineConnector,
|
|
1252
1407
|
defineTool,
|
|
1253
1408
|
handleIfScriptMain,
|
|
1409
|
+
isConnectorHttpError,
|
|
1254
1410
|
runDispatchCli,
|
|
1411
|
+
throwForStatus,
|
|
1255
1412
|
toFunctions,
|
|
1256
1413
|
zapierConnectionResolver
|
|
1257
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",
|