@opencode/client 0.0.0-dev-19564 → 0.0.0-dev-19565
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/service.js +27 -4
- package/dist/promise/service.js +32 -7
- package/package.json +4 -4
package/dist/effect/service.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ServerStatus } from "@opencode/protocol/groups/server";
|
|
2
1
|
import { Effect, FileSystem, Option, Schedule, Schema } from "effect";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
3
|
import { join } from "node:path";
|
|
@@ -21,6 +20,8 @@ export const discover = Effect.fn("service.discover")(function* (options = {}) {
|
|
|
21
20
|
const found = (yield* registered(options.file)).service;
|
|
22
21
|
if (found?.state !== "ready")
|
|
23
22
|
return undefined;
|
|
23
|
+
if (!found.compatible)
|
|
24
|
+
return undefined;
|
|
24
25
|
if (!matchesVersion(found.version, options))
|
|
25
26
|
return undefined;
|
|
26
27
|
return found.endpoint;
|
|
@@ -31,6 +32,8 @@ export const incumbent = Effect.fn("service.incumbent")(function* (options) {
|
|
|
31
32
|
const found = info === undefined ? undefined : yield* probe({ ...info, url: options.url });
|
|
32
33
|
if (found === undefined)
|
|
33
34
|
return undefined;
|
|
35
|
+
if (!found.compatible)
|
|
36
|
+
return undefined;
|
|
34
37
|
if (!matchesVersion(found.version, options))
|
|
35
38
|
return undefined;
|
|
36
39
|
return { endpoint: found.endpoint, state: found.state };
|
|
@@ -86,7 +89,7 @@ export const ensure = Effect.fn("service.ensure")(function* (options = {}) {
|
|
|
86
89
|
timeouts = undefined;
|
|
87
90
|
if (service !== undefined) {
|
|
88
91
|
spawnDelay = timing.spawnDelay;
|
|
89
|
-
const compatible = matchesVersion(service.version, options);
|
|
92
|
+
const compatible = service.compatible && matchesVersion(service.version, options);
|
|
90
93
|
if (compatible && service.state === "ready") {
|
|
91
94
|
yield* Effect.tryPromise(() => PtyHandoff.complete(options.file ?? fallback(), service.info));
|
|
92
95
|
return Option.some(service);
|
|
@@ -159,7 +162,10 @@ export const Info = Schema.Struct({
|
|
|
159
162
|
password: Schema.optional(Schema.String),
|
|
160
163
|
});
|
|
161
164
|
const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Info));
|
|
162
|
-
const decodeStatus = Schema.decodeUnknownOption(
|
|
165
|
+
const decodeStatus = Schema.decodeUnknownOption(Schema.Struct({
|
|
166
|
+
version: Schema.String,
|
|
167
|
+
pid: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
168
|
+
}));
|
|
163
169
|
// A missing or corrupt file means no valid info; callers treat both
|
|
164
170
|
// the same (the registering server self-evicts, clients rediscover).
|
|
165
171
|
const read = Effect.fnUntraced(function* (file) {
|
|
@@ -181,11 +187,27 @@ const probeResult = Effect.fnUntraced(function* (info, timeout = defaultEnsureTi
|
|
|
181
187
|
};
|
|
182
188
|
const signal = AbortSignal.timeout(timeout);
|
|
183
189
|
const result = yield* Effect.promise(() => fetch(new URL("/api/status", info.url), { headers: headers(endpoint), signal })
|
|
184
|
-
.then(async (response) => ({
|
|
190
|
+
.then(async (response) => ({
|
|
191
|
+
response,
|
|
192
|
+
body: response.status === 404 ? undefined : (await response.json()),
|
|
193
|
+
}))
|
|
185
194
|
.then((value) => ({ value }), (cause) => ({ cause })));
|
|
186
195
|
if ("cause" in result)
|
|
187
196
|
return { service: undefined, timedOut: signal.aborted };
|
|
188
197
|
const response = result.value.response;
|
|
198
|
+
// The previous V2 service exposes /api/health instead. Its authenticated 404 is enough
|
|
199
|
+
// to recognize the registered daemon as incompatible and route it through replacement.
|
|
200
|
+
if (response.status === 404)
|
|
201
|
+
return {
|
|
202
|
+
service: {
|
|
203
|
+
info,
|
|
204
|
+
endpoint,
|
|
205
|
+
version: info.version,
|
|
206
|
+
state: "ready",
|
|
207
|
+
compatible: false,
|
|
208
|
+
},
|
|
209
|
+
timedOut: false,
|
|
210
|
+
};
|
|
189
211
|
const body = result.value.body;
|
|
190
212
|
const status = decodeStatus(body);
|
|
191
213
|
if (Option.isSome(status)) {
|
|
@@ -199,6 +221,7 @@ const probeResult = Effect.fnUntraced(function* (info, timeout = defaultEnsureTi
|
|
|
199
221
|
endpoint,
|
|
200
222
|
version: status.value.version,
|
|
201
223
|
state: response.ok ? "ready" : response.status === 500 ? "failed" : "waiting",
|
|
224
|
+
compatible: true,
|
|
202
225
|
},
|
|
203
226
|
timedOut: false,
|
|
204
227
|
};
|
package/dist/promise/service.js
CHANGED
|
@@ -16,6 +16,8 @@ export async function discover(options = {}) {
|
|
|
16
16
|
const found = (await registered(options.file)).service;
|
|
17
17
|
if (found?.state !== "ready")
|
|
18
18
|
return undefined;
|
|
19
|
+
if (!found.compatible)
|
|
20
|
+
return undefined;
|
|
19
21
|
if (!matchesVersion(found.version, options))
|
|
20
22
|
return undefined;
|
|
21
23
|
return found.endpoint;
|
|
@@ -70,7 +72,7 @@ export async function ensure(options = {}) {
|
|
|
70
72
|
if (registration.service !== undefined) {
|
|
71
73
|
spawnDelay = timing.spawnDelay;
|
|
72
74
|
const service = registration.service;
|
|
73
|
-
const compatible = matchesVersion(service.version, options);
|
|
75
|
+
const compatible = service.compatible && matchesVersion(service.version, options);
|
|
74
76
|
if (compatible && service.state === "ready") {
|
|
75
77
|
await PtyHandoff.complete(options.file ?? fallback(), service.info);
|
|
76
78
|
return service.endpoint;
|
|
@@ -156,30 +158,53 @@ async function probeResult(info, timeout = defaultEnsureTiming.requestTimeout) {
|
|
|
156
158
|
const result = await fetch(new URL("/api/status", info.url), { headers: headers(endpoint), signal })
|
|
157
159
|
.then(async (response) => ({
|
|
158
160
|
response,
|
|
159
|
-
body: (await response.json()),
|
|
161
|
+
body: response.status === 404 ? undefined : (await response.json()),
|
|
160
162
|
}))
|
|
161
163
|
.then((value) => ({ value }), (cause) => ({ cause }));
|
|
162
164
|
if ("cause" in result)
|
|
163
165
|
return { service: undefined, timedOut: signal.aborted };
|
|
164
166
|
const response = result.value.response;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
// The previous V2 service exposes /api/health instead. Its authenticated 404 is enough
|
|
168
|
+
// to recognize the registered daemon as incompatible and route it through replacement.
|
|
169
|
+
if (response.status === 404)
|
|
170
|
+
return {
|
|
171
|
+
service: {
|
|
172
|
+
info,
|
|
173
|
+
endpoint,
|
|
174
|
+
version: info.version,
|
|
175
|
+
state: "ready",
|
|
176
|
+
compatible: false,
|
|
177
|
+
},
|
|
178
|
+
timedOut: false,
|
|
179
|
+
};
|
|
180
|
+
const status = decodeStatus(result.value.body);
|
|
181
|
+
if (status !== undefined) {
|
|
182
|
+
if (status.pid !== info.pid)
|
|
168
183
|
return { service: undefined, timedOut: false };
|
|
169
|
-
if (info.version !== undefined &&
|
|
184
|
+
if (info.version !== undefined && status.version !== info.version)
|
|
170
185
|
return { service: undefined, timedOut: false };
|
|
171
186
|
return {
|
|
172
187
|
service: {
|
|
173
188
|
info,
|
|
174
189
|
endpoint,
|
|
175
|
-
version:
|
|
190
|
+
version: status.version,
|
|
176
191
|
state: response.ok ? "ready" : response.status === 500 ? "failed" : "waiting",
|
|
192
|
+
compatible: true,
|
|
177
193
|
},
|
|
178
194
|
timedOut: false,
|
|
179
195
|
};
|
|
180
196
|
}
|
|
181
197
|
return { service: undefined, timedOut: false };
|
|
182
198
|
}
|
|
199
|
+
function decodeStatus(input) {
|
|
200
|
+
if (typeof input !== "object" || input === null)
|
|
201
|
+
return;
|
|
202
|
+
if (!("version" in input) || typeof input.version !== "string")
|
|
203
|
+
return;
|
|
204
|
+
if (!("pid" in input) || typeof input.pid !== "number" || !Number.isInteger(input.pid) || input.pid < 0)
|
|
205
|
+
return;
|
|
206
|
+
return { version: input.version, pid: input.pid };
|
|
207
|
+
}
|
|
183
208
|
async function registered(file, timeout) {
|
|
184
209
|
const info = await read(file);
|
|
185
210
|
if (info === 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-19565",
|
|
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-19565",
|
|
61
|
+
"@opencode/protocol": "0.0.0-dev-19565"
|
|
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-19565",
|
|
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",
|