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

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;
@@ -85,18 +84,18 @@ function createSessionFetch(options = {}) {
85
84
  }),
86
85
  signal: AbortSignal.any([request.signal, AbortSignal.timeout(SESSION_TIMEOUT_MS)])
87
86
  });
88
- const text = await answer.text();
87
+ const text2 = await answer.text();
89
88
  if (answer.status === 503) {
90
89
  throw new SessionUnavailableError(
91
- refusal(text) ?? "Vorn could not reach the signed-in window"
90
+ refusal(text2) ?? "Vorn could not reach the signed-in window"
92
91
  );
93
92
  }
94
93
  if (!answer.ok) {
95
94
  throw new SessionRefusedError(
96
- refusal(text) ?? `The signed-in window refused the call with HTTP ${answer.status}`
95
+ refusal(text2) ?? `The signed-in window refused the call with HTTP ${answer.status}`
97
96
  );
98
97
  }
99
- const reply2 = readReply(text);
98
+ const reply2 = readReply(text2);
100
99
  return new Response(NULL_BODY_STATUSES.has(reply2.status) ? null : reply2.body ?? "", {
101
100
  status: reply2.status,
102
101
  ...reply2.headers && { headers: reply2.headers }
@@ -104,6 +103,148 @@ function createSessionFetch(options = {}) {
104
103
  });
105
104
  }
106
105
 
106
+ // src/protocol.ts
107
+ var PROTOCOL_VERSION = 1;
108
+ var SUPPORTED_PROTOCOLS = [PROTOCOL_VERSION];
109
+ var MAX_FRAME_BYTES = 16 * 1024 * 1024;
110
+ var PROTOCOL_METHODS = {
111
+ hello: "vorn/hello",
112
+ manifest: "connector/manifest",
113
+ preflight: "connector/preflight",
114
+ options: "connector/options",
115
+ poll: "trigger/poll",
116
+ action: "action/run",
117
+ footer: "extension/footer",
118
+ handler: "extension/handler"
119
+ };
120
+ var PROTOCOL_ERROR_CODES = {
121
+ methodNotFound: -32601,
122
+ invalidParams: -32602,
123
+ connectorError: -32e3,
124
+ unsupportedProtocol: -32001,
125
+ beforeHello: -32002
126
+ };
127
+ var PROTOCOL_ERROR_KINDS = [
128
+ "validation",
129
+ "app-offline",
130
+ "signed-out",
131
+ "upstream",
132
+ "internal"
133
+ ];
134
+
135
+ // src/resilience.ts
136
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
137
+ var DEFAULT_ATTEMPTS = 3;
138
+ var DEFAULT_BASE_DELAY_MS = 250;
139
+ var DEFAULT_MAX_DELAY_MS = 3e4;
140
+ var MAX_ATTEMPTS = 10;
141
+ var MAX_TOTAL_WAIT_MS = 12e4;
142
+ var wait = (ms) => new Promise((resolve4) => {
143
+ setTimeout(resolve4, ms);
144
+ });
145
+ function retryAfterMs(header, now) {
146
+ if (!header) return void 0;
147
+ const trimmed = header.trim();
148
+ if (trimmed === "") return void 0;
149
+ const seconds = Number(trimmed);
150
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
151
+ const at = Date.parse(trimmed);
152
+ if (Number.isNaN(at)) return void 0;
153
+ return Math.max(0, at - now);
154
+ }
155
+ function backoffMs(attempt, policy = {}) {
156
+ const base = policy.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
157
+ const max = policy.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
158
+ return Math.min(max, base * 2 ** attempt);
159
+ }
160
+ function isFinal(error) {
161
+ return error?.retryable === false;
162
+ }
163
+ function resilientFetch(options) {
164
+ const attempts = Math.min(MAX_ATTEMPTS, Math.max(1, options.retry?.attempts ?? DEFAULT_ATTEMPTS));
165
+ const sleep = options.sleep ?? wait;
166
+ const ceiling = options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
167
+ const send = async (input, init) => {
168
+ let waited = 0;
169
+ const pause = async (ms) => {
170
+ if (waited + ms > MAX_TOTAL_WAIT_MS) return false;
171
+ waited += ms;
172
+ await sleep(ms);
173
+ return true;
174
+ };
175
+ for (let attempt = 0; attempt < attempts; attempt++) {
176
+ const last = attempt === attempts - 1;
177
+ try {
178
+ const response = await options.fetchImpl(input, init);
179
+ if (!RETRYABLE_STATUS.has(response.status)) return response;
180
+ if (!options.retryable || last) return response;
181
+ const asked = retryAfterMs(response.headers.get("retry-after"), Date.now());
182
+ const delay = asked === void 0 ? backoffMs(attempt, options.retry) : Math.min(asked, ceiling);
183
+ if (!await pause(delay)) return response;
184
+ } catch (error) {
185
+ if (!options.retryable || last || isFinal(error)) throw error;
186
+ if (!await pause(backoffMs(attempt, options.retry))) throw error;
187
+ }
188
+ }
189
+ throw new Error("Request was never attempted");
190
+ };
191
+ return send;
192
+ }
193
+
194
+ // src/errors.ts
195
+ var ActionArgumentError = class extends Error {
196
+ field;
197
+ constructor(field, message) {
198
+ super(message);
199
+ this.name = "ActionArgumentError";
200
+ this.field = field;
201
+ }
202
+ };
203
+ var UpstreamStatusError = class extends Error {
204
+ status;
205
+ viaSession;
206
+ constructor(status, message, viaSession = false) {
207
+ super(message);
208
+ this.name = "UpstreamStatusError";
209
+ this.status = status;
210
+ this.viaSession = viaSession;
211
+ }
212
+ };
213
+ var UnknownNameError = class extends Error {
214
+ constructor(message) {
215
+ super(message);
216
+ this.name = "UnknownNameError";
217
+ }
218
+ };
219
+ var messageOf = (error) => error instanceof Error ? error.message : String(error);
220
+ function* causes(error) {
221
+ let at = error;
222
+ for (let depth = 0; at !== void 0 && depth < 8; depth++) {
223
+ yield at;
224
+ at = at instanceof Error ? at.cause : void 0;
225
+ }
226
+ }
227
+ function protocolError(error) {
228
+ const failed = (data) => ({
229
+ code: PROTOCOL_ERROR_CODES.connectorError,
230
+ message: messageOf(error),
231
+ data
232
+ });
233
+ for (const at of causes(error)) {
234
+ if (at instanceof ActionArgumentError) return failed({ kind: "validation", field: at.field });
235
+ if (at instanceof SessionUnavailableError) {
236
+ return failed({ kind: "app-offline", retryable: false });
237
+ }
238
+ if (at instanceof UpstreamStatusError) {
239
+ if (at.viaSession && (at.status === 401 || at.status === 403)) {
240
+ return failed({ kind: "signed-out", retryable: false });
241
+ }
242
+ return failed({ kind: "upstream", retryable: RETRYABLE_STATUS.has(at.status) });
243
+ }
244
+ }
245
+ return failed({ kind: "internal" });
246
+ }
247
+
107
248
  // src/origins.ts
