Sign In

@opencode-ai/client

Package Overview
Dependencies
Maintainers
2
Versions
920
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@opencode-ai/client - npm Package Compare versions

Comparing version
0.0.0-dev-17518
to
0.0.0-dev-17534
+29
dist/solid/connection.d.ts
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"
}
}