Files
opencode-mobile/src/lib/file-roots.ts
Den 9a24cea61f feat(connect): list server filesystem roots/drives in directory browser (closes #57) (#100)
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>
2026-07-17 10:24:37 -07:00

41 lines
1.6 KiB
TypeScript

// 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
}