@opencode-ai/client 0.0.0-next-14962 → 0.0.0-next-14971
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.
|
@@ -471,6 +471,8 @@ export declare const make: (options?: {
|
|
|
471
471
|
health: {
|
|
472
472
|
get: () => Effect.Effect<{
|
|
473
473
|
readonly healthy: true;
|
|
474
|
+
readonly version: string;
|
|
475
|
+
readonly pid: number;
|
|
474
476
|
}, import("@opencode-ai/protocol/errors").InvalidRequestError | Schema.SchemaError | import("@opencode-ai/protocol/errors").UnauthorizedError | HttpClientError.HttpClientError | ClientError, never>;
|
|
475
477
|
};
|
|
476
478
|
location: {
|
package/dist/effect/service.js
CHANGED
|
@@ -5,13 +5,15 @@ import { join } from "node:path";
|
|
|
5
5
|
// Read-only lookup: registration file plus health check and version gate.
|
|
6
6
|
// Never spawns; escalation to start() is the caller's policy.
|
|
7
7
|
export const discover = Effect.fn("service.discover")(function* (options = {}) {
|
|
8
|
+
return (yield* discoverLocal(options))?.transport;
|
|
9
|
+
});
|
|
10
|
+
const discoverLocal = Effect.fnUntraced(function* (options) {
|
|
8
11
|
const info = yield* read(options.file);
|
|
9
12
|
if (info === undefined)
|
|
10
13
|
return undefined;
|
|
11
14
|
if (options.version !== undefined && info.version !== options.version)
|
|
12
15
|
return undefined;
|
|
13
|
-
|
|
14
|
-
return found?.transport;
|
|
16
|
+
return yield* probe(info, options.version);
|
|
15
17
|
});
|
|
16
18
|
// Idempotent ensure-running: reuses a healthy compatible server, replaces a
|
|
17
19
|
// version-mismatched one, and otherwise spawns the service command detached.
|
|
@@ -25,13 +27,19 @@ export const start = Effect.fn("service.start")(function* (options = {}) {
|
|
|
25
27
|
const [command, ...args] = options.command ?? ["opencode", "serve", "--service"];
|
|
26
28
|
if (command === undefined)
|
|
27
29
|
return yield* Effect.fail(new Error("Missing service command"));
|
|
28
|
-
yield* Effect.try({
|
|
30
|
+
const child = yield* Effect.try({
|
|
29
31
|
try: () => {
|
|
30
|
-
spawn(command, args, { detached: true, stdio: "ignore" })
|
|
32
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
33
|
+
child.unref();
|
|
34
|
+
return child;
|
|
31
35
|
},
|
|
32
36
|
catch: (cause) => new Error("Failed to start server", { cause }),
|
|
33
37
|
});
|
|
34
|
-
return yield*
|
|
38
|
+
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
|
|
39
|
+
? Effect.void
|
|
40
|
+
: Effect.sync(() => {
|
|
41
|
+
child.kill("SIGTERM");
|
|
42
|
+
})), Effect.map((found) => found.transport), Effect.tapError(() => Effect.try({ try: () => child.kill("SIGTERM"), catch: () => undefined }).pipe(Effect.ignore)), Effect.mapError(() => new Error("Failed to start server")));
|
|
35
43
|
});
|
|
36
44
|
export const stop = Effect.fn("service.stop")(function* (options = {}) {
|
|
37
45
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -55,6 +63,7 @@ export const Info = Schema.Struct({
|
|
|
55
63
|
password: Schema.optional(Schema.String),
|
|
56
64
|
});
|
|
57
65
|
const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Info));
|
|
66
|
+
const decodeHealth = Schema.decodeUnknownEffect(Schema.Struct({ healthy: Schema.Literal(true), version: Schema.String, pid: Schema.Int }));
|
|
58
67
|
// A missing or corrupt file means no valid info; callers treat both
|
|
59
68
|
// the same (the registering server self-evicts, clients rediscover).
|
|
60
69
|
const read = Effect.fnUntraced(function* (file) {
|
|
@@ -64,13 +73,20 @@ const read = Effect.fnUntraced(function* (file) {
|
|
|
64
73
|
return undefined;
|
|
65
74
|
return yield* decode(text.value).pipe(Effect.option, Effect.map(Option.getOrUndefined));
|
|
66
75
|
});
|
|
67
|
-
const probe = Effect.fnUntraced(function* (info) {
|
|
76
|
+
const probe = Effect.fnUntraced(function* (info, version) {
|
|
68
77
|
const headers = info.password === undefined ? undefined : auth(info.password);
|
|
69
|
-
const
|
|
78
|
+
const response = yield* Effect.tryPromise(() => fetch(new URL("/api/health", info.url), {
|
|
70
79
|
headers,
|
|
71
80
|
signal: AbortSignal.timeout(2_000),
|
|
72
|
-
})).pipe(Effect.
|
|
73
|
-
if (!
|
|
81
|
+
})).pipe(Effect.option, Effect.map(Option.getOrUndefined));
|
|
82
|
+
if (response === undefined || !response.ok)
|
|
83
|
+
return undefined;
|
|
84
|
+
const health = yield* Effect.tryPromise(() => response.json()).pipe(Effect.flatMap(decodeHealth), Effect.option, Effect.map(Option.getOrUndefined));
|
|
85
|
+
if (health?.pid !== info.pid)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (info.version !== undefined && health.version !== info.version)
|
|
88
|
+
return undefined;
|
|
89
|
+
if (version !== undefined && health.version !== version)
|
|
74
90
|
return undefined;
|
|
75
91
|
return { info, transport: { url: info.url, headers } };
|
|
76
92
|
});
|
|
@@ -131,6 +131,8 @@ export type ProjectCopyError = {
|
|
|
131
131
|
export declare const isProjectCopyError: (value: unknown) => value is ProjectCopyError;
|
|
132
132
|
export type HealthGetOutput = {
|
|
133
133
|
readonly healthy: true;
|
|
134
|
+
readonly version: string;
|
|
135
|
+
readonly pid: number;
|
|
134
136
|
};
|
|
135
137
|
export type LocationGetInput = {
|
|
136
138
|
readonly location?: {
|
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-next-
|
|
4
|
+
"version": "0.0.0-next-14971",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"typecheck": "tsgo --noEmit"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@opencode-ai/schema": "0.0.0-next-
|
|
45
|
-
"@opencode-ai/protocol": "0.0.0-next-
|
|
44
|
+
"@opencode-ai/schema": "0.0.0-next-14971",
|
|
45
|
+
"@opencode-ai/protocol": "0.0.0-next-14971"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"effect": "4.0.0-beta.83"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@effect/platform-node": "4.0.0-beta.83",
|
|
57
|
-
"@opencode-ai/httpapi-codegen": "0.0.0-next-
|
|
57
|
+
"@opencode-ai/httpapi-codegen": "0.0.0-next-14971",
|
|
58
58
|
"@tsconfig/bun": "1.0.9",
|
|
59
59
|
"@types/bun": "1.3.13",
|
|
60
60
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|