@opencode/client 0.0.0-dev-20558 → 0.0.0-dev-20560
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/chunks/{service-vvwt8khz.js → service-7fhbdh77.js} +6 -6
- package/dist/chunks/service-ank0d71z.js +75 -0
- package/dist/chunks/{service-5yf5aaev.js → service-d59rj3hx.js} +3 -3
- package/dist/chunks/{service-w1w809zr.js → service-mnyft54j.js} +3 -3
- package/dist/effect/client.js +5 -5
- package/dist/effect/generated/client.js +3 -3
- package/dist/effect/generated/index.js +1 -1
- package/dist/effect/index.js +3 -3
- package/dist/effect/rpc.js +3 -3
- package/dist/effect/service.d.ts +5 -7
- package/dist/effect/service.js +21 -80
- package/dist/promise/service.d.ts +2 -4
- package/dist/promise/service.js +18 -79
- package/dist/service-probe.d.ts +53 -0
- package/dist/service-probe.js +13 -0
- package/package.json +4 -4
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
ClientError3
|
|
6
6
|
} from "./service-7f0py9js.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
} from "./service-
|
|
8
|
+
make6
|
|
9
|
+
} from "./service-d59rj3hx.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
make5,
|
|
12
12
|
aborted2
|
|
13
|
-
} from "./service-
|
|
13
|
+
} from "./service-mnyft54j.js";
|
|
14
14
|
import {
|
|
15
15
|
__export
|
|
16
16
|
} from "./service-9rqn6x4v.js";
|
|
@@ -30,7 +30,7 @@ var CurrentHeaders = Context.Reference("@opencode/client/effect/rpc/headers", {
|
|
|
30
30
|
});
|
|
31
31
|
var make4 = Effect.fn("OpenCode.make")(function* (options) {
|
|
32
32
|
const httpClient = yield* HttpClient.HttpClient;
|
|
33
|
-
const raw = yield*
|
|
33
|
+
const raw = yield* make6(options).pipe(Effect.provideService(HttpClient.HttpClient, HttpClient.mapRequestEffect(httpClient, (request) => Effect.map(CurrentHeaders, (headers) => headers ? HttpClientRequest.setHeaders(request, new Headers(headers)) : request))));
|
|
34
34
|
const context = yield* Effect.context();
|
|
35
35
|
const native = raw.event.subscribe();
|
|
36
36
|
|
|
@@ -45,7 +45,7 @@ var make4 = Effect.fn("OpenCode.make")(function* (options) {
|
|
|
45
45
|
return {
|
|
46
46
|
...raw,
|
|
47
47
|
event: { ...raw.event, subscribe },
|
|
48
|
-
rpc: Object.assign(
|
|
48
|
+
rpc: Object.assign(make5((input, options) => raw.rpc.call(input).pipe(Effect.provideService(CurrentHeaders, options?.headers)), subscribe), raw.rpc)
|
|
49
49
|
};
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultEnsureTiming2
|
|
3
|
+
} from "./service-xgnj2w4v.js";
|
|
4
|
+
|
|
5
|
+
// src/service-probe.ts
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
function fallback2() {
|
|
9
|
+
return join(process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state"), "opencode", "service.json");
|
|
10
|
+
}
|
|
11
|
+
function same2(left, right) {
|
|
12
|
+
return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid;
|
|
13
|
+
}
|
|
14
|
+
async function probeResult2(info, timeout = defaultEnsureTiming2.requestTimeout) {
|
|
15
|
+
const endpoint = {
|
|
16
|
+
url: info.url,
|
|
17
|
+
auth: info.password === undefined ? undefined : { type: "basic", username: "opencode", password: info.password }
|
|
18
|
+
};
|
|
19
|
+
const signal = AbortSignal.timeout(timeout);
|
|
20
|
+
const result = await fetch(new URL("/api/info", info.url), { headers: headers2(endpoint), signal }).then(async (response) => ({
|
|
21
|
+
response,
|
|
22
|
+
body: response.status === 404 ? undefined : await response.json()
|
|
23
|
+
})).catch(() => {
|
|
24
|
+
return;
|
|
25
|
+
});
|
|
26
|
+
if (result === undefined)
|
|
27
|
+
return { service: undefined, timedOut: signal.aborted };
|
|
28
|
+
const response = result.response;
|
|
29
|
+
if (response.status === 404)
|
|
30
|
+
return {
|
|
31
|
+
service: {
|
|
32
|
+
info,
|
|
33
|
+
endpoint,
|
|
34
|
+
version: info.version,
|
|
35
|
+
state: "ready",
|
|
36
|
+
compatible: false
|
|
37
|
+
},
|
|
38
|
+
timedOut: false
|
|
39
|
+
};
|
|
40
|
+
const serverInfo = decodeInfo(result.body);
|
|
41
|
+
if (serverInfo === undefined)
|
|
42
|
+
return { service: undefined, timedOut: false };
|
|
43
|
+
if (serverInfo.pid !== info.pid)
|
|
44
|
+
return { service: undefined, timedOut: false };
|
|
45
|
+
if (info.version !== undefined && serverInfo.version !== info.version)
|
|
46
|
+
return { service: undefined, timedOut: false };
|
|
47
|
+
return {
|
|
48
|
+
service: {
|
|
49
|
+
info,
|
|
50
|
+
endpoint,
|
|
51
|
+
version: serverInfo.version,
|
|
52
|
+
state: response.ok ? "ready" : response.status === 500 ? "failed" : "waiting",
|
|
53
|
+
compatible: true
|
|
54
|
+
},
|
|
55
|
+
timedOut: false
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function headers2(endpoint) {
|
|
59
|
+
if (endpoint.auth === undefined)
|
|
60
|
+
return;
|
|
61
|
+
return {
|
|
62
|
+
authorization: "Basic " + Buffer.from(endpoint.auth.username + ":" + endpoint.auth.password).toString("base64")
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function decodeInfo(input) {
|
|
66
|
+
if (typeof input !== "object" || input === null)
|
|
67
|
+
return;
|
|
68
|
+
if (!("version" in input) || typeof input.version !== "string")
|
|
69
|
+
return;
|
|
70
|
+
if (!("pid" in input) || typeof input.pid !== "number" || !Number.isInteger(input.pid) || input.pid < 0)
|
|
71
|
+
return;
|
|
72
|
+
return { version: input.version, pid: input.pid };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { fallback2, same2, probeResult2, headers2 };
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
// src/effect/generated/client.ts
|
|
12
12
|
var exports_client4 = {};
|
|
13
13
|
__export(exports_client4, {
|
|
14
|
-
make: () =>
|
|
14
|
+
make: () => make6
|
|
15
15
|
});
|
|
16
16
|
import { Effect, Stream, Schema } from "effect";
|
|
17
17
|
import { Sse } from "effect/unstable/encoding";
|
|
@@ -613,6 +613,6 @@ var adaptClient = (raw) => ({
|
|
|
613
613
|
websearch: adaptGroupWebsearch(raw["server.websearch"]),
|
|
614
614
|
config: adaptGroupConfig(raw["server.config"])
|
|
615
615
|
});
|
|
616
|
-
var
|
|
616
|
+
var make6 = (options) => HttpApiClient.make(ClientApi2, options).pipe(Effect.map(adaptClient));
|
|
617
617
|
|
|
618
|
-
export {
|
|
618
|
+
export { make6, exports_client4 };
|
|
@@ -13,10 +13,10 @@ var exports_rpc = {};
|
|
|
13
13
|
__export(exports_rpc, {
|
|
14
14
|
RpcClientRuntime: () => exports_rpc,
|
|
15
15
|
aborted: () => aborted2,
|
|
16
|
-
make: () =>
|
|
16
|
+
make: () => make5
|
|
17
17
|
});
|
|
18
18
|
import { Effect, Stream } from "effect";
|
|
19
|
-
function
|
|
19
|
+
function make5(call, subscribe) {
|
|
20
20
|
return (definition) => {
|
|
21
21
|
const methods = Object.fromEntries(Object.entries(definition.methods).map(([name, method]) => [
|
|
22
22
|
name,
|
|
@@ -59,4 +59,4 @@ function aborted2(signal) {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
export {
|
|
62
|
+
export { make5, aborted2, exports_rpc };
|
package/dist/effect/client.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
make4,
|
|
3
3
|
exports_client3
|
|
4
|
-
} from "../chunks/service-
|
|
4
|
+
} from "../chunks/service-7fhbdh77.js";
|
|
5
5
|
import"../chunks/service-6mptkzbc.js";
|
|
6
|
-
import"../chunks/service-
|
|
6
|
+
import"../chunks/service-mnyft54j.js";
|
|
7
|
+
import"../chunks/service-d59rj3hx.js";
|
|
7
8
|
import"../chunks/service-7f0py9js.js";
|
|
8
|
-
import"../chunks/service-w1w809zr.js";
|
|
9
|
-
import"../chunks/service-vmytr4q8.js";
|
|
10
|
-
import"../chunks/service-t1xa31vm.js";
|
|
11
9
|
import"../chunks/service-frbwqjmf.js";
|
|
10
|
+
import"../chunks/service-t1xa31vm.js";
|
|
11
|
+
import"../chunks/service-vmytr4q8.js";
|
|
12
12
|
export {
|
|
13
13
|
exports_client3 as OpenCode,
|
|
14
14
|
make4 as make
|
package/dist/effect/index.js
CHANGED
|
@@ -2,14 +2,14 @@ import {
|
|
|
2
2
|
ClientError3
|
|
3
3
|
} from "../chunks/service-7f0py9js.js";
|
|
4
4
|
import"../chunks/service-vmytr4q8.js";
|
|
5
|
-
import"../chunks/service-
|
|
5
|
+
import"../chunks/service-d59rj3hx.js";
|
|
6
6
|
import"../chunks/service-6mptkzbc.js";
|
|
7
7
|
import"../chunks/service-frbwqjmf.js";
|
|
8
8
|
import"../chunks/service-t1xa31vm.js";
|
|
9
|
-
import"../chunks/service-
|
|
9
|
+
import"../chunks/service-mnyft54j.js";
|
|
10
10
|
import {
|
|
11
11
|
exports_client3
|
|
12
|
-
} from "../chunks/service-
|
|
12
|
+
} from "../chunks/service-7fhbdh77.js";
|
|
13
13
|
|
|
14
14
|
// src/effect/index.ts
|
|
15
15
|
import { Agent } from "@opencode/schema/agent";
|
package/dist/effect/rpc.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
make5,
|
|
3
3
|
aborted2,
|
|
4
4
|
exports_rpc
|
|
5
|
-
} from "../chunks/service-
|
|
5
|
+
} from "../chunks/service-mnyft54j.js";
|
|
6
6
|
import"../chunks/service-t1xa31vm.js";
|
|
7
7
|
export {
|
|
8
8
|
exports_rpc as RpcClientRuntime,
|
|
9
9
|
aborted2 as aborted,
|
|
10
|
-
|
|
10
|
+
make5 as make
|
|
11
11
|
};
|
package/dist/effect/service.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Effect, FileSystem, Schema } from "effect";
|
|
2
|
-
import type { DiscoverOptions,
|
|
2
|
+
import type { DiscoverOptions, EnsureOptions, StopOptions } from "../service.js";
|
|
3
|
+
import { headers } from "../service-probe.js";
|
|
3
4
|
export * from "../service.js";
|
|
5
|
+
export { headers };
|
|
4
6
|
/** Contents of the local service registration file. */
|
|
5
7
|
export type Info = import("../service.js").Info;
|
|
6
8
|
/** Discover a healthy, compatible local service without starting one. */
|
|
@@ -27,13 +29,9 @@ export declare const incumbent: (options: DiscoverOptions & {
|
|
|
27
29
|
state: "failed" | "ready" | "waiting";
|
|
28
30
|
} | undefined, never, FileSystem.FileSystem>;
|
|
29
31
|
/** Ensure a healthy, compatible local service is running. */
|
|
30
|
-
export declare const ensure: (options?: EnsureOptions | undefined) => Effect.Effect<Endpoint, Error, FileSystem.FileSystem>;
|
|
32
|
+
export declare const ensure: (options?: EnsureOptions | undefined) => Effect.Effect<import("./service.js").Endpoint, Error, FileSystem.FileSystem>;
|
|
31
33
|
/** Stop the registered local service. */
|
|
32
34
|
export declare const stop: (options?: StopOptions | undefined) => Effect.Effect<void, Error, FileSystem.FileSystem>;
|
|
33
|
-
/** Create HTTP authentication headers for a service endpoint. */
|
|
34
|
-
export declare function headers(endpoint: Endpoint): {
|
|
35
|
-
authorization: string;
|
|
36
|
-
} | undefined;
|
|
37
35
|
/** Schema for the local service registration file. */
|
|
38
36
|
export declare const Info: Schema.Struct<{
|
|
39
37
|
readonly id: Schema.optional<Schema.String>;
|
|
@@ -65,7 +63,7 @@ export declare const Service: {
|
|
|
65
63
|
};
|
|
66
64
|
state: "failed" | "ready" | "waiting";
|
|
67
65
|
} | undefined, never, FileSystem.FileSystem>;
|
|
68
|
-
ensure: (options?: EnsureOptions | undefined) => Effect.Effect<Endpoint, Error, FileSystem.FileSystem>;
|
|
66
|
+
ensure: (options?: EnsureOptions | undefined) => Effect.Effect<import("./service.js").Endpoint, Error, FileSystem.FileSystem>;
|
|
69
67
|
stop: (options?: StopOptions | undefined) => Effect.Effect<void, Error, FileSystem.FileSystem>;
|
|
70
68
|
headers: typeof headers;
|
|
71
69
|
Info: Schema.Struct<{
|
package/dist/effect/service.js
CHANGED
|
@@ -16,12 +16,16 @@ import {
|
|
|
16
16
|
complete2,
|
|
17
17
|
clear2
|
|
18
18
|
} from "../chunks/service-71wkqa80.js";
|
|
19
|
+
import {
|
|
20
|
+
fallback2,
|
|
21
|
+
same2,
|
|
22
|
+
probeResult2,
|
|
23
|
+
headers2
|
|
24
|
+
} from "../chunks/service-ank0d71z.js";
|
|
19
25
|
import"../chunks/service-qav6mfdz.js";
|
|
20
26
|
|
|
21
27
|
// src/effect/service.ts
|
|
22
28
|
import { Effect, FileSystem, Option, Schedule, Schema } from "effect";
|
|
23
|
-
import { homedir } from "node:os";
|
|
24
|
-
import { join } from "node:path";
|
|
25
29
|
var discover = Effect.fn("service.discover")(function* (options = {}) {
|
|
26
30
|
const found = (yield* registered(options.file)).service;
|
|
27
31
|
if (found?.state !== "ready")
|
|
@@ -34,10 +38,10 @@ var discover = Effect.fn("service.discover")(function* (options = {}) {
|
|
|
34
38
|
});
|
|
35
39
|
var incumbent = Effect.fn("service.incumbent")(function* (options) {
|
|
36
40
|
const info = yield* read(options.file);
|
|
37
|
-
|
|
38
|
-
if (found === undefined)
|
|
41
|
+
if (info === undefined)
|
|
39
42
|
return;
|
|
40
|
-
|
|
43
|
+
const found = (yield* Effect.promise(() => probeResult2({ ...info, url: options.url }))).service;
|
|
44
|
+
if (!found?.compatible)
|
|
41
45
|
return;
|
|
42
46
|
if (!matchesVersion2(found.version, options))
|
|
43
47
|
return;
|
|
@@ -61,7 +65,7 @@ var ensure = Effect.fn("service.ensure")(function* (options = {}) {
|
|
|
61
65
|
const [command, ...args] = options.command ?? ["opencode", "serve", "--service"];
|
|
62
66
|
if (command === undefined)
|
|
63
67
|
return yield* Effect.fail(new Error("Missing service command"));
|
|
64
|
-
const env = yield* Effect.tryPromise(() => environment2(options.file ??
|
|
68
|
+
const env = yield* Effect.tryPromise(() => environment2(options.file ?? fallback2(), options.env));
|
|
65
69
|
return yield* Effect.try({
|
|
66
70
|
try: () => {
|
|
67
71
|
return spawnServiceContender2(command, args, env);
|
|
@@ -76,12 +80,12 @@ var ensure = Effect.fn("service.ensure")(function* (options = {}) {
|
|
|
76
80
|
if (registration.timedOut && info !== undefined) {
|
|
77
81
|
timeouts = {
|
|
78
82
|
info,
|
|
79
|
-
count: timeouts !== undefined &&
|
|
83
|
+
count: timeouts !== undefined && same2(timeouts.info, info) ? timeouts.count + 1 : 1
|
|
80
84
|
};
|
|
81
85
|
if (timeouts.count >= 3) {
|
|
82
86
|
yield* announce("missing");
|
|
83
87
|
yield* Effect.logWarning("Background service is unresponsive; recovery cannot preserve persistent terminals");
|
|
84
|
-
yield* Effect.tryPromise(() => clear2(options.file ??
|
|
88
|
+
yield* Effect.tryPromise(() => clear2(options.file ?? fallback2()));
|
|
85
89
|
yield* terminate(info, options, timing);
|
|
86
90
|
for (const item of contenders) {
|
|
87
91
|
if (item.child.pid === info.pid || contenderFinished2(item)) {
|
|
@@ -102,7 +106,7 @@ var ensure = Effect.fn("service.ensure")(function* (options = {}) {
|
|
|
102
106
|
if (!service2.compatible && versionMatches)
|
|
103
107
|
return yield* Effect.fail(new Error("Background service uses an incompatible health protocol. Update this client or explicitly restart the service."));
|
|
104
108
|
if (compatible && service2.state === "ready") {
|
|
105
|
-
yield* Effect.tryPromise(() => complete2(options.file ??
|
|
109
|
+
yield* Effect.tryPromise(() => complete2(options.file ?? fallback2(), service2.info));
|
|
106
110
|
return Option.some(service2);
|
|
107
111
|
}
|
|
108
112
|
if (compatible && service2.state === "failed")
|
|
@@ -151,19 +155,10 @@ var ensure = Effect.fn("service.ensure")(function* (options = {}) {
|
|
|
151
155
|
});
|
|
152
156
|
var stop = Effect.fn("service.stop")(function* (options = {}) {
|
|
153
157
|
const info = yield* read(options.file);
|
|
154
|
-
yield* Effect.tryPromise(() => options.pty === "handoff" && info !== undefined ? prepare2(options.file ??
|
|
158
|
+
yield* Effect.tryPromise(() => options.pty === "handoff" && info !== undefined ? prepare2(options.file ?? fallback2(), info, defaultEnsureTiming2.requestTimeout) : clear2(options.file ?? fallback2())).pipe(Effect.catch((cause) => Effect.logWarning("Failed to prepare persistent terminals for replacement", cause)));
|
|
155
159
|
if (info !== undefined)
|
|
156
160
|
yield* terminate(info, options, ensureTiming2(options));
|
|
157
161
|
});
|
|
158
|
-
function fallback() {
|
|
159
|
-
const state = process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state");
|
|
160
|
-
return join(state, "opencode", "service.json");
|
|
161
|
-
}
|
|
162
|
-
function headers(endpoint) {
|
|
163
|
-
if (endpoint.auth === undefined)
|
|
164
|
-
return;
|
|
165
|
-
return { authorization: "Basic " + btoa(endpoint.auth.username + ":" + endpoint.auth.password) };
|
|
166
|
-
}
|
|
167
162
|
var Info = Schema.Struct({
|
|
168
163
|
id: Schema.optional(Schema.String),
|
|
169
164
|
version: Schema.optional(Schema.String),
|
|
@@ -172,69 +167,18 @@ var Info = Schema.Struct({
|
|
|
172
167
|
password: Schema.optional(Schema.String)
|
|
173
168
|
});
|
|
174
169
|
var decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Info));
|
|
175
|
-
var decodeInfo = Schema.decodeUnknownOption(Schema.Struct({
|
|
176
|
-
version: Schema.String,
|
|
177
|
-
pid: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
|
|
178
|
-
}));
|
|
179
170
|
var read = Effect.fnUntraced(function* (file) {
|
|
180
171
|
const fs = yield* FileSystem.FileSystem;
|
|
181
|
-
const text = yield* fs.readFileString(file ??
|
|
172
|
+
const text = yield* fs.readFileString(file ?? fallback2()).pipe(Effect.option);
|
|
182
173
|
if (Option.isNone(text))
|
|
183
174
|
return;
|
|
184
175
|
return yield* decode(text.value).pipe(Effect.option, Effect.map(Option.getOrUndefined));
|
|
185
176
|
});
|
|
186
|
-
var probe = Effect.fnUntraced(function* (info) {
|
|
187
|
-
return (yield* probeResult(info)).service;
|
|
188
|
-
});
|
|
189
|
-
var probeResult = Effect.fnUntraced(function* (info, timeout = defaultEnsureTiming2.requestTimeout) {
|
|
190
|
-
const endpoint = {
|
|
191
|
-
url: info.url,
|
|
192
|
-
auth: info.password === undefined ? undefined : { type: "basic", username: "opencode", password: info.password }
|
|
193
|
-
};
|
|
194
|
-
const signal = AbortSignal.timeout(timeout);
|
|
195
|
-
const result = yield* Effect.promise(() => fetch(new URL("/api/info", info.url), { headers: headers(endpoint), signal }).then(async (response) => ({
|
|
196
|
-
response,
|
|
197
|
-
body: response.status === 404 ? undefined : await response.json()
|
|
198
|
-
})).then((value) => ({ value }), (cause) => ({ cause })));
|
|
199
|
-
if ("cause" in result)
|
|
200
|
-
return { service: undefined, timedOut: signal.aborted };
|
|
201
|
-
const response = result.value.response;
|
|
202
|
-
if (response.status === 404)
|
|
203
|
-
return {
|
|
204
|
-
service: {
|
|
205
|
-
info,
|
|
206
|
-
endpoint,
|
|
207
|
-
version: info.version,
|
|
208
|
-
state: "ready",
|
|
209
|
-
compatible: false
|
|
210
|
-
},
|
|
211
|
-
timedOut: false
|
|
212
|
-
};
|
|
213
|
-
const body = result.value.body;
|
|
214
|
-
const serverInfo = decodeInfo(body);
|
|
215
|
-
if (Option.isSome(serverInfo)) {
|
|
216
|
-
if (serverInfo.value.pid !== info.pid)
|
|
217
|
-
return { service: undefined, timedOut: false };
|
|
218
|
-
if (info.version !== undefined && serverInfo.value.version !== info.version)
|
|
219
|
-
return { service: undefined, timedOut: false };
|
|
220
|
-
return {
|
|
221
|
-
service: {
|
|
222
|
-
info,
|
|
223
|
-
endpoint,
|
|
224
|
-
version: serverInfo.value.version,
|
|
225
|
-
state: response.ok ? "ready" : response.status === 500 ? "failed" : "waiting",
|
|
226
|
-
compatible: true
|
|
227
|
-
},
|
|
228
|
-
timedOut: false
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
return { service: undefined, timedOut: false };
|
|
232
|
-
});
|
|
233
177
|
var registered = Effect.fnUntraced(function* (file, timeout) {
|
|
234
178
|
const info = yield* read(file);
|
|
235
179
|
if (info === undefined)
|
|
236
180
|
return { info: undefined, service: undefined, timedOut: false };
|
|
237
|
-
return { info, ...yield*
|
|
181
|
+
return { info, ...yield* Effect.promise(() => probeResult2(info, timeout)) };
|
|
238
182
|
});
|
|
239
183
|
var poll = (timing) => Schedule.max([Schedule.spaced(timing.stopPollInterval), Schedule.recurs(timing.stopPollAttempts)]);
|
|
240
184
|
var signal = (pid, name) => Effect.try({ try: () => process.kill(pid, name), catch: (cause) => cause }).pipe(Effect.ignore);
|
|
@@ -244,12 +188,9 @@ var stopped = Effect.fnUntraced(function* (pid) {
|
|
|
244
188
|
return true;
|
|
245
189
|
return yield* Effect.fail(new Error(`Server process ${pid} is still running`));
|
|
246
190
|
});
|
|
247
|
-
function same(left, right) {
|
|
248
|
-
return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid;
|
|
249
|
-
}
|
|
250
191
|
var terminate = Effect.fnUntraced(function* (info, options, timing) {
|
|
251
192
|
const current = yield* read(options.file);
|
|
252
|
-
if (current === undefined || !
|
|
193
|
+
if (current === undefined || !same2(current, info))
|
|
253
194
|
return;
|
|
254
195
|
yield* signal(info.pid, "SIGTERM");
|
|
255
196
|
const done = yield* stopped(info.pid).pipe(Effect.retry(poll(timing)), Effect.option);
|
|
@@ -258,18 +199,18 @@ var terminate = Effect.fnUntraced(function* (info, options, timing) {
|
|
|
258
199
|
yield* stopped(info.pid).pipe(Effect.retry(poll(timing)));
|
|
259
200
|
}
|
|
260
201
|
const latest = yield* read(options.file);
|
|
261
|
-
if (latest === undefined || !
|
|
202
|
+
if (latest === undefined || !same2(latest, info))
|
|
262
203
|
return;
|
|
263
204
|
const fs = yield* FileSystem.FileSystem;
|
|
264
|
-
yield* fs.remove(options.file ??
|
|
205
|
+
yield* fs.remove(options.file ?? fallback2()).pipe(Effect.ignore);
|
|
265
206
|
});
|
|
266
|
-
var Service = { discover, incumbent, ensure, stop, headers, Info };
|
|
207
|
+
var Service = { discover, incumbent, ensure, stop, headers: headers2, Info };
|
|
267
208
|
export {
|
|
268
209
|
Info,
|
|
269
210
|
Service,
|
|
270
211
|
discover,
|
|
271
212
|
ensure,
|
|
272
|
-
headers,
|
|
213
|
+
headers2 as headers,
|
|
273
214
|
incumbent,
|
|
274
215
|
stop
|
|
275
216
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DiscoverOptions, Endpoint, EnsureOptions, StopOptions } from "../service.js";
|
|
2
|
+
import { headers } from "../service-probe.js";
|
|
2
3
|
export * from "../service.js";
|
|
4
|
+
export { headers };
|
|
3
5
|
/** Discover a healthy, compatible local service without starting one. */
|
|
4
6
|
export declare function discover(options?: DiscoverOptions): Promise<{
|
|
5
7
|
url: string;
|
|
@@ -13,10 +15,6 @@ export declare function discover(options?: DiscoverOptions): Promise<{
|
|
|
13
15
|
export declare function ensure(options?: EnsureOptions): Promise<Endpoint>;
|
|
14
16
|
/** Stop the registered local service. */
|
|
15
17
|
export declare function stop(options?: StopOptions): Promise<void>;
|
|
16
|
-
/** Create HTTP authentication headers for a service endpoint. */
|
|
17
|
-
export declare function headers(endpoint: Endpoint): {
|
|
18
|
-
authorization: string;
|
|
19
|
-
} | undefined;
|
|
20
18
|
/** Promise-based local service lifecycle operations. */
|
|
21
19
|
export declare const Service: {
|
|
22
20
|
discover: typeof discover;
|
package/dist/promise/service.js
CHANGED
|
@@ -16,12 +16,16 @@ import {
|
|
|
16
16
|
complete2,
|
|
17
17
|
clear2
|
|
18
18
|
} from "../chunks/service-71wkqa80.js";
|
|
19
|
+
import {
|
|
20
|
+
fallback2,
|
|
21
|
+
same2,
|
|
22
|
+
probeResult2,
|
|
23
|
+
headers2
|
|
24
|
+
} from "../chunks/service-ank0d71z.js";
|
|
19
25
|
import"../chunks/service-qav6mfdz.js";
|
|
20
26
|
|
|
21
27
|
// src/promise/service.ts
|
|
22
28
|
import { readFile, rm } from "node:fs/promises";
|
|
23
|
-
import { homedir } from "node:os";
|
|
24
|
-
import { join } from "node:path";
|
|
25
29
|
async function discover(options = {}) {
|
|
26
30
|
const found = (await registered(options.file)).service;
|
|
27
31
|
if (found?.state !== "ready")
|
|
@@ -52,7 +56,7 @@ async function ensure(options = {}) {
|
|
|
52
56
|
if (command === undefined)
|
|
53
57
|
throw new Error("Missing service command");
|
|
54
58
|
try {
|
|
55
|
-
return spawnServiceContender2(command, args, await environment2(options.file ??
|
|
59
|
+
return spawnServiceContender2(command, args, await environment2(options.file ?? fallback2(), options.env));
|
|
56
60
|
} catch (cause) {
|
|
57
61
|
throw new Error("Failed to start server", { cause });
|
|
58
62
|
}
|
|
@@ -65,12 +69,12 @@ async function ensure(options = {}) {
|
|
|
65
69
|
if (registration.timedOut && registration.info !== undefined) {
|
|
66
70
|
timeouts = {
|
|
67
71
|
info: registration.info,
|
|
68
|
-
count: timeouts !== undefined &&
|
|
72
|
+
count: timeouts !== undefined && same2(timeouts.info, registration.info) ? timeouts.count + 1 : 1
|
|
69
73
|
};
|
|
70
74
|
if (timeouts.count >= 3) {
|
|
71
75
|
announce("missing");
|
|
72
76
|
console.warn("Background service is unresponsive; recovery cannot preserve persistent terminals");
|
|
73
|
-
await clear2(options.file ??
|
|
77
|
+
await clear2(options.file ?? fallback2());
|
|
74
78
|
await terminate(registration.info, options, timing);
|
|
75
79
|
for (const item of contenders) {
|
|
76
80
|
if (item.child.pid === registration.info.pid || contenderFinished2(item)) {
|
|
@@ -92,7 +96,7 @@ async function ensure(options = {}) {
|
|
|
92
96
|
if (!service2.compatible && versionMatches)
|
|
93
97
|
throw new Error("Background service uses an incompatible health protocol. Update this client or explicitly restart the service.");
|
|
94
98
|
if (compatible && service2.state === "ready") {
|
|
95
|
-
await complete2(options.file ??
|
|
99
|
+
await complete2(options.file ?? fallback2(), service2.info);
|
|
96
100
|
return service2.endpoint;
|
|
97
101
|
}
|
|
98
102
|
if (compatible && service2.state === "failed")
|
|
@@ -141,22 +145,12 @@ async function ensure(options = {}) {
|
|
|
141
145
|
}
|
|
142
146
|
async function stop(options = {}) {
|
|
143
147
|
const info = await read(options.file);
|
|
144
|
-
await (options.pty === "handoff" && info !== undefined ? prepare2(options.file ??
|
|
148
|
+
await (options.pty === "handoff" && info !== undefined ? prepare2(options.file ?? fallback2(), info, defaultEnsureTiming2.requestTimeout) : clear2(options.file ?? fallback2())).catch((cause) => console.warn("Failed to prepare persistent terminals for replacement", cause));
|
|
145
149
|
if (info !== undefined)
|
|
146
150
|
await terminate(info, options, ensureTiming2(options));
|
|
147
151
|
}
|
|
148
|
-
function fallback() {
|
|
149
|
-
return join(process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state"), "opencode", "service.json");
|
|
150
|
-
}
|
|
151
|
-
function headers(endpoint) {
|
|
152
|
-
if (endpoint.auth === undefined)
|
|
153
|
-
return;
|
|
154
|
-
return {
|
|
155
|
-
authorization: "Basic " + Buffer.from(endpoint.auth.username + ":" + endpoint.auth.password).toString("base64")
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
152
|
async function read(file) {
|
|
159
|
-
const text = await readFile(file ??
|
|
153
|
+
const text = await readFile(file ?? fallback2(), "utf8").catch(() => {
|
|
160
154
|
return;
|
|
161
155
|
});
|
|
162
156
|
if (text === undefined)
|
|
@@ -167,63 +161,11 @@ async function read(file) {
|
|
|
167
161
|
return;
|
|
168
162
|
}
|
|
169
163
|
}
|
|
170
|
-
async function probeResult(info, timeout = defaultEnsureTiming2.requestTimeout) {
|
|
171
|
-
const endpoint = {
|
|
172
|
-
url: info.url,
|
|
173
|
-
auth: info.password === undefined ? undefined : { type: "basic", username: "opencode", password: info.password }
|
|
174
|
-
};
|
|
175
|
-
const signal = AbortSignal.timeout(timeout);
|
|
176
|
-
const result = await fetch(new URL("/api/info", info.url), { headers: headers(endpoint), signal }).then(async (response) => ({
|
|
177
|
-
response,
|
|
178
|
-
body: response.status === 404 ? undefined : await response.json()
|
|
179
|
-
})).then((value) => ({ value }), (cause) => ({ cause }));
|
|
180
|
-
if ("cause" in result)
|
|
181
|
-
return { service: undefined, timedOut: signal.aborted };
|
|
182
|
-
const response = result.value.response;
|
|
183
|
-
if (response.status === 404)
|
|
184
|
-
return {
|
|
185
|
-
service: {
|
|
186
|
-
info,
|
|
187
|
-
endpoint,
|
|
188
|
-
version: info.version,
|
|
189
|
-
state: "ready",
|
|
190
|
-
compatible: false
|
|
191
|
-
},
|
|
192
|
-
timedOut: false
|
|
193
|
-
};
|
|
194
|
-
const serverInfo = decodeInfo(result.value.body);
|
|
195
|
-
if (serverInfo !== undefined) {
|
|
196
|
-
if (serverInfo.pid !== info.pid)
|
|
197
|
-
return { service: undefined, timedOut: false };
|
|
198
|
-
if (info.version !== undefined && serverInfo.version !== info.version)
|
|
199
|
-
return { service: undefined, timedOut: false };
|
|
200
|
-
return {
|
|
201
|
-
service: {
|
|
202
|
-
info,
|
|
203
|
-
endpoint,
|
|
204
|
-
version: serverInfo.version,
|
|
205
|
-
state: response.ok ? "ready" : response.status === 500 ? "failed" : "waiting",
|
|
206
|
-
compatible: true
|
|
207
|
-
},
|
|
208
|
-
timedOut: false
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
return { service: undefined, timedOut: false };
|
|
212
|
-
}
|
|
213
|
-
function decodeInfo(input) {
|
|
214
|
-
if (typeof input !== "object" || input === null)
|
|
215
|
-
return;
|
|
216
|
-
if (!("version" in input) || typeof input.version !== "string")
|
|
217
|
-
return;
|
|
218
|
-
if (!("pid" in input) || typeof input.pid !== "number" || !Number.isInteger(input.pid) || input.pid < 0)
|
|
219
|
-
return;
|
|
220
|
-
return { version: input.version, pid: input.pid };
|
|
221
|
-
}
|
|
222
164
|
async function registered(file, timeout) {
|
|
223
165
|
const info = await read(file);
|
|
224
166
|
if (info === undefined)
|
|
225
167
|
return { info: undefined, service: undefined, timedOut: false };
|
|
226
|
-
return { info, ...await
|
|
168
|
+
return { info, ...await probeResult2(info, timeout) };
|
|
227
169
|
}
|
|
228
170
|
function signal(pid, name) {
|
|
229
171
|
try {
|
|
@@ -247,12 +189,9 @@ async function waitUntilStopped(pid, timing) {
|
|
|
247
189
|
}
|
|
248
190
|
return false;
|
|
249
191
|
}
|
|
250
|
-
function same(left, right) {
|
|
251
|
-
return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid;
|
|
252
|
-
}
|
|
253
192
|
async function terminate(info, options, timing) {
|
|
254
193
|
const current = await read(options.file);
|
|
255
|
-
if (current === undefined || !
|
|
194
|
+
if (current === undefined || !same2(current, info))
|
|
256
195
|
return;
|
|
257
196
|
signal(info.pid, "SIGTERM");
|
|
258
197
|
if (!await waitUntilStopped(info.pid, timing)) {
|
|
@@ -261,18 +200,18 @@ async function terminate(info, options, timing) {
|
|
|
261
200
|
throw new Error(`Server process ${info.pid} is still running`);
|
|
262
201
|
}
|
|
263
202
|
const latest = await read(options.file);
|
|
264
|
-
if (latest === undefined || !
|
|
203
|
+
if (latest === undefined || !same2(latest, info))
|
|
265
204
|
return;
|
|
266
|
-
await rm(options.file ??
|
|
205
|
+
await rm(options.file ?? fallback2(), { force: true });
|
|
267
206
|
}
|
|
268
207
|
function delay(milliseconds) {
|
|
269
208
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
270
209
|
}
|
|
271
|
-
var Service = { discover, ensure, stop, headers };
|
|
210
|
+
var Service = { discover, ensure, stop, headers: headers2 };
|
|
272
211
|
export {
|
|
273
212
|
Service,
|
|
274
213
|
discover,
|
|
275
214
|
ensure,
|
|
276
|
-
headers,
|
|
215
|
+
headers2 as headers,
|
|
277
216
|
stop
|
|
278
217
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Endpoint, Info } from "./service.js";
|
|
2
|
+
export type LocalService = {
|
|
3
|
+
readonly info: Info;
|
|
4
|
+
readonly endpoint: Endpoint;
|
|
5
|
+
readonly version?: string;
|
|
6
|
+
readonly state: "ready" | "waiting" | "failed";
|
|
7
|
+
readonly compatible: boolean;
|
|
8
|
+
};
|
|
9
|
+
/** The default registration file. */
|
|
10
|
+
export declare function fallback(): string;
|
|
11
|
+
/** Whether two registrations describe the same service instance. */
|
|
12
|
+
export declare function same(left: Info, right: Info): boolean;
|
|
13
|
+
/** Ask a registered owner for its health and classify the answer. */
|
|
14
|
+
export declare function probeResult(info: Info, timeout?: number): Promise<{
|
|
15
|
+
service: undefined;
|
|
16
|
+
timedOut: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
service: {
|
|
19
|
+
info: Info;
|
|
20
|
+
endpoint: {
|
|
21
|
+
url: string;
|
|
22
|
+
auth: {
|
|
23
|
+
type: "basic";
|
|
24
|
+
username: string;
|
|
25
|
+
password: string;
|
|
26
|
+
} | undefined;
|
|
27
|
+
};
|
|
28
|
+
version: string | undefined;
|
|
29
|
+
state: "ready";
|
|
30
|
+
compatible: false;
|
|
31
|
+
};
|
|
32
|
+
timedOut: boolean;
|
|
33
|
+
} | {
|
|
34
|
+
service: {
|
|
35
|
+
info: Info;
|
|
36
|
+
endpoint: {
|
|
37
|
+
url: string;
|
|
38
|
+
auth: {
|
|
39
|
+
type: "basic";
|
|
40
|
+
username: string;
|
|
41
|
+
password: string;
|
|
42
|
+
} | undefined;
|
|
43
|
+
};
|
|
44
|
+
version: string;
|
|
45
|
+
state: "failed" | "ready" | "waiting";
|
|
46
|
+
compatible: true;
|
|
47
|
+
};
|
|
48
|
+
timedOut: boolean;
|
|
49
|
+
}>;
|
|
50
|
+
/** Create HTTP authentication headers for a service endpoint. */
|
|
51
|
+
export declare function headers(endpoint: Endpoint): {
|
|
52
|
+
authorization: string;
|
|
53
|
+
} | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@opencode/client",
|
|
4
|
-
"version": "0.0.0-dev-
|
|
4
|
+
"version": "0.0.0-dev-20560",
|
|
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/schema": "0.0.0-dev-
|
|
61
|
-
"@opencode/protocol": "0.0.0-dev-
|
|
60
|
+
"@opencode/schema": "0.0.0-dev-20560",
|
|
61
|
+
"@opencode/protocol": "0.0.0-dev-20560"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"effect": "4.0.0-rc.112",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
77
|
-
"@opencode/httpapi-codegen": "0.0.0-dev-
|
|
77
|
+
"@opencode/httpapi-codegen": "0.0.0-dev-20560",
|
|
78
78
|
"@tsconfig/bun": "1.0.9",
|
|
79
79
|
"@types/bun": "1.4.0",
|
|
80
80
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|