@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.
- package/dist/effect/api/api.d.ts +0 -8
- package/dist/effect/generated/client.d.ts +1 -2
- package/dist/effect/generated/client.js +1 -2
- package/dist/effect/service.js +16 -55
- package/dist/promise/generated/client.d.ts +1 -2
- package/dist/promise/generated/client.js +0 -8
- package/dist/promise/generated/types.d.ts +22 -11
- package/dist/promise/service.js +17 -51
- package/dist/service-contender.d.ts +1 -1
- package/dist/service-contender.js +6 -2
- package/dist/service.d.ts +2 -0
- package/package.json +4 -4
package/dist/effect/api/api.d.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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)));
|
package/dist/effect/service.js
CHANGED
|
@@ -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*
|
|
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*
|
|
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
|
|
128
|
-
if (
|
|
129
|
-
yield*
|
|
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
|
|
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.
|
|
239
|
-
|
|
240
|
-
|
|
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(
|
|
237
|
+
yield* signal(info.pid, "SIGKILL");
|
|
238
|
+
yield* stopped(info.pid).pipe(Effect.retry(poll(timing)));
|
|
257
239
|
}
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
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*
|
|
265
|
-
yield*
|
|
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 {
|
|
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
|
|
23
|
-
|
|
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
|
};
|
package/dist/promise/service.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
112
|
-
if (
|
|
113
|
-
await
|
|
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
|
|
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
|
-
|
|
227
|
-
|
|
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(
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
|
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, {
|
|
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-
|
|
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-
|
|
61
|
-
"@opencode-ai/protocol": "0.0.0-dev-
|
|
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-
|
|
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",
|