| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- /**
- * Host directory-picking Remote owner: capability gating, cancellation, and the
- * stable wire failure vocabulary over the `ctx.directoryPicker` seam.
- */
- import { Context } from '@deepseek-ai/cordis'
- import { z } from 'zod'
- import { DirectoryPickerError } from '@deepseek-ai/dsh-host-directory-picker'
- import type {
- DirectoryPickerCapabilities, DirectoryPickerErrorCode,
- } from '@deepseek-ai/dsh-host-directory-picker'
- // The seam owns the listing declaration; the generator requires the reference
- // site to name that package rather than this package's re-export of it.
- import type { DirectoryListing } from '@deepseek-ai/dsh-host-directory-picker/types'
- import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
- import type { RemoteErrorCode } from '@deepseek-ai/dsh-typert-protocol'
- const createDirectoryRequestSchema = z.object({
- path: z.string(),
- name: z.string(),
- }).refine(
- request => request.name.trim() !== '' && request.name !== '.' && request.name !== '..'
- && !/[/\\]/.test(request.name),
- { message: 'host.createDirectory requires a single non-blank path segment name' },
- )
- declare module '@deepseek-ai/cordis' {
- interface Context {
- /** Host directory-picking Remote namespace owner. */
- directoryPickerController: DirectoryPickerController
- }
- }
- /**
- * Host service backing the generated `ctx.remote.directoryPicker` namespace. The
- * seam it exports is abstract and therefore never a Loader entry of its own, so
- * this controller carries the wire verbs: one composed backend serves either the
- * native chooser or the browse primitives, and a verb the composition cannot
- * serve is refused rather than approximated.
- */
- export class DirectoryPickerController extends TypertRemoteService {
- static inject = ['directoryPicker']
- /** @param ctx - Host context carrying the composed directory-picking backend. */
- constructor(ctx: Context) {
- super(ctx, 'directoryPickerController', { namespace: 'directoryPicker' })
- }
- /**
- * Open the host's OS chooser for a Remote caller.
- * @param signal - caller lifetime; abort terminates the chooser.
- * @returns the chosen absolute path, or null when the operator cancels.
- */
- @Remote('pick')
- async pick(signal: AbortSignal): Promise<string | null> {
- const capability = this.requireCapability('native', 'pick')
- try {
- return await capability.pick(signal)
- } catch (error: unknown) {
- throw cancellableFailure(error, signal, 'directory picker was aborted', 'directory picker failed')
- }
- }
- /**
- * List one directory level for a Remote caller's in-app browser.
- * @param path - absolute directory to list; absent lists the home directory.
- * @param signal - caller lifetime; abort stops the backend's scan instead of
- * letting it outlive a disconnected caller.
- * @returns the level's listing with its ancestry.
- */
- @Remote('list')
- async list(path: string | undefined, signal: AbortSignal): Promise<DirectoryListing> {
- const capability = this.requireCapability('browse', 'list')
- try {
- return await capability.list(path, signal)
- } catch (error: unknown) {
- throw cancellableFailure(error, signal, 'directory listing was aborted')
- }
- }
- /**
- * Create one child directory for a Remote caller's in-app browser.
- * @param path - absolute existing parent directory.
- * @param name - single non-blank path segment.
- * @returns the created directory's absolute path.
- */
- @Remote('createDirectory')
- async createDirectory(path: string, name: string): Promise<string> {
- const request = createDirectoryRequestSchema.safeParse({ path, name })
- if (!request.success) {
- throw new RemoteError(
- 'gateway/bad-request',
- 'invalid payload for host.createDirectory',
- { issues: request.error.issues },
- )
- }
- const capability = this.requireCapability('browse', 'createDirectory')
- try {
- return await capability.createDirectory(request.data.path, request.data.name)
- } catch (error: unknown) {
- throw browseFailure(error)
- }
- }
- /** Resolve the capability one wire verb needs, or refuse with the kind this backend serves. */
- private requireCapability<Kind extends keyof DirectoryPickerCapabilities>(
- kind: Kind,
- method: string,
- ): DirectoryPickerCapabilities[Kind] {
- const capability = this.ctx.directoryPicker.capability()
- if (capability.kind !== kind) {
- throw new RemoteError(
- 'directory-picker/unavailable',
- `directoryPicker.${method} needs the ${kind} capability; the composed picker serves "${capability.kind}"`,
- { capability: capability.kind },
- )
- }
- return capability as DirectoryPickerCapabilities[Kind]
- }
- }
- /**
- * Wire code answered for each seam browse failure. The seam's closed codes are
- * its own local vocabulary, so this controller owns the projection onto the
- * `directory-picker/*` codes a Remote caller discriminates on.
- */
- const BROWSE_FAILURE_CODES = {
- 'directory-unreadable': 'directory-picker/unreadable',
- 'directory-exists': 'directory-picker/exists',
- 'directory-create-failed': 'directory-picker/create-failed',
- } as const satisfies Record<DirectoryPickerErrorCode, RemoteErrorCode>
- /**
- * Classify a browse-primitive rejection: the seam's own closed codes carry the
- * path they are about, and anything else stays an infrastructure failure.
- * @param error - the primitive's rejection.
- * @returns the failure to throw across the Remote boundary.
- */
- function browseFailure(error: unknown): RemoteError {
- if (error instanceof DirectoryPickerError) {
- return new RemoteError(
- BROWSE_FAILURE_CODES[error.code],
- error.message,
- { path: error.path },
- { cause: error },
- )
- }
- return new RemoteError('gateway/internal', errorMessage(error), {}, { cause: error })
- }
- /**
- * Classify a cancellable primitive's rejection. An abort is the caller's own
- * timeout or disconnect, not a backend failure, so it answers `gateway/cancelled`
- * before the business classification runs.
- * @param error - the primitive's rejection.
- * @param signal - the caller lifetime the primitive ran under.
- * @param cancelled - operator-facing text for the abort outcome.
- * @param failed - prefix for a non-seam failure, when the verb has no closed codes.
- * @returns the failure to throw across the Remote boundary.
- */
- function cancellableFailure(
- error: unknown,
- signal: AbortSignal,
- cancelled: string,
- failed?: string,
- ): RemoteError {
- if (signal.aborted) return new RemoteError('gateway/cancelled', cancelled, {}, { cause: error })
- if (failed === undefined) return browseFailure(error)
- return new RemoteError('gateway/internal', `${failed}: ${errorMessage(error)}`, {}, { cause: error })
- }
- function errorMessage(error: unknown): string {
- return error instanceof Error ? error.message : String(error)
- }
|