Skip to content

Commit c2bd5c9

Browse files
committed
fix: address tool/plugin issues anomalyco#11413, anomalyco#11130, anomalyco#11436
- fix(provider): remove non-standard JSON Schema fields (ref, $schema) for Gemini compatibility (anomalyco#11413) - fix(config): detect and report duplicate keys in config files with helpful error message (anomalyco#11130) - fix(file): add 100ms tolerance to file modification time check to prevent false positives (anomalyco#11436)
1 parent 511c7ab commit c2bd5c9

3 files changed

Lines changed: 47 additions & 3 deletions

File tree

‎packages/opencode/src/config/config.ts‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
modify,
1919
parse as parseJsonc,
2020
printParseErrorCode,
21+
visit as visitJsonc,
2122
} from "jsonc-parser"
2223
import { Instance } from "../project/instance"
2324
import { LSPServer } from "../lsp/server"
@@ -1190,8 +1191,27 @@ export namespace Config {
11901191
}
11911192

11921193
const errors: JsoncParseError[] = []
1194+
const duplicateKeys: { key: string; offset: number }[] = []
1195+
const keyStack: string[][] = [[]]
1196+
visitJsonc(text, {
1197+
onObjectBegin() {
1198+
keyStack.push([])
1199+
},
1200+
onObjectEnd() {
1201+
keyStack.pop()
1202+
},
1203+
onObjectProperty(property, offset) {
1204+
const currentLevel = keyStack[keyStack.length - 1]
1205+
if (currentLevel.includes(property)) {
1206+
duplicateKeys.push({ key: property, offset })
1207+
} else {
1208+
currentLevel.push(property)
1209+
}
1210+
},
1211+
})
1212+
11931213
const data = parseJsonc(text, errors, { allowTrailingComma: true })
1194-
if (errors.length) {
1214+
if (errors.length || duplicateKeys.length) {
11951215
const lines = text.split("\n")
11961216
const errorDetails = errors
11971217
.map((e) => {
@@ -1207,9 +1227,25 @@ export namespace Config {
12071227
})
12081228
.join("\n")
12091229

1230+
const duplicateDetails = duplicateKeys
1231+
.map((d) => {
1232+
const beforeOffset = text.substring(0, d.offset).split("\n")
1233+
const line = beforeOffset.length
1234+
const column = beforeOffset[beforeOffset.length - 1].length + 1
1235+
const problemLine = lines[line - 1]
1236+
1237+
const error = `Duplicate key "${d.key}" at line ${line}, column ${column}. Use an array for multiple values, e.g. "plugin": ["value1", "value2"]`
1238+
if (!problemLine) return error
1239+
1240+
return `${error}\n Line ${line}: ${problemLine}\n${"".padStart(column + 9)}^`
1241+
})
1242+
.join("\n")
1243+
1244+
const allErrors = [errorDetails, duplicateDetails].filter(Boolean).join("\n")
1245+
12101246
throw new JsonError({
12111247
path: configFilepath,
1212-
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
1248+
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${allErrors}\n--- End ---`,
12131249
})
12141250
}
12151251

‎packages/opencode/src/file/time.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ export namespace FileTime {
6060
const time = get(sessionID, filepath)
6161
if (!time) throw new Error(`You must read file ${filepath} before overwriting it. Use the Read tool first`)
6262
const stats = await Bun.file(filepath).stat()
63-
if (stats.mtime.getTime() > time.getTime()) {
63+
const mtime = stats.mtime.getTime()
64+
const readTime = time.getTime()
65+
// Allow 100ms tolerance to account for filesystem timestamp precision differences
66+
// Some filesystems may update mtime slightly after the read operation completes
67+
if (mtime > readTime + 100) {
6468
throw new Error(
6569
`File ${filepath} has been modified since it was last read.\nLast modification: ${stats.mtime.toISOString()}\nLast read: ${time.toISOString()}\n\nPlease read the file again before modifying it.`,
6670
)

‎packages/opencode/src/provider/transform.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,10 @@ export namespace ProviderTransform {
758758

759759
const result: any = {}
760760
for (const [key, value] of Object.entries(obj)) {
761+
// Skip non-standard JSON Schema fields that Gemini doesn't support
762+
if (key === "ref" || key === "$schema") {
763+
continue
764+
}
761765
if (key === "enum" && Array.isArray(value)) {
762766
// Convert all enum values to strings
763767
result[key] = value.map((v) => String(v))

0 commit comments

Comments
 (0)