| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- import {
- type Message,
- type Agent,
- type Session,
- type Part,
- type Config,
- type Path,
- type File,
- type FileNode,
- type Project,
- type FileDiff,
- type Todo,
- type SessionStatus,
- type ProviderListResponse,
- type ProviderAuthResponse,
- createOpencodeClient,
- } from "@opencode-ai/sdk/v2/client"
- import { createStore, produce, reconcile } from "solid-js/store"
- import { Binary } from "@opencode-ai/util/binary"
- import { createSimpleContext } from "@opencode-ai/ui/context"
- import { useGlobalSDK } from "./global-sdk"
- import { onMount } from "solid-js"
- type State = {
- ready: boolean
- agent: Agent[]
- project: string
- provider: ProviderListResponse
- config: Config
- path: Path
- session: Session[]
- session_status: {
- [sessionID: string]: SessionStatus
- }
- session_diff: {
- [sessionID: string]: FileDiff[]
- }
- todo: {
- [sessionID: string]: Todo[]
- }
- limit: number
- message: {
- [sessionID: string]: Message[]
- }
- part: {
- [messageID: string]: Part[]
- }
- node: FileNode[]
- changes: File[]
- }
- export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimpleContext({
- name: "GlobalSync",
- init: () => {
- const globalSDK = useGlobalSDK()
- const [globalStore, setGlobalStore] = createStore<{
- ready: boolean
- project: Project[]
- provider: ProviderListResponse
- provider_auth: ProviderAuthResponse
- children: Record<string, State>
- }>({
- ready: false,
- project: [],
- provider: { all: [], connected: [], default: {} },
- provider_auth: {},
- children: {},
- })
- async function bootstrapInstance(directory: string) {
- const [store, setStore] = child(directory)
- const sdk = createOpencodeClient({
- baseUrl: globalSDK.url,
- directory,
- })
- const load = {
- project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
- provider: () => sdk.provider.list().then((x) => setStore("provider", x.data!)),
- path: () => sdk.path.get().then((x) => setStore("path", x.data!)),
- agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
- session: () =>
- sdk.session.list().then((x) => {
- const sessions = (x.data ?? [])
- .slice()
- .sort((a, b) => a.id.localeCompare(b.id))
- .slice(0, store.limit)
- setStore("session", sessions)
- }),
- status: () => sdk.session.status().then((x) => setStore("session_status", x.data!)),
- config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
- changes: () => sdk.file.status().then((x) => setStore("changes", x.data!)),
- node: () => sdk.file.list({ path: "/" }).then((x) => setStore("node", x.data!)),
- }
- await Promise.all(Object.values(load).map((p) => p())).then(() => setStore("ready", true))
- }
- const children: Record<string, ReturnType<typeof createStore<State>>> = {}
- function child(directory: string) {
- if (!children[directory]) {
- setGlobalStore("children", directory, {
- project: "",
- provider: { all: [], connected: [], default: {} },
- config: {},
- path: { state: "", config: "", worktree: "", directory: "", home: "" },
- ready: false,
- agent: [],
- session: [],
- session_status: {},
- session_diff: {},
- todo: {},
- limit: 5,
- message: {},
- part: {},
- node: [],
- changes: [],
- })
- children[directory] = createStore(globalStore.children[directory])
- bootstrapInstance(directory)
- }
- return children[directory]
- }
- globalSDK.event.listen((e) => {
- const directory = e.name
- const event = e.details
- if (directory === "global") {
- switch (event.type) {
- case "global.disposed": {
- bootstrap()
- break
- }
- case "project.updated": {
- const result = Binary.search(globalStore.project, event.properties.id, (s) => s.id)
- if (result.found) {
- setGlobalStore("project", result.index, reconcile(event.properties))
- return
- }
- setGlobalStore(
- "project",
- produce((draft) => {
- draft.splice(result.index, 0, event.properties)
- }),
- )
- break
- }
- }
- return
- }
- const [store, setStore] = child(directory)
- switch (event.type) {
- case "server.instance.disposed": {
- bootstrapInstance(directory)
- break
- }
- 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 "session.diff":
- setStore("session_diff", event.properties.sessionID, event.properties.diff)
- break
- case "todo.updated":
- setStore("todo", event.properties.sessionID, event.properties.todos)
- break
- case "session.status": {
- setStore("session_status", event.properties.sessionID, event.properties.status)
- 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 = 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
- }
- }
- })
- async function bootstrap() {
- return Promise.all([
- globalSDK.client.project.list().then(async (x) => {
- setGlobalStore(
- "project",
- x
- .data!.filter((p) => !p.worktree.includes("opencode-test") && p.vcs)
- .sort((a, b) => a.id.localeCompare(b.id)),
- )
- }),
- globalSDK.client.provider.list().then((x) => {
- setGlobalStore("provider", x.data ?? {})
- }),
- globalSDK.client.provider.auth().then((x) => {
- setGlobalStore("provider_auth", x.data ?? {})
- }),
- ]).then(() => setGlobalStore("ready", true))
- }
- onMount(() => {
- bootstrap()
- })
- return {
- data: globalStore,
- get ready() {
- return globalStore.ready
- },
- child,
- bootstrap,
- }
- },
- })
|