import type { IncomingMessage, ServerResponse } from 'http'; import { mkdir, writeFile } from 'node:fs/promises'; import path from 'node:path'; import type { HostApiContext } from '../context'; import { importCurrentUserModelConfig } from './providers'; import { flushStreamingHeaders, parseJsonBody, sendJson, writeStreamingChunk } from '../route-utils'; import { createOpencodeClient, decorateOpencodeRequest, type OpencodeCommandInfo, type OpencodeCommandPartInput, type OpencodeFilePartInput, type OpencodePermissionReply, type RevertOpencodeSessionMessageInput, type SendOpencodeSessionMessageInput, } from '../../opencode/client'; import { readWorksPublishFile } from '../../opencode/works-publish-file'; import { readWorksDeployCheck } from '../../opencode/works-square-deploy-check'; import { buildOpencodeRuntimeConfigSummaryFromNianCodeProviders, type OpencodeRuntimeConfigSummary, } from '../../opencode/provider-config'; import { PLAYWRIGHT_MCP_SERVER_ID, resolvePlaywrightMcpServer, } from '../../opencode/playwright-mcp'; import { NIANCODE_USER_MODEL_ACCOUNT_ID } from '../../../shared/user-model-config'; import { listInstalledOpencodeSkills } from '../../opencode/skill-registry'; import { BUNDLED_COURSE_SKILL_IDS } from '../../opencode/superpowers'; import { logger } from '../../utils/logger'; import { getProviderService } from '../../services/providers/provider-service'; import { getValidWorksSquareAccessToken } from '../../services/works-square-session'; import { getHostApiToken } from '../server'; import { listProjectKnowledge, readProjectConfig, writeProjectConfig, type ProjectConfigReadResult, } from '../../opencode/project-config'; import { readProjectConversationState, writeProjectConversationState, } from '../../opencode/project-conversations'; import { completeProjectSession, removeProjectSessionMetadata, patchProjectSessionMetadata, upsertProjectSessionMetadata, type ProjectConversationState, } from '../../../shared/project-conversations'; import type { ProjectConfig } from '../../../shared/project-config'; import { initializeProjectDirectory } from '../../opencode/project-directory-initialization'; const bundledCourseSkillIds = new Set(BUNDLED_COURSE_SKILL_IDS); type ActiveProjectClient = ReturnType; type OpencodePromptModel = NonNullable; type RuntimeStatus = ReturnType; interface ExpectedUserModelProxyConfig { usesLocalProxy: boolean; baseUrl: string; modelIds: string[]; imageInputModelIds: string[]; modelLimits: Record; } const LOCAL_AI_PROXY_PATH = '/api/ai-proxy/v1'; const WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE = 'works_square_ai_gateway'; const DIRECT_WORKS_SQUARE_CREDENTIAL_REFRESH_SKEW_MS = 2 * 60 * 1000; const BASE64_DATA_URL_PATTERN = /^data:[^,]+;base64,/i; const COMMAND_TOKEN_PATTERN = /^[^\s/][^\s]*$/u; const COMMAND_CONTROL_PATTERN = /\p{Cc}/u; const COMMAND_IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u; const COMMAND_MODEL_PATTERN = /^[^/\s]{1,128}\/[^/\s]{1,128}$/u; const MAX_COMMAND_NAME = 256; const MAX_COMMAND_CONTEXT_TEXT = 2_000; const MAX_COMMAND_PARTS = 32; const MAX_COMMAND_ARGUMENTS = 8_192; const MAX_COMMAND_FILE_URL_CHARS = 32 * 1024 * 1024; const COMMAND_IMAGE_DATA_URL_PATTERN = /^data:(image\/[A-Za-z0-9.+-]+);base64,([A-Za-z0-9+/]*={0,2})$/iu; const STRICT_BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u; function asRecord(value: unknown): Record | null { if (!value || typeof value !== 'object' || Array.isArray(value)) return null; return value as Record; } function isValidCommandToken(value: string): boolean { return value.length <= MAX_COMMAND_NAME && COMMAND_TOKEN_PATTERN.test(value) && !COMMAND_CONTROL_PATTERN.test(value); } function normalizeCommandInfo(value: unknown): OpencodeCommandInfo | null { const record = asRecord(value); if (!record) return null; const name = typeof record.name === 'string' ? record.name.trim() : ''; if (!name || !isValidCommandToken(name)) return null; return { name, ...(typeof record.description === 'string' ? { description: record.description } : {}), ...(typeof record.agent === 'string' ? { agent: record.agent } : {}), ...(typeof record.model === 'string' ? { model: record.model } : {}), ...(record.source === 'command' || record.source === 'mcp' || record.source === 'skill' ? { source: record.source } : {}), ...(typeof record.subtask === 'boolean' ? { subtask: record.subtask } : {}), hints: Array.isArray(record.hints) ? record.hints.filter((hint): hint is string => typeof hint === 'string') : [], }; } function normalizeCommandParts(value: unknown): OpencodeCommandPartInput[] | null { if (value === undefined) return []; if (!Array.isArray(value) || value.length > MAX_COMMAND_PARTS) return null; const parts: OpencodeCommandPartInput[] = []; for (const item of value) { const record = asRecord(item); if (!record) return null; if (record.type === 'text') { if (record.synthetic !== true || typeof record.text !== 'string') return null; if ( !record.text || record.text.length > MAX_COMMAND_CONTEXT_TEXT || record.text.includes('\0') ) { return null; } parts.push({ type: 'text', text: record.text, synthetic: true }); continue; } if (record.type === 'file') { const mime = typeof record.mime === 'string' ? record.mime.trim().toLowerCase() : ''; const url = typeof record.url === 'string' ? record.url.trim() : ''; const filename = typeof record.filename === 'string' ? record.filename.trim() : ''; const match = url.length <= MAX_COMMAND_FILE_URL_CHARS ? url.match(COMMAND_IMAGE_DATA_URL_PATTERN) : null; const base64 = match?.[2] ?? ''; if ( !mime.startsWith('image/') || !match || match[1].toLowerCase() !== mime || !base64 || !STRICT_BASE64_PATTERN.test(base64) ) { return null; } parts.push({ type: 'file', mime, url, ...(filename ? { filename: filename.slice(0, 255) } : {}), }); continue; } return null; } return parts; } function parseRequiredCommandName(value: unknown): string | null { if (typeof value !== 'string') return null; const command = value.trim(); return command && isValidCommandToken(command) ? command : null; } function parseOptionalCommandIdentifier(value: unknown): string | undefined { if (typeof value !== 'string') return undefined; const candidate = value.trim(); return COMMAND_IDENTIFIER_PATTERN.test(candidate) ? candidate : undefined; } function parseOptionalCommandModel(value: unknown): string | undefined { if (typeof value !== 'string') return undefined; const candidate = value.trim(); return COMMAND_MODEL_PATTERN.test(candidate) ? candidate : undefined; } function normalizePromptFileParts(value: unknown): OpencodeFilePartInput[] { if (!Array.isArray(value)) return []; const files: OpencodeFilePartInput[] = []; for (const item of value) { const record = asRecord(item); if (!record || record.type !== 'file') continue; const mime = typeof record.mime === 'string' ? record.mime.trim() : ''; const url = typeof record.url === 'string' ? record.url.trim() : ''; if (!mime || !BASE64_DATA_URL_PATTERN.test(url)) continue; const filename = typeof record.filename === 'string' ? record.filename.trim() : ''; files.push({ type: 'file', mime, url, ...(filename ? { filename } : {}), }); } return files; } function promptModelLogRef(model: OpencodePromptModel | undefined): string | null { return model ? `${model.providerID}/${model.modelID}` : null; } function parseRuntimePromptModel(modelRef: unknown): OpencodePromptModel | undefined { if (typeof modelRef !== 'string') return undefined; const trimmed = modelRef.trim(); if (!trimmed) return undefined; const separatorIndex = trimmed.indexOf('/'); if (separatorIndex <= 0 || separatorIndex === trimmed.length - 1) return undefined; const providerID = trimmed.slice(0, separatorIndex).trim(); const modelID = trimmed.slice(separatorIndex + 1).trim(); if (!providerID || !modelID) return undefined; return { providerID, modelID }; } async function buildNianCodeRuntimeConfigSummary(): Promise { return await buildOpencodeRuntimeConfigSummaryFromNianCodeProviders({ mcpServers: { [PLAYWRIGHT_MCP_SERVER_ID]: resolvePlaywrightMcpServer(), }, }); } function publicRuntimeConfigSummary(summary: OpencodeRuntimeConfigSummary) { return { ...summary, ...(summary.providers ? { providers: summary.providers.map(({ baseURL: _baseURL, ...provider }) => provider), } : {}), }; } async function loadRuntimeConfigSummaryForPrompt(): Promise { try { return await buildNianCodeRuntimeConfigSummary(); } catch (error) { logger.warn('[opencode-route] Failed to resolve runtime prompt model', error); return undefined; } } let directWorksSquareCredentialRefreshPromise: Promise | null = null; async function refreshDirectWorksSquareCredentialBeforePrompt(ctx: HostApiContext): Promise { const account = await getProviderService().getAccount(NIANCODE_USER_MODEL_ACCOUNT_ID); if (account?.metadata?.worksSquareCredentialMode !== WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE) { return; } const expiresAt = Date.parse(account.metadata.worksSquareCredentialExpiresAt ?? ''); if ( Number.isFinite(expiresAt) && expiresAt > Date.now() + DIRECT_WORKS_SQUARE_CREDENTIAL_REFRESH_SKEW_MS ) { return; } const accessToken = await getValidWorksSquareAccessToken(); if (!accessToken) { throw new Error('Works Square access token unavailable; please log in again'); } await importCurrentUserModelConfig(ctx, accessToken); logger.info('[opencode-route] Refreshed direct Works Square AI gateway credential before prompt'); } async function ensureDirectWorksSquareCredentialBeforePrompt(ctx: HostApiContext): Promise { if (!directWorksSquareCredentialRefreshPromise) { directWorksSquareCredentialRefreshPromise = refreshDirectWorksSquareCredentialBeforePrompt(ctx) .finally(() => { directWorksSquareCredentialRefreshPromise = null; }); } await directWorksSquareCredentialRefreshPromise; } async function resolveRuntimePromptModel( summary?: OpencodeRuntimeConfigSummary, ): Promise { if (summary) { return parseRuntimePromptModel(summary.model); } return parseRuntimePromptModel((await loadRuntimeConfigSummaryForPrompt())?.model); } function normalizeRuntimeBaseUrl(value: unknown): string | null { if (typeof value !== 'string') return null; const trimmed = value.trim().replace(/\/+$/, ''); return trimmed || null; } function providerBaseUrlFromRuntimeConfig(config: unknown, providerId: string): string | null { const provider = providerFromRuntimeConfig(config, providerId); if (!provider) return null; const options = provider.options; if (!options || typeof options !== 'object' || Array.isArray(options)) return null; return normalizeRuntimeBaseUrl((options as Record).baseURL); } function providerFromRuntimeConfig(config: unknown, providerId: string): Record | null { if (!config || typeof config !== 'object' || Array.isArray(config)) return null; const providers = (config as Record).provider; if (!providers || typeof providers !== 'object' || Array.isArray(providers)) return null; const provider = (providers as Record)[providerId]; if (!provider || typeof provider !== 'object' || Array.isArray(provider)) return null; return provider as Record; } function providerModelsFromRuntimeConfig(config: unknown, providerId: string): Record | null { const provider = providerFromRuntimeConfig(config, providerId); return asRecord(provider?.models); } function runtimeModelHasImageInput(models: Record, modelId: string): boolean { const model = asRecord(models[modelId]); if (!model) return false; const modalities = asRecord(model.modalities); const modalityInput = Array.isArray(modalities?.input) ? modalities.input : []; if (modalityInput.some((item) => typeof item === 'string' && item.toLowerCase() === 'image')) { return true; } const capabilities = asRecord(model.capabilities); const capabilityInput = asRecord(capabilities?.input); return capabilityInput?.image === true; } function runtimeModelHasExpectedLimit( models: Record, modelId: string, expected: { context: number; output: number }, ): boolean { const model = asRecord(models[modelId]); const limit = asRecord(model?.limit); return limit?.context === expected.context && limit?.output === expected.output; } function isLocalAiProxyBaseUrl(baseUrl: string): boolean { if (baseUrl.includes('?') || baseUrl.includes('#')) return false; const rawAuthority = baseUrl.match(/^http:\/\/([^/]+)(?:\/|$)/i)?.[1]; if (!rawAuthority || !/^(?:127\.0\.0\.1|\[::1\])(?::\d+)?$/.test(rawAuthority)) { return false; } try { const url = new URL(baseUrl); const isLiteralLoopback = url.hostname === '127.0.0.1' || url.hostname === '[::1]'; return ( url.protocol === 'http:' && isLiteralLoopback && url.pathname.replace(/\/+$/, '') === LOCAL_AI_PROXY_PATH && url.username === '' && url.password === '' && url.search === '' && url.hash === '' ); } catch { return false; } } function expectedUserModelProxyConfig( summary: OpencodeRuntimeConfigSummary | undefined, ): ExpectedUserModelProxyConfig | null { const providers = Array.isArray(summary?.providers) ? summary.providers : []; const provider = providers.find((item) => item.id === NIANCODE_USER_MODEL_ACCOUNT_ID); if (!provider) return null; const configuredBaseUrl = normalizeRuntimeBaseUrl(provider.baseURL); if (!configuredBaseUrl) return null; const usesLocalProxy = isLocalAiProxyBaseUrl(configuredBaseUrl); const modelLimits = Object.fromEntries( Object.entries(provider.modelLimits ?? {}).map(([modelId, limit]) => [ modelId, { ...limit }, ]), ); if (!usesLocalProxy && Object.keys(modelLimits).length === 0) return null; const selectedModel = parseRuntimePromptModel(summary?.model); const modelIds = new Set(provider.modelIds ?? []); if (selectedModel?.providerID === NIANCODE_USER_MODEL_ACCOUNT_ID) { modelIds.add(selectedModel.modelID); } return { usesLocalProxy, baseUrl: configuredBaseUrl, modelIds: [...modelIds], imageInputModelIds: [...new Set(provider.imageInputModelIds ?? [])], modelLimits, }; } async function rebindLocalProxyHostApiTokenIfNeeded(expectedBaseUrl: string): Promise { const providerService = getProviderService(); const account = await providerService.getAccount(NIANCODE_USER_MODEL_ACCOUNT_ID); const accountBaseUrl = normalizeRuntimeBaseUrl(account?.baseUrl); if (!account || accountBaseUrl !== expectedBaseUrl) { return false; } const currentHostApiToken = getHostApiToken(); if (!currentHostApiToken) { return false; } const existingApiKey = await providerService.getAccountApiKey(NIANCODE_USER_MODEL_ACCOUNT_ID); if (existingApiKey === currentHostApiToken) { return false; } await providerService.updateAccount( NIANCODE_USER_MODEL_ACCOUNT_ID, account, currentHostApiToken, ); return true; } async function runtimeUsesExpectedUserModelProxy( status: RuntimeStatus, directory: string, expectedConfig: ExpectedUserModelProxyConfig, ): Promise { if (status.state !== 'running' || !status.url) return false; const client = createOpencodeClient({ baseUrl: status.url, directory, }); const runtimeConfig = await client.getConfig(); const actualBaseUrl = providerBaseUrlFromRuntimeConfig( runtimeConfig, NIANCODE_USER_MODEL_ACCOUNT_ID, ); if (actualBaseUrl !== expectedConfig.baseUrl) return false; const actualModels = providerModelsFromRuntimeConfig(runtimeConfig, NIANCODE_USER_MODEL_ACCOUNT_ID); if (!actualModels) return Object.keys(expectedConfig.modelLimits).length === 0; const actualModelIds = Object.keys(actualModels); if (!expectedConfig.modelIds.every((modelId) => actualModelIds.includes(modelId))) return false; if ( !expectedConfig.imageInputModelIds.every( (modelId) => runtimeModelHasImageInput(actualModels, modelId), ) ) { return false; } return Object.entries(expectedConfig.modelLimits).every(([modelId, limit]) => runtimeModelHasExpectedLimit(actualModels, modelId, limit), ); } async function ensureRuntimeUserModelProxyConfig( res: ServerResponse, ctx: HostApiContext, status: RuntimeStatus, directory: string, summary?: OpencodeRuntimeConfigSummary, ): Promise { let expectedConfig: ExpectedUserModelProxyConfig | null; try { expectedConfig = expectedUserModelProxyConfig( summary ?? await buildNianCodeRuntimeConfigSummary(), ); } catch (error) { logger.warn('[opencode-route] Failed to inspect generated runtime provider config', error); return status; } if (!expectedConfig) return status; try { if ( expectedConfig.usesLocalProxy && await rebindLocalProxyHostApiTokenIfNeeded(expectedConfig.baseUrl) ) { logger.warn('[opencode-route] Restarting runtime because local AI proxy Host API token changed'); const restartedStatus = await ctx.opencodeManager.restart(); if (restartedStatus.state !== 'running' || !restartedStatus.url) { sendJson(res, 409, { success: false, error: 'Runtime restarted but is not running', }); return null; } status = restartedStatus; } } catch (error) { logger.warn('[opencode-route] Failed to rebind local AI proxy Host API token', error); } try { if (await runtimeUsesExpectedUserModelProxy(status, directory, expectedConfig)) { return status; } } catch (error) { logger.warn('[opencode-route] Failed to inspect running runtime provider config', error); } logger.warn('[opencode-route] Restarting runtime because user model provider config is stale', { expectedModelIds: expectedConfig.modelIds, expectedImageInputModelIds: expectedConfig.imageInputModelIds, expectedLimitedModelIds: Object.keys(expectedConfig.modelLimits), }); const restartedStatus = await ctx.opencodeManager.restart(); if (restartedStatus.state !== 'running' || !restartedStatus.url) { sendJson(res, 409, { success: false, error: 'Runtime restarted but is not running', }); return null; } try { if (await runtimeUsesExpectedUserModelProxy(restartedStatus, directory, expectedConfig)) { return restartedStatus; } } catch (error) { logger.warn('[opencode-route] Failed to inspect restarted runtime provider config', error); } sendJson(res, 409, { success: false, error: 'Runtime provider configuration is stale. Please stop the runtime and start it again.', }); return null; } function buildNewProjectPath(parentPath: string | undefined, projectName: string | undefined): string { const normalizedParent = parentPath?.trim(); const normalizedName = projectName?.trim(); if (!normalizedParent) throw new Error('Missing project parent path'); if (!normalizedName) throw new Error('Missing project name'); if (normalizedName === '.' || normalizedName === '..') throw new Error('Invalid project name'); if (path.isAbsolute(normalizedName) || normalizedName.includes('/') || normalizedName.includes('\\') || normalizedName.includes('\0')) { throw new Error('Invalid project name'); } const parent = path.resolve(normalizedParent); const target = path.resolve(parent, normalizedName); const relative = path.relative(parent, target); if (relative.startsWith('..') || path.isAbsolute(relative)) { throw new Error('Invalid project name'); } return target; } function buildSelectedProjectPath(projectPath: string | undefined): string { const normalizedPath = projectPath?.trim(); if (!normalizedPath) throw new Error('Missing selected project path'); if (normalizedPath.includes('\0')) throw new Error('Invalid selected project path'); const target = path.resolve(normalizedPath); if (target === path.parse(target).root) { throw new Error('The filesystem root cannot be used as a project folder'); } return target; } async function sendProjectSnapshot( res: ServerResponse, ctx: HostApiContext, extra?: Record, ) { const [projects, activeProject] = await Promise.all([ ctx.opencodeProjectStore.listProjects(), ctx.opencodeProjectStore.getActiveProject(), ]); const validatedActiveProject = await getValidatedActiveProject(projects, activeProject); sendJson(res, 200, { ...extra, projects, activeProject: validatedActiveProject, }); } async function findProjectById(ctx: HostApiContext, projectId: string) { const projects = await ctx.opencodeProjectStore.listProjects(); return projects.find((project) => project.id === projectId) ?? null; } type ProjectActivationCheck = | { ok: true } | { ok: false; status: Exclude | 'incomplete'; error: string }; async function validateProjectForActivation(project: { path: string }): Promise { const result = await readProjectConfig(project.path); if (result.status === 'valid') return { ok: true }; return { ok: false, status: result.status, error: result.status === 'missing' ? 'Project configuration is missing' : result.error, }; } async function getValidatedActiveProject( projects: TProject[], activeProject: TProject | null | undefined, ): Promise { if (!activeProject) return null; const project = projects.find((candidate) => candidate.id === activeProject.id); if (!project) return null; const projectCheck = await validateProjectForActivation(project); return projectCheck.ok ? project : null; } function sendProjectActivationFailure( res: ServerResponse, projectCheck: Exclude, ) { sendJson(res, 409, { success: false, error: projectCheck.error, projectStatus: projectCheck.status, }); } async function getValidatedActiveProjectForRuntime( res: ServerResponse, ctx: HostApiContext, ): Promise { const activeProject = await ctx.opencodeProjectStore.getActiveProject() as TProject | null; if (!activeProject) { sendJson(res, 409, { success: false, error: 'No active project selected', }); return null; } const projectCheck = await validateProjectForActivation(activeProject); if (!projectCheck.ok) { sendProjectActivationFailure(res, projectCheck); return null; } const config = await readProjectConfig(activeProject.path); if (config.status !== 'valid' || !config.config.initialized) { sendJson(res, 409, { success: false, error: 'Project initialization is incomplete', projectStatus: 'incomplete' }); return null; } return activeProject; } async function createClientForActiveProject( res: ServerResponse, ctx: HostApiContext, options: { ensureUserModelProxyConfig?: boolean } = {}, runtimeConfigSummary?: OpencodeRuntimeConfigSummary, ): Promise { let status = ctx.opencodeManager.getStatus(); if (status.state !== 'running' || !status.url) { sendJson(res, 409, { success: false, error: 'Runtime is not running', }); return null; } const activeProject = await getValidatedActiveProjectForRuntime(res, ctx); if (!activeProject) { return null; } if (options.ensureUserModelProxyConfig) { const ensuredStatus = await ensureRuntimeUserModelProxyConfig( res, ctx, status, activeProject.path, runtimeConfigSummary, ); if (!ensuredStatus) return null; status = ensuredStatus; } return createOpencodeClient({ baseUrl: status.url, directory: activeProject.path, }); } async function getActiveProjectContext(res: ServerResponse, ctx: HostApiContext) { const status = ctx.opencodeManager.getStatus(); if (status.state !== 'running' || !status.url) { sendJson(res, 409, { success: false, error: 'Runtime is not running', }); return null; } const activeProject = await getValidatedActiveProjectForRuntime(res, ctx); if (!activeProject) { return null; } return { status, activeProject }; } function parseSseFrame(frame: string): { eventName: string; data: string } | null { const lines = frame.replace(/\r/g, '').split('\n'); let eventName = ''; const dataLines: string[] = []; for (const line of lines) { if (line.startsWith('event:')) { eventName = line.slice(6).trim(); continue; } if (line.startsWith('data:')) { dataLines.push(line.slice(5).trimStart()); } } if (dataLines.length === 0) return null; return { eventName, data: dataLines.join('\n'), }; } function getEventSessionId(input: unknown): string | null { if (!input || typeof input !== 'object') return null; const record = input as Record; if (typeof record.sessionID === 'string' && record.sessionID.trim()) return record.sessionID; if (typeof record.sessionId === 'string' && record.sessionId.trim()) return record.sessionId; return getEventSessionId(record.part) ?? getEventSessionId(record.info) ?? getEventSessionId(record.message) ?? null; } function isQuestionAnswers(input: unknown): input is string[][] { return Array.isArray(input) && input.every((answer) => ( Array.isArray(answer) && answer.every((item) => typeof item === 'string') )); } function isPermissionReply(input: unknown): input is OpencodePermissionReply { return input === 'once' || input === 'always' || input === 'reject'; } function normalizeRevertPayload(input: Record): RevertOpencodeSessionMessageInput | null { const messageID = typeof input.messageID === 'string' && input.messageID.trim() ? input.messageID.trim() : typeof input.messageId === 'string' && input.messageId.trim() ? input.messageId.trim() : null; if (!messageID) return null; const partID = typeof input.partID === 'string' && input.partID.trim() ? input.partID.trim() : typeof input.partId === 'string' && input.partId.trim() ? input.partId.trim() : undefined; return { messageID, ...(partID ? { partID } : {}), }; } function normalizeOpencodeEventFrame( frame: { eventName: string; data: string }, sessionFilter: string | null, ): { type: string; payload: unknown } | null { try { const raw = JSON.parse(frame.data) as unknown; const record = raw && typeof raw === 'object' ? raw as Record : null; const type = frame.eventName || (typeof record?.type === 'string' ? record.type : ''); const payload = record && 'properties' in record ? record.properties : raw; if (!type) return null; const sessionId = getEventSessionId(payload); if (sessionFilter && sessionId && sessionId !== sessionFilter) { return null; } if (sessionFilter && !sessionId && type !== 'server.connected' && type !== 'server.disconnected') { return null; } return { type, payload }; } catch { return null; } } function summarizeOpencodeSessionError(payload: unknown): { sessionID?: string; errorName?: string; message?: string; } | null { if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return null; const record = payload as Record; const error = record.error; const summary: { sessionID?: string; errorName?: string; message?: string; } = {}; if (typeof record.sessionID === 'string' && record.sessionID.trim()) { summary.sessionID = record.sessionID; } if (typeof error === 'string' && error.trim()) { summary.message = error; } else if (error && typeof error === 'object' && !Array.isArray(error)) { const errorRecord = error as Record; if (typeof errorRecord.name === 'string' && errorRecord.name.trim()) { summary.errorName = errorRecord.name; } const data = errorRecord.data; if (data && typeof data === 'object' && !Array.isArray(data)) { const dataMessage = (data as Record).message; if (typeof dataMessage === 'string' && dataMessage.trim()) { summary.message = dataMessage; } } if (!summary.message && typeof errorRecord.message === 'string' && errorRecord.message.trim()) { summary.message = errorRecord.message; } } return summary.sessionID || summary.errorName || summary.message ? summary : null; } export async function handleOpencodeRoutes( req: IncomingMessage, res: ServerResponse, url: URL, ctx: HostApiContext, ): Promise { if (url.pathname === '/api/opencode/events' && req.method === 'GET') { try { const context = await getActiveProjectContext(res, ctx); if (!context) return true; const decorated = decorateOpencodeRequest({ baseUrl: context.status.url!, path: '/event', directory: context.activeProject.path, }); const upstream = await globalThis.fetch(decorated.url, { method: 'GET', headers: { ...decorated.headers, Accept: 'text/event-stream', }, }); if (!upstream.ok || !upstream.body) { sendJson(res, 502, { success: false, error: `Failed to connect to event stream (${upstream.status})`, }); return true; } const sessionFilter = url.searchParams.get('sessionId')?.trim() ?? null; res.statusCode = 200; res.setHeader('Content-Type', 'text/event-stream; charset=utf-8'); res.setHeader('Cache-Control', 'no-cache, no-transform'); res.setHeader('Connection', 'keep-alive'); flushStreamingHeaders(res); if (!await writeStreamingChunk(res, ': connected\n\n')) { return true; } let buffer = ''; for await (const chunk of upstream.body as AsyncIterable) { buffer += Buffer.from(chunk).toString('utf8'); buffer = buffer.replace(/\r\n/g, '\n'); let boundary = buffer.indexOf('\n\n'); while (boundary >= 0) { const frameText = buffer.slice(0, boundary); buffer = buffer.slice(boundary + 2); const parsed = parseSseFrame(frameText); const normalized = parsed ? normalizeOpencodeEventFrame(parsed, sessionFilter) : null; if (normalized) { if (normalized.type === 'session.error') { const summary = summarizeOpencodeSessionError(normalized.payload); logger.warn('[opencode-events] session.error from runtime', summary ?? {}); } if (!await writeStreamingChunk( res, `event: ${normalized.type}\ndata: ${JSON.stringify(normalized.payload)}\n\n`, )) { return true; } } boundary = buffer.indexOf('\n\n'); } } res.end(); } catch (error) { if (res.headersSent) { if (!res.destroyed && !res.writableEnded) { res.end(); } } else { sendJson(res, 500, { success: false, error: String(error) }); } } return true; } if (url.pathname === '/api/opencode/projects' && req.method === 'GET') { try { await sendProjectSnapshot(res, ctx); } catch (error) { sendJson(res, 400, { success: false, error: error instanceof Error ? error.message : String(error) }); } return true; } if (url.pathname === '/api/opencode/projects/open' && req.method === 'POST') { try { const body = await parseJsonBody<{ path?: string }>(req); if (!body.path) throw new Error('Missing project path'); const project = await ctx.opencodeProjectStore.rememberProject(body.path); await sendProjectSnapshot(res, ctx, { success: true, project }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/projects/create' && req.method === 'POST') { try { const body = await parseJsonBody<{ projectPath?: string; parentPath?: string; projectName?: string; defaultModel?: unknown; }>(req); const defaultModel = body.defaultModel === undefined || body.defaultModel === null ? null : typeof body.defaultModel === 'string' && body.defaultModel.trim() ? body.defaultModel.trim() : (() => { throw new Error('Invalid default model'); })(); const useSelectedDirectory = typeof body.projectPath === 'string' && Boolean(body.projectPath.trim()); if (useSelectedDirectory && (body.parentPath?.trim() || body.projectName?.trim())) { throw new Error('Ambiguous project path input'); } const projectPath = useSelectedDirectory ? buildSelectedProjectPath(body.projectPath) : buildNewProjectPath(body.parentPath, body.projectName); const { config } = await initializeProjectDirectory({ projectPath, defaultModel, allowExistingDirectory: useSelectedDirectory, }); const project = await ctx.opencodeProjectStore.rememberProject(projectPath); await sendProjectSnapshot(res, ctx, { success: true, project, config }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/projects/config' && req.method === 'GET') { try { const projectId = url.searchParams.get('projectId')?.trim(); if (!projectId) throw new Error('Missing project id'); const project = await findProjectById(ctx, projectId); if (!project) throw new Error('Project not found'); const result = await readProjectConfig(project.path); const knowledgeFiles = result.status === 'valid' ? await listProjectKnowledge(project.path) : []; sendJson(res, 200, { ...result, knowledgeFiles }); } catch (error) { sendJson(res, 500, { status: 'invalid', error: error instanceof Error ? error.message : String(error), knowledgeFiles: [] }); } return true; } if (url.pathname === '/api/opencode/projects/config' && req.method === 'PUT') { try { const body = await parseJsonBody<{ projectId?: string; config?: ProjectConfig }>(req); if (!body.projectId || !body.config) throw new Error('Missing project configuration'); const project = await findProjectById(ctx, body.projectId); if (!project) throw new Error('Project not found'); const previousResult = await readProjectConfig(project.path); const previousConfig = previousResult.status === 'valid' ? previousResult.config : null; const config = await writeProjectConfig(project.path, body.config); let status = ctx.opencodeManager.getStatus(); const runtimeSettingsChanged = !previousConfig || previousConfig.defaultModel !== config.defaultModel || previousConfig.superpowersEnabled !== config.superpowersEnabled; if (config.initialized && runtimeSettingsChanged && status.state === 'running') { status = await ctx.opencodeManager.restart(); } sendJson(res, 200, { success: true, config, knowledgeFiles: await listProjectKnowledge(project.path), status }); } catch (error) { sendJson(res, 500, { success: false, error: error instanceof Error ? error.message : String(error) }); } return true; } if (url.pathname === '/api/opencode/projects/conversations' && req.method === 'GET') { try { const projectId = url.searchParams.get('projectId')?.trim(); if (!projectId) throw new Error('Missing project id'); const project = await findProjectById(ctx, projectId); if (!project) throw new Error('Project not found'); sendJson(res, 200, { state: await readProjectConversationState(project.path) }); } catch (error) { sendJson(res, 500, { success: false, error: error instanceof Error ? error.message : String(error) }); } return true; } if (url.pathname === '/api/opencode/projects/conversations' && req.method === 'POST') { try { const body = await parseJsonBody<{ projectId?: string; sessionId?: string; agentId?: string; action?: 'link' | 'archive' | 'restore' | 'delete' | 'read' | 'increment-unread' | 'complete'; unread?: boolean; }>(req); const projectId = body.projectId?.trim(); const sessionId = body.sessionId?.trim(); if (!projectId || !sessionId) throw new Error('Missing conversation identifiers'); const project = await findProjectById(ctx, projectId); if (!project) throw new Error('Project not found'); const state = await readProjectConversationState(project.path); let nextState: ProjectConversationState; const now = new Date().toISOString(); switch (body.action) { case 'link': if (!body.agentId?.trim()) throw new Error('Missing Agent id'); nextState = upsertProjectSessionMetadata(state, sessionId, body.agentId.trim(), now); break; case 'archive': nextState = patchProjectSessionMetadata(state, sessionId, { archivedAt: now }, now); break; case 'restore': nextState = patchProjectSessionMetadata(state, sessionId, { archivedAt: null }, now); break; case 'delete': nextState = removeProjectSessionMetadata(state, sessionId, now); break; case 'read': nextState = patchProjectSessionMetadata(state, sessionId, { unreadCount: 0 }, now); break; case 'increment-unread': { const current = state.sessions.find((item) => item.sessionId === sessionId); nextState = patchProjectSessionMetadata( state, sessionId, { unreadCount: (current?.unreadCount ?? 0) + 1 }, now, ); break; } case 'complete': nextState = completeProjectSession(state, sessionId, body.unread === true, now); break; default: throw new Error('Unknown conversation action'); } sendJson(res, 200, { success: true, state: await writeProjectConversationState(project.path, nextState) }); } catch (error) { sendJson(res, 500, { success: false, error: error instanceof Error ? error.message : String(error) }); } return true; } if (url.pathname === '/api/opencode/projects/knowledge' && req.method === 'POST') { try { const body = await parseJsonBody<{ projectId?: string; fileName?: string; contentBase64?: string }>(req); if (!body.projectId || !body.fileName || typeof body.contentBase64 !== 'string') throw new Error('Missing knowledge file'); const project = await findProjectById(ctx, body.projectId); if (!project) throw new Error('Project not found'); const config = await readProjectConfig(project.path); if (config.status !== 'valid') throw new Error('Project configuration is unavailable'); const fileName = path.basename(body.fileName.trim()); if (!fileName || fileName === '.' || fileName === '..' || fileName.includes('\0')) throw new Error('Invalid knowledge filename'); const content = Buffer.from(body.contentBase64, 'base64'); if (content.byteLength > 25 * 1024 * 1024) throw new Error('Knowledge file exceeds 25 MB'); const directory = path.join(project.path, 'knowledge'); await mkdir(directory, { recursive: true }); await writeFile(path.join(directory, fileName), content, { flag: 'wx' }); sendJson(res, 200, { success: true, knowledgeFiles: await listProjectKnowledge(project.path) }); } catch (error) { sendJson(res, 500, { success: false, error: error instanceof Error ? error.message : String(error) }); } return true; } const worksPublishMatch = url.pathname.match(/^\/api\/opencode\/projects\/([^/]+)\/works-publish$/); if (worksPublishMatch && req.method === 'GET') { try { const projectId = decodeURIComponent(worksPublishMatch[1] ?? ''); if (!projectId) throw new Error('Missing project id'); const project = await findProjectById(ctx, projectId); if (!project) throw new Error('Project not found'); sendJson(res, 200, await readWorksPublishFile(project.path)); } catch (error) { sendJson(res, 500, { status: 'invalid', error: error instanceof Error ? error.message : String(error), }); } return true; } const worksDeployCheckMatch = url.pathname.match(/^\/api\/opencode\/projects\/([^/]+)\/works-deploy-check$/); if (worksDeployCheckMatch && req.method === 'GET') { try { const projectId = decodeURIComponent(worksDeployCheckMatch[1] ?? ''); if (!projectId) throw new Error('Missing project id'); const project = await findProjectById(ctx, projectId); if (!project) throw new Error('Project not found'); const publish = await readWorksPublishFile(project.path); sendJson( res, 200, await readWorksDeployCheck(project.path, publish.status === 'ready' ? publish.publish : null), ); } catch (error) { sendJson(res, 500, { status: 'invalid', filePath: '', error: error instanceof Error ? error.message : String(error), }); } return true; } const worksCloudDeployMatch = url.pathname.match(/^\/api\/opencode\/projects\/([^/]+)\/works-cloud-deploy$/); if (worksCloudDeployMatch && (req.method === 'GET' || req.method === 'POST')) { try { const projectId = decodeURIComponent(worksCloudDeployMatch[1] ?? ''); if (!projectId) throw new Error('Missing project id'); if (!ctx.worksCloudDeployment) throw new Error('Cloud deployment coordinator is unavailable'); if (req.method === 'POST') { const deployment = await ctx.worksCloudDeployment.arm(projectId); sendJson(res, 202, { success: true, deployment }); } else { sendJson(res, 200, { success: true, deployment: await ctx.worksCloudDeployment.get(projectId) }); } } catch (error) { sendJson(res, 500, { success: false, error: error instanceof Error ? error.message : String(error), }); } return true; } if (url.pathname === '/api/opencode/projects/remove' && req.method === 'POST') { try { const body = await parseJsonBody<{ projectId?: string }>(req); if (!body.projectId) throw new Error('Missing project id'); await ctx.opencodeProjectStore.removeProject(body.projectId); await sendProjectSnapshot(res, ctx, { success: true, removedProjectId: body.projectId }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/projects/active' && req.method === 'GET') { try { const [projects, activeProject] = await Promise.all([ ctx.opencodeProjectStore.listProjects(), ctx.opencodeProjectStore.getActiveProject(), ]); sendJson(res, 200, await getValidatedActiveProject(projects, activeProject)); } catch (error) { sendJson(res, 400, { success: false, error: error instanceof Error ? error.message : String(error) }); } return true; } if (url.pathname === '/api/opencode/projects/active' && req.method === 'POST') { try { const body = await parseJsonBody<{ projectId?: string }>(req); if (!body.projectId) throw new Error('Missing project id'); const requestedProject = await findProjectById(ctx, body.projectId); if (!requestedProject) throw new Error('Project not found'); const projectCheck = await validateProjectForActivation(requestedProject); if (!projectCheck.ok) { sendProjectActivationFailure(res, projectCheck); return true; } const project = await ctx.opencodeProjectStore.setActiveProject(body.projectId); await sendProjectSnapshot(res, ctx, { success: true, project }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/commands' && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const [commands, runtimeConfig] = await Promise.all([ client.listCommands(), client.getConfig(), ]); sendJson(res, 200, { commands: commands.flatMap((command) => { const normalized = normalizeCommandInfo(command); return normalized ? [normalized] : []; }), shareEnabled: asRecord(runtimeConfig)?.share !== 'disabled', }); } catch (error) { sendJson(res, 500, { success: false, error: error instanceof Error ? error.message : String(error), }); } return true; } if (url.pathname === '/api/opencode/sessions' && req.method === 'GET') { const client = await createClientForActiveProject(res, ctx); if (!client) return true; sendJson(res, 200, { sessions: await client.listSessions() }); return true; } if (url.pathname === '/api/opencode/skills' && req.method === 'GET') { const getManagedConfigDir = 'getManagedConfigDir' in ctx.opencodeManager ? ctx.opencodeManager.getManagedConfigDir.bind(ctx.opencodeManager) : undefined; const installedSkills = (await listInstalledOpencodeSkills(getManagedConfigDir?.())) .filter((skill) => bundledCourseSkillIds.has(skill.name)); sendJson(res, 200, { skills: installedSkills }); return true; } if (url.pathname === '/api/opencode/sessions' && req.method === 'POST') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const body = await parseJsonBody>(req); sendJson(res, 200, { success: true, session: await client.createSession(body) }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionForkMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)\/fork$/); if (sessionForkMatch && req.method === 'POST') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const body = await parseJsonBody<{ messageID?: unknown }>(req); const messageID = typeof body.messageID === 'string' && body.messageID.trim() ? body.messageID.trim() : undefined; const sessionID = decodeURIComponent(sessionForkMatch[1]); const session = await client.forkSession(sessionID, messageID ? { messageID } : {}); sendJson(res, 200, { success: true, session }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionShareMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)\/share$/); if (sessionShareMatch && (req.method === 'POST' || req.method === 'DELETE')) { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const sessionID = decodeURIComponent(sessionShareMatch[1]); const session = req.method === 'POST' ? await client.shareSession(sessionID) : await client.unshareSession(sessionID); sendJson(res, 200, { success: true, session }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionSummarizeMatch = url.pathname.match( /^\/api\/opencode\/sessions\/([^/]+)\/summarize$/, ); if (sessionSummarizeMatch && req.method === 'POST') { try { await ensureDirectWorksSquareCredentialBeforePrompt(ctx); const runtimeConfigSummary = await loadRuntimeConfigSummaryForPrompt(); const client = await createClientForActiveProject( res, ctx, { ensureUserModelProxyConfig: true }, runtimeConfigSummary, ); if (!client) return true; const body = await parseJsonBody<{ model?: unknown }>(req); const explicitModel = body.model === undefined ? undefined : parseRuntimePromptModel(body.model); if (body.model !== undefined && !explicitModel) { sendJson(res, 400, { success: false, error: 'Invalid model reference' }); return true; } const model = explicitModel ?? await resolveRuntimePromptModel(runtimeConfigSummary); if (!model) { sendJson(res, 409, { success: false, error: 'No runtime model configured' }); return true; } const sessionID = decodeURIComponent(sessionSummarizeMatch[1]); await client.summarizeSession(sessionID, model); sendJson(res, 202, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionCommandMatch = url.pathname.match( /^\/api\/opencode\/sessions\/([^/]+)\/command$/, ); if (sessionCommandMatch && req.method === 'POST') { try { await ensureDirectWorksSquareCredentialBeforePrompt(ctx); const runtimeConfigSummary = await loadRuntimeConfigSummaryForPrompt(); const client = await createClientForActiveProject( res, ctx, { ensureUserModelProxyConfig: true }, runtimeConfigSummary, ); if (!client) return true; const body = await parseJsonBody<{ command?: unknown; arguments?: unknown; agent?: unknown; model?: unknown; variant?: unknown; parts?: unknown; }>(req); const command = parseRequiredCommandName(body.command); const argumentsText = typeof body.arguments === 'string' && body.arguments.length <= MAX_COMMAND_ARGUMENTS ? body.arguments : null; const parts = normalizeCommandParts(body.parts); if (!command || argumentsText === null || !parts) { sendJson(res, 400, { success: false, error: 'Invalid command payload' }); return true; } const sessionID = decodeURIComponent(sessionCommandMatch[1]); const agent = parseOptionalCommandIdentifier(body.agent); const model = parseOptionalCommandModel(body.model); const variant = parseOptionalCommandIdentifier(body.variant); const invalidOptionalField = ( (body.agent !== undefined && !agent) || (body.model !== undefined && !model) || (body.variant !== undefined && !variant) ); if (invalidOptionalField) { sendJson(res, 400, { success: false, error: 'Invalid command runtime context' }); return true; } logger.info('[opencode-route] Executing session command', { sessionID, command, partCount: parts.length, fileMimes: parts.flatMap((part) => part.type === 'file' ? [part.mime] : []), }); await client.executeSessionCommand(sessionID, { command, arguments: argumentsText, ...(agent ? { agent } : {}), ...(model ? { model } : {}), ...(variant ? { variant } : {}), ...(parts.length ? { parts } : {}), }); sendJson(res, 202, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/sessions/status' && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; sendJson(res, 200, { statuses: await client.getSessionStatuses() }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/questions' && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; sendJson(res, 200, { questions: await client.listQuestions() }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const questionReplyMatch = url.pathname.match(/^\/api\/opencode\/questions\/([^/]+)\/reply$/); if (questionReplyMatch && req.method === 'POST') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const body = await parseJsonBody<{ answers?: unknown }>(req); if (!isQuestionAnswers(body.answers)) { sendJson(res, 400, { success: false, error: 'Invalid question answers' }); return true; } const requestID = decodeURIComponent(questionReplyMatch[1]); await client.replyQuestion(requestID, body.answers); sendJson(res, 200, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const questionRejectMatch = url.pathname.match(/^\/api\/opencode\/questions\/([^/]+)\/reject$/); if (questionRejectMatch && req.method === 'POST') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const requestID = decodeURIComponent(questionRejectMatch[1]); await client.rejectQuestion(requestID); sendJson(res, 200, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/permissions' && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const permissions = await client.listPermissions(); sendJson(res, 200, { permissions }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const permissionReplyMatch = url.pathname.match(/^\/api\/opencode\/permissions\/([^/]+)\/reply$/); if (permissionReplyMatch && req.method === 'POST') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const body = await parseJsonBody<{ reply?: unknown; message?: unknown }>(req); if (!isPermissionReply(body.reply)) { sendJson(res, 400, { success: false, error: 'Invalid permission reply' }); return true; } const message = typeof body.message === 'string' && body.message.trim() ? body.message.trim() : undefined; const requestID = decodeURIComponent(permissionReplyMatch[1]); await client.replyPermission(requestID, body.reply, message); sendJson(res, 200, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/files/status' && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; sendJson(res, 200, { files: await client.getFileStatuses() }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/files/find' && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const query = url.searchParams.get('query')?.trim() ?? ''; if (!query) { sendJson(res, 400, { success: false, error: 'Missing file query' }); return true; } const limit = Number(url.searchParams.get('limit')); sendJson(res, 200, { files: await client.findFiles(query, { limit: Number.isFinite(limit) && limit > 0 ? limit : undefined, }), }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/search' && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const pattern = url.searchParams.get('pattern')?.trim() ?? ''; if (!pattern) { sendJson(res, 400, { success: false, error: 'Missing search pattern' }); return true; } sendJson(res, 200, { matches: await client.searchText(pattern), }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/files/content' && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const filePath = url.searchParams.get('path')?.trim() ?? ''; if (!filePath) { sendJson(res, 400, { success: false, error: 'Missing file path' }); return true; } sendJson(res, 200, { file: await client.getFileContent(filePath) }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)$/); if (sessionMatch && req.method === 'PATCH') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const body = await parseJsonBody<{ title?: string }>(req); const title = typeof body.title === 'string' ? body.title.trim() : ''; if (!title) { sendJson(res, 400, { success: false, error: 'Missing session title' }); return true; } const sessionID = decodeURIComponent(sessionMatch[1]); sendJson(res, 200, { success: true, session: await client.updateSession(sessionID, { title }), }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (sessionMatch && req.method === 'DELETE') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const sessionID = decodeURIComponent(sessionMatch[1]); await client.deleteSession(sessionID); sendJson(res, 200, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionAbortMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)\/abort$/); if (sessionAbortMatch && req.method === 'POST') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const sessionID = decodeURIComponent(sessionAbortMatch[1]); await client.abortSession(sessionID); sendJson(res, 200, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionDiffMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)\/diff$/); if (sessionDiffMatch && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const sessionID = decodeURIComponent(sessionDiffMatch[1]); sendJson(res, 200, { diffs: await client.getSessionDiff(sessionID) }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionTodosMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)\/todos$/); if (sessionTodosMatch && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const sessionID = decodeURIComponent(sessionTodosMatch[1]); sendJson(res, 200, { todos: await client.getSessionTodos(sessionID) }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionRevertMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)\/revert$/); if (sessionRevertMatch && req.method === 'POST') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const body = await parseJsonBody>(req); const payload = normalizeRevertPayload(body); if (!payload) { sendJson(res, 400, { success: false, error: 'Missing message id' }); return true; } const sessionID = decodeURIComponent(sessionRevertMatch[1]); const reverted = await client.revertSessionMessage(sessionID, payload); sendJson(res, 200, { success: true, session: reverted }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionUnrevertMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)\/unrevert$/); if (sessionUnrevertMatch && req.method === 'POST') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const sessionID = decodeURIComponent(sessionUnrevertMatch[1]); const restored = await client.unrevertSession(sessionID); sendJson(res, 200, { success: true, session: restored }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } const sessionMessagesMatch = url.pathname.match(/^\/api\/opencode\/sessions\/([^/]+)\/messages$/); if (sessionMessagesMatch && req.method === 'GET') { try { const client = await createClientForActiveProject(res, ctx); if (!client) return true; const sessionID = decodeURIComponent(sessionMessagesMatch[1]); sendJson(res, 200, { messages: await client.getSessionMessages(sessionID) }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (sessionMessagesMatch && req.method === 'POST') { try { await ensureDirectWorksSquareCredentialBeforePrompt(ctx); const runtimeConfigSummary = await loadRuntimeConfigSummaryForPrompt(); const client = await createClientForActiveProject( res, ctx, { ensureUserModelProxyConfig: true }, runtimeConfigSummary, ); if (!client) return true; const body = await parseJsonBody<{ text?: string; files?: unknown; userContext?: unknown; agent?: unknown; model?: unknown; }>(req); const text = typeof body.text === 'string' ? body.text.trim() : ''; if (!text) { sendJson(res, 400, { success: false, error: 'Missing message text' }); return true; } const sessionID = decodeURIComponent(sessionMessagesMatch[1]); const explicitModel = body.model === undefined ? undefined : parseRuntimePromptModel(body.model); if (body.model !== undefined && !explicitModel) { sendJson(res, 400, { success: false, error: 'Invalid model reference' }); return true; } const model = explicitModel ?? await resolveRuntimePromptModel(runtimeConfigSummary); const files = normalizePromptFileParts(body.files); const userContext = typeof body.userContext === 'string' ? body.userContext.trim().slice(0, 2_000) : ''; const agent = typeof body.agent === 'string' && /^[a-z0-9][a-z0-9-]{0,63}$/.test(body.agent.trim()) ? body.agent.trim() : ''; logger.info('[opencode-route] Starting session prompt', { sessionID, runtimeUrl: ctx.opencodeManager.getStatus().url ?? null, model: promptModelLogRef(model), textLength: text.length, fileCount: files.length, fileMimes: files.map((file) => file.mime), }); await client.promptSessionAsync(sessionID, { text, ...(userContext ? { system: userContext } : {}), ...(agent ? { agent } : {}), ...(files.length ? { files } : {}), ...(model ? { model } : {}), }); sendJson(res, 202, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/status' && req.method === 'GET') { sendJson(res, 200, ctx.opencodeManager.getStatus()); return true; } if (url.pathname === '/api/opencode/start' && req.method === 'POST') { try { const status = await ctx.opencodeManager.start(); sendJson(res, 200, { success: true, status }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/stop' && req.method === 'POST') { try { await ctx.opencodeManager.stop(); sendJson(res, 200, { success: true }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/restart' && req.method === 'POST') { try { const status = await ctx.opencodeManager.restart(); sendJson(res, 200, { success: true, status }); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } if (url.pathname === '/api/opencode/health' && req.method === 'GET') { sendJson(res, 200, await ctx.opencodeManager.checkHealth()); return true; } if (url.pathname === '/api/opencode/config-summary' && req.method === 'GET') { try { sendJson(res, 200, publicRuntimeConfigSummary(await buildNianCodeRuntimeConfigSummary())); } catch (error) { sendJson(res, 500, { success: false, error: String(error) }); } return true; } return false; }