108
249
  var ORIGIN_PATTERN = /^https:\/\/(\*\.)?[a-z0-9-]+(\.[a-z0-9-]+)+$/i;
109
250
  function withinOrigins(origins, url) {
@@ -697,18 +838,6 @@ function resolveConfig(connector, env = process.env) {
697
838
  }
698
839
 
699
840
  // 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
841
  function connectionSetup(connector, triggerType) {
713
842
  const trigger = connector.triggers.find((entry) => entry.type === triggerType);
714
843
  if (!trigger) {
@@ -717,16 +846,6 @@ function connectionSetup(connector, triggerType) {
717
846
  return {
718
847
  connectorId: connector.id,
719
848
  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
849
  env: connector.config.map((field) => ({
731
850
  name: envNameFor(field.key, field.env),
732
851
  required: field.required === true,
@@ -769,6 +888,7 @@ function manifestContributions(connector) {
769
888
  function connectorManifest(connector) {
770
889
  const contributes = manifestContributions(connector);
771
890
  return {
891
+ protocol: PROTOCOL_VERSION,
772
892
  id: connector.id,
773
893
  name: connector.name,
774
894
  version: connector.version,
@@ -794,11 +914,13 @@ function connectorManifest(connector) {
794
914
  type: action.type,
795
915
  label: action.label,
796
916
  ...action.description !== void 0 && { description: action.description },
917
+ ...action.idempotent !== void 0 && { idempotent: action.idempotent },
797
918
  inputs: (action.inputs ?? []).map((input) => ({
798
919
  key: input.key,
799
920
  label: input.label,
800
921
  type: input.type ?? "string",
801
922
  required: input.required === true,
923
+ ...input.description !== void 0 && { description: input.description },
802
924
  ...input.options !== void 0 && { options: input.options },
803
925
  ...input.loadOptions !== void 0 && { loadOptions: input.loadOptions },
804
926
  ...input.builderHint !== void 0 && { builderHint: input.builderHint }
@@ -810,11 +932,37 @@ function connectorManifest(connector) {
810
932
  }
811
933
 
812
934
  // src/packaging.ts
935
+ import { spawn } from "child_process";
813
936
  import { builtinModules } from "module";
814
937
  import { existsSync, readFileSync } from "fs";
815
938
  import { cp, mkdtemp, readdir, stat, writeFile } from "fs/promises";
816
939
  import { tmpdir } from "os";
817
940
  import { dirname, isAbsolute, join, resolve } from "path";
941
+
942
+ // src/lines.ts
943
+ function lineReader(onLine, overflow) {
944
+ let pending = [];
945
+ let size = 0;
946
+ return (chunk) => {
947
+ let start = 0;
948
+ for (let end = chunk.indexOf(10); end !== -1; end = chunk.indexOf(10, start)) {
949
+ const part = chunk.subarray(start, end);
950
+ if (size + part.length > MAX_FRAME_BYTES) return overflow();
951
+ const whole = pending.length === 0 ? part : Buffer.concat([...pending, part], size + part.length);
952
+ const line = whole.toString("utf8");
953
+ pending = [];
954
+ size = 0;
955
+ start = end + 1;
956
+ onLine(line.endsWith("\r") ? line.slice(0, -1) : line);
957
+ }
958
+ const rest = chunk.subarray(start);
959
+ size += rest.length;
960
+ if (size > MAX_FRAME_BYTES) return overflow();
961
+ if (rest.length > 0) pending.push(rest);
962
+ };
963
+ }
964
+
965
+ // src/packaging.ts
818
966
  var MAX_PACK_BYTES = 8 * 1024 * 1024;
819
967
  var MAX_UNPACKED_BYTES = 32 * 1024 * 1024;
820
968
  var LIFECYCLE_SCRIPTS = [
@@ -1075,8 +1223,8 @@ function launchEnv() {
1075
1223
  }
1076
1224
  return env;
1077
1225
  }
1078
- function errorLine(text) {
1079
- const lines = text.split("\n").map((line) => line.trim()).filter((line) => line !== "");
1226
+ function errorLine(text2) {
1227
+ const lines = text2.split("\n").map((line) => line.trim()).filter((line) => line !== "");
1080
1228
  return [...lines].reverse().find((line) => /Error\b/.test(line)) ?? lines[lines.length - 1];
1081
1229
  }
1082
1230
  function withTimeout(promise, ms, message) {
@@ -1088,24 +1236,50 @@ function withTimeout(promise, ms, message) {
1088
1236
  })
1089
1237
  ]);
1090
1238
  }
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"
1239
+ function helloAnswer(child) {
1240
+ return new Promise((resolve4, reject) => {
1241
+ const onLine = (line) => {
1242
+ let reply2;
1243
+ try {
1244
+ reply2 = JSON.parse(line);
1245
+ } catch {
1246
+ return;
1247
+ }
1248
+ if (reply2?.id !== 1) return;
1249
+ if (typeof reply2.result?.protocol === "number") return resolve4();
1250
+ reject(
1251
+ new Error(
1252
+ `answered ${PROTOCOL_METHODS.hello} with ${String(reply2.error?.message ?? "no protocol")}`
1253
+ )
1254
+ );
1255
+ };
1256
+ child.stdout.on(
1257
+ "data",
1258
+ lineReader(onLine, () => reject(new Error(`wrote a line over ${MAX_FRAME_BYTES} bytes`)))
1259
+ );
1260
+ child.once("error", reject);
1261
+ child.once("exit", (code) => reject(new Error(`exited with code ${code} before answering`)));
1262
+ child.stdin.on("error", () => {
1263
+ });
1264
+ const params = {
1265
+ protocols: [...SUPPORTED_PROTOCOLS],
1266
+ host: { name: "vorn-connector-check", version: "1" }
1267
+ };
1268
+ child.stdin.write(
1269
+ `${JSON.stringify({ jsonrpc: "2.0", id: 1, method: PROTOCOL_METHODS.hello, params })}
1270
+ `
1271
+ );
1100
1272
  });
1101
- const client = new Client({ name: "vorn-connector-check", version: "1" }, { capabilities: {} });
1273
+ }
1274
+ async function packLaunchFindings(dir) {
1275
+ const child = spawn(process.execPath, ["index.js"], { cwd: dir, env: launchEnv() });
1102
1276
  let stderr = "";
1103
- transport.stderr?.on("data", (chunk) => {
1277
+ child.stderr.on("data", (chunk) => {
1104
1278
  stderr += chunk.toString();
1105
1279
  });
1106
1280
  try {
1107
1281
  await withTimeout(
1108
- client.connect(transport),
1282
+ helloAnswer(child),
1109
1283
  LAUNCH_TIMEOUT_MS,
1110
1284
  `did not answer within ${LAUNCH_TIMEOUT_MS / 1e3}s of starting`
1111
1285
  );
@@ -1116,10 +1290,7 @@ async function packLaunchFindings(dir) {
1116
1290
  finding("pack-launch", "bundle", `did not start as a pack: ${errorLine(stderr) ?? said}`)
1117
1291
  ];
1118
1292
  } finally {
1119
- await client.close().catch(() => {
1120
- });
1121
- await transport.close().catch(() => {
1122
- });
1293
+ child.kill("SIGKILL");
1123
1294
  }
1124
1295
  }
1125
1296
  async function esbuildBundle(request) {
@@ -1189,13 +1360,13 @@ function createExtensionHost(options) {
1189
1360
  body: JSON.stringify({ sessionId: options.sessionId, ...params }),
1190
1361
  signal: AbortSignal.timeout(HOST_TIMEOUT_MS)
1191
1362
  });
1192
- const text = await response.text();
1193
- if (response.status === 403) throw new PermissionDeniedError(method, text || "not granted");
1363
+ const text2 = await response.text();
1364
+ if (response.status === 403) throw new PermissionDeniedError(method, text2 || "not granted");
1194
1365
  if (!response.ok) throw new Error(`The host answered ${method} with HTTP ${response.status}`);
1195
- if (text === "") return void 0;
1366
+ if (text2 === "") return void 0;
1196
1367
  let parsed;
1197
1368
  try {
1198
- parsed = JSON.parse(text);
1369
+ parsed = JSON.parse(text2);
1199
1370
  } catch {
1200
1371
  throw new HostReplyError(method, "a body that is not JSON");
1201
1372
  }
@@ -1209,7 +1380,7 @@ function createExtensionHost(options) {
1209
1380
  status: () => ask("status", {}),
1210
1381
  output: (opts) => ask("output", { ...opts?.lines !== void 0 && { lines: opts.lines } }),
1211
1382
  selection: () => ask("selection", {}),
1212
- send: (text) => ask("send", { text }),
1383
+ send: (text2) => ask("send", { text: text2 }),
1213
1384
  rename: (name) => ask("rename", { name }),
1214
1385
  usage: () => ask("usage", {})
1215
1386
  };
@@ -1424,8 +1595,8 @@ function resolveTemplates(value, scope, substitute) {
1424
1595
  return value.replace(PLACEHOLDER, (_match, source, path) => {
1425
1596
  const resolved = lookup(source, path, scope);
1426
1597
  if (resolved === void 0 || resolved === null) return "";
1427
- const text = String(resolved);
1428
- return substitute === void 0 ? text : substitute(text, source);
1598
+ const text2 = String(resolved);
1599
+ return substitute === void 0 ? text2 : substitute(text2, source);
1429
1600
  });
1430
1601
  }
1431
1602
  if (Array.isArray(value)) return value.map((entry) => resolveTemplates(entry, scope, substitute));
@@ -1485,14 +1656,14 @@ function resolveRequest(request, scope) {
1485
1656
  return resolved;
1486
1657
  }
1487
1658
  async function readBody(response) {
1488
- const text = await response.text();
1489
- if (text === "") return void 0;
1659
+ const text2 = await response.text();
1660
+ if (text2 === "") return void 0;
1490
1661
  const type = response.headers.get("content-type") ?? "";
1491
- if (!type.includes("json")) return text;
1662
+ if (!type.includes("json")) return text2;
1492
1663
  try {
1493
- return JSON.parse(text);
1664
+ return JSON.parse(text2);
1494
1665
  } catch {
1495
- return text;
1666
+ return text2;
1496
1667
  }
1497
1668
  }
1498
1669
  function describeFailure(response, body) {
@@ -1507,7 +1678,13 @@ async function sendRequest(resolved, options) {
1507
1678
  ...resolved.body !== void 0 && { body: resolved.body }
1508
1679
  });
1509
1680
  const body = await readBody(response);
1510
- if (!response.ok) throw new Error(describeFailure(response, body));
1681
+ if (!response.ok) {
1682
+ throw new UpstreamStatusError(
1683
+ response.status,
1684
+ describeFailure(response, body),
1685
+ options.viaSession
1686
+ );
1687
+ }
1511
1688
  return { response, body };
1512
1689
  }
1513
1690
  function asOutput(value) {
@@ -1583,65 +1760,6 @@ async function executeRequest(request, postReceive, scope, options) {
1583
1760
  return asOutput(applyPostReceive(body, postReceive));
1584
1761
  }
1585
1762
 
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
1763
  // src/runtime.ts
1646
1764
  function wrap(fetchImpl, options, retryable) {
1647
1765
  return resilientFetch({
@@ -1660,7 +1778,7 @@ var MAX_POLL_PAGES = 1e3;
1660
1778
  async function runPoll(connector, triggerType, options = {}) {
1661
1779
  const trigger = connector.triggers.find((entry) => entry.type === triggerType);
1662
1780
  if (!trigger) {
1663
- throw new Error(`Connector ${connector.id} has no trigger "${triggerType}"`);
1781
+ throw new UnknownNameError(`${connector.id} has no trigger "${triggerType}"`);
1664
1782
  }
1665
1783
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
1666
1784
  const polledAt = now();
@@ -1709,7 +1827,7 @@ var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
1709
1827
  async function runOptions(connector, name, options = {}) {
1710
1828
  const loader = connector.options?.[name];
1711
1829
  if (!loader) {
1712
- throw new Error(`Connector ${connector.id} serves no options set "${name}"`);
1830
+ throw new UnknownNameError(`${connector.id} serves no options set "${name}"`);
1713
1831
  }
1714
1832
  const session = sessionFor(connector, options, true);
1715
1833
  const loaded = await loader({
@@ -1729,46 +1847,52 @@ var MAX_QUOTED_VALUE = 80;
1729
1847
  function quote(value) {
1730
1848
  return value.length > MAX_QUOTED_VALUE ? `${value.slice(0, MAX_QUOTED_VALUE)}\u2026` : value;
1731
1849
  }
1850
+ var shown = (value) => typeof value === "string" ? `"${quote(value)}"` : quote(JSON.stringify(value) ?? String(value));
1732
1851
  function coerceArg(value, type) {
1733
- if (typeof value !== "string") return value;
1734
1852
  if (type === "number") {
1735
- const parsed = Number(value);
1736
- if (Number.isNaN(parsed)) throw new Error(`Expected a number, got "${quote(value)}"`);
1853
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1854
+ const parsed = typeof value === "string" ? Number(value) : Number.NaN;
1855
+ if (Number.isNaN(parsed)) throw new Error(`Expected a number, got ${shown(value)}`);
1737
1856
  return parsed;
1738
1857
  }
1739
1858
  if (type === "boolean") {
1859
+ if (typeof value === "boolean") return value;
1740
1860
  if (value === "true") return true;
1741
1861
  if (value === "false") return false;
1742
- throw new Error(`Expected a boolean, got "${quote(value)}"`);
1862
+ throw new Error(`Expected a boolean, got ${shown(value)}`);
1743
1863
  }
1744
1864
  if (type === "json") {
1865
+ if (typeof value !== "string") return value;
1745
1866
  try {
1746
1867
  return JSON.parse(value);
1747
1868
  } catch {
1748
- throw new Error(`Expected JSON, got "${quote(value)}"`);
1869
+ throw new Error(`Expected JSON, got ${shown(value)}`);
1749
1870
  }
1750
1871
  }
1751
- return value;
1872
+ if (typeof value === "string") return value;
1873
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
1752
1874
  }
1753
1875
  async function runAction(connector, actionType, args, options = {}) {
1754
1876
  const action = connector.actions.find((entry) => entry.type === actionType);
1755
1877
  if (!action) {
1756
- throw new Error(`Connector ${connector.id} has no action "${actionType}"`);
1878
+ throw new UnknownNameError(`${connector.id} has no action "${actionType}"`);
1757
1879
  }
1758
1880
  const coerced = { ...args };
1759
1881
  for (const input of action.inputs ?? []) {
1760
1882
  const value = coerced[input.key];
1761
- if (value === void 0 || value === "") {
1762
- if (input.required) throw new Error(`Action ${actionType} requires "${input.key}"`);
1883
+ if (value === void 0 || value === null || value === "") {
1884
+ if (input.required) {
1885
+ throw new ActionArgumentError(input.key, `Action ${actionType} requires "${input.key}"`);
1886
+ }
1763
1887
  delete coerced[input.key];
1764
1888
  continue;
1765
1889
  }
1766
1890
  try {
1767
1891
  coerced[input.key] = coerceArg(value, input.type);
1768
1892
  } catch (error) {
1769
- throw new Error(
1770
- `Action ${actionType} argument "${input.key}": ${error instanceof Error ? error.message : String(error)}`,
1771
- { cause: error }
1893
+ throw new ActionArgumentError(
1894
+ input.key,
1895
+ `Action ${actionType} argument "${input.key}": ${error instanceof Error ? error.message : String(error)}`
1772
1896
  );
1773
1897
  }
1774
1898
  }
@@ -1783,7 +1907,7 @@ async function runAction(connector, actionType, args, options = {}) {
1783
1907
  action.request,
1784
1908
  action.postReceive,
1785
1909
  { args: coerced, config },
1786
- { fetchImpl: session?.fetch ?? fetchImpl }
1910
+ { fetchImpl: session?.fetch ?? fetchImpl, viaSession: session !== void 0 }
1787
1911
  );
1788
1912
  } catch (error) {
1789
1913
  throw new Error(
@@ -1830,9 +1954,9 @@ var MockRouteMissError = class extends Error {
1830
1954
  }
1831
1955
  };
1832
1956
  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;
1957
+ for (const at of causes(error)) {
1958
+ if (at instanceof MockRouteMissError) return true;
1959
+ if (at instanceof Error && at.message.includes("No mock route for ")) return true;
1836
1960
  }
1837
1961
  return false;
1838
1962
  }
@@ -2091,6 +2215,26 @@ function mockConfig(connector) {
2091
2215
  }
2092
2216
  return config;
2093
2217
  }
2218
+ function outputTypeOf(value) {
2219
+ if (Array.isArray(value)) return "array";
2220
+ return value === null ? "null" : typeof value;
2221
+ }
2222
+ function outputTypeFindings(action, output) {
2223
+ return (action.outputs ?? []).flatMap((field) => {
2224
+ const value = output[field.key];
2225
+ if (field.type === void 0 || value === void 0 || value === null) return [];
2226
+ const actual = outputTypeOf(value);
2227
+ if (actual === field.type) return [];
2228
+ return [
2229
+ finding2(
2230
+ "warn",
2231
+ "mock-output-type",
2232
+ `action ${action.type}`,
2233
+ `returned "${field.key}" as ${actual}, but declares it ${field.type}`
2234
+ )
2235
+ ];
2236
+ });
2237
+ }
2094
2238
  async function mockFindings(connector, options) {
2095
2239
  if (!options.mock) return [];
2096
2240
  const config = options.config ?? mockConfig(connector);
@@ -2101,19 +2245,20 @@ async function mockFindings(connector, options) {
2101
2245
  const args = Object.fromEntries(
2102
2246
  (action.inputs ?? []).map((input) => [input.key, sampleArg(input)])
2103
2247
  );
2104
- const { result: thrown, calls } = await withMockHttp(routes, async () => {
2248
+ const { result: ran, calls } = await withMockHttp(routes, async () => {
2105
2249
  try {
2106
- await runAction(connector, action.type, args, {
2250
+ const output = await runAction(connector, action.type, args, {
2107
2251
  config,
2108
2252
  ...options.now && { now: options.now },
2109
2253
  sessionFetchImpl: globalThis.fetch
2110
2254
  });
2111
- return void 0;
2255
+ return { output };
2112
2256
  } catch (error) {
2113
- return error;
2257
+ return { thrown: error };
2114
2258
  }
2115
2259
  });
2116
- if (thrown !== void 0) {
2260
+ if ("thrown" in ran) {
2261
+ const { thrown } = ran;
2117
2262
  const reason = thrown instanceof Error ? thrown.message : String(thrown);
2118
2263
  const escaped = escapedMockHttp(thrown);
2119
2264
  found.push(
@@ -2136,6 +2281,7 @@ async function mockFindings(connector, options) {
2136
2281
  )
2137
2282
  );
2138
2283
  }
2284
+ found.push(...outputTypeFindings(action, ran.output));
2139
2285
  }
2140
2286
  return found;
2141
2287
  }
@@ -2296,7 +2442,7 @@ function liveExamines(connector) {
2296
2442
  return connector.preflight !== void 0 || connector.actions.some(liveRunnable);
2297
2443
  }
2298
2444
  function needsWindow(error) {
2299
- return error instanceof SessionUnavailableError || error instanceof Error && error.cause instanceof SessionUnavailableError;
2445
+ return [...causes(error)].some((at) => at instanceof SessionUnavailableError);
2300
2446
  }
2301
2447
  async function liveFindings(connector, options) {
2302
2448
  if (!options.live) return [];
@@ -2517,6 +2663,7 @@ var CHECK_OWNERS = {
2517
2663
  "mock-action-failed": "mock",
2518
2664
  "mock-network-escape": "mock",
2519
2665
  "mock-not-observed": "mock",
2666
+ "mock-output-type": "mock",
2520
2667
  "preflight-failed": "live",
2521
2668
  "live-action-failed": "live",
2522
2669
  "pack-launch": "launch",
@@ -2643,7 +2790,7 @@ async function packConnector(connector, options) {
2643
2790
 
2644
2791
  // src/scaffold.ts
2645
2792
  var ID_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
2646
- var SDK_DEPENDENCY_RANGE = "^0.7.0-beta.14";
2793
+ var SDK_DEPENDENCY_RANGE = "^0.7.1-beta.3";
2647
2794
  var SCAFFOLD_VERSION = "0.1.0";
2648
2795
  var VITEST_RANGE = "^4.1.10";
2649
2796
  function jsonFile(value) {
@@ -3208,271 +3355,228 @@ function scaffoldFiles(options) {
3208
3355
  }
3209
3356
 
3210
3357
  // 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.`;
3358
+ var SDK_VERSION = true ? "0.7.1-beta.3" : "0.0.0-source";
3359
+ var InvalidParams = class extends Error {
3360
+ };
3361
+ function text(params, key) {
3362
+ const value = params[key];
3363
+ if (typeof value !== "string" || value === "") {
3364
+ throw new InvalidParams(`"${key}" must be a non-empty string`);
3237
3365
  }
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;
3366
+ return value;
3367
+ }
3368
+ function optionalText(params, key) {
3369
+ const value = params[key];
3370
+ if (value === void 0 || value === null) return void 0;
3371
+ if (typeof value !== "string") throw new InvalidParams(`"${key}" must be a string`);
3372
+ return value;
3242
3373
  }
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();
3374
+ function optionalNumber(params, key) {
3375
+ const value = params[key];
3376
+ if (value === void 0 || value === null) return void 0;
3377
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3378
+ throw new InvalidParams(`"${key}" must be a number`);
3248
3379
  }
3249
- return shape;
3380
+ return value;
3250
3381
  }
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();
3382
+ function failure(id, error) {
3383
+ return { jsonrpc: "2.0", id, error };
3256
3384
  }
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);
3385
+ function refusal2(id, code, message) {
3386
+ return failure(id, { code, message });
3263
3387
  }
3264
3388
  function createConnectorServer(connector, options = {}) {
3265
- const server = new McpServer(
3266
- { name: connector.id, version: connector.version },
3267
- { capabilities: { tools: {} } }
3268
- );
3269
3389
  let cached = options.config;
3270
3390
  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())
3391
+ const now = options.now;
3392
+ let greeted = false;
3393
+ const runtime = (params) => ({
3394
+ config: config(),
3395
+ now,
3396
+ sessionCall: optionalText(params, "sessionCall")
3384
3397
  });
3385
3398
  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
- }
3399
+ const sessionContext = (params) => {
3400
+ const agent = text(params, "agent");
3401
+ if (!EXTENSION_AGENTS.includes(agent)) {
3402
+ throw new InvalidParams(`"agent" must be one of ${EXTENSION_AGENTS.join(", ")}`);
3403
+ }
3404
+ const sessionId = text(params, "sessionId");
3405
+ return {
3406
+ sessionId,
3407
+ worktreePath: text(params, "worktreePath"),
3408
+ agent,
3409
+ host: hostFor(sessionId),
3410
+ now: now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
3411
+ };
3412
+ };
3413
+ const methods = {
3414
+ [PROTOCOL_METHODS.manifest]: () => connectorManifest(connector),
3415
+ // A throw is the connector saying "broken", which must not read as a passing check.
3416
+ [PROTOCOL_METHODS.preflight]: async () => {
3417
+ if (!connector.preflight) return { ok: null };
3418
+ try {
3419
+ return { ...await connector.preflight() };
3420
+ } catch (error) {
3421
+ return { ok: false, message: messageOf(error) };
3404
3422
  }
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
- }
3423
+ },
3424
+ [PROTOCOL_METHODS.options]: async (params) => ({
3425
+ options: await runOptions(connector, text(params, "name"), runtime(params))
3426
+ }),
3427
+ [PROTOCOL_METHODS.poll]: (params) => runPoll(connector, text(params, "trigger"), {
3428
+ ...runtime(params),
3429
+ cursor: optionalText(params, "cursor"),
3430
+ since: optionalText(params, "since"),
3431
+ limit: optionalNumber(params, "limit")
3432
+ }),
3433
+ [PROTOCOL_METHODS.action]: (params) => {
3434
+ const action = text(params, "action");
3435
+ const args = params.args ?? {};
3436
+ if (!isRecord(args)) throw new InvalidParams('"args" must be an object');
3437
+ return runAction(connector, action, args, runtime(params));
3438
+ },
3439
+ [PROTOCOL_METHODS.footer]: async (params) => {
3440
+ const id = text(params, "footer");
3441
+ const footer = connector.contributes?.footers?.find((entry) => entry.id === id);
3442
+ if (!footer) throw new InvalidParams(`${connector.id} contributes no footer "${id}"`);
3443
+ return { items: await footer.run(sessionContext(params)) };
3444
+ },
3445
+ [PROTOCOL_METHODS.handler]: async (params) => {
3446
+ const id = text(params, "handler");
3447
+ const handler = connector.contributes?.linkHandlers?.find((entry) => entry.id === id);
3448
+ if (!handler) throw new InvalidParams(`${connector.id} contributes no link handler "${id}"`);
3449
+ const context = { ...sessionContext(params), url: text(params, "url") };
3450
+ return { ...await handler.run(context) ?? {} };
3451
+ }
3452
+ };
3453
+ const hello = (id, params) => {
3454
+ const offered = params.protocols;
3455
+ if (!Array.isArray(offered)) {
3456
+ return refusal2(id, PROTOCOL_ERROR_CODES.invalidParams, '"protocols" must be a list');
3457
+ }
3458
+ if (!offered.includes(PROTOCOL_VERSION)) {
3459
+ return refusal2(
3460
+ id,
3461
+ PROTOCOL_ERROR_CODES.unsupportedProtocol,
3462
+ `Vorn offers connector protocol ${offered.join(", ") || "none"}; ${connector.id} speaks ${PROTOCOL_VERSION}`
3463
+ );
3464
+ }
3465
+ greeted = true;
3466
+ const result = {
3467
+ protocol: PROTOCOL_VERSION,
3468
+ sdk: { name: "@vornrun/connector-sdk", version: SDK_VERSION },
3469
+ connector: { id: connector.id, version: connector.version, kind: connector.kind }
3470
+ };
3471
+ return { jsonrpc: "2.0", id, result };
3472
+ };
3473
+ return {
3474
+ async handle(message) {
3475
+ if (!isRecord(message) || typeof message.id !== "number") return void 0;
3476
+ const { id, method } = message;
3477
+ if (typeof method !== "string") {
3478
+ return refusal2(id, PROTOCOL_ERROR_CODES.invalidParams, "A request names its method");
3431
3479
  }
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);
3480
+ const params = message.params ?? {};
3481
+ if (!isRecord(params)) {
3482
+ return refusal2(id, PROTOCOL_ERROR_CODES.invalidParams, '"params" must be an object');
3483
+ }
3484
+ if (method === PROTOCOL_METHODS.hello) return hello(id, params);
3485
+ const run = Object.hasOwn(methods, method) ? methods[method] : void 0;
3486
+ if (!run) return refusal2(id, PROTOCOL_ERROR_CODES.methodNotFound, "Method not found");
3487
+ if (!greeted) {
3488
+ return refusal2(
3489
+ id,
3490
+ PROTOCOL_ERROR_CODES.beforeHello,
3491
+ `Call ${PROTOCOL_METHODS.hello} before ${method}`
3492
+ );
3493
+ }
3494
+ try {
3495
+ return { jsonrpc: "2.0", id, result: await run(params) };
3496
+ } catch (error) {
3497
+ if (error instanceof InvalidParams || error instanceof UnknownNameError) {
3498
+ return refusal2(id, PROTOCOL_ERROR_CODES.invalidParams, error.message);
3457
3499
  }
3500
+ return failure(id, protocolError(error));
3458
3501
  }
3459
- );
3502
+ }
3503
+ };
3504
+ }
3505
+ function frame(response) {
3506
+ try {
3507
+ const line = JSON.stringify(response);
3508
+ if (Buffer.byteLength(line) > MAX_FRAME_BYTES) {
3509
+ throw new Error(`The answer is over ${MAX_FRAME_BYTES} bytes`);
3510
+ }
3511
+ return line;
3512
+ } catch (error) {
3513
+ return JSON.stringify(failure(response.id, protocolError(error)));
3460
3514
  }
3461
- return server;
3462
3515
  }
3516
+ var SERVING = /* @__PURE__ */ Symbol.for("@vornrun/connector-sdk/serving");
3463
3517
  async function serveConnector(connector, options = {}) {
3518
+ const shared = globalThis;
3519
+ if (shared[SERVING]) return;
3520
+ shared[SERVING] = true;
3521
+ const reply2 = process.stdout.write.bind(process.stdout);
3522
+ process.stdout.write = process.stderr.write.bind(process.stderr);
3464
3523
  const server = createConnectorServer(connector, options);
3465
- await server.connect(new StdioServerTransport());
3524
+ let inFlight = 0;
3525
+ let ended = false;
3526
+ const finish = () => {
3527
+ if (ended && inFlight === 0) reply2("", () => process.exit(0));
3528
+ };
3529
+ const onLine = (line) => {
3530
+ if (line.trim() === "") return;
3531
+ let message;
3532
+ try {
3533
+ message = JSON.parse(line);
3534
+ } catch {
3535
+ process.stderr.write(`${connector.id}: skipped a line that is not JSON
3536
+ `);
3537
+ return;
3538
+ }
3539
+ inFlight++;
3540
+ void server.handle(message).then((response) => {
3541
+ if (response) reply2(`${frame(response)}
3542
+ `);
3543
+ }).finally(() => {
3544
+ inFlight--;
3545
+ finish();
3546
+ });
3547
+ };
3548
+ process.stdin.on(
3549
+ "data",
3550
+ lineReader(onLine, () => {
3551
+ process.stderr.write(`${connector.id}: a message was over ${MAX_FRAME_BYTES} bytes
3552
+ `);
3553
+ process.exit(1);
3554
+ })
3555
+ );
3556
+ process.stdin.on("end", () => {
3557
+ ended = true;
3558
+ finish();
3559
+ });
3466
3560
  }
3467
3561
 
3468
3562
  export {
3469
3563
  BROWSER_HOST_ENV,
3470
3564
  BROWSER_TOKEN_ENV,
3471
- SESSION_CALL_META,
3472
3565
  SESSION_CALL_HEADER,
3473
3566
  SessionUnavailableError,
3474
3567
  SessionRefusedError,
3475
3568
  createSessionFetch,
3569
+ PROTOCOL_VERSION,
3570
+ SUPPORTED_PROTOCOLS,
3571
+ MAX_FRAME_BYTES,
3572
+ PROTOCOL_METHODS,
3573
+ PROTOCOL_ERROR_CODES,
3574
+ PROTOCOL_ERROR_KINDS,
3575
+ retryAfterMs,
3576
+ backoffMs,
3577
+ resilientFetch,
3578
+ ActionArgumentError,
3579
+ UpstreamStatusError,
3476
3580
  ORIGIN_PATTERN,
3477
3581
  withinOrigins,
3478
3582
  valueAt,
@@ -3483,12 +3587,6 @@ export {
3483
3587
  defineConnector,
3484
3588
  defineExtension,
3485
3589
  resolveConfig,
3486
- pollToolName,
3487
- footerToolName,
3488
- handlerToolName,
3489
- MANIFEST_TOOL,
3490
- PREFLIGHT_TOOL,
3491
- OPTIONS_TOOL,
3492
3590
  connectionSetup,
3493
3591
  connectorManifest,
3494
3592
  MAX_PACK_BYTES,
@@ -3510,9 +3608,6 @@ export {
3510
3608
  MAX_REQUEST_PAGES,
3511
3609
  nextLink,
3512
3610
  executeRequest,
3513
- retryAfterMs,
3514
- backoffMs,
3515
- resilientFetch,
3516
3611
  MAX_POLL_PAGES,
3517
3612
  runPoll,
3518
3613
  drainPoll,