@vostride/agent-qa-plugin-kernel 0.3.1 → 0.3.2

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 CHANGED
@@ -40,10 +40,10 @@ A test runs there with `use.browser.remote: kernel` in the config, a suite or th
40
40
 
41
41
  Options belong to the `plugins` entry and describe the account.
42
42
 
43
- | Option | Default | Meaning |
44
- | -------- | -------------------------- | ------------------------------------------------------------------------------ |
45
- | `apiKey` | required | The Kernel API key, a credential. Write it as a `{{secret:<name>}}` reference. |
46
- | `apiUrl` | `https://api.onkernel.com` | Kernel's API. It must use `https://` unless it points at this machine. |
43
+ | Option | Default | Meaning |
44
+ | -------- | -------------------------- | ------------------------------------------------------------------------------------------------ |
45
+ | `apiKey` | required | The Kernel API key, a credential. Write it as a `{{secret:<name>}}` reference. |
46
+ | `apiUrl` | `https://api.onkernel.com` | Kernel's API. It must use `https://` with no user or password, unless it points at this machine. |
47
47
 
48
48
  The key is sent only as the `Authorization: Bearer` header. It never appears in a message, a log or
49
49
  an answer.
package/dist/main.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as plugin_default } from "./plugin-C2raYjWg.js";
1
+ import { t as plugin_default } from "./plugin-Bh7Aks1i.js";
2
2
  import { runPlugin } from "@vostride/agent-qa-plugin-kit";
3
3
  // src/main.ts
4
4
  runPlugin(plugin_default);
@@ -1,73 +1,22 @@
1
- import { PluginError, credential, definePlugin, z } from "@vostride/agent-qa-plugin-kit";
1
+ import { PluginError, checkEndpoints, createVendorApi, credential, definePlugin, z } from "@vostride/agent-qa-plugin-kit";
2
2
  import { setTimeout } from "node:timers/promises";
3
3
  // src/kernel-api.ts
4
+ const KERNEL_STATUS_KINDS = {
5
+ 403: "auth",
6
+ 529: "capacity"
7
+ };
4
8
  const KernelBrowserSchema = z.object({
5
9
  session_id: z.string().min(1),
6
10
  cdp_ws_url: z.string().min(1),
7
11
  browser_live_view_url: z.string().min(1).optional()
8
12
  });
