mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-27 20:54:21 +01:00
204 lines
6.9 KiB
TypeScript
204 lines
6.9 KiB
TypeScript
import type { Message, Agent, Provider, Session, Part, Config, Path, File, FileNode, Project } from "@opencode-ai/sdk"
|
|
import { createStore, produce, reconcile } from "solid-js/store"
|
|
import { createMemo } from "solid-js"
|
|
import { Binary } from "@/utils/binary"
|
|
import { createSimpleContext } from "./helper"
|
|
import { useSDK } from "./sdk"
|
|
|
|
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|
name: "Sync",
|
|
init: () => {
|
|
const [store, setStore] = createStore<{
|
|
ready: boolean
|
|
provider: Provider[]
|
|
agent: Agent[]
|
|
project: Project
|
|
config: Config
|
|
path: Path
|
|
session: Session[]
|
|
limit: number
|
|
more: boolean
|
|
message: {
|
|
[sessionID: string]: Message[]
|
|
}
|
|
part: {
|
|
[messageID: string]: Part[]
|
|
}
|
|
node: FileNode[]
|
|
changes: File[]
|
|
}>({
|
|
project: { id: "", worktree: "", time: { created: 0, initialized: 0 } },
|
|
config: {},
|
|
path: { state: "", config: "", worktree: "", directory: "" },
|
|
ready: false,
|
|
agent: [],
|
|
provider: [],
|
|
session: [],
|
|
limit: 10,
|
|
more: false,
|
|
message: {},
|
|
part: {},
|
|
node: [],
|
|
changes: [],
|
|
})
|
|
|
|
const sdk = useSDK()
|
|
sdk.event.listen((e) => {
|
|
const event = e.details
|
|
switch (event.type) {
|
|
case "session.updated": {
|
|
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
|
|
if (result.found) {
|
|
setStore("session", result.index, reconcile(event.properties.info))
|
|
break
|
|
}
|
|
setStore(
|
|
"session",
|
|
produce((draft) => {
|
|
draft.splice(result.index, 0, event.properties.info)
|
|
}),
|
|
)
|
|
break
|
|
}
|
|
case "message.updated": {
|
|
const messages = store.message[event.properties.info.sessionID]
|
|
if (!messages) {
|
|
setStore("message", event.properties.info.sessionID, [event.properties.info])
|
|
break
|
|
}
|
|
const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
|
|
if (result.found) {
|
|
setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
|
|
break
|
|
}
|
|
setStore(
|
|
"message",
|
|
event.properties.info.sessionID,
|
|
produce((draft) => {
|
|
draft.splice(result.index, 0, event.properties.info)
|
|
}),
|
|
)
|
|
break
|
|
}
|
|
case "message.part.updated": {
|
|
const part = sanitizePart(event.properties.part)
|
|
const parts = store.part[part.messageID]
|
|
if (!parts) {
|
|
setStore("part", part.messageID, [part])
|
|
break
|
|
}
|
|
const result = Binary.search(parts, part.id, (p) => p.id)
|
|
if (result.found) {
|
|
setStore("part", part.messageID, result.index, reconcile(part))
|
|
break
|
|
}
|
|
setStore(
|
|
"part",
|
|
part.messageID,
|
|
produce((draft) => {
|
|
draft.splice(result.index, 0, part)
|
|
}),
|
|
)
|
|
break
|
|
}
|
|
}
|
|
})
|
|
|
|
const load = {
|
|
project: () => sdk.client.project.current().then((x) => setStore("project", x.data!)),
|
|
provider: () => sdk.client.config.providers().then((x) => setStore("provider", x.data!.providers)),
|
|
path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)),
|
|
agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
|
session: () =>
|
|
sdk.client.session.list().then((x) => {
|
|
const sessions = (x.data ?? [])
|
|
.slice()
|
|
.sort((a, b) => a.id.localeCompare(b.id))
|
|
.slice(0, store.limit)
|
|
setStore("session", sessions)
|
|
setStore("more", sessions.length === store.limit)
|
|
}),
|
|
config: () => sdk.client.config.get().then((x) => setStore("config", x.data!)),
|
|
changes: () => sdk.client.file.status().then((x) => setStore("changes", x.data!)),
|
|
node: () => sdk.client.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)),
|
|
}
|
|
|
|
Promise.all(Object.values(load).map((p) => p())).then(() => setStore("ready", true))
|
|
|
|
const sanitizer = createMemo(() => new RegExp(`${store.path.directory}/`, "g"))
|
|
const sanitize = (text: string) => text.replace(sanitizer(), "")
|
|
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
|
|
const sanitizePart = (part: Part) => {
|
|
if (part.type === "tool") {
|
|
if (part.state.status === "completed" || part.state.status === "error") {
|
|
for (const key in part.state.metadata) {
|
|
if (typeof part.state.metadata[key] === "string") {
|
|
part.state.metadata[key] = sanitize(part.state.metadata[key] as string)
|
|
}
|
|
}
|
|
for (const key in part.state.input) {
|
|
if (typeof part.state.input[key] === "string") {
|
|
part.state.input[key] = sanitize(part.state.input[key] as string)
|
|
}
|
|
}
|
|
if ("error" in part.state) {
|
|
part.state.error = sanitize(part.state.error as string)
|
|
}
|
|
}
|
|
}
|
|
return part
|
|
}
|
|
|
|
return {
|
|
data: store,
|
|
set: setStore,
|
|
get ready() {
|
|
return store.ready
|
|
},
|
|
session: {
|
|
get(sessionID: string) {
|
|
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
|
if (match.found) return store.session[match.index]
|
|
return undefined
|
|
},
|
|
async sync(sessionID: string, isRetry = false) {
|
|
const [session, messages] = await Promise.all([
|
|
sdk.client.session.get({ path: { id: sessionID } }),
|
|
sdk.client.session.messages({ path: { id: sessionID } }),
|
|
])
|
|
|
|
// If no messages and this might be a new session, retry after a delay
|
|
if (!isRetry && messages.data!.length === 0) {
|
|
setTimeout(() => this.sync(sessionID, true), 500)
|
|
return
|
|
}
|
|
|
|
setStore(
|
|
produce((draft) => {
|
|
const match = Binary.search(draft.session, sessionID, (s) => s.id)
|
|
draft.session[match.index] = session.data!
|
|
draft.message[sessionID] = messages
|
|
.data!.map((x) => x.info)
|
|
.slice()
|
|
.sort((a, b) => a.id.localeCompare(b.id))
|
|
for (const message of messages.data!) {
|
|
draft.part[message.info.id] = message.parts
|
|
.slice()
|
|
.map(sanitizePart)
|
|
.sort((a, b) => a.id.localeCompare(b.id))
|
|
}
|
|
}),
|
|
)
|
|
},
|
|
fetch: async (count = 10) => {
|
|
setStore("limit", (x) => x + count)
|
|
await load.session()
|
|
},
|
|
},
|
|
load,
|
|
absolute,
|
|
sanitize,
|
|
}
|
|
},
|
|
})
|