@vornrun/connector-sdk 0.7.1-beta.2 → 0.7.1-beta.4

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.
@@ -21,7 +21,6 @@ function loopbackEndpoint(env, names, fail = (message) => new Error(message)) {
21
21
  // src/session.ts
22
22
  var BROWSER_HOST_ENV = "VORN_BROWSER_HOST";
23
23
  var BROWSER_TOKEN_ENV = "VORN_BROWSER_TOKEN";
24
- var SESSION_CALL_META = "vorn/sessionCall";
25
24
  var SESSION_CALL_HEADER = "x-vorn-session-call";
26
25
  var SessionUnavailableError = class extends Error {
27
26
  /** Asking again cannot bring the window back, so the SDK's retries let this through at once. */
@@ -40,10 +39,10 @@ var SessionRefusedError = class extends Error {
40
39
  };
41
40
  var SESSION_TIMEOUT_MS = 45e3;
42
41
  var NULL_BODY_STATUSES = /* @__PURE__ */ new Set([204, 205, 304]);
43
- function readReply(text) {
42
+ function readReply(text2) {
44
43
  let parsed;
45
44
  try {
46
- parsed = JSON.parse(text);
45
+ parsed = JSON.parse(text2);
47
46
  } catch {
48
47
  throw new Error("The signed-in window answered with a body that is not JSON");
49
48
  }
@@ -53,7 +52,7 @@ function readReply(text) {
53
52
  }
54
53
  return reply2;
55
54
  }
56
- var refusal = (text) => text.trim() || void 0;
55
+ var refusal = (text2) => text2.trim() || void 0;
57
56
  function createSessionFetch(options = {}) {
58
57
  const env = options.env ?? process.env;
59
58
  const call = options.fetchImpl ?? fetch;
@@ -81,29 +80,173 @@ function createSessionFetch(options = {}) {
81
80
  url: request.url,
82
81
  method: request.method,
83
82
  headers: Object.fromEntries(request.headers),
84
- ...body !== void 0 && { body }
83
+ ...body !== void 0 && { body },
84
+ binaryBody: true
85
85
  }),
86
86
  signal: AbortSignal.any([request.signal, AbortSignal.timeout(SESSION_TIMEOUT_MS)])
87
87
  });
88
- const text = await answer.text();
88
+ const text2 = await answer.text();
89
89
  if (answer.status === 503) {
90
90
  throw new SessionUnavailableError(
91
- refusal(text) ?? "Vorn could not reach the signed-in window"
91
+ refusal(text2) ?? "Vorn could not reach the signed-in window"
92
92
  );
93
93
  }
94
94
  if (!answer.ok) {
95
95
  throw new SessionRefusedError(
96
- refusal(text) ?? `The signed-in window refused the call with HTTP ${answer.status}`
96
+ refusal(text2) ?? `The signed-in window refused the call with HTTP ${answer.status}`
97
97
  );
98
98
  }
99
- const reply2 = readReply(text);
100
- return new Response(NULL_BODY_STATUSES.has(reply2.status) ? null : reply2.body ?? "", {
99
+ const reply2 = readReply(text2);
100
+ const content = reply2.bodyBase64 !== void 0 ? Buffer.from(reply2.bodyBase64, "base64") : reply2.body ?? "";
101
+ return new Response(NULL_BODY_STATUSES.has(reply2.status) ? null : content, {
101
102
  status: reply2.status,
102
103
  ...reply2.headers && { headers: reply2.headers }
103
104
  });
104
105
  });
105
106
  }
106
107
 
108
+ // src/protocol.ts
109
+ var PROTOCOL_VERSION = 1;
110
+ var SUPPORTED_PROTOCOLS = [PROTOCOL_VERSION];
111
+ var MAX_FRAME_BYTES = 16 * 1024 * 1024;
112
+ var PROTOCOL_METHODS = {
113
+ hello: "vorn/hello",
114
+ manifest: "connector/manifest",
115
+ preflight: "connector/preflight",
116
+ options: "connector/options",
117
+ poll: "trigger/poll",
118
+ action: "action/run",
119
+ footer: "extension/footer",
120
+ handler: "extension/handler"
121
+ };
122
+ var PROTOCOL_ERROR_CODES = {
123
+ methodNotFound: -32601,
124
+ invalidParams: -32602,
125
+ connectorError: -32e3,
126
+ unsupportedProtocol: -32001,
127
+ beforeHello: -32002
128
+ };
129
+ var PROTOCOL_ERROR_KINDS = [
130
+ "validation",
131
+ "app-offline",
132
+ "signed-out",
133
+ "upstream",
134
+ "internal"
135
+ ];
136
+
137
+ // src/resilience.ts
138
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
139
+ var DEFAULT_ATTEMPTS = 3;
140
+ var DEFAULT_BASE_DELAY_MS = 250;
141
+ var DEFAULT_MAX_DELAY_MS = 3e4;
142
+ var MAX_ATTEMPTS = 10;
143
+ var MAX_TOTAL_WAIT_MS = 12e4;
144
+ var wait = (ms) => new Promise((resolve4) => {
145
+ setTimeout(resolve4, ms);
146
+ });
147
+ function retryAfterMs(header, now) {
148
+ if (!header) return void 0;
149
+ const trimmed = header.trim();
150
+ if (trimmed === "") return void 0;
151
+ const seconds = Number(trimmed);
152
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
153
+ const at = Date.parse(trimmed);
154
+ if (Number.isNaN(at)) return void 0;
155
+ return Math.max(0, at - now);
156
+ }
157
+ function backoffMs(attempt, policy = {}) {
158
+ const base = policy.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
159
+ const max = policy.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
160
+ return Math.min(max, base * 2 ** attempt);
161
+ }
162
+ function isFinal(error) {
163
+ return error?.retryable === false;
164
+ }
165
+ function resilientFetch(options) {
166
+ const attempts = Math.min(MAX_ATTEMPTS, Math.max(1, options.retry?.attempts ?? DEFAULT_ATTEMPTS));
167
+ const sleep = options.sleep ?? wait;
168
+ const ceiling = options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
169
+ const send = async (input, init) => {
170
+ let waited = 0;
171
+ const pause = async (ms) => {
172
+ if (waited + ms > MAX_TOTAL_WAIT_MS) return false;
173
+ waited += ms;
174
+ await sleep(ms);
175
+ return true;
176
+ };
177
+ for (let attempt = 0; attempt < attempts; attempt++) {
178
+ const last = attempt === attempts - 1;
179
+ try {
180
+ const response = await options.fetchImpl(input, init);
181
+ if (!RETRYABLE_STATUS.has(response.status)) return response;
182
+ if (!options.retryable || last) return response;
183
+ const asked = retryAfterMs(response.headers.get("retry-after"), Date.now());
184
+ const delay = asked === void 0 ? backoffMs(attempt, options.retry) : Math.min(asked, ceiling);
185
+ if (!await pause(delay)) return response;
186
+ } catch (error) {
187
+ if (!options.retryable || last || isFinal(error)) throw error;
188
+ if (!await pause(backoffMs(attempt, options.retry))) throw error;
189
+ }
190
+ }
191
+ throw new Error("Request was never attempted");
192
+ };
193
+ return send;
194
+ }
195
+
196
+ // src/errors.ts
197
+ var ActionArgumentError = class extends Error {
198
+ field;
199
+ constructor(field, message) {
200
+ super(message);
201
+ this.name = "ActionArgumentError";
202
+ this.field = field;
203
+ }
204
+ };
205
+ var UpstreamStatusError = class extends Error {
206
+ status;
207
+ viaSession;
208
+ constructor(status, message, viaSession = false) {
209
+ super(message);
210
+ this.name = "UpstreamStatusError";
211
+ this.status = status;
212
+ this.viaSession = viaSession;
213
+ }
214
+ };
215
+ var UnknownNameError = class extends Error {
216
+ constructor(message) {
217
+ super(message);
218
+ this.name = "UnknownNameError";
219
+ }
220
+ };
221
+ var messageOf = (error) => error instanceof Error ? error.message : String(error);
222
+ function* causes(error) {
223
+ let at = error;
224
+ for (let depth = 0; at !== void 0 && depth < 8; depth++) {
225
+ yield at;
226
+ at = at instanceof Error ? at.cause : void 0;
227
+ }
228
+ }
229
+ function protocolError(error) {
230
+ const failed = (data) => ({
231
+ code: PROTOCOL_ERROR_CODES.connectorError,
232
+ message: messageOf(error),
233
+ data
234
+ });
235
+ for (const at of causes(error)) {
236
+ if (at instanceof ActionArgumentError) return failed({ kind: "validation", field: at.field });
237
+ if (at instanceof SessionUnavailableError) {
238
+ return failed({ kind: "app-offline", retryable: false });
239
+ }
240
+ if (at instanceof UpstreamStatusError) {
241
+ if (at.viaSession && (at.status === 401 || at.status === 403)) {
242
+ return failed({ kind: "signed-out", retryable: false });
243
+ }
244
+ return failed({ kind: "upstream", retryable: RETRYABLE_STATUS.has(at.status) });
245
+ }
246
+ }
247
+ return failed({ kind: "internal" });
248
+ }
249
+
107
250
  // src/origins.ts
