jh-web-gateway 1.0.0
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 +182 -0
- package/dist/cli.js +2469 -0
- package/dist/cli.js.map +1 -0
- package/package.json +43 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/setup.ts","../src/infra/chrome-cdp.ts","../src/infra/config.ts","../src/core/auth-capture.ts","../src/infra/gateway-auth.ts","../src/infra/types.ts","../src/server.ts","../src/routes/models.ts","../src/routes/health.ts","../src/routes/chat-completions.ts","../src/core/message-builder.ts","../src/core/client.ts","../src/core/stream-translator.ts","../src/core/tool-parser.ts","../src/infra/logger.ts","../src/core/request-queue.ts","../src/core/page-pool.ts","../src/cli/serve.ts","../src/cli/auth.ts","../src/cli/config.ts","../src/cli/status.ts","../src/cli/logs.ts","../src/cli/start.ts","../src/infra/chrome-manager.ts","../src/core/token-refresher.ts","../src/core/reauth-lock.ts","../src/cli.ts"],"sourcesContent":["/**\n * Interactive TUI setup wizard.\n * Steps: Chrome detection → JH auth → port selection → verify\n */\nimport * as p from \"@clack/prompts\";\nimport { getChromeWebSocketUrl } from \"../infra/chrome-cdp.js\";\nimport { captureCredentials, getTokenExpiry } from \"../core/auth-capture.js\";\nimport { loadConfig, updateConfig } from \"../infra/config.js\";\nimport { generateApiKey } from \"../infra/gateway-auth.js\";\nimport { MODEL_ENDPOINT_MAP } from \"../infra/types.js\";\n\nconst COMMON_CDP_PORTS = [9222, 9223];\n\n/** Format a UNIX timestamp as a human-readable date string. */\nfunction formatExpiry(exp: number): string {\n if (exp === 0) {return \"unknown\";}\n return new Date(exp * 1000).toLocaleString();\n}\n\n/** Step 1: Detect Chrome on common ports or prompt for custom URL. */\nasync function detectChrome(): Promise<string> {\n p.log.step(\"Step 1: Chrome detection\");\n\n for (const port of COMMON_CDP_PORTS) {\n const url = `http://127.0.0.1:${port}`;\n const spinner = p.spinner();\n spinner.start(`Checking ${url}…`);\n try {\n await getChromeWebSocketUrl(url, 2000);\n spinner.stop(`Chrome found at ${url}`);\n return url;\n } catch {\n spinner.stop(`Not found at ${url}`);\n }\n }\n\n // Prompt for custom CDP URL\n const custom = await p.text({\n message: \"Enter your Chrome CDP URL (e.g. http://127.0.0.1:9224):\",\n placeholder: \"http://127.0.0.1:9222\",\n validate(value) {\n if (!value.startsWith(\"http://\") && !value.startsWith(\"https://\")) {\n return \"Must be a valid URL starting with http:// or https://\";\n }\n },\n });\n\n if (p.isCancel(custom)) {\n p.cancel(\"Setup cancelled.\");\n process.exit(0);\n }\n\n // Verify the custom URL\n const spinner = p.spinner();\n spinner.start(`Checking ${custom}…`);\n try {\n await getChromeWebSocketUrl(custom, 5000);\n spinner.stop(`Chrome found at ${custom}`);\n return custom;\n } catch (err) {\n spinner.stop(`Failed to connect to ${custom}`);\n throw err;\n }\n}\n\n/** Step 2: Capture JH credentials via CDP. */\nasync function captureAuth(cdpUrl: string): Promise<number> {\n p.log.step(\"Step 2: JH authentication\");\n p.log.info(\n \"Opening chat.ai.jh.edu in your browser. Send any message to trigger auth capture…\"\n );\n\n const spinner = p.spinner();\n spinner.start(\"Waiting for credentials (up to 120s)…\");\n\n const creds = await captureCredentials(cdpUrl, 120_000);\n const expiry = getTokenExpiry(creds.bearerToken);\n spinner.stop(`Credentials captured! Token expires: ${formatExpiry(expiry)}`);\n\n return expiry;\n}\n\n/** Step 3: Port selection. */\nasync function selectPort(): Promise<number> {\n p.log.step(\"Step 3: Port selection\");\n\n const input = await p.text({\n message: \"Gateway port:\",\n placeholder: \"8741\",\n defaultValue: \"8741\",\n validate(value) {\n const n = Number(value);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n return \"Must be a valid port number (1–65535)\";\n }\n },\n });\n\n if (p.isCancel(input)) {\n p.cancel(\"Setup cancelled.\");\n process.exit(0);\n }\n\n return Number(input);\n}\n\n/** Step 4: Verify by listing available models. */\nasync function verifyConnection(): Promise<void> {\n p.log.step(\"Step 4: Verification\");\n const models = Object.keys(MODEL_ENDPOINT_MAP);\n p.log.success(`Available models: ${models.join(\", \")}`);\n}\n\n/** Run the full setup wizard. */\nexport async function runSetup(): Promise<void> {\n p.intro(\"JH Web Gateway — Setup Wizard\");\n\n let cdpUrl: string | undefined;\n let port = 8741;\n\n // Step 1: Chrome detection (with retry)\n while (true) {\n try {\n cdpUrl = await detectChrome();\n break;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n p.log.error(`Chrome detection failed: ${msg}`);\n const retry = await p.confirm({ message: \"Retry Chrome detection?\" });\n if (p.isCancel(retry) || !retry) {\n p.cancel(\"Setup cancelled.\");\n process.exit(1);\n }\n }\n }\n\n // Save CDP URL\n await updateConfig({ cdpUrl });\n\n // Step 2: Auth capture (with retry)\n while (true) {\n try {\n await captureAuth(cdpUrl);\n break;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n p.log.error(`Auth capture failed: ${msg}`);\n const retry = await p.confirm({ message: \"Retry authentication?\" });\n if (p.isCancel(retry) || !retry) {\n p.cancel(\"Setup cancelled.\");\n process.exit(1);\n }\n }\n }\n\n // Step 3: Port selection\n try {\n port = await selectPort();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n p.log.error(`Port selection failed: ${msg}`);\n p.cancel(\"Setup cancelled.\");\n process.exit(1);\n }\n\n // Generate API key and persist final config\n const apiKey = generateApiKey();\n await updateConfig({\n port,\n auth: { mode: \"bearer\", token: apiKey },\n });\n\n // Step 4: Verify\n await verifyConnection();\n\n // Load final config to confirm\n const config = await loadConfig();\n const baseUrl = `http://127.0.0.1:${config.port}`;\n\n p.outro(\"Setup complete!\");\n\n console.log(\"\\n Base URL: \" + baseUrl);\n console.log(\" API Key: \" + apiKey);\n console.log(\"\\n Test with curl:\");\n console.log(\n ` curl ${baseUrl}/v1/models -H \"Authorization: Bearer ${apiKey}\"\\n`\n );\n}\n","import { chromium } from \"playwright-core\";\nimport type { Browser, Page } from \"playwright-core\";\n\nconst DEFAULT_TIMEOUT_MS = 5000;\nconst JH_URL = \"https://chat.ai.jh.edu\";\n\n/**\n * Fetch /json/version from Chrome's CDP HTTP endpoint and extract webSocketDebuggerUrl.\n * Throws a descriptive error if Chrome is unreachable or the response is malformed.\n */\nexport async function getChromeWebSocketUrl(\n cdpUrl: string,\n timeoutMs: number = DEFAULT_TIMEOUT_MS\n): Promise<string> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let raw: string;\n try {\n const res = await fetch(`${cdpUrl}/json/version`, {\n signal: controller.signal,\n });\n raw = await res.text();\n } catch {\n throw new Error(\n `Chrome is not running or remote debugging is not enabled at ${cdpUrl}`\n );\n } finally {\n clearTimeout(timer);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Unexpected CDP response format: ${raw}`);\n }\n\n const wsUrl =\n parsed !== null &&\n typeof parsed === \"object\" &&\n \"webSocketDebuggerUrl\" in parsed\n ? (parsed as Record<string, unknown>).webSocketDebuggerUrl\n : undefined;\n\n if (typeof wsUrl !== \"string\" || wsUrl.length === 0) {\n throw new Error(`Unexpected CDP response format: ${raw}`);\n }\n\n return wsUrl;\n}\n\n/**\n * Connect to Chrome via Playwright CDP.\n * Returns the Browser and the first existing page (or a new one).\n */\nexport async function connectToChrome(\n cdpUrl: string\n): Promise<{ browser: Browser; page: Page }> {\n const wsUrl = await getChromeWebSocketUrl(cdpUrl);\n const browser = await chromium.connectOverCDP(wsUrl);\n\n const contexts = browser.contexts();\n const existingPage =\n contexts.length > 0 ? contexts[0].pages()[0] : undefined;\n\n const page =\n existingPage ??\n (await (contexts.length > 0\n ? contexts[0].newPage()\n : (await browser.newContext()).newPage()));\n\n return { browser, page };\n}\n\n/**\n * Find an existing chat.ai.jh.edu page across all contexts, or open a new one.\n */\nexport async function findOrOpenJhPage(browser: Browser): Promise<Page> {\n for (const context of browser.contexts()) {\n for (const page of context.pages()) {\n if (page.url().includes(\"chat.ai.jh.edu\")) {\n return page;\n }\n }\n }\n\n // No existing JH page found — open one\n const contexts = browser.contexts();\n const context =\n contexts.length > 0 ? contexts[0] : await browser.newContext();\n const page = await context.newPage();\n await page.goto(JH_URL);\n return page;\n}\n","import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { GatewayConfig } from \"./types.js\";\n\n// ── Paths ─────────────────────────────────────────────────────────────────────\n\nexport function getConfigPath(): string {\n return join(homedir(), \".jh-gateway\", \"config.json\");\n}\n\n// ── Defaults ──────────────────────────────────────────────────────────────────\n\nexport function getDefaultConfig(): GatewayConfig {\n return {\n cdpUrl: \"http://127.0.0.1:9222\",\n port: 8741,\n defaultModel: \"claude-opus-4.5\",\n defaultEndpoint: \"AnthropicClaude\",\n credentials: null,\n auth: { mode: \"none\", token: null },\n maxQueueWaitMs: 120000,\n };\n}\n\n// ── Validation ────────────────────────────────────────────────────────────────\n\nexport function validateConfig(raw: unknown): GatewayConfig {\n if (typeof raw !== \"object\" || raw === null) {\n throw new Error(\"Config validation error: config must be a JSON object\");\n }\n\n const c = raw as Record<string, unknown>;\n\n // cdpUrl\n if (typeof c.cdpUrl !== \"string\" || !/^https?:\\/\\//.test(c.cdpUrl)) {\n throw new Error(\n \"Config validation error: cdpUrl must be a valid URL starting with http:// or https://\"\n );\n }\n\n // port\n if (\n typeof c.port !== \"number\" ||\n !Number.isInteger(c.port) ||\n c.port < 1 ||\n c.port > 65535\n ) {\n throw new Error(\n \"Config validation error: port must be between 1 and 65535\"\n );\n }\n\n // defaultModel\n if (typeof c.defaultModel !== \"string\" || c.defaultModel.trim() === \"\") {\n throw new Error(\n \"Config validation error: defaultModel must be a non-empty string\"\n );\n }\n\n // defaultEndpoint\n if (\n typeof c.defaultEndpoint !== \"string\" ||\n c.defaultEndpoint.trim() === \"\"\n ) {\n throw new Error(\n \"Config validation error: defaultEndpoint must be a non-empty string\"\n );\n }\n\n // credentials\n if (c.credentials !== null && c.credentials !== undefined) {\n if (typeof c.credentials !== \"object\" || Array.isArray(c.credentials)) {\n throw new Error(\n \"Config validation error: credentials must be null or an object with bearerToken, cookie, and userAgent strings\"\n );\n }\n const creds = c.credentials as Record<string, unknown>;\n for (const field of [\"bearerToken\", \"cookie\", \"userAgent\"] as const) {\n if (typeof creds[field] !== \"string\") {\n throw new Error(\n `Config validation error: credentials.${field} must be a string`\n );\n }\n }\n }\n\n // auth\n if (typeof c.auth !== \"object\" || c.auth === null || Array.isArray(c.auth)) {\n throw new Error(\n \"Config validation error: auth must be an object with mode and token fields\"\n );\n }\n const auth = c.auth as Record<string, unknown>;\n if (\n auth.mode !== \"none\" &&\n auth.mode !== \"bearer\" &&\n auth.mode !== \"basic\"\n ) {\n throw new Error(\n 'Config validation error: auth.mode must be \"none\", \"bearer\", or \"basic\"'\n );\n }\n if (auth.token !== null && typeof auth.token !== \"string\") {\n throw new Error(\n \"Config validation error: auth.token must be null or a string\"\n );\n }\n\n // maxQueueWaitMs\n if (typeof c.maxQueueWaitMs !== \"number\" || c.maxQueueWaitMs <= 0) {\n throw new Error(\n \"Config validation error: maxQueueWaitMs must be a positive number\"\n );\n }\n\n return {\n cdpUrl: c.cdpUrl,\n port: c.port,\n defaultModel: c.defaultModel,\n defaultEndpoint: c.defaultEndpoint,\n credentials:\n c.credentials != null\n ? {\n bearerToken: (c.credentials as Record<string, unknown>).bearerToken as string,\n cookie: (c.credentials as Record<string, unknown>).cookie as string,\n userAgent: (c.credentials as Record<string, unknown>).userAgent as string,\n expiresAt: typeof (c.credentials as Record<string, unknown>).expiresAt === \"number\"\n ? ((c.credentials as Record<string, unknown>).expiresAt as number)\n : 0,\n }\n : null,\n auth: {\n mode: auth.mode,\n token: (auth.token) ?? null,\n },\n maxQueueWaitMs: c.maxQueueWaitMs,\n };\n}\n\n// ── I/O ───────────────────────────────────────────────────────────────────────\n\nexport async function loadConfig(): Promise<GatewayConfig> {\n const configPath = getConfigPath();\n const configDir = join(homedir(), \".jh-gateway\");\n\n let raw: string;\n try {\n raw = await readFile(configPath, \"utf8\");\n } catch (err: unknown) {\n // File or directory missing — create with defaults\n if (isNodeError(err) && (err.code === \"ENOENT\" || err.code === \"ENOTDIR\")) {\n const defaults = getDefaultConfig();\n await mkdir(configDir, { recursive: true });\n await writeFile(configPath, JSON.stringify(defaults, null, 2), \"utf8\");\n return defaults;\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n `Config validation error: config file at ${configPath} contains malformed JSON`\n );\n }\n\n return validateConfig(parsed);\n}\n\nexport async function saveConfig(config: GatewayConfig): Promise<void> {\n const configPath = getConfigPath();\n const configDir = join(homedir(), \".jh-gateway\");\n await mkdir(configDir, { recursive: true });\n await writeFile(configPath, JSON.stringify(config, null, 2), \"utf8\");\n}\n\nexport async function updateConfig(\n partial: Partial<GatewayConfig>\n): Promise<void> {\n const current = await loadConfig();\n\n // Deep merge for nested `auth` object; shallow merge for everything else\n const updated: GatewayConfig = { ...current, ...partial };\n if (partial.auth !== undefined) {\n updated.auth = { ...current.auth, ...partial.auth };\n }\n\n await saveConfig(updated);\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && \"code\" in err;\n}\n","import type { Browser } from \"playwright-core\";\nimport { connectToChrome, findOrOpenJhPage } from \"../infra/chrome-cdp.js\";\nimport { updateConfig } from \"../infra/config.js\";\nimport type { CapturedCredentials } from \"../infra/types.js\";\n\nconst JH_HOST = \"chat.ai.jh.edu\";\n\n/**\n * Decode the JWT `exp` claim from the middle (payload) segment.\n * Returns 0 if decoding fails for any reason.\n */\nexport function getTokenExpiry(token: string): number {\n try {\n const parts = token.split(\".\");\n if (parts.length < 2) return 0;\n\n // Base64url → base64 → decode\n const payload = parts[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n\n const json = Buffer.from(payload, \"base64\").toString(\"utf8\");\n const parsed = JSON.parse(json) as Record<string, unknown>;\n\n const exp = parsed[\"exp\"];\n if (typeof exp !== \"number\") return 0;\n return exp;\n } catch {\n return 0;\n }\n}\n\n/**\n * Connect to Chrome via CDP, intercept outgoing requests to chat.ai.jh.edu,\n * capture the Bearer token, cookies, and user agent, then persist to config.\n *\n * Throws a descriptive message if no token is captured within `timeoutMs`.\n */\nexport async function captureCredentials(\n cdpUrl: string,\n timeoutMs: number = 120_000\n): Promise<CapturedCredentials> {\n const { browser, page: _initialPage } = await connectToChrome(cdpUrl);\n\n // Find or open the JH page — we want to intercept on that specific page.\n const page = await findOrOpenJhPage(browser as Browser);\n\n return new Promise<CapturedCredentials>((resolve, reject) => {\n let settled = false;\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n reject(\n new Error(\n `Credential capture timed out after ${timeoutMs / 1000}s. ` +\n \"Please log in to chat.ai.jh.edu and send a message to trigger authentication.\"\n )\n );\n }, timeoutMs);\n\n // Intercept all requests on the page and look for the Authorization header.\n page\n .route(\"**/*\", async (route) => {\n // Always continue the request so we don't block the browser.\n const request = route.request();\n const headers = await request.headers();\n\n const authHeader =\n headers[\"authorization\"] ?? headers[\"Authorization\"] ?? \"\";\n\n if (\n !settled &&\n authHeader.startsWith(\"Bearer \") &&\n request.url().includes(JH_HOST)\n ) {\n settled = true;\n clearTimeout(timer);\n\n try {\n const bearerToken = authHeader.slice(\"Bearer \".length).trim();\n\n // Collect cookies from the browser context.\n const rawCookies = await page.context().cookies();\n const cookie = rawCookies\n .map((c) => `${c.name}=${c.value}`)\n .join(\"; \");\n\n // Capture the user agent from the page.\n const userAgent = await page.evaluate(\n () => navigator.userAgent\n );\n\n const expiresAt = getTokenExpiry(bearerToken);\n\n const captured: CapturedCredentials = {\n bearerToken,\n cookie,\n userAgent,\n expiresAt,\n };\n\n // Persist to config store (fire-and-forget; errors surface via reject).\n await updateConfig({ credentials: captured });\n\n resolve(captured);\n } catch (err) {\n reject(err);\n }\n }\n\n // Always continue so the browser request is not blocked.\n await route.continue();\n })\n .catch((err: unknown) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n reject(err);\n }\n });\n });\n}\n","import { randomBytes } from \"node:crypto\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { GatewayConfig } from \"./types.js\";\n\n/** Generate a random gateway API key prefixed with \"jh-local-\". */\nexport function generateApiKey(): string {\n return `jh-local-${randomBytes(16).toString(\"hex\")}`;\n}\n\n/**\n * Hono middleware that validates the Authorization header against the config.\n *\n * - mode \"none\": all requests pass through\n * - mode \"bearer\": requires `Authorization: Bearer <token>`\n * - mode \"basic\": requires `Authorization: Basic <base64(\"gateway:<token>\")>`\n */\nexport function authMiddleware(config: GatewayConfig): MiddlewareHandler {\n return async (c, next) => {\n const { mode, token } = config.auth;\n\n if (mode === \"none\") {\n return next();\n }\n\n const authHeader = c.req.header(\"authorization\");\n if (!authHeader) {\n return c.json(\n {\n error: {\n message: \"Missing Authorization header\",\n type: \"authentication_error\",\n code: \"missing_auth\",\n param: null,\n },\n },\n 401,\n );\n }\n\n if (mode === \"bearer\") {\n const expected = `Bearer ${token}`;\n if (authHeader !== expected) {\n return c.json(\n {\n error: {\n message: \"Invalid bearer token\",\n type: \"authentication_error\",\n code: \"invalid_auth\",\n param: null,\n },\n },\n 401,\n );\n }\n } else if (mode === \"basic\") {\n // Expect Basic base64(\"gateway:<token>\")\n const expected = `Basic ${Buffer.from(`gateway:${token}`).toString(\"base64\")}`;\n if (authHeader !== expected) {\n return c.json(\n {\n error: {\n message: \"Invalid basic credentials\",\n type: \"authentication_error\",\n code: \"invalid_auth\",\n param: null,\n },\n },\n 401,\n );\n }\n }\n\n return next();\n };\n}\n","// Shared type definitions for jh-web-gateway\n\n// ── Credentials & Config ──────────────────────────────────────────────────────\n\nexport interface GatewayCredentials {\n bearerToken: string;\n cookie: string;\n userAgent: string;\n /** JWT exp claim (unix seconds), optional for backward compatibility */\n expiresAt?: number;\n}\n\nexport interface GatewayConfig {\n /** default: \"http://127.0.0.1:9222\" */\n cdpUrl: string;\n /** default: 8741 */\n port: number;\n /** default: \"claude-opus-4.5\" */\n defaultModel: string;\n /** default: \"AnthropicClaude\" */\n defaultEndpoint: string;\n credentials: GatewayCredentials | null;\n auth: {\n mode: \"none\" | \"bearer\" | \"basic\";\n token: string | null;\n };\n /** default: 120000 */\n maxQueueWaitMs: number;\n}\n\n// ── OpenAI Wire Types ─────────────────────────────────────────────────────────\n\nexport interface ToolCall {\n id: string;\n type: \"function\";\n index?: number;\n function: {\n name: string;\n arguments: string;\n };\n}\n\nexport interface ToolCallDelta {\n index: number;\n id?: string;\n type?: \"function\";\n function?: {\n name?: string;\n arguments?: string;\n };\n}\n\nexport interface OpenAIMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string | null;\n tool_call_id?: string;\n tool_calls?: ToolCall[];\n}\n\nexport interface OpenAITool {\n type: \"function\";\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\nexport interface OpenAIChunk {\n id: string;\n object: \"chat.completion.chunk\";\n created: number;\n model: string;\n choices: [{\n index: number;\n delta: {\n role?: string;\n content?: string;\n tool_calls?: ToolCallDelta[];\n };\n finish_reason: string | null;\n }];\n}\n\nexport interface OpenAICompletion {\n id: string;\n object: \"chat.completion\";\n created: number;\n model: string;\n choices: [{\n index: number;\n message: {\n role: string;\n content: string | null;\n tool_calls?: ToolCall[];\n };\n finish_reason: string;\n }];\n usage: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n };\n}\n\nexport interface OpenAIErrorResponse {\n error: {\n message: string;\n type: string;\n code: string;\n param: string | null;\n };\n}\n\n// ── Tool Parsing ──────────────────────────────────────────────────────────────\n\nexport interface ParsedToolCall {\n id: string;\n name: string;\n /** valid JSON string */\n arguments: string;\n}\n\nexport interface ParsedContent {\n /** text outside tags */\n text: string;\n toolCalls: ParsedToolCall[];\n /** extracted think content, stripped from text */\n thinking: string | null;\n}\n\n// ── Logging ───────────────────────────────────────────────────────────────────\n\nexport interface RequestLogEntry {\n timestamp: string;\n method: string;\n path: string;\n model: string | null;\n statusCode: number;\n latencyMs: number;\n estimatedTokens: {\n prompt: number;\n completion: number;\n };\n}\n\n// ── Auth Capture ──────────────────────────────────────────────────────────────\n\nexport interface CapturedCredentials {\n bearerToken: string;\n cookie: string;\n userAgent: string;\n /** decoded from JWT exp claim */\n expiresAt: number;\n}\n\n// ── Browser Client ────────────────────────────────────────────────────────────\n\nexport interface ChatRequest {\n model: string;\n /** flattened from message-builder */\n prompt: string;\n conversationId?: string;\n parentMessageId?: string;\n}\n\nexport interface ChatResponse {\n rawSseText: string;\n conversationId: string;\n parentMessageId: string;\n}\n\n// ── Message Builder ───────────────────────────────────────────────────────────\n\nexport interface BuiltPrompt {\n /** flattened conversation text */\n prompt: string;\n /** extracted system message */\n systemPrompt?: string;\n}\n\n// ── Model → Endpoint Mapping ──────────────────────────────────────────────────\n\n/** Maps model IDs to JH platform endpoint paths */\nexport const MODEL_ENDPOINT_MAP: Record<string, string> = {\n \"claude-opus-4.5\": \"AnthropicClaude\",\n \"claude-sonnet-4.5\": \"AnthropicClaude\",\n \"claude-haiku-4.5\": \"AnthropicClaude\",\n \"gpt-4.1\": \"OpenAI\",\n \"o3\": \"OpenAI\",\n \"o3-mini\": \"OpenAI\",\n \"gpt-5\": \"OpenAI\",\n \"gpt-5.1\": \"OpenAI\",\n \"gpt-5.2\": \"OpenAI\",\n \"llama3-3-70b-instruct\": \"Meta\",\n} as const;\n","import { Hono } from \"hono\";\nimport { serve } from \"@hono/node-server\";\nimport { modelsRouter } from \"./routes/models.js\";\nimport { healthRouter } from \"./routes/health.js\";\nimport { chatCompletionsRouter } from \"./routes/chat-completions.js\";\nimport { authMiddleware } from \"./infra/gateway-auth.js\";\nimport { Logger } from \"./infra/logger.js\";\nimport type { PagePool } from \"./core/page-pool.js\";\nimport type { GatewayConfig, GatewayCredentials, RequestLogEntry } from \"./infra/types.js\";\nimport type { ReauthLock } from \"./core/reauth-lock.js\";\n\nexport interface ServerDeps {\n getPool: () => PagePool | null;\n getCredentials: () => GatewayConfig[\"credentials\"];\n reauthLock?: ReauthLock;\n setCredentials?: (creds: GatewayCredentials) => void;\n}\n\nexport function createServer(config: GatewayConfig, deps?: ServerDeps): Hono {\n const app = new Hono();\n const startTime = Date.now();\n const logger = new Logger();\n\n // Auth middleware on /v1/* routes\n app.use(\"/v1/*\", authMiddleware(config));\n\n // Request logging middleware — logs every API request with latency + token estimates\n app.use(\"*\", async (c, next) => {\n const start = Date.now();\n await next();\n const latencyMs = Date.now() - start;\n\n // Extract model from request body for chat completions (best-effort)\n let model: string | null = null;\n if (c.req.method === \"POST\" && c.req.path.includes(\"chat/completions\")) {\n try {\n // Body already consumed by route; read model from response or stored value\n const bodyText = await c.req.raw.clone().text();\n const parsed = JSON.parse(bodyText);\n model = typeof parsed?.model === \"string\" ? parsed.model : null;\n } catch {\n // Body may not be re-readable; that's fine\n }\n }\n\n // Approximate token estimates from response size (1 token ≈ 4 chars)\n const resBody = c.res?.headers?.get(\"content-length\");\n const resSize = resBody ? parseInt(resBody, 10) : 0;\n\n const entry: RequestLogEntry = {\n timestamp: new Date().toISOString(),\n method: c.req.method,\n path: c.req.path,\n model,\n statusCode: c.res.status,\n latencyMs,\n estimatedTokens: {\n prompt: 0,\n completion: Math.max(0, Math.ceil(resSize / 4)),\n },\n };\n\n // Fire-and-forget — don't block the response\n logger.log(entry).catch(() => { });\n });\n\n // Mount routes\n app.route(\"/v1/models\", modelsRouter(config));\n app.route(\"/health\", healthRouter(config, startTime));\n\n if (deps) {\n app.route(\n \"/v1/chat/completions\",\n chatCompletionsRouter(config, {\n getPool: deps.getPool,\n getCredentials: deps.getCredentials,\n reauthLock: deps.reauthLock,\n setCredentials: deps.setCredentials,\n }),\n );\n }\n\n // Global error handler — OpenAI error format\n app.onError((err, c) => {\n return c.json(\n {\n error: {\n message: err.message || \"Internal server error\",\n type: \"server_error\",\n code: \"internal_error\",\n param: null,\n },\n },\n 500,\n );\n });\n\n // 404 handler — OpenAI error format\n app.notFound((c) => {\n return c.json(\n {\n error: {\n message: `Route ${c.req.method} ${c.req.path} not found`,\n type: \"invalid_request_error\",\n code: \"route_not_found\",\n param: null,\n },\n },\n 404,\n );\n });\n\n return app;\n}\n\nexport interface ServerHandle {\n close: () => Promise<void>;\n}\n\nexport async function startServer(\n config: GatewayConfig,\n deps: ServerDeps & { browser?: { close(): Promise<void> } },\n): Promise<ServerHandle> {\n const hostname = \"127.0.0.1\";\n const app = createServer(config, deps);\n const port = config.port;\n\n const server = serve({\n fetch: app.fetch,\n port,\n hostname,\n });\n\n console.log(`JH Web Gateway listening on http://${hostname}:${port}`);\n\n let shuttingDown = false;\n const DRAIN_TIMEOUT_MS = 10_000;\n\n const shutdown = async () => {\n if (shuttingDown) { return; }\n shuttingDown = true;\n console.log(\"\\nShutting down gracefully...\");\n\n // Stop accepting new connections and drain in-flight requests\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n console.log(\"Drain timeout reached, forcing close.\");\n resolve();\n }, DRAIN_TIMEOUT_MS);\n\n server.close(() => {\n clearTimeout(timer);\n resolve();\n });\n });\n\n // Close Chrome CDP connection if available\n if (deps.browser) {\n try {\n await deps.browser.close();\n } catch {\n // Browser may already be disconnected\n }\n }\n\n console.log(\"Shutdown complete.\");\n };\n\n // Register signal handlers\n process.on(\"SIGINT\", async () => {\n await shutdown();\n process.exit(0);\n });\n process.on(\"SIGTERM\", async () => {\n await shutdown();\n process.exit(0);\n });\n\n return { close: shutdown };\n}\n","import { Hono } from \"hono\";\nimport { MODEL_ENDPOINT_MAP } from \"../infra/types.js\";\nimport type { GatewayConfig } from \"../infra/types.js\";\n\nconst MODEL_LIST = Object.keys(MODEL_ENDPOINT_MAP).map((id) => ({\n id,\n object: \"model\" as const,\n created: 1700000000,\n owned_by: \"jh-web\",\n}));\n\nconst MODEL_SET = new Set(Object.keys(MODEL_ENDPOINT_MAP));\n\nexport function modelsRouter(_config: GatewayConfig): Hono {\n const app = new Hono();\n\n app.get(\"/\", (c) => {\n return c.json({ object: \"list\", data: MODEL_LIST });\n });\n\n app.get(\"/:id\", (c) => {\n const id = c.req.param(\"id\");\n if (!MODEL_SET.has(id)) {\n return c.json(\n {\n error: {\n message: `Model '${id}' not found`,\n type: \"invalid_request_error\",\n code: \"model_not_found\",\n param: \"id\",\n },\n },\n 404\n );\n }\n return c.json({ id, object: \"model\", created: 1700000000, owned_by: \"jh-web\" });\n });\n\n return app;\n}\n","import { Hono } from \"hono\";\nimport { getTokenExpiry } from \"../core/auth-capture.js\";\nimport type { GatewayConfig } from \"../infra/types.js\";\n\nexport function healthRouter(config: GatewayConfig, startTime: number): Hono {\n const app = new Hono();\n\n app.get(\"/\", (c) => {\n const uptime = (Date.now() - startTime) / 1000;\n\n const tokenExpiry =\n config.credentials?.bearerToken\n ? getTokenExpiry(config.credentials.bearerToken) || null\n : null;\n\n const tokenExpired =\n tokenExpiry !== null && Date.now() / 1000 > tokenExpiry;\n\n return c.json({\n status: \"ok\",\n uptime,\n tokenExpiry,\n tokenExpired,\n cdpUrl: config.cdpUrl,\n });\n });\n\n return app;\n}\n","import { Hono } from \"hono\";\nimport { stream as honoStream } from \"hono/streaming\";\nimport type { GatewayConfig, GatewayCredentials, OpenAIMessage, OpenAITool } from \"../infra/types.js\";\nimport { MODEL_ENDPOINT_MAP } from \"../infra/types.js\";\nimport { buildPrompt } from \"../core/message-builder.js\";\nimport { sendChatRequest } from \"../core/client.js\";\nimport { translateToStream, translateToCompletion } from \"../core/stream-translator.js\";\nimport type { PagePool } from \"../core/page-pool.js\";\nimport type { ReauthLock } from \"../core/reauth-lock.js\";\nimport { captureCredentials } from \"../core/auth-capture.js\";\nimport { randomBytes } from \"node:crypto\";\n\nconst MODEL_SET = new Set(Object.keys(MODEL_ENDPOINT_MAP));\n\ninterface ChatCompletionsDeps {\n getPool: () => PagePool | null;\n getCredentials: () => GatewayConfig[\"credentials\"];\n reauthLock?: ReauthLock;\n setCredentials?: (creds: GatewayCredentials) => void;\n}\n\nexport function chatCompletionsRouter(\n _config: GatewayConfig,\n deps: ChatCompletionsDeps,\n): Hono {\n const app = new Hono();\n\n app.post(\"/\", async (c) => {\n // Parse request body\n let body: Record<string, unknown>;\n try {\n body = await c.req.json();\n } catch {\n return c.json(\n {\n error: {\n message: \"Request body must be valid JSON\",\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n param: null,\n },\n },\n 400,\n );\n }\n\n // Validate required fields\n const model = body.model;\n if (typeof model !== \"string\" || !model) {\n return c.json(\n {\n error: {\n message: \"Missing required field: model\",\n type: \"invalid_request_error\",\n code: \"missing_field\",\n param: \"model\",\n },\n },\n 400,\n );\n }\n\n if (!MODEL_SET.has(model)) {\n return c.json(\n {\n error: {\n message: `Model '${model}' is not supported. Available models: ${[...MODEL_SET].join(\", \")}`,\n type: \"invalid_request_error\",\n code: \"model_not_found\",\n param: \"model\",\n },\n },\n 400,\n );\n }\n\n const messages = body.messages;\n if (!Array.isArray(messages) || messages.length === 0) {\n return c.json(\n {\n error: {\n message: \"Missing or empty required field: messages\",\n type: \"invalid_request_error\",\n code: \"missing_field\",\n param: \"messages\",\n },\n },\n 400,\n );\n }\n\n const shouldStream = body.stream === true;\n const tools = (body.tools as OpenAITool[] | undefined) ?? undefined;\n const toolChoice = body.tool_choice as\n | string\n | { type: string; function: { name: string } }\n | undefined;\n\n // Check page pool availability\n const pool = deps.getPool();\n if (!pool) {\n return c.json(\n {\n error: {\n message: \"Chrome browser is not connected. Run `jh-gateway setup` or `jh-gateway auth`.\",\n type: \"service_unavailable\",\n code: \"chrome_disconnected\",\n param: null,\n },\n },\n 503,\n );\n }\n\n const credentials = deps.getCredentials();\n if (!credentials) {\n return c.json(\n {\n error: {\n message: \"No credentials available. Run `jh-gateway auth` to capture credentials.\",\n type: \"authentication_error\",\n code: \"no_credentials\",\n param: null,\n },\n },\n 401,\n );\n }\n\n const completionId = `chatcmpl-${randomBytes(12).toString(\"hex\")}`;\n\n // Parallelize prompt building and page acquisition — they're independent\n const [built, acquired] = await Promise.all([\n Promise.resolve(buildPrompt(messages as OpenAIMessage[], tools, toolChoice)),\n pool.acquire(),\n ]);\n const { page, queue, release } = acquired;\n const stats = pool.stats;\n console.log(`[chat] Acquired page (pool: ${stats.busy}/${stats.total} busy)`);\n\n try {\n // Enqueue and execute via browser client\n const response = await queue.enqueue(() =>\n sendChatRequest(page, credentials, {\n model,\n prompt: built.prompt,\n }),\n );\n\n if (shouldStream) {\n // Streaming SSE response\n const chunks = translateToStream(response.rawSseText, model, completionId);\n\n return honoStream(c, async (stream) => {\n c.header(\"Content-Type\", \"text/event-stream\");\n c.header(\"Cache-Control\", \"no-cache\");\n c.header(\"Connection\", \"keep-alive\");\n\n try {\n for (const chunk of chunks) {\n await stream.write(`data: ${JSON.stringify(chunk)}\\n\\n`);\n }\n } catch (streamErr: unknown) {\n // Mid-stream error — emit SSE error event before [DONE]\n const sErr = streamErr as Error;\n const errorEvent = {\n error: {\n message: sErr.message || \"An error occurred during streaming\",\n type: \"server_error\",\n code: \"stream_error\",\n param: null,\n },\n };\n await stream.write(`data: ${JSON.stringify(errorEvent)}\\n\\n`);\n } finally {\n release();\n }\n await stream.write(\"data: [DONE]\\n\\n\");\n });\n }\n\n // Non-streaming JSON response\n release();\n const completion = translateToCompletion(response.rawSseText, model, completionId);\n return c.json(completion);\n } catch (err: unknown) {\n release();\n const error = err as Error & { statusCode?: number };\n const statusCode = error.statusCode ?? 500;\n\n // ── 401 retry via ReauthLock ──────────────────────────────────────\n if (statusCode === 401 && deps.reauthLock) {\n try {\n const freshCreds = await deps.reauthLock.acquire(async () => {\n const captured = await captureCredentials(_config.cdpUrl);\n return {\n bearerToken: captured.bearerToken,\n cookie: captured.cookie,\n userAgent: captured.userAgent,\n };\n });\n\n // Update the credential holder so future requests use fresh creds\n deps.setCredentials?.(freshCreds);\n\n // Re-acquire a page and retry exactly once\n const retry = await pool.acquire();\n try {\n const retryResponse = await retry.queue.enqueue(() =>\n sendChatRequest(retry.page, freshCreds, {\n model: model as string,\n prompt: built.prompt,\n }),\n );\n\n if (shouldStream) {\n const chunks = translateToStream(retryResponse.rawSseText, model as string, completionId);\n return honoStream(c, async (stream) => {\n c.header(\"Content-Type\", \"text/event-stream\");\n c.header(\"Cache-Control\", \"no-cache\");\n c.header(\"Connection\", \"keep-alive\");\n try {\n for (const chunk of chunks) {\n await stream.write(`data: ${JSON.stringify(chunk)}\\n\\n`);\n }\n } catch (streamErr: unknown) {\n const sErr = streamErr as Error;\n const errorEvent = {\n error: {\n message: sErr.message || \"An error occurred during streaming\",\n type: \"server_error\",\n code: \"stream_error\",\n param: null,\n },\n };\n await stream.write(`data: ${JSON.stringify(errorEvent)}\\n\\n`);\n } finally {\n retry.release();\n }\n await stream.write(\"data: [DONE]\\n\\n\");\n });\n }\n\n retry.release();\n const completion = translateToCompletion(retryResponse.rawSseText, model as string, completionId);\n return c.json(completion);\n } catch {\n retry.release();\n // Retry failed — fall through to return 401\n }\n } catch {\n // Re-capture itself failed — fall through to return 401\n }\n\n // If we reach here, either re-capture or retry failed → 401\n const reauthErrorBody = {\n error: {\n message: \"Authentication failed after automatic re-capture attempt.\",\n type: \"authentication_error\",\n code: \"upstream_error\",\n param: null,\n },\n };\n\n if (shouldStream) {\n return honoStream(c, async (stream) => {\n c.header(\"Content-Type\", \"text/event-stream\");\n c.header(\"Cache-Control\", \"no-cache\");\n c.header(\"Connection\", \"keep-alive\");\n await stream.write(`data: ${JSON.stringify(reauthErrorBody)}\\n\\n`);\n await stream.write(\"data: [DONE]\\n\\n\");\n });\n }\n\n return c.json(reauthErrorBody, 401);\n }\n\n // ── Standard error response ───────────────────────────────────────\n const typeMap: Record<number, string> = {\n 400: \"invalid_request_error\",\n 401: \"authentication_error\",\n 403: \"permission_error\",\n 429: \"rate_limit_error\",\n 503: \"service_unavailable\",\n };\n\n const errorBody = {\n error: {\n message: error.message || \"Internal server error\",\n type: typeMap[statusCode] ?? \"server_error\",\n code: statusCode === 429 ? \"queue_overflow\" : \"upstream_error\",\n param: null,\n },\n };\n\n // If streaming was requested, emit error as SSE event\n if (shouldStream) {\n return honoStream(c, async (stream) => {\n c.header(\"Content-Type\", \"text/event-stream\");\n c.header(\"Cache-Control\", \"no-cache\");\n c.header(\"Connection\", \"keep-alive\");\n await stream.write(`data: ${JSON.stringify(errorBody)}\\n\\n`);\n await stream.write(\"data: [DONE]\\n\\n\");\n });\n }\n\n return c.json(\n errorBody,\n statusCode as 400 | 401 | 403 | 429 | 500 | 503,\n );\n }\n });\n\n return app;\n}\n","import type { OpenAIMessage, OpenAITool, BuiltPrompt } from \"../infra/types.js\";\n\n/**\n * Format tool definitions as XML for injection into the system prompt.\n */\nexport function formatToolDefinitionsXml(tools: OpenAITool[]): string {\n const defs = tools.map((t) => {\n const fn = t.function;\n let xml = `<tool name=\"${fn.name}\"`;\n if (fn.description) {\n xml += ` description=\"${escapeXmlAttr(fn.description)}\"`;\n }\n xml += \">\";\n if (fn.parameters) {\n xml += `\\n <parameters>${JSON.stringify(fn.parameters)}</parameters>`;\n }\n xml += \"\\n</tool>\";\n return xml;\n });\n\n return `<tools>\\n${defs.join(\"\\n\")}\\n</tools>`;\n}\n\n/**\n * Format a tool result message as `<tool_response>` XML.\n */\nexport function formatToolResponse(toolCallId: string, content: string): string {\n return `<tool_response id=\"${toolCallId}\">${content}</tool_response>`;\n}\n\n/**\n * Convert OpenAI messages + tools into JH prompt format.\n *\n * Rules:\n * 1. system → prepended as system prompt section\n * 2. user → \"Human: {content}\"\n * 3. assistant → \"Assistant: {content}\" (including tool_calls as XML)\n * 4. tool → \"<tool_response id=\"{tool_call_id}\">{content}</tool_response>\"\n * 5. tools array → XML tool definitions injected into system prompt\n * 6. tool_choice: \"required\" → append instruction\n * 7. tool_choice: { function: { name } } → append specific tool instruction\n */\nexport function buildPrompt(\n messages: OpenAIMessage[],\n tools?: OpenAITool[],\n toolChoice?: string | { type: string; function: { name: string } },\n): BuiltPrompt {\n let systemPrompt: string | undefined;\n const parts: string[] = [];\n\n for (const msg of messages) {\n switch (msg.role) {\n case \"system\":\n // Accumulate system messages (rare to have multiple, but handle it)\n systemPrompt = systemPrompt\n ? `${systemPrompt}\\n${msg.content ?? \"\"}`\n : (msg.content ?? \"\");\n break;\n\n case \"user\":\n parts.push(`${msg.content ?? \"\"}`);\n break;\n\n case \"assistant\": {\n let content = msg.content ?? \"\";\n // If assistant message includes tool_calls, append them as XML\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n const toolXml = msg.tool_calls\n .map(\n (tc) =>\n `<tool_call id=\"${tc.id}\" name=\"${tc.function.name}\">${tc.function.arguments}</tool_call>`,\n )\n .join(\"\");\n content = content ? `${content}\\n${toolXml}` : toolXml;\n }\n parts.push(`Assistant: ${content}`);\n break;\n }\n\n case \"tool\":\n parts.push(formatToolResponse(msg.tool_call_id ?? \"unknown\", msg.content ?? \"\"));\n break;\n }\n }\n\n // Inject tool definitions into system prompt if tools are provided\n if (tools && tools.length > 0) {\n const toolsXml = formatToolDefinitionsXml(tools);\n const toolInstructions =\n \"You have access to the following tools. To use a tool, respond with \" +\n '<tool_call id=\"call_ID\" name=\"TOOL_NAME\">ARGUMENTS_JSON</tool_call>';\n const injection = `${toolInstructions}\\n\\n${toolsXml}`;\n systemPrompt = systemPrompt ? `${systemPrompt}\\n\\n${injection}` : injection;\n }\n\n // Append tool_choice instructions\n if (toolChoice) {\n let instruction = \"\";\n if (toolChoice === \"required\") {\n instruction = \"You MUST use a tool in your response.\";\n } else if (typeof toolChoice === \"object\" && toolChoice.function?.name) {\n instruction = `You MUST use the tool \"${toolChoice.function.name}\" in your response.`;\n }\n if (instruction) {\n parts.push(instruction);\n }\n }\n\n return {\n prompt: parts.join(\"\\n\\n\"),\n systemPrompt,\n };\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction escapeXmlAttr(s: string): string {\n return s.replace(/&/g, \"&\").replace(/\"/g, \""\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n","import type { Page } from \"playwright-core\";\nimport type {\n GatewayCredentials,\n ChatRequest,\n ChatResponse,\n} from \"../infra/types.js\";\nimport { MODEL_ENDPOINT_MAP } from \"../infra/types.js\";\nimport { captureCredentials } from \"./auth-capture.js\";\n\nconst JH_API_BASE = \"https://chat.ai.jh.edu/api\";\nconst JH_DEFAULT_GREETING = \"Hello! How can I help you today?\";\nconst NULL_UUID = \"00000000-0000-0000-0000-000000000000\";\n\n/**\n * Check if a JWT bearer token has expired.\n * Returns true if the current time exceeds the `exp` claim.\n */\nexport function isTokenExpired(token: string): boolean {\n try {\n const parts = token.split(\".\");\n if (parts.length < 2) return true;\n const payload = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const json = Buffer.from(payload, \"base64\").toString(\"utf8\");\n const parsed = JSON.parse(json) as Record<string, unknown>;\n const exp = parsed[\"exp\"];\n if (typeof exp !== \"number\") return true;\n return Date.now() / 1000 > exp;\n } catch {\n return true;\n }\n}\n\n/**\n * Execute a chat request via in-browser fetch (Cloudflare bypass).\n * Auto-retries up to 2 times if the stream fetch fails (JH platform flakiness).\n */\nexport async function sendChatRequest(\n page: Page,\n credentials: GatewayCredentials,\n request: ChatRequest,\n options?: {\n cdpUrl?: string;\n onCredentialsRefreshed?: (creds: GatewayCredentials) => void;\n },\n): Promise<ChatResponse> {\n const MAX_ATTEMPTS = 3;\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n return await sendChatRequestInner(page, credentials, request, options, false);\n } catch (err) {\n const error = err as Error & { statusCode?: number };\n // Only retry on 404 (stream not found) — other errors are real\n if (error.statusCode === 404 && attempt < MAX_ATTEMPTS) {\n console.log(`[gateway] Stream not found, retrying (attempt ${attempt + 1}/${MAX_ATTEMPTS})...`);\n // Brief pause before retry\n await new Promise((r) => setTimeout(r, 500));\n continue;\n }\n throw err;\n }\n }\n // Should never reach here\n throw new Error(\"Unexpected: all retry attempts exhausted\");\n}\n\nasync function sendChatRequestInner(\n page: Page,\n credentials: GatewayCredentials,\n request: ChatRequest,\n options: {\n cdpUrl?: string;\n onCredentialsRefreshed?: (creds: GatewayCredentials) => void;\n } | undefined,\n isRetry: boolean,\n): Promise<ChatResponse> {\n if (!isRetry && isTokenExpired(credentials.bearerToken)) {\n throw Object.assign(\n new Error(\"Bearer token has expired. Run `jh-gateway auth` to capture fresh credentials.\"),\n { statusCode: 401 },\n );\n }\n\n const endpoint = MODEL_ENDPOINT_MAP[request.model];\n if (!endpoint) {\n throw Object.assign(new Error(`Model '${request.model}' is not supported`), { statusCode: 400 });\n }\n\n const conversationId = request.conversationId ?? crypto.randomUUID();\n const parentMessageId = request.parentMessageId ?? NULL_UUID;\n const messageId = crypto.randomUUID();\n\n const body = {\n text: request.prompt,\n sender: \"User\",\n clientTimestamp: new Date().toISOString(),\n isCreatedByUser: true,\n parentMessageId,\n conversationId,\n messageId,\n error: false,\n endpoint,\n endpointType: \"custom\",\n model: request.model,\n resendFiles: true,\n greeting: JH_DEFAULT_GREETING,\n key: \"never\",\n modelDisplayLabel: \"Claude\",\n isTemporary: true,\n isRegenerate: false,\n isContinued: false,\n ephemeralAgent: {\n execute_code: false,\n web_search: false,\n file_search: false,\n artifacts: false,\n mcp: [],\n },\n };\n\n // Strategy: intercept the stream GET at the network level via page.route().\n // The page's JS fires the GET immediately after POST, but the stream may not\n // be ready yet (server needs time to set it up). We intercept the request,\n // retry with backoff until the stream is available, then fulfill the response\n // to both the page and our code.\n\n type Result = { error: boolean; status: number; statusText: string; body: string };\n\n let sseResolve: (v: Result) => void;\n const ssePromise = new Promise<Result>((res) => { sseResolve = res; });\n let sseResolved = false;\n\n const streamPattern = \"**/api/agents/chat/stream/*\";\n\n const routeHandler = async (route: import(\"playwright-core\").Route) => {\n console.log(`[gateway] Route handler intercepted: ${route.request().url()}`);\n const url = route.request().url();\n\n // Don't use route.fetch() for retries — each call consumes the stream.\n // Instead, poll with Node.js fetch until the stream is ready, then fulfill once.\n // Aggressive early polling — stream is usually ready within 100-300ms\n const delays = [0, 50, 100, 150, 250, 400, 700, 1200, 2000, 3500];\n let lastStatus = 0;\n let lastBody = \"\";\n let lastHeaders: Record<string, string> = {};\n\n // Extract headers from the original request to forward them\n const reqHeaders = route.request().headers();\n\n for (let i = 0; i < delays.length; i++) {\n if (delays[i] > 0) {\n await new Promise((r) => setTimeout(r, delays[i]));\n }\n try {\n // Use Node.js global fetch — NOT route.fetch() — to avoid consuming the stream\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Accept: reqHeaders[\"accept\"] ?? \"text/event-stream\",\n Authorization: reqHeaders[\"authorization\"] ?? \"\",\n Cookie: reqHeaders[\"cookie\"] ?? \"\",\n Referer: reqHeaders[\"referer\"] ?? \"\",\n \"User-Agent\": reqHeaders[\"user-agent\"] ?? \"\",\n },\n });\n\n lastStatus = response.status;\n lastBody = await response.text();\n lastHeaders = {};\n response.headers.forEach((v, k) => { lastHeaders[k] = v; });\n\n if (lastStatus === 200) {\n console.log(`[gateway] Stream ready on attempt ${i + 1}, body length: ${lastBody.length}`);\n if (!sseResolved) {\n sseResolved = true;\n sseResolve({ error: false, status: 200, statusText: \"OK\", body: lastBody });\n }\n // Fulfill the intercepted route with the data we got\n await route.fulfill({ status: 200, headers: lastHeaders, body: lastBody });\n return;\n }\n\n if (lastStatus !== 404) {\n console.log(`[gateway] Stream non-404 error: ${lastStatus}, body: ${lastBody.slice(0, 200)}`);\n break;\n }\n } catch (err) {\n lastBody = String(err);\n lastStatus = 500;\n break;\n }\n }\n\n console.log(`[gateway] Stream fetch failed after ${delays.length} attempts, last status: ${lastStatus}`);\n if (!sseResolved) {\n sseResolved = true;\n sseResolve({ error: true, status: lastStatus, statusText: \"stream fetch failed\", body: lastBody });\n }\n try {\n await route.fulfill({ status: lastStatus, headers: lastHeaders, body: lastBody });\n } catch {\n try { await route.continue(); } catch { /* ignore */ }\n }\n };\n\n await page.route(streamPattern, routeHandler);\n\n // POST to start the chat\n const postResult = await page.evaluate(\n async ({\n apiBase,\n bearerToken,\n endpointPath,\n requestBody,\n }: {\n apiBase: string;\n bearerToken: string;\n endpointPath: string;\n requestBody: Record<string, unknown>;\n }) => {\n try {\n const res = await fetch(`${apiBase}/agents/chat/${endpointPath}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!res.ok) {\n return {\n error: true,\n status: res.status,\n statusText: res.statusText,\n body: (await res.text()).slice(0, 2000),\n contentType: \"\",\n };\n }\n\n const ct = res.headers.get(\"content-type\") ?? \"\";\n\n if (ct.includes(\"text/event-stream\")) {\n const reader = res.body?.getReader();\n if (!reader) return { error: true, status: 500, statusText: \"No body\", body: \"No SSE body\", contentType: ct };\n const decoder = new TextDecoder();\n let text = \"\";\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n text += decoder.decode(value, { stream: true });\n }\n return { error: false, status: 200, statusText: \"OK\", body: text, contentType: ct };\n }\n\n const bodyText = await res.text();\n console.log(`[gateway] POST response content-type: ${ct}, body: ${bodyText.slice(0, 500)}`);\n return { error: false, status: 200, statusText: \"OK\", body: bodyText, contentType: ct };\n } catch (err) {\n return { error: true, status: 500, statusText: \"fetch error\", body: String(err), contentType: \"\" };\n }\n },\n {\n apiBase: JH_API_BASE,\n bearerToken: credentials.bearerToken,\n endpointPath: endpoint,\n requestBody: body as unknown as Record<string, unknown>,\n },\n );\n\n let result: Result;\n\n if (postResult.error) {\n result = { error: true, status: postResult.status, statusText: postResult.statusText ?? \"\", body: postResult.body };\n await page.unroute(streamPattern, routeHandler);\n } else if (postResult.contentType.includes(\"text/event-stream\")) {\n result = { error: false, status: 200, statusText: \"OK\", body: postResult.body };\n await page.unroute(streamPattern, routeHandler);\n } else {\n // POST returned JSON with streamId — the page's JS will GET the stream,\n // and our route handler will intercept it with retry logic.\n // Also fire our own GET as a fallback in case the page doesn't.\n let streamId: string | undefined;\n try {\n streamId = (JSON.parse(postResult.body) as { streamId?: string }).streamId;\n } catch { /* not JSON */ }\n\n if (!streamId) {\n result = { error: false, status: 200, statusText: \"OK\", body: postResult.body };\n await page.unroute(streamPattern, routeHandler);\n } else {\n // Fire our own GET as fallback (the route handler intercepts it too)\n const streamUrl = `${JH_API_BASE}/agents/chat/stream/${streamId}`;\n page.evaluate(\n async ({ url, token }: { url: string; token: string }) => {\n await new Promise((r) => setTimeout(r, 10));\n try {\n await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"text/event-stream\", Authorization: `Bearer ${token}` },\n });\n } catch { /* route handler captures it */ }\n },\n { url: streamUrl, token: credentials.bearerToken },\n ).catch(() => { /* ignore */ });\n\n // Wait for the route handler to resolve\n const timeout = new Promise<Result>((res) =>\n setTimeout(() => {\n if (!sseResolved) {\n sseResolved = true;\n res({ error: true, status: 408, statusText: \"timeout\", body: \"Stream capture timed out after 120s\" });\n }\n }, 120_000),\n );\n\n result = await Promise.race([ssePromise, timeout]);\n await page.unroute(streamPattern, routeHandler);\n }\n }\n\n if (result.error) {\n const status = result.status as number;\n const responseBody = result.body as string;\n\n if (status === 401 && !isRetry) {\n const cdpUrl = options?.cdpUrl ?? \"http://127.0.0.1:9222\";\n try {\n await page.reload({ waitUntil: \"networkidle\" });\n const fresh = await captureCredentials(cdpUrl, 30_000);\n const newCreds: GatewayCredentials = {\n bearerToken: fresh.bearerToken,\n cookie: fresh.cookie,\n userAgent: fresh.userAgent,\n };\n options?.onCredentialsRefreshed?.(newCreds);\n return sendChatRequestInner(page, newCreds, request, options, true);\n } catch {\n throw Object.assign(\n new Error(\"JH platform returned 401 and automatic re-authentication failed. Run `jh-gateway auth` to capture fresh credentials.\"),\n { statusCode: 401 },\n );\n }\n }\n\n if (status === 401) {\n throw Object.assign(\n new Error(\"JH platform returned 401 after re-authentication attempt. Run `jh-gateway auth` to capture fresh credentials.\"),\n { statusCode: 401 },\n );\n }\n\n if (status === 403) {\n throw Object.assign(\n new Error(\"JH platform returned 403 — Cloudflare session has expired. Please open chat.ai.jh.edu in your browser, complete any challenge, then run `jh-gateway auth`.\"),\n { statusCode: 403 },\n );\n }\n\n throw Object.assign(new Error(`JH platform returned ${status}: ${responseBody}`), { statusCode: status });\n }\n\n console.log(`[gateway] Final result: error=${result.error}, status=${result.status}, body length=${result.body.length}`);\n\n const rawSseText = result.body as string;\n const { newConversationId, newParentMessageId } =\n extractConversationState(rawSseText, conversationId, messageId);\n\n return { rawSseText, conversationId: newConversationId, parentMessageId: newParentMessageId };\n}\n\nfunction extractConversationState(\n rawSse: string,\n fallbackConversationId: string | null,\n fallbackParentMessageId: string,\n): { newConversationId: string; newParentMessageId: string } {\n let newConversationId = fallbackConversationId ?? \"\";\n let newParentMessageId = fallbackParentMessageId;\n\n const blocks = rawSse.split(\"\\n\\n\");\n for (const block of blocks) {\n const trimmed = block.trim();\n if (!trimmed) continue;\n let event = \"\";\n let data = \"\";\n for (const line of trimmed.split(\"\\n\")) {\n if (line.startsWith(\"event: \")) event = line.slice(7).trim();\n else if (line.startsWith(\"data: \")) data = line.slice(6);\n else if (line.startsWith(\"data:\")) data = line.slice(5);\n }\n if (event === \"message\" && data) {\n try {\n const parsed = JSON.parse(data);\n if (parsed?.isCreatedByUser === false) {\n if (parsed.conversationId) newConversationId = parsed.conversationId;\n if (parsed.messageId) newParentMessageId = parsed.messageId;\n }\n // Also check nested message format\n const msg = parsed?.message;\n if (msg?.isCreatedByUser === false) {\n if (msg.conversationId) newConversationId = msg.conversationId;\n if (msg.messageId) newParentMessageId = msg.messageId;\n }\n } catch { /* skip */ }\n }\n }\n\n return { newConversationId, newParentMessageId };\n}\n","import { randomBytes } from \"node:crypto\";\nimport type { OpenAIChunk, OpenAICompletion } from \"../infra/types.js\";\nimport { parseToolsAndThinking, toOpenAIToolCalls } from \"./tool-parser.js\";\n\n// ── SSE Parsing ───────────────────────────────────────────────────────────────\n\ninterface SseEvent {\n event: string;\n data: string;\n}\n\n/** Parse raw SSE text into individual events. */\nfunction parseSseEvents(rawSse: string): SseEvent[] {\n const events: SseEvent[] = [];\n const blocks = rawSse.split(\"\\n\\n\");\n\n for (const block of blocks) {\n const trimmed = block.trim();\n if (!trimmed) continue;\n\n let event = \"\";\n let data = \"\";\n\n for (const line of trimmed.split(\"\\n\")) {\n if (line.startsWith(\"event: \")) {\n event = line.slice(7).trim();\n } else if (line.startsWith(\"data: \")) {\n data = line.slice(6);\n } else if (line.startsWith(\"data:\")) {\n data = line.slice(5);\n }\n }\n\n if (event || data) {\n events.push({ event, data });\n }\n }\n\n return events;\n}\n\n/**\n * Resolve the effective event type from a JH SSE event.\n *\n * JH wraps all events in `event: message` and puts the real type\n * in the JSON payload's `event` field. We also support the flat format\n * (event: on_message_delta) for test compatibility.\n */\nfunction resolveEventType(ev: SseEvent): { type: string; parsed: Record<string, unknown> | null } {\n // Flat format: event line already has the real type\n if (ev.event && ev.event !== \"message\") {\n try {\n return { type: ev.event, parsed: JSON.parse(ev.data) };\n } catch {\n return { type: ev.event, parsed: null };\n }\n }\n\n // JH wrapped format: event: message, real type in JSON .event field\n if (ev.data) {\n try {\n const parsed = JSON.parse(ev.data) as Record<string, unknown>;\n const jsonEvent = typeof parsed.event === \"string\" ? parsed.event : null;\n return { type: jsonEvent ?? ev.event, parsed };\n } catch {\n return { type: ev.event, parsed: null };\n }\n }\n\n return { type: ev.event, parsed: null };\n}\n\n/** Check if an event is a user echo (skip). */\nfunction isUserEcho(parsed: Record<string, unknown> | null): boolean {\n if (!parsed) return false;\n // Direct format: { isCreatedByUser: true }\n if (parsed.isCreatedByUser === true) return true;\n // Nested format: { message: { isCreatedByUser: true } }\n const msg = parsed.message as Record<string, unknown> | undefined;\n if (msg?.isCreatedByUser === true || msg?.sender === \"User\") return true;\n return false;\n}\n\n/**\n * Extract text delta from an on_message_delta event payload.\n * Supports both flat format (delta at top level) and nested format (inside .data).\n */\nfunction extractDeltaText(parsed: Record<string, unknown> | null): string | null {\n if (!parsed) return null;\n\n // Try flat format: { delta: { content: [...] } }\n let content = (parsed.delta as Record<string, unknown>)?.content;\n\n // Try nested format: { data: { delta: { content: [...] } } }\n if (!content) {\n const dataObj = parsed.data as Record<string, unknown> | undefined;\n content = (dataObj?.delta as Record<string, unknown>)?.content;\n }\n\n if (!Array.isArray(content)) return null;\n\n const texts: string[] = [];\n for (const item of content) {\n if (item?.type === \"text\" && typeof item.text === \"string\") {\n texts.push(item.text);\n }\n }\n return texts.length > 0 ? texts.join(\"\") : null;\n}\n\n/**\n * Extract accumulated text from a final message event.\n * JH sends { message: { text: \"full accumulated text\", isCreatedByUser: false } }\n */\nfunction extractMessageText(parsed: Record<string, unknown> | null): string | null {\n if (!parsed) return null;\n const msg = parsed.message as Record<string, unknown> | undefined;\n if (!msg) return null;\n if (msg.isCreatedByUser === true || msg.sender === \"User\") return null;\n if (typeof msg.text === \"string\" && msg.text) return msg.text;\n return null;\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/** Generate a unique completion ID. */\nfunction generateCompletionId(): string {\n return `chatcmpl-${randomBytes(12).toString(\"hex\")}`;\n}\n/**\n * Extract text content directly from JH SSE events.\n * Supports both flat SSE format and JH's wrapped format.\n */\nexport function extractContentFromJhSse(rawSse: string): string {\n const events = parseSseEvents(rawSse);\n const parts: string[] = [];\n let lastMessageText = \"\";\n\n for (const ev of events) {\n const { type, parsed } = resolveEventType(ev);\n if (isUserEcho(parsed)) continue;\n if (type === \"on_run_step\") continue;\n\n if (type === \"on_message_delta\") {\n const delta = extractDeltaText(parsed);\n if (delta !== null) {\n parts.push(delta);\n }\n }\n\n // Also handle final message events with accumulated text\n if (type === \"message\" || !type) {\n const msgText = extractMessageText(parsed);\n if (msgText && msgText.length > lastMessageText.length) {\n lastMessageText = msgText;\n }\n }\n }\n\n // If we got deltas, use those. Otherwise fall back to accumulated message text.\n const deltaText = parts.join(\"\");\n return deltaText || lastMessageText;\n}\n\n/**\n * Parse JH SSE text and return OpenAI SSE chunks.\n * Skips user echoes and metadata events.\n */\nexport function translateToStream(\n rawSse: string,\n model: string,\n completionId?: string,\n): OpenAIChunk[] {\n const id = completionId ?? generateCompletionId();\n const created = Math.floor(Date.now() / 1000);\n const events = parseSseEvents(rawSse);\n const chunks: OpenAIChunk[] = [];\n let lastMessageText = \"\";\n\n // First chunk: role announcement\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { role: \"assistant\" }, finish_reason: null }],\n });\n\n let gotDeltas = false;\n\n for (const ev of events) {\n const { type, parsed } = resolveEventType(ev);\n if (isUserEcho(parsed)) continue;\n if (type === \"on_run_step\") continue;\n\n if (type === \"on_message_delta\") {\n const delta = extractDeltaText(parsed);\n if (delta !== null) {\n gotDeltas = true;\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { content: delta }, finish_reason: null }],\n });\n }\n }\n\n // Track accumulated message text as fallback\n if (type === \"message\" || !type) {\n const msgText = extractMessageText(parsed);\n if (msgText && msgText.length > lastMessageText.length) {\n const delta = msgText.slice(lastMessageText.length);\n lastMessageText = msgText;\n if (!gotDeltas && delta) {\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { content: delta }, finish_reason: null }],\n });\n }\n }\n }\n }\n\n // Final chunk: finish_reason stop\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }],\n });\n\n return chunks;\n}\n\n/**\n * Parse JH SSE text and return a single OpenAI completion JSON.\n * Collects full response text, parses tool calls, estimates token usage.\n */\nexport function translateToCompletion(\n rawSse: string,\n model: string,\n completionId?: string,\n): OpenAICompletion {\n const id = completionId ?? generateCompletionId();\n const created = Math.floor(Date.now() / 1000);\n\n const fullText = extractContentFromJhSse(rawSse);\n const parsed = parseToolsAndThinking(fullText);\n\n // Approximate token estimates (1 token ≈ 4 chars)\n const promptTokens = 0;\n const completionTokens = Math.max(1, Math.ceil(fullText.length / 4));\n\n const message: OpenAICompletion[\"choices\"][0][\"message\"] = {\n role: \"assistant\",\n content: parsed.text || null,\n };\n\n if (parsed.toolCalls.length > 0) {\n message.tool_calls = toOpenAIToolCalls(parsed.toolCalls);\n }\n\n return {\n id,\n object: \"chat.completion\",\n created,\n model,\n choices: [{ index: 0, message, finish_reason: \"stop\" }],\n usage: {\n prompt_tokens: promptTokens,\n completion_tokens: completionTokens,\n total_tokens: promptTokens + completionTokens,\n },\n };\n}\n","import type { ParsedToolCall, ParsedContent, ToolCall } from \"../infra/types.js\";\n\n// ── Regex patterns ────────────────────────────────────────────────────────────\n\nconst TOOL_CALL_RE =\n /<tool_call\\s+id=\"([^\"]*)\"\\s+name=\"([^\"]*)\">([\\s\\S]*?)<\\/tool_call>/g;\n\nconst THINK_RE = /<think>([\\s\\S]*?)<\\/think>/g;\n\n// Detects an opening <tool_call that never closes — used for malformed detection\nconst PARTIAL_TOOL_CALL_RE = /<tool_call\\b[^>]*>(?:(?!<\\/tool_call>)[\\s\\S])*$/;\n\n/**\n * Parse response text, extracting `<tool_call>` and `<think>` tags.\n * Malformed XML is emitted as raw text (never throws).\n */\nexport function parseToolsAndThinking(text: string): ParsedContent {\n const toolCalls: ParsedToolCall[] = [];\n let thinking: string | null = null;\n\n // Extract think tags first, accumulate thinking content\n const thinkMatches = [...text.matchAll(THINK_RE)];\n if (thinkMatches.length > 0) {\n thinking = thinkMatches.map((m) => m[1]).join(\"\\n\");\n }\n\n // Remove think tags from text for further processing\n let remaining = text.replace(THINK_RE, \"\");\n\n // Extract well-formed tool_call tags\n const toolMatches = [...remaining.matchAll(TOOL_CALL_RE)];\n for (const match of toolMatches) {\n const id = match[1];\n const name = match[2];\n const rawArgs = match[3].trim();\n\n // Validate JSON arguments; if invalid, keep as-is (still valid JSON string)\n let args: string;\n try {\n JSON.parse(rawArgs);\n args = rawArgs;\n } catch {\n // Malformed JSON in arguments — wrap as string to keep valid JSON\n args = JSON.stringify(rawArgs);\n }\n\n toolCalls.push({ id, name, arguments: args });\n }\n\n // Remove well-formed tool_call tags from remaining text\n remaining = remaining.replace(TOOL_CALL_RE, \"\");\n\n // Any leftover partial/malformed <tool_call> tags stay as raw text\n const cleanedText = remaining.trim();\n\n return {\n text: cleanedText,\n toolCalls,\n thinking,\n };\n}\n\n/**\n * Convert ParsedToolCall[] to OpenAI tool_calls format with incrementing index.\n */\nexport function toOpenAIToolCalls(calls: ParsedToolCall[]): ToolCall[] {\n return calls.map((call, index) => ({\n id: call.id,\n type: \"function\" as const,\n index,\n function: {\n name: call.name,\n arguments: call.arguments,\n },\n }));\n}\n\n/**\n * Reconstruct XML from parsed tool calls (for round-trip testing).\n */\nexport function toToolCallXml(calls: ParsedToolCall[]): string {\n return calls\n .map((c) => `<tool_call id=\"${c.id}\" name=\"${c.name}\">${c.arguments}</tool_call>`)\n .join(\"\");\n}\n\n// ── Streaming buffer support ──────────────────────────────────────────────────\n\n/**\n * Streaming XML buffer that holds partial `<tool_call>` content until a\n * complete closing tag boundary is found.\n *\n * Feed chunks via `push()`. Completed tool calls are returned; any buffered\n * partial content is held until the closing tag arrives.\n */\nexport class StreamingToolBuffer {\n private buffer = \"\";\n\n /**\n * Push a text chunk. Returns an object with:\n * - `text`: safe-to-emit text (outside any partial tag)\n * - `completedCalls`: fully parsed tool calls from this chunk\n */\n push(chunk: string): { text: string; completedCalls: ParsedToolCall[] } {\n this.buffer += chunk;\n\n const completedCalls: ParsedToolCall[] = [];\n let safeText = \"\";\n\n // Extract all complete tool_call tags\n let match: RegExpExecArray | null;\n const re = new RegExp(TOOL_CALL_RE.source, \"g\");\n\n let lastIndex = 0;\n while ((match = re.exec(this.buffer)) !== null) {\n // Text before this match is safe to emit\n safeText += this.buffer.slice(lastIndex, match.index);\n\n const id = match[1];\n const name = match[2];\n const rawArgs = match[3].trim();\n\n let args: string;\n try {\n JSON.parse(rawArgs);\n args = rawArgs;\n } catch {\n args = JSON.stringify(rawArgs);\n }\n\n completedCalls.push({ id, name, arguments: args });\n lastIndex = match.index + match[0].length;\n }\n\n // Remaining buffer after all complete matches\n const remainder = this.buffer.slice(lastIndex);\n\n // Check if remainder contains a partial opening tag\n if (PARTIAL_TOOL_CALL_RE.test(remainder)) {\n // Hold the partial tag in the buffer; emit text before it\n const partialStart = remainder.search(/<tool_call\\b/);\n if (partialStart > 0) {\n safeText += remainder.slice(0, partialStart);\n }\n this.buffer = partialStart >= 0 ? remainder.slice(partialStart) : remainder;\n } else {\n // No partial tag — emit everything, clear buffer\n safeText += remainder;\n this.buffer = \"\";\n }\n\n return { text: safeText, completedCalls };\n }\n\n /** Flush any remaining buffer content as raw text (end of stream). */\n flush(): string {\n const remaining = this.buffer;\n this.buffer = \"\";\n return remaining;\n }\n}\n","import { appendFile, readFile, readdir, mkdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { RequestLogEntry } from \"./types.js\";\n\nconst DEFAULT_LOG_DIR = join(homedir(), \".jh-gateway\", \"logs\");\n\n/** Format a Date as YYYY-MM-DD for daily log file rotation. */\nfunction dateStamp(d: Date = new Date()): string {\n return d.toISOString().slice(0, 10);\n}\n\nexport class Logger {\n private logDir: string;\n private dirCreated = false;\n\n constructor(logDir?: string) {\n this.logDir = logDir ?? DEFAULT_LOG_DIR;\n }\n\n /** Ensure the log directory exists (lazy, once per instance). */\n private async ensureDir(): Promise<void> {\n if (this.dirCreated) {return;}\n await mkdir(this.logDir, { recursive: true });\n this.dirCreated = true;\n }\n\n /** Append a log entry to today's JSONL file. */\n async log(entry: RequestLogEntry): Promise<void> {\n await this.ensureDir();\n const file = join(this.logDir, `${dateStamp()}.jsonl`);\n await appendFile(file, JSON.stringify(entry) + \"\\n\", \"utf8\");\n }\n\n /** Query log entries. Reads from a specific date or the most recent file. */\n async query(options: { date?: string; limit?: number } = {}): Promise<RequestLogEntry[]> {\n await this.ensureDir();\n const limit = options.limit ?? 100;\n\n if (options.date) {\n return this.readLogFile(join(this.logDir, `${options.date}.jsonl`), limit);\n }\n\n // Read from most recent files until we have enough entries\n let names: string[];\n try {\n names = await readdir(this.logDir);\n } catch {\n return [];\n }\n\n const jsonlFiles = names\n .filter((n) => n.endsWith(\".jsonl\"))\n .toSorted()\n .toReversed();\n\n const entries: RequestLogEntry[] = [];\n for (const name of jsonlFiles) {\n if (entries.length >= limit) {break;}\n const batch = await this.readLogFile(join(this.logDir, name), limit - entries.length);\n entries.push(...batch);\n }\n\n return entries.slice(0, limit);\n }\n\n private async readLogFile(filePath: string, limit: number): Promise<RequestLogEntry[]> {\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf8\");\n } catch {\n return [];\n }\n\n const entries: RequestLogEntry[] = [];\n const lines = raw.split(\"\\n\");\n // Read from end for most-recent-first\n for (let i = lines.length - 1; i >= 0 && entries.length < limit; i--) {\n const line = lines[i].trim();\n if (!line) {continue;}\n try {\n entries.push(JSON.parse(line) as RequestLogEntry);\n } catch {\n // Skip malformed lines\n }\n }\n return entries;\n }\n}\n","/**\n * FIFO promise queue that serializes async tasks so only one executes at a time.\n * Rejects with a 429-style error when queue wait exceeds maxWaitMs.\n */\nexport class RequestQueue {\n private queue: Array<() => void> = [];\n private running = false;\n private maxWaitMs: number;\n\n constructor(maxWaitMs: number = 120_000) {\n this.maxWaitMs = maxWaitMs;\n }\n\n /** Current number of tasks waiting in the queue (not including the active one). */\n get pending(): number {\n return this.queue.length;\n }\n\n /** Enqueue a task. Resolves when the task completes. Rejects on timeout or task error. */\n async enqueue<T>(task: () => Promise<T>): Promise<T> {\n // Wait for our turn\n await this.waitForTurn();\n\n try {\n return await task();\n } finally {\n // Adaptive cooldown: only delay if there are queued requests waiting.\n // The JH platform needs a brief pause between generations, but 1500ms\n // was overly conservative. 300ms is enough for cleanup.\n if (this.queue.length > 0) {\n await new Promise((r) => setTimeout(r, 300));\n }\n this.release();\n }\n }\n\n private waitForTurn(): Promise<void> {\n if (!this.running) {\n this.running = true;\n return Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n // Remove ourselves from the queue\n const idx = this.queue.indexOf(release);\n if (idx !== -1) this.queue.splice(idx, 1);\n reject(\n Object.assign(\n new Error(\n `Request queue wait exceeded ${this.maxWaitMs}ms — server is overloaded`,\n ),\n { statusCode: 429 },\n ),\n );\n }, this.maxWaitMs);\n\n const release = () => {\n clearTimeout(timer);\n resolve();\n };\n\n this.queue.push(release);\n });\n }\n\n private release(): void {\n const next = this.queue.shift();\n if (next) {\n next();\n } else {\n this.running = false;\n }\n }\n}\n","/**\n * Pool of browser pages for concurrent request handling.\n * Each page has its own RequestQueue (concurrency=1), but with N pages\n * we can handle N concurrent requests to the upstream.\n */\nimport type { Page, Browser } from \"playwright-core\";\nimport { RequestQueue } from \"./request-queue.js\";\n\nexport interface PooledPage {\n page: Page;\n queue: RequestQueue;\n inUse: boolean;\n}\n\nexport class PagePool {\n private pages: PooledPage[] = [];\n private browser: Browser | null = null;\n private targetUrl: string;\n private maxPages: number;\n private maxWaitMs: number;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: {\n targetUrl?: string;\n maxPages?: number;\n maxWaitMs?: number;\n } = {}) {\n this.targetUrl = options.targetUrl ?? \"https://chat.ai.jh.edu\";\n this.maxPages = options.maxPages ?? 3;\n this.maxWaitMs = options.maxWaitMs ?? 120_000;\n }\n\n /** Initialize the pool with an existing browser connection and seed page */\n async init(browser: Browser, seedPage: Page): Promise<void> {\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = this._doInit(browser, seedPage);\n return this.initPromise;\n }\n\n private async _doInit(browser: Browser, seedPage: Page): Promise<void> {\n this.browser = browser;\n\n // Add the seed page as the first pooled page\n this.pages.push({\n page: seedPage,\n queue: new RequestQueue(this.maxWaitMs),\n inUse: false,\n });\n\n console.log(`[PagePool] Initialized with 1 page, will scale up to ${this.maxPages}`);\n }\n\n /** Get pool statistics */\n get stats(): { total: number; busy: number; available: number } {\n const busy = this.pages.filter(p => p.inUse).length;\n return {\n total: this.pages.length,\n busy,\n available: this.pages.length - busy,\n };\n }\n\n /**\n * Acquire a page for use. Creates new pages on-demand up to maxPages.\n * Note: We intentionally don't lock here — allowing multiple requests to\n * grab the same page and queue on it is actually faster than creating new pages.\n */\n async acquire(): Promise<{ page: Page; queue: RequestQueue; release: () => void }> {\n // First, try to find an available (non-busy) page\n let pooled = this.pages.find(p => !p.inUse);\n\n // If all pages are busy and we haven't hit max, create a new one\n if (!pooled && this.pages.length < this.maxPages && this.browser) {\n pooled = await this.createPage();\n }\n\n // If still no page available, pick the one with the smallest queue\n if (!pooled) {\n pooled = this.pages.reduce((a, b) =>\n a.queue.pending <= b.queue.pending ? a : b\n );\n }\n\n pooled.inUse = true;\n const p = pooled;\n\n return {\n page: p.page,\n queue: p.queue,\n release: () => {\n p.inUse = false;\n },\n };\n }\n\n private async createPage(): Promise<PooledPage> {\n if (!this.browser) {\n throw new Error(\"PagePool not initialized\");\n }\n\n console.log(`[PagePool] Creating new page (${this.pages.length + 1}/${this.maxPages})...`);\n\n const context = this.browser.contexts()[0];\n if (!context) {\n throw new Error(\"No browser context available\");\n }\n\n const page = await context.newPage();\n\n // Navigate to the target URL\n await page.goto(this.targetUrl, { waitUntil: \"networkidle\", timeout: 30_000 });\n console.log(`[PagePool] New page ready: ${page.url()}`);\n\n const pooled: PooledPage = {\n page,\n queue: new RequestQueue(this.maxWaitMs),\n inUse: false,\n };\n\n this.pages.push(pooled);\n return pooled;\n }\n\n /** Close all pages except the seed page */\n async drain(): Promise<void> {\n // Keep the first page (seed), close the rest\n const toClose = this.pages.slice(1);\n this.pages = this.pages.slice(0, 1);\n\n for (const p of toClose) {\n try {\n await p.page.close();\n } catch {\n // Page may already be closed\n }\n }\n\n console.log(`[PagePool] Drained ${toClose.length} pages`);\n }\n}\n","/**\n * `serve` command — start the HTTP server.\n */\nimport { loadConfig, updateConfig } from \"../infra/config.js\";\nimport { connectToChrome, findOrOpenJhPage } from \"../infra/chrome-cdp.js\";\nimport { startServer } from \"../server.js\";\nimport { PagePool } from \"../core/page-pool.js\";\nimport type { Page } from \"playwright-core\";\n\nexport async function runServe(options: { port?: number; pages?: number }): Promise<void> {\n const config = await loadConfig();\n\n if (options.port !== undefined) {\n await updateConfig({ port: options.port });\n config.port = options.port;\n }\n\n const maxPages = options.pages ?? 3;\n\n let pool: PagePool | null = null;\n let browser: { close(): Promise<void> } | undefined;\n try {\n const conn = await connectToChrome(config.cdpUrl);\n const seedPage = await findOrOpenJhPage(conn.browser);\n browser = conn.browser;\n console.log(`Connected to Chrome at ${config.cdpUrl}`);\n\n const currentUrl = seedPage.url();\n if (!currentUrl.includes(\"chat.ai.jh.edu\")) {\n console.log(\"Navigating to chat.ai.jh.edu...\");\n await seedPage.goto(\"https://chat.ai.jh.edu\", { waitUntil: \"networkidle\" });\n }\n console.log(`Browser page: ${seedPage.url()}`);\n\n // Initialize the page pool\n pool = new PagePool({\n maxPages,\n maxWaitMs: config.maxQueueWaitMs,\n });\n await pool.init(conn.browser, seedPage);\n console.log(`Page pool initialized (max ${maxPages} concurrent pages)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`Warning: Could not connect to Chrome: ${msg}`);\n console.warn(\"Chat completions will fail until Chrome is available.\");\n }\n\n await startServer(config, {\n getPool: () => pool,\n getCredentials: () => config.credentials,\n browser,\n });\n}\n","/**\n * `auth` command — re-capture JH credentials without full setup wizard.\n */\nimport { loadConfig } from \"../infra/config.js\";\nimport { captureCredentials, getTokenExpiry } from \"../core/auth-capture.js\";\n\nfunction formatExpiry(exp: number): string {\n if (exp === 0) {return \"unknown\";}\n return new Date(exp * 1000).toLocaleString();\n}\n\nexport async function runAuth(): Promise<void> {\n const config = await loadConfig();\n\n console.log(`Connecting to Chrome at ${config.cdpUrl}…`);\n console.log(\n \"Opening chat.ai.jh.edu. Send any message to trigger auth capture (timeout: 120s)…\"\n );\n\n const creds = await captureCredentials(config.cdpUrl, 120_000);\n const expiry = getTokenExpiry(creds.bearerToken);\n\n console.log(\"Credentials captured successfully.\");\n console.log(`Token expires: ${formatExpiry(expiry)}`);\n}\n","/**\n * `config` command — print current configuration with credentials redacted.\n */\nimport { loadConfig } from \"../infra/config.js\";\nimport type { GatewayConfig } from \"../infra/types.js\";\n\nfunction redactConfig(config: GatewayConfig): Record<string, unknown> {\n return {\n cdpUrl: config.cdpUrl,\n port: config.port,\n defaultModel: config.defaultModel,\n defaultEndpoint: config.defaultEndpoint,\n credentials:\n config.credentials !== null\n ? {\n bearerToken: \"[REDACTED]\",\n cookie: \"[REDACTED]\",\n userAgent: config.credentials.userAgent,\n }\n : null,\n auth: {\n mode: config.auth.mode,\n token: config.auth.token !== null ? \"[REDACTED]\" : null,\n },\n maxQueueWaitMs: config.maxQueueWaitMs,\n };\n}\n\nexport async function runConfig(): Promise<void> {\n const config = await loadConfig();\n const redacted = redactConfig(config);\n console.log(JSON.stringify(redacted, null, 2));\n}\n","/**\n * `status` command — display Chrome connection status, token expiry, gateway state.\n */\nimport { loadConfig } from \"../infra/config.js\";\nimport { getChromeWebSocketUrl } from \"../infra/chrome-cdp.js\";\nimport { getTokenExpiry } from \"../core/auth-capture.js\";\nimport { isTokenExpired } from \"../core/client.js\";\n\nfunction formatExpiry(exp: number): string {\n if (exp === 0) {return \"unknown\";}\n return new Date(exp * 1000).toLocaleString();\n}\n\nexport async function runStatus(): Promise<void> {\n const config = await loadConfig();\n\n // Chrome connection status\n let chromeStatus: string;\n try {\n await getChromeWebSocketUrl(config.cdpUrl, 3000);\n chromeStatus = `connected (${config.cdpUrl})`;\n } catch {\n chromeStatus = `disconnected (${config.cdpUrl})`;\n }\n\n // Token expiry\n let tokenStatus: string;\n if (!config.credentials) {\n tokenStatus = \"no credentials stored\";\n } else {\n const exp = getTokenExpiry(config.credentials.bearerToken);\n const expired = isTokenExpired(config.credentials.bearerToken);\n tokenStatus = expired\n ? `expired at ${formatExpiry(exp)}`\n : `valid until ${formatExpiry(exp)}`;\n }\n\n // Gateway running state — check if something is listening on the configured port\n let gatewayStatus: string;\n try {\n const res = await fetch(`http://127.0.0.1:${config.port}/health`, {\n signal: AbortSignal.timeout(1000),\n });\n if (res.ok) {\n gatewayStatus = `running on port ${config.port}`;\n } else {\n gatewayStatus = `port ${config.port} responded with ${res.status}`;\n }\n } catch {\n gatewayStatus = `not running (port ${config.port})`;\n }\n\n console.log(`Chrome: ${chromeStatus}`);\n console.log(`Token: ${tokenStatus}`);\n console.log(`Gateway: ${gatewayStatus}`);\n}\n","/**\n * `logs` command — query and display recent request logs.\n */\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { RequestLogEntry } from \"../infra/types.js\";\n\nconst LOG_DIR = join(homedir(), \".jh-gateway\", \"logs\");\n\nasync function readLogFile(filePath: string): Promise<RequestLogEntry[]> {\n const raw = await readFile(filePath, \"utf8\");\n const entries: RequestLogEntry[] = [];\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) {continue;}\n try {\n entries.push(JSON.parse(trimmed) as RequestLogEntry);\n } catch {\n // Skip malformed lines\n }\n }\n return entries;\n}\n\nexport async function runLogs(options: { limit?: number }): Promise<void> {\n const limit = options.limit ?? 50;\n\n let files: string[];\n try {\n const names = await readdir(LOG_DIR);\n // Sort descending (newest first) by filename (YYYY-MM-DD.jsonl)\n files = names\n .filter((n) => n.endsWith(\".jsonl\"))\n .toSorted()\n .toReversed()\n .map((n) => join(LOG_DIR, n));\n } catch {\n console.log(\"No log files found. Logs are written once the server handles requests.\");\n return;\n }\n\n const entries: RequestLogEntry[] = [];\n for (const file of files) {\n if (entries.length >= limit) {break;}\n try {\n const fileEntries = await readLogFile(file);\n entries.push(...fileEntries);\n } catch {\n // Skip unreadable files\n }\n }\n\n // Take the most recent `limit` entries (files are sorted newest-first,\n // but entries within a file are oldest-first — reverse within each file)\n const recent = entries.slice(-limit).toReversed();\n\n if (recent.length === 0) {\n console.log(\"No log entries found.\");\n return;\n }\n\n for (const entry of recent) {\n const tokens = `${entry.estimatedTokens.prompt}p/${entry.estimatedTokens.completion}c`;\n console.log(\n `${entry.timestamp} ${entry.method.padEnd(4)} ${entry.path.padEnd(30)} ` +\n `${String(entry.statusCode).padEnd(4)} ${String(entry.latencyMs).padStart(6)}ms ` +\n `${(entry.model ?? \"-\").padEnd(20)} tokens:${tokens}`\n );\n }\n}\n","/**\n * `start` command — unified single-command startup.\n * Orchestrates Chrome connection, authentication, server startup,\n * and background token refresh in one terminal.\n */\nimport * as p from \"@clack/prompts\";\nimport { loadConfig, updateConfig } from \"../infra/config.js\";\nimport { ChromeManager } from \"../infra/chrome-manager.js\";\nimport type { ChromeManagerState } from \"../infra/chrome-manager.js\";\nimport { findOrOpenJhPage } from \"../infra/chrome-cdp.js\";\nimport { captureCredentials, getTokenExpiry } from \"../core/auth-capture.js\";\nimport { shouldRefresh, CredentialHolder, TokenRefresher } from \"../core/token-refresher.js\";\nimport { ReauthLock } from \"../core/reauth-lock.js\";\nimport { PagePool } from \"../core/page-pool.js\";\nimport { startServer } from \"../server.js\";\n\nexport interface StartOptions {\n port?: number;\n pages?: number;\n headless?: boolean;\n}\n\nexport async function runStart(options: StartOptions): Promise<void> {\n // ── Load config and apply overrides ─────────────────────────────────\n const config = await loadConfig();\n\n if (options.port !== undefined) {\n await updateConfig({ port: options.port });\n config.port = options.port;\n }\n\n const maxPages = options.pages ?? 3;\n const cdpPort = parseInt(new URL(config.cdpUrl).port, 10) || 9222;\n\n const chromeManager = new ChromeManager({\n cdpPort,\n headless: options.headless,\n });\n\n // ── Phase 1: Connect to Chrome ──────────────────────────────────────\n const s = p.spinner();\n s.start(\"Connecting to Chrome...\");\n\n let state: ChromeManagerState;\n try {\n state = await chromeManager.connect();\n s.stop(\n state.selfLaunched\n ? \"Chrome launched and connected.\"\n : \"Connected to existing Chrome instance.\",\n );\n } catch (err) {\n s.stop(\"Failed to connect to Chrome.\");\n throw err;\n }\n\n // ── Phase 2: Authenticate if needed ─────────────────────────────────\n const needsAuth =\n !config.credentials ||\n !config.credentials.expiresAt ||\n shouldRefresh(Date.now(), config.credentials.expiresAt, 0);\n\n if (needsAuth && options.headless) {\n await chromeManager.shutdown(state);\n throw new Error(\n \"Cannot authenticate in headless mode — no browser window to log in.\\n\" +\n \"Run `jh-gateway start` (without --headless) first to log in, then use --headless on subsequent runs.\",\n );\n }\n\n if (needsAuth) {\n s.start(\"Waiting for login (timeout: 300s)...\");\n try {\n // Navigate to JH login page\n await findOrOpenJhPage(state.browser);\n\n // Capture credentials with 300s timeout\n const creds = await captureCredentials(config.cdpUrl, 300_000);\n config.credentials = {\n bearerToken: creds.bearerToken,\n cookie: creds.cookie,\n userAgent: creds.userAgent,\n expiresAt: creds.expiresAt,\n };\n\n // Minimize Chrome window after successful auth\n await chromeManager.minimizeWindow(state);\n\n const expiryStr = creds.expiresAt\n ? new Date(creds.expiresAt * 1000).toLocaleString()\n : \"unknown\";\n s.stop(`Credentials captured. Token expires: ${expiryStr}`);\n } catch (err) {\n s.stop(\"Authentication failed.\");\n // Clean up Chrome if we launched it\n await chromeManager.shutdown(state);\n throw err;\n }\n } else {\n p.log.info(\"Valid credentials found, skipping login.\");\n }\n\n // ── Phase 3: Initialize PagePool, CredentialHolder, ReauthLock, Server\n s.start(`Starting server on port ${config.port}...`);\n\n // Find the seed page for the pool\n const seedPage = await findOrOpenJhPage(state.browser);\n\n const pool = new PagePool({\n maxPages,\n maxWaitMs: config.maxQueueWaitMs,\n });\n await pool.init(state.browser, seedPage);\n\n const credentialHolder = new CredentialHolder();\n if (config.credentials) {\n credentialHolder.set(config.credentials);\n }\n\n const _reauthLock = new ReauthLock();\n\n const serverHandle = await startServer(config, {\n getPool: () => pool,\n getCredentials: () => credentialHolder.get(),\n browser: state.browser,\n });\n\n s.stop(`Server running on port ${config.port}.`);\n\n // ── Phase 4: Start TokenRefresher background loop ───────────────────\n const tokenRefresher = new TokenRefresher(credentialHolder, config.cdpUrl);\n tokenRefresher.start();\n\n // ── Display success info ────────────────────────────────────────────\n const baseUrl = `http://127.0.0.1:${config.port}`;\n const apiKey = config.auth.token ?? \"(no auth required)\";\n\n p.log.success(`Gateway ready!`);\n p.log.info(`Base URL: ${baseUrl}`);\n p.log.info(`API Key: ${apiKey}`);\n p.log.info(\"Press Ctrl+C to stop.\");\n\n // ── Shutdown handler ────────────────────────────────────────────────\n const shutdown = async () => {\n tokenRefresher.stop();\n await serverHandle.close();\n await chromeManager.shutdown(state);\n };\n\n process.on(\"SIGINT\", async () => {\n await shutdown();\n process.exit(0);\n });\n process.on(\"SIGTERM\", async () => {\n await shutdown();\n process.exit(0);\n });\n}\n","import { existsSync } from \"node:fs\";\nimport { execSync, spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { chromium } from \"playwright-core\";\nimport type { Browser } from \"playwright-core\";\nimport type { ChildProcess } from \"node:child_process\";\nimport { getChromeWebSocketUrl } from \"./chrome-cdp.js\";\n\n// ── Interfaces ────────────────────────────────────────────────────────────────\n\nexport interface ChromeManagerOptions {\n /** CDP remote debugging port. Default: 9222 */\n cdpPort?: number;\n /** Launch Chrome in headless mode. Default: false */\n headless?: boolean;\n /** Chrome user data directory. Default: ~/.jh-gateway/chrome-profile */\n userDataDir?: string;\n}\n\nexport interface ChromeManagerState {\n browser: Browser;\n /** true if ChromeManager spawned the process */\n selfLaunched: boolean;\n /** Only set when selfLaunched is true */\n process?: ChildProcess;\n}\n\n// ── Chrome path constants ─────────────────────────────────────────────────────\n\nconst MACOS_CHROME_PATH =\n \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\";\n\nconst WINDOWS_CHROME_PATHS = [\n \"C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n];\n\nconst LINUX_CHROME_CANDIDATES = [\n \"google-chrome\",\n \"google-chrome-stable\",\n \"chromium-browser\",\n \"chromium\",\n];\n\n// ── ChromeManager ─────────────────────────────────────────────────────────────\n\nexport class ChromeManager {\n private readonly cdpPort: number;\n private readonly headless: boolean;\n private readonly userDataDir: string;\n\n constructor(options?: ChromeManagerOptions) {\n this.cdpPort = options?.cdpPort ?? 9222;\n this.headless = options?.headless ?? false;\n this.userDataDir =\n options?.userDataDir ?? join(homedir(), \".jh-gateway\", \"chrome-profile\");\n }\n\n /**\n * Try connecting to an existing Chrome instance at the configured CDP port.\n * If no instance is running, launch a new Chrome process and connect to it.\n */\n async connect(): Promise<ChromeManagerState> {\n const cdpUrl = `http://127.0.0.1:${this.cdpPort}`;\n\n // ── Phase 1: Try connecting to an existing Chrome instance ────────\n try {\n const wsUrl = await getChromeWebSocketUrl(cdpUrl);\n const browser = await chromium.connectOverCDP(wsUrl);\n return { browser, selfLaunched: false };\n } catch {\n // No existing Chrome — fall through to launch\n }\n\n // ── Phase 2: Find Chrome and launch it ───────────────────────────\n const chromePath = ChromeManager.findChromePath();\n if (!chromePath) {\n throw new Error(\n `Chrome executable not found. Please install Google Chrome or Chromium.\\n` +\n `Expected locations for ${process.platform}:\\n` +\n (process.platform === \"darwin\"\n ? ` - ${MACOS_CHROME_PATH}\\n`\n : process.platform === \"linux\"\n ? LINUX_CHROME_CANDIDATES.map((c) => ` - ${c} (via PATH)`).join(\"\\n\") + \"\\n\"\n : WINDOWS_CHROME_PATHS.map((p) => ` - ${p}`).join(\"\\n\") + \"\\n\"),\n );\n }\n\n const args = [\n `--remote-debugging-port=${this.cdpPort}`,\n `--user-data-dir=${this.userDataDir}`,\n \"--no-first-run\",\n \"--no-default-browser-check\",\n ];\n if (this.headless) {\n args.push(\"--headless=new\");\n }\n\n const child = spawn(chromePath, args, {\n detached: false,\n stdio: \"ignore\",\n });\n\n // ── Phase 3: Wait for CDP to become available ─────────────────────\n await this.waitForCdp(cdpUrl);\n\n // ── Phase 4: Connect via Playwright ───────────────────────────────\n const wsUrl = await getChromeWebSocketUrl(cdpUrl);\n const browser = await chromium.connectOverCDP(wsUrl);\n\n return { browser, selfLaunched: true, process: child };\n }\n\n /**\n * Poll the CDP /json/version endpoint until it responds, or timeout.\n */\n private async waitForCdp(\n cdpUrl: string,\n timeoutMs: number = 15_000,\n intervalMs: number = 250,\n ): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n\n while (Date.now() < deadline) {\n try {\n const res = await fetch(`${cdpUrl}/json/version`);\n if (res.ok) return;\n } catch {\n // Not ready yet\n }\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n\n throw new Error(\n `Chrome did not become available at ${cdpUrl} within ${timeoutMs / 1000}s`,\n );\n }\n\n /**\n * Terminate managed Chrome (only if selfLaunched).\n * Tries to close the browser connection gracefully first, then kills the process.\n */\n async shutdown(state: ChromeManagerState): Promise<void> {\n if (!state.selfLaunched || !state.process) {\n return;\n }\n\n try {\n await state.browser.close();\n } catch {\n // Browser may already be disconnected — ignore\n }\n\n try {\n state.process.kill(\"SIGTERM\");\n } catch {\n // Process may already be dead — ignore\n }\n }\n\n /**\n * Attempt to relaunch and reconnect to Chrome within 30s.\n * Returns a fresh ChromeManagerState.\n */\n async reconnect(): Promise<ChromeManagerState> {\n const RECONNECT_TIMEOUT_MS = 30_000;\n const deadline = Date.now() + RECONNECT_TIMEOUT_MS;\n\n while (Date.now() < deadline) {\n try {\n return await this.connect();\n } catch {\n await new Promise((r) => setTimeout(r, 1_000));\n }\n }\n\n throw new Error(\n `Failed to reconnect to Chrome within ${RECONNECT_TIMEOUT_MS / 1000}s`,\n );\n }\n\n /**\n * Minimize the Chrome window via CDP Browser.setWindowBounds.\n * This is best-effort — errors are silently caught.\n */\n async minimizeWindow(state: ChromeManagerState): Promise<void> {\n try {\n const page = state.browser.contexts()[0]?.pages()[0];\n if (!page) return;\n\n const cdpSession = await page.context().newCDPSession(page);\n const { windowId } = (await cdpSession.send(\n \"Browser.getWindowForTarget\",\n )) as { windowId: number };\n await cdpSession.send(\"Browser.setWindowBounds\", {\n windowId,\n bounds: { windowState: \"minimized\" },\n });\n } catch {\n // Minimizing is best-effort — silently ignore errors\n }\n }\n\n /**\n * Detect the Chrome/Chromium executable path for the current OS.\n * Returns the absolute path string, or `null` if no installation is found.\n */\n static findChromePath(): string | null {\n const platform = process.platform;\n\n if (platform === \"darwin\") {\n return existsSync(MACOS_CHROME_PATH) ? MACOS_CHROME_PATH : null;\n }\n\n if (platform === \"linux\") {\n for (const candidate of LINUX_CHROME_CANDIDATES) {\n try {\n const resolved = execSync(`which ${candidate}`, {\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }).trim();\n if (resolved) {\n return resolved;\n }\n } catch {\n // candidate not found, try next\n }\n }\n return null;\n }\n\n if (platform === \"win32\") {\n for (const winPath of WINDOWS_CHROME_PATHS) {\n if (existsSync(winPath)) {\n return winPath;\n }\n }\n return null;\n }\n\n // Unsupported platform\n return null;\n }\n}\n","import type { GatewayCredentials } from \"../infra/types.js\";\nimport { captureCredentials } from \"./auth-capture.js\";\nimport { updateConfig } from \"../infra/config.js\";\n\n/**\n * Thread-safe holder for the current gateway credentials.\n * After the first `set()`, `get()` will never return `null`.\n */\nexport class CredentialHolder {\n private creds: GatewayCredentials | null = null;\n\n /** Returns the current credentials, or `null` if none have been set yet. */\n get(): GatewayCredentials | null {\n return this.creds;\n }\n\n /** Atomically replaces the stored credentials. */\n set(creds: GatewayCredentials): void {\n this.creds = creds;\n }\n}\n\n/**\n * Returns true if the token should be refreshed.\n *\n * @param nowMs - Current time in milliseconds (e.g. Date.now())\n * @param expiresAt - JWT exp claim in unix seconds\n * @param thresholdMs - Refresh threshold in milliseconds\n */\nexport function shouldRefresh(\n nowMs: number,\n expiresAt: number,\n thresholdMs: number,\n): boolean {\n return expiresAt * 1000 - nowMs < thresholdMs;\n}\n\nexport interface TokenRefresherOptions {\n checkIntervalMs?: number; // default: 60_000\n refreshBeforeExpiryMs?: number; // default: 300_000 (5 min)\n maxRetries?: number; // default: 3\n}\n\nconst BACKOFF_DELAYS = [5_000, 15_000, 30_000];\n\n/**\n * Background service that monitors JWT expiry and proactively\n * re-captures credentials before they expire.\n */\nexport class TokenRefresher {\n private credentialHolder: CredentialHolder;\n private cdpUrl: string;\n private checkIntervalMs: number;\n private refreshBeforeExpiryMs: number;\n private maxRetries: number;\n private intervalId: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n credentialHolder: CredentialHolder,\n cdpUrl: string,\n options?: TokenRefresherOptions,\n ) {\n this.credentialHolder = credentialHolder;\n this.cdpUrl = cdpUrl;\n this.checkIntervalMs = options?.checkIntervalMs ?? 60_000;\n this.refreshBeforeExpiryMs = options?.refreshBeforeExpiryMs ?? 300_000;\n this.maxRetries = options?.maxRetries ?? 3;\n }\n\n /** Start the background check interval. */\n start(): void {\n if (this.intervalId !== null) return;\n this.intervalId = setInterval(() => {\n void this.checkAndRefresh();\n }, this.checkIntervalMs);\n }\n\n /** Stop the background check interval. */\n stop(): void {\n if (this.intervalId !== null) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n }\n\n /** Check if a refresh is needed and perform it. Returns true if refreshed. */\n async checkAndRefresh(): Promise<boolean> {\n const creds = this.credentialHolder.get();\n if (!creds || !creds.expiresAt) {\n return false;\n }\n\n if (!shouldRefresh(Date.now(), creds.expiresAt, this.refreshBeforeExpiryMs)) {\n return false;\n }\n\n // Attempt refresh with retries and backoff\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\n try {\n const newCreds = await captureCredentials(this.cdpUrl);\n const gatewayCreds: GatewayCredentials = {\n bearerToken: newCreds.bearerToken,\n cookie: newCreds.cookie,\n userAgent: newCreds.userAgent,\n expiresAt: newCreds.expiresAt,\n };\n\n this.credentialHolder.set(gatewayCreds);\n await updateConfig({ credentials: gatewayCreds });\n\n const expiryDate = new Date(newCreds.expiresAt * 1000).toISOString();\n console.log(`[TokenRefresher] Credentials refreshed successfully. New expiry: ${expiryDate}`);\n return true;\n } catch (err) {\n const delay = BACKOFF_DELAYS[attempt] ?? BACKOFF_DELAYS[BACKOFF_DELAYS.length - 1];\n if (attempt < this.maxRetries - 1) {\n console.warn(\n `[TokenRefresher] Refresh attempt ${attempt + 1}/${this.maxRetries} failed, retrying in ${delay / 1000}s...`,\n );\n await this.sleep(delay);\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(\n `\\n⚠️ [TokenRefresher] All ${this.maxRetries} refresh attempts failed. ${msg}\\n` +\n ` Continuing with current credentials. If requests start failing with 401,\\n` +\n ` restart with \\`jh-gateway start\\` (without --headless) to re-login.\\n`,\n );\n }\n }\n }\n\n return false;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { GatewayCredentials } from \"../infra/types.js\";\n\n/**\n * Deduplication lock ensuring only one credential re-capture runs at a time.\n * Concurrent callers share the same in-flight promise; after it settles the\n * lock resets so the next `acquire()` starts a fresh re-capture.\n */\nexport class ReauthLock {\n private inflight: Promise<GatewayCredentials> | null = null;\n\n async acquire(\n recaptureFn: () => Promise<GatewayCredentials>,\n ): Promise<GatewayCredentials> {\n if (this.inflight) {\n return this.inflight;\n }\n\n this.inflight = recaptureFn().finally(() => {\n this.inflight = null;\n });\n\n return this.inflight;\n }\n}\n","#!/usr/bin/env node\n/**\n * jh-gateway CLI entry point.\n * Registered as the `jh-gateway` bin in package.json.\n */\nimport { runSetup } from \"./cli/setup.js\";\nimport { runServe } from \"./cli/serve.js\";\nimport { runAuth } from \"./cli/auth.js\";\nimport { runConfig } from \"./cli/config.js\";\nimport { runStatus } from \"./cli/status.js\";\nimport { runLogs } from \"./cli/logs.js\";\nimport { runStart } from \"./cli/start.js\";\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nfunction parseFlags(argv: string[]): Record<string, string | boolean> {\n const flags: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith(\"--\")) {\n const key = arg.slice(2);\n const next = argv[i + 1];\n if (next !== undefined && !next.startsWith(\"--\")) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n }\n }\n return flags;\n}\n\nfunction printHelp(): void {\n console.log(`\njh-gateway — JH Web Gateway CLI\n\nUsage:\n jh-gateway <command> [options]\n\nCommands:\n start Launch Chrome, authenticate, and start the gateway server\n setup Interactive setup wizard (Chrome detection, auth, port)\n serve Start the HTTP server\n auth Re-capture JH credentials\n config Print current configuration (credentials redacted)\n status Show Chrome connection, token expiry, and gateway state\n logs Display recent request logs\n\nOptions:\n start --headless Launch Chrome in headless mode\n start --port <n> Override the configured port\n start --pages <n> Max concurrent browser pages (default: 3)\n serve --port <n> Override the configured port\n serve --pages <n> Max concurrent browser pages (default: 3)\n logs --limit <n> Number of log entries to show (default: 50)\n --help Show this help message\n`);\n}\n\nasync function main(): Promise<void> {\n if (!command || command === \"--help\" || command === \"-h\") {\n printHelp();\n process.exit(0);\n }\n\n const flags = parseFlags(args.slice(1));\n\n try {\n switch (command) {\n case \"start\": {\n const headless = flags[\"headless\"] === true;\n const startPortFlag = flags[\"port\"];\n const startPort =\n startPortFlag !== undefined && startPortFlag !== true\n ? Number(startPortFlag)\n : undefined;\n if (startPort !== undefined && (isNaN(startPort) || startPort < 1 || startPort > 65535)) {\n console.error(\"Error: --port must be a valid port number (1–65535)\");\n process.exit(1);\n }\n const startPagesFlag = flags[\"pages\"];\n const startPages =\n startPagesFlag !== undefined && startPagesFlag !== true\n ? Number(startPagesFlag)\n : undefined;\n if (startPages !== undefined && (isNaN(startPages) || startPages < 1 || startPages > 10)) {\n console.error(\"Error: --pages must be between 1 and 10\");\n process.exit(1);\n }\n await runStart({ headless, port: startPort, pages: startPages });\n break;\n }\n\n case \"setup\":\n await runSetup();\n break;\n\n case \"serve\": {\n const portFlag = flags[\"port\"];\n const port =\n portFlag !== undefined && portFlag !== true\n ? Number(portFlag)\n : undefined;\n if (port !== undefined && (isNaN(port) || port < 1 || port > 65535)) {\n console.error(\"Error: --port must be a valid port number (1–65535)\");\n process.exit(1);\n }\n const pagesFlag = flags[\"pages\"];\n const pages =\n pagesFlag !== undefined && pagesFlag !== true\n ? Number(pagesFlag)\n : undefined;\n if (pages !== undefined && (isNaN(pages) || pages < 1 || pages > 10)) {\n console.error(\"Error: --pages must be between 1 and 10\");\n process.exit(1);\n }\n await runServe({ port, pages });\n break;\n }\n\n case \"auth\":\n await runAuth();\n break;\n\n case \"config\":\n await runConfig();\n break;\n\n case \"status\":\n await runStatus();\n break;\n\n case \"logs\": {\n const limitFlag = flags[\"limit\"];\n const limit =\n limitFlag !== undefined && limitFlag !== true\n ? Number(limitFlag)\n : undefined;\n if (limit !== undefined && (isNaN(limit) || limit < 1)) {\n console.error(\"Error: --limit must be a positive number\");\n process.exit(1);\n }\n await runLogs({ limit });\n break;\n }\n\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exit(1);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Error: ${msg}`);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["context","page","spinner","Hono","Hono","Hono","Hono","text","randomBytes","text","args","randomBytes","randomBytes","MODEL_SET","Hono","readFile","mkdir","homedir","join","Hono","p","formatExpiry","formatExpiry","readFile","readdir","homedir","join","p","homedir","join","chromium","join","homedir","wsUrl","browser","chromium","p","args"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jh-web-gateway",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Standalone local HTTP server exposing an OpenAI-compatible API backed by the JH web platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"jh-gateway": "dist/cli.js",
|
|
8
|
+
"jh-web-gateway": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"release": "bash scripts/release.sh",
|
|
18
|
+
"prepublishOnly": "npm run build && npm test",
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"compile:macos-arm64": "bun build --compile --target=bun-darwin-arm64 src/cli.ts --outfile binaries/jh-gateway-macos-arm64",
|
|
22
|
+
"compile:macos-x64": "bun build --compile --target=bun-darwin-x64 src/cli.ts --outfile binaries/jh-gateway-macos-x64",
|
|
23
|
+
"compile:linux-x64": "bun build --compile --target=bun-linux-x64 src/cli.ts --outfile binaries/jh-gateway-linux-x64",
|
|
24
|
+
"compile:windows-x64": "bun build --compile --target=bun-windows-x64 src/cli.ts --outfile binaries/jh-gateway-windows-x64.exe",
|
|
25
|
+
"compile:all": "bun run compile:macos-arm64 && bun run compile:macos-x64 && bun run compile:linux-x64 && bun run compile:windows-x64",
|
|
26
|
+
"test": "vitest --run",
|
|
27
|
+
"test:coverage": "vitest --run --coverage"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@clack/prompts": "^0.10.0",
|
|
31
|
+
"@hono/node-server": "^1.14.0",
|
|
32
|
+
"hono": "^4.7.0",
|
|
33
|
+
"playwright-core": "^1.52.0",
|
|
34
|
+
"ws": "^8.18.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/ws": "^8.5.0",
|
|
38
|
+
"fast-check": "*",
|
|
39
|
+
"tsup": "^8.4.0",
|
|
40
|
+
"typescript": "^5.8.0",
|
|
41
|
+
"vitest": "^3.1.0"
|
|
42
|
+
}
|
|
43
|
+
}
|