import type { AdRuntime } from "./components/ads/types"; import type { Locale } from "./i18n/runtime"; export type ModelType = "openai" | "anthropic"; export interface Model { model_hash: string; sort_order: number; display_name: string; type: ModelType; base_url: string; use_full_url: boolean; api_key: string; tooltip_data: string; model_id: string; reasoning_effort: string | null; openai_endpoint: string; openai_extra_params_enabled: boolean; openai_extra_params: Record; custom_headers_enabled: boolean; custom_headers: Record; anthropic_extra_params_enabled: boolean; anthropic_extra_params: Record; context_window_tokens: number | null; max_completion_tokens: number | null; anthropic_max_tokens: number | null; anthropic_thinking_effort: string | null; thinking_budget_tokens: number | null; created_at_ms: number; updated_at_ms: number; } export interface ModelInput { sort_order: number; display_name: string; type: ModelType; base_url: string; use_full_url: boolean; api_key: string; tooltip_data: string; model_id: string; reasoning_effort: string | null; openai_endpoint: string; openai_extra_params_enabled: boolean; openai_extra_params: Record; custom_headers_enabled: boolean; custom_headers: Record; anthropic_extra_params_enabled: boolean; anthropic_extra_params: Record; context_window_tokens: number | null; max_completion_tokens: number | null; anthropic_max_tokens: number | null; anthropic_thinking_effort: string | null; thinking_budget_tokens: number | null; } export interface ModelDiscoveryInput { type: ModelType; base_url: string; api_key: string; custom_headers_enabled: boolean; custom_headers: Record; } export interface LegacyModelImportPreviewItem { model_hash: string; display_name: string; model_id: string; type: ModelType; existing: boolean; } export interface LegacyModelImportPreview { source: string; total: number; new_models: number; existing_models: number; models: LegacyModelImportPreviewItem[]; } export interface LegacyModelImportResult { imported: number; skipped: number; total: number; } export interface ModelConnectivityResult { duration_ms: number; first_text_ms: number | null; output_tokens: number; tokens_per_second: number; tokens_estimated: boolean; output: string; } export type CaState = "missing" | "untrusted" | "ready" | "invalid"; export type IntegrationState = "disabled" | "enabled" | "degraded"; export interface CursorHarnessStatus { platform: string; ca: CaState; configured_models: number; enabled_models: number; integration: IntegrationState; proxy_url: string | null; ca_install_command: string | null; } export interface PortSettings { proxy_port: number; service_port: number; } export interface StatisticsStorage { bytes: number; call_count: number; trace_count: number; } export type StatisticsStorageScope = "details" | "all"; export type ProxyMode = "system" | "custom"; export interface ProxySettings { mode: ProxyMode; address: string; auth_enabled: boolean; username: string; has_password: boolean; } export interface ProxySettingsInput { mode: ProxyMode; address: string; auth_enabled: boolean; username: string; password?: string; } export type TabMode = "public" | "direct" | "custom"; export interface TabSettings { mode: TabMode; address: string; } export interface DesktopSettings { silent_start: boolean; show_dock_icon: boolean; } export interface OverviewMetrics { llm_calls: number; successful_calls: number; failed_calls: number; token_usage: number; prompt_tokens: number; input_tokens: number; cache_read_tokens: number; cache_write_tokens: number; output_tokens: number; } export type TokenUsageGranularity = "minute" | "hour" | "day"; export interface OverviewTokenUsageBucket { bucket_start_ms: number; input_tokens: number; cache_read_tokens: number; cache_write_tokens: number; output_tokens: number; } export interface Overview { metrics: OverviewMetrics; token_usage_granularity: TokenUsageGranularity; token_usage_series: OverviewTokenUsageBucket[]; } export interface LlmCall { call_kind: "provider_llm" | "cursor_official"; route: "local_byok" | "cursor_official"; call_id: string; run_id: string; conversation_id: string; provider_call_index: number; model_hash: string | null; provider_type: string; provider_url: string; request_type: string; request_url: string; model_id: string; display_name: string; reasoning_effort: string | null; fast: boolean | null; status: string; finish_reason: string | null; created_at_ms: number; ttfb_ms: number | null; ttft_ms: number | null; duration_ms: number | null; input_tokens: number | null; output_tokens: number | null; total_tokens: number | null; cache_read_tokens: number | null; cache_write_tokens: number | null; reasoning_tokens: number | null; message_count: number; tool_count: number; http_status: number | null; error_kind: string | null; error_message: string | null; detailed: boolean; } export interface CallDetail { call: LlmCall; request: { headers: unknown; body: unknown; byte_count: number } | null; response_chunks: Array<{ seq: number; received_offset_ms: number; data: string; byte_count: number }>; cursor_trace: { trace: { request_id: string; conversation_id: string | null; route: "local_byok" | "cursor_official"; model_id: string | null; status: string; request_bytes: number; response_bytes: number; response_event_count: number; http_status: number | null; received_at_ms: number; first_response_at_ms: number | null; finished_at_ms: number | null; error_message: string | null; }; artifacts: Array<{ seq: number; artifact_type: string; source: string; metadata: unknown; created_at_ms: number; byte_count: number; encoding: "utf8" | "base64"; data: string; }>; } | null; } const packagedDesktop = "__TAURI_INTERNALS__" in window || window.location.protocol === "tauri:" || window.location.hostname === "tauri.localhost"; const API_ROOT = "/__byok-api__/api"; async function request(path: string, init?: RequestInit): Promise { let response: Response; try { response = await fetch(`${API_ROOT}${path}`, { ...init, headers: init?.body ? { "content-type": "application/json", ...init.headers } : init?.headers, }); } catch (cause) { throw new Error(t("无法连接本地管理服务"), { cause }); } if (!response.ok) { const body = await response.text(); let message = body; try { const parsed = JSON.parse(body) as { message?: unknown }; if (typeof parsed.message === "string") message = parsed.message; } catch { // Plain-text errors are already suitable for display. } throw new Error(message || `${response.status} ${response.statusText}`); } if (response.status === 204) return undefined as T; return response.json() as Promise; } export const api = { ads: (disabledAdIds: Iterable, locale: Locale) => { const value = [...disabledAdIds].join(","); return request("/ads", { headers: { "accept-language": locale, ...(value ? { "disable-ad-ids": value } : {}), }, }); }, dismissAd: (id: string, reason: string) => request(`/ads/${encodeURIComponent(id)}/dismissals`, { method: "POST", body: JSON.stringify({ reason }) }), models: () => request("/models"), createModels: (models: ModelInput[]) => request("/models", { method: "POST", body: JSON.stringify({ models }) }), reorderModels: (modelHashes: string[]) => request("/models/order", { method: "PUT", body: JSON.stringify({ model_hashes: modelHashes }) }), discoverModels: (input: ModelDiscoveryInput) => request<{ models: string[] }>("/models/discover", { method: "POST", body: JSON.stringify(input) }), previewV0049Models: () => request("/models/import-v0049"), importV0049Models: () => request("/models/import-v0049", { method: "POST" }), updateModel: (hash: string, model: ModelInput) => request(`/models/${hash}`, { method: "PUT", body: JSON.stringify(model) }), deleteModel: (hash: string) => request(`/models/${hash}`, { method: "DELETE" }), testModel: (hash: string, testId: string, signal?: AbortSignal) => request(`/models/${hash}/test/${encodeURIComponent(testId)}`, { method: "POST", signal }), cancelModelTest: (hash: string, testId: string) => request(`/models/${hash}/test/${encodeURIComponent(testId)}`, { method: "DELETE" }), overview: (filter?: { startMs: number; endMs: number; modelHashes?: string[] }) => { const params = new URLSearchParams(); if (filter) { params.set("start_ms", String(filter.startMs)); params.set("end_ms", String(filter.endMs)); if (filter.modelHashes?.length) params.set("model_hashes", JSON.stringify(filter.modelHashes)); } const query = params.toString(); return request(`/overview${query ? `?${query}` : ""}`); }, cursorHarness: () => request("/harness/cursor/status"), initializeCursorCa: () => request("/harness/cursor/ca/initialize", { method: "POST" }), openCursorCaInstallTerminal: async (command: string) => { if (!packagedDesktop) throw new Error(t("请在桌面应用中打开终端安装 CA")); const { invoke } = await import("@tauri-apps/api/core"); await invoke("open_terminal_with_command", { command }); }, copyCursorText: async (text: string) => { if (!packagedDesktop) throw new Error(t("请在桌面应用中复制到系统剪贴板")); const { writeText } = await import("@tauri-apps/plugin-clipboard-manager"); await writeText(text); }, setCursorEnabled: (enabled: boolean) => request("/harness/cursor/enabled", { method: "PUT", body: JSON.stringify({ enabled }) }), calls: () => request("/llm-calls?limit=200"), call: (id: string) => request(`/llm-calls/${encodeURIComponent(id)}`), openCallDetails: async (id: string) => { const url = new URL(window.location.href); url.hash = `/calls/${encodeURIComponent(id)}`; await request("/desktop/open-external-url", { method: "POST", body: JSON.stringify({ url: url.toString() }) }); }, openExternalUrl: (url: string) => request("/desktop/open-external-url", { method: "POST", body: JSON.stringify({ url }) }), observability: () => request<{ detailed: boolean }>("/settings/observability"), setObservability: (detailed: boolean) => request<{ detailed: boolean }>("/settings/observability", { method: "PUT", body: JSON.stringify({ detailed }) }), ports: () => request("/settings/ports"), setPorts: (settings: PortSettings) => request("/settings/ports", { method: "PUT", body: JSON.stringify(settings) }), statisticsStorage: () => request("/settings/storage/statistics"), clearStatisticsStorage: (scope: StatisticsStorageScope) => request("/settings/storage/statistics", { method: "DELETE", body: JSON.stringify({ scope }) }), proxySettings: () => request("/settings/proxy"), setProxySettings: (settings: ProxySettingsInput) => request("/settings/proxy", { method: "PUT", body: JSON.stringify(settings) }), tabSettings: () => request("/settings/tab"), setTabSettings: (settings: TabSettings) => request("/settings/tab", { method: "PUT", body: JSON.stringify(settings) }), desktopSettings: () => request("/settings/desktop"), setDesktopSettings: (settings: DesktopSettings) => request("/settings/desktop", { method: "PUT", body: JSON.stringify(settings) }), };