108
251
  var ORIGIN_PATTERN = /^https:\/\/(\*\.)?[a-z0-9-]+(\.[a-z0-9-]+)+$/i;
109
252
  function withinOrigins(origins, url) {
@@ -697,18 +840,6 @@ function resolveConfig(connector, env = process.env) {
697
840
  }
698
841
 
699
842
  // src/setup.ts
700
- function pollToolName(triggerType) {
701
- return `poll_${triggerType}`;
702
- }
703
- function footerToolName(footerId) {
704
- return `vorn_footer_${footerId}`;
705
- }
706
- function handlerToolName(handlerId) {
707
- return `vorn_handler_${handlerId}`;
708
- }
709
- var MANIFEST_TOOL = "vorn_connector_manifest";
710
- var PREFLIGHT_TOOL = "vorn_connector_preflight";
711
- var OPTIONS_TOOL = "vorn_connector_options";
712
843
  function connectionSetup(connector, triggerType) {
713
844
  const trigger = connector.triggers.find((entry) => entry.type === triggerType);
714
845
  if (!trigger) {
@@ -717,16 +848,6 @@ function connectionSetup(connector, triggerType) {
717
848
  return {
718
849
  connectorId: connector.id,
719
850
  triggerType,
720
- filters: {
721
- pollTool: pollToolName(triggerType),
722
- itemsPath: "items",
723
- idField: "externalId",
724
- timestampField: "updatedAt",
725
- titleField: "title",
726
- urlField: "url",
727
- cursorArg: "cursor",
728
- cursorPath: "nextCursor"
729
- },
730
851
  env: connector.config.map((field) => ({
731
852
  name: envNameFor(field.key, field.env),
732
853
  required: field.required === true,
@@ -769,6 +890,7 @@ function manifestContributions(connector) {
769
890
  function connectorManifest(connector) {
770
891
  const contributes = manifestContributions(connector);
771
892
  return {
893
+ protocol: PROTOCOL_VERSION,
772
894
  id: connector.id,
773
895
  name: connector.name,
774
896
  version: connector.version,
@@ -794,11 +916,13 @@ function connectorManifest(connector) {
794
916
  type: action.type,
795
917
  label: action.label,
796
918
  ...action.description !== void 0 && { description: action.description },
919
+ ...action.idempotent !== void 0 && { idempotent: action.idempotent },
797
920
  inputs: (action.inputs ?? []).map((input) => ({
798
921
  key: input.key,
799
922
  label: input.label,
800
923
  type: input.type ?? "string",
801
924
  required: input.required === true,
925
+ ...input.description !== void 0 && { description: input.description },
802
926
  ...input.options !== void 0 && { options: input.options },
803
927
  ...input.loadOptions !== void 0 && { loadOptions: input.loadOptions },
804
928
  ...input.builderHint !== void 0 && { builderHint: input.builderHint }
@@ -810,11 +934,37 @@ function connectorManifest(connector) {
810
934
  }
811
935
 
812
936
  // src/packaging.ts
937
+ import { spawn } from "child_process";
813
938
  import { builtinModules } from "module";
814
939
  import { existsSync, readFileSync } from "fs";
815
940
  import { cp, mkdtemp, readdir, stat, writeFile } from "fs/promises";
816
941
  import { tmpdir } from "os";
817
942
  import { dirname, isAbsolute, join, resolve } from "path";
943
+
944
+ // src/lines.ts
945
+ function lineReader(onLine, overflow) {
946
+ let pending = [];
947
+ let size = 0;
948
+ return (chunk) => {
949
+ let start = 0;
950
+ for (let end = chunk.indexOf(10); end !== -1; end = chunk.indexOf(10, start)) {
951
+ const part = chunk.subarray(start, end);
952
+ if (size + part.length > MAX_FRAME_BYTES) return overflow();
953
+ const whole = pending.length === 0 ? part : Buffer.concat([...pending, part], size + part.length);
954
+ const line = whole.toString("utf8");
955
+ pending = [];
956
+ size = 0;
957
+ start = end + 1;
958
+ onLine(line.endsWith("\r") ? line.slice(0, -1) : line);
959
+ }
960
+ const rest = chunk.subarray(start);
961
+ size += rest.length;
962
+ if (size > MAX_FRAME_BYTES) return overflow();
963
+ if (rest.length > 0) pending.push(rest);
964
+ };
965
+ }
966
+
967
+ // src/packaging.ts
818
968
  var MAX_PACK_BYTES = 8 * 1024 * 1024;
819
969
  var MAX_UNPACKED_BYTES = 32 * 1024 * 1024;
820
970
  var LIFECYCLE_SCRIPTS = [
@@ -1075,8 +1225,8 @@ function launchEnv() {
1075
1225
  }
1076
1226
  return env;
1077
1227
  }
1078
- function errorLine(text) {
1079
- const lines = text.split("\n").map((line) => line.trim()).filter((line) => line !== "");
1228
+ function errorLine(text2) {
1229
+ const lines = text2.split("\n").map((line) => line.trim()).filter((line) => line !== "");
1080
1230
  return [...lines].reverse().find((line) => /Error\b/.test(line)) ?? lines[lines.length - 1];
1081
1231
  }
1082
1232
  function withTimeout(promise, ms, message) {
@@ -1088,24 +1238,50 @@ function withTimeout(promise, ms, message) {
1088
1238
  })
1089
1239
  ]);
1090
1240
  }
1091
- async function packLaunchFindings(dir) {
1092
- const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
1093
- const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
1094
- const transport = new StdioClientTransport({
1095
- command: process.execPath,
1096
- args: ["index.js"],
1097
- cwd: dir,
1098
- env: launchEnv(),
1099
- stderr: "pipe"
1241
+ function helloAnswer(child) {
1242
+ return new Promise((resolve4, reject) => {
1243
+ const onLine = (line) => {
1244
+ let reply2;
1245
+ try {
1246
+ reply2 = JSON.parse(line);
1247
+ } catch {
1248
+ return;
1249
+ }
1250
+ if (reply2?.id !== 1) return;
1251
+ if (typeof reply2.result?.protocol === "number") return resolve4();
1252
+ reject(
1253
+ new Error(
1254
+ `answered ${PROTOCOL_METHODS.hello} with ${String(reply2.error?.message ?? "no protocol")}`
1255
+ )
1256
+ );
1257
+ };
1258
+ child.stdout.on(
1259
+ "data",
1260
+ lineReader(onLine, () => reject(new Error(`wrote a line over ${MAX_FRAME_BYTES} bytes`)))
1261
+ );
1262
+ child.once("error", reject);
1263
+ child.once("exit", (code) => reject(new Error(`exited with code ${code} before answering`)));
1264
+ child.stdin.on("error", () => {
1265
+ });
1266
+ const params = {
1267
+ protocols: [...SUPPORTED_PROTOCOLS],
1268
+ host: { name: "vorn-connector-check", version: "1" }
1269
+ };
1270
+ child.stdin.write(
1271
+ `${JSON.stringify({ jsonrpc: "2.0", id: 1, method: PROTOCOL_METHODS.hello, params })}
1272
+ `
1273
+ );
1100
1274
  });
1101
- const client = new Client({ name: "vorn-connector-check", version: "1" }, { capabilities: {} });
1275
+ }
1276
+ async function packLaunchFindings(dir) {
1277
+ const child = spawn(process.execPath, ["index.js"], { cwd: dir, env: launchEnv() });
1102
1278
  let stderr = "";
1103
- transport.stderr?.on("data", (chunk) => {
1279
+ child.stderr.on("data", (chunk) => {
1104
1280
  stderr += chunk.toString();
1105
1281
  });
1106
1282
  try {
1107
1283
  await withTimeout(
1108
- client.connect(transport),
1284
+ helloAnswer(child),
1109
1285
  LAUNCH_TIMEOUT_MS,
1110
1286
  `did not answer within ${LAUNCH_TIMEOUT_MS / 1e3}s of starting`
1111
1287
  );
@@ -1116,10 +1292,7 @@ async function packLaunchFindings(dir) {
1116
1292
  finding("pack-launch", "bundle", `did not start as a pack: ${errorLine(stderr) ?? said}`)
1117
1293
  ];
1118
1294
  } finally {
1119
- await client.close().catch(() => {
1120
- });
1121
- await transport.close().catch(() => {
1122
- });
1295
+ child.kill("SIGKILL");
1123
1296
  }
1124
1297
  }
1125
1298
  async function esbuildBundle(request) {
@@ -1189,13 +1362,13 @@ function createExtensionHost(options) {
1189
1362
  body: JSON.stringify({ sessionId: options.sessionId, ...params }),
1190
1363
  signal: AbortSignal.timeout(HOST_TIMEOUT_MS)
1191
1364
  });
1192
- const text = await response.text();
1193
- if (response.status === 403) throw new PermissionDeniedError(method, text || "not granted");
1365
+ const text2 = await response.text();
1366
+ if (response.status === 403) throw new PermissionDeniedError(method, text2 || "not granted");
1194
1367
  if (!response.ok) throw new Error(`The host answered ${method} with HTTP ${response.status}`);
1195
- if (text === "") return void 0;
1368
+ if (text2 === "") return void 0;
1196
1369
  let parsed;
1197
1370
  try {
1198
- parsed = JSON.parse(text);
1371
+ parsed = JSON.parse(text2);
1199
1372
  } catch {
1200
1373
  throw new HostReplyError(method, "a body that is not JSON");
1201
1374
  }
@@ -1209,7 +1382,7 @@ function createExtensionHost(options) {
1209
1382
  status: () => ask("status", {}),
1210
1383
  output: (opts) => ask("output", { ...opts?.lines !== void 0 && { lines: opts.lines } }),
1211
1384
  selection: () => ask("selection", {}),
1212
- send: (text) => ask("send", { text }),
1385
+ send: (text2) => ask("send", { text: text2 }),
1213
1386
  rename: (name) => ask("rename", { name }),
1214
1387
  usage: () => ask("usage", {})
1215
1388
  };
@@ -1424,8 +1597,8 @@ function resolveTemplates(value, scope, substitute) {
1424
1597
  return value.replace(PLACEHOLDER, (_match, source, path) => {
1425
1598
  const resolved = lookup(source, path, scope);
1426
1599
  if (resolved === void 0 || resolved === null) return "";
1427
- const text = String(resolved);
1428
- return substitute === void 0 ? text : substitute(text, source);
1600
+ const text2 = String(resolved);
1601
+ return substitute === void 0 ? text2 : substitute(text2, source);
1429
1602
  });
1430
1603
  }
1431
1604
  if (Array.isArray(value)) return value.map((entry) => resolveTemplates(entry, scope, substitute));
@@ -1485,14 +1658,14 @@ function resolveRequest(request, scope) {
1485
1658
  return resolved;
1486
1659
  }
1487
1660
  async function readBody(response) {
1488
- const text = await response.text();
1489
- if (text === "") return void 0;
1661
+ const text2 = await response.text();
1662
+ if (text2 === "") return void 0;
1490
1663
  const type = response.headers.get("content-type") ?? "";
1491
- if (!type.includes("json")) return text;
1664
+ if (!type.includes("json")) return text2;
1492
1665
  try {
1493
- return JSON.parse(text);
1666
+ return JSON.parse(text2);
1494
1667
  } catch {
1495
- return text;
1668
+ return text2;
1496
1669
  }
1497
1670
  }
1498
1671
  function describeFailure(response, body) {
@@ -1507,7 +1680,13 @@ async function sendRequest(resolved, options) {
1507
1680
  ...resolved.body !== void 0 && { body: resolved.body }
1508
1681
  });
1509
1682
  const body = await readBody(response);
1510
- if (!response.ok) throw new Error(describeFailure(response, body));
1683
+ if (!response.ok) {
1684
+ throw new UpstreamStatusError(
1685
+ response.status,
1686
+ describeFailure(response, body),
1687
+ options.viaSession
1688
+ );
1689
+ }
1511
1690
  return { response, body };
1512
1691
  }
1513
1692
  function asOutput(value) {
@@ -1583,65 +1762,6 @@ async function executeRequest(request, postReceive, scope, options) {
1583
1762
  return asOutput(applyPostReceive(body, postReceive));
1584
1763
  }
1585
1764
 
1586
- // src/resilience.ts
1587
- var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
1588
- var DEFAULT_ATTEMPTS = 3;
1589
- var DEFAULT_BASE_DELAY_MS = 250;
1590
- var DEFAULT_MAX_DELAY_MS = 3e4;
1591
- var MAX_ATTEMPTS = 10;
1592
- var MAX_TOTAL_WAIT_MS = 12e4;
1593
- var wait = (ms) => new Promise((resolve4) => {
1594
- setTimeout(resolve4, ms);
1595
- });
1596
- function retryAfterMs(header, now) {
1597
- if (!header) return void 0;
1598
- const trimmed = header.trim();
1599
- if (trimmed === "") return void 0;
1600
- const seconds = Number(trimmed);
1601
- if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
1602
- const at = Date.parse(trimmed);
1603
- if (Number.isNaN(at)) return void 0;
1604
- return Math.max(0, at - now);
1605
- }
1606
- function backoffMs(attempt, policy = {}) {
1607
- const base = policy.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
1608
- const max = policy.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
1609
- return Math.min(max, base * 2 ** attempt);
1610
- }
1611
- function isFinal(error) {
1612
- return error?.retryable === false;
1613
- }
1614
- function resilientFetch(options) {
1615
- const attempts = Math.min(MAX_ATTEMPTS, Math.max(1, options.retry?.attempts ?? DEFAULT_ATTEMPTS));
1616
- const sleep = options.sleep ?? wait;
1617
- const ceiling = options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
1618
- const send = async (input, init) => {
1619
- let waited = 0;
1620
- const pause = async (ms) => {
1621
- if (waited + ms > MAX_TOTAL_WAIT_MS) return false;
1622
- waited += ms;
1623
- await sleep(ms);
1624
- return true;
1625
- };
1626
- for (let attempt = 0; attempt < attempts; attempt++) {
1627
- const last = attempt === attempts - 1;
1628
- try {
1629
- const response = await options.fetchImpl(input, init);
1630
- if (!RETRYABLE_STATUS.has(response.status)) return response;
1631
- if (!options.retryable || last) return response;
1632
- const asked = retryAfterMs(response.headers.get("retry-after"), Date.now());
1633
- const delay = asked === void 0 ? backoffMs(attempt, options.retry) : Math.min(asked, ceiling);
1634
- if (!await pause(delay)) return response;
1635
- } catch (error) {
1636
- if (!options.retryable || last || isFinal(error)) throw error;
1637
- if (!await pause(backoffMs(attempt, options.retry))) throw error;
1638
- }
1639
- }
1640
- throw new Error("Request was never attempted");
1641
- };
1642
- return send;
1643
- }
1644
-
1645
1765
  // src/runtime.ts
1646
1766
  function wrap(fetchImpl, options, retryable) {
1647
1767
  return resilientFetch({
@@ -1660,7 +1780,7 @@ var MAX_POLL_PAGES = 1e3;
1660
1780
  async function runPoll(connector, triggerType, options = {}) {
1661
1781
  const trigger = connector.triggers.find((entry) => entry.type === triggerType);
1662
1782
  if (!trigger) {
1663
- throw new Error(`Connector ${connector.id} has no trigger "${triggerType}"`);
1783
+ throw new UnknownNameError(`${connector.id} has no trigger "${triggerType}"`);
1664
1784
  }
1665
1785
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
1666
1786
  const polledAt = now();
@@ -1709,7 +1829,7 @@ var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
1709
1829
  async function runOptions(connector, name, options = {}) {
1710
1830
  const loader = connector.options?.[name];
1711
1831
  if (!loader) {
1712
- throw new Error(`Connector ${connector.id} serves no options set "${name}"`);
1832
+ throw new UnknownNameError(`${connector.id} serves no options set "${name}"`);
1713
1833
  }
1714
1834
  const session = sessionFor(connector, options, true);
1715
1835
  const loaded = await loader({
@@ -1729,46 +1849,52 @@ var MAX_QUOTED_VALUE = 80;
1729
1849
  function quote(value) {
1730
1850
  return value.length > MAX_QUOTED_VALUE ? `${value.slice(0, MAX_QUOTED_VALUE)}\u2026` : value;
1731
1851
  }
1852
+ var shown = (value) => typeof value === "string" ? `"${quote(value)}"` : quote(JSON.stringify(value) ?? String(value));
1732
1853
  function coerceArg(value, type) {
1733
- if (typeof value !== "string") return value;
1734
1854
  if (type === "number") {
1735
- const parsed = Number(value);
1736
- if (Number.isNaN(parsed)) throw new Error(`Expected a number, got "${quote(value)}"`);
1855
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1856
+ const parsed = typeof value === "string" ? Number(value) : Number.NaN;
1857
+ if (Number.isNaN(parsed)) throw new Error(`Expected a number, got ${shown(value)}`);
1737
1858
  return parsed;
1738
1859
  }
1739
1860
  if (type === "boolean") {
1861
+ if (typeof value === "boolean") return value;
1740
1862
  if (value === "true") return true;
1741
1863
  if (value === "false") return false;
1742
- throw new Error(`Expected a boolean, got "${quote(value)}"`);
1864
+ throw new Error(`Expected a boolean, got ${shown(value)}`);
1743
1865
  }
1744
1866
  if (type === "json") {
1867
+ if (typeof value !== "string") return value;
1745
1868
  try {
1746
1869
  return JSON.parse(value);
1747
1870
  } catch {
1748
- throw new Error(`Expected JSON, got "${quote(value)}"`);
1871
+ throw new Error(`Expected JSON, got ${shown(value)}`);
1749
1872
  }
1750
1873
  }
1751
- return value;
1874
+ if (typeof value === "string") return value;
1875
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
1752
1876
  }
1753
1877
  async function runAction(connector, actionType, args, options = {}) {
1754
1878
  const action = connector.actions.find((entry) => entry.type === actionType);
1755
1879
  if (!action) {
1756
- throw new Error(`Connector ${connector.id} has no action "${actionType}"`);
1880
+ throw new UnknownNameError(`${connector.id} has no action "${actionType}"`);
1757
1881
  }
1758
1882
  const coerced = { ...args };
1759
1883
  for (const input of action.inputs ?? []) {
1760
1884
  const value = coerced[input.key];
1761
- if (value === void 0 || value === "") {
1762
- if (input.required) throw new Error(`Action ${actionType} requires "${input.key}"`);
1885
+ if (value === void 0 || value === null || value === "") {
1886
+ if (input.required) {
1887
+ throw new ActionArgumentError(input.key, `Action ${actionType} requires "${input.key}"`);
1888
+ }
1763
1889
  delete coerced[input.key];
1764
1890
  continue;
1765
1891
  }
1766
1892
  try {
1767
1893
  coerced[input.key] = coerceArg(value, input.type);
1768
1894
  } catch (error) {
1769
- throw new Error(
1770
- `Action ${actionType} argument "${input.key}": ${error instanceof Error ? error.message : String(error)}`,
1771
- { cause: error }
1895
+ throw new ActionArgumentError(
1896
+ input.key,
1897
+ `Action ${actionType} argument "${input.key}": ${error instanceof Error ? error.message : String(error)}`
1772
1898
  );
1773
1899
  }
1774
1900
  }
@@ -1783,7 +1909,7 @@ async function runAction(connector, actionType, args, options = {}) {
1783
1909
  action.request,
1784
1910
  action.postReceive,
1785
1911
  { args: coerced, config },
1786
- { fetchImpl: session?.fetch ?? fetchImpl }
1912
+ { fetchImpl: session?.fetch ?? fetchImpl, viaSession: session !== void 0 }
1787
1913
  );
1788
1914
  } catch (error) {
1789
1915
  throw new Error(
@@ -1830,9 +1956,9 @@ var MockRouteMissError = class extends Error {
1830
1956
  }
1831
1957
  };
1832
1958
  function escapedMockHttp(error) {
1833
- for (let current = error; current instanceof Error; current = current.cause) {
1834
- if (current instanceof MockRouteMissError) return true;
1835
- if (current.message.includes("No mock route for ")) return true;
1959
+ for (const at of causes(error)) {
1960
+ if (at instanceof MockRouteMissError) return true;
1961
+ if (at instanceof Error && at.message.includes("No mock route for ")) return true;
1836
1962
  }
1837
1963
  return false;
1838
1964
  }
@@ -2091,6 +2217,26 @@ function mockConfig(connector) {
2091
2217
  }
2092
2218
  return config;
2093
2219
  }
2220
+ function outputTypeOf(value) {
2221
+ if (Array.isArray(value)) return "array";
2222
+ return value === null ? "null" : typeof value;
2223
+ }
2224
+ function outputTypeFindings(action, output) {
2225
+ return (action.outputs ?? []).flatMap((field) => {
2226
+ const value = output[field.key];
2227
+ if (field.type === void 0 || value === void 0 || value === null) return [];
2228
+ const actual = outputTypeOf(value);
2229
+ if (actual === field.type) return [];
2230
+ return [
2231
+ finding2(
2232
+ "warn",
2233
+ "mock-output-type",
2234
+ `action ${action.type}`,
2235
+ `returned "${field.key}" as ${actual}, but declares it ${field.type}`
2236
+ )
2237
+ ];
2238
+ });
2239
+ }
2094
2240
  async function mockFindings(connector, options) {
2095
2241
  if (!options.mock) return [];
2096
2242
  const config = options.config ?? mockConfig(connector);
@@ -2101,19 +2247,20 @@ async function mockFindings(connector, options) {
2101
2247
  const args = Object.fromEntries(
2102
2248
  (action.inputs ?? []).map((input) => [input.key, sampleArg(input)])
2103
2249
  );
2104
- const { result: thrown, calls } = await withMockHttp(routes, async () => {
2250
+ const { result: ran, calls } = await withMockHttp(routes, async () => {
2105
2251
  try {
2106
- await runAction(connector, action.type, args, {
2252
+ const output = await runAction(connector, action.type, args, {
2107
2253
  config,
2108
2254
  ...options.now && { now: options.now },
2109
2255
  sessionFetchImpl: globalThis.fetch
2110
2256
  });
2111
- return void 0;
2257
+ return { output };
2112
2258
  } catch (error) {
2113
- return error;
2259
+ return { thrown: error };
2114
2260
  }
2115
2261
  });
2116
- if (thrown !== void 0) {
2262
+ if ("thrown" in ran) {
2263
+ const { thrown } = ran;
2117
2264
  const reason = thrown instanceof Error ? thrown.message : String(thrown);
2118
2265
  const escaped = escapedMockHttp(thrown);
2119
2266
  found.push(
@@ -2136,6 +2283,7 @@ async function mockFindings(connector, options) {
2136
2283
  )
2137
2284
  );
2138
2285
  }
2286
+ found.push(...outputTypeFindings(action, ran.output));
2139
2287
  }
2140
2288
  return found;
2141
2289
  }
@@ -2296,7 +2444,7 @@ function liveExamines(connector) {
2296
2444
  return connector.preflight !== void 0 || connector.actions.some(liveRunnable);
2297
2445
  }
2298
2446
  function needsWindow(error) {
2299
- return error instanceof SessionUnavailableError || error instanceof Error && error.cause instanceof SessionUnavailableError;
2447
+ return [...causes(error)].some((at) => at instanceof SessionUnavailableError);
2300
2448
  }
2301
2449
  async function liveFindings(connector, options) {
2302
2450
  if (!options.live) return [];
@@ -2517,6 +2665,7 @@ var CHECK_OWNERS = {
2517
2665
  "mock-action-failed": "mock",
2518
2666
  "mock-network-escape": "mock",
2519
2667
  "mock-not-observed": "mock",
2668
+ "mock-output-type": "mock",
2520
2669
  "preflight-failed": "live",
2521
2670
  "live-action-failed": "live",
2522
2671
  "pack-launch": "launch",
@@ -2643,7 +2792,7 @@ async function packConnector(connector, options) {
2643
2792
 
2644
2793
  // src/scaffold.ts
2645
2794
  var ID_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
2646
- var SDK_DEPENDENCY_RANGE = "^0.7.0-beta.14";
2795
+ var SDK_DEPENDENCY_RANGE = "^0.7.1-beta.3";
2647
2796
  var SCAFFOLD_VERSION = "0.1.0";
2648
2797
  var VITEST_RANGE = "^4.1.10";
2649
2798
  function jsonFile(value) {
@@ -3208,271 +3357,228 @@ function scaffoldFiles(options) {
3208
3357
  }
3209
3358
 
3210
3359
  // src/server.ts
3211
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3212
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3213
- import { z } from "zod";
3214
- function sessionCallOf(extra) {
3215
- const key = extra._meta?.[SESSION_CALL_META];
3216
- return typeof key === "string" ? { sessionCall: key } : {};
3217
- }
3218
- function json(value) {
3219
- return {
3220
- // Vorn reads `structuredContent` to build step output and to find the
3221
- // `items` array a poll returned; the text block keeps the result readable
3222
- // in any generic MCP client.
3223
- content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
3224
- structuredContent: value
3225
- };
3226
- }
3227
- function failure(error) {
3228
- return {
3229
- content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
3230
- isError: true
3231
- };
3232
- }
3233
- function describeInput(input) {
3234
- const base = input.description ?? input.label;
3235
- if (input.loadOptions !== void 0) {
3236
- return `${base}. Choices come from this connector's "${input.loadOptions}" list.`;
3360
+ var SDK_VERSION = true ? "0.7.1-beta.4" : "0.0.0-source";
3361
+ var InvalidParams = class extends Error {
3362
+ };
3363
+ function text(params, key) {
3364
+ const value = params[key];
3365
+ if (typeof value !== "string" || value === "") {
3366
+ throw new InvalidParams(`"${key}" must be a non-empty string`);
3237
3367
  }
3238
- if (input.type === "json") return `${base}. Takes JSON.`;
3239
- const choices = (input.options ?? []).map((option) => option.value).filter((value) => typeof value === "string" && value !== "");
3240
- if (choices.length > 0) return `${base}. Suggested values: ${choices.join(", ")}.`;
3241
- return base;
3368
+ return value;
3369
+ }
3370
+ function optionalText(params, key) {
3371
+ const value = params[key];
3372
+ if (value === void 0 || value === null) return void 0;
3373
+ if (typeof value !== "string") throw new InvalidParams(`"${key}" must be a string`);
3374
+ return value;
3242
3375
  }
3243
- function inputShape(inputs) {
3244
- const shape = {};
3245
- for (const input of inputs) {
3246
- const base = z.string().describe(describeInput(input));
3247
- shape[input.key] = input.required ? base : base.optional();
3376
+ function optionalNumber(params, key) {
3377
+ const value = params[key];
3378
+ if (value === void 0 || value === null) return void 0;
3379
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3380
+ throw new InvalidParams(`"${key}" must be a number`);
3248
3381
  }
3249
- return shape;
3382
+ return value;
3250
3383
  }
3251
- function scalar(type) {
3252
- if (type === "number") return z.number();
3253
- if (type === "boolean") return z.boolean();
3254
- if (type === "string") return z.string();
3255
- return z.unknown();
3384
+ function failure(id, error) {
3385
+ return { jsonrpc: "2.0", id, error };
3256
3386
  }
3257
- function outputSchema(outputs) {
3258
- const shape = {};
3259
- for (const output of outputs) {
3260
- shape[output.key] = scalar(output.type).nullish().describe(output.description ?? output.key);
3261
- }
3262
- return z.looseObject(shape);
3387
+ function refusal2(id, code, message) {
3388
+ return failure(id, { code, message });
3263
3389
  }
3264
3390
  function createConnectorServer(connector, options = {}) {
3265
- const server = new McpServer(
3266
- { name: connector.id, version: connector.version },
3267
- { capabilities: { tools: {} } }
3268
- );
3269
3391
  let cached = options.config;
3270
3392
  const config = () => cached ??= resolveConfig(connector);
3271
- server.registerTool(
3272
- MANIFEST_TOOL,
3273
- {
3274
- description: `Describe the ${connector.name} connector and how to configure it`,
3275
- inputSchema: {},
3276
- outputSchema: z.looseObject({})
3277
- },
3278
- () => json(connectorManifest(connector))
3279
- );
3280
- if (connector.preflight) {
3281
- const preflight = connector.preflight.bind(connector);
3282
- server.registerTool(
3283
- PREFLIGHT_TOOL,
3284
- {
3285
- description: `Check whether ${connector.name} can run right now`,
3286
- inputSchema: {},
3287
- // Declared rather than left open like the manifest's: this shape is
3288
- // fixed, so a caller can validate against it. Still loose, because a
3289
- // connector adding a field of its own should not fail the call.
3290
- outputSchema: z.looseObject({
3291
- ok: z.boolean().describe("Whether the connector could run right now"),
3292
- message: z.string().optional().describe("What to do about it, when it could not")
3293
- })
3294
- },
3295
- async () => {
3296
- try {
3297
- return json({ ...await preflight() });
3298
- } catch (error) {
3299
- return json({
3300
- ok: false,
3301
- message: error instanceof Error ? error.message : String(error)
3302
- });
3303
- }
3304
- }
3305
- );
3306
- }
3307
- const optionSets = Object.keys(connector.options ?? {});
3308
- if (optionSets.length > 0) {
3309
- server.registerTool(
3310
- OPTIONS_TOOL,
3311
- {
3312
- description: `List what one of ${connector.name}'s fields can be set to`,
3313
- inputSchema: {
3314
- name: z.enum(optionSets).describe("Which options set to list")
3315
- },
3316
- outputSchema: z.looseObject({
3317
- options: z.array(z.looseObject({ value: z.string(), label: z.string().optional() })).describe("The choices, each a value to send and words to show")
3318
- })
3319
- },
3320
- async (args, extra) => {
3321
- try {
3322
- return json({
3323
- options: await runOptions(connector, args.name, {
3324
- config: config(),
3325
- ...options.now && { now: options.now },
3326
- ...sessionCallOf(extra)
3327
- })
3328
- });
3329
- } catch (error) {
3330
- return failure(error);
3331
- }
3332
- }
3333
- );
3334
- }
3335
- for (const trigger of connector.triggers) {
3336
- server.registerTool(
3337
- pollToolName(trigger.type),
3338
- {
3339
- description: trigger.description ?? `Poll ${connector.name} for ${trigger.label}`,
3340
- inputSchema: {
3341
- since: z.string().optional().describe("Only return items changed after this ISO timestamp"),
3342
- cursor: z.string().optional().describe("Opaque cursor from a previous page"),
3343
- limit: z.string().optional().describe("Maximum number of items to return")
3344
- },
3345
- outputSchema: z.looseObject({
3346
- items: z.array(z.looseObject({})).describe("Normalized items"),
3347
- nextCursor: z.string().optional().describe("Cursor for the next page"),
3348
- hasMore: z.boolean().describe("Whether another page is immediately available")
3349
- })
3350
- },
3351
- async (args, extra) => {
3352
- try {
3353
- const limit = args.limit === void 0 ? void 0 : Number(args.limit);
3354
- if (limit !== void 0 && !Number.isFinite(limit)) {
3355
- throw new Error(`Invalid limit "${args.limit}"`);
3356
- }
3357
- return json(
3358
- await runPoll(connector, trigger.type, {
3359
- config: config(),
3360
- ...args.since !== void 0 && { since: args.since },
3361
- ...args.cursor !== void 0 && { cursor: args.cursor },
3362
- ...limit !== void 0 && { limit },
3363
- ...options.now && { now: options.now },
3364
- ...sessionCallOf(extra)
3365
- })
3366
- );
3367
- } catch (error) {
3368
- return failure(error);
3369
- }
3370
- }
3371
- );
3372
- }
3373
- const sessionShape = {
3374
- sessionId: z.string().describe("The session this is being computed for"),
3375
- worktreePath: z.string().describe("Where the session's work is"),
3376
- agent: z.enum(EXTENSION_AGENTS).describe("Which agent runs in the session")
3377
- };
3378
- const sessionContext = (args, host) => ({
3379
- sessionId: args.sessionId,
3380
- worktreePath: args.worktreePath,
3381
- agent: args.agent,
3382
- host,
3383
- now: options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
3393
+ const now = options.now;
3394
+ let greeted = false;
3395
+ const runtime = (params) => ({
3396
+ config: config(),
3397
+ now,
3398
+ sessionCall: optionalText(params, "sessionCall")
3384
3399
  });
3385
3400
  const hostFor = (sessionId) => options.host?.(sessionId) ?? createExtensionHost({ sessionId });
3386
- for (const footer of connector.contributes?.footers ?? []) {
3387
- server.registerTool(
3388
- footerToolName(footer.id),
3389
- {
3390
- title: footer.title,
3391
- description: footer.description ?? `Recompute ${footer.title} for one session`,
3392
- inputSchema: sessionShape,
3393
- outputSchema: z.looseObject({
3394
- items: z.array(z.looseObject({ label: z.string(), value: z.string() })).describe("The readings to show in the band")
3395
- })
3396
- },
3397
- async (args) => {
3398
- try {
3399
- const items = await footer.run(sessionContext(args, hostFor(args.sessionId)));
3400
- return json({ items });
3401
- } catch (error) {
3402
- return failure(error);
3403
- }
3401
+ const sessionContext = (params) => {
3402
+ const agent = text(params, "agent");
3403
+ if (!EXTENSION_AGENTS.includes(agent)) {
3404
+ throw new InvalidParams(`"agent" must be one of ${EXTENSION_AGENTS.join(", ")}`);
3405
+ }
3406
+ const sessionId = text(params, "sessionId");
3407
+ return {
3408
+ sessionId,
3409
+ worktreePath: text(params, "worktreePath"),
3410
+ agent,
3411
+ host: hostFor(sessionId),
3412
+ now: now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
3413
+ };
3414
+ };
3415
+ const methods = {
3416
+ [PROTOCOL_METHODS.manifest]: () => connectorManifest(connector),
3417
+ // A throw is the connector saying "broken", which must not read as a passing check.
3418
+ [PROTOCOL_METHODS.preflight]: async () => {
3419
+ if (!connector.preflight) return { ok: null };
3420
+ try {
3421
+ return { ...await connector.preflight() };
3422
+ } catch (error) {
3423
+ return { ok: false, message: messageOf(error) };
3404
3424
  }
3405
- );
3406
- }
3407
- for (const handler of connector.contributes?.linkHandlers ?? []) {
3408
- server.registerTool(
3409
- handlerToolName(handler.id),
3410
- {
3411
- title: handler.title,
3412
- description: handler.description ?? `Open ${handler.title} for a clicked link`,
3413
- inputSchema: {
3414
- ...sessionShape,
3415
- url: z.string().describe("The clicked text, which matched this handler")
3416
- },
3417
- outputSchema: z.looseObject({
3418
- openPane: z.string().optional().describe("Id of one of this extension's panes to open")
3419
- })
3420
- },
3421
- async (args) => {
3422
- try {
3423
- const handled = await handler.run({
3424
- ...sessionContext(args, hostFor(args.sessionId)),
3425
- url: args.url
3426
- });
3427
- return json({ ...handled ?? {} });
3428
- } catch (error) {
3429
- return failure(error);
3430
- }
3425
+ },
3426
+ [PROTOCOL_METHODS.options]: async (params) => ({
3427
+ options: await runOptions(connector, text(params, "name"), runtime(params))
3428
+ }),
3429
+ [PROTOCOL_METHODS.poll]: (params) => runPoll(connector, text(params, "trigger"), {
3430
+ ...runtime(params),
3431
+ cursor: optionalText(params, "cursor"),
3432
+ since: optionalText(params, "since"),
3433
+ limit: optionalNumber(params, "limit")
3434
+ }),
3435
+ [PROTOCOL_METHODS.action]: (params) => {
3436
+ const action = text(params, "action");
3437
+ const args = params.args ?? {};
3438
+ if (!isRecord(args)) throw new InvalidParams('"args" must be an object');
3439
+ return runAction(connector, action, args, runtime(params));
3440
+ },
3441
+ [PROTOCOL_METHODS.footer]: async (params) => {
3442
+ const id = text(params, "footer");
3443
+ const footer = connector.contributes?.footers?.find((entry) => entry.id === id);
3444
+ if (!footer) throw new InvalidParams(`${connector.id} contributes no footer "${id}"`);
3445
+ return { items: await footer.run(sessionContext(params)) };
3446
+ },
3447
+ [PROTOCOL_METHODS.handler]: async (params) => {
3448
+ const id = text(params, "handler");
3449
+ const handler = connector.contributes?.linkHandlers?.find((entry) => entry.id === id);
3450
+ if (!handler) throw new InvalidParams(`${connector.id} contributes no link handler "${id}"`);
3451
+ const context = { ...sessionContext(params), url: text(params, "url") };
3452
+ return { ...await handler.run(context) ?? {} };
3453
+ }
3454
+ };
3455
+ const hello = (id, params) => {
3456
+ const offered = params.protocols;
3457
+ if (!Array.isArray(offered)) {
3458
+ return refusal2(id, PROTOCOL_ERROR_CODES.invalidParams, '"protocols" must be a list');
3459
+ }
3460
+ if (!offered.includes(PROTOCOL_VERSION)) {
3461
+ return refusal2(
3462
+ id,
3463
+ PROTOCOL_ERROR_CODES.unsupportedProtocol,
3464
+ `Vorn offers connector protocol ${offered.join(", ") || "none"}; ${connector.id} speaks ${PROTOCOL_VERSION}`
3465
+ );
3466
+ }
3467
+ greeted = true;
3468
+ const result = {
3469
+ protocol: PROTOCOL_VERSION,
3470
+ sdk: { name: "@vornrun/connector-sdk", version: SDK_VERSION },
3471
+ connector: { id: connector.id, version: connector.version, kind: connector.kind }
3472
+ };
3473
+ return { jsonrpc: "2.0", id, result };
3474
+ };
3475
+ return {
3476
+ async handle(message) {
3477
+ if (!isRecord(message) || typeof message.id !== "number") return void 0;
3478
+ const { id, method } = message;
3479
+ if (typeof method !== "string") {
3480
+ return refusal2(id, PROTOCOL_ERROR_CODES.invalidParams, "A request names its method");
3431
3481
  }
3432
- );
3433
- }
3434
- for (const action of connector.actions) {
3435
- const base = action.description ?? `${action.label} in ${connector.name}`;
3436
- const retryHint = action.idempotent === void 0 ? "" : action.idempotent ? " Safe to retry: repeating this call with the same arguments has no additional effect." : " Not idempotent: repeating this call performs the operation again.";
3437
- server.registerTool(
3438
- action.type,
3439
- {
3440
- // Carries the authored label, so a picker can name the action rather than its tool.
3441
- title: action.label,
3442
- description: `${base}${retryHint}`,
3443
- inputSchema: inputShape(action.inputs ?? []),
3444
- outputSchema: outputSchema(action.outputs ?? [])
3445
- },
3446
- async (args, extra) => {
3447
- try {
3448
- return json(
3449
- await runAction(connector, action.type, args, {
3450
- config: config(),
3451
- ...options.now && { now: options.now },
3452
- ...sessionCallOf(extra)
3453
- })
3454
- );
3455
- } catch (error) {
3456
- return failure(error);
3482
+ const params = message.params ?? {};
3483
+ if (!isRecord(params)) {
3484
+ return refusal2(id, PROTOCOL_ERROR_CODES.invalidParams, '"params" must be an object');
3485
+ }
3486
+ if (method === PROTOCOL_METHODS.hello) return hello(id, params);
3487
+ const run = Object.hasOwn(methods, method) ? methods[method] : void 0;
3488
+ if (!run) return refusal2(id, PROTOCOL_ERROR_CODES.methodNotFound, "Method not found");
3489
+ if (!greeted) {
3490
+ return refusal2(
3491
+ id,
3492
+ PROTOCOL_ERROR_CODES.beforeHello,
3493
+ `Call ${PROTOCOL_METHODS.hello} before ${method}`
3494
+ );
3495
+ }
3496
+ try {
3497
+ return { jsonrpc: "2.0", id, result: await run(params) };
3498
+ } catch (error) {
3499
+ if (error instanceof InvalidParams || error instanceof UnknownNameError) {
3500
+ return refusal2(id, PROTOCOL_ERROR_CODES.invalidParams, error.message);
3457
3501
  }
3502
+ return failure(id, protocolError(error));
3458
3503
  }
3459
- );
3504
+ }
3505
+ };
3506
+ }
3507
+ function frame(response) {
3508
+ try {
3509
+ const line = JSON.stringify(response);
3510
+ if (Buffer.byteLength(line) > MAX_FRAME_BYTES) {
3511
+ throw new Error(`The answer is over ${MAX_FRAME_BYTES} bytes`);
3512
+ }
3513
+ return line;
3514
+ } catch (error) {
3515
+ return JSON.stringify(failure(response.id, protocolError(error)));
3460
3516
  }
3461
- return server;
3462
3517
  }
3518
+ var SERVING = /* @__PURE__ */ Symbol.for("@vornrun/connector-sdk/serving");
3463
3519
  async function serveConnector(connector, options = {}) {
3520
+ const shared = globalThis;
3521
+ if (shared[SERVING]) return;
3522
+ shared[SERVING] = true;
3523
+ const reply2 = process.stdout.write.bind(process.stdout);
3524
+ process.stdout.write = process.stderr.write.bind(process.stderr);
3464
3525
  const server = createConnectorServer(connector, options);
3465
- await server.connect(new StdioServerTransport());
3526
+ let inFlight = 0;
3527
+ let ended = false;
3528
+ const finish = () => {
3529
+ if (ended && inFlight === 0) reply2("", () => process.exit(0));
3530
+ };
3531
+ const onLine = (line) => {
3532
+ if (line.trim() === "") return;
3533
+ let message;
3534
+ try {
3535
+ message = JSON.parse(line);
3536
+ } catch {
3537
+ process.stderr.write(`${connector.id}: skipped a line that is not JSON
3538
+ `);
3539
+ return;
3540
+ }
3541
+ inFlight++;
3542
+ void server.handle(message).then((response) => {
3543
+ if (response) reply2(`${frame(response)}
3544
+ `);
3545
+ }).finally(() => {
3546
+ inFlight--;
3547
+ finish();
3548
+ });
3549
+ };
3550
+ process.stdin.on(
3551
+ "data",
3552
+ lineReader(onLine, () => {
3553
+ process.stderr.write(`${connector.id}: a message was over ${MAX_FRAME_BYTES} bytes
3554
+ `);
3555
+ process.exit(1);
3556
+ })
3557
+ );
3558
+ process.stdin.on("end", () => {
3559
+ ended = true;
3560
+ finish();
3561
+ });
3466
3562
  }
3467
3563
 
3468
3564
  export {
3469
3565
  BROWSER_HOST_ENV,
3470
3566
  BROWSER_TOKEN_ENV,
3471
- SESSION_CALL_META,
3472
3567
  SESSION_CALL_HEADER,
3473
3568
  SessionUnavailableError,
3474
3569
  SessionRefusedError,
3475
3570
  createSessionFetch,
3571
+ PROTOCOL_VERSION,
3572
+ SUPPORTED_PROTOCOLS,
3573
+ MAX_FRAME_BYTES,
3574
+ PROTOCOL_METHODS,
3575
+ PROTOCOL_ERROR_CODES,
3576
+ PROTOCOL_ERROR_KINDS,
3577
+ retryAfterMs,
3578
+ backoffMs,
3579
+ resilientFetch,
3580
+ ActionArgumentError,
3581
+ UpstreamStatusError,
3476
3582
  ORIGIN_PATTERN,
3477
3583
  withinOrigins,
3478
3584
  valueAt,
@@ -3483,12 +3589,6 @@ export {
3483
3589
  defineConnector,
3484
3590
  defineExtension,
3485
3591
  resolveConfig,
3486
- pollToolName,
3487
- footerToolName,
3488
- handlerToolName,
3489
- MANIFEST_TOOL,
3490
- PREFLIGHT_TOOL,
3491
- OPTIONS_TOOL,
3492
3592
  connectionSetup,
3493
3593
  connectorManifest,
3494
3594
  MAX_PACK_BYTES,
@@ -3510,9 +3610,6 @@ export {
3510
3610
  MAX_REQUEST_PAGES,
3511
3611
  nextLink,
3512
3612
  executeRequest,
3513
- retryAfterMs,
3514
- backoffMs,
3515
- resilientFetch,
3516
3613
  MAX_POLL_PAGES,
3517
3614
  runPoll,
3518
3615
  drainPoll,