Files
oh-my-opencode/src/plugin/ultrawork-db-model-override.ts
T

172 lines
5.1 KiB
TypeScript
Raw Normal View History

import { join } from "node:path"
import { existsSync } from "node:fs"
import { getDataDir } from "../shared/data-path"
import { log } from "../shared"
type BunDatabase = import("bun:sqlite").Database
type SqliteModule = { Database: new (path: string) => BunDatabase }
/** @internal test-only seam: override to simulate non-Bun runtime */
let _bunSqliteImporter: () => Promise<SqliteModule | null> = () =>
import("bun:sqlite").catch(() => null) as Promise<SqliteModule | null>
/** @internal test-only */
export function __setBunSqliteImporterForTesting(
impl: () => Promise<SqliteModule | null>,
): void {
_bunSqliteImporter = impl
}
/** @internal test-only */
export function __resetBunSqliteImporterForTesting(): void {
_bunSqliteImporter = () => import("bun:sqlite").catch(() => null) as Promise<SqliteModule | null>
}
function getDbPath(): string {
return join(getDataDir(), "opencode", "opencode.db")
}
const MAX_MICROTASK_RETRIES = 10
function tryUpdateMessageModel(
db: BunDatabase,
messageId: string,
targetModel: { providerID: string; modelID: string },
variant?: string,
): boolean {
const stmt = db.prepare(
`UPDATE message SET data = json_set(data, '$.model.providerID', ?, '$.model.modelID', ?) WHERE id = ?`,
)
const result = stmt.run(targetModel.providerID, targetModel.modelID, messageId)
if (result.changes === 0) return false
if (variant) {
db.prepare(
`UPDATE message SET data = json_set(data, '$.variant', ?, '$.thinking', ?) WHERE id = ?`,
).run(variant, variant, messageId)
}
return true
}
function retryViaMicrotask(
db: BunDatabase,
messageId: string,
targetModel: { providerID: string; modelID: string },
variant: string | undefined,
attempt: number,
): void {
if (attempt >= MAX_MICROTASK_RETRIES) {
log("[ultrawork-db-override] Exhausted microtask retries, falling back to setTimeout", {
messageId,
attempt,
})
setTimeout(() => {
try {
if (tryUpdateMessageModel(db, messageId, targetModel, variant)) {
log(`[ultrawork-db-override] setTimeout fallback succeeded: ${targetModel.providerID}/${targetModel.modelID}`, { messageId })
} else {
log("[ultrawork-db-override] setTimeout fallback failed - message not found", { messageId })
}
} catch (error) {
log("[ultrawork-db-override] setTimeout fallback failed with error", {
messageId,
error: String(error),
})
} finally {
try {
db.close()
} catch (error) {
log("[ultrawork-db-override] Failed to close DB after setTimeout fallback", {
messageId,
error: String(error),
})
}
}
}, 0)
return
}
queueMicrotask(() => {
let shouldCloseDb = true
try {
if (tryUpdateMessageModel(db, messageId, targetModel, variant)) {
log(`[ultrawork-db-override] Deferred DB update (attempt ${attempt}): ${targetModel.providerID}/${targetModel.modelID}`, { messageId })
return
}
shouldCloseDb = false
retryViaMicrotask(db, messageId, targetModel, variant, attempt + 1)
} catch (error) {
log("[ultrawork-db-override] Deferred DB update failed with error", {
messageId,
attempt,
error: String(error),
})
} finally {
if (shouldCloseDb) {
try {
db.close()
} catch (error) {
log("[ultrawork-db-override] Failed to close DB after deferred DB update", {
messageId,
attempt,
error: String(error),
})
}
}
}
})
}
/**
* Schedules a deferred SQLite update to change the message model in the DB
* WITHOUT triggering a Bus event. Uses microtask retry loop to wait for
* Session.updateMessage() to save the message first, then overwrites the model.
*
* Falls back to setTimeout(fn, 0) after 10 microtask attempts.
*
*/
export function scheduleDeferredModelOverride(
messageId: string,
targetModel: { providerID: string; modelID: string },
variant?: string,
): void {
queueMicrotask(async () => {
// Lazy-load bun:sqlite so this module can be imported under Node/Electron
// without crashing the ESM loader (bun: protocol is Bun-only).
const sqliteModule = await _bunSqliteImporter()
if (sqliteModule === null) {
log("[ultrawork-db-override] bun:sqlite unavailable (non-Bun runtime), skipping deferred override")
return
}
const { Database } = sqliteModule
const dbPath = getDbPath()
if (!existsSync(dbPath)) {
log("[ultrawork-db-override] DB not found, skipping deferred override")
return
}
let db: BunDatabase
try {
db = new Database(dbPath)
} catch (error) {
log("[ultrawork-db-override] Failed to open DB, skipping deferred override", {
messageId,
error: String(error),
})
return
}
try {
retryViaMicrotask(db, messageId, targetModel, variant, 0)
} catch (error) {
log("[ultrawork-db-override] Failed to apply deferred model override", {
error: String(error),
})
db.close()
}
})
}