@opencode-ai/client 0.0.0-dev-17603 → 0.0.0-dev-17635

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,16 +42,8 @@ export type Endpoint0_0Output = {
42
42
  readonly pid: number;
43
43
  };
44
44
  export type HealthGetOperation<E = never> = () => Effect.Effect<Endpoint0_0Output, E>;
45
- export type Endpoint0_1Input = {
46
- readonly instanceID: string;
47
- };
48
- export type Endpoint0_1Output = {
49
- readonly accepted: boolean;
50
- };
51
- export type HealthStopOperation<E = never> = (input: Endpoint0_1Input) => Effect.Effect<Endpoint0_1Output, E>;
52
45
  export interface HealthApi<E = never> {
53
46
  readonly get: HealthGetOperation<E>;
54
- readonly stop: HealthStopOperation<E>;
55
47
  }
56
48
  export type Endpoint1_0Output = {
57
49
  readonly urls: ReadonlyArray<string>;
@@ -1,14 +1,13 @@
1
1
  import { Effect, Stream, Schema } from "effect";
2
2
  import { Sse } from "effect/unstable/encoding";
3
3
  import { HttpClientError } from "effect/unstable/http";
4
- import type { Endpoint0_0Output, Endpoint0_1Input, Endpoint0_1Output, Endpoint1_0Output, Endpoint2_0Input, Endpoint3_0Input, Endpoint3_0Output, Endpoint3_1Input, Endpoint3_1Output, Endpoint4_0Input, Endpoint4_0Output, Endpoint5_0Input, Endpoint5_0Output, Endpoint5_1Input, Endpoint5_2Input, Endpoint5_3Input, Endpoint5_3Output, Endpoint5_4Output, Endpoint5_5Input, Endpoint5_6Input, Endpoint5_7Input, Endpoint5_8Input, Endpoint5_9Input, Endpoint5_10Input, Endpoint5_11Input, Endpoint5_12Input, Endpoint5_13Input, Endpoint5_14Input, Endpoint5_15Input, Endpoint5_16Input, Endpoint5_17Input, Endpoint5_18Input, Endpoint5_19Input, Endpoint5_20Input, Endpoint5_21Input, Endpoint5_22Input, Endpoint5_22Output, Endpoint5_23Input, Endpoint5_23Output, Endpoint5_24Input, Endpoint5_25Input, Endpoint5_26Input, Endpoint5_27Input, Endpoint5_27Output, Endpoint5_28Input, Endpoint5_29Input, Endpoint5_30Input, Endpoint5_30Output, Endpoint5_31Input, Endpoint5_31Output, Endpoint5_32Input, Endpoint5_33Input, Endpoint5_34Input, Endpoint5_35Input, Endpoint6_0Input, Endpoint6_0Output, Endpoint7_0Input, Endpoint7_0Output, Endpoint7_1Input, Endpoint7_1Output, Endpoint8_0Input, Endpoint8_0Output, Endpoint9_0Input, Endpoint9_0Output, Endpoint9_1Input, Endpoint9_1Output, Endpoint10_0Input, Endpoint10_0Output, Endpoint10_1Input, Endpoint10_1Output, Endpoint10_2Input, Endpoint10_3Input, Endpoint10_4Input, Endpoint10_4Output, Endpoint10_5Input, Endpoint10_5Output, Endpoint10_6Input, Endpoint10_7Input, Endpoint10_8Input, Endpoint10_8Output, Endpoint10_9Input, Endpoint10_9Output, Endpoint10_10Input, Endpoint11_0Input, Endpoint11_0Output, Endpoint11_1Input, Endpoint11_2Input, Endpoint11_3Input, Endpoint11_4Input, Endpoint11_5Input, Endpoint11_5Output, Endpoint12_0Input, Endpoint12_1Input, Endpoint13_0Output, Endpoint13_1Input, Endpoint14_0Input, Endpoint14_0Output, Endpoint14_1Input, Endpoint14_1Output, Endpoint14_2Input, Endpoint14_3Input, Endpoint14_4Input, Endpoint14_5Input, Endpoint14_6Input, Endpoint15_0Input, Endpoint15_0Output, Endpoint15_1Input, Endpoint15_1Output, Endpoint15_2Input, Endpoint15_3Input, Endpoint15_3Output, Endpoint15_4Input, Endpoint15_4Output, Endpoint15_5Input, Endpoint15_6Input, Endpoint16_0Input, Endpoint16_0Output, Endpoint16_1Input, Endpoint16_1Output, Endpoint17_0Input, Endpoint17_0Output, Endpoint18_0Input, Endpoint18_0Output, Endpoint20_0Input, Endpoint20_0Output, Endpoint20_1Input, Endpoint20_1Output, Endpoint20_2Input, Endpoint20_2Output, Endpoint20_3Input, Endpoint20_3Output, Endpoint20_4Input, Endpoint21_0Input, Endpoint21_0Output, Endpoint21_1Input, Endpoint21_1Output, Endpoint21_2Input, Endpoint21_2Output, Endpoint21_3Input, Endpoint21_3Output, Endpoint21_4Input, Endpoint21_4Output, Endpoint21_5Input, Endpoint22_0Input, Endpoint22_0Output, Endpoint23_0Input, Endpoint23_1Input, Endpoint23_2Input, Endpoint23_3Input, Endpoint24_0Input, Endpoint24_0Output, Endpoint24_1Input, Endpoint24_1Output, Endpoint24_2Input, Endpoint24_2Output, Endpoint25_0Output, Endpoint25_1Input, Endpoint26_0Output, Endpoint27_0Input, Endpoint27_0Output, Endpoint27_1Input, Endpoint27_1Output, Endpoint28_0Input, Endpoint28_0Output } from "../api/api.js";
4
+ import type { Endpoint0_0Output, Endpoint1_0Output, Endpoint2_0Input, Endpoint3_0Input, Endpoint3_0Output, Endpoint3_1Input, Endpoint3_1Output, Endpoint4_0Input, Endpoint4_0Output, Endpoint5_0Input, Endpoint5_0Output, Endpoint5_1Input, Endpoint5_2Input, Endpoint5_3Input, Endpoint5_3Output, Endpoint5_4Output, Endpoint5_5Input, Endpoint5_6Input, Endpoint5_7Input, Endpoint5_8Input, Endpoint5_9Input, Endpoint5_10Input, Endpoint5_11Input, Endpoint5_12Input, Endpoint5_13Input, Endpoint5_14Input, Endpoint5_15Input, Endpoint5_16Input, Endpoint5_17Input, Endpoint5_18Input, Endpoint5_19Input, Endpoint5_20Input, Endpoint5_21Input, Endpoint5_22Input, Endpoint5_22Output, Endpoint5_23Input, Endpoint5_23Output, Endpoint5_24Input, Endpoint5_25Input, Endpoint5_26Input, Endpoint5_27Input, Endpoint5_27Output, Endpoint5_28Input, Endpoint5_29Input, Endpoint5_30Input, Endpoint5_30Output, Endpoint5_31Input, Endpoint5_31Output, Endpoint5_32Input, Endpoint5_33Input, Endpoint5_34Input, Endpoint5_35Input, Endpoint6_0Input, Endpoint6_0Output, Endpoint7_0Input, Endpoint7_0Output, Endpoint7_1Input, Endpoint7_1Output, Endpoint8_0Input, Endpoint8_0Output, Endpoint9_0Input, Endpoint9_0Output, Endpoint9_1Input, Endpoint9_1Output, Endpoint10_0Input, Endpoint10_0Output, Endpoint10_1Input, Endpoint10_1Output, Endpoint10_2Input, Endpoint10_3Input, Endpoint10_4Input, Endpoint10_4Output, Endpoint10_5Input, Endpoint10_5Output, Endpoint10_6Input, Endpoint10_7Input, Endpoint10_8Input, Endpoint10_8Output, Endpoint10_9Input, Endpoint10_9Output, Endpoint10_10Input, Endpoint11_0Input, Endpoint11_0Output, Endpoint11_1Input, Endpoint11_2Input, Endpoint11_3Input, Endpoint11_4Input, Endpoint11_5Input, Endpoint11_5Output, Endpoint12_0Input, Endpoint12_1Input, Endpoint13_0Output, Endpoint13_1Input, Endpoint14_0Input, Endpoint14_0Output, Endpoint14_1Input, Endpoint14_1Output, Endpoint14_2Input, Endpoint14_3Input, Endpoint14_4Input, Endpoint14_5Input, Endpoint14_6Input, Endpoint15_0Input, Endpoint15_0Output, Endpoint15_1Input, Endpoint15_1Output, Endpoint15_2Input, Endpoint15_3Input, Endpoint15_3Output, Endpoint15_4Input, Endpoint15_4Output, Endpoint15_5Input, Endpoint15_6Input, Endpoint16_0Input, Endpoint16_0Output, Endpoint16_1Input, Endpoint16_1Output, Endpoint17_0Input, Endpoint17_0Output, Endpoint18_0Input, Endpoint18_0Output, Endpoint20_0Input, Endpoint20_0Output, Endpoint20_1Input, Endpoint20_1Output, Endpoint20_2Input, Endpoint20_2Output, Endpoint20_3Input, Endpoint20_3Output, Endpoint20_4Input, Endpoint21_0Input, Endpoint21_0Output, Endpoint21_1Input, Endpoint21_1Output, Endpoint21_2Input, Endpoint21_2Output, Endpoint21_3Input, Endpoint21_3Output, Endpoint21_4Input, Endpoint21_4Output, Endpoint21_5Input, Endpoint22_0Input, Endpoint22_0Output, Endpoint23_0Input, Endpoint23_1Input, Endpoint23_2Input, Endpoint23_3Input, Endpoint24_0Input, Endpoint24_0Output, Endpoint24_1Input, Endpoint24_1Output, Endpoint24_2Input, Endpoint24_2Output, Endpoint25_0Output, Endpoint25_1Input, Endpoint26_0Output, Endpoint27_0Input, Endpoint27_0Output, Endpoint27_1Input, Endpoint27_1Output, Endpoint28_0Input, Endpoint28_0Output } from "../api/api.js";
5
5
  import { ClientError } from "./client-error.js";
6
6
  export declare const make: (options?: {
7
7
  readonly baseUrl?: URL | string;
8
8
  }) => Effect.Effect<{
9
9
  health: {
10
10
  get: () => Effect.Effect<Endpoint0_0Output, import("@opencode-ai/protocol/errors").InvalidRequestError | import("@opencode-ai/protocol/errors").UnauthorizedError | Schema.SchemaError | ClientError | HttpClientError.HttpClientError, never>;
11
- stop: (input: Endpoint0_1Input) => Effect.Effect<Endpoint0_1Output, import("@opencode-ai/protocol/errors").InvalidRequestError | import("@opencode-ai/protocol/errors").UnauthorizedError | Schema.SchemaError | ClientError | HttpClientError.HttpClientError, never>;
12
11
  };
13
12
  server: {
14
13
  get: () => Effect.Effect<Endpoint1_0Output, import("@opencode-ai/protocol/errors").InvalidRequestError | import("@opencode-ai/protocol/errors").UnauthorizedError | Schema.SchemaError | ClientError | HttpClientError.HttpClientError, never>;
@@ -11,8 +11,7 @@ const mapClientError = (error) => HttpClientError.isHttpClientError(error) || Sc
11
11
  const preserveEffect = () => (effect) => effect;
12
12
  const preserveStream = () => (stream) => stream;
13
13
  const Endpoint0_0 = (raw) => () => preserveEffect()(raw["health.get"]({}).pipe(Effect.mapError(mapClientError)));
14
- const Endpoint0_1 = (raw) => (input) => preserveEffect()(raw["health.stop"]({ payload: { instanceID: input["instanceID"] } }).pipe(Effect.mapError(mapClientError)));
15
- const adaptGroup0 = (raw) => ({ get: Endpoint0_0(raw), stop: Endpoint0_1(raw) });
14
+ const adaptGroup0 = (raw) => ({ get: Endpoint0_0(raw) });
16
15
  const Endpoint1_0 = (raw) => () => preserveEffect()(raw["server.get"]({}).pipe(Effect.mapError(mapClientError)));
17
16
  const adaptGroup1 = (raw) => ({ get: Endpoint1_0(raw) });
18
17
  const Endpoint2_0 = (raw) => (input) => preserveEffect()(raw["location.get"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)));
@@ -60,7 +60,7 @@ export const ensure = Effect.fn("service.ensure")(function* (options = {}) {
60
60
  return yield* Effect.fail(new Error("Missing service command"));
61
61
  return yield* Effect.try({
62
62
  try: () => {
63
- return spawnServiceContender(command, args);
63
+ return spawnServiceContender(command, args, options.env);
64
64
  },
65
65
  catch: (cause) => new Error("Failed to start server", { cause }),
66
66
  });
@@ -76,7 +76,7 @@ export const ensure = Effect.fn("service.ensure")(function* (options = {}) {
76
76
  };
77
77
  if (timeouts.count >= 3) {
78
78
  yield* announce("missing");
79
- yield* evict(info, options, timing);
79
+ yield* terminate(info, options, timing);
80
80
  timeouts = undefined;
81
81
  lastSpawn = Date.now() - spawnDelay;
82
82
  }
@@ -93,7 +93,7 @@ export const ensure = Effect.fn("service.ensure")(function* (options = {}) {
93
93
  if (compatible)
94
94
  return Option.none();
95
95
  yield* announce("version-mismatch", service.version);
96
- yield* kill(service, options, timing).pipe(Effect.ignore);
96
+ yield* terminate(service.info, options, timing).pipe(Effect.ignore);
97
97
  lastSpawn = 0;
98
98
  return Option.none();
99
99
  }
@@ -124,9 +124,9 @@ export const ensure = Effect.fn("service.ensure")(function* (options = {}) {
124
124
  });
125
125
  /** Stop the registered local service. */
126
126
  export const stop = Effect.fn("service.stop")(function* (options = {}) {
127
- const existing = yield* find(options);
128
- if (existing !== undefined)
129
- yield* kill(existing, options, defaultEnsureTiming);
127
+ const info = yield* read(options.file);
128
+ if (info !== undefined)
129
+ yield* terminate(info, options, defaultEnsureTiming);
130
130
  });
131
131
  function fallback() {
132
132
  const state = process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state");
@@ -211,11 +211,6 @@ const registered = Effect.fnUntraced(function* (file, allowLegacy = false, timeo
211
211
  return { info: undefined, service: undefined, timedOut: false };
212
212
  return { info, ...(yield* probeResult(info, allowLegacy, timeout)) };
213
213
  });
214
- // Health-checked lookup without the version gate: lifecycle operations must be
215
- // able to see (and replace or stop) a server from a different version.
216
- const find = Effect.fnUntraced(function* (options) {
217
- return (yield* registered(options.file, true)).service;
218
- });
219
214
  // 50ms cadence bounded at ~5s, shared by stop escalation and each ensure
220
215
  // discovery window.
221
216
  const poll = (timing) => Schedule.max([Schedule.spaced(timing.stopPollInterval), Schedule.recurs(timing.stopPollAttempts)]);
@@ -229,58 +224,24 @@ const stopped = Effect.fnUntraced(function* (pid) {
229
224
  function same(left, right) {
230
225
  return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid;
231
226
  }
232
- const evict = Effect.fnUntraced(function* (info, options, timing) {
227
+ const terminate = Effect.fnUntraced(function* (info, options, timing) {
233
228
  const current = yield* read(options.file);
234
229
  if (current === undefined || !same(current, info))
235
230
  return;
236
231
  yield* signal(info.pid, "SIGTERM");
237
232
  const done = yield* stopped(info.pid).pipe(Effect.retry(poll(timing)), Effect.option);
238
- if (Option.isSome(done))
239
- return;
240
- const latest = yield* read(options.file);
241
- if (latest === undefined || !same(latest, info))
242
- return;
243
- yield* signal(info.pid, "SIGKILL");
244
- yield* stopped(info.pid).pipe(Effect.retry(poll(timing)));
245
- });
246
- const kill = Effect.fnUntraced(function* (service, options, timing) {
247
- const requested = yield* requestStop(service, timing.requestTimeout);
248
- if (requested === "rejected")
249
- return;
250
- if (requested === "unsupported") {
251
- // A stale registration may point at a reused PID. Authenticate again
252
- // immediately before the legacy signal fallback.
253
- const current = yield* find(options);
254
- if (current === undefined || !same(current.info, service.info))
233
+ if (Option.isNone(done)) {
234
+ const latest = yield* read(options.file);
235
+ if (latest === undefined || !same(latest, info))
255
236
  return;
256
- yield* signal(service.info.pid, "SIGTERM");
237
+ yield* signal(info.pid, "SIGKILL");
238
+ yield* stopped(info.pid).pipe(Effect.retry(poll(timing)));
257
239
  }
258
- const done = yield* stopped(service.info.pid).pipe(Effect.retry(poll(timing)), Effect.option);
259
- if (Option.isSome(done))
260
- return;
261
- const latest = yield* find(options);
262
- if (latest === undefined || !same(latest.info, service.info))
240
+ const latest = yield* read(options.file);
241
+ if (latest === undefined || !same(latest, info))
263
242
  return;
264
- yield* signal(service.info.pid, "SIGKILL");
265
- yield* stopped(service.info.pid).pipe(Effect.retry(poll(timing)));
266
- });
267
- const decodeStopResponse = Schema.decodeUnknownOption(ServiceStatus.StopResponse);
268
- const requestStop = Effect.fnUntraced(function* (service, timeout = defaultEnsureTiming.requestTimeout) {
269
- if (service.info.id === undefined || service.legacy)
270
- return "unsupported";
271
- const response = yield* Effect.tryPromise(() => fetch(new URL("/api/service/stop", service.info.url), {
272
- method: "POST",
273
- headers: { ...headers(service.endpoint), "content-type": "application/json" },
274
- body: JSON.stringify({ instanceID: service.info.id }),
275
- signal: AbortSignal.timeout(timeout),
276
- })).pipe(Effect.option, Effect.map(Option.getOrUndefined));
277
- if (response === undefined || response.status === 404 || response.status === 405)
278
- return "unsupported";
279
- const body = yield* Effect.tryPromise(() => response.json()).pipe(Effect.option, Effect.map(Option.getOrUndefined));
280
- const decoded = decodeStopResponse(body);
281
- if (!response.ok || Option.isNone(decoded) || !decoded.value.accepted)
282
- return "rejected";
283
- return "accepted";
243
+ const fs = yield* FileSystem.FileSystem;
244
+ yield* fs.remove(options.file ?? fallback()).pipe(Effect.ignore);
284
245
  });
285
246
  /** Effect-based local service lifecycle operations. */
286
247
  export const Service = { discover, incumbent, ensure, stop, headers, Info };
@@ -1,4 +1,4 @@
1
- import type { HealthStopInput, ServerGetOutput, LocationGetInput, LocationGetOutput, AgentListInput, AgentListOutput, AgentGetInput, AgentGetOutput, PluginListInput, PluginListOutput, SessionListInput, 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, SessionEnvironmentInput, 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, CredentialRemoveInput, ProjectListOutput, 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, ShellListInput, ShellListOutput, ShellCreateInput, ShellCreateOutput, ShellGetInput, ShellGetOutput, ShellTimeoutInput, ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, ReferenceListInput, ReferenceListOutput, WorktreeListInput, WorktreeCreateInput, WorktreeRemoveInput, WorktreeRefreshInput, VcsGetInput, VcsGetOutput, VcsStatusInput, VcsStatusOutput, VcsDiffInput, VcsDiffOutput, DebugLocationListOutput, DebugLocationEvictInput, MigrationV1StatusOutput, WebsearchProvidersInput, WebsearchProvidersOutput, WebsearchQueryInput, WebsearchQueryOutput, ConfigGetInput, ConfigGetOutput } from "./types.js";
1
+ import type { ServerGetOutput, LocationGetInput, LocationGetOutput, AgentListInput, AgentListOutput, AgentGetInput, AgentGetOutput, PluginListInput, PluginListOutput, SessionListInput, 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, SessionEnvironmentInput, 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, CredentialRemoveInput, ProjectListOutput, 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, ShellListInput, ShellListOutput, ShellCreateInput, ShellCreateOutput, ShellGetInput, ShellGetOutput, ShellTimeoutInput, ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, ReferenceListInput, ReferenceListOutput, WorktreeListInput, WorktreeCreateInput, WorktreeRemoveInput, WorktreeRefreshInput, VcsGetInput, VcsGetOutput, VcsStatusInput, VcsStatusOutput, VcsDiffInput, VcsDiffOutput, DebugLocationListOutput, DebugLocationEvictInput, MigrationV1StatusOutput, WebsearchProvidersInput, WebsearchProvidersOutput, WebsearchQueryInput, WebsearchQueryOutput, ConfigGetInput, ConfigGetOutput } from "./types.js";
2
2
  export interface ClientOptions {
3
3
  readonly baseUrl: string;
4
4
  readonly fetch?: typeof globalThis.fetch;
@@ -11,7 +11,6 @@ export interface RequestOptions {
11
11
  export declare function make(options: ClientOptions): {
12
12
  health: {
13
13
  get: (requestOptions?: RequestOptions) => Promise<import("./types.js").ServiceHealth>;
14
- stop: (input: HealthStopInput, requestOptions?: RequestOptions) => Promise<import("./types.js").ServiceStopResponse>;
15
14
  };
16
15
  server: {
17
16
  get: (requestOptions?: RequestOptions) => Promise<ServerGetOutput>;
@@ -129,14 +129,6 @@ export function make(options) {
129
129
  return {
130
130
  health: {
131
131
  get: (requestOptions) => request({ method: "GET", path: `/api/health`, successStatus: 200, declaredStatuses: [401, 400], empty: false }, requestOptions),
132
- stop: (input, requestOptions) => request({
133
- method: "POST",
134
- path: `/api/service/stop`,
135
- body: { instanceID: input["instanceID"] },
136
- successStatus: 200,
137
- declaredStatuses: [401, 400],
138
- empty: false,
139
- }, requestOptions),
140
132
  },
141
133
  server: {
142
134
  get: (requestOptions) => request({ method: "GET", path: `/api/server`, successStatus: 200, declaredStatuses: [401, 400], empty: false }, requestOptions),
@@ -6,9 +6,6 @@ export type ServiceHealth = {
6
6
  version: string;
7
7
  pid: number;
8
8
  };
9
- export type ServiceStopResponse = {
10
- accepted: boolean;
11
- };
12
9
  export type ModelRef = {
13
10
  id: string;
14
11
  providerID: string;
@@ -19,8 +16,16 @@ export type ProviderSettings = {
19
16
  };
20
17
  export type AgentColor = string;
21
18
  export type PermissionEffect = "allow" | "deny" | "ask";
22
- export type PluginInfo = {
23
- id: string;
19
+ export type PluginSource = {
20
+ type: "builtin";
21
+ } | {
22
+ type: "package";
23
+ package: string;
24
+ } | {
25
+ type: "local";
26
+ path: string;
27
+ } | {
28
+ type: "sdk";
24
29
  };
25
30
  export type SessionForkBoundary = {
26
31
  type: "before";
@@ -312,6 +317,18 @@ export type PermissionRule = {
312
317
  resource: string;
313
318
  effect: PermissionEffect;
314
319
  };
320
+ export type PluginInfo = {
321
+ id: string;
322
+ source: PluginSource;
323
+ status: "active";
324
+ tui: boolean;
325
+ } | {
326
+ id?: string;
327
+ source: PluginSource;
328
+ status: "failed";
329
+ error: string;
330
+ tui: boolean;
331
+ };
315
332
  export type TokenUsageInfo = {
316
333
  input: number;
317
334
  output: number;
@@ -2918,12 +2935,6 @@ export type WorktreeError = {
2918
2935
  };
2919
2936
  export declare const isWorktreeError: (value: unknown) => value is WorktreeError;
2920
2937
  export type HealthGetOutput = ServiceHealth;
2921
- export type HealthStopInput = {
2922
- readonly instanceID: {
2923
- readonly instanceID: string;
2924
- }["instanceID"];
2925
- };
2926
- export type HealthStopOutput = ServiceStopResponse;
2927
2938
  export type ServerGetOutput = {
2928
2939
  urls: Array<string>;
2929
2940
  };
@@ -1,4 +1,4 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { readFile, rm } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { contenderFailure, contenderFinished, spawnServiceContender, } from "../service-contender.js";
@@ -42,7 +42,7 @@ export async function ensure(options = {}) {
42
42
  if (command === undefined)
43
43
  throw new Error("Missing service command");
44
44
  try {
45
- return spawnServiceContender(command, args);
45
+ return spawnServiceContender(command, args, options.env);
46
46
  }
47
47
  catch (cause) {
48
48
  throw new Error("Failed to start server", { cause });
@@ -60,7 +60,7 @@ export async function ensure(options = {}) {
60
60
  };
61
61
  if (timeouts.count >= 3) {
62
62
  announce("missing");
63
- await evict(registration.info, options, timing);
63
+ await terminate(registration.info, options, timing);
64
64
  timeouts = undefined;
65
65
  lastSpawn = Date.now() - spawnDelay;
66
66
  }
@@ -77,7 +77,7 @@ export async function ensure(options = {}) {
77
77
  throw new Error("Background service failed to start");
78
78
  if (!compatible) {
79
79
  announce("version-mismatch", service.version);
80
- await kill(service, options, timing).catch(() => undefined);
80
+ await terminate(service.info, options, timing).catch(() => undefined);
81
81
  lastSpawn = 0;
82
82
  }
83
83
  }
@@ -108,9 +108,9 @@ export async function ensure(options = {}) {
108
108
  }
109
109
  /** Stop the registered local service. */
110
110
  export async function stop(options = {}) {
111
- const existing = await find(options);
112
- if (existing !== undefined)
113
- await kill(existing, options, defaultEnsureTiming);
111
+ const info = await read(options.file);
112
+ if (info !== undefined)
113
+ await terminate(info, options, defaultEnsureTiming);
114
114
  }
115
115
  function fallback() {
116
116
  return join(process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state"), "opencode", "service.json");
@@ -187,9 +187,6 @@ async function registered(file, allowLegacy = false, timeout) {
187
187
  return { info: undefined, service: undefined, timedOut: false };
188
188
  return { info, ...(await probeResult(info, allowLegacy, timeout)) };
189
189
  }
190
- async function find(options) {
191
- return (await registered(options.file, true)).service;
192
- }
193
190
  function signal(pid, name) {
194
191
  try {
195
192
  process.kill(pid, name);
@@ -217,54 +214,23 @@ async function waitUntilStopped(pid, timing) {
217
214
  function same(left, right) {
218
215
  return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid;
219
216
  }
220
- async function evict(info, options, timing) {
217
+ async function terminate(info, options, timing) {
221
218
  const current = await read(options.file);
222
219
  if (current === undefined || !same(current, info))
223
220
  return;
224
221
  signal(info.pid, "SIGTERM");
225
- if (await waitUntilStopped(info.pid, timing))
226
- return;
227
- const latest = await read(options.file);
228
- if (latest === undefined || !same(latest, info))
229
- return;
230
- signal(info.pid, "SIGKILL");
231
- if (!(await waitUntilStopped(info.pid, timing)))
232
- throw new Error(`Server process ${info.pid} is still running`);
233
- }
234
- async function kill(service, options, timing) {
235
- const requested = await requestStop(service, timing.requestTimeout);
236
- if (requested === "rejected")
237
- return;
238
- if (requested === "unsupported") {
239
- const current = await find(options);
240
- if (current === undefined || !same(current.info, service.info))
222
+ if (!(await waitUntilStopped(info.pid, timing))) {
223
+ const latest = await read(options.file);
224
+ if (latest === undefined || !same(latest, info))
241
225
  return;
242
- signal(service.info.pid, "SIGTERM");
226
+ signal(info.pid, "SIGKILL");
227
+ if (!(await waitUntilStopped(info.pid, timing)))
228
+ throw new Error(`Server process ${info.pid} is still running`);
243
229
  }
244
- if (await waitUntilStopped(service.info.pid, timing))
245
- return;
246
- const latest = await find(options);
247
- if (latest === undefined || !same(latest.info, service.info))
230
+ const latest = await read(options.file);
231
+ if (latest === undefined || !same(latest, info))
248
232
  return;
249
- signal(service.info.pid, "SIGKILL");
250
- if (!(await waitUntilStopped(service.info.pid, timing)))
251
- throw new Error(`Server process ${service.info.pid} is still running`);
252
- }
253
- async function requestStop(service, timeout = defaultEnsureTiming.requestTimeout) {
254
- if (service.info.id === undefined || service.legacy)
255
- return "unsupported";
256
- const response = await fetch(new URL("/api/service/stop", service.info.url), {
257
- method: "POST",
258
- headers: { ...headers(service.endpoint), "content-type": "application/json" },
259
- body: JSON.stringify({ instanceID: service.info.id }),
260
- signal: AbortSignal.timeout(timeout),
261
- }).catch(() => undefined);
262
- if (response === undefined || response.status === 404 || response.status === 405)
263
- return "unsupported";
264
- const body = (await response.json().catch(() => undefined));
265
- if (!response.ok || body?.accepted !== true)
266
- return "rejected";
267
- return "accepted";
233
+ await rm(options.file ?? fallback(), { force: true });
268
234
  }
269
235
  function delay(milliseconds) {
270
236
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -6,6 +6,6 @@ export type ServiceContender = {
6
6
  readonly stderr: () => string;
7
7
  readonly release: () => void;
8
8
  };
9
- export declare function spawnServiceContender(command: string, args: ReadonlyArray<string>): ServiceContender;
9
+ export declare function spawnServiceContender(command: string, args: ReadonlyArray<string>, env?: Readonly<Record<string, string>>): ServiceContender;
10
10
  export declare function contenderFailure(contender: ServiceContender): Error | undefined;
11
11
  export declare function contenderFinished(contender: ServiceContender): boolean;
@@ -1,7 +1,11 @@
1
1
  import { spawn } from "node:child_process";
2
2
  const stderrLimit = 8 * 1024;
3
- export function spawnServiceContender(command, args) {
4
- const child = spawn(command, args, { detached: true, stdio: ["ignore", "ignore", "pipe"] });
3
+ export function spawnServiceContender(command, args, env) {
4
+ const child = spawn(command, args, {
5
+ detached: true,
6
+ stdio: ["ignore", "ignore", "pipe"],
7
+ env: { ...process.env, ...env },
8
+ });
5
9
  let error;
6
10
  let closed = false;
7
11
  let stderr = Buffer.alloc(0);
package/dist/service.d.ts CHANGED
@@ -25,6 +25,8 @@ export type EnsureReason = "missing" | "version-mismatch";
25
25
  export type EnsureOptions = DiscoverOptions & {
26
26
  /** Service command and arguments. Defaults to `opencode serve --service`. */
27
27
  readonly command?: ReadonlyArray<string>;
28
+ /** Environment variables added to the inherited service process environment. */
29
+ readonly env?: Readonly<Record<string, string>>;
28
30
  /** Called once before spawning a new service process. */
29
31
  readonly onStart?: (reason: EnsureReason, previousVersion?: string) => void;
30
32
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode-ai/client",
4
- "version": "0.0.0-dev-17603",
4
+ "version": "0.0.0-dev-17635",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -57,8 +57,8 @@
57
57
  "typecheck": "tsgo --noEmit"
58
58
  },
59
59
  "dependencies": {
60
- "@opencode-ai/schema": "0.0.0-dev-17603",
61
- "@opencode-ai/protocol": "0.0.0-dev-17603"
60
+ "@opencode-ai/schema": "0.0.0-dev-17635",
61
+ "@opencode-ai/protocol": "0.0.0-dev-17635"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "effect": "4.0.0-beta.107",
@@ -74,7 +74,7 @@
74
74
  },
75
75
  "devDependencies": {
76
76
  "@effect/platform-node": "4.0.0-beta.107",
77
- "@opencode-ai/httpapi-codegen": "0.0.0-dev-17603",
77
+ "@opencode-ai/httpapi-codegen": "0.0.0-dev-17635",
78
78
  "@tsconfig/bun": "1.0.9",
79
79
  "@types/bun": "1.3.13",
80
80
  "@typescript/native-preview": "7.0.0-dev.20251207.1",