New:Socket for Asana Is Now Available.Learn more
Get Started

@opencode-ai/client

Package Overview
Dependencies
Maintainers
2
Versions
1267
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-beta-18387
to
0.0.0-beta-18414
+9
dist/pty-handoff.d.ts
export * as PtyHandoff from "./pty-handoff.js";
import type { Info } from "./service.js";
/** Publish the ticket before stopping its owner so every replacement contender can adopt it. */
export declare function prepare(file: string, info: Info, timeout: number): Promise<void>;
export declare function environment(file: string, env?: Readonly<Record<string, string>>): Promise<{
OPENCODE_PTY_HANDOFF: string | undefined;
}>;
export declare function complete(file: string, info: Info): Promise<void>;
export declare function clear(file: string): Promise<void>;
export * as PtyHandoff from "./pty-handoff.js";
import { readFile, rename, rm, writeFile } from "node:fs/promises";
/** Publish the ticket before stopping its owner so every replacement contender can adopt it. */
export async function prepare(file, info, timeout) {
const existing = await read(file);
if (existing !== undefined && existing.expiresAt > Date.now() && same(existing.source, info))
return;
const { ClientError, OpenCode } = await import("./promise/index.js");
const client = OpenCode.make({
baseUrl: info.url,
headers: info.password === undefined
? undefined
: { authorization: "Basic " + Buffer.from(`opencode:${info.password}`).toString("base64") },
});
const missing = (error) => error instanceof ClientError &&
error.reason === "UnexpectedStatus" &&
typeof error.cause === "object" &&
error.cause !== null &&
"status" in error.cause &&
error.cause.status === 404;
const result = await client.experimental.persistentPty.handoff({ signal: AbortSignal.timeout(timeout) }).then((value) => ({ value }), (cause) => ({ cause }));
if ("cause" in result) {
// Another caller may already have prepared and stopped this server.
const concurrent = await read(file);
if (concurrent !== undefined && concurrent.expiresAt > Date.now() && same(concurrent.source, info))
return;
if (!missing(result.cause))
throw new Error("Failed to prepare persistent terminals for service replacement", { cause: result.cause });
console.warn("Background service cannot hand off persistent terminals; shutting them down before replacement");
await client.experimental.persistentPty
.shutdown({ signal: AbortSignal.timeout(timeout) })
.catch((cause) => {
if (missing(cause))
return;
throw new Error("Failed to shut down persistent terminals before service replacement", { cause });
});
await publish(file, info, null);
return;
}
const body = result.value;
if (typeof body !== "object" || body === null || !("handoff" in body))
throw new Error("Invalid persistent terminal handoff response");
if (body.handoff === null) {
await publish(file, info, null);
return;
}
if (!isHandoff(body.handoff) || body.handoff.expiresAt <= Date.now())
throw new Error("Invalid or expired persistent terminal handoff");
await publish(file, info, body.handoff);
}
async function publish(file, info, handoff) {
const temporary = `${file}.pty-handoff.${crypto.randomUUID()}.tmp`;
await writeFile(temporary, JSON.stringify({
source: { id: info.id, pid: info.pid, url: info.url },
handoff,
expiresAt: handoff?.expiresAt ?? Date.now() + 30_000,
}), { mode: 0o600, flag: "wx" });
await rename(temporary, file + ".pty-handoff").finally(() => rm(temporary, { force: true }));
}
export async function environment(file, env) {
const record = await read(file);
const current = await readFile(file, "utf8")
.then((text) => JSON.parse(text))
.catch(() => undefined);
const handoff = record !== undefined && record.expiresAt > Date.now() && (current === undefined || same(record.source, current))
? record.handoff
: undefined;
return { ...env, OPENCODE_PTY_HANDOFF: handoff == null ? undefined : JSON.stringify(handoff) };
}
export async function complete(file, info) {
const record = await read(file);
if (record !== undefined && !same(record.source, info))
await clear(file);
}
export async function clear(file) {
await rm(file + ".pty-handoff", { force: true });
}
async function read(file) {
const value = await readFile(file + ".pty-handoff", "utf8")
.then((text) => JSON.parse(text))
.catch(() => undefined);
if (typeof value !== "object" || value === null || !("source" in value) || !("handoff" in value))
return;
if (typeof value.source !== "object" || value.source === null)
return;
if (!("pid" in value.source) || typeof value.source.pid !== "number")
return;
if (!("url" in value.source) || typeof value.source.url !== "string")
return;
if ("id" in value.source && typeof value.source.id !== "string")
return;
if (value.handoff !== null && !isHandoff(value.handoff))
return;
if (!("expiresAt" in value) || typeof value.expiresAt !== "number" || !Number.isFinite(value.expiresAt))
return;
return {
source: {
id: "id" in value.source && typeof value.source.id === "string" ? value.source.id : undefined,
pid: value.source.pid,
url: value.source.url,
},
handoff: value.handoff,
expiresAt: value.expiresAt,
};
}
function same(left, right) {
return left.id === right.id && left.pid === right.pid && left.url === right.url;
}
function isHandoff(value) {
return (typeof value === "object" &&
value !== null &&
"directory" in value &&
typeof value.directory === "string" &&
"instanceID" in value &&
typeof value.instanceID === "string" &&
"ticket" in value &&
typeof value.ticket === "string" &&
"expiresAt" in value &&
typeof value.expiresAt === "number" &&
Number.isFinite(value.expiresAt));
}
+2
-0

