@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.
- package/README.md +44 -27
- package/dist/{check-CzSTEQD8.d.ts → check-DQttwqgl.d.ts} +20 -87
- package/dist/{chunk-C2WRZCMZ.js → chunk-NNCU2VFL.js} +490 -393
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +7 -11
- package/dist/index.d.ts +212 -23
- package/dist/index.js +17 -15
- package/package.json +2 -6
|
@@ -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(
|
|
42
|
+
function readReply(text2) {
|
|
44
43
|
let parsed;
|
|
45
44
|
try {
|
|
46
|
-
parsed = JSON.parse(
|
|
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 = (
|
|
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
|
|
88
|
+
const text2 = await answer.text();
|
|
89
89
|
if (answer.status === 503) {
|
|
90
90
|
throw new SessionUnavailableError(
|
|
91
|
-
refusal(
|
|
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(
|
|
96
|
+
refusal(text2) ?? `The signed-in window refused the call with HTTP ${answer.status}`
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
|
-
const reply2 = readReply(
|
|
100
|
-
|
|
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(
|
|
1079
|
-
const lines =
|
|
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
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
1275
|
+
}
|
|
1276
|
+
async function packLaunchFindings(dir) {
|
|
1277
|
+
const child = spawn(process.execPath, ["index.js"], { cwd: dir, env: launchEnv() });
|
|
1102
1278
|
let stderr = "";
|
|
1103
|
-
|
|
1279
|
+
child.stderr.on("data", (chunk) => {
|
|
1104
1280
|
stderr += chunk.toString();
|
|
1105
1281
|
});
|
|
1106
1282
|
try {
|
|
1107
1283
|
await withTimeout(
|
|
1108
|
-
|
|
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
|
-
|
|
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
|
|
1193
|
-
if (response.status === 403) throw new PermissionDeniedError(method,
|
|
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 (
|
|
1368
|
+
if (text2 === "") return void 0;
|
|
1196
1369
|
let parsed;
|
|
1197
1370
|
try {
|
|
1198
|
-
parsed = JSON.parse(
|
|
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: (
|
|
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
|
|
1428
|
-
return substitute === void 0 ?
|
|
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
|
|
1489
|
-
if (
|
|
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
|
|
1664
|
+
if (!type.includes("json")) return text2;
|
|
1492
1665
|
try {
|
|
1493
|
-
return JSON.parse(
|
|
1666
|
+
return JSON.parse(text2);
|
|
1494
1667
|
} catch {
|
|
1495
|
-
return
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
1736
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
1770
|
-
|
|
1771
|
-
{
|
|
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 (
|
|
1834
|
-
if (
|
|
1835
|
-
if (
|
|
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:
|
|
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
|
|
2257
|
+
return { output };
|
|
2112
2258
|
} catch (error) {
|
|
2113
|
-
return error;
|
|
2259
|
+
return { thrown: error };
|
|
2114
2260
|
}
|
|
2115
2261
|
});
|
|
2116
|
-
if (thrown
|
|
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
|
|
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.
|
|
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
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
function
|
|
3215
|
-
const
|
|
3216
|
-
|
|
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
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
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
|
|
3244
|
-
const
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
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
|
|
3382
|
+
return value;
|
|
3250
3383
|
}
|
|
3251
|
-
function
|
|
3252
|
-
|
|
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
|
|
3258
|
-
|
|
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
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
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
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
{
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
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
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
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
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
{
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
}
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
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
|
-
|
|
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,
|