mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat: add global sdk event pipeline
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "^1.1.19",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@solid-primitives/event-bus": "^1.1.2",
|
||||
"@solid-primitives/storage": "^4.3.3",
|
||||
"@tauri-apps/api": "^2.0.0",
|
||||
"@tauri-apps/plugin-dialog": "~2.6.0",
|
||||
|
||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
||||
'@radix-ui/colors':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
'@solid-primitives/event-bus':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(solid-js@1.9.10)
|
||||
'@solid-primitives/storage':
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(solid-js@1.9.10)
|
||||
@@ -447,6 +450,11 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@solid-primitives/event-bus@1.1.2':
|
||||
resolution: {integrity: sha512-l+n10/51neGcMaP3ypYt21bXfoeWh8IaC8k7fYuY3ww2a8S1Zv2N2a7FF5Qn+waTu86l0V8/nRHjkyqVIZBYwA==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
|
||||
'@solid-primitives/storage@4.3.3':
|
||||
resolution: {integrity: sha512-ACbNwMZ1s8VAvld6EUXkDkX/US3IhtlPLxg6+B2s9MwNUugwdd51I98LPEaHrdLpqPmyzqgoJe0TxEFlf3Dqrw==}
|
||||
peerDependencies:
|
||||
@@ -1281,6 +1289,11 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.55.1':
|
||||
optional: true
|
||||
|
||||
'@solid-primitives/event-bus@1.1.2(solid-js@1.9.10)':
|
||||
dependencies:
|
||||
'@solid-primitives/utils': 6.3.2(solid-js@1.9.10)
|
||||
solid-js: 1.9.10
|
||||
|
||||
'@solid-primitives/storage@4.3.3(solid-js@1.9.10)':
|
||||
dependencies:
|
||||
'@solid-primitives/utils': 6.3.2(solid-js@1.9.10)
|
||||
|
||||
131
src/app/context/global-sdk.tsx
Normal file
131
src/app/context/global-sdk.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client";
|
||||
import { createGlobalEmitter } from "@solid-primitives/event-bus";
|
||||
import { batch, createContext, onCleanup, useContext, type ParentProps } from "solid-js";
|
||||
|
||||
import { usePlatform } from "./platform";
|
||||
import { useServer } from "./server";
|
||||
|
||||
type GlobalSDKContextValue = {
|
||||
url: string;
|
||||
client: ReturnType<typeof createOpencodeClient>;
|
||||
event: ReturnType<typeof createGlobalEmitter<{ [key: string]: Event }>>;
|
||||
};
|
||||
|
||||
const GlobalSDKContext = createContext<GlobalSDKContextValue | undefined>(undefined);
|
||||
|
||||
export function GlobalSDKProvider(props: ParentProps) {
|
||||
const server = useServer();
|
||||
const platform = usePlatform();
|
||||
const abort = new AbortController();
|
||||
|
||||
const eventClient = createOpencodeClient({
|
||||
baseUrl: server.url,
|
||||
signal: abort.signal,
|
||||
fetch: platform.fetch,
|
||||
});
|
||||
|
||||
const emitter = createGlobalEmitter<{ [key: string]: Event }>();
|
||||
|
||||
type Queued = { directory: string; payload: Event };
|
||||
|
||||
let queue: Array<Queued | undefined> = [];
|
||||
const coalesced = new Map<string, number>();
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
let last = 0;
|
||||
|
||||
const keyForEvent = (directory: string, payload: Event) => {
|
||||
if (payload.type === "session.status") return `session.status:${directory}:${payload.properties.sessionID}`;
|
||||
if (payload.type === "lsp.updated") return `lsp.updated:${directory}`;
|
||||
if (payload.type === "todo.updated") return `todo.updated:${directory}:${payload.properties.sessionID}`;
|
||||
if (payload.type === "mcp.tools.changed") return `mcp.tools.changed:${directory}:${payload.properties.server}`;
|
||||
if (payload.type === "message.part.updated") {
|
||||
const part = payload.properties.part;
|
||||
return `message.part.updated:${directory}:${part.messageID}:${part.id}`;
|
||||
}
|
||||
};
|
||||
|
||||
const flush = () => {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = undefined;
|
||||
|
||||
const events = queue;
|
||||
queue = [];
|
||||
coalesced.clear();
|
||||
if (events.length === 0) return;
|
||||
|
||||
last = Date.now();
|
||||
batch(() => {
|
||||
for (const entry of events) {
|
||||
if (!entry) continue;
|
||||
emitter.emit(entry.directory, entry.payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const schedule = () => {
|
||||
if (timer) return;
|
||||
const elapsed = Date.now() - last;
|
||||
timer = setTimeout(flush, Math.max(0, 16 - elapsed));
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
flush();
|
||||
};
|
||||
|
||||
void (async () => {
|
||||
const subscription = await eventClient.event.subscribe(undefined, { signal: abort.signal });
|
||||
let yielded = Date.now();
|
||||
|
||||
for await (const event of subscription.stream as AsyncIterable<unknown>) {
|
||||
const record = event as Event & { directory?: string; payload?: Event };
|
||||
const payload = record.payload ?? record;
|
||||
if (!payload?.type) continue;
|
||||
|
||||
const directory = typeof record.directory === "string" ? record.directory : "global";
|
||||
const key = keyForEvent(directory, payload);
|
||||
if (key) {
|
||||
const index = coalesced.get(key);
|
||||
if (index !== undefined) {
|
||||
queue[index] = undefined;
|
||||
}
|
||||
coalesced.set(key, queue.length);
|
||||
}
|
||||
|
||||
queue.push({ directory, payload });
|
||||
schedule();
|
||||
|
||||
if (Date.now() - yielded < 8) continue;
|
||||
yielded = Date.now();
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
})()
|
||||
.finally(stop)
|
||||
.catch(() => undefined);
|
||||
|
||||
onCleanup(() => {
|
||||
abort.abort();
|
||||
stop();
|
||||
});
|
||||
|
||||
const client = createOpencodeClient({
|
||||
baseUrl: server.url,
|
||||
fetch: platform.fetch,
|
||||
throwOnError: true,
|
||||
});
|
||||
|
||||
const value: GlobalSDKContextValue = {
|
||||
url: server.url,
|
||||
client,
|
||||
event: emitter,
|
||||
};
|
||||
|
||||
return <GlobalSDKContext.Provider value={value}>{props.children}</GlobalSDKContext.Provider>;
|
||||
}
|
||||
|
||||
export function useGlobalSDK() {
|
||||
const context = useContext(GlobalSDKContext);
|
||||
if (!context) {
|
||||
throw new Error("Global SDK context is missing");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user