diff --git a/sdk-typescript/packages/kernel/LICENSE b/sdk-typescript/packages/kernel/LICENSE new file mode 100644 index 0000000..8ea70cc --- /dev/null +++ b/sdk-typescript/packages/kernel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 WAVE Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sdk-typescript/packages/kernel/README.md b/sdk-typescript/packages/kernel/README.md new file mode 100644 index 0000000..949c90d --- /dev/null +++ b/sdk-typescript/packages/kernel/README.md @@ -0,0 +1,54 @@ +# @wave-av/kernel + +The **one** shared Kernel cloud-browser client for WAVE. A thin, typed wrapper +over [`@onkernel/sdk`](https://www.npmjs.com/package/@onkernel/sdk) pinned to the +`api.onkernel.com` host. Every WAVE consumer talks to Kernel through +`WaveKernel`, never the raw SDK — so telemetry, resilience, and auth live in one +place. + +The client always targets `https://api.onkernel.com/` (the SDK default) and +never the legacy `api.kernel.sh` host. + +## Install + +```bash +pnpm add @wave-av/kernel +``` + +## Usage + +```typescript +import { WaveKernel, withTelemetry } from '@wave-av/kernel'; + +const kernel = new WaveKernel({ apiKey: process.env.KERNEL_API_KEY }); + +// `withTelemetry` opts a browser session into WAVE's default CDP telemetry +// categories (console + network + page; screenshot stays off). +const browser = await kernel.browsers.create(withTelemetry({})); +``` + +`WaveKernel` re-exposes the full 0.78 SDK resource surface as typed passthrough +getters (`browsers`, `invocations`, `browserPools`, `profiles`, `credentials`, +`credentialProviders`, `apiKeys`, `projects`, `auth`, `proxies`, `deployments`, +`apps`, `organization`, `auditLogs`, `extensions`). Use `kernel.raw` only as an +escape hatch to the underlying SDK client. + +## Configuration + +| Option | Default | Notes | +| ------------- | ----------------------------- | ------------------------------------------------ | +| `apiKey` | `process.env.KERNEL_API_KEY` | Kernel API key. | +| `baseURL` | `https://api.onkernel.com/` | Override only for testing. | +| `projectID` | — | Optional project scope applied to all requests. | +| `timeoutMs` | `30000` | Request timeout in milliseconds. | +| `resilience` | — | Slice-2 seams (see below). | + +## Resilience seams + +The `resilience` hooks (circuit breaker, request signer, error capture) are +**inert seams** in slice 1 — the interfaces exist so slice-2 wiring can drop in +without changing the public surface. Passing them today is a no-op. + +## License + +Apache-2.0 diff --git a/sdk-typescript/packages/kernel/package.json b/sdk-typescript/packages/kernel/package.json new file mode 100644 index 0000000..54cbf2b --- /dev/null +++ b/sdk-typescript/packages/kernel/package.json @@ -0,0 +1,78 @@ +{ + "name": "@wave-av/kernel", + "version": "0.1.0", + "description": "WAVE shared Kernel cloud-browser client — one typed client on @onkernel/sdk, api.onkernel.com, telemetry-ready", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "default": "./dist/index.js" + }, + "./telemetry": { + "types": "./dist/telemetry.d.ts", + "import": "./dist/telemetry.js", + "require": "./dist/telemetry.cjs", + "default": "./dist/telemetry.js" + } + }, + "typesVersions": { + "*": { + "telemetry": [ + "./dist/telemetry.d.ts" + ] + } + }, + "scripts": { + "build": "tsup src/index.ts src/telemetry.ts --format esm,cjs && tsc -p tsconfig.json --emitDeclarationOnly", + "dev": "tsup src/index.ts --format esm,cjs --dts --watch", + "lint": "eslint src/", + "type-check": "tsc --noEmit", + "test": "vitest run", + "prepublishOnly": "npm run build" + }, + "keywords": [ + "wave", + "kernel", + "cloud-browser", + "browser", + "onkernel", + "telemetry" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org/" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "repository": { + "type": "git", + "url": "https://github.com/wave-av/sdks.git", + "directory": "sdk-typescript/packages/kernel" + }, + "bugs": { + "url": "https://github.com/wave-av/sdks/issues" + }, + "homepage": "https://wave.online/developers/kernel", + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@onkernel/sdk": "^0.78.0" + }, + "devDependencies": { + "@types/node": "^25.0.3", + "tsup": "^8.0.0", + "typescript": "^5.9.0", + "vitest": "^4.1.4" + }, + "author": "WAVE Online, LLC " +} diff --git a/sdk-typescript/packages/kernel/src/__tests__/client.test.ts b/sdk-typescript/packages/kernel/src/__tests__/client.test.ts new file mode 100644 index 0000000..cc2d24a --- /dev/null +++ b/sdk-typescript/packages/kernel/src/__tests__/client.test.ts @@ -0,0 +1,116 @@ +import { describe, it, expect } from 'vitest'; +import { WaveKernel } from '../client.js'; +import { + KernelApiError, + KernelErrorCode, + CircuitOpenError, + isKernelApiError, +} from '../errors.js'; +import { withTelemetry, WAVE_TELEMETRY_DEFAULT } from '../telemetry.js'; + +describe('WaveKernel', () => { + it('constructs with an explicit apiKey without throwing (no network)', () => { + expect(() => new WaveKernel({ apiKey: 'test-key' })).not.toThrow(); + }); + + it('exposes the SDK resource surface', () => { + const kernel = new WaveKernel({ apiKey: 'test-key' }); + expect(kernel.browsers).toBeDefined(); + expect(kernel.invocations).toBeDefined(); + expect(kernel.browserPools).toBeDefined(); + expect(kernel.credentials).toBeDefined(); + }); + + it('exposes the full 0.78 resource surface (all 15 resources)', () => { + const kernel = new WaveKernel({ apiKey: 'test-key' }); + for (const resource of [ + kernel.browsers, + kernel.invocations, + kernel.browserPools, + kernel.profiles, + kernel.credentials, + kernel.credentialProviders, + kernel.apiKeys, + kernel.projects, + kernel.auth, + kernel.proxies, + kernel.deployments, + kernel.apps, + kernel.organization, + kernel.auditLogs, + kernel.extensions, + ]) { + expect(resource).toBeDefined(); + } + }); + + it('defaults to the api.onkernel.com base URL (never api.kernel.sh)', () => { + const kernel = new WaveKernel({ apiKey: 'x' }); + expect(kernel.raw.baseURL).toBe('https://api.onkernel.com/'); + }); + + it('honors an explicit baseURL override', () => { + const kernel = new WaveKernel({ + apiKey: 'x', + baseURL: 'https://kernel.test.internal/', + }); + expect(kernel.raw.baseURL).toBe('https://kernel.test.internal/'); + }); +}); + +describe('withTelemetry', () => { + it('enables console/network/page and leaves screenshot undefined', () => { + const params = withTelemetry({}); + expect(params.telemetry.enabled).toBe(true); + expect(params.telemetry.browser?.console?.enabled).toBe(true); + expect(params.telemetry.browser?.network?.enabled).toBe(true); + expect(params.telemetry.browser?.page?.enabled).toBe(true); + expect(params.telemetry.browser?.screenshot).toBeUndefined(); + }); + + it('does not enable screenshot in the default categories', () => { + expect(WAVE_TELEMETRY_DEFAULT.screenshot).toBeUndefined(); + }); +}); + +describe('KernelApiError', () => { + it('timeout() sets a code', () => { + const err = KernelApiError.timeout(30000); + expect(err.code).toBe(KernelErrorCode.TIMEOUT); + expect(err.code).toBeTruthy(); + expect(isKernelApiError(err)).toBe(true); + }); + + it('rateLimited() sets code, name, and retryAfterMs context', () => { + const err = KernelApiError.rateLimited(1500); + expect(err.code).toBe(KernelErrorCode.RATE_LIMITED); + expect(err.name).toBe('KernelApiError'); + expect(err.context).toEqual({ retryAfterMs: 1500 }); + expect(isKernelApiError(err)).toBe(true); + }); + + it('sessionNotFound() sets code, name, and sessionId context', () => { + const err = KernelApiError.sessionNotFound('sess_123'); + expect(err.code).toBe(KernelErrorCode.SESSION_NOT_FOUND); + expect(err.name).toBe('KernelApiError'); + expect(err.context).toEqual({ sessionId: 'sess_123' }); + expect(isKernelApiError(err)).toBe(true); + }); + + it('poolExhausted() sets code, name, and poolId context', () => { + const err = KernelApiError.poolExhausted('pool_abc'); + expect(err.code).toBe(KernelErrorCode.POOL_EXHAUSTED); + expect(err.name).toBe('KernelApiError'); + expect(err.context).toEqual({ poolId: 'pool_abc' }); + expect(isKernelApiError(err)).toBe(true); + }); + + it('circuitOpen() returns a CircuitOpenError with the circuit-open code', () => { + const err = KernelApiError.circuitOpen('kernel'); + expect(err).toBeInstanceOf(CircuitOpenError); + expect(err.code).toBe(KernelErrorCode.CIRCUIT_OPEN); + expect(err.name).toBe('CircuitOpenError'); + expect(err.context).toEqual({ serviceName: 'kernel' }); + expect(isKernelApiError(err)).toBe(true); + }); +}); diff --git a/sdk-typescript/packages/kernel/src/client.ts b/sdk-typescript/packages/kernel/src/client.ts new file mode 100644 index 0000000..1a89bf2 --- /dev/null +++ b/sdk-typescript/packages/kernel/src/client.ts @@ -0,0 +1,146 @@ +/** + * WAVE shared Kernel cloud-browser client. + * + * `WaveKernel` is the ONE Kernel entrypoint for all of WAVE (law: + * kernel-substrate-governed). Every WAVE consumer — adk, render, and the other + * product edge homes — talks to Kernel through this single typed wrapper over + * `@onkernel/sdk`, never the raw SDK. That gives us one place to add telemetry, + * resilience, and auth without touching callers. + * + * Base URL is the SDK default (`https://api.onkernel.com/`); we never point at + * the legacy `api.kernel.sh` host. + * + * @example + * ```typescript + * import { WaveKernel } from '@wave-av/kernel'; + * + * const kernel = new WaveKernel({ apiKey: process.env.KERNEL_API_KEY }); + * const browser = await kernel.browsers.create({}); + * ``` + */ +import { Kernel } from '@onkernel/sdk'; +import type { ResilienceHooks } from './resilience.js'; + +/** Default request timeout in milliseconds. */ +export const DEFAULT_TIMEOUT_MS = 30_000; + +/** Configuration for {@link WaveKernel}. */ +export interface WaveKernelConfig { + /** Kernel API key. Defaults to `process.env.KERNEL_API_KEY`. */ + apiKey?: string; + /** Override the API base URL. Defaults to the SDK default (`https://api.onkernel.com/`). */ + baseURL?: string; + /** Optional project scope applied to all requests. */ + projectID?: string; + /** Request timeout in milliseconds. Defaults to {@link DEFAULT_TIMEOUT_MS}. */ + timeoutMs?: number; + /** + * Optional resilience hooks (circuit breaker, signer). Inert in slice 1 — + * see {@link ResilienceHooks} and `src/resilience.ts`. + */ + resilience?: ResilienceHooks; +} + +/** + * The single shared WAVE Kernel client. Wraps a private `@onkernel/sdk` + * {@link Kernel} instance and re-exposes its full 0.78 resource surface as + * typed passthrough getters so callers never touch the raw SDK. + */ +export class WaveKernel { + private readonly kernel: Kernel; + + /** Resilience hooks captured at construction (inert in slice 1). */ + readonly resilience?: ResilienceHooks; + + constructor(config: WaveKernelConfig = {}) { + this.resilience = config.resilience; + this.kernel = new Kernel({ + apiKey: config.apiKey ?? process.env.KERNEL_API_KEY, + // Only override baseURL when explicitly provided so the SDK default + // (https://api.onkernel.com/) is used otherwise. + ...(config.baseURL !== undefined ? { baseURL: config.baseURL } : {}), + ...(config.projectID !== undefined ? { projectID: config.projectID } : {}), + timeout: config.timeoutMs ?? DEFAULT_TIMEOUT_MS, + }); + } + + /** Escape hatch to the underlying SDK client. Prefer the typed getters below. */ + get raw(): Kernel { + return this.kernel; + } + + /** Cloud browser sessions: create / retrieve / update / list / curl / loadExtensions. */ + get browsers() { + return this.kernel.browsers; + } + + /** App invocations: create / retrieve / update / list / follow. */ + get invocations() { + return this.kernel.invocations; + } + + /** Browser pools: create / update / list / delete / acquire / release. */ + get browserPools() { + return this.kernel.browserPools; + } + + /** Browser profiles: create / retrieve / update / list / delete. */ + get profiles() { + return this.kernel.profiles; + } + + /** Stored credentials: create / retrieve / update / list / delete / totpCode. */ + get credentials() { + return this.kernel.credentials; + } + + /** Credential providers: create / retrieve / update / list / delete / listItems / test. */ + get credentialProviders() { + return this.kernel.credentialProviders; + } + + /** API key management. */ + get apiKeys() { + return this.kernel.apiKeys; + } + + /** Project management. */ + get projects() { + return this.kernel.projects; + } + + /** Auth resource. */ + get auth() { + return this.kernel.auth; + } + + /** Proxy configuration. */ + get proxies() { + return this.kernel.proxies; + } + + /** App deployments. */ + get deployments() { + return this.kernel.deployments; + } + + /** Deployed apps. */ + get apps() { + return this.kernel.apps; + } + + /** Organization management. */ + get organization() { + return this.kernel.organization; + } + + /** Audit logs. */ + get auditLogs() { + return this.kernel.auditLogs; + } + + /** Browser extensions: upload / list / delete / download. */ + get extensions() { + return this.kernel.extensions; + } +} diff --git a/sdk-typescript/packages/kernel/src/errors.ts b/sdk-typescript/packages/kernel/src/errors.ts new file mode 100644 index 0000000..bcf97a3 --- /dev/null +++ b/sdk-typescript/packages/kernel/src/errors.ts @@ -0,0 +1,109 @@ +/** + * Kernel error taxonomy. Mirrors WAVE's prior internal Kernel error taxonomy + * so consumers can migrate onto the shared client without rewriting error + * handling. + */ + +/** Stable, machine-readable Kernel error codes. */ +export const KernelErrorCode = { + API_ERROR: 'KERNEL_API_ERROR', + CIRCUIT_OPEN: 'KERNEL_CIRCUIT_OPEN', + RATE_LIMITED: 'KERNEL_RATE_LIMITED', + SESSION_NOT_FOUND: 'KERNEL_SESSION_NOT_FOUND', + POOL_EXHAUSTED: 'KERNEL_POOL_EXHAUSTED', + TIMEOUT: 'KERNEL_TIMEOUT', +} as const; + +export type KernelErrorCodeValue = + (typeof KernelErrorCode)[keyof typeof KernelErrorCode]; + +/** Base error for all Kernel client failures. */ +export class KernelApiError extends Error { + /** Machine-readable error code (see {@link KernelErrorCode}). */ + readonly code: string; + /** Optional structured context for the error. */ + readonly context: Record; + + constructor( + message: string, + code: string = KernelErrorCode.API_ERROR, + context: Record = {}, + ) { + super(message); + this.name = 'KernelApiError'; + this.code = code; + this.context = context; + Error.captureStackTrace?.(this, this.constructor); + } + + /** The circuit breaker is open — requests are being shed. */ + static circuitOpen(serviceName = 'kernel'): CircuitOpenError { + return new CircuitOpenError( + `Circuit breaker is open for ${serviceName}`, + { serviceName }, + ); + } + + /** The request was rate limited. */ + static rateLimited(retryAfterMs?: number): KernelApiError { + const suffix = + retryAfterMs !== undefined ? ` Retry after ${retryAfterMs}ms` : ''; + return new KernelApiError( + `Rate limited.${suffix}`, + KernelErrorCode.RATE_LIMITED, + retryAfterMs !== undefined ? { retryAfterMs } : {}, + ); + } + + /** The referenced browser session does not exist. */ + static sessionNotFound(sessionId: string): KernelApiError { + return new KernelApiError( + `Browser session not found: ${sessionId}`, + KernelErrorCode.SESSION_NOT_FOUND, + { sessionId }, + ); + } + + /** The referenced browser pool has no available capacity. */ + static poolExhausted(poolId: string): KernelApiError { + return new KernelApiError( + `Browser pool exhausted: ${poolId}`, + KernelErrorCode.POOL_EXHAUSTED, + { poolId }, + ); + } + + /** The operation exceeded its time budget. */ + static timeout(durationMs: number): KernelApiError { + return new KernelApiError( + `Operation timed out after ${durationMs}ms`, + KernelErrorCode.TIMEOUT, + { durationMs }, + ); + } + + toJSON(): Record { + return { + name: this.name, + message: this.message, + code: this.code, + context: this.context, + }; + } +} + +/** Raised when the circuit breaker is open and requests are shed fast. */ +export class CircuitOpenError extends KernelApiError { + constructor( + message = 'Circuit breaker is open', + context: Record = {}, + ) { + super(message, KernelErrorCode.CIRCUIT_OPEN, context); + this.name = 'CircuitOpenError'; + } +} + +/** Type guard for {@link KernelApiError} (and its subclasses). */ +export function isKernelApiError(error: unknown): error is KernelApiError { + return error instanceof KernelApiError; +} diff --git a/sdk-typescript/packages/kernel/src/index.ts b/sdk-typescript/packages/kernel/src/index.ts new file mode 100644 index 0000000..40fe8ce --- /dev/null +++ b/sdk-typescript/packages/kernel/src/index.ts @@ -0,0 +1,48 @@ +/** + * @wave-av/kernel — the ONE shared Kernel cloud-browser client for WAVE. + * + * A thin, typed wrapper over `@onkernel/sdk` (api.onkernel.com) with a + * telemetry-ready surface and clean resilience seams. Every WAVE consumer + * talks to Kernel through {@link WaveKernel}, never the raw SDK + * (law: kernel-substrate-governed). + * + * @example + * ```typescript + * import { WaveKernel, withTelemetry } from '@wave-av/kernel'; + * + * const kernel = new WaveKernel({ apiKey: process.env.KERNEL_API_KEY }); + * const browser = await kernel.browsers.create(withTelemetry({})); + * ``` + */ + +// Client +export { + WaveKernel, + DEFAULT_TIMEOUT_MS, + type WaveKernelConfig, +} from './client.js'; + +// Errors +export { + KernelApiError, + CircuitOpenError, + KernelErrorCode, + isKernelApiError, + type KernelErrorCodeValue, +} from './errors.js'; + +// Telemetry (also available at the ./telemetry subpath) +export { + WAVE_TELEMETRY_DEFAULT, + withTelemetry, + type BrowserTelemetry, + type BrowserTelemetryCategoriesConfig, + type BrowserCreateParams, +} from './telemetry.js'; + +// Resilience seams (slice-2 wiring lands later) +export type { + ResilienceHooks, + CircuitBreakerLike, + SignerLike, +} from './resilience.js'; diff --git a/sdk-typescript/packages/kernel/src/resilience.ts b/sdk-typescript/packages/kernel/src/resilience.ts new file mode 100644 index 0000000..6a17737 --- /dev/null +++ b/sdk-typescript/packages/kernel/src/resilience.ts @@ -0,0 +1,55 @@ +/** + * Resilience seams — SLICE 1 provides interfaces only, no implementation. + * + * These are the clean boundaries the WaveKernel constructor accepts so that + * slice 2 can drop in the real circuit breaker + signing without changing the + * public surface. Everything here is optional and inert in slice 1: the client + * typechecks and runs without any of it wired up. + * + * TODO(slice-2): port from WAVE's internal Kernel client — + * - opossum circuit breaker (errorThresholdPercentage: 50, resetTimeout: + * 30000, volumeThreshold: 5) + * - Sentry capture (tags: { service: 'kernel' }) + * - WebBotAuth Ed25519 request signing (RFC 9421, fail-open) + */ + +/** + * Minimal circuit-breaker contract. Slice 2 will back this with opossum + * (errorThresholdPercentage: 50, resetTimeout: 30000, volumeThreshold: 5). + */ +export interface CircuitBreakerLike { + /** Run `action` through the breaker; rejects fast with a circuit-open error while open. */ + fire(action: () => Promise): Promise; + /** Whether the breaker is currently open (shedding requests). */ + readonly opened: boolean; +} + +/** + * WebBotAuth (RFC 9421 HTTP Message Signatures) signer contract. Slice 2 will + * back this with an Ed25519 signer that FAILS OPEN — a signing failure must + * never block a request. + */ +export interface SignerLike { + /** + * Produce RFC-9421 signature headers for an outbound request. Implementations + * MUST fail open (return `{}`) rather than throw on signing failure. + */ + sign(input: { + method: string; + url: string; + headers: Record; + }): Promise>; +} + +/** + * Optional resilience wiring accepted by the WaveKernel constructor. Inert in + * slice 1; every field is optional. + */ +export interface ResilienceHooks { + /** Circuit breaker guarding Kernel calls. */ + breaker?: CircuitBreakerLike; + /** WebBotAuth request signer (fail-open). */ + signer?: SignerLike; + /** Capture sink for errors (e.g. Sentry), tagged `service: 'kernel'` in slice 2. */ + captureError?: (error: unknown, context?: Record) => void; +} diff --git a/sdk-typescript/packages/kernel/src/telemetry.ts b/sdk-typescript/packages/kernel/src/telemetry.ts new file mode 100644 index 0000000..53df0da --- /dev/null +++ b/sdk-typescript/packages/kernel/src/telemetry.ts @@ -0,0 +1,68 @@ +/** + * Telemetry helpers for Kernel browser sessions — the Phase C foundation. + * + * Kernel exposes per-category CDP telemetry on `browsers.create`. The + * operational categories (control / connection / system / captcha) are ON by + * default whenever telemetry is enabled; the CDP categories (console / network + * / page / interaction) and `screenshot` are OFF by default and must be opted + * into. We default WAVE sessions to console + network + page — enough to debug + * agent runs — and deliberately leave `screenshot` off (high-volume) and never + * enable capture of credential fields. + * + * Typed against the SDK's exported types (`@onkernel/sdk`), so the shape stays + * in lockstep with the vendored surface. + */ +import type { Kernel } from '@onkernel/sdk'; + +/** The `telemetry` block accepted by `browsers.create`. */ +export type BrowserTelemetry = Kernel.BrowserCreateParams.Telemetry; + +/** + * The SDK's per-category CDP telemetry config. Derived from the create-params + * telemetry shape — the SDK exports this under the `Browsers` namespace rather + * than at the `Kernel` root, so we take it from the surface we already type. + */ +export type BrowserTelemetryCategoriesConfig = NonNullable< + BrowserTelemetry['browser'] +>; + +/** Params object accepted by `browsers.create`. */ +export type BrowserCreateParams = Kernel.BrowserCreateParams; + +/** + * WAVE's default browser telemetry categories: console + network + page ON. + * `screenshot` is intentionally omitted (off by default — high volume). + */ +export const WAVE_TELEMETRY_DEFAULT: BrowserTelemetryCategoriesConfig = { + console: { enabled: true }, + network: { enabled: true }, + page: { enabled: true }, +}; + +/** + * Merge WAVE's telemetry defaults into a `browsers.create` params object. + * + * @param createParams The params you would pass to `kernel.browsers.create`. + * @param categories Optional category overrides. When omitted, + * {@link WAVE_TELEMETRY_DEFAULT} (console + network + page) + * is used. Provided categories layer onto the SDK default + * operational set server-side. + * @returns A new params object with `telemetry: { enabled: true, browser }` set. + * + * @example + * ```typescript + * await kernel.browsers.create(withTelemetry({ headless: true })); + * ``` + */ +export function withTelemetry( + createParams: T, + categories: BrowserTelemetryCategoriesConfig = WAVE_TELEMETRY_DEFAULT, +): T & { telemetry: BrowserTelemetry } { + return { + ...createParams, + telemetry: { + enabled: true, + browser: { ...categories }, + }, + }; +} diff --git a/sdk-typescript/packages/kernel/tsconfig.json b/sdk-typescript/packages/kernel/tsconfig.json new file mode 100644 index 0000000..98ba8c2 --- /dev/null +++ b/sdk-typescript/packages/kernel/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules", "**/*.test.ts"] +} diff --git a/sdk-typescript/pnpm-lock.yaml b/sdk-typescript/pnpm-lock.yaml index 707cab6..f3be9af 100644 --- a/sdk-typescript/pnpm-lock.yaml +++ b/sdk-typescript/pnpm-lock.yaml @@ -492,6 +492,25 @@ importers: specifier: 3.25.76 version: 3.25.76 + packages/kernel: + dependencies: + '@onkernel/sdk': + specifier: ^0.78.0 + version: 0.78.0 + devDependencies: + '@types/node': + specifier: ^25.0.3 + version: 25.9.1 + tsup: + specifier: ^8.0.0 + version: 8.5.1(postcss@8.5.15)(typescript@5.9.3) + typescript: + specifier: ^5.9.0 + version: 5.9.3 + vitest: + specifier: ^4.1.4 + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)) + packages/marketplace: dependencies: '@wave-av/core': @@ -1386,6 +1405,9 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@onkernel/sdk@0.78.0': + resolution: {integrity: sha512-VrGEDcuSwO6AKe6oYTNaQsAHnOAVeqehmStDTM0EFd3u8+WhITYxhU+jNFq+9yEt0N/nrn2COsk/ThQ+dxWBlw==} + '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} @@ -1428,42 +1450,36 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.2': resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.2': resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.2': resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.2': resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.2': resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.2': resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} @@ -1525,79 +1541,66 @@ packages: resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.4': resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.4': resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.4': resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.4': resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.4': resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.4': resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.4': resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.4': resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.4': resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.4': resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.4': resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.4': resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.4': resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} @@ -2003,28 +2006,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -2604,6 +2603,8 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@onkernel/sdk@0.78.0': {} + '@opentelemetry/api@1.9.1': {} '@oxc-project/types@0.132.0': {}