@opencode-ai/client
Advanced tools
| import type { OpenCodeClient, OpenCodeEvent } from "../promise"; | ||
| export type ClientConnectionStatus = "connected" | "connecting" | "reconnecting"; | ||
| export type ClientConnectionEvent = { | ||
| readonly type: "client.connection"; | ||
| readonly created: number; | ||
| readonly data: { | ||
| readonly status: "connecting" | "connected" | "disconnected" | "reconnecting"; | ||
| readonly attempt: number; | ||
| readonly error?: string; | ||
| }; | ||
| }; | ||
| export type ClientConnectionOptions = { | ||
| readonly reconnect?: (signal: AbortSignal) => Promise<OpenCodeClient>; | ||
| readonly onEvent: (event: OpenCodeEvent) => void; | ||
| readonly flushInterval?: number; | ||
| readonly pageLifecycle?: boolean; | ||
| readonly log?: { | ||
| readonly debug?: (message: string, data?: Readonly<Record<string, unknown>>) => void; | ||
| readonly info?: (message: string, data?: Readonly<Record<string, unknown>>) => void; | ||
| }; | ||
| }; | ||
| export declare function createClientConnection(initialApi: OpenCodeClient, options: ClientConnectionOptions): { | ||
| status: () => ClientConnectionStatus; | ||
| attempt: () => number; | ||
| error: () => string | undefined; | ||
| internal: { | ||
| history: () => ClientConnectionEvent[]; | ||
| }; | ||
| }; |
| import { batch, onCleanup, onMount } from "solid-js"; | ||
| import { createStore } from "solid-js/store"; | ||
| const connectTimeout = 2_000; | ||
| const reconnectDelay = 1_000; | ||
| const connectionHistoryLimit = 50; | ||
| export function createClientConnection(initialApi, options) { | ||
| const abort = new AbortController(); | ||
| const history = []; | ||
| const [connection, setConnection] = createStore({ status: "connecting", attempt: 0 }); | ||
| let api = initialApi; | ||
| let pending = []; | ||
| let flushTimer; | ||
| let stream; | ||
| let run; | ||
| let started = false; | ||
| let generation = 0; | ||
| function record(status, attempt, error) { | ||
| history.push({ type: "client.connection", created: Date.now(), data: { status, attempt, error } }); | ||
| if (history.length > connectionHistoryLimit) | ||
| history.shift(); | ||
| } | ||
| function publish(event) { | ||
| pending.push(event); | ||
| if (flushTimer) | ||
| return; | ||
| flushTimer = setTimeout(() => { | ||
| flushTimer = undefined; | ||
| const events = pending; | ||
| pending = []; | ||
| batch(() => events.forEach(options.onEvent)); | ||
| }, options.flushInterval ?? 10); | ||
| } | ||
| async function connect(signal, attempt) { | ||
| let connectedAt; | ||
| const request = new AbortController(); | ||
| const cancel = () => request.abort(signal.reason); | ||
| const timeout = setTimeout(() => request.abort(new Error("Timed out connecting to server")), connectTimeout); | ||
| signal.addEventListener("abort", cancel, { once: true }); | ||
| try { | ||
| record(attempt === 0 ? "connecting" : "reconnecting", attempt); | ||
| options.log?.info?.("event stream connecting", { attempt }); | ||
| const iterator = api.event.subscribe({ signal: request.signal })[Symbol.asyncIterator](); | ||
| const first = await iterator.next(); | ||
| if (signal.aborted) | ||
| return { error: undefined, connectedAt }; | ||
| if (first.done) | ||
| return { | ||
| error: request.signal.reason instanceof Error ? request.signal.reason : new Error("Event stream disconnected"), | ||
| connectedAt, | ||
| }; | ||
| if (first.value.type !== "server.connected") | ||
| return { error: new Error("Event stream did not start with server.connected"), connectedAt }; | ||
| clearTimeout(timeout); | ||
| record("connected", attempt); | ||
| connectedAt = Date.now(); | ||
| options.log?.info?.("event stream connected"); | ||
| publish(first.value); | ||
| setConnection({ status: "connected", attempt: 0, error: undefined }); | ||
| while (!signal.aborted) { | ||
| const event = await iterator.next(); | ||
| if (signal.aborted) | ||
| return { error: undefined, connectedAt }; | ||
| if (event.done) | ||
| return { error: new Error("Event stream disconnected"), connectedAt }; | ||
| if ("durable" in event.value) | ||
| options.log?.debug?.("event", { | ||
| type: event.value.type, | ||
| aggregateID: event.value.durable.aggregateID, | ||
| seq: event.value.durable.seq, | ||
| }); | ||
| publish(event.value); | ||
| } | ||
| return { error: undefined, connectedAt }; | ||
| } | ||
| catch (error) { | ||
| return { error, connectedAt }; | ||
| } | ||
| finally { | ||
| request.abort(); | ||
| clearTimeout(timeout); | ||
| signal.removeEventListener("abort", cancel); | ||
| } | ||
| } | ||
| async function runStream(active) { | ||
| let attempt = 0; | ||
| while (!abort.signal.aborted && started && generation === active) { | ||
| setConnection({ status: attempt === 0 ? "connecting" : "reconnecting", attempt }); | ||
| const controller = new AbortController(); | ||
| stream = controller; | ||
| const cancel = () => controller.abort(abort.signal.reason); | ||
| abort.signal.addEventListener("abort", cancel); | ||
| const result = await connect(controller.signal, attempt); | ||
| abort.signal.removeEventListener("abort", cancel); | ||
| if (abort.signal.aborted || !started || generation !== active) | ||
| return; | ||
| if (result.connectedAt !== undefined && Date.now() - result.connectedAt >= reconnectDelay) | ||
| attempt = 0; | ||
| attempt += 1; | ||
| const message = errorMessage(result.error); | ||
| record("disconnected", attempt, message); | ||
| options.log?.info?.("event stream disconnected", { attempt, error: message }); | ||
| setConnection({ status: "reconnecting", attempt, error: message }); | ||
| if (options.reconnect) { | ||
| const next = await options.reconnect(controller.signal).catch((error) => { | ||
| if (!controller.signal.aborted) | ||
| options.log?.info?.("server resolution failed", { attempt, error: errorMessage(error) }); | ||
| }); | ||
| if (abort.signal.aborted || controller.signal.aborted || !started || generation !== active) | ||
| return; | ||
| if (next) { | ||
| api = next; | ||
| if (attempt === 1) | ||
| continue; | ||
| } | ||
| } | ||
| await wait(reconnectDelay, controller.signal); | ||
| } | ||
| } | ||
| function start() { | ||
| if (started) | ||
| return run; | ||
| started = true; | ||
| const active = ++generation; | ||
| const previous = run; | ||
| const current = (async () => { | ||
| if (previous) | ||
| await previous; | ||
| await runStream(active); | ||
| })().finally(() => { | ||
| if (run !== current) | ||
| return; | ||
| run = undefined; | ||
| }); | ||
| run = current; | ||
| return run; | ||
| } | ||
| function stop() { | ||
| started = false; | ||
| generation += 1; | ||
| stream?.abort(); | ||
| } | ||
| onMount(() => { | ||
| if (options.pageLifecycle) { | ||
| const pagehide = () => stop(); | ||
| const pageshow = (event) => { | ||
| if (event.persisted) | ||
| void start(); | ||
| }; | ||
| window.addEventListener("pagehide", pagehide); | ||
| window.addEventListener("pageshow", pageshow); | ||
| onCleanup(() => { | ||
| window.removeEventListener("pagehide", pagehide); | ||
| window.removeEventListener("pageshow", pageshow); | ||
| }); | ||
| } | ||
| void start(); | ||
| }); | ||
| onCleanup(() => { | ||
| stop(); | ||
| abort.abort(); | ||
| if (flushTimer) | ||
| clearTimeout(flushTimer); | ||
| pending = []; | ||
| }); | ||
| return { | ||
| status: () => connection.status, | ||
| attempt: () => connection.attempt, | ||
| error: () => connection.error, | ||
| internal: { | ||
| history: () => history.slice(), | ||
| }, | ||
| }; | ||
| } | ||
| function errorMessage(error) { | ||
| if (error === undefined) | ||
| return undefined; | ||
| if (error instanceof Error) | ||
| return error.message; | ||
| return String(error); | ||
| } | ||
| function wait(delay, signal) { | ||
| return new Promise((resolve) => { | ||
| const timer = setTimeout(done, delay); | ||
| signal.addEventListener("abort", done, { once: true }); | ||
| function done() { | ||
| clearTimeout(timer); | ||
| signal.removeEventListener("abort", done); | ||
| resolve(); | ||
| } | ||
| }); | ||
| } |
| import type { AgentInfo, CommandInfo, FormInfo, IntegrationInfo, LocationRef, LocationGetOutput, McpResource, McpServer, ModelInfo, PermissionSavedInfo, PermissionRequest, PermissionReplyInput, Project, ProviderInfo, ReferenceInfo, SessionMessageInfo, SessionInfo, SessionInboxInfo, ShellInfo, SkillInfo, VcsInfo, OpenCodeEvent, OpenCodeClient, WebSearchProvider } from "../promise"; | ||
| export type DataSessionStatus = "idle" | "running"; | ||
| export type CreateDataInput = { | ||
| readonly api: () => OpenCodeClient; | ||
| readonly directory: string; | ||
| readonly event: { | ||
| readonly on: <Type extends OpenCodeEvent["type"]>(type: Type, handler: (event: Extract<OpenCodeEvent, { | ||
| type: Type; | ||
| }>) => void) => () => void; | ||
| readonly listen: (handler: (event: { | ||
| name: OpenCodeEvent["type"]; | ||
| details: OpenCodeEvent; | ||
| }) => void) => () => void; | ||
| }; | ||
| readonly connection?: { | ||
| readonly status: () => "connected" | "connecting" | "reconnecting"; | ||
| }; | ||
| }; | ||
| export type FormWithLocation = FormInfo & { | ||
| readonly location?: LocationRef; | ||
| }; | ||
| type ShellWithLocation = ShellInfo & { | ||
| readonly location: LocationRef; | ||
| }; | ||
| export declare function locationKey(location: LocationRef): string; | ||
| export declare function createData(config: CreateDataInput): { | ||
| on: <Type extends OpenCodeEvent["type"]>(type: Type, handler: (event: Extract<OpenCodeEvent, { | ||
| type: Type; | ||
| }>) => void) => () => void; | ||
| listen: (handler: (event: { | ||
| name: OpenCodeEvent["type"]; | ||
| details: OpenCodeEvent; | ||
| }) => void) => () => void; | ||
| session: { | ||
| list(): SessionInfo[]; | ||
| get(sessionID: string): SessionInfo; | ||
| remember(info: SessionInfo): void; | ||
| setStatus(sessionID: string, status: DataSessionStatus): void; | ||
| lineage: { | ||
| peek(sessionID: string): { | ||
| session: SessionInfo; | ||
| root: SessionInfo; | ||
| } | undefined; | ||
| resolve(sessionID: string): Promise<{ | ||
| session: SessionInfo; | ||
| root: SessionInfo; | ||
| }>; | ||
| }; | ||
| root(sessionID: string): string; | ||
| family(sessionID: string): string[]; | ||
| cost(sessionID: string): number; | ||
| status(sessionID: string): DataSessionStatus; | ||
| input: { | ||
| list(sessionID: string): string[]; | ||
| has(sessionID: string, inboxID: string): boolean; | ||
| }; | ||
| pending: { | ||
| list(sessionID: string): SessionInboxInfo[]; | ||
| sync(sessionID: string): Promise<void>; | ||
| invalidate(sessionID: string): void; | ||
| }; | ||
| sync(sessionID: string, options?: { | ||
| children?: boolean; | ||
| }): Promise<void>; | ||
| invalidate(sessionID: string): void; | ||
| message: { | ||
| list(sessionID: string): SessionMessageInfo[]; | ||
| get(sessionID: string, messageID: string): SessionMessageInfo | undefined; | ||
| sync(sessionID: string): Promise<void>; | ||
| invalidate(sessionID: string): void; | ||
| }; | ||
| permission: { | ||
| list(sessionID: string): PermissionRequest[]; | ||
| sync(sessionID: string): Promise<void>; | ||
| invalidate(sessionID: string): void; | ||
| reply(input: PermissionReplyInput): Promise<void>; | ||
| }; | ||
| form: { | ||
| list(sessionID: string, ref?: LocationRef): FormWithLocation[] | undefined; | ||
| sync(sessionID: string, ref?: LocationRef): Promise<void>; | ||
| invalidate(sessionID: string, ref?: LocationRef): void; | ||
| }; | ||
| }; | ||
| project: { | ||
| list(): Project[]; | ||
| get(projectID: string): Project; | ||
| sync(): Promise<void>; | ||
| invalidate(): void; | ||
| permission: { | ||
| list(projectID: string): PermissionSavedInfo[]; | ||
| sync(projectID: string): Promise<void>; | ||
| invalidate(projectID: string): void; | ||
| }; | ||
| }; | ||
| shell: { | ||
| list(location?: LocationRef): ShellWithLocation[]; | ||
| listBySession(sessionID: string): ShellWithLocation[]; | ||
| get(id: string): ShellWithLocation | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| location: { | ||
| info(ref?: LocationRef): LocationGetOutput | undefined; | ||
| default(): LocationRef; | ||
| syncInfo(ref?: LocationRef): Promise<void>; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| vcs: { | ||
| info(location?: LocationRef): VcsInfo | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| agent: { | ||
| list(location?: LocationRef): AgentInfo[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| command: { | ||
| list(location?: LocationRef): CommandInfo[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| integration: { | ||
| list(location?: LocationRef): IntegrationInfo[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| mcp: { | ||
| server: { | ||
| list(location?: LocationRef): McpServer[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| resource: { | ||
| list(location?: LocationRef): McpResource[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| }; | ||
| model: { | ||
| list(location?: LocationRef): ModelInfo[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| provider: { | ||
| list(location?: LocationRef): ProviderInfo[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| reference: { | ||
| list(location?: LocationRef): ReferenceInfo[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| websearch: { | ||
| list(location?: LocationRef): WebSearchProvider[] | undefined; | ||
| refresh(ref?: LocationRef): Promise<void>; | ||
| }; | ||
| skill: { | ||
| list(location?: LocationRef): SkillInfo[] | undefined; | ||
| sync(ref?: LocationRef): Promise<void>; | ||
| invalidate(ref?: LocationRef): void; | ||
| }; | ||
| }; | ||
| }; | ||
| export type Data = ReturnType<typeof createData>; | ||
| export {}; |
Sorry, the diff of this file is too big to display
| export * from "./data"; | ||
| export * from "./connection"; |
| export * from "./data"; | ||
| export * from "./connection"; |
+15
-6
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "name": "@opencode-ai/client", | ||
| "version": "0.0.0-dev-17518", | ||
| "version": "0.0.0-dev-17534", | ||
| "type": "module", | ||
@@ -35,2 +35,6 @@ "license": "MIT", | ||
| }, | ||
| "./solid": { | ||
| "import": "./dist/solid/index.js", | ||
| "types": "./dist/solid/index.d.ts" | ||
| }, | ||
| "./effect": { | ||
@@ -57,7 +61,8 @@ "import": "./dist/effect/index.js", | ||
| "dependencies": { | ||
| "@opencode-ai/schema": "0.0.0-dev-17518", | ||
| "@opencode-ai/protocol": "0.0.0-dev-17518" | ||
| "@opencode-ai/schema": "0.0.0-dev-17534", | ||
| "@opencode-ai/protocol": "0.0.0-dev-17534" | ||
| }, | ||
| "peerDependencies": { | ||
| "effect": "4.0.0-beta.101" | ||
| "effect": "4.0.0-beta.101", | ||
| "solid-js": ">=1.9.0" | ||
| }, | ||
@@ -67,2 +72,5 @@ "peerDependenciesMeta": { | ||
| "optional": true | ||
| }, | ||
| "solid-js": { | ||
| "optional": true | ||
| } | ||
@@ -72,8 +80,9 @@ }, | ||
| "@effect/platform-node": "4.0.0-beta.101", | ||
| "@opencode-ai/httpapi-codegen": "0.0.0-dev-17518", | ||
| "@opencode-ai/httpapi-codegen": "0.0.0-dev-17534", | ||
| "@tsconfig/bun": "1.0.9", | ||
| "@types/bun": "1.3.13", | ||
| "@typescript/native-preview": "7.0.0-dev.20251207.1", | ||
| "effect": "4.0.0-beta.101" | ||
| "effect": "4.0.0-beta.101", | ||
| "solid-js": "1.9.10" | ||
| } | ||
| } |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
693623
13.67%46
15%16916
11.74%4
33.33%7
16.67%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed