Description
The OpenCode edit boundary rejects a valid edits[] request when OpenCode or the model includes empty-string sentinels for the other optional mode fields.
Environment
@cortexkit/aft-opencode: 0.49.0
@cortexkit/aft-bridge: 0.49.0
- OpenCode: 1.18.4
- Windows x64
Reproduction
Send this to the hoisted edit tool:
{
"filePath": "src/example.ts",
"edits": [
{ "oldString": "old", "newString": "new" }
],
"appendContent": "",
"symbol": "",
"content": ""
}
The same failure occurs if path is used instead of filePath.
Actual behavior
edit: conflicting modes: appendContent, edits, symbol/content
Expected behavior
Empty optional mode fields should be treated as omitted when exactly one real mode is present. The request should normalize to the equivalent of:
{
"path": "src/example.ts",
"edits": [
{ "oldString": "old", "newString": "new" }
]
}
Genuine mixed-mode calls should continue to be rejected. Empty content must remain valid when paired with a non-empty symbol, because replacing a symbol with empty content may be intentional.
Root cause
packages/aft-bridge/src/path-aliases.ts counts edit modes with property-presence checks:
if (hasOwn(record, "appendContent")) modes.push("appendContent");
if (hasOwn(record, "edits")) modes.push("edits");
if (hasOwn(record, "symbol") || hasOwn(record, "content")) modes.push("symbol/content");
Therefore empty-string sentinels are treated as active modes before the request reaches the Rust edit engine. The boundary should distinguish empty optional sentinels from meaningful mode values while preserving valid empty replacement content.
Workaround
A local tool.execute.before hook that removes empty competing mode fields allows the request through, but this should be fixed in the shared AFT boundary normalizer.
Description
The OpenCode edit boundary rejects a valid
edits[]request when OpenCode or the model includes empty-string sentinels for the other optional mode fields.Environment
@cortexkit/aft-opencode: 0.49.0@cortexkit/aft-bridge: 0.49.0Reproduction
Send this to the hoisted
edittool:{ "filePath": "src/example.ts", "edits": [ { "oldString": "old", "newString": "new" } ], "appendContent": "", "symbol": "", "content": "" }The same failure occurs if
pathis used instead offilePath.Actual behavior
Expected behavior
Empty optional mode fields should be treated as omitted when exactly one real mode is present. The request should normalize to the equivalent of:
{ "path": "src/example.ts", "edits": [ { "oldString": "old", "newString": "new" } ] }Genuine mixed-mode calls should continue to be rejected. Empty
contentmust remain valid when paired with a non-emptysymbol, because replacing a symbol with empty content may be intentional.Root cause
packages/aft-bridge/src/path-aliases.tscounts edit modes with property-presence checks:Therefore empty-string sentinels are treated as active modes before the request reaches the Rust edit engine. The boundary should distinguish empty optional sentinels from meaningful mode values while preserving valid empty replacement content.
Workaround
A local
tool.execute.beforehook that removes empty competing mode fields allows the request through, but this should be fixed in the shared AFT boundary normalizer.