Files
deepseek-harness/packages/fs/tool-fs/src/write.ts
T
Dudu-0223 ef37ce3b9d refactor(fs): split filesystem seam into provider ctx.fs + policy ctx.fileContext
Implements the split-the-filesystem-seam RFC. ctx.fs shrinks to a text-storage
provider seam (resolve/stat/readText/streamText/writeText/editText with branded
FsTargetKey/FsVersion and an explicit FsWriteExpectation); the new
dsh-file-context package owns the model-facing policy (read windowing,
observed-state, write/edit freshness) as the concrete ctx.fileContext service.

Authorization is now freshness-based rather than full/partial view: a windowed
read records the file version and authorizes a later edit when the file is
unchanged, removing the dead-end where reading lines 100-150 of a large file
could not edit line 120. editText stays a provider primitive so version guard +
literal match + atomic rewrite remain one critical section, and the stale check
runs before matching so a stale edit reports FS_STALE_VERSION. tool-fs injects
fileContext, never reaching around to ctx.fs (the no-bypass contract).
2026-06-26 17:23:18 +08:00

64 lines
2.7 KiB
TypeScript

/**
* The model-facing `write` tool: create or fully replace a UTF-8 text file.
* Execution goes through `ctx.fileContext`, which enforces the freshness policy
* (creating a new file needs no prior read; replacing an existing file requires
* a prior read in the same execution context at the unchanged version).
*
* @module @deepseek-ai/dsh-tool-fs/write
*/
import type { Context } from 'cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { FsWriteOutcome } from '@deepseek-ai/dsh-fs'
import type {} from '@deepseek-ai/dsh-system-prompt'
/** Validate value constraints the schema DSL can't express. */
export function parseWriteArgs(args: { file_path: string; content: string }): { filePath: string; content: string } {
if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')
return { filePath: args.file_path, content: args.content }
}
/** Format a write outcome as one model-facing text block body. */
export function formatWriteOutput(displayPath: string, outcome: FsWriteOutcome): string {
const verb = outcome.operation === 'create' ? 'Created' : 'Updated'
return `<path>${displayPath}</path>
<type>file</type>
<content>
${verb} file
</content>`
}
/** Register the `write` tool and its system-prompt guidance. */
export function apply(ctx: Context): void {
ctx.systemPrompt.section({
name: 'tool:write',
order: 101,
text: 'Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the backend requires it) and prefer edit for targeted changes.',
})
ctx.tools.register(defineTool({
name: 'write',
description: 'Create or fully replace a UTF-8 text file.',
parameters: {
file_path: { type: 'string', required: true, description: 'Path to write, resolved by the filesystem backend.' },
content: { type: 'string', required: true, description: 'Full UTF-8 text content to write.' },
},
async execute(args, exec): Promise<ContentBlock[]> {
const input = parseWriteArgs(args)
const target = await ctx.fileContext.resolve(input.filePath)
const outcome = await ctx.fileContext.write(target, input.content, exec, exec.signal)
return [{ type: 'text', text: formatWriteOutput(target.displayPath, outcome) }]
},
}))
}
/** Cordis plugin name used by loader diagnostics. */
export const name = 'fs-write'
/** Services required by the `write` tool plugin. */
export const inject = ['tools', 'fileContext', 'systemPrompt']
/** Named helper for direct registration in the root plugin and tests. */
export const applyWriteTool = apply