@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.
- package/README.md +42 -27
- package/dist/{check-CzSTEQD8.d.ts → check-DQttwqgl.d.ts} +20 -87
- package/dist/{chunk-C2WRZCMZ.js → chunk-727CYX57.js} +486 -391
- 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;
|
|
@@ -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
|
|
87
|
+
const text2 = await answer.text();
|
|
89
88
|
if (answer.status === 503) {
|
|
90
89
|
throw new SessionUnavailableError(
|
|
91
|
-
refusal(
|
|
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(
|
|
95
|
+
refusal(text2) ?? `The signed-in window refused the call with HTTP ${answer.status}`
|
|
97
96
|
);
|
|
98
97
|
}
|
|
99
|
-
const reply2 = readReply(
|
|
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(
|
|
1079
|
-
const lines =
|
|
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
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
1273
|
+
}
|
|
1274
|
+
async function packLaunchFindings(dir) {
|
|
1275
|
+
const child = spawn(process.execPath, ["index.js"], { cwd: dir, env: launchEnv() });
|
|
1102
1276
|
let stderr = "";
|
|
1103
|
-
|
|
1277
|
+
child.stderr.on("data", (chunk) => {
|
|
1104
1278
|
stderr += chunk.toString();
|
|
1105
1279
|
});
|
|
1106
1280
|
try {
|
|
1107
1281
|
await withTimeout(
|
|
1108
|
-
|
|
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
|
-
|
|
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
|
|
1193
|
-
if (response.status === 403) throw new PermissionDeniedError(method,
|
|
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 (
|
|
1366
|
+
if (text2 === "") return void 0;
|
|
1196
1367
|
let parsed;
|
|
1197
1368
|
try {
|
|
1198
|
-
parsed = JSON.parse(
|
|
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: (
|
|
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
|
|
1428
|
-
return substitute === void 0 ?
|
|
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
|
|
1489
|
-
if (
|
|
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
|
|
1662
|
+
if (!type.includes("json")) return text2;
|
|
1492
1663
|
try {
|
|
1493
|
-
return JSON.parse(
|
|
1664
|
+
return JSON.parse(text2);
|
|
1494
1665
|
} catch {
|
|
1495
|
-
return
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
1736
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
1770
|
-
|
|
1771
|
-
{
|
|
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 (
|
|
1834
|
-
if (
|
|
1835
|
-
if (
|
|
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:
|
|
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
|
|
2255
|
+
return { output };
|
|
2112
2256
|
} catch (error) {
|
|
2113
|
-
return error;
|
|
2257
|
+
return { thrown: error };
|
|
2114
2258
|
}
|
|
2115
2259
|
});
|
|
2116
|
-
if (thrown
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.`;
|
|
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
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
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
|
|
3244
|
-
const
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
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
|
|
3380
|
+
return value;
|
|
3250
3381
|
}
|
|
3251
|
-
function
|
|
3252
|
-
|
|
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
|
|
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);
|
|
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
|
-
|
|
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())
|
|
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
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
{
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
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
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
-
|
|
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,
|