Files
deepseek-harness/website/zh-CN/api/harness/fs.md
lintianle efba9fab0a website: generate the API reference from source (cordis + all 15 harness services)
scripts/gen-website-api.ts renders website/zh-CN/api/{cordis,harness}/* and the
api-sidebar.json fragment the VitePress config imports, so pages and navigation
can never drift from the code: signatures, @param/@returns prose, dispatch
modes, and GitHub source links are extracted, never transcribed, and the
generator hard-errors on any rendered member missing docs. verify-website-api
(doc-sync + run-gates) is the freshness gate.

Replaces the hand-written zh api pages (7 pages covering 7 of 15 services,
with phantom APIs: Context.current/Context.events, agent/post-step, tool/call,
compact/*, llm/pre-request none of which exist) with generated English
references: 5 cordis pages, 15 per-service pages, and a 35-event catalog
grouped by scope. The hand-written hub api/index.md stays and now indexes the
full surface; zh for these pages arrives with the unified translation flow.
2026-07-16 18:13:34 +08:00

7.0 KiB

ctx.fs

FileSystem (abstract seam) — provided by @deepseek-ai/dsh-fs.

Abstract filesystem provider service. Subclass, implement the seven storage primitives, and load the subclass as a plugin — it registers as ctx.fs (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). Semantics every backend must honor:

  • resolve returns a stable FsTarget; the same underlying file reached by different input paths must yield the same targetKey so stale guards and target lookup agree across paths (e.g. through symlinks).
  • stat returns FsInfo metadata (never content) or undefined when the target is absent.
  • readText/streamText read the whole regular text file (the stream for large files); both own regular-file checks, UTF-8 decoding, binary/NUL rejection, and FS_NOT_TEXT.
  • listDir returns direct children of a directory in stable name order with resolved child targets and cheap metadata only. It never reads file contents. Missing targets throw FS_NOT_FOUND, non-directories throw FS_NOT_DIRECTORY, permission failures throw FS_PERMISSION_DENIED, and other backend I/O failures throw FS_IO_ERROR.
  • writeText is atomic temp-file + rename. expected is OPTIONAL: omit it for an unconditional create-or-overwrite (the bare-provider default), or supply a FsWriteIntent to guard the write.
  • editText verifies expected.version BEFORE literal matching (so a stale edit reports FS_STALE_VERSION, not FS_EDIT_NOT_FOUND/ FS_AMBIGUOUS_EDIT against newer content), then applies literal replacement and writes atomically — all inside one mutation critical section. expected is OPTIONAL: omit it for an unconditional edit of the current content (a missing target still reports FS_STALE_VERSION).

Source

ctx.fs.resolve(path, opts?)

abstract resolve(path: string, opts?: { cwd?: string }): Promise<FsTarget>

Resolve a model/plugin-supplied path into a stable FsTarget. May perform I/O (a remote/sandboxed backend may need a round-trip to map a path to a stable identity), hence async even though the local backend only normalizes + realpaths. opts.cwd is the base directory a RELATIVE path resolves against; an absolute path ignores it. Omitted ⇒ the backend's own default base (the local backend uses its configured cwd). The CALLER supplies this — the seam does not read a session or agent — so a tool can resolve against the caller's per-session workspace (exec.agent.session.header.cwd) without the provider depending on dsh-agent/dsh-session. Mirrors how dsh-tool-bash defaults a bash workdir to the session cwd.

  • path — the path to resolve; relative paths resolve against opts.cwd.
  • opts — cwd overrides the backend's default base for relative paths.

Returns the stable target; the same file yields the same targetKey.

Source

ctx.fs.stat(target, signal?)

abstract stat(target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined>

Return target metadata, or undefined when the target does not exist.

  • target — the resolved target to stat.
  • signal — aborts the metadata round-trip.

Returns metadata only, never content; undefined for an absent target.

Source

ctx.fs.readText(target, signal?)

abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>

Read the whole regular text file as a single decoded string.

  • target — the resolved target to read.
  • signal — aborts the read.

Returns the full decoded UTF-8 content.

Source

ctx.fs.streamText(target, signal?)

abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>

Stream the whole regular text file as decoded text chunks (same text semantics as readText, for large files). The backend owns cross-chunk UTF-8 decoding and binary rejection so the policy layer never touches raw bytes.

  • target — the resolved target to read.
  • signal — aborts the stream, including between chunks.

Returns the chunk iterable, decoded and validated like {@link readText}.

Source

ctx.fs.listDir(target, signal?)

abstract listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>

List direct children of a directory in stable name order. Returns resolved child targets plus cheap metadata only; never reads file contents.

  • target — the resolved directory target.
  • signal — aborts the listing.

Returns one entry per direct child, in stable name order.

Source

ctx.fs.writeText(target, content, expected?, signal?)

abstract writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal): Promise<FsWriteOutcome>

Create or fully replace a UTF-8 text file atomically. expected is the create-vs-replace decision and stale guard when supplied; OMITTING it is an unconditional create-or-overwrite (the bare provider — no version guard, no read-first requirement). Atomic either way.

  • target — the resolved target to write.
  • content — the full new file content.
  • expected — the write intent guarding the write; omit for unconditional.
  • signal — aborts before the atomic rename takes effect.

Returns the outcome, including the version the write produced.

Source

ctx.fs.editText(target, edit, expected?, signal?)

abstract editText(target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal): Promise<FsEditOutcome>

Apply a literal edit to an existing UTF-8 text file. When expected is supplied, verifies expected.version as the stale guard BEFORE literal matching; OMITTING it edits the current content unconditionally (no version guard). Either way applies the replacement and writes atomically — one mutation critical section — and a missing target reports FS_STALE_VERSION.

  • target — the resolved target to edit.
  • edit — the literal search/replace request.
  • expected — the version guard; omit for an unconditional edit.
  • signal — aborts before the atomic rename takes effect.

Returns the outcome, including the version the edit produced.

Source