@@ -438,2 +438,3 @@ // Generated by @opencode-ai/httpapi-codegen. Do not edit.

const EndpointExperimentalPersistentPtyShutdown = (raw) => () => preserveEffect()(raw["persistentPty.shutdown"]({}).pipe(Effect.mapError(mapClientError)));
const EndpointExperimentalPersistentPtyHandoff = (raw) => () => preserveEffect()(raw["persistentPty.handoff"]({}).pipe(Effect.mapError(mapClientError)));
const EndpointExperimentalPersistentPtyGet = (raw) => (input) => preserveEffect()(raw["persistentPty.get"]({ params: { ptyID: input["ptyID"] } }).pipe(Effect.mapError(mapClientError), Effect.map((value) => value.data)));

@@ -455,2 +456,3 @@ const EndpointExperimentalPersistentPtyUpdate = (raw) => (input) => preserveEffect()(raw["persistentPty.update"]({

shutdown: EndpointExperimentalPersistentPtyShutdown(raw),
handoff: EndpointExperimentalPersistentPtyHandoff(raw),
get: EndpointExperimentalPersistentPtyGet(raw),

@@ -457,0 +459,0 @@ update: EndpointExperimentalPersistentPtyUpdate(raw),

@@ -8,2 +8,3 @@ import { ServiceStatus } from "@opencode-ai/protocol/groups/health";

import { matchesVersion } from "../service-version.js";
import { PtyHandoff } from "../pty-handoff.js";
export * from "../service.js";

@@ -59,5 +60,6 @@ // Find, start, and stop the local opencode background service.

return yield* Effect.fail(new Error("Missing service command"));
const env = yield* Effect.tryPromise(() => PtyHandoff.environment(options.file ?? fallback(), options.env));
return yield* Effect.try({
try: () => {
return spawnServiceContender(command, args, options.env);
return spawnServiceContender(command, args, env);
},

@@ -78,2 +80,4 @@ catch: (cause) => new Error("Failed to start server", { cause }),

yield* announce("missing");
yield* Effect.logWarning("Background service is unresponsive; recovery cannot preserve persistent terminals");
yield* Effect.tryPromise(() => PtyHandoff.clear(options.file ?? fallback()));
yield* terminate(info, options, timing);

@@ -89,4 +93,6 @@ timeouts = undefined;

const compatible = !service.legacy && matchesVersion(service.version, options);
if (compatible && service.state === "ready")
if (compatible && service.state === "ready") {
yield* Effect.tryPromise(() => PtyHandoff.complete(options.file ?? fallback(), service.info));
return Option.some(service);
}
if (compatible && service.state === "failed")

@@ -97,2 +103,9 @@ return yield* Effect.fail(new Error("Background service failed to start"));

yield* announce("version-mismatch", service.version);
if (!service.legacy && service.state === "ready")
yield* Effect.tryPromise(() => PtyHandoff.prepare(options.file ?? fallback(), service.info, timing.requestTimeout));
else {
if (!service.legacy)
yield* Effect.logWarning("Background service is not ready; replacement cannot preserve persistent terminals");
yield* Effect.tryPromise(() => PtyHandoff.clear(options.file ?? fallback()));
}
yield* terminate(service.info, options, timing).pipe(Effect.ignore);

@@ -129,2 +142,3 @@ lastSpawn = 0;

export const stop = Effect.fn("service.stop")(function* (options = {}) {
yield* Effect.tryPromise(() => PtyHandoff.clear(options.file ?? fallback()));
const info = yield* read(options.file);

@@ -131,0 +145,0 @@ if (info !== undefined)

+2
-1

@@ -1,2 +0,2 @@

import type { ServerGetOutput, LocationGetInput, LocationGetOutput, AgentListInput, AgentListOutput, AgentGetInput, AgentGetOutput, PluginListInput, PluginListOutput, SessionListInput, SessionStatsInput, SessionCreateInput, SessionImportInput, SessionExportInput, SessionGetInput, SessionRemoveInput, SessionForkInput, SessionSwitchAgentInput, SessionSwitchModelInput, SessionRenameInput, SessionMoveInput, SessionPromptInput, SessionCommandInput, SessionSkillInput, SessionSyntheticInput, SessionShellInput, SessionCompactInput, SessionWaitInput, SessionRevertStageInput, SessionRevertClearInput, SessionRevertCommitInput, SessionContextInput, SessionInboxListInput, SessionInboxCancelInput, SessionInboxSteerInput, SessionInboxQueueInput, SessionInstructionsEntryListInput, SessionInstructionsEntryPutInput, SessionInstructionsEntryRemoveInput, SessionGenerateInput, SessionLogInput, SessionLogOutput, SessionInterruptInput, SessionBackgroundInput, SessionMessageInput, SessionMessageUpdateInput, SessionEnvironmentInput, SessionViewInput, MessageListInput, ModelListInput, ModelListOutput, ModelDefaultInput, ModelDefaultOutput, GenerateTextInput, ProviderListInput, ProviderListOutput, ProviderGetInput, ProviderGetOutput, IntegrationListInput, IntegrationListOutput, IntegrationGetInput, IntegrationGetOutput, IntegrationWellknownAddInput, IntegrationConnectKeyInput, IntegrationOauthConnectInput, IntegrationOauthConnectOutput, IntegrationOauthStatusInput, IntegrationOauthStatusOutput, IntegrationOauthCompleteInput, IntegrationOauthCancelInput, IntegrationCommandConnectInput, IntegrationCommandConnectOutput, IntegrationCommandStatusInput, IntegrationCommandStatusOutput, IntegrationCommandCancelInput, McpListInput, McpListOutput, McpAddInput, McpRemoveInput, McpConnectInput, McpDisconnectInput, McpResourceCatalogInput, McpResourceCatalogOutput, CredentialUpdateInput, CredentialActivateInput, CredentialRemoveInput, ProjectListOutput, ProjectUpdateInput, ProjectCurrentInput, FormRequestListInput, FormRequestListOutput, FormListInput, FormCreateInput, FormGetInput, FormStateInput, FormReplyInput, FormCancelInput, PermissionRequestListInput, PermissionRequestListOutput, PermissionSavedListInput, PermissionSavedRemoveInput, PermissionCreateInput, PermissionListInput, PermissionGetInput, PermissionReplyInput, FileReadInput, FileReadOutput, FileListInput, FileListOutput, FileFindInput, FileFindOutput, CommandListInput, CommandListOutput, SkillListInput, SkillListOutput, EventSubscribeOutput, PtyListInput, PtyListOutput, PtyCreateInput, PtyCreateOutput, PtyGetInput, PtyGetOutput, PtyUpdateInput, PtyUpdateOutput, PtyRemoveInput, PtyConnectTokenInput, PtyConnectTokenOutput, ExperimentalPersistentPtyListInput, ExperimentalPersistentPtyCreateInput, ExperimentalPersistentPtyGetInput, ExperimentalPersistentPtyUpdateInput, ExperimentalPersistentPtySnapshotInput, ExperimentalPersistentPtyRemoveInput, ExperimentalPersistentPtyConnectTokenInput, ShellListInput, ShellListOutput, ShellCreateInput, ShellCreateOutput, ShellGetInput, ShellGetOutput, ShellTimeoutInput, ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, ReferenceListInput, ReferenceListOutput, WorktreeListInput, WorktreeCreateInput, WorktreeRemoveInput, WorktreeRefreshInput, WorkspaceCreateInput, WorkspaceDestroyInput, VcsGetInput, VcsGetOutput, VcsStatusInput, VcsStatusOutput, VcsBranchesInput, VcsBranchesOutput, VcsDiffInput, VcsDiffOutput, DebugLocationListOutput, DebugLocationEvictInput, MigrationV1StatusOutput, WebsearchProvidersInput, WebsearchProvidersOutput, WebsearchQueryInput, WebsearchQueryOutput, ConfigGetInput, ConfigGetOutput } from "./types.js";
import type { ServerGetOutput, LocationGetInput, LocationGetOutput, AgentListInput, AgentListOutput, AgentGetInput, AgentGetOutput, PluginListInput, PluginListOutput, SessionListInput, SessionStatsInput, SessionCreateInput, SessionImportInput, SessionExportInput, SessionGetInput, SessionRemoveInput, SessionForkInput, SessionSwitchAgentInput, SessionSwitchModelInput, SessionRenameInput, SessionMoveInput, SessionPromptInput, SessionCommandInput, SessionSkillInput, SessionSyntheticInput, SessionShellInput, SessionCompactInput, SessionWaitInput, SessionRevertStageInput, SessionRevertClearInput, SessionRevertCommitInput, SessionContextInput, SessionInboxListInput, SessionInboxCancelInput, SessionInboxSteerInput, SessionInboxQueueInput, SessionInstructionsEntryListInput, SessionInstructionsEntryPutInput, SessionInstructionsEntryRemoveInput, SessionGenerateInput, SessionLogInput, SessionLogOutput, SessionInterruptInput, SessionBackgroundInput, SessionMessageInput, SessionMessageUpdateInput, SessionEnvironmentInput, SessionViewInput, MessageListInput, ModelListInput, ModelListOutput, ModelDefaultInput, ModelDefaultOutput, GenerateTextInput, ProviderListInput, ProviderListOutput, ProviderGetInput, ProviderGetOutput, IntegrationListInput, IntegrationListOutput, IntegrationGetInput, IntegrationGetOutput, IntegrationWellknownAddInput, IntegrationConnectKeyInput, IntegrationOauthConnectInput, IntegrationOauthConnectOutput, IntegrationOauthStatusInput, IntegrationOauthStatusOutput, IntegrationOauthCompleteInput, IntegrationOauthCancelInput, IntegrationCommandConnectInput, IntegrationCommandConnectOutput, IntegrationCommandStatusInput, IntegrationCommandStatusOutput, IntegrationCommandCancelInput, McpListInput, McpListOutput, McpAddInput, McpRemoveInput, McpConnectInput, McpDisconnectInput, McpResourceCatalogInput, McpResourceCatalogOutput, CredentialUpdateInput, CredentialActivateInput, CredentialRemoveInput, ProjectListOutput, ProjectUpdateInput, ProjectCurrentInput, FormRequestListInput, FormRequestListOutput, FormListInput, FormCreateInput, FormGetInput, FormStateInput, FormReplyInput, FormCancelInput, PermissionRequestListInput, PermissionRequestListOutput, PermissionSavedListInput, PermissionSavedRemoveInput, PermissionCreateInput, PermissionListInput, PermissionGetInput, PermissionReplyInput, FileReadInput, FileReadOutput, FileListInput, FileListOutput, FileFindInput, FileFindOutput, CommandListInput, CommandListOutput, SkillListInput, SkillListOutput, EventSubscribeOutput, PtyListInput, PtyListOutput, PtyCreateInput, PtyCreateOutput, PtyGetInput, PtyGetOutput, PtyUpdateInput, PtyUpdateOutput, PtyRemoveInput, PtyConnectTokenInput, PtyConnectTokenOutput, ExperimentalPersistentPtyListInput, ExperimentalPersistentPtyCreateInput, ExperimentalPersistentPtyHandoffOutput, ExperimentalPersistentPtyGetInput, ExperimentalPersistentPtyUpdateInput, ExperimentalPersistentPtySnapshotInput, ExperimentalPersistentPtyRemoveInput, ExperimentalPersistentPtyConnectTokenInput, ShellListInput, ShellListOutput, ShellCreateInput, ShellCreateOutput, ShellGetInput, ShellGetOutput, ShellTimeoutInput, ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, ReferenceListInput, ReferenceListOutput, WorktreeListInput, WorktreeCreateInput, WorktreeRemoveInput, WorktreeRefreshInput, WorkspaceCreateInput, WorkspaceDestroyInput, VcsGetInput, VcsGetOutput, VcsStatusInput, VcsStatusOutput, VcsBranchesInput, VcsBranchesOutput, VcsDiffInput, VcsDiffOutput, DebugLocationListOutput, DebugLocationEvictInput, MigrationV1StatusOutput, WebsearchProvidersInput, WebsearchProvidersOutput, WebsearchQueryInput, WebsearchQueryOutput, ConfigGetInput, ConfigGetOutput } from "./types.js";
export interface ClientOptions {

@@ -201,2 +201,3 @@ readonly baseUrl: string;

shutdown: (requestOptions?: RequestOptions) => Promise<void>;
handoff: (requestOptions?: RequestOptions) => Promise<ExperimentalPersistentPtyHandoffOutput>;
get: (input: ExperimentalPersistentPtyGetInput, requestOptions?: RequestOptions) => Promise<import("./types.js").PersistentPtyInfo>;

@@ -203,0 +204,0 @@ update: (input: ExperimentalPersistentPtyUpdateInput, requestOptions?: RequestOptions) => Promise<import("./types.js").PersistentPtyInfo>;

@@ -226,3 +226,3 @@ import { ClientError } from "./client-error.js";

successStatus: 200,
declaredStatuses: [409, 401, 400],
declaredStatuses: [409, 404, 401, 400],
empty: false,

@@ -1050,2 +1050,9 @@ }, requestOptions).then((value) => value.data),

}, requestOptions),
handoff: (requestOptions) => request({
method: "POST",
path: `/api/experimental/persistent-pty/handoff`,
successStatus: 200,
declaredStatuses: [503, 401, 400],
empty: false,
}, requestOptions),
get: (input, requestOptions) => request({

@@ -1052,0 +1059,0 @@ method: "GET",

@@ -7,2 +7,3 @@ import { readFile, rm } from "node:fs/promises";

import { matchesVersion } from "../service-version.js";
import { PtyHandoff } from "../pty-handoff.js";
export * from "../service.js";

@@ -38,3 +39,3 @@ // Find, start, and stop the local opencode background service.

};
const spawnContender = () => {
const spawnContender = async () => {
const [command, ...args] = options.command ?? ["opencode", "serve", "--service"];

@@ -44,3 +45,3 @@ if (command === undefined)

try {
return spawnServiceContender(command, args, options.env);
return spawnServiceContender(command, args, await PtyHandoff.environment(options.file ?? fallback(), options.env));
}

@@ -63,2 +64,4 @@ catch (cause) {

announce("missing");
console.warn("Background service is unresponsive; recovery cannot preserve persistent terminals");
await PtyHandoff.clear(options.file ?? fallback());
await terminate(registration.info, options, timing);

@@ -75,4 +78,6 @@ timeouts = undefined;

const compatible = !service.legacy && matchesVersion(service.version, options);
if (compatible && service.state === "ready")
if (compatible && service.state === "ready") {
await PtyHandoff.complete(options.file ?? fallback(), service.info);
return service.endpoint;
}
if (compatible && service.state === "failed")

@@ -82,2 +87,9 @@ throw new Error("Background service failed to start");

announce("version-mismatch", service.version);
if (!service.legacy && service.state === "ready")
await PtyHandoff.prepare(options.file ?? fallback(), service.info, timing.requestTimeout);
else {
if (!service.legacy)
console.warn("Background service is not ready; replacement cannot preserve persistent terminals");
await PtyHandoff.clear(options.file ?? fallback());
}
await terminate(service.info, options, timing).catch(() => undefined);

@@ -101,3 +113,3 @@ lastSpawn = 0;

announce("missing");
contenders.add(spawnContender());
contenders.add(await spawnContender());
lastSpawn = Date.now();

@@ -115,2 +127,3 @@ }

export async function stop(options = {}) {
await PtyHandoff.clear(options.file ?? fallback());
const info = await read(options.file);

@@ -117,0 +130,0 @@ if (info !== undefined)

@@ -9,4 +9,4 @@ import { type ChildProcess } from "node:child_process";

};
export declare function spawnServiceContender(command: string, args: ReadonlyArray<string>, env?: Readonly<Record<string, string>>): ServiceContender;
export declare function spawnServiceContender(command: string, args: ReadonlyArray<string>, env?: Readonly<Record<string, string | undefined>>): ServiceContender;
export declare function contenderFailure(contender: ServiceContender): Error | undefined;
export declare function contenderFinished(contender: ServiceContender): boolean;
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/client",
"version": "0.0.0-beta-18387",
"version": "0.0.0-beta-18414",
"type": "module",

@@ -60,4 +60,4 @@ "license": "MIT",

"dependencies": {
"@opencode-ai/schema": "0.0.0-beta-18387",
"@opencode-ai/protocol": "0.0.0-beta-18387"
"@opencode-ai/schema": "0.0.0-beta-18414",
"@opencode-ai/protocol": "0.0.0-beta-18414"
},

@@ -78,3 +78,3 @@ "peerDependencies": {

"@effect/platform-node": "4.0.0-rc.111",
"@opencode-ai/httpapi-codegen": "0.0.0-beta-18387",
"@opencode-ai/httpapi-codegen": "0.0.0-beta-18414",
"@tsconfig/bun": "1.0.9",

@@ -81,0 +81,0 @@ "@types/bun": "1.3.13",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display