The directory browser could only descend from a manually-typed path since the SDK had no way to enumerate the server's filesystem roots (multiple drives on Windows, mount points, home dir). Add file.roots() to sdk.ts (GET /file/roots, added server-side in dzianisv/opencode#238) and show the results as pinned top-level chips in DirectoryBrowserSheet that jump straight into that root. Degrades gracefully: older servers 404 on the new endpoint, which the SDK turns into null, normalizeRoots() turns into an empty list, and the browser just shows no chips — manual "Jump to path" entry keeps working exactly as before. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
40
src/lib/file-roots.ts
Normal file
40
src/lib/file-roots.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
// Pure helpers for normalizing GET /file/roots responses from opencode
|
||||
// servers. No React Native imports — unit-testable with node --test.
|
||||
//
|
||||
// Older servers don't expose the endpoint (404), which the SDK client turns
|
||||
// into `null`; newer servers may still send malformed, empty-path, or
|
||||
// duplicate entries, so this module defends against all of that before the
|
||||
// directory browser renders anything.
|
||||
|
||||
export interface FileRoot {
|
||||
path: string
|
||||
label: string
|
||||
}
|
||||
|
||||
function isPlausibleRoot(value: unknown): value is { path: string; label: unknown } {
|
||||
if (typeof value !== "object" || value === null) return false
|
||||
const candidate = value as Record<string, unknown>
|
||||
return typeof candidate.path === "string" && candidate.path.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and dedupe a raw /file/roots response into pinned entries for
|
||||
* the directory browser. Drops entries missing a non-empty `path`, falls
|
||||
* back to the path itself when `label` is missing/blank, and drops later
|
||||
* duplicates by path (keeping first-seen order). Non-array input (including
|
||||
* `null`, which the SDK returns for servers that don't support the
|
||||
* endpoint) normalizes to an empty list rather than throwing.
|
||||
*/
|
||||
export function normalizeRoots(raw: unknown): FileRoot[] {
|
||||
if (!Array.isArray(raw)) return []
|
||||
const seen = new Set<string>()
|
||||
const roots: FileRoot[] = []
|
||||
for (const item of raw) {
|
||||
if (!isPlausibleRoot(item)) continue
|
||||
if (seen.has(item.path)) continue
|
||||
seen.add(item.path)
|
||||
const label = typeof item.label === "string" && item.label.trim() ? item.label : item.path
|
||||
roots.push({ path: item.path, label })
|
||||
}
|
||||
return roots
|
||||
}
|
||||
Reference in New Issue
Block a user