9
- function kernelFailureKind(status) {
10
- if (status === void 0) return "unreachable";
11
- if (status === 401 || status === 403) return "auth";
12
- if (status === 429 || status === 529) return "capacity";
13
- return "refused";
14
- }
15
- const MAX_REASON_LENGTH = 200;
16
- function oneLine(text) {
17
- const line = text.replace(/\s+/g, " ").trim();
18
- return line.length > MAX_REASON_LENGTH ? `${line.slice(0, MAX_REASON_LENGTH)}…` : line;
19
- }
20
- async function vendorReason(response) {
21
- const text = await response.text().catch(() => "");
22
- try {
23
- const body = JSON.parse(text);
24
- if (typeof body === "object" && body !== null && "message" in body) {
25
- const { message } = body;
26
- if (typeof message === "string" && message.trim() !== "") return `: ${oneLine(message)}`;
27
- }
28
- } catch {}
29
- return "";
30
- }
31
- async function statusFailure(response, request) {
32
- const retryAfter = response.headers.get("retry-after");
33
- const wait = response.status === 429 && retryAfter !== null ? `, retry after ${retryAfter} s` : "";
34
- const reason = await vendorReason(response);
35
- return new PluginError(`Kernel answered ${response.status} to ${request}${reason}${wait}`, { kind: kernelFailureKind(response.status) });
36
- }
37
- async function jsonOf(response) {
38
- const text = await response.text();
39
- try {
40
- return JSON.parse(text);
41
- } catch {
42
- return;
43
- }
44
- }
45
- function causeText(error) {
46
- if (!(error instanceof Error)) return String(error);
47
- const cause = error.cause;
48
- if (cause instanceof Error) return oneLine("code" in cause && typeof cause.code === "string" ? cause.code : cause.message);
49
- return oneLine(error.message);
50
- }
51
13
  function createKernelApi(input) {
52
- const base = input.apiUrl.endsWith("/") ? input.apiUrl : `${input.apiUrl}/`;
53
- const authorization = `Bearer ${input.apiKey}`;
54
- const send = async (method, path, init) => {
55
- const url = new URL(path, base);
56
- try {
57
- return await fetch(url, {
58
- method,
59
- headers: {
60
- ...init.headers,
61
- authorization
62
- },
63
- ...init.body === void 0 ? {} : { body: init.body },
64
- ...init.signal === void 0 ? {} : { signal: init.signal }
65
- });
66
- } catch (error) {
67
- if (init.signal?.aborted === true) throw error;
68
- throw new PluginError(`Kernel could not be reached at ${url.origin}: ${causeText(error)}`, { kind: "unreachable" });
69
- }
70
- };
14
+ const api = createVendorApi({
15
+ vendor: "Kernel",
16
+ baseUrl: input.apiUrl,
17
+ headers: { authorization: `Bearer ${input.apiKey}` },
18
+ kinds: KERNEL_STATUS_KINDS
19
+ });
71
20
  const jsonHeaders = {
72
21
  "content-type": "application/json",
73
22
  accept: "application/json"
@@ -75,24 +24,24 @@ function createKernelApi(input) {
75
24
  const poolPath = (pool, action = "") => `browser_pools/${encodeURIComponent(pool)}${action === "" ? "" : `/${action}`}`;
76
25
  return {
77
26
  async createBrowser(body, signal) {
78
- const response = await send("POST", "browsers", {
27
+ const response = await api.send("POST", "browsers", {
79
28
  headers: jsonHeaders,
80
29
  body: JSON.stringify(body),
81
30
  signal
82
31
  });
83
- if (!response.ok) throw await statusFailure(response, "POST /browsers");
84
- return jsonOf(response);
32
+ if (!response.ok) throw await api.failure(response, "POST /browsers");
33
+ return api.json(response);
85
34
  },
86
35
  async deleteBrowser(id, signal) {
87
- const response = await send("DELETE", `browsers/${encodeURIComponent(id)}`, { signal });
36
+ const response = await api.send("DELETE", `browsers/${encodeURIComponent(id)}`, { signal });
88
37
  if (response.ok || response.status === 404) {
89
38
  await response.body?.cancel();
90
39
  return;
91
40
  }
92
- throw await statusFailure(response, "DELETE /browsers/{id}");
41
+ throw await api.failure(response, "DELETE /browsers/{id}");
93
42
  },
94
43
  async acquireFromPool(pool, body, signal) {
95
- const response = await send("POST", poolPath(pool, "acquire"), {
44
+ const response = await api.send("POST", poolPath(pool, "acquire"), {
96
45
  headers: jsonHeaders,
97
46
  body: JSON.stringify(body),
98
47
  signal
@@ -101,14 +50,14 @@ function createKernelApi(input) {
101
50
  await response.body?.cancel();
102
51
  return { acquired: false };
103
52
  }
104
- if (!response.ok) throw await statusFailure(response, "POST /browser_pools/{pool}/acquire");
53
+ if (!response.ok) throw await api.failure(response, "POST /browser_pools/{pool}/acquire");
105
54
  return {
106
55
  acquired: true,
107
- browser: await jsonOf(response)
56
+ browser: await api.json(response)
108
57
  };
109
58
  },
110
59
  async releaseToPool(pool, sessionId, reuse, signal) {
111
- const response = await send("POST", poolPath(pool, "release"), {
60
+ const response = await api.send("POST", poolPath(pool, "release"), {
112
61
  headers: jsonHeaders,
113
62
  body: JSON.stringify({
114
63
  session_id: sessionId,
@@ -120,26 +69,26 @@ function createKernelApi(input) {
120
69
  await response.body?.cancel();
121
70
  return;
122
71
  }
123
- throw await statusFailure(response, "POST /browser_pools/{pool}/release");
72
+ throw await api.failure(response, "POST /browser_pools/{pool}/release");
124
73
  },
125
74
  async listBrowsers(limit, signal) {
126
- const response = await send("GET", `browsers?limit=${String(limit)}`, {
75
+ const response = await api.send("GET", `browsers?limit=${String(limit)}`, {
127
76
  headers: { accept: "application/json" },
128
77
  signal
129
78
  });
130
- if (!response.ok) throw await statusFailure(response, "GET /browsers");
79
+ if (!response.ok) throw await api.failure(response, "GET /browsers");
131
80
  await response.body?.cancel();
132
81
  },
133
82
  async orgLimits(signal) {
134
- const response = await send("GET", "org/limits", {
83
+ const response = await api.send("GET", "org/limits", {
135
84
  headers: { accept: "application/json" },
136
85
  signal
137
86
  });
138
- if (!response.ok) throw await statusFailure(response, "GET /org/limits");
139
- return jsonOf(response);
87
+ if (!response.ok) throw await api.failure(response, "GET /org/limits");
88
+ return api.json(response);
140
89
  },
141
90
  async getPool(pool, signal) {
142
- const response = await send("GET", poolPath(pool), {
91
+ const response = await api.send("GET", poolPath(pool), {
143
92
  headers: { accept: "application/json" },
144
93
  signal
145
94
  });
@@ -147,7 +96,7 @@ function createKernelApi(input) {
147
96
  await response.body?.cancel();
148
97
  return false;
149
98
  }
150
- if (!response.ok) throw await statusFailure(response, "GET /browser_pools/{pool}");
99
+ if (!response.ok) throw await api.failure(response, "GET /browser_pools/{pool}");
151
100
  await response.body?.cancel();
152
101
  return true;
153
102
  }
@@ -156,7 +105,6 @@ function createKernelApi(input) {
156
105
  // src/plugin.ts
157
106
  const KERNEL_API_URL = "https://api.onkernel.com";
158
107
  const POOL_POLL_FLOOR_MS = 1e3;
159
- const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "[::1]"]);
160
108
  const options = z.strictObject({
161
109
  apiKey: credential("The Kernel API key"),
162
110
  apiUrl: z.url().default(KERNEL_API_URL).describe("Kernel's API, https:// unless it points at this machine")
@@ -176,13 +124,6 @@ const settings = z.strictObject({
176
124
  profile: z.union([z.string().min(1), z.strictObject({ id: z.string().min(1) })]).optional().describe("A Kernel profile loaded read-only, by name or as { id }; needs context: reuse"),
177
125
  extra: z.record(z.string(), z.unknown()).optional().describe("Fields merged last into the create or pool acquire request, overriding the rest")
178
126
  });
179
- function apiUrlAllowed(value) {
180
- const url = new URL(value);
181
- if (url.username !== "" || url.password !== "") return false;
182
- if (url.protocol === "https:") return true;
183
- if (url.protocol !== "http:") return false;
184
- return LOOPBACK_HOSTS.has(url.hostname) || /^127(?:\.\d{1,3}){3}$/.test(url.hostname);
185
- }
186
127
  const CREATE_ONLY_SETTINGS = [
187
128
  "stealth",
188
129
  "region",
@@ -301,10 +242,10 @@ var plugin_default = definePlugin({
301
242
  serves: ["cdp"],
302
243
  skill: "./skill",
303
244
  initialize({ options }) {
304
- if (!apiUrlAllowed(options.apiUrl)) throw new PluginError("apiUrl must use https:// unless it points at this machine.", {
305
- kind: "refused",
306
- path: "apiUrl"
307
- });
245
+ checkEndpoints(options, [{
246
+ path: "apiUrl",
247
+ scheme: "https"
248
+ }]);
308
249
  },
309
250
  async check({ options, remotes }, ctx) {
310
251
  const api = createKernelApi(options);
@@ -369,4 +310,4 @@ var plugin_default = definePlugin({
369
310
  });
370
311
  export { plugin_default as t };
371
312
 
372
- //# sourceMappingURL=plugin-C2raYjWg.js.map
313
+ //# sourceMappingURL=plugin-Bh7Aks1i.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-Bh7Aks1i.js","names":["delay"],"sources":["../src/kernel-api.ts","../src/plugin.ts"],"sourcesContent":["import {\n createVendorApi,\n vendorFailureKind,\n z,\n type VendorStatusKinds,\n} from '@vostride/agent-qa-plugin-kit'\n\ntype KernelFailureKind = ReturnType<typeof vendorFailureKind>\n\nconst KERNEL_STATUS_KINDS: VendorStatusKinds = { 403: 'auth', 529: 'capacity' }\n\nexport const KernelBrowserSchema = z.object({\n session_id: z.string().min(1),\n cdp_ws_url: z.string().min(1),\n browser_live_view_url: z.string().min(1).optional(),\n})\n\ninterface KernelApiInput {\n apiUrl: string\n apiKey: string\n}\n\nexport type PoolAcquireAnswer = { acquired: true; browser: unknown } | { acquired: false }\n\nexport interface KernelApi {\n createBrowser(body: Record<string, unknown>, signal?: AbortSignal): Promise<unknown>\n deleteBrowser(id: string, signal?: AbortSignal): Promise<void>\n acquireFromPool(\n pool: string,\n body: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<PoolAcquireAnswer>\n releaseToPool(pool: string, sessionId: string, reuse: boolean, signal?: AbortSignal): Promise<void>\n listBrowsers(limit: number, signal?: AbortSignal): Promise<void>\n orgLimits(signal?: AbortSignal): Promise<unknown>\n getPool(pool: string, signal?: AbortSignal): Promise<boolean>\n}\n\nexport function kernelFailureKind(status: number | undefined): KernelFailureKind {\n return vendorFailureKind(status, KERNEL_STATUS_KINDS)\n}\n\nexport function createKernelApi(input: KernelApiInput): KernelApi {\n const api = createVendorApi({\n vendor: 'Kernel',\n baseUrl: input.apiUrl,\n headers: { authorization: `Bearer ${input.apiKey}` },\n kinds: KERNEL_STATUS_KINDS,\n })\n\n const jsonHeaders = { 'content-type': 'application/json', accept: 'application/json' }\n const poolPath = (pool: string, action = '') =>\n `browser_pools/${encodeURIComponent(pool)}${action === '' ? '' : `/${action}`}`\n\n return {\n async createBrowser(body, signal) {\n const response = await api.send('POST', 'browsers', {\n headers: jsonHeaders,\n body: JSON.stringify(body),\n signal,\n })\n if (!response.ok) throw await api.failure(response, 'POST /browsers')\n return api.json(response)\n },\n\n async deleteBrowser(id, signal) {\n const response = await api.send('DELETE', `browsers/${encodeURIComponent(id)}`, { signal })\n if (response.ok || response.status === 404) {\n await response.body?.cancel()\n return\n }\n throw await api.failure(response, 'DELETE /browsers/{id}')\n },\n\n async acquireFromPool(pool, body, signal) {\n const response = await api.send('POST', poolPath(pool, 'acquire'), {\n headers: jsonHeaders,\n body: JSON.stringify(body),\n signal,\n })\n if (response.status === 204) {\n await response.body?.cancel()\n return { acquired: false }\n }\n if (!response.ok) throw await api.failure(response, 'POST /browser_pools/{pool}/acquire')\n return { acquired: true, browser: await api.json(response) }\n },\n\n async releaseToPool(pool, sessionId, reuse, signal) {\n const response = await api.send('POST', poolPath(pool, 'release'), {\n headers: jsonHeaders,\n body: JSON.stringify({ session_id: sessionId, reuse }),\n signal,\n })\n if (response.ok || response.status === 404) {\n await response.body?.cancel()\n return\n }\n throw await api.failure(response, 'POST /browser_pools/{pool}/release')\n },\n\n async listBrowsers(limit, signal) {\n const response = await api.send('GET', `browsers?limit=${String(limit)}`, {\n headers: { accept: 'application/json' },\n signal,\n })\n if (!response.ok) throw await api.failure(response, 'GET /browsers')\n await response.body?.cancel()\n },\n\n async orgLimits(signal) {\n const response = await api.send('GET', 'org/limits', {\n headers: { accept: 'application/json' },\n signal,\n })\n if (!response.ok) throw await api.failure(response, 'GET /org/limits')\n return api.json(response)\n },\n\n async getPool(pool, signal) {\n const response = await api.send('GET', poolPath(pool), {\n headers: { accept: 'application/json' },\n signal,\n })\n if (response.status === 404) {\n await response.body?.cancel()\n return false\n }\n if (!response.ok) throw await api.failure(response, 'GET /browser_pools/{pool}')\n await response.body?.cancel()\n return true\n },\n }\n}\n","import { setTimeout as delay } from 'node:timers/promises'\nimport {\n checkEndpoints,\n credential,\n definePlugin,\n PluginError,\n z,\n type AcquireInput,\n type PluginCallContext,\n} from '@vostride/agent-qa-plugin-kit'\nimport {\n createKernelApi,\n KernelBrowserSchema,\n type KernelApi,\n type PoolAcquireAnswer,\n} from './kernel-api.js'\n\nconst KERNEL_API_URL = 'https://api.onkernel.com'\n// Kernel answers 204 at once when the acquire timeout is short, so a poll waits at least this long.\nconst POOL_POLL_FLOOR_MS = 1_000\n\nconst options = z.strictObject({\n apiKey: credential('The Kernel API key'),\n apiUrl: z\n .url()\n .default(KERNEL_API_URL)\n .describe(\"Kernel's API, https:// unless it points at this machine\"),\n})\n\nconst settings = z.strictObject({\n headless: z\n .boolean()\n .default(true)\n .describe('Run a headless browser; Kernel offers a live view only for a headful one'),\n timeoutSeconds: z\n .int()\n .min(10)\n .max(259_200)\n .default(60)\n .describe('Seconds with no connection before Kernel deletes the browser'),\n stealth: z.boolean().optional().describe(\"Kernel's stealth mode\"),\n region: z.string().min(1).optional().describe('The Kernel region, for example eu-west'),\n proxy: z.record(z.string(), z.unknown()).optional().describe(\"Kernel's proxy, sent as given\"),\n viewport: z\n .strictObject({\n width: z.int().positive(),\n height: z.int().positive(),\n refreshRate: z.int().positive().optional(),\n })\n .optional()\n .describe('The browser window size'),\n pool: z\n .string()\n .min(1)\n .optional()\n .describe('A Kernel browser pool, by id or name, to acquire from instead of creating a browser'),\n profile: z\n .union([z.string().min(1), z.strictObject({ id: z.string().min(1) })])\n .optional()\n .describe('A Kernel profile loaded read-only, by name or as { id }; needs context: reuse'),\n extra: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Fields merged last into the create or pool acquire request, overriding the rest'),\n})\n\ntype KernelSettings = z.output<typeof settings>\ntype Session = AcquireInput<unknown, unknown>['session']\n\nconst CREATE_ONLY_SETTINGS = ['stealth', 'region', 'proxy', 'viewport'] as const\n\nfunction profileBody(input: KernelSettings, session: Session) {\n const { profile } = input\n if (profile === undefined) return undefined\n if (session.platform !== 'web' || session.context !== 'reuse') {\n throw new PluginError(\n 'profile loads only into a reused context; set context: reuse on the remote.',\n { kind: 'refused', path: 'settings.profile' },\n )\n }\n const named = typeof profile === 'string' ? { name: profile } : { id: profile.id }\n return { ...named, save_changes: false }\n}\n\nfunction createBody(input: KernelSettings, profile: object | undefined): Record<string, unknown> {\n const { viewport } = input\n return {\n headless: input.headless,\n timeout_seconds: input.timeoutSeconds,\n ...(input.stealth === undefined ? {} : { stealth: input.stealth }),\n ...(input.region === undefined ? {} : { region: input.region }),\n ...(input.proxy === undefined ? {} : { proxy: input.proxy }),\n ...(viewport === undefined\n ? {}\n : {\n viewport: {\n width: viewport.width,\n height: viewport.height,\n ...(viewport.refreshRate === undefined ? {} : { refresh_rate: viewport.refreshRate }),\n },\n }),\n ...(profile === undefined ? {} : { profile }),\n ...input.extra,\n }\n}\n\nconst HandleSchema = z.object({\n sessionId: z.string().min(1),\n pool: z.string().min(1).optional(),\n reuse: z.boolean().optional(),\n})\ntype KernelHandle = z.output<typeof HandleSchema>\n\nfunction sessionIdOf(answer: unknown): string | undefined {\n if (typeof answer !== 'object' || answer === null || !('session_id' in answer)) return undefined\n const id = answer.session_id\n return typeof id === 'string' && id !== '' ? id : undefined\n}\n\nfunction cdpAnswer(answer: unknown, place: Omit<KernelHandle, 'sessionId'>) {\n const sessionId = sessionIdOf(answer)\n const handle = sessionId === undefined ? undefined : { sessionId, ...place }\n const browser = KernelBrowserSchema.safeParse(answer)\n if (!browser.success) {\n throw new PluginError('Kernel answered a browser without a session_id and a cdp_ws_url.', {\n kind: 'refused',\n ...(handle === undefined ? {} : { handle }),\n })\n }\n const { cdp_ws_url: url, browser_live_view_url: liveView } = browser.data\n return {\n handle: { sessionId: browser.data.session_id, ...place },\n protocol: 'cdp' as const,\n url,\n mask: liveView === undefined ? [url] : [url, liveView],\n }\n}\n\nasync function acquireFromPool(\n api: KernelApi,\n pool: string,\n body: Record<string, unknown>,\n reuse: boolean,\n ctx: PluginCallContext,\n) {\n const stopped = () => new PluginError(`stopped waiting for pool ${pool}`, { kind: 'timeout' })\n // Unlike a create, a wait that ends in 204 holds no browser, so the cancel stops the wait.\n for (;;) {\n const began = Date.now()\n let answer: PoolAcquireAnswer\n try {\n answer = await api.acquireFromPool(pool, body, ctx.signal)\n } catch (error) {\n if (ctx.signal.aborted) throw stopped()\n throw error\n }\n if (answer.acquired) return cdpAnswer(answer.browser, { pool, reuse })\n if (ctx.signal.aborted) throw stopped()\n ctx.progress({ message: `waiting for pool ${pool}` })\n const rest = POOL_POLL_FLOOR_MS - (Date.now() - began)\n if (rest > 0) await delay(rest, undefined, { signal: ctx.signal }).catch(() => {})\n if (ctx.signal.aborted) throw stopped()\n }\n}\n\nconst OrgLimitsSchema = z.object({\n concurrent_sessions_available: z.int().nonnegative(),\n concurrent_sessions_used: z.int().nonnegative(),\n})\n\nfunction refusedKey(error: unknown): boolean {\n return error instanceof PluginError && error.kind === 'auth'\n}\n\nfunction failureLine(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nasync function limitsLine(api: KernelApi, signal: AbortSignal): Promise<string> {\n let answer: unknown\n try {\n answer = await api.orgLimits(signal)\n } catch (error) {\n if (refusedKey(error)) return 'organization limits not readable with this key'\n return `organization limits not readable: ${failureLine(error)}`\n }\n const limits = OrgLimitsSchema.safeParse(answer)\n if (!limits.success) return 'organization limits not readable: Kernel answered another shape'\n const { concurrent_sessions_available: free, concurrent_sessions_used: used } = limits.data\n return `${String(free)} of ${String(free + used)} concurrent sessions free`\n}\n\nexport default definePlugin({\n options,\n settings,\n serves: ['cdp'],\n skill: './skill',\n\n initialize({ options }) {\n checkEndpoints(options, [{ path: 'apiUrl', scheme: 'https' }])\n },\n\n async check({ options, remotes }, ctx) {\n const api = createKernelApi(options)\n try {\n await api.listBrowsers(1, ctx.signal)\n } catch (error) {\n const line = refusedKey(error) ? 'the API key was refused' : failureLine(error)\n return { state: 'warn', lines: [line] }\n }\n const lines = ['the API key was accepted', await limitsLine(api, ctx.signal)]\n let state: 'ok' | 'warn' = 'ok'\n const pools = new Set(remotes.flatMap(({ settings }) => settings.pool ?? []))\n for (const pool of pools) {\n let found: boolean\n try {\n found = await api.getPool(pool, ctx.signal)\n } catch (error) {\n state = 'warn'\n lines.push(`pool ${pool} not readable: ${failureLine(error)}`)\n continue\n }\n if (!found) state = 'warn'\n lines.push(found ? `pool ${pool} found` : `pool ${pool} not found`)\n }\n return { state, lines }\n },\n\n async acquire({ options, settings, session }, ctx) {\n if (session.platform !== 'web' || session.engine !== 'chromium') {\n const what = session.platform === 'web' ? session.engine : session.platform\n throw new PluginError(`Kernel runs Chromium web sessions only, not ${what}.`, {\n kind: 'refused',\n })\n }\n const profile = profileBody(settings, session)\n const api = createKernelApi(options)\n if (settings.pool !== undefined) {\n const fromCreate = CREATE_ONLY_SETTINGS.find((name) => settings[name] !== undefined)\n if (fromCreate !== undefined) {\n throw new PluginError(\n `${fromCreate} comes from the pool's own configuration; remove it from a remote with pool.`,\n { kind: 'refused', path: `settings.${fromCreate}` },\n )\n }\n const body = { ...(profile === undefined ? {} : { profile }), ...settings.extra }\n const reuse = session.context !== 'reuse'\n return acquireFromPool(api, settings.pool, body, reuse, ctx)\n }\n // The create is not cancelled: once Kernel has a browser, the handle must come back to be\n // deleted, and the kit releases a handle that arrives after a cancel was answered.\n return cdpAnswer(await api.createBrowser(createBody(settings, profile)), {})\n },\n\n async release({ options, handle }, ctx) {\n const parsed = HandleSchema.safeParse(handle)\n if (!parsed.success) {\n throw new PluginError('A Kernel handle names its browser by sessionId.', { kind: 'refused' })\n }\n const { sessionId, pool, reuse } = parsed.data\n const api = createKernelApi(options)\n if (pool === undefined) await api.deleteBrowser(sessionId, ctx.signal)\n else await api.releaseToPool(pool, sessionId, reuse ?? false, ctx.signal)\n },\n})\n"],"mappings":"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"}
package/dist/plugin.js CHANGED
@@ -1 +1 @@
1
- export { t as default } from "./plugin-C2raYjWg.js";
1
+ export { t as default } from "./plugin-Bh7Aks1i.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vostride/agent-qa-plugin-kernel",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "description": "agent-qa plugin for Kernel: creates a Kernel browser before a test connects and deletes it after.",
6
6
  "keywords": [
@@ -42,11 +42,11 @@
42
42
  "clean": "rm -rf dist .turbo"
43
43
  },
44
44
  "dependencies": {
45
- "@vostride/agent-qa-plugin-kit": "0.3.1"
45
+ "@vostride/agent-qa-plugin-kit": "0.3.2"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "~24.19.2",
49
- "@vostride/agent-qa-arch": "0.3.1",
49
+ "@vostride/agent-qa-arch": "0.3.2",
50
50
  "tsdown": "~0.23.4",
51
51
  "typescript": "~7.0.2",
52
52
  "vitest": "~5.0.5"
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin-C2raYjWg.js","names":["delay"],"sources":["../src/kernel-api.ts","../src/plugin.ts"],"sourcesContent":["import { PluginError, z } from '@vostride/agent-qa-plugin-kit'\n\ntype KernelFailureKind = 'auth' | 'capacity' | 'refused' | 'unreachable'\n\nexport const KernelBrowserSchema = z.object({\n session_id: z.string().min(1),\n cdp_ws_url: z.string().min(1),\n browser_live_view_url: z.string().min(1).optional(),\n})\n\ninterface KernelApiInput {\n apiUrl: string\n apiKey: string\n}\n\nexport type PoolAcquireAnswer = { acquired: true; browser: unknown } | { acquired: false }\n\nexport interface KernelApi {\n createBrowser(body: Record<string, unknown>, signal?: AbortSignal): Promise<unknown>\n deleteBrowser(id: string, signal?: AbortSignal): Promise<void>\n acquireFromPool(\n pool: string,\n body: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<PoolAcquireAnswer>\n releaseToPool(pool: string, sessionId: string, reuse: boolean, signal?: AbortSignal): Promise<void>\n listBrowsers(limit: number, signal?: AbortSignal): Promise<void>\n orgLimits(signal?: AbortSignal): Promise<unknown>\n getPool(pool: string, signal?: AbortSignal): Promise<boolean>\n}\n\nexport function kernelFailureKind(status: number | undefined): KernelFailureKind {\n if (status === undefined) return 'unreachable'\n if (status === 401 || status === 403) return 'auth'\n if (status === 429 || status === 529) return 'capacity'\n return 'refused'\n}\n\ninterface SendInit {\n headers?: Record<string, string>\n body?: string\n signal?: AbortSignal | undefined\n}\n\nconst MAX_REASON_LENGTH = 200\n\nfunction oneLine(text: string): string {\n const line = text.replace(/\\s+/g, ' ').trim()\n return line.length > MAX_REASON_LENGTH ? `${line.slice(0, MAX_REASON_LENGTH)}…` : line\n}\n\nasync function vendorReason(response: Response): Promise<string> {\n const text = await response.text().catch(() => '')\n try {\n const body: unknown = JSON.parse(text)\n if (typeof body === 'object' && body !== null && 'message' in body) {\n const { message } = body\n if (typeof message === 'string' && message.trim() !== '') return `: ${oneLine(message)}`\n }\n } catch {\n // a body that is not JSON carries no reason worth showing\n }\n return ''\n}\n\nasync function statusFailure(response: Response, request: string): Promise<PluginError> {\n const retryAfter = response.headers.get('retry-after')\n const wait = response.status === 429 && retryAfter !== null ? `, retry after ${retryAfter} s` : ''\n const reason = await vendorReason(response)\n return new PluginError(`Kernel answered ${response.status} to ${request}${reason}${wait}`, {\n kind: kernelFailureKind(response.status),\n })\n}\n\nasync function jsonOf(response: Response): Promise<unknown> {\n const text = await response.text()\n try {\n return JSON.parse(text) as unknown\n } catch {\n return undefined\n }\n}\n\nfunction causeText(error: unknown): string {\n if (!(error instanceof Error)) return String(error)\n const cause = error.cause\n if (cause instanceof Error) {\n const code = 'code' in cause && typeof cause.code === 'string' ? cause.code : cause.message\n return oneLine(code)\n }\n return oneLine(error.message)\n}\n\nexport function createKernelApi(input: KernelApiInput): KernelApi {\n const base = input.apiUrl.endsWith('/') ? input.apiUrl : `${input.apiUrl}/`\n const authorization = `Bearer ${input.apiKey}`\n\n const send = async (method: string, path: string, init: SendInit): Promise<Response> => {\n const url = new URL(path, base)\n try {\n return await fetch(url, {\n method,\n headers: { ...init.headers, authorization },\n ...(init.body === undefined ? {} : { body: init.body }),\n ...(init.signal === undefined ? {} : { signal: init.signal }),\n })\n } catch (error) {\n if (init.signal?.aborted === true) throw error\n throw new PluginError(`Kernel could not be reached at ${url.origin}: ${causeText(error)}`, {\n kind: 'unreachable',\n })\n }\n }\n\n const jsonHeaders = { 'content-type': 'application/json', accept: 'application/json' }\n const poolPath = (pool: string, action = '') =>\n `browser_pools/${encodeURIComponent(pool)}${action === '' ? '' : `/${action}`}`\n\n return {\n async createBrowser(body, signal) {\n const response = await send('POST', 'browsers', {\n headers: jsonHeaders,\n body: JSON.stringify(body),\n signal,\n })\n if (!response.ok) throw await statusFailure(response, 'POST /browsers')\n return jsonOf(response)\n },\n\n async deleteBrowser(id, signal) {\n const response = await send('DELETE', `browsers/${encodeURIComponent(id)}`, { signal })\n if (response.ok || response.status === 404) {\n await response.body?.cancel()\n return\n }\n throw await statusFailure(response, 'DELETE /browsers/{id}')\n },\n\n async acquireFromPool(pool, body, signal) {\n const response = await send('POST', poolPath(pool, 'acquire'), {\n headers: jsonHeaders,\n body: JSON.stringify(body),\n signal,\n })\n if (response.status === 204) {\n await response.body?.cancel()\n return { acquired: false }\n }\n if (!response.ok) throw await statusFailure(response, 'POST /browser_pools/{pool}/acquire')\n return { acquired: true, browser: await jsonOf(response) }\n },\n\n async releaseToPool(pool, sessionId, reuse, signal) {\n const response = await send('POST', poolPath(pool, 'release'), {\n headers: jsonHeaders,\n body: JSON.stringify({ session_id: sessionId, reuse }),\n signal,\n })\n if (response.ok || response.status === 404) {\n await response.body?.cancel()\n return\n }\n throw await statusFailure(response, 'POST /browser_pools/{pool}/release')\n },\n\n async listBrowsers(limit, signal) {\n const response = await send('GET', `browsers?limit=${String(limit)}`, {\n headers: { accept: 'application/json' },\n signal,\n })\n if (!response.ok) throw await statusFailure(response, 'GET /browsers')\n await response.body?.cancel()\n },\n\n async orgLimits(signal) {\n const response = await send('GET', 'org/limits', {\n headers: { accept: 'application/json' },\n signal,\n })\n if (!response.ok) throw await statusFailure(response, 'GET /org/limits')\n return jsonOf(response)\n },\n\n async getPool(pool, signal) {\n const response = await send('GET', poolPath(pool), {\n headers: { accept: 'application/json' },\n signal,\n })\n if (response.status === 404) {\n await response.body?.cancel()\n return false\n }\n if (!response.ok) throw await statusFailure(response, 'GET /browser_pools/{pool}')\n await response.body?.cancel()\n return true\n },\n }\n}\n","import { setTimeout as delay } from 'node:timers/promises'\nimport {\n credential,\n definePlugin,\n PluginError,\n z,\n type AcquireInput,\n type PluginCallContext,\n} from '@vostride/agent-qa-plugin-kit'\nimport {\n createKernelApi,\n KernelBrowserSchema,\n type KernelApi,\n type PoolAcquireAnswer,\n} from './kernel-api.js'\n\nconst KERNEL_API_URL = 'https://api.onkernel.com'\n// Kernel answers 204 at once when the acquire timeout is short, so a poll waits at least this long.\nconst POOL_POLL_FLOOR_MS = 1_000\nconst LOOPBACK_HOSTS = new Set(['localhost', '[::1]'])\n\nconst options = z.strictObject({\n apiKey: credential('The Kernel API key'),\n apiUrl: z\n .url()\n .default(KERNEL_API_URL)\n .describe(\"Kernel's API, https:// unless it points at this machine\"),\n})\n\nconst settings = z.strictObject({\n headless: z\n .boolean()\n .default(true)\n .describe('Run a headless browser; Kernel offers a live view only for a headful one'),\n timeoutSeconds: z\n .int()\n .min(10)\n .max(259_200)\n .default(60)\n .describe('Seconds with no connection before Kernel deletes the browser'),\n stealth: z.boolean().optional().describe(\"Kernel's stealth mode\"),\n region: z.string().min(1).optional().describe('The Kernel region, for example eu-west'),\n proxy: z.record(z.string(), z.unknown()).optional().describe(\"Kernel's proxy, sent as given\"),\n viewport: z\n .strictObject({\n width: z.int().positive(),\n height: z.int().positive(),\n refreshRate: z.int().positive().optional(),\n })\n .optional()\n .describe('The browser window size'),\n pool: z\n .string()\n .min(1)\n .optional()\n .describe('A Kernel browser pool, by id or name, to acquire from instead of creating a browser'),\n profile: z\n .union([z.string().min(1), z.strictObject({ id: z.string().min(1) })])\n .optional()\n .describe('A Kernel profile loaded read-only, by name or as { id }; needs context: reuse'),\n extra: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Fields merged last into the create or pool acquire request, overriding the rest'),\n})\n\ntype KernelSettings = z.output<typeof settings>\ntype Session = AcquireInput<unknown, unknown>['session']\n\nfunction apiUrlAllowed(value: string): boolean {\n const url = new URL(value)\n if (url.username !== '' || url.password !== '') return false\n if (url.protocol === 'https:') return true\n if (url.protocol !== 'http:') return false\n return LOOPBACK_HOSTS.has(url.hostname) || /^127(?:\\.\\d{1,3}){3}$/.test(url.hostname)\n}\n\nconst CREATE_ONLY_SETTINGS = ['stealth', 'region', 'proxy', 'viewport'] as const\n\nfunction profileBody(input: KernelSettings, session: Session) {\n const { profile } = input\n if (profile === undefined) return undefined\n if (session.platform !== 'web' || session.context !== 'reuse') {\n throw new PluginError(\n 'profile loads only into a reused context; set context: reuse on the remote.',\n { kind: 'refused', path: 'settings.profile' },\n )\n }\n const named = typeof profile === 'string' ? { name: profile } : { id: profile.id }\n return { ...named, save_changes: false }\n}\n\nfunction createBody(input: KernelSettings, profile: object | undefined): Record<string, unknown> {\n const { viewport } = input\n return {\n headless: input.headless,\n timeout_seconds: input.timeoutSeconds,\n ...(input.stealth === undefined ? {} : { stealth: input.stealth }),\n ...(input.region === undefined ? {} : { region: input.region }),\n ...(input.proxy === undefined ? {} : { proxy: input.proxy }),\n ...(viewport === undefined\n ? {}\n : {\n viewport: {\n width: viewport.width,\n height: viewport.height,\n ...(viewport.refreshRate === undefined ? {} : { refresh_rate: viewport.refreshRate }),\n },\n }),\n ...(profile === undefined ? {} : { profile }),\n ...input.extra,\n }\n}\n\nconst HandleSchema = z.object({\n sessionId: z.string().min(1),\n pool: z.string().min(1).optional(),\n reuse: z.boolean().optional(),\n})\ntype KernelHandle = z.output<typeof HandleSchema>\n\nfunction sessionIdOf(answer: unknown): string | undefined {\n if (typeof answer !== 'object' || answer === null || !('session_id' in answer)) return undefined\n const id = answer.session_id\n return typeof id === 'string' && id !== '' ? id : undefined\n}\n\nfunction cdpAnswer(answer: unknown, place: Omit<KernelHandle, 'sessionId'>) {\n const sessionId = sessionIdOf(answer)\n const handle = sessionId === undefined ? undefined : { sessionId, ...place }\n const browser = KernelBrowserSchema.safeParse(answer)\n if (!browser.success) {\n throw new PluginError('Kernel answered a browser without a session_id and a cdp_ws_url.', {\n kind: 'refused',\n ...(handle === undefined ? {} : { handle }),\n })\n }\n const { cdp_ws_url: url, browser_live_view_url: liveView } = browser.data\n return {\n handle: { sessionId: browser.data.session_id, ...place },\n protocol: 'cdp' as const,\n url,\n mask: liveView === undefined ? [url] : [url, liveView],\n }\n}\n\nasync function acquireFromPool(\n api: KernelApi,\n pool: string,\n body: Record<string, unknown>,\n reuse: boolean,\n ctx: PluginCallContext,\n) {\n const stopped = () => new PluginError(`stopped waiting for pool ${pool}`, { kind: 'timeout' })\n // Unlike a create, a wait that ends in 204 holds no browser, so the cancel stops the wait.\n for (;;) {\n const began = Date.now()\n let answer: PoolAcquireAnswer\n try {\n answer = await api.acquireFromPool(pool, body, ctx.signal)\n } catch (error) {\n if (ctx.signal.aborted) throw stopped()\n throw error\n }\n if (answer.acquired) return cdpAnswer(answer.browser, { pool, reuse })\n if (ctx.signal.aborted) throw stopped()\n ctx.progress({ message: `waiting for pool ${pool}` })\n const rest = POOL_POLL_FLOOR_MS - (Date.now() - began)\n if (rest > 0) await delay(rest, undefined, { signal: ctx.signal }).catch(() => {})\n if (ctx.signal.aborted) throw stopped()\n }\n}\n\nconst OrgLimitsSchema = z.object({\n concurrent_sessions_available: z.int().nonnegative(),\n concurrent_sessions_used: z.int().nonnegative(),\n})\n\nfunction refusedKey(error: unknown): boolean {\n return error instanceof PluginError && error.kind === 'auth'\n}\n\nfunction failureLine(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nasync function limitsLine(api: KernelApi, signal: AbortSignal): Promise<string> {\n let answer: unknown\n try {\n answer = await api.orgLimits(signal)\n } catch (error) {\n if (refusedKey(error)) return 'organization limits not readable with this key'\n return `organization limits not readable: ${failureLine(error)}`\n }\n const limits = OrgLimitsSchema.safeParse(answer)\n if (!limits.success) return 'organization limits not readable: Kernel answered another shape'\n const { concurrent_sessions_available: free, concurrent_sessions_used: used } = limits.data\n return `${String(free)} of ${String(free + used)} concurrent sessions free`\n}\n\nexport default definePlugin({\n options,\n settings,\n serves: ['cdp'],\n skill: './skill',\n\n initialize({ options }) {\n if (!apiUrlAllowed(options.apiUrl)) {\n throw new PluginError('apiUrl must use https:// unless it points at this machine.', {\n kind: 'refused',\n path: 'apiUrl',\n })\n }\n },\n\n async check({ options, remotes }, ctx) {\n const api = createKernelApi(options)\n try {\n await api.listBrowsers(1, ctx.signal)\n } catch (error) {\n const line = refusedKey(error) ? 'the API key was refused' : failureLine(error)\n return { state: 'warn', lines: [line] }\n }\n const lines = ['the API key was accepted', await limitsLine(api, ctx.signal)]\n let state: 'ok' | 'warn' = 'ok'\n const pools = new Set(remotes.flatMap(({ settings }) => settings.pool ?? []))\n for (const pool of pools) {\n let found: boolean\n try {\n found = await api.getPool(pool, ctx.signal)\n } catch (error) {\n state = 'warn'\n lines.push(`pool ${pool} not readable: ${failureLine(error)}`)\n continue\n }\n if (!found) state = 'warn'\n lines.push(found ? `pool ${pool} found` : `pool ${pool} not found`)\n }\n return { state, lines }\n },\n\n async acquire({ options, settings, session }, ctx) {\n if (session.platform !== 'web' || session.engine !== 'chromium') {\n const what = session.platform === 'web' ? session.engine : session.platform\n throw new PluginError(`Kernel runs Chromium web sessions only, not ${what}.`, {\n kind: 'refused',\n })\n }\n const profile = profileBody(settings, session)\n const api = createKernelApi(options)\n if (settings.pool !== undefined) {\n const fromCreate = CREATE_ONLY_SETTINGS.find((name) => settings[name] !== undefined)\n if (fromCreate !== undefined) {\n throw new PluginError(\n `${fromCreate} comes from the pool's own configuration; remove it from a remote with pool.`,\n { kind: 'refused', path: `settings.${fromCreate}` },\n )\n }\n const body = { ...(profile === undefined ? {} : { profile }), ...settings.extra }\n const reuse = session.context !== 'reuse'\n return acquireFromPool(api, settings.pool, body, reuse, ctx)\n }\n // The create is not cancelled: once Kernel has a browser, the handle must come back to be\n // deleted, and the kit releases a handle that arrives after a cancel was answered.\n return cdpAnswer(await api.createBrowser(createBody(settings, profile)), {})\n },\n\n async release({ options, handle }, ctx) {\n const parsed = HandleSchema.safeParse(handle)\n if (!parsed.success) {\n throw new PluginError('A Kernel handle names its browser by sessionId.', { kind: 'refused' })\n }\n const { sessionId, pool, reuse } = parsed.data\n const api = createKernelApi(options)\n if (pool === undefined) await api.deleteBrowser(sessionId, ctx.signal)\n else await api.releaseToPool(pool, sessionId, reuse ?? false, ctx.signal)\n },\n})\n"],"mappings":"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"}