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>
41 lines
1.6 KiB
TypeScript
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
|
|
}
|