@yejiming/dsh-data-agent 0.0.1
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/LICENSE +21 -0
- package/README.en.md +185 -0
- package/README.md +185 -0
- package/cordis.patch.yml +16 -0
- package/lib/client.js +1157 -0
- package/lib/client.js.map +1 -0
- package/lib/defaults-D__D30ED.js +283 -0
- package/lib/index.js +153 -0
- package/lib/invariant.js +22 -0
- package/lib/query-DAjhNTo8.js +86 -0
- package/lib/routes.js +250 -0
- package/lib/tool.js +107 -0
- package/lib/types/client/DataAgentWorkbench.d.ts +22 -0
- package/lib/types/client/index.d.ts +27 -0
- package/lib/types/client/locales.d.ts +95 -0
- package/lib/types/client/persistence.d.ts +37 -0
- package/lib/types/clients.d.ts +96 -0
- package/lib/types/connections.d.ts +70 -0
- package/lib/types/defaults.d.ts +20 -0
- package/lib/types/index.d.ts +143 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/query.d.ts +60 -0
- package/lib/types/routes.d.ts +107 -0
- package/lib/types/tool.d.ts +61 -0
- package/package.json +102 -0
- package/preset/data-agent/agent.cordis.yml +45 -0
- package/preset/data-agent/preset.yml +3 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["useRef","useState","css","StateDot","Modal"],"sources":["../src/client/persistence.ts","../src/client/DataAgentWorkbench.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Connection-config persistence for the database workbench. The most recent\n * successful connection (type/host/port/user/database/password) is kept in\n * localStorage under one key so remounts and restarts can restore the form\n * and auto-reconnect (the server-side connection store stays in-memory).\n *\n * Security note: the password is persisted in PLAIN TEXT by explicit user\n * decision (local single-user scenario) — see README 安全说明. The storage\n * key is versioned so a future shape change can migrate or ignore old data.\n * @module @yejiming/dsh-data-agent/persistence\n */\n\nimport type { DatabaseType } from './DataAgentWorkbench.tsx'\n\n/** localStorage key holding the most recent connection configuration. */\nexport const CONNECTION_STORAGE_KEY = 'dsh-data-agent.connection.v1'\n\n/** The persisted connection configuration (password included). */\nexport interface SavedConnection {\n type: DatabaseType\n host?: string\n port?: number\n user?: string\n database: string\n password?: string\n /** Diagnostic timestamp of the save. */\n savedAt: string\n}\n\n/** Runtime storage face (injectable for tests). */\nexport interface StorageLike {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\n/** The default storage face (localStorage; unavailable → degraded no-op). */\nfunction defaultStorage(): StorageLike | undefined {\n try {\n if (typeof localStorage !== 'undefined') {\n const probe = '__dsh_probe__'\n localStorage.setItem(probe, '1')\n localStorage.removeItem(probe)\n return localStorage\n }\n } catch {\n // localStorage disabled (privacy mode, sandboxed frame): degrade silently.\n }\n return undefined\n}\n\n/** Validate one parsed storage value into a SavedConnection (or null). */\nfunction parseSaved(value: unknown): SavedConnection | null {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return null\n const candidate = value as Record<string, unknown>\n const type = candidate.type\n if (type !== 'mysql' && type !== 'postgres' && type !== 'sqlite'\n && type !== 'oracle' && type !== 'hive' && type !== 'impala') {\n return null\n }\n const database = candidate.database\n // 草稿语义:database 允许为空串(用户可能只填了部分字段就切换会话)。\n if (typeof database !== 'string') return null\n const saved: SavedConnection = { type, database, savedAt: typeof candidate.savedAt === 'string' ? candidate.savedAt : '' }\n if (typeof candidate.host === 'string') saved.host = candidate.host\n if (typeof candidate.port === 'number' && Number.isInteger(candidate.port)) saved.port = candidate.port\n if (typeof candidate.user === 'string') saved.user = candidate.user\n if (typeof candidate.password === 'string') saved.password = candidate.password\n return saved\n}\n\n/** Save one connection configuration (best-effort; storage failures degrade silently). */\nexport function saveConnection(connection: SavedConnection, storage: StorageLike | undefined = defaultStorage()): void {\n if (storage === undefined) return\n try {\n storage.setItem(CONNECTION_STORAGE_KEY, JSON.stringify(connection))\n } catch {\n // Quota / serialization failure: degrade silently, the UI stays usable.\n }\n}\n\n/** Load the saved connection configuration; null when absent or malformed. */\nexport function loadConnection(storage: StorageLike | undefined = defaultStorage()): SavedConnection | null {\n if (storage === undefined) return null\n try {\n const raw = storage.getItem(CONNECTION_STORAGE_KEY)\n if (raw === null) return null\n return parseSaved(JSON.parse(raw))\n } catch {\n return null\n }\n}\n\n/** Remove the saved connection configuration. */\nexport function clearConnection(storage: StorageLike | undefined = defaultStorage()): void {\n if (storage === undefined) return\n try {\n storage.removeItem(CONNECTION_STORAGE_KEY)\n } catch {\n // Degrade silently.\n }\n}\n","/**\n * The database workbench: connection config (collapsible after connect),\n * a browse button that opens the schema explorer Modal, and the SQL command\n * box, rendered into the composer input dock (the strip ABOVE the input bar)\n * for data-agent sessions.\n *\n * Layout is phase-driven by the conversation root's `data-phase` attribute:\n * - hero (blank session): the workbench is a full-width stacked strip above\n * the input bar (which stays at the bottom);\n * - active (conversation started): the workbench becomes a fixed left rail\n * (measured from the conversation column), the chat records and the input\n * bar shift right via the `da-split` CSS rules; if measurement is\n * unavailable the workbench falls back to a docked bottom panel.\n *\n * Interaction model:\n * - after a successful connect the connection form collapses into a summary\n * row (click 连接配置 to expand the readonly form again);\n * - the schema explorer lives in a Modal (ui-primitives) opened by the\n * 库表 button; a single click on a database toggles its table list in a\n * file-explorer style tree (folder rows with chevrons, indented table rows\n * with guide lines, the whole tree scrolls inside the column);\n * - clicking a table loads its columns into the Modal's structure panel.\n *\n * Non-data-agent sessions render null — zero impact on ordinary sessions.\n * Connection state lives on the server (the dataAgentConnections store), so\n * remounts never lose it — this component mirrors `/status` on mount.\n */\nimport { useEffect, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'\n// Type-only: pulls the ui-conversation slot declarations (conversation.input.dock)\n// and the framework-standard view props into this program.\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport {\n loadConnection,\n saveConnection,\n type SavedConnection,\n} from './persistence.ts'\nimport css from './DataAgentWorkbench.module.css'\n\n/** The plugin's preset id, matching the installed agent preset directory. */\nconst DATA_AGENT_PRESET = 'data-agent'\n\n/** 16×16 stroke icons drawn inline (the primitives package does not export icon atoms). */\nfunction Icon({ className, children, size = 14 }: { className?: string; children: ReactNode; size?: number }) {\n return (\n <svg className={className} width={size} height={size} viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n {children}\n </svg>\n )\n}\n\n/** Database cylinder: section header of the connection form. */\nfunction DatabaseIcon({ className }: { className?: string }) {\n return (\n <Icon className={className}>\n <ellipse cx=\"8\" cy=\"4\" rx=\"5.5\" ry=\"2.1\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n <path d=\"M2.5 4V12C2.5 13.16 4.96 14.1 8 14.1C11.04 14.1 13.5 13.16 13.5 12V4\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n <path d=\"M2.5 8C2.5 9.16 4.96 10.1 8 10.1C11.04 10.1 13.5 9.16 13.5 8\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n </Icon>\n )\n}\n\n/** Table grid: browse row + table rows in the tree. */\nfunction TableIcon({ className }: { className?: string }) {\n return (\n <Icon className={className}>\n <rect x=\"2\" y=\"3\" width=\"12\" height=\"10\" rx=\"1.5\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n <path d=\"M2 6.5H14\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n <path d=\"M6.5 6.5V13\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n </Icon>\n )\n}\n\n/** Folder: schema rows in the tree. */\nfunction FolderIcon({ className }: { className?: string }) {\n return (\n <Icon className={className}>\n <path\n d=\"M1.8 4.6C1.8 4.05 2.25 3.6 2.8 3.6H5.9L7.1 5.1H13.2C13.75 5.1 14.2 5.55 14.2 6.1V11.4C14.2 11.95 13.75 12.4 13.2 12.4H2.8C2.25 12.4 1.8 11.95 1.8 11.4V4.6Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.2\"\n strokeLinejoin=\"round\"\n />\n </Icon>\n )\n}\n\n/** Terminal prompt: section header of the SQL box. */\nfunction TerminalIcon({ className }: { className?: string }) {\n return (\n <Icon className={className}>\n <rect x=\"1.5\" y=\"2.5\" width=\"13\" height=\"11\" rx=\"2\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n <path d=\"M4.5 6L6.5 8L4.5 10\" stroke=\"currentColor\" strokeWidth=\"1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <path d=\"M8 10.5H11.2\" stroke=\"currentColor\" strokeWidth=\"1.2\" strokeLinecap=\"round\" />\n </Icon>\n )\n}\n\n/** Chevron: tree expand indicator (rotates 90° when open). */\nfunction ChevronIcon({ className }: { className?: string }) {\n return (\n <svg className={className} width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M4.5 2.5L8 6L4.5 9.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\n/** Alert triangle: error bar heading. */\nfunction AlertIcon({ className }: { className?: string }) {\n return (\n <Icon className={className}>\n <path d=\"M8 2.2L14.2 13H1.8L8 2.2Z\" stroke=\"currentColor\" strokeWidth=\"1.2\" strokeLinejoin=\"round\" />\n <path d=\"M8 6.4V9.2\" stroke=\"currentColor\" strokeWidth=\"1.2\" strokeLinecap=\"round\" />\n <circle cx=\"8\" cy=\"11\" r=\"0.9\" fill=\"currentColor\" />\n </Icon>\n )\n}\n\n/** Play triangle: SQL run button. */\nfunction PlayIcon({ className }: { className?: string }) {\n return (\n <svg className={className} width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M3.4 2.1C3.4 1.66 3.88 1.4 4.26 1.62L9.9 5.02C10.26 5.23 10.26 5.77 9.9 5.98L4.26 9.38C3.88 9.6 3.4 9.34 3.4 8.9V2.1Z\" />\n </svg>\n )\n}\n\n/** Database kinds offered by the connection form. */\nexport type DatabaseType = 'mysql' | 'postgres' | 'sqlite' | 'oracle' | 'hive' | 'impala'\n\n/** The sessions-list slice the workbench needs (structural; avoids a runtime import). */\nexport interface SessionListLike {\n byId: Record<string, { agentPreset?: string }>\n}\n\n/** One described column. */\ninterface ColumnInfo {\n name: string\n type: string\n nullable?: boolean\n}\n\n/** Wire shapes of the plugin routes. */\ninterface ConnectResponse { ok: boolean; tables?: string[]; error?: string }\ninterface StatusResponse {\n connected: boolean\n summary?: {\n type: DatabaseType\n host?: string\n port?: number\n user?: string\n database: string\n tables?: string[]\n }\n}\ninterface SchemasResponse { ok: boolean; schemas?: string[]; error?: string }\ninterface TablesResponse { ok: boolean; tables?: string[]; error?: string }\ninterface DescribeResponse { ok: boolean; columns?: ColumnInfo[]; error?: string }\ninterface QueryResponse {\n ok: boolean\n result?: { exitCode: number | null; stdout: string; stderr: string; truncated: boolean }\n error?: string\n}\n\n/** Registration-side business face: the sessions-list observable becomes `useSessions`. */\nexport interface DataAgentWorkbenchInjected {\n hooks: {\n sessions: {\n getSnapshot(): SessionListLike\n subscribe(fn: () => void): () => void\n }\n }\n}\n\n/** The workbench's full component props: the dock seat + the locale seat + the injected sessions hook. */\nexport type DataAgentWorkbenchProps =\n PropsRuntime<'conversation.input.dock'>\n & PropsLocale<'data-agent'>\n & InjectFace<DataAgentWorkbenchInjected>\n\n/** The conversation column's phase attribute value while the session is blank. */\nfunction isHeroPhase(element: HTMLElement | null): boolean {\n return element?.getAttribute('data-phase') === 'hero'\n}\n\n/**\n * 开始对话后(active 布局)左栏工作台相对会话列顶部的下移偏移(px):\n * 让工作台避开会话头部区域、整体往下沉一些,顶部露出对话记录。\n */\nconst RAIL_TOP_OFFSET = 96\n\n/** Default port per type (used to fill the form from a saved connection). */\nfunction defaultPortOf(type: DatabaseType): string {\n switch (type) {\n case 'postgres': return '5432'\n case 'oracle': return '1521'\n case 'hive': return '10000'\n case 'impala': return '21050'\n case 'sqlite': return ''\n case 'mysql': return '3306'\n }\n}\n\n/** Run one /connect request (shared by the form connect and mount auto-reconnect). */\nasync function performConnect(sessionId: string, body: Record<string, unknown>): Promise<ConnectResponse> {\n const response = await fetch('/plugins/data-agent/connect', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ sessionId, ...body }),\n })\n return response.json() as Promise<ConnectResponse>\n}\n\n/** Build the /connect payload from a saved connection. */\nfunction payloadFromSaved(saved: SavedConnection): Record<string, unknown> {\n if (saved.type === 'sqlite') return { type: saved.type, database: saved.database }\n const body: Record<string, unknown> = {\n type: saved.type,\n host: saved.host ?? '127.0.0.1',\n user: saved.user ?? '',\n database: saved.database,\n }\n if (saved.port !== undefined) body.port = saved.port\n if (saved.password !== undefined && saved.password !== '') body.password = saved.password\n return body\n}\n\n/** The database workbench body. */\nexport function DataAgentWorkbench({ sessionId, useSessions, t }: DataAgentWorkbenchProps) {\n const list = useSessions(snapshot => snapshot)\n const isDataAgent = list.byId[sessionId as never]?.agentPreset === DATA_AGENT_PRESET\n\n const rootRef = useRef<HTMLDivElement | null>(null)\n const [phase, setPhase] = useState<'hero' | 'active'>('hero')\n const [railRect, setRailRect] = useState<{ left: number; top: number; bottom: number } | null>(null)\n\n // 表单从已保存的连接配置(localStorage)惰性初始化:切换会话/刷新/重启后回填。\n const [initialSaved] = useState(loadConnection)\n const [type, setType] = useState<DatabaseType>(initialSaved?.type ?? 'mysql')\n const [host, setHost] = useState(initialSaved?.host ?? '127.0.0.1')\n const [port, setPort] = useState(\n initialSaved?.port !== undefined ? String(initialSaved.port) : defaultPortOf(initialSaved?.type ?? 'mysql'),\n )\n const [user, setUser] = useState(initialSaved?.user ?? '')\n const [password, setPassword] = useState(initialSaved?.password ?? '')\n const [database, setDatabase] = useState(initialSaved?.database ?? '')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [connected, setConnected] = useState(false)\n\n // Connection form collapsed into a summary row once connected.\n const [configOpen, setConfigOpen] = useState(false)\n // Mount-time auto-reconnect in flight (from the saved connection).\n const [restoring, setRestoring] = useState(false)\n // Schema explorer Modal.\n const [schemaModalOpen, setSchemaModalOpen] = useState(false)\n\n const [schemas, setSchemas] = useState<string[]>([])\n const [activeSchema, setActiveSchema] = useState<string | null>(null)\n const [tables, setTables] = useState<string[]>([])\n const [activeTable, setActiveTable] = useState<string | null>(null)\n const [columns, setColumns] = useState<ColumnInfo[] | null>(null)\n\n const [sql, setSql] = useState('')\n const [sqlBusy, setSqlBusy] = useState(false)\n const [sqlResult, setSqlResult] = useState<string | null>(null)\n\n // Track the conversation column phase and its bounds for the left-rail\n // layout; toggle the da-split class that shifts chat + input to the right.\n useEffect(() => {\n const column = rootRef.current?.closest<HTMLElement>('[data-phase]') ?? null\n if (column === null) return\n const measure = (): void => {\n const rect = column.getBoundingClientRect()\n setRailRect({ left: rect.left, top: rect.top, bottom: window.innerHeight - rect.bottom })\n }\n const refreshPhase = (): void => { setPhase(isHeroPhase(column) ? 'hero' : 'active') }\n refreshPhase()\n measure()\n const observer = new MutationObserver(refreshPhase)\n observer.observe(column, { attributes: true, attributeFilter: ['data-phase'] })\n const resizer = new ResizeObserver(measure)\n resizer.observe(column)\n window.addEventListener('resize', measure)\n return () => {\n observer.disconnect()\n resizer.disconnect()\n window.removeEventListener('resize', measure)\n }\n }, [])\n\n // Split layout is active only for a live conversation (phase active) with\n // a measurable column; everything else stays stacked above the input.\n const split = phase === 'active' && railRect !== null\n useEffect(() => {\n const root = document.documentElement\n const splitClass = css['da-split']\n if (split && splitClass !== undefined) root.classList.add(splitClass)\n else if (splitClass !== undefined) root.classList.remove(splitClass)\n return () => { if (splitClass !== undefined) root.classList.remove(splitClass) }\n }, [split])\n\n // 草稿持久化:任何表单字段变化立即保存(含密码,用户已确认),\n // 使未连接的输入在切换会话/刷新后也能恢复。首轮跳过(初始化回填值\n // 无需重写,也避免从未输入时写入空配置)。\n const firstDraftRun = useRef(true)\n useEffect(() => {\n if (firstDraftRun.current) {\n firstDraftRun.current = false\n return\n }\n const draft: SavedConnection = {\n type,\n database,\n ...type !== 'sqlite' ? { host, user } : {},\n ...type !== 'sqlite' && port !== '' ? { port: Number(port) } : {},\n ...password !== '' ? { password } : {},\n savedAt: new Date().toISOString(),\n }\n saveConnection(draft)\n }, [type, host, port, user, database, password])\n\n // Mirror the server-side connection on mount; auto-reconnect once when the\n // server store was lost (restart).\n useEffect(() => {\n let cancelled = false\n const saved = initialSaved\n setBusy(true)\n fetch(`/plugins/data-agent/status?sessionId=${encodeURIComponent(sessionId)}`)\n .then(response => response.json() as Promise<StatusResponse>)\n .then(async (body) => {\n if (cancelled) return\n if (body.connected) {\n setConnected(true)\n if (body.summary !== undefined) {\n setType(body.summary.type)\n setHost(body.summary.host ?? '')\n setPort(body.summary.port !== undefined ? String(body.summary.port) : '')\n setUser(body.summary.user ?? '')\n setDatabase(body.summary.database)\n }\n const response = await fetch(`/plugins/data-agent/schemas?sessionId=${encodeURIComponent(sessionId)}`)\n const schemasBody = await response.json() as SchemasResponse\n if (!cancelled && schemasBody.ok) setSchemas(schemasBody.schemas ?? [])\n return\n }\n // Server connection lost (restart): restore with the saved config, once.\n // 草稿未完成(database 为空)时不自动重连,仅保留表单回填。\n if (saved !== null && saved.database !== '') {\n setRestoring(true)\n try {\n const result = await performConnect(sessionId, payloadFromSaved(saved))\n if (cancelled) return\n if (result.ok) {\n setConnected(true)\n setConfigOpen(false)\n const response = await fetch(`/plugins/data-agent/schemas?sessionId=${encodeURIComponent(sessionId)}`)\n const schemasBody = await response.json() as SchemasResponse\n if (!cancelled && schemasBody.ok) setSchemas(schemasBody.schemas ?? [])\n } else {\n setError(`连接恢复失败:${result.error ?? 'unknown error'}`)\n }\n } catch (cause) {\n if (!cancelled) {\n setError(`连接恢复失败:${cause instanceof Error ? cause.message : String(cause)}`)\n }\n } finally {\n if (!cancelled) setRestoring(false)\n }\n }\n })\n .catch(() => { /* the form stays usable; connect will surface errors */ })\n .finally(() => { if (!cancelled) setBusy(false) })\n return () => { cancelled = true }\n }, [sessionId])\n\n const sqlite = type === 'sqlite'\n\n const connect = async (): Promise<void> => {\n setBusy(true)\n setError(null)\n const body: Record<string, unknown> = { sessionId, type }\n if (sqlite) {\n body.database = database\n } else {\n body.host = host\n if (port !== '') body.port = Number(port)\n body.user = user\n body.database = database\n if (password !== '') body.password = password\n }\n try {\n const result = await performConnect(sessionId, body)\n if (result.ok) {\n setConnected(true)\n // Collapse the form into the summary row. 密码保留在输入框(草稿已持久化)。\n setConfigOpen(false)\n const schemasResponse = await fetch(`/plugins/data-agent/schemas?sessionId=${encodeURIComponent(sessionId)}`)\n const schemasBody = await schemasResponse.json() as SchemasResponse\n if (schemasBody.ok) setSchemas(schemasBody.schemas ?? [])\n } else {\n setError(result.error ?? 'unknown error')\n }\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }\n\n const disconnect = async (): Promise<void> => {\n setBusy(true)\n setError(null)\n try {\n await fetch('/plugins/data-agent/disconnect', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ sessionId }),\n })\n setConnected(false)\n setConfigOpen(false)\n setSchemaModalOpen(false)\n setSchemas([])\n setActiveSchema(null)\n setTables([])\n setActiveTable(null)\n setColumns(null)\n setSqlResult(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }\n\n /** Toggle one schema's table list (single click; only one open at a time). */\n const toggleSchema = async (schema: string): Promise<void> => {\n if (activeSchema === schema) {\n setActiveSchema(null)\n setTables([])\n setActiveTable(null)\n setColumns(null)\n return\n }\n setActiveSchema(schema)\n setActiveTable(null)\n setColumns(null)\n setTables([])\n try {\n const response = await fetch(\n `/plugins/data-agent/tables?sessionId=${encodeURIComponent(sessionId)}&schema=${encodeURIComponent(schema)}`,\n )\n const result = await response.json() as TablesResponse\n if (result.ok) setTables(result.tables ?? [])\n else setError(result.error ?? 'unknown error')\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const selectTable = async (table: string): Promise<void> => {\n setActiveTable(table)\n setColumns(null)\n const params = new URLSearchParams({ sessionId, table })\n if (activeSchema !== null) params.set('schema', activeSchema)\n try {\n const response = await fetch(`/plugins/data-agent/describe?${params.toString()}`)\n const result = await response.json() as DescribeResponse\n if (result.ok) setColumns(result.columns ?? [])\n else setError(result.error ?? 'unknown error')\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const runSql = async (): Promise<void> => {\n if (sql.trim() === '') return\n setSqlBusy(true)\n setSqlResult(null)\n try {\n const response = await fetch('/plugins/data-agent/query', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ sessionId, sql }),\n })\n const result = await response.json() as QueryResponse\n if (result.ok && result.result !== undefined) {\n const parts: string[] = []\n if (result.result.stdout !== '') parts.push(result.result.stdout)\n if (result.result.stderr !== '') parts.push(`[stderr]\\n${result.result.stderr}`)\n if (result.result.truncated) parts.push('… 输出超过上限,已截断')\n if (result.result.exitCode !== 0) parts.push(`[exit code: ${result.result.exitCode ?? 'signal'}]`)\n setSqlResult(parts.join('\\n'))\n } else {\n setSqlResult(`Error: ${result.error ?? 'unknown error'}`)\n }\n } catch (cause) {\n setSqlResult(`Error: ${cause instanceof Error ? cause.message : String(cause)}`)\n } finally {\n setSqlBusy(false)\n }\n }\n\n // A session not running the data-agent preset: nothing renders at all.\n if (!isDataAgent) return null\n\n const databaseLabel = sqlite\n ? t('form.database.sqlite')\n : type === 'oracle'\n ? t('form.database.oracle')\n : type === 'hive' || type === 'impala'\n ? t('form.database.hive')\n : t('form.database')\n\n const railStyle = split\n ? {\n position: 'fixed' as const,\n left: railRect!.left,\n top: railRect!.top + RAIL_TOP_OFFSET,\n bottom: railRect!.bottom,\n width: 380,\n }\n : undefined\n\n // Form fields are readonly while connected (edit requires disconnecting).\n const formDisabled = busy || connected\n\n return (\n <div\n ref={rootRef}\n className={`${css.workbench} ${split ? css.rail : phase === 'active' ? css.docked : css.strip}`}\n style={railStyle}\n >\n <div className={css.sections}>\n {connected && !configOpen ? (\n // ── connected summary row (form collapsed) ──────────────────────\n <section className={css.card}>\n <div className={css.summaryRow}>\n <StateDot state=\"done\" size={8} className={css.dot} />\n <span className={css.statusOk}>{t('state.connected')}</span>\n <span className={css.summaryType}>{t(`type.${type}`)}</span>\n <span className={css.summaryDb} title={database}>{database}</span>\n <span className={css.summaryActions}>\n <button type=\"button\" className={`${css.ghost} ${css.small}`} onClick={() => setConfigOpen(true)}>\n {t('action.config')}\n </button>\n <button type=\"button\" className={`${css.ghost} ${css.small}`} disabled={busy} onClick={() => { void disconnect() }}>\n {t('action.disconnect')}\n </button>\n </span>\n </div>\n </section>\n ) : (\n // ── connection form (editable when disconnected, readonly when expanded) ──\n <section className={css.card}>\n <div className={css.cardTitle}>\n <DatabaseIcon className={css.titleIcon} />\n <span>{t('form.title')}</span>\n </div>\n <div className={css.fieldGrid}>\n <label className={css.field}>\n <span className={css.label}>{t('form.type')}</span>\n <select\n className={css.input}\n value={type}\n disabled={formDisabled}\n onChange={(event) => {\n const next = event.target.value as DatabaseType\n setType(next)\n setPort(next === 'postgres' ? '5432' : next === 'mysql' ? '3306' : next === 'oracle' ? '1521' : next === 'hive' ? '10000' : next === 'impala' ? '21050' : '')\n }}\n >\n <option value=\"mysql\">{t('type.mysql')}</option>\n <option value=\"postgres\">{t('type.postgres')}</option>\n <option value=\"sqlite\">{t('type.sqlite')}</option>\n <option value=\"oracle\">{t('type.oracle')}</option>\n <option value=\"hive\">{t('type.hive')}</option>\n <option value=\"impala\">{t('type.impala')}</option>\n </select>\n </label>\n\n {!sqlite && (\n <div className={css.fieldRow2}>\n <label className={css.field}>\n <span className={css.label}>{t('form.host')}</span>\n <input className={css.input} type=\"text\" value={host} disabled={formDisabled} onChange={(event) => setHost(event.target.value)} />\n </label>\n <label className={css.field}>\n <span className={css.label}>{t('form.port')}</span>\n <input className={css.input} type=\"number\" min={1} max={65535} value={port} disabled={formDisabled} onChange={(event) => setPort(event.target.value)} />\n </label>\n </div>\n )}\n\n {!sqlite && (\n <div className={css.fieldRow2}>\n <label className={css.field}>\n <span className={css.label}>{t('form.user')}</span>\n <input className={css.input} type=\"text\" value={user} disabled={formDisabled} onChange={(event) => setUser(event.target.value)} />\n </label>\n <label className={css.field}>\n <span className={css.label}>{t('form.password')}</span>\n <input className={css.input} type=\"password\" value={password} autoComplete=\"new-password\" disabled={formDisabled} onChange={(event) => setPassword(event.target.value)} />\n </label>\n </div>\n )}\n\n <label className={css.field}>\n <span className={css.label}>{databaseLabel}</span>\n <input\n className={css.input}\n type=\"text\"\n value={database}\n placeholder={sqlite ? t('form.database.sqlite.placeholder') : undefined}\n disabled={formDisabled}\n onChange={(event) => setDatabase(event.target.value)}\n />\n </label>\n </div>\n\n <div className={css.actions}>\n {!connected ? (\n <button\n type=\"button\"\n className={css.primary}\n disabled={busy || database === '' || (!sqlite && host === '')}\n onClick={() => { void connect() }}\n >\n {restoring ? t('state.reconnecting') : busy ? t('state.checking') : t('action.connect')}\n </button>\n ) : (\n <>\n <button type=\"button\" className={css.ghost} onClick={() => setConfigOpen(false)}>\n {t('action.collapse')}\n </button>\n <button type=\"button\" className={css.ghost} disabled={busy} onClick={() => { void disconnect() }}>\n {t('action.disconnect')}\n </button>\n </>\n )}\n </div>\n </section>\n )}\n\n {connected && (\n // ── schema explorer entry ───────────────────────────────────────\n <section className={css.card}>\n <button type=\"button\" className={css.browseRow} onClick={() => setSchemaModalOpen(true)}>\n <TableIcon className={css.browseIcon} />\n <span>{t('action.browse')}</span>\n <ChevronIcon className={css.browseChevron} />\n </button>\n </section>\n )}\n\n <section className={css.card}>\n <div className={css.cardTitle}>\n <TerminalIcon className={css.titleIcon} />\n <span>{t('wb.sql')}</span>\n </div>\n <textarea\n className={css.sqlInput}\n value={sql}\n rows={8}\n spellCheck={false}\n placeholder={t('wb.sql.placeholder')}\n onChange={(event) => setSql(event.target.value)}\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void runSql()\n }\n }}\n />\n <div className={css.sqlActions}>\n <span className={css.shortcutHint}>{t('wb.sql.shortcut')}</span>\n <button\n type=\"button\"\n className={css.primary}\n disabled={sqlBusy || !connected || sql.trim() === ''}\n onClick={() => { void runSql() }}\n >\n <PlayIcon />\n {sqlBusy ? t('wb.sql.running') : t('wb.sql.run')}\n </button>\n </div>\n <pre className={css.sqlResult}>{sqlResult ?? t('wb.sql.empty')}</pre>\n </section>\n </div>\n\n {error !== null && (\n <div className={css.errorBar}>\n <div className={css.errorHead}>\n <AlertIcon className={css.errorIcon} />\n <span>{t('error.title')}</span>\n </div>\n <span className={css.errorText}>{error}</span>\n </div>\n )}\n\n {schemaModalOpen && (\n <Modal\n open={schemaModalOpen}\n onClose={() => setSchemaModalOpen(false)}\n title={t('wb.modal.title')}\n closeLabel={t('action.close')}\n className={css.schemaModal}\n >\n <div className={css.modalBody}>\n <div className={css.modalCol}>\n <div className={css.modalColTitle}>\n <span>{t('wb.schemas')}</span>\n {schemas.length > 0 && <span className={css.colCount}>{schemas.length}</span>}\n </div>\n <div className={css.treeScroll}>\n {schemas.length === 0 && <div className={css.hint}>{t('wb.loading')}</div>}\n <ul className={css.tree}>\n {schemas.map(schema => (\n <li key={schema} className={css.treeNode}>\n <button\n type=\"button\"\n className={`${css.treeItem}${activeSchema === schema ? ` ${css.active}` : ''}`}\n onClick={() => { void toggleSchema(schema) }}\n >\n <ChevronIcon className={`${css.treeChevron}${activeSchema === schema ? ` ${css.open}` : ''}`} />\n <FolderIcon className={css.treeIcon} />\n <span className={css.treeName}>{schema}</span>\n {activeSchema === schema && tables.length > 0 && (\n <span className={css.treeCount}>{tables.length}</span>\n )}\n </button>\n {activeSchema === schema && (\n <div className={css.treeChildren}>\n {tables.length === 0 && <div className={css.hint}>{t('wb.empty')}</div>}\n {tables.map(table => (\n <button\n key={table}\n type=\"button\"\n className={`${css.treeItem}${activeTable === table ? ` ${css.active}` : ''}`}\n onClick={() => { void selectTable(table) }}\n >\n <TableIcon className={css.treeIcon} />\n <span className={css.treeName}>{table}</span>\n </button>\n ))}\n </div>\n )}\n </li>\n ))}\n </ul>\n </div>\n <div className={css.modalHint}>{t('wb.hint.click')}</div>\n </div>\n\n <div className={css.modalCol}>\n <div className={css.modalColTitle}>\n <span>{t('wb.columns')}{activeTable !== null && ` · ${activeTable}`}</span>\n </div>\n {columns === null ? (\n <div className={css.emptyState}>\n <TableIcon className={css.emptyStateIcon} />\n <span className={css.emptyStateText}>{t('wb.hint.click')}</span>\n </div>\n ) : (\n <div className={css.colScroll}>\n <table className={css.columnsTable}>\n <thead>\n <tr><th>name</th><th>type</th><th>null</th></tr>\n </thead>\n <tbody>\n {columns.map(column => (\n <tr key={column.name}>\n <td>{column.name}</td>\n <td className={css.typeCell}>{column.type}</td>\n <td className={css.nullCell}>{column.nullable === undefined ? '' : column.nullable ? 'YES' : 'NO'}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n </div>\n </Modal>\n )}\n </div>\n )\n}\n","/** `data-agent` namespace dictionaries for the database workbench. */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'data-agent'\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'form.title': '数据库连接',\n 'form.type': '数据库类型',\n 'type.mysql': 'MySQL',\n 'type.postgres': 'PostgreSQL',\n 'type.sqlite': 'SQLite',\n 'type.oracle': 'Oracle',\n 'type.hive': 'Hive',\n 'type.impala': 'Impala',\n 'form.host': '主机',\n 'form.port': '端口',\n 'form.user': '用户名',\n 'form.password': '密码',\n 'form.database': '数据库名',\n 'form.database.oracle': '服务名 / SID',\n 'form.database.hive': '默认库',\n 'form.database.impala': '默认库',\n 'form.database.sqlite': '数据库文件路径',\n 'form.database.sqlite.placeholder': '/path/to/orders.db',\n 'action.connect': '连接',\n 'action.disconnect': '断开',\n 'state.connected': '已连接',\n 'state.disconnected': '未连接',\n 'state.checking': '正在检查连接…',\n 'state.reconnecting': '正在恢复连接…',\n 'wb.schemas': '库',\n 'wb.tables': '表',\n 'wb.columns': '表结构',\n 'wb.sql': 'SQL 命令',\n 'wb.sql.placeholder': '在此输入 SQL,例如 SELECT * FROM orders LIMIT 10;',\n 'wb.sql.run': '运行',\n 'wb.sql.running': '运行中…',\n 'wb.sql.shortcut': 'Ctrl / ⌘ + Enter 运行',\n 'wb.sql.empty': '-- 尚无输出 --',\n 'wb.loading': '加载中…',\n 'wb.empty': '(空)',\n 'wb.modal.title': '库表浏览',\n 'wb.hint.click': '单击库展开表 · 点击表查看结构',\n 'action.config': '连接配置',\n 'action.browse': '库表',\n 'action.close': '关闭',\n 'action.collapse': '收起',\n 'error.title': '操作失败',\n} satisfies Record<string, string>\n\n/** The data-agent namespace key union. */\nexport type DataAgentKey = keyof typeof zh\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en = {\n 'form.title': 'Database connection',\n 'form.type': 'Database type',\n 'type.mysql': 'MySQL',\n 'type.postgres': 'PostgreSQL',\n 'type.sqlite': 'SQLite',\n 'type.oracle': 'Oracle',\n 'type.hive': 'Hive',\n 'type.impala': 'Impala',\n 'form.host': 'Host',\n 'form.port': 'Port',\n 'form.user': 'User',\n 'form.password': 'Password',\n 'form.database': 'Database',\n 'form.database.oracle': 'Service name / SID',\n 'form.database.hive': 'Default database',\n 'form.database.impala': 'Default database',\n 'form.database.sqlite': 'Database file path',\n 'form.database.sqlite.placeholder': '/path/to/orders.db',\n 'action.connect': 'Connect',\n 'action.disconnect': 'Disconnect',\n 'state.connected': 'Connected',\n 'state.disconnected': 'Not connected',\n 'state.checking': 'Checking connection…',\n 'state.reconnecting': 'Restoring connection…',\n 'wb.schemas': 'Databases',\n 'wb.tables': 'Tables',\n 'wb.columns': 'Columns',\n 'wb.sql': 'SQL',\n 'wb.sql.placeholder': 'Write SQL here, e.g. SELECT * FROM orders LIMIT 10;',\n 'wb.sql.run': 'Run',\n 'wb.sql.running': 'Running…',\n 'wb.sql.shortcut': 'Ctrl / ⌘ + Enter to run',\n 'wb.sql.empty': '-- no output --',\n 'wb.loading': 'Loading…',\n 'wb.empty': '(empty)',\n 'wb.modal.title': 'Database explorer',\n 'wb.hint.click': 'Click a database to expand tables · click a table for columns',\n 'action.config': 'Connection settings',\n 'action.browse': 'Tables',\n 'action.close': 'Close',\n 'action.collapse': 'Collapse',\n 'error.title': 'Operation failed',\n} satisfies Record<DataAgentKey, string>\n","/**\n * Data Agent browser half, plugin entry: registers the database workbench\n * into the composer input dock (the strip ABOVE the input bar) for\n * data-agent sessions, and the `data-agent` dictionaries. The old\n * conversation-view tab is gone — the workbench lives inside the session.\n * Connection state lives in the server-side connection store, so layout and\n * session switches never lose it — the view only mirrors what\n * `/plugins/data-agent/status` reports.\n * @module @yejiming/dsh-data-agent/client\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale) into this program.\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the ui-conversation slot declarations (conversation.input.dock)\n// and the session standard props (sessionId) into this program.\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { DataAgentWorkbench, type SessionListLike } from './DataAgentWorkbench.tsx'\nimport { NS, en, zh, type DataAgentKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The database workbench copy. */\n 'data-agent': DataAgentKey\n }\n}\n\n/** Required services: the locale service, the slot registry, and the sessions list. */\nexport const inject = ['slots', 'locale', 'sessions']\n\n/**\n * Client plugin body: register the data-agent dictionaries and the database\n * workbench into the composer input dock. The registration rides the slot\n * service's effect wrapper, so plugin unload removes it.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'data-agent: dictionaries')\n const t = ctx.locale.bind(NS)\n ctx.inject(['slots', 'locale', 'sessions'], (scope: ClientContext) => {\n const list = scope.sessions.list\n // The sessions list is the agent-preset authority on the client: the\n // ui-agent-preset surface records `agentPreset` onto each session summary.\n const sessionsSource = {\n getSnapshot: (): SessionListLike => list.getSnapshot() as unknown as SessionListLike,\n subscribe: (fn: () => void): (() => void) => list.subscribe(fn),\n }\n // The workbench strip sits above the input bar (order 0, first entry);\n // it renders only for data-agent sessions and returns null elsewhere.\n scope.slots.inject('conversation.input.dock', () => scope.slots.register({\n name: 'conversation.input.dock',\n id: 'data-agent',\n order: 0,\n locale: NS,\n inject: () => ({ hooks: { sessions: sessionsSource } }),\n }, DataAgentWorkbench))\n })\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import z from "schemastery";
|
|
2
|
+
//#region src/clients.ts
|
|
3
|
+
/** Loader schema for one client override (all fields optional at input). */
|
|
4
|
+
const clientConfigSchema = z.object({
|
|
5
|
+
command: z.string(),
|
|
6
|
+
args: z.array(z.string())
|
|
7
|
+
});
|
|
8
|
+
/** Loader schema for the whole `clients` config object (any type key). */
|
|
9
|
+
const clientsSchema = z.dict(clientConfigSchema).default({});
|
|
10
|
+
/** Query-mode flag arguments per type (plain/human output). */
|
|
11
|
+
const QUERY_ARGS = {
|
|
12
|
+
mysql: ["--batch", "--raw"],
|
|
13
|
+
postgres: ["-A"],
|
|
14
|
+
sqlite: ["-header", "-column"],
|
|
15
|
+
oracle: ["-S", "/nolog"],
|
|
16
|
+
hive: ["--silent=true", "--outputformat=tsv2"],
|
|
17
|
+
impala: ["-B"]
|
|
18
|
+
};
|
|
19
|
+
/** Introspection-mode flag arguments per type (machine-readable listing). */
|
|
20
|
+
const INTROSPECT_ARGS = {
|
|
21
|
+
mysql: ["--batch", "--raw"],
|
|
22
|
+
postgres: ["-t", "-A"],
|
|
23
|
+
sqlite: ["-noheader", "-list"],
|
|
24
|
+
oracle: ["-S", "/nolog"],
|
|
25
|
+
hive: ["--silent=true", "--outputformat=tsv2"],
|
|
26
|
+
impala: ["-B"]
|
|
27
|
+
};
|
|
28
|
+
/** Default ports when the connection does not carry one. */
|
|
29
|
+
const DEFAULT_PORTS = {
|
|
30
|
+
mysql: 3306,
|
|
31
|
+
postgres: 5432,
|
|
32
|
+
sqlite: 0,
|
|
33
|
+
oracle: 1521,
|
|
34
|
+
hive: 1e4,
|
|
35
|
+
impala: 21050
|
|
36
|
+
};
|
|
37
|
+
/** Built-in commands per type (also the loader defaults; see `src/defaults.ts`). */
|
|
38
|
+
const DEFAULT_CLIENTS_COMMAND = {
|
|
39
|
+
mysql: "mysql",
|
|
40
|
+
postgres: "psql",
|
|
41
|
+
sqlite: "sqlite3",
|
|
42
|
+
oracle: "sqlplus",
|
|
43
|
+
hive: "beeline",
|
|
44
|
+
impala: "impala-shell"
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Connection flags for one type. Oracle and Hive carry NO connection flags:
|
|
48
|
+
* their endpoint + credentials travel in the stdin prefix; Impala takes
|
|
49
|
+
* `-i host:port -d db` on the argv. SQLite's `database` file is positional
|
|
50
|
+
* and must come AFTER the flags.
|
|
51
|
+
*/
|
|
52
|
+
function connectionArgs(type, connection) {
|
|
53
|
+
switch (type) {
|
|
54
|
+
case "mysql": return [
|
|
55
|
+
"-h",
|
|
56
|
+
connection.host ?? "127.0.0.1",
|
|
57
|
+
"-P",
|
|
58
|
+
String(connection.port ?? DEFAULT_PORTS.mysql),
|
|
59
|
+
"-u",
|
|
60
|
+
connection.user ?? "root",
|
|
61
|
+
"-D",
|
|
62
|
+
connection.database
|
|
63
|
+
];
|
|
64
|
+
case "postgres": return [
|
|
65
|
+
"-h",
|
|
66
|
+
connection.host ?? "127.0.0.1",
|
|
67
|
+
"-p",
|
|
68
|
+
String(connection.port ?? DEFAULT_PORTS.postgres),
|
|
69
|
+
"-U",
|
|
70
|
+
connection.user ?? "postgres",
|
|
71
|
+
"-d",
|
|
72
|
+
connection.database
|
|
73
|
+
];
|
|
74
|
+
case "sqlite": return [connection.database];
|
|
75
|
+
case "impala": return [
|
|
76
|
+
"-i",
|
|
77
|
+
`${connection.host ?? "127.0.0.1"}:${connection.port ?? DEFAULT_PORTS.impala}`,
|
|
78
|
+
"-d",
|
|
79
|
+
connection.database
|
|
80
|
+
];
|
|
81
|
+
case "oracle":
|
|
82
|
+
case "hive": return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Credential environment entries per type; absent password yields an empty env. */
|
|
86
|
+
function credentialEnv(type, connection) {
|
|
87
|
+
const password = connection.password;
|
|
88
|
+
if (password === void 0) return {};
|
|
89
|
+
switch (type) {
|
|
90
|
+
case "mysql": return { MYSQL_PWD: password };
|
|
91
|
+
case "postgres": return { PGPASSWORD: password };
|
|
92
|
+
case "sqlite":
|
|
93
|
+
case "oracle":
|
|
94
|
+
case "hive":
|
|
95
|
+
case "impala": return {};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The stdin prefix per type: Oracle and Hive establish the session here, so
|
|
100
|
+
* their credentials never appear in argv. Oracle also silences sqlplus
|
|
101
|
+
* decoration (PAGESIZE/FEEDBACK/HEADING) and pins the column separator to
|
|
102
|
+
* `|` for the describe parser; Hive connects through beeline's `!connect`.
|
|
103
|
+
*/
|
|
104
|
+
function stdinPrefix(type, connection) {
|
|
105
|
+
switch (type) {
|
|
106
|
+
case "oracle": return `${[
|
|
107
|
+
"SET PAGESIZE 0",
|
|
108
|
+
"SET FEEDBACK OFF",
|
|
109
|
+
"SET HEADING OFF",
|
|
110
|
+
"SET COLSEP '|'",
|
|
111
|
+
"SET TRIMSPOOL ON",
|
|
112
|
+
connection.user !== void 0 ? `connect ${connection.user}${connection.password !== void 0 ? `/${connection.password}` : ""}@${connection.host ?? "127.0.0.1"}:${connection.port ?? DEFAULT_PORTS.oracle}/${connection.database}` : ""
|
|
113
|
+
].filter((line) => line !== "").join("\n")}\n`;
|
|
114
|
+
case "hive": return connection.user !== void 0 ? `!connect jdbc:hive2://${connection.host ?? "127.0.0.1"}:${connection.port ?? DEFAULT_PORTS.hive}/${connection.database} ${connection.user} ${connection.password ?? ""}\n` : "";
|
|
115
|
+
case "mysql":
|
|
116
|
+
case "postgres":
|
|
117
|
+
case "sqlite":
|
|
118
|
+
case "impala": return "";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Apply one deployment override's extra args in front of the built-in flags. */
|
|
122
|
+
function withOverrides(flags, override) {
|
|
123
|
+
if (override === void 0 || override.args === void 0) return flags;
|
|
124
|
+
return [...override.args, ...flags];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Build one client invocation for a query execution (plain output). Flags
|
|
128
|
+
* come BEFORE the connection arguments everywhere: sqlite3 takes
|
|
129
|
+
* `[options] <database>`, and putting flags first is harmless for the others.
|
|
130
|
+
*/
|
|
131
|
+
function buildClientTemplate(type, connection, override) {
|
|
132
|
+
return {
|
|
133
|
+
command: override?.command ?? DEFAULT_CLIENTS_COMMAND[type],
|
|
134
|
+
args: [...withOverrides(QUERY_ARGS[type], override), ...connectionArgs(type, connection)],
|
|
135
|
+
env: credentialEnv(type, connection),
|
|
136
|
+
stdinPrefix: stdinPrefix(type, connection)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Build one client invocation for metadata runs (machine-readable flags). */
|
|
140
|
+
function buildIntrospectTemplate(type, connection, override) {
|
|
141
|
+
return {
|
|
142
|
+
command: override?.command ?? DEFAULT_CLIENTS_COMMAND[type],
|
|
143
|
+
args: [...withOverrides(INTROSPECT_ARGS[type], override), ...connectionArgs(type, connection)],
|
|
144
|
+
env: credentialEnv(type, connection),
|
|
145
|
+
stdinPrefix: stdinPrefix(type, connection)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The table-listing SQL per type, run at /connect time to verify
|
|
150
|
+
* connectivity: the connected database's own tables (mysql uses the
|
|
151
|
+
* connection's database as the schema; postgres lists `public`; oracle lists
|
|
152
|
+
* the connected user's tables; hive/impala list the default database).
|
|
153
|
+
*/
|
|
154
|
+
function tableListingSql(type, connection) {
|
|
155
|
+
switch (type) {
|
|
156
|
+
case "mysql": return `SHOW TABLES FROM \`${connection?.database ?? ""}\`;`;
|
|
157
|
+
case "postgres": return "SELECT tablename FROM pg_tables WHERE schemaname='public' ORDER BY 1;";
|
|
158
|
+
case "sqlite": return "SELECT name FROM sqlite_master WHERE type='table' ORDER BY 1;";
|
|
159
|
+
case "oracle": return "SELECT table_name FROM user_tables ORDER BY 1;";
|
|
160
|
+
case "hive":
|
|
161
|
+
case "impala": return "SHOW TABLES;";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Metadata query per kind × type. `schema`/`table` are identifier whitelist
|
|
166
|
+
* validated by the caller (`[A-Za-z0-9_$#.-]`) before they reach here.
|
|
167
|
+
*/
|
|
168
|
+
function metadataQuery(kind, type, schema, table) {
|
|
169
|
+
switch (kind) {
|
|
170
|
+
case "schemas": switch (type) {
|
|
171
|
+
case "mysql": return "SHOW DATABASES;";
|
|
172
|
+
case "postgres": return "SELECT schema_name FROM information_schema.schemata ORDER BY 1;";
|
|
173
|
+
case "sqlite": return "SELECT 'main';";
|
|
174
|
+
case "oracle": return "SELECT username FROM all_users ORDER BY 1;";
|
|
175
|
+
case "hive":
|
|
176
|
+
case "impala": return "SHOW DATABASES;";
|
|
177
|
+
}
|
|
178
|
+
case "tables": switch (type) {
|
|
179
|
+
case "mysql": return `SHOW TABLES FROM \`${schema}\`;`;
|
|
180
|
+
case "postgres": return `SELECT tablename FROM pg_tables WHERE schemaname='${schema}' ORDER BY 1;`;
|
|
181
|
+
case "sqlite": return "SELECT name FROM sqlite_master WHERE type='table' ORDER BY 1;";
|
|
182
|
+
case "oracle": return `SELECT table_name FROM all_tables WHERE owner='${schema}' ORDER BY 1;`;
|
|
183
|
+
case "hive":
|
|
184
|
+
case "impala": return `SHOW TABLES IN ${schema};`;
|
|
185
|
+
}
|
|
186
|
+
case "describe": switch (type) {
|
|
187
|
+
case "mysql": return `DESCRIBE \`${schema}\`.\`${table}\`;`;
|
|
188
|
+
case "postgres": return `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema='${schema}' AND table_name='${table}' ORDER BY ordinal_position;`;
|
|
189
|
+
case "sqlite": return `PRAGMA table_info("${table}");`;
|
|
190
|
+
case "oracle": return `SELECT column_name, data_type, nullable FROM all_tab_columns WHERE owner='${schema}' AND table_name='${table}' ORDER BY column_id;`;
|
|
191
|
+
case "hive":
|
|
192
|
+
case "impala": return `DESCRIBE ${schema}.${table};`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Split one type's machine-readable listing output into trimmed lines.
|
|
198
|
+
* Header lines are stripped per type: mysql `--batch` prints a header row
|
|
199
|
+
* (skip 1); postgres `-t`, sqlite `-noheader`, oracle `SET HEADING OFF`,
|
|
200
|
+
* hive/impala batch modes print none (skip 0).
|
|
201
|
+
*/
|
|
202
|
+
function parseListing(type, stdout) {
|
|
203
|
+
const lines = stdout.split("\n");
|
|
204
|
+
const start = type === "mysql" ? 1 : 0;
|
|
205
|
+
const items = [];
|
|
206
|
+
for (let index = start; index < lines.length; index += 1) {
|
|
207
|
+
const name = lines[index].trim();
|
|
208
|
+
if (name.length > 0) items.push(name);
|
|
209
|
+
}
|
|
210
|
+
return items;
|
|
211
|
+
}
|
|
212
|
+
/** Parse one type's table-listing output (the /connect connectivity check). */
|
|
213
|
+
function parseTableListing(type, stdout) {
|
|
214
|
+
return parseListing(type, stdout);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Parse one type's describe output into columns. Formats:
|
|
218
|
+
* - mysql `--batch`: `Field\tType\tNull\tKey\t...` (skip header);
|
|
219
|
+
* - postgres `-t -A`: `name|type|is_nullable`;
|
|
220
|
+
* - sqlite `-noheader -list`: `cid|name|type|notnull|dflt|pk` (name is part 1);
|
|
221
|
+
* - oracle (`SET COLSEP '|'`, heading off): `NAME|TYPE|NULLABLE`;
|
|
222
|
+
* - hive/impala batch: `name\ttype\tcomment`.
|
|
223
|
+
*/
|
|
224
|
+
function parseColumns(type, stdout) {
|
|
225
|
+
const lines = stdout.split("\n");
|
|
226
|
+
const start = type === "mysql" ? 1 : 0;
|
|
227
|
+
const columns = [];
|
|
228
|
+
for (let index = start; index < lines.length; index += 1) {
|
|
229
|
+
const line = lines[index].trim();
|
|
230
|
+
if (line.length === 0) continue;
|
|
231
|
+
const parts = line.includes(" ") ? line.split(" ") : line.split("|");
|
|
232
|
+
const nameIndex = type === "sqlite" ? 1 : 0;
|
|
233
|
+
const name = parts[nameIndex]?.trim() ?? "";
|
|
234
|
+
const columnType = parts[nameIndex + 1]?.trim() ?? "";
|
|
235
|
+
if (name.length === 0) continue;
|
|
236
|
+
const rawNullable = parts[nameIndex + 2]?.trim().toLowerCase();
|
|
237
|
+
let nullable;
|
|
238
|
+
switch (type) {
|
|
239
|
+
case "mysql":
|
|
240
|
+
nullable = rawNullable === "yes";
|
|
241
|
+
break;
|
|
242
|
+
case "postgres":
|
|
243
|
+
nullable = rawNullable === "yes";
|
|
244
|
+
break;
|
|
245
|
+
case "sqlite":
|
|
246
|
+
nullable = rawNullable !== "1";
|
|
247
|
+
break;
|
|
248
|
+
case "oracle":
|
|
249
|
+
nullable = rawNullable === "y";
|
|
250
|
+
break;
|
|
251
|
+
case "hive":
|
|
252
|
+
case "impala": nullable = void 0;
|
|
253
|
+
}
|
|
254
|
+
columns.push({
|
|
255
|
+
name,
|
|
256
|
+
type: columnType,
|
|
257
|
+
...nullable !== void 0 ? { nullable } : {}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return columns;
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/defaults.ts
|
|
264
|
+
/**
|
|
265
|
+
* Package-wide defaults shared by the server half (`src/index.ts`) and the
|
|
266
|
+
* sqlcmd tool half (`src/tool.ts`). Loader schemas carry these as their
|
|
267
|
+
* defaults so a deployment may override every one of them in cordis.yml.
|
|
268
|
+
* @module @yejiming/dsh-data-agent/defaults
|
|
269
|
+
*/
|
|
270
|
+
/** Preset directory name installed into `$DSH_HOME/.agent-presets/`. */
|
|
271
|
+
const DEFAULT_PRESET_ID = "data-agent";
|
|
272
|
+
/** End-to-end deadline for one `/connect` connectivity check, milliseconds. */
|
|
273
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
274
|
+
/** End-to-end deadline for one sqlcmd query, milliseconds. */
|
|
275
|
+
const DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
276
|
+
/** In-memory cap on sqlcmd captured output (stdout and stderr each). */
|
|
277
|
+
const DEFAULT_MAX_RESULT_CHARS = 2e4;
|
|
278
|
+
/** Cap on one /query SQL text length (abuse guard; the wire body stays small). */
|
|
279
|
+
const DEFAULT_MAX_QUERY_CHARS = 65536;
|
|
280
|
+
/** Grace period for the subprocess terminate escalation. */
|
|
281
|
+
const DEFAULT_GRACE_MS = 5e3;
|
|
282
|
+
//#endregion
|
|
283
|
+
export { DEFAULT_PRESET_ID as a, buildIntrospectTemplate as c, parseColumns as d, parseListing as f, DEFAULT_MAX_RESULT_CHARS as i, clientsSchema as l, tableListingSql as m, DEFAULT_GRACE_MS as n, DEFAULT_QUERY_TIMEOUT_MS as o, parseTableListing as p, DEFAULT_MAX_QUERY_CHARS as r, buildClientTemplate as s, DEFAULT_CONNECT_TIMEOUT_MS as t, metadataQuery as u };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { a as DEFAULT_PRESET_ID, i as DEFAULT_MAX_RESULT_CHARS, l as clientsSchema, o as DEFAULT_QUERY_TIMEOUT_MS, t as DEFAULT_CONNECT_TIMEOUT_MS } from "./defaults-D__D30ED.js";
|
|
2
|
+
import { access, cp, mkdir } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import z from "schemastery";
|
|
7
|
+
/** Build the password-stripped copy of one connection. */
|
|
8
|
+
function summarize(connection) {
|
|
9
|
+
const summary = {
|
|
10
|
+
type: connection.type,
|
|
11
|
+
database: connection.database
|
|
12
|
+
};
|
|
13
|
+
if (connection.host !== void 0) summary.host = connection.host;
|
|
14
|
+
if (connection.port !== void 0) summary.port = connection.port;
|
|
15
|
+
if (connection.user !== void 0) summary.user = connection.user;
|
|
16
|
+
if (connection.tables !== void 0) summary.tables = [...connection.tables];
|
|
17
|
+
return summary;
|
|
18
|
+
}
|
|
19
|
+
/** Create a fresh connection store (per-process singleton, one per plugin instance). */
|
|
20
|
+
function createConnectionStore() {
|
|
21
|
+
const connections = /* @__PURE__ */ new Map();
|
|
22
|
+
/** Exact entry first; the wildcard (`'*'`) entry is the fallback. */
|
|
23
|
+
const exactOrWildcard = (sessionId) => connections.get(sessionId) ?? connections.get("*");
|
|
24
|
+
return {
|
|
25
|
+
set(sessionId, connection) {
|
|
26
|
+
connections.set(sessionId, connection);
|
|
27
|
+
},
|
|
28
|
+
get(sessionId) {
|
|
29
|
+
const connection = exactOrWildcard(sessionId);
|
|
30
|
+
return connection === void 0 ? void 0 : summarize(connection);
|
|
31
|
+
},
|
|
32
|
+
getWithSecret(sessionId) {
|
|
33
|
+
return exactOrWildcard(sessionId);
|
|
34
|
+
},
|
|
35
|
+
has(sessionId) {
|
|
36
|
+
return exactOrWildcard(sessionId) !== void 0;
|
|
37
|
+
},
|
|
38
|
+
clear(sessionId) {
|
|
39
|
+
connections.delete(sessionId);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/index.ts
|
|
45
|
+
/**
|
|
46
|
+
* Data Agent server half for the dsh web GUI. The host row provides the
|
|
47
|
+
* `dataAgentConnections` service (session-scoped in-memory store; passwords
|
|
48
|
+
* never leave memory), seeds config connections (`connections`, `'*'` =
|
|
49
|
+
* wildcard default), and installs the `data-agent` agent preset into
|
|
50
|
+
* `$DSH_HOME/.agent-presets/` (idempotent, never overwrites a user-edited
|
|
51
|
+
* directory).
|
|
52
|
+
*
|
|
53
|
+
* The HTTP routes live in the separate `./routes` entry
|
|
54
|
+
* (`@yejiming/dsh-data-agent/routes`, cordis row `data-agent-routes`) so
|
|
55
|
+
* this row keeps working in headless profiles without a webserver; the sqlcmd
|
|
56
|
+
* tool itself lives in the `./tool` entry and is mounted only by the
|
|
57
|
+
* data-agent preset.
|
|
58
|
+
* @module @yejiming/dsh-data-agent
|
|
59
|
+
*/
|
|
60
|
+
/** Cordis plugin name (diagnostics only). */
|
|
61
|
+
const name = "data-agent";
|
|
62
|
+
/** Services required before the store can serve. */
|
|
63
|
+
const inject = ["subprocess"];
|
|
64
|
+
/** Loader schema with deployment defaults (no library defaults). */
|
|
65
|
+
const Config = z.object({
|
|
66
|
+
presetId: z.string().default(DEFAULT_PRESET_ID),
|
|
67
|
+
installPreset: z.boolean().default(true),
|
|
68
|
+
connectTimeoutMs: z.number().step(1).min(1e3).default(DEFAULT_CONNECT_TIMEOUT_MS),
|
|
69
|
+
introspectMaxTables: z.number().step(1).min(1).default(500),
|
|
70
|
+
queryTimeoutMs: z.number().step(1).min(1e3).default(DEFAULT_QUERY_TIMEOUT_MS),
|
|
71
|
+
maxResultChars: z.number().step(1).min(1024).default(DEFAULT_MAX_RESULT_CHARS),
|
|
72
|
+
clients: clientsSchema,
|
|
73
|
+
connections: z.dict(z.object({
|
|
74
|
+
type: z.union([
|
|
75
|
+
z.const("mysql"),
|
|
76
|
+
z.const("postgres"),
|
|
77
|
+
z.const("sqlite"),
|
|
78
|
+
z.const("oracle"),
|
|
79
|
+
z.const("hive"),
|
|
80
|
+
z.const("impala")
|
|
81
|
+
]),
|
|
82
|
+
host: z.string(),
|
|
83
|
+
port: z.natural(),
|
|
84
|
+
user: z.string(),
|
|
85
|
+
database: z.string()
|
|
86
|
+
})).default({})
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the harness home the same way `@deepseek-ai/dsh-paths` does:
|
|
90
|
+
* `$DSH_HOME` (non-blank) else `~/.dsh`, normalized absolute.
|
|
91
|
+
*/
|
|
92
|
+
function resolveDshHome(env = process.env) {
|
|
93
|
+
const fromEnv = env.DSH_HOME;
|
|
94
|
+
const selected = fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(homedir(), ".dsh");
|
|
95
|
+
return resolve(selected.startsWith("~/") ? join(homedir(), selected.slice(2)) : selected);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Install the packaged `preset/data-agent/` directory into
|
|
99
|
+
* `$DSH_HOME/.agent-presets/<presetId>/`. Idempotent: an existing target
|
|
100
|
+
* directory is left untouched (user edits survive); `installPreset: false`
|
|
101
|
+
* never calls this. Best-effort — a failure logs a warning with manual
|
|
102
|
+
* install instructions instead of failing the boot.
|
|
103
|
+
*/
|
|
104
|
+
async function installPreset(ctx, presetId) {
|
|
105
|
+
const targetDir = join(resolveDshHome(), ".agent-presets", presetId);
|
|
106
|
+
try {
|
|
107
|
+
await access(targetDir);
|
|
108
|
+
ctx.logger.info("data-agent: preset \"%s\" already present at %s, skipping install", presetId, targetDir);
|
|
109
|
+
return;
|
|
110
|
+
} catch {}
|
|
111
|
+
const sourceDir = fileURLToPath(new URL("../preset/data-agent/", import.meta.url));
|
|
112
|
+
try {
|
|
113
|
+
await mkdir(targetDir, { recursive: true });
|
|
114
|
+
await cp(sourceDir, targetDir, { recursive: true });
|
|
115
|
+
ctx.logger.info("data-agent: installed preset \"%s\" to %s", presetId, targetDir);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
ctx.logger.warn("data-agent: failed to install preset \"%s\" to %s (%s); copy preset/data-agent/ manually to enable the 数据Agent preset", presetId, targetDir, error instanceof Error ? error.message : String(error));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Mount the data-agent host row: connection store, config-seeded
|
|
122
|
+
* connections, and preset self-install. HTTP routes are the sibling
|
|
123
|
+
* `data-agent-routes` row (`./routes`).
|
|
124
|
+
* @param ctx - host cordis context.
|
|
125
|
+
* @param config - validated loader configuration.
|
|
126
|
+
*/
|
|
127
|
+
function apply(ctx, config) {
|
|
128
|
+
const resolved = {
|
|
129
|
+
presetId: config.presetId,
|
|
130
|
+
installPreset: config.installPreset,
|
|
131
|
+
connectTimeoutMs: config.connectTimeoutMs,
|
|
132
|
+
introspectMaxTables: config.introspectMaxTables,
|
|
133
|
+
queryTimeoutMs: config.queryTimeoutMs,
|
|
134
|
+
maxResultChars: config.maxResultChars,
|
|
135
|
+
clients: config.clients,
|
|
136
|
+
connections: config.connections
|
|
137
|
+
};
|
|
138
|
+
const store = createConnectionStore();
|
|
139
|
+
ctx.provide("dataAgentConnections", store);
|
|
140
|
+
for (const [sessionId, spec] of Object.entries(config.connections)) {
|
|
141
|
+
const connection = {
|
|
142
|
+
type: spec.type,
|
|
143
|
+
database: spec.type === "sqlite" ? resolve(process.cwd(), spec.database) : spec.database,
|
|
144
|
+
...spec.host !== void 0 ? { host: spec.host } : {},
|
|
145
|
+
...spec.port !== void 0 ? { port: spec.port } : {},
|
|
146
|
+
...spec.user !== void 0 ? { user: spec.user } : {}
|
|
147
|
+
};
|
|
148
|
+
store.set(sessionId, connection);
|
|
149
|
+
}
|
|
150
|
+
if (resolved.installPreset) installPreset(ctx, resolved.presetId);
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { Config, apply, inject, installPreset, name, resolveDshHome };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/invariant.ts
|
|
2
|
+
const PACKAGE_NAME = "@yejiming/dsh-data-agent";
|
|
3
|
+
/** Cordis companion plugin name. */
|
|
4
|
+
const name = "data-agent-invariant";
|
|
5
|
+
/** Service required before the companion can reserve package ownership. */
|
|
6
|
+
const inject = ["invariants"];
|
|
7
|
+
/**
|
|
8
|
+
* No runtime invariant: the connection store's session→connection mapping is
|
|
9
|
+
* a plain in-memory Map owned entirely by this package, every wire input is
|
|
10
|
+
* validated at the HTTP boundary, and the security invariants (password
|
|
11
|
+
* stripping, session isolation, sqlite-only-for-sqlite) are asserted by this
|
|
12
|
+
* package's unit tests.
|
|
13
|
+
*/
|
|
14
|
+
const install = () => {};
|
|
15
|
+
/**
|
|
16
|
+
* Register this package's invariant companion.
|
|
17
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
18
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
19
|
+
*/
|
|
20
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
21
|
+
//#endregion
|
|
22
|
+
export { apply, inject, name };
|