2026-06-22 10:48:41 +08:00
/**
2026-06-28 13:49:02 +08:00
* The model-facing `write` tool: create or fully replace a UTF-8 text file. The
2026-07-02 03:12:38 +08:00
* tool is the executor: it dispatches the `fs/write-intent` waterfall to
2026-06-28 13:49:02 +08:00
* obtain the optional version guard, calls `ctx.fs.writeText` directly, and
2026-06-29 10:34:08 +08:00
* emits `fs/observed`. The default thunk returns `undefined` (unconditional
* create-or-overwrite — the bare provider); a policy plugin
2026-07-02 03:12:38 +08:00
* (`@deepseek-ai/dsh-fs-policy`) occupies the single decision slot and
2026-06-28 13:49:02 +08:00
* returns `createIfAbsent`/`replaceIfVersion` instead. The tool stats ZERO
* times either way.
2026-06-22 10:48:41 +08:00
*
2026-06-28 17:43:29 +08:00
* @module @deepseek-ai/dsh-tool-fs/src/write
2026-06-22 10:48:41 +08:00
*/
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'
2026-06-28 13:49:02 +08:00
import type { } from '@deepseek-ai/dsh-fs'
2026-06-22 10:48:41 +08:00
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. */
2026-06-28 17:43:29 +08:00
export function applyWriteTool ( ctx : Context ) : void {
2026-06-22 10:48:41 +08:00
ctx . systemPrompt . section ( {
name : 'tool:write' ,
order : 101 ,
2026-07-02 03:12:38 +08:00
text : 'Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.' ,
2026-06-22 10:48:41 +08:00
} )
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 )
2026-06-28 13:49:02 +08:00
const target = await ctx . fs . resolve ( input . filePath )
// Single-slot decision: the policy plugin produces createIfAbsent/
// replaceIfVersion; the bare default is undefined (unconditional). No stat.
2026-07-02 03:12:38 +08:00
const intent = await ctx . waterfall ( 'fs/write-intent' , target , exec , ( ) = > undefined )
const outcome = await ctx . fs . writeText ( target , input . content , intent , exec . signal )
2026-06-29 10:34:08 +08:00
// Record the observed version (a no-op when no policy plugin listens).
ctx . emit ( 'fs/observed' , target , outcome . version , exec )
2026-06-22 10:48:41 +08:00
return [ { type : 'text' , text : formatWriteOutput ( target . displayPath , outcome ) } ]
} ,
} ) )
}