@opencode-ai/client
Advanced tools
@@ -9,4 +9,12 @@ import type { Effect, Stream } from "effect"; | ||
| export type HealthGetOperation<E = never> = () => Effect.Effect<Endpoint0_0Output, E>; | ||
| type Endpoint0_1Request = Parameters<RawClient["server.health"]["health.stop"]>[0]; | ||
| export type Endpoint0_1Input = { | ||
| readonly instanceID: Endpoint0_1Request["payload"]["instanceID"]; | ||
| readonly targetVersion?: Endpoint0_1Request["payload"]["targetVersion"]; | ||
| }; | ||
| export type Endpoint0_1Output = EffectValue<ReturnType<RawClient["server.health"]["health.stop"]>>; | ||
| export type HealthStopOperation<E = never> = (input: Endpoint0_1Input) => Effect.Effect<Endpoint0_1Output, E>; | ||
| export interface HealthApi<E = never> { | ||
| readonly get: HealthGetOperation<E>; | ||
| readonly stop: HealthStopOperation<E>; | ||
| } | ||
@@ -13,0 +21,0 @@ export type Endpoint1_0Output = EffectValue<ReturnType<RawClient["server.server"]["server.get"]>>; |
@@ -12,3 +12,4 @@ // Generated by @opencode-ai/httpapi-codegen. Do not edit. | ||
| const Endpoint0_0 = (raw) => () => raw["health.get"]({}).pipe(Effect.mapError(mapClientError)); | ||
| const adaptGroup0 = (raw) => ({ get: Endpoint0_0(raw) }); | ||
| const Endpoint0_1 = (raw) => (input) => raw["health.stop"]({ payload: { instanceID: input["instanceID"], targetVersion: input["targetVersion"] } }).pipe(Effect.mapError(mapClientError)); | ||
| const adaptGroup0 = (raw) => ({ get: Endpoint0_0(raw), stop: Endpoint0_1(raw) }); | ||
| const Endpoint1_0 = (raw) => () => raw["server.get"]({}).pipe(Effect.mapError(mapClientError)); | ||
@@ -15,0 +16,0 @@ const adaptGroup1 = (raw) => ({ get: Endpoint1_0(raw) }); |
+26
-10
@@ -0,1 +1,2 @@ | ||
| import { ServiceStatus } from "@opencode-ai/protocol/groups/health"; | ||
| import { Effect, FileSystem, Schema } from "effect"; | ||
@@ -18,3 +19,19 @@ export type Endpoint = { | ||
| readonly onStart?: (reason: StartReason, existing?: Info) => void; | ||
| readonly onStatus?: (status: Status) => void; | ||
| }; | ||
| export type Status = { | ||
| readonly type: "missing"; | ||
| } | { | ||
| readonly type: "unreachable"; | ||
| } | { | ||
| readonly type: "unresponsive"; | ||
| } | (ServiceStatus.State & { | ||
| readonly version?: string; | ||
| }); | ||
| declare const FailedError_base: Schema.Class<FailedError, Schema.TaggedStruct<"ServiceFailedError", { | ||
| readonly message: Schema.String; | ||
| readonly action: Schema.String; | ||
| }>, import("effect/Cause").YieldableError>; | ||
| export declare class FailedError extends FailedError_base { | ||
| } | ||
| export declare const discover: (options?: Options | undefined) => Effect.Effect<{ | ||
@@ -28,12 +45,11 @@ url: string; | ||
| } | undefined, never, FileSystem.FileSystem>; | ||
| export declare const start: (options?: StartOptions | undefined) => Effect.Effect<{ | ||
| url: string; | ||
| auth: { | ||
| type: "basic"; | ||
| username: string; | ||
| password: string; | ||
| } | undefined; | ||
| }, Error, FileSystem.FileSystem>; | ||
| export declare const stop: (options?: Options | undefined) => Effect.Effect<void, Error, FileSystem.FileSystem>; | ||
| export declare function headers(endpoint: Endpoint): RequestInit["headers"]; | ||
| export declare const status: (options?: Options | undefined) => Effect.Effect<Status, never, FileSystem.FileSystem>; | ||
| export declare const start: (options?: StartOptions | undefined) => Effect.Effect<Endpoint, Error, FileSystem.FileSystem>; | ||
| export type StopMetadata = { | ||
| readonly targetVersion?: string; | ||
| }; | ||
| export declare const stop: (options?: Options | undefined, metadata?: StopMetadata | undefined) => Effect.Effect<void, Error, FileSystem.FileSystem>; | ||
| export declare function headers(endpoint: Endpoint): { | ||
| authorization: string; | ||
| } | undefined; | ||
| export declare const Info: Schema.Struct<{ | ||
@@ -40,0 +56,0 @@ readonly id: Schema.optional<Schema.String>; |
+182
-55
@@ -0,1 +1,2 @@ | ||
| import { ServiceStatus } from "@opencode-ai/protocol/groups/health"; | ||
| import { Effect, FileSystem, Option, Schedule, Schema } from "effect"; | ||
@@ -5,2 +6,7 @@ import { spawn } from "node:child_process"; | ||
| import { join } from "node:path"; | ||
| export class FailedError extends Schema.TaggedErrorClass()("ServiceFailedError", { | ||
| message: Schema.String, | ||
| action: Schema.String, | ||
| }) { | ||
| } | ||
| // Read-only lookup: registration file plus health check and version gate. | ||
@@ -11,45 +17,133 @@ // Never spawns; escalation to start() is the caller's policy. | ||
| }); | ||
| export const status = Effect.fn("service.status")(function* (options = {}) { | ||
| const result = yield* registered(options.file, true); | ||
| if (result.info === undefined) | ||
| return { type: "missing" }; | ||
| if (result.service === undefined) | ||
| return { type: "unreachable" }; | ||
| return publicStatus(result.service); | ||
| }); | ||
| function publicStatus(service) { | ||
| return { ...service.status, version: service.version }; | ||
| } | ||
| const discoverLocal = Effect.fnUntraced(function* (options) { | ||
| const info = yield* read(options.file); | ||
| if (info === undefined) | ||
| const found = (yield* registered(options.file)).service; | ||
| if (found?.status.type !== "ready") | ||
| return undefined; | ||
| if (options.version !== undefined && info.version !== options.version) | ||
| if (options.version !== undefined && found.version !== options.version) | ||
| return undefined; | ||
| return yield* probe(info, options.version); | ||
| return found; | ||
| }); | ||
| // Idempotent ensure-running: reuses a healthy compatible server, replaces a | ||
| // version-mismatched one, and otherwise spawns the service command detached. | ||
| // version-mismatched one, and otherwise spawns small contenders until a server | ||
| // becomes discoverable. A contender is never killed merely for slow startup. | ||
| export const start = Effect.fn("service.start")(function* (options = {}) { | ||
| const compatible = yield* discover(options); | ||
| if (compatible !== undefined) | ||
| return compatible; | ||
| const existing = yield* find(options); | ||
| if (existing?.version !== undefined && (options.version === undefined || existing.version === options.version)) | ||
| return existing.endpoint; | ||
| yield* Effect.sync(() => options.onStart?.(existing === undefined ? "missing" : "version-mismatch", existing?.info)); | ||
| if (existing !== undefined) | ||
| yield* kill(existing.info, options).pipe(Effect.ignore); | ||
| const [command, ...args] = options.command ?? ["opencode", "serve", "--service"]; | ||
| if (command === undefined) | ||
| return yield* Effect.fail(new Error("Missing service command")); | ||
| const child = yield* Effect.try({ | ||
| try: () => { | ||
| const child = spawn(command, args, { detached: true, stdio: "ignore" }); | ||
| child.unref(); | ||
| return child; | ||
| }, | ||
| catch: (cause) => new Error("Failed to start server", { cause }), | ||
| const contenders = new Set(); | ||
| let announced = false; | ||
| let reported; | ||
| let lastSpawn = 0; | ||
| let spawnDelay = 5_000; | ||
| let ownerHeld = false; | ||
| const announce = (reason, existing) => Effect.sync(() => { | ||
| if (announced) | ||
| return; | ||
| announced = true; | ||
| options.onStart?.(reason, existing); | ||
| }); | ||
| return yield* discoverLocal(options).pipe(Effect.flatMap((found) => found === undefined ? Effect.fail(new Error("Server is not ready")) : Effect.succeed(found)), Effect.retry(poll), Effect.tap((found) => found.info.pid === child.pid | ||
| ? Effect.void | ||
| : Effect.sync(() => { | ||
| child.kill("SIGTERM"); | ||
| })), Effect.map((found) => found.endpoint), Effect.tapError(() => Effect.try({ try: () => child.kill("SIGTERM"), catch: () => undefined }).pipe(Effect.ignore)), Effect.mapError(() => new Error("Failed to start server"))); | ||
| const spawnContender = Effect.gen(function* () { | ||
| const [command, ...args] = options.command ?? ["opencode", "serve", "--service"]; | ||
| if (command === undefined) | ||
| return yield* Effect.fail(new Error("Missing service command")); | ||
| return yield* Effect.try({ | ||
| try: () => { | ||
| const child = spawn(command, args, { detached: true, stdio: "ignore" }); | ||
| let error; | ||
| child.once("error", (cause) => { | ||
| error = new Error("Failed to start server", { cause }); | ||
| }); | ||
| child.unref(); | ||
| return { child, error: () => error }; | ||
| }, | ||
| catch: (cause) => new Error("Failed to start server", { cause }), | ||
| }); | ||
| }); | ||
| const found = yield* Effect.gen(function* () { | ||
| const registration = yield* registered(options.file, true); | ||
| const info = registration.info; | ||
| const service = registration.service; | ||
| const current = service === undefined ? { type: info === undefined ? "missing" : "unreachable" } : publicStatus(service); | ||
| const next = ownerHeld && service === undefined ? { type: "unresponsive" } : current; | ||
| yield* Effect.sync(() => { | ||
| if (sameStatus(reported, next)) | ||
| return; | ||
| reported = next; | ||
| options.onStatus?.(next); | ||
| }); | ||
| if (service !== undefined) { | ||
| ownerHeld = false; | ||
| spawnDelay = 5_000; | ||
| const compatible = !service.legacy && (options.version === undefined || service.version === options.version); | ||
| if (compatible && service.status.type === "ready") | ||
| return Option.some(service); | ||
| if (compatible && service.status.type === "failed") | ||
| return yield* new FailedError({ message: service.status.message, action: service.status.action }); | ||
| if (compatible || service.status.type === "stopping") | ||
| return Option.none(); | ||
| yield* announce("version-mismatch", service.info); | ||
| yield* kill(service, options, options.version).pipe(Effect.ignore); | ||
| lastSpawn = 0; | ||
| return Option.none(); | ||
| } | ||
| else if (lastSpawn === 0 && info !== undefined) | ||
| lastSpawn = Date.now(); | ||
| const failure = [...contenders].map(contenderFailure).find((error) => error !== undefined); | ||
| if (failure !== undefined) | ||
| return yield* Effect.fail(failure); | ||
| const finished = [...contenders].filter(contenderFinished); | ||
| if (finished.some((item) => item.child.exitCode === 0)) { | ||
| ownerHeld = true; | ||
| spawnDelay = Math.min(spawnDelay * 2, 30_000); | ||
| } | ||
| finished.forEach((item) => contenders.delete(item)); | ||
| // Keep one candidate plus one lock probe so a pre-lock stall cannot block recovery. | ||
| if (contenders.size < 2 && Date.now() - lastSpawn >= spawnDelay) { | ||
| yield* announce("missing"); | ||
| contenders.add(yield* spawnContender); | ||
| lastSpawn = Date.now(); | ||
| } | ||
| return Option.none(); | ||
| }).pipe(Effect.repeat({ until: Option.isSome, schedule: Schedule.spaced("1 second") })); | ||
| return Option.getOrThrow(found).endpoint; | ||
| }); | ||
| export const stop = Effect.fn("service.stop")(function* (options = {}) { | ||
| const fs = yield* FileSystem.FileSystem; | ||
| function sameStatus(left, right) { | ||
| if (left?.type !== right.type) | ||
| return false; | ||
| if (right.type === "failed") | ||
| return (left.type === "failed" && | ||
| left.version === right.version && | ||
| left.message === right.message && | ||
| left.action === right.action); | ||
| if (right.type === "stopping") | ||
| return left.type === "stopping" && left.version === right.version && left.targetVersion === right.targetVersion; | ||
| if (right.type === "starting" || right.type === "ready") | ||
| return left.type === right.type && left.version === right.version; | ||
| return true; | ||
| } | ||
| function contenderFailure(contender) { | ||
| const error = contender.error(); | ||
| if (error !== undefined) | ||
| return error; | ||
| if (contender.child.exitCode !== null && contender.child.exitCode !== 0) | ||
| return new Error(`Server process exited with code ${contender.child.exitCode}`); | ||
| if (contender.child.signalCode !== null) | ||
| return new Error(`Server process terminated by ${contender.child.signalCode}`); | ||
| return undefined; | ||
| } | ||
| function contenderFinished(contender) { | ||
| return contender.error() !== undefined || contender.child.exitCode !== null || contender.child.signalCode !== null; | ||
| } | ||
| export const stop = Effect.fn("service.stop")(function* (options = {}, metadata = {}) { | ||
| const existing = yield* find(options); | ||
| if (existing !== undefined) | ||
| yield* kill(existing.info, options); | ||
| yield* fs.remove(options.file ?? fallback()).pipe(Effect.ignore); | ||
| yield* kill(existing, options, metadata.targetVersion); | ||
| }); | ||
@@ -73,3 +167,3 @@ function fallback() { | ||
| const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Info)); | ||
| const decodeHealth = Schema.decodeUnknownOption(Schema.Struct({ healthy: Schema.Literal(true), version: Schema.String, pid: Schema.Int })); | ||
| const decodeHealth = Schema.decodeUnknownOption(ServiceStatus.Health); | ||
| const decodeLegacyHealth = Schema.decodeUnknownOption(Schema.Struct({ healthy: Schema.Literal(true) })); | ||
@@ -85,3 +179,3 @@ // A missing or corrupt file means no valid info; callers treat both | ||
| }); | ||
| const probe = Effect.fnUntraced(function* (info, version, allowLegacy = false) { | ||
| const probe = Effect.fnUntraced(function* (info, allowLegacy = false) { | ||
| const endpoint = { | ||
@@ -97,3 +191,3 @@ url: info.url, | ||
| })).pipe(Effect.option, Effect.map(Option.getOrUndefined)); | ||
| if (response === undefined || !response.ok) | ||
| if (response === undefined) | ||
| return undefined; | ||
@@ -107,5 +201,11 @@ const body = yield* Effect.tryPromise(() => response.json()).pipe(Effect.option, Effect.map(Option.getOrUndefined)); | ||
| return undefined; | ||
| if (version !== undefined && health.value.version !== version) | ||
| if (info.id !== undefined && health.value.instanceID !== undefined && health.value.instanceID !== info.id) | ||
| return undefined; | ||
| return { info, endpoint, version: health.value.version }; | ||
| return { | ||
| info, | ||
| endpoint, | ||
| version: health.value.version, | ||
| status: health.value.status, | ||
| legacy: false, | ||
| }; | ||
| } | ||
@@ -116,13 +216,17 @@ if (!allowLegacy || | ||
| return undefined; | ||
| return { info, endpoint }; | ||
| return { info, endpoint, status: { type: "ready" }, legacy: true }; | ||
| }); | ||
| // Health-checked lookup without the version gate: lifecycle operations must be | ||
| const registered = Effect.fnUntraced(function* (file, allowLegacy = false) { | ||
| const info = yield* read(file); | ||
| if (info === undefined) | ||
| return { info: undefined, service: undefined }; | ||
| return { info, service: yield* probe(info, allowLegacy) }; | ||
| }); | ||
| // Health-checked lookup without the version gate: status operations must be | ||
| // able to see (and replace or stop) a server from a different version. | ||
| const find = Effect.fnUntraced(function* (options) { | ||
| const info = yield* read(options.file); | ||
| if (info === undefined) | ||
| return undefined; | ||
| return yield* probe(info, undefined, true); | ||
| return (yield* registered(options.file, true)).service; | ||
| }); | ||
| // 50ms cadence bounded at ~5s, shared by stop escalation and start readiness. | ||
| // 50ms cadence bounded at ~5s, shared by stop escalation and each start | ||
| // discovery window. | ||
| const poll = Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(100))); | ||
@@ -139,18 +243,41 @@ const signal = (pid, name) => Effect.try({ try: () => process.kill(pid, name), catch: (cause) => cause }).pipe(Effect.ignore); | ||
| } | ||
| const kill = Effect.fnUntraced(function* (info, options) { | ||
| // A stale registration may point at a PID that has since been reused by | ||
| // another process. Only signal the PID after authenticating the server. | ||
| const current = yield* find(options); | ||
| if (current === undefined || !same(current.info, info)) | ||
| const kill = Effect.fnUntraced(function* (service, options, targetVersion) { | ||
| const requested = yield* requestStop(service, targetVersion); | ||
| if (requested === "rejected") | ||
| return; | ||
| yield* signal(info.pid, "SIGTERM"); | ||
| const done = yield* stopped(info.pid).pipe(Effect.retry(poll), Effect.option); | ||
| if (requested === "unsupported") { | ||
| // A stale registration may point at a reused PID. Authenticate again | ||
| // immediately before the legacy signal fallback. | ||
| const current = yield* find(options); | ||
| if (current === undefined || !same(current.info, service.info)) | ||
| return; | ||
| yield* signal(service.info.pid, "SIGTERM"); | ||
| } | ||
| const done = yield* stopped(service.info.pid).pipe(Effect.retry(poll), Effect.option); | ||
| if (Option.isSome(done)) | ||
| return; | ||
| const latest = yield* find(options); | ||
| if (latest === undefined || !same(latest.info, info)) | ||
| if (latest === undefined || !same(latest.info, service.info)) | ||
| return; | ||
| yield* signal(info.pid, "SIGKILL"); | ||
| yield* stopped(info.pid).pipe(Effect.retry(poll)); | ||
| yield* signal(service.info.pid, "SIGKILL"); | ||
| yield* stopped(service.info.pid).pipe(Effect.retry(poll)); | ||
| }); | ||
| const decodeStopResponse = Schema.decodeUnknownOption(ServiceStatus.StopResponse); | ||
| const requestStop = Effect.fnUntraced(function* (service, targetVersion) { | ||
| if (service.info.id === undefined || service.legacy) | ||
| return "unsupported"; | ||
| const response = yield* Effect.tryPromise(() => fetch(new URL("/api/service/stop", service.info.url), { | ||
| method: "POST", | ||
| headers: { ...headers(service.endpoint), "content-type": "application/json" }, | ||
| body: JSON.stringify({ instanceID: service.info.id, targetVersion }), | ||
| signal: AbortSignal.timeout(2_000), | ||
| })).pipe(Effect.option, Effect.map(Option.getOrUndefined)); | ||
| if (response === undefined || response.status === 404 || response.status === 405) | ||
| return "unsupported"; | ||
| const body = yield* Effect.tryPromise(() => response.json()).pipe(Effect.option, Effect.map(Option.getOrUndefined)); | ||
| const decoded = decodeStopResponse(body); | ||
| if (!response.ok || Option.isNone(decoded) || !decoded.value.accepted) | ||
| return "rejected"; | ||
| return "accepted"; | ||
| }); | ||
| export * as Service from "./service.js"; |
@@ -1,2 +0,2 @@ | ||
| import type { HealthGetOutput, ServerGetOutput, LocationGetInput, LocationGetOutput, AgentListInput, AgentListOutput, PluginListInput, PluginListOutput, SessionListInput, SessionCreateInput, SessionGetInput, SessionRemoveInput, SessionForkInput, SessionSwitchAgentInput, SessionSwitchModelInput, SessionRenameInput, SessionMoveInput, SessionPromptInput, SessionCommandInput, SessionSkillInput, SessionSyntheticInput, SessionShellInput, SessionCompactInput, SessionWaitInput, SessionRevertStageInput, SessionRevertClearInput, SessionRevertCommitInput, SessionContextInput, SessionPendingListInput, SessionInstructionsEntryListInput, SessionInstructionsEntryPutInput, SessionInstructionsEntryRemoveInput, SessionLogInput, SessionLogOutput, SessionInterruptInput, SessionBackgroundInput, SessionMessageInput, MessageListInput, ModelListInput, ModelListOutput, ModelDefaultInput, ModelDefaultOutput, GenerateTextInput, ProviderListInput, ProviderListOutput, ProviderGetInput, ProviderGetOutput, IntegrationListInput, IntegrationListOutput, IntegrationGetInput, IntegrationGetOutput, IntegrationConnectKeyInput, IntegrationConnectOauthInput, IntegrationConnectOauthOutput, IntegrationAttemptStatusInput, IntegrationAttemptStatusOutput, IntegrationAttemptCompleteInput, IntegrationAttemptCancelInput, McpListInput, McpListOutput, McpResourceCatalogInput, McpResourceCatalogOutput, CredentialUpdateInput, CredentialRemoveInput, ProjectListOutput, ProjectCurrentInput, ProjectDirectoriesInput, 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, ShellListInput, ShellListOutput, ShellCreateInput, ShellCreateOutput, ShellGetInput, ShellGetOutput, ShellTimeoutInput, ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, QuestionRequestListInput, QuestionRequestListOutput, QuestionListInput, QuestionReplyInput, QuestionRejectInput, ReferenceListInput, ReferenceListOutput, ProjectCopyCreateInput, ProjectCopyRemoveInput, ProjectCopyRefreshInput, VcsStatusInput, VcsStatusOutput, VcsDiffInput, VcsDiffOutput, DebugLocationListOutput, DebugLocationEvictInput } from "./types"; | ||
| import type { HealthStopInput, ServerGetOutput, LocationGetInput, LocationGetOutput, AgentListInput, AgentListOutput, PluginListInput, PluginListOutput, SessionListInput, SessionCreateInput, SessionGetInput, SessionRemoveInput, SessionForkInput, SessionSwitchAgentInput, SessionSwitchModelInput, SessionRenameInput, SessionMoveInput, SessionPromptInput, SessionCommandInput, SessionSkillInput, SessionSyntheticInput, SessionShellInput, SessionCompactInput, SessionWaitInput, SessionRevertStageInput, SessionRevertClearInput, SessionRevertCommitInput, SessionContextInput, SessionPendingListInput, SessionInstructionsEntryListInput, SessionInstructionsEntryPutInput, SessionInstructionsEntryRemoveInput, SessionLogInput, SessionLogOutput, SessionInterruptInput, SessionBackgroundInput, SessionMessageInput, MessageListInput, ModelListInput, ModelListOutput, ModelDefaultInput, ModelDefaultOutput, GenerateTextInput, ProviderListInput, ProviderListOutput, ProviderGetInput, ProviderGetOutput, IntegrationListInput, IntegrationListOutput, IntegrationGetInput, IntegrationGetOutput, IntegrationConnectKeyInput, IntegrationConnectOauthInput, IntegrationConnectOauthOutput, IntegrationAttemptStatusInput, IntegrationAttemptStatusOutput, IntegrationAttemptCompleteInput, IntegrationAttemptCancelInput, McpListInput, McpListOutput, McpResourceCatalogInput, McpResourceCatalogOutput, CredentialUpdateInput, CredentialRemoveInput, ProjectListOutput, ProjectCurrentInput, ProjectDirectoriesInput, 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, ShellListInput, ShellListOutput, ShellCreateInput, ShellCreateOutput, ShellGetInput, ShellGetOutput, ShellTimeoutInput, ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, QuestionRequestListInput, QuestionRequestListOutput, QuestionListInput, QuestionReplyInput, QuestionRejectInput, ReferenceListInput, ReferenceListOutput, ProjectCopyCreateInput, ProjectCopyRemoveInput, ProjectCopyRefreshInput, VcsStatusInput, VcsStatusOutput, VcsDiffInput, VcsDiffOutput, DebugLocationListOutput, DebugLocationEvictInput } from "./types"; | ||
| export interface ClientOptions { | ||
@@ -13,3 +13,4 @@ readonly baseUrl: string; | ||
| health: { | ||
| get: (requestOptions?: RequestOptions) => Promise<HealthGetOutput>; | ||
| get: (requestOptions?: RequestOptions) => Promise<import("./types").ServiceHealth>; | ||
| stop: (input: HealthStopInput, requestOptions?: RequestOptions) => Promise<import("./types").ServiceStopResponse>; | ||
| }; | ||
@@ -16,0 +17,0 @@ server: { |
@@ -132,2 +132,10 @@ import { ClientError } from "./client-error"; | ||
| get: (requestOptions) => request({ method: "GET", path: `/api/health`, successStatus: 200, declaredStatuses: [401, 400], empty: false }, requestOptions), | ||
| stop: (input, requestOptions) => request({ | ||
| method: "POST", | ||
| path: `/api/service/stop`, | ||
| body: { instanceID: input["instanceID"], targetVersion: input["targetVersion"] }, | ||
| successStatus: 200, | ||
| declaredStatuses: [401, 400], | ||
| empty: false, | ||
| }, requestOptions), | ||
| }, | ||
@@ -134,0 +142,0 @@ server: { |
+4
-4
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "name": "@opencode-ai/client", | ||
| "version": "0.0.0-next-15541", | ||
| "version": "0.0.0-next-15555", | ||
| "type": "module", | ||
@@ -48,4 +48,4 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@opencode-ai/schema": "0.0.0-next-15541", | ||
| "@opencode-ai/protocol": "0.0.0-next-15541" | ||
| "@opencode-ai/schema": "0.0.0-next-15555", | ||
| "@opencode-ai/protocol": "0.0.0-next-15555" | ||
| }, | ||
@@ -62,3 +62,3 @@ "peerDependencies": { | ||
| "@effect/platform-node": "4.0.0-beta.83", | ||
| "@opencode-ai/httpapi-codegen": "0.0.0-next-15541", | ||
| "@opencode-ai/httpapi-codegen": "0.0.0-next-15555", | ||
| "@tsconfig/bun": "1.0.9", | ||
@@ -65,0 +65,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
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.
727709
1.28%16535
1.29%2
-33.33%9
12.5%+ Added
+ Added
- Removed
- Removed