refactor(core): move database schema ownership (#29068)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,17 +1,14 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { spawn } from "child_process"
|
||||
import { Database } from "@/storage/db"
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite"
|
||||
import { Database as BunDatabase } from "bun:sqlite"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { JsonMigration } from "@/storage/json-migration"
|
||||
import { EOL } from "os"
|
||||
import { errorMessage } from "../../util/error"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { Effect } from "effect"
|
||||
import { sql } from "drizzle-orm"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
|
||||
const QueryCommand = cmd({
|
||||
const QueryCommand = effectCmd({
|
||||
command: "$0 [query]",
|
||||
describe: "open an interactive sqlite3 shell or run a query",
|
||||
instance: false,
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
.positional("query", {
|
||||
@@ -25,96 +22,41 @@ const QueryCommand = cmd({
|
||||
describe: "Output format",
|
||||
})
|
||||
},
|
||||
handler: async (args: { query?: string; format: string }) => {
|
||||
handler: Effect.fn("Cli.db.query")(function* (args: { query?: string; format: string }) {
|
||||
const query = args.query as string | undefined
|
||||
if (query) {
|
||||
const db = new BunDatabase(Database.getPath(), { readonly: true })
|
||||
try {
|
||||
const result = db.query(query).all() as Record<string, unknown>[]
|
||||
if (args.format === "json") {
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
} else if (result.length > 0) {
|
||||
const keys = Object.keys(result[0])
|
||||
console.log(keys.join("\t"))
|
||||
for (const row of result) {
|
||||
console.log(keys.map((k) => row[k]).join("\t"))
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
UI.error(errorMessage(err))
|
||||
process.exit(1)
|
||||
const { db } = yield* Database.Service
|
||||
const result = yield* db.all<Record<string, unknown>>(sql.raw(query)).pipe(Effect.orDie)
|
||||
if (args.format === "json") console.log(JSON.stringify(result, null, 2))
|
||||
else if (result.length > 0) {
|
||||
const keys = Object.keys(result[0])
|
||||
console.log(keys.join("\t"))
|
||||
for (const row of result) console.log(keys.map((key) => row[key]).join("\t"))
|
||||
}
|
||||
db.close()
|
||||
return
|
||||
}
|
||||
const child = spawn("sqlite3", [Database.getPath()], {
|
||||
const child = spawn("sqlite3", [Database.path()], {
|
||||
stdio: "inherit",
|
||||
})
|
||||
await new Promise((resolve) => child.on("close", resolve))
|
||||
},
|
||||
yield* Effect.promise(() => new Promise((resolve) => child.on("close", resolve)))
|
||||
}),
|
||||
})
|
||||
|
||||
const PathCommand = cmd({
|
||||
const PathCommand = effectCmd({
|
||||
command: "path",
|
||||
describe: "print the database path",
|
||||
handler: () => {
|
||||
console.log(Database.getPath())
|
||||
},
|
||||
instance: false,
|
||||
handler: Effect.fn("Cli.db.path")(function* () {
|
||||
console.log(Database.path())
|
||||
}),
|
||||
})
|
||||
|
||||
const MigrateCommand = cmd({
|
||||
command: "migrate",
|
||||
describe: "migrate JSON data to SQLite (merges with existing data)",
|
||||
handler: async () => {
|
||||
const sqlite = new BunDatabase(Database.getPath())
|
||||
const tty = process.stderr.isTTY
|
||||
const width = 36
|
||||
const orange = "\x1b[38;5;214m"
|
||||
const muted = "\x1b[0;2m"
|
||||
const reset = "\x1b[0m"
|
||||
let last = -1
|
||||
if (tty) process.stderr.write("\x1b[?25l")
|
||||
try {
|
||||
const stats = await JsonMigration.run(drizzle({ client: sqlite }), {
|
||||
progress: (event) => {
|
||||
const percent = Math.floor((event.current / event.total) * 100)
|
||||
if (percent === last) return
|
||||
last = percent
|
||||
if (tty) {
|
||||
const fill = Math.round((percent / 100) * width)
|
||||
const bar = `${"■".repeat(fill)}${"・".repeat(width - fill)}`
|
||||
process.stderr.write(
|
||||
`\r${orange}${bar} ${percent.toString().padStart(3)}%${reset} ${muted}${event.current}/${event.total}${reset} `,
|
||||
)
|
||||
} else {
|
||||
process.stderr.write(`sqlite-migration:${percent}${EOL}`)
|
||||
}
|
||||
},
|
||||
})
|
||||
if (tty) process.stderr.write("\n")
|
||||
if (tty) process.stderr.write("\x1b[?25h")
|
||||
else process.stderr.write(`sqlite-migration:done${EOL}`)
|
||||
UI.println(
|
||||
`Migration complete: ${stats.projects} projects, ${stats.sessions} sessions, ${stats.messages} messages`,
|
||||
)
|
||||
if (stats.errors.length > 0) {
|
||||
UI.println(`${stats.errors.length} errors occurred during migration`)
|
||||
}
|
||||
} catch (err) {
|
||||
if (tty) process.stderr.write("\x1b[?25h")
|
||||
UI.error(`Migration failed: ${errorMessage(err)}`)
|
||||
process.exit(1)
|
||||
} finally {
|
||||
sqlite.close()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const DbCommand = cmd({
|
||||
export const DbCommand = effectCmd({
|
||||
command: "db",
|
||||
describe: "database tools",
|
||||
instance: false,
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs.command(QueryCommand).command(PathCommand).command(MigrateCommand).demandCommand()
|
||||
return yargs.command(QueryCommand).command(PathCommand).demandCommand()
|
||||
},
|
||||
handler: () => {},
|
||||
handler: Effect.fn("Cli.db")(function* () {}),
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EOL } from "os"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { basename } from "path"
|
||||
import { Cause, Effect } from "effect"
|
||||
import { Agent } from "../../../agent/agent"
|
||||
@@ -163,7 +164,7 @@ const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(functio
|
||||
)
|
||||
})
|
||||
const now = Date.now()
|
||||
const message: MessageV2.Assistant = {
|
||||
const message: SessionLegacy.Assistant = {
|
||||
id: messageID,
|
||||
sessionID: session.id,
|
||||
role: "assistant",
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { EOL } from "os"
|
||||
import { Project } from "@/project/project"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
import { cmd } from "../cmd"
|
||||
|
||||
const runtime = makeRuntime(Project.Service, Project.defaultLayer)
|
||||
|
||||
export const ScrapCommand = cmd({
|
||||
command: "scrap",
|
||||
describe: "list all known projects",
|
||||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
const timer = Log.Default.time("scrap")
|
||||
const list = await Project.list()
|
||||
const list = await runtime.runPromise((project) => project.list())
|
||||
process.stdout.write(JSON.stringify(list, null, 2) + EOL)
|
||||
timer.stop()
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { SessionID } from "../../session/schema"
|
||||
import { effectCmd, fail } from "../effect-cmd"
|
||||
@@ -31,7 +32,7 @@ function diff(kind: string, diffs: { file?: string; patch?: string }[] | undefin
|
||||
}))
|
||||
}
|
||||
|
||||
function source(part: MessageV2.FilePart) {
|
||||
function source(part: SessionLegacy.FilePart) {
|
||||
if (!part.source) return part.source
|
||||
if (part.source.type === "symbol") {
|
||||
return {
|
||||
@@ -56,7 +57,7 @@ function source(part: MessageV2.FilePart) {
|
||||
}
|
||||
}
|
||||
|
||||
function filepart(part: MessageV2.FilePart): MessageV2.FilePart {
|
||||
function filepart(part: SessionLegacy.FilePart): SessionLegacy.FilePart {
|
||||
return {
|
||||
...part,
|
||||
url: redact("file-url", part.id, part.url),
|
||||
@@ -65,7 +66,7 @@ function filepart(part: MessageV2.FilePart): MessageV2.FilePart {
|
||||
}
|
||||
}
|
||||
|
||||
function part(part: MessageV2.Part): MessageV2.Part {
|
||||
function part(part: SessionLegacy.Part): SessionLegacy.Part {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return {
|
||||
@@ -159,7 +160,7 @@ function part(part: MessageV2.Part): MessageV2.Part {
|
||||
|
||||
const partFn = part
|
||||
|
||||
function sanitize(data: { info: Session.Info; messages: MessageV2.WithParts[] }) {
|
||||
function sanitize(data: { info: Session.Info; messages: SessionLegacy.WithParts[] }) {
|
||||
return {
|
||||
info: {
|
||||
...data.info,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from "path"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { exec } from "child_process"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import * as prompts from "@clack/prompts"
|
||||
@@ -26,8 +27,9 @@ import { Session } from "@/session/session"
|
||||
import type { SessionID } from "../../session/schema"
|
||||
import { MessageID, PartID } from "../../session/schema"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { Bus } from "../../bus"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { SessionPrompt } from "@/session/prompt"
|
||||
import { Git } from "@/git"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
@@ -159,7 +161,7 @@ export { parseGitHubRemote }
|
||||
* Returns null for non-text responses (signals summary needed).
|
||||
* Throws only for truly empty responses.
|
||||
*/
|
||||
export function extractResponseText(parts: MessageV2.Part[]): string | null {
|
||||
export function extractResponseText(parts: SessionLegacy.Part[]): string | null {
|
||||
const textPart = parts.findLast((p) => p.type === "text")
|
||||
if (textPart) return textPart.text
|
||||
|
||||
@@ -435,7 +437,7 @@ export const GithubRunCommand = effectCmd({
|
||||
const sessionSvc = yield* Session.Service
|
||||
const sessionShare = yield* SessionShare.Service
|
||||
const sessionPrompt = yield* SessionPrompt.Service
|
||||
const busSvc = yield* Bus.Service
|
||||
const events = yield* EventV2Bridge.Service
|
||||
const runLocalEffect = <A, E>(effect: Effect.Effect<A, E>) =>
|
||||
Effect.runPromise(effect.pipe(Effect.provideService(InstanceRef, ctx)))
|
||||
yield* Effect.promise(async () => {
|
||||
@@ -897,10 +899,12 @@ export const GithubRunCommand = effectCmd({
|
||||
|
||||
let text = ""
|
||||
await runLocalEffect(
|
||||
busSvc.subscribeCallback(MessageV2.Event.PartUpdated, (evt) => {
|
||||
if (evt.properties.part.sessionID !== session.id) return
|
||||
events.listen((evt) => {
|
||||
if (evt.type !== MessageV2.Event.PartUpdated.type) return Effect.void
|
||||
const data = evt.data as EventV2.Data<typeof MessageV2.Event.PartUpdated>
|
||||
if (data.part.sessionID !== session.id) return Effect.void
|
||||
//if (evt.properties.part.messageID === messageID) return
|
||||
const part = evt.properties.part
|
||||
const part = data.part
|
||||
|
||||
if (part.type === "tool" && part.state.status === "completed") {
|
||||
const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD]
|
||||
@@ -920,9 +924,10 @@ export const GithubRunCommand = effectCmd({
|
||||
UI.println(UI.markdown(text))
|
||||
UI.empty()
|
||||
text = ""
|
||||
return
|
||||
return Effect.void
|
||||
}
|
||||
}
|
||||
return Effect.void
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { Session } from "@/session/session"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { CliError, effectCmd } from "../effect-cmd"
|
||||
import { Database } from "@/storage/db"
|
||||
import { SessionTable, MessageTable, PartTable } from "../../session/session.sql"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { SessionTable, MessageTable, PartTable } from "@opencode-ai/core/session/sql"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { ShareNext } from "@/share/share-next"
|
||||
import { EOL } from "os"
|
||||
@@ -12,8 +13,8 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Effect, Schema } from "effect"
|
||||
import type { InstanceContext } from "@/project/instance-context"
|
||||
|
||||
const decodeMessageInfo = Schema.decodeUnknownSync(MessageV2.Info)
|
||||
const decodePart = Schema.decodeUnknownSync(MessageV2.Part)
|
||||
const decodeMessageInfo = Schema.decodeUnknownSync(SessionLegacy.Info)
|
||||
const decodePart = Schema.decodeUnknownSync(SessionLegacy.Part)
|
||||
|
||||
/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */
|
||||
export type ShareData =
|
||||
@@ -98,6 +99,7 @@ export const ImportCommand = effectCmd({
|
||||
const runImport = Effect.fn("Cli.import.body")(function* (file: string, ctx: InstanceContext) {
|
||||
const share = yield* ShareNext.Service
|
||||
const fs = yield* AppFileSystem.Service
|
||||
const { db } = yield* Database.Service
|
||||
|
||||
let exportData: ExportData | undefined
|
||||
|
||||
@@ -175,48 +177,45 @@ const runImport = Effect.fn("Cli.import.body")(function* (file: string, ctx: Ins
|
||||
path: path.relative(path.resolve(ctx.worktree), ctx.directory).replaceAll("\\", "/"),
|
||||
}) as Session.Info
|
||||
const row = Session.toRow(info)
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(SessionTable)
|
||||
.values(row)
|
||||
.onConflictDoUpdate({
|
||||
target: SessionTable.id,
|
||||
set: { project_id: row.project_id, directory: row.directory, path: row.path },
|
||||
})
|
||||
.run(),
|
||||
)
|
||||
yield* db
|
||||
.insert(SessionTable)
|
||||
.values(row)
|
||||
.onConflictDoUpdate({
|
||||
target: SessionTable.id,
|
||||
set: { project_id: row.project_id, directory: row.directory, path: row.path },
|
||||
})
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
for (const msg of exportData.messages) {
|
||||
const msgInfo = decodeMessageInfo(msg.info) as MessageV2.Info
|
||||
const msgInfo = decodeMessageInfo(msg.info) as SessionLegacy.Info
|
||||
const { id, sessionID: _, ...msgData } = msgInfo
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(MessageTable)
|
||||
.values({
|
||||
id,
|
||||
session_id: row.id,
|
||||
time_created: msgInfo.time?.created ?? Date.now(),
|
||||
data: msgData,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run(),
|
||||
)
|
||||
yield* db
|
||||
.insert(MessageTable)
|
||||
.values({
|
||||
id,
|
||||
session_id: row.id,
|
||||
time_created: msgInfo.time?.created ?? Date.now(),
|
||||
data: msgData as never,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
for (const part of msg.parts) {
|
||||
const partInfo = decodePart(part) as MessageV2.Part
|
||||
const partInfo = decodePart(part) as SessionLegacy.Part
|
||||
const { id: partId, sessionID: _s, messageID, ...partData } = partInfo
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(PartTable)
|
||||
.values({
|
||||
id: partId,
|
||||
message_id: messageID,
|
||||
session_id: row.id,
|
||||
data: partData,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run(),
|
||||
)
|
||||
yield* db
|
||||
.insert(PartTable)
|
||||
.values({
|
||||
id: partId,
|
||||
message_id: messageID,
|
||||
session_id: row.id,
|
||||
data: partData,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { modify, applyEdits } from "jsonc-parser"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Bus } from "../../bus"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Effect } from "effect"
|
||||
|
||||
function getAuthStatusIcon(status: MCP.AuthStatus): string {
|
||||
@@ -256,13 +257,17 @@ export const McpAuthCommand = effectCmd({
|
||||
spinner.start("Starting OAuth flow...")
|
||||
|
||||
// Subscribe to browser open failure events to show URL for manual opening
|
||||
const unsubscribe = Bus.subscribe(MCP.BrowserOpenFailed, (evt) => {
|
||||
if (evt.properties.mcpName === serverName) {
|
||||
const events = yield* EventV2Bridge.Service
|
||||
const unsubscribe = yield* events.listen((event) => {
|
||||
if (event.type !== MCP.BrowserOpenFailed.type) return Effect.void
|
||||
const data = event.data as EventV2.Data<typeof MCP.BrowserOpenFailed>
|
||||
if (data.mcpName === serverName) {
|
||||
spinner.stop("Could not open browser automatically")
|
||||
prompts.log.warn("Please open this URL in your browser to authenticate:")
|
||||
prompts.log.info(evt.properties.url)
|
||||
prompts.log.info(data.url)
|
||||
spinner.start("Waiting for authorization...")
|
||||
}
|
||||
return Effect.void
|
||||
})
|
||||
|
||||
yield* MCP.Service.use((mcp) => mcp.authenticate(serverName)).pipe(
|
||||
@@ -300,7 +305,7 @@ export const McpAuthCommand = effectCmd({
|
||||
prompts.log.error(error instanceof Error ? error.message : String(error))
|
||||
}),
|
||||
),
|
||||
Effect.ensuring(Effect.sync(() => unsubscribe())),
|
||||
Effect.ensuring(unsubscribe),
|
||||
)
|
||||
|
||||
prompts.outro("Done")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { EOL } from "os"
|
||||
import { Effect } from "effect"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ProviderID } from "../../provider/schema"
|
||||
|
||||
import { ModelsDev } from "@opencode-ai/core/models-dev"
|
||||
import { effectCmd, fail } from "../effect-cmd"
|
||||
import { UI } from "../ui"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
|
||||
export const ModelsCommand = effectCmd({
|
||||
command: "models [provider]",
|
||||
@@ -33,7 +34,7 @@ export const ModelsCommand = effectCmd({
|
||||
const provider = yield* Provider.Service
|
||||
const providers = yield* provider.list()
|
||||
|
||||
const print = (providerID: ProviderID, verbose?: boolean) => {
|
||||
const print = (providerID: ProviderV2.ID, verbose?: boolean) => {
|
||||
const p = providers[providerID]
|
||||
const sorted = Object.entries(p.models).sort(([a], [b]) => a.localeCompare(b))
|
||||
for (const [modelID, model] of sorted) {
|
||||
@@ -47,7 +48,7 @@ export const ModelsCommand = effectCmd({
|
||||
}
|
||||
|
||||
if (args.provider) {
|
||||
const providerID = ProviderID.make(args.provider)
|
||||
const providerID = ProviderV2.ID.make(args.provider)
|
||||
if (!providers[providerID]) return yield* fail(`Provider not found: ${args.provider}`)
|
||||
print(providerID, args.verbose)
|
||||
return
|
||||
@@ -61,6 +62,6 @@ export const ModelsCommand = effectCmd({
|
||||
return a.localeCompare(b)
|
||||
})
|
||||
|
||||
for (const providerID of ids) print(ProviderID.make(providerID), args.verbose)
|
||||
for (const providerID of ids) print(ProviderV2.ID.make(providerID), args.verbose)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Effect } from "effect"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { Session } from "@/session/session"
|
||||
import { NotFoundError } from "@/storage/storage"
|
||||
import { Database } from "@/storage/db"
|
||||
import { SessionTable } from "../../session/session.sql"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { Project } from "@/project/project"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
|
||||
@@ -80,9 +80,10 @@ export const StatsCommand = effectCmd({
|
||||
}),
|
||||
})
|
||||
|
||||
const getAllSessions = Effect.sync(() =>
|
||||
Database.use((db) => db.select().from(SessionTable).all()).map((row) => Session.fromRow(row)),
|
||||
)
|
||||
const getAllSessions = Effect.fnUntraced(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
return (yield* db.select().from(SessionTable).all().pipe(Effect.orDie)).map((row) => Session.fromRow(row))
|
||||
})
|
||||
|
||||
const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* (
|
||||
days?: number,
|
||||
@@ -90,7 +91,7 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* (
|
||||
currentProject?: Project.Info,
|
||||
) {
|
||||
const svc = yield* Session.Service
|
||||
const sessions = yield* getAllSessions
|
||||
const sessions = yield* getAllSessions()
|
||||
const MS_IN_DAY = 24 * 60 * 60 * 1000
|
||||
|
||||
const cutoffTime = (() => {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { SessionID } from "@/session/schema"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Effect, Schema } from "effect"
|
||||
|
||||
const DEFAULT_TOAST_DURATION = 5000
|
||||
|
||||
export const TuiEvent = {
|
||||
PromptAppend: BusEvent.define("tui.prompt.append", Schema.Struct({ text: Schema.String })),
|
||||
CommandExecute: BusEvent.define(
|
||||
"tui.command.execute",
|
||||
Schema.Struct({
|
||||
PromptAppend: EventV2.define({ type: "tui.prompt.append", schema: { text: Schema.String } }),
|
||||
CommandExecute: EventV2.define({
|
||||
type: "tui.command.execute",
|
||||
schema: {
|
||||
command: Schema.Union([
|
||||
Schema.Literals([
|
||||
"session.list",
|
||||
@@ -31,23 +31,23 @@ export const TuiEvent = {
|
||||
]),
|
||||
Schema.String,
|
||||
]),
|
||||
}),
|
||||
),
|
||||
ToastShow: BusEvent.define(
|
||||
"tui.toast.show",
|
||||
Schema.Struct({
|
||||
},
|
||||
}),
|
||||
ToastShow: EventV2.define({
|
||||
type: "tui.toast.show",
|
||||
schema: {
|
||||
title: Schema.optional(Schema.String),
|
||||
message: Schema.String,
|
||||
variant: Schema.Literals(["info", "success", "warning", "error"]),
|
||||
duration: PositiveInt.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({
|
||||
description: "Duration in milliseconds",
|
||||
}),
|
||||
}),
|
||||
),
|
||||
SessionSelect: BusEvent.define(
|
||||
"tui.session.select",
|
||||
Schema.Struct({
|
||||
},
|
||||
}),
|
||||
SessionSelect: EventV2.define({
|
||||
type: "tui.session.select",
|
||||
schema: {
|
||||
sessionID: SessionID.annotate({ description: "Session ID to navigate to" }),
|
||||
}),
|
||||
),
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { TextAttributes } from "@opentui/core"
|
||||
import { Schema } from "effect"
|
||||
import { TuiEvent } from "../event"
|
||||
|
||||
type ToastInput = Schema.Codec.Encoded<typeof TuiEvent.ToastShow.properties>
|
||||
export type ToastOptions = Schema.Schema.Type<typeof TuiEvent.ToastShow.properties>
|
||||
type ToastInput = Schema.Codec.Encoded<typeof TuiEvent.ToastShow.data>
|
||||
export type ToastOptions = Schema.Schema.Type<typeof TuiEvent.ToastShow.data>
|
||||
|
||||
const decodeToastOptions = Schema.decodeUnknownSync(TuiEvent.ToastShow.properties)
|
||||
const decodeToastOptions = Schema.decodeUnknownSync(TuiEvent.ToastShow.data)
|
||||
|
||||
export function Toast() {
|
||||
const toast = useToast()
|
||||
|
||||
Reference in New Issue
Block a user