@opencode/client 0.0.0-dev-19564 → 0.0.0-dev-19566

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.
@@ -1710,7 +1710,7 @@ export type IntegrationGetInput = {
1710
1710
  };
1711
1711
  export type IntegrationGetOutput = {
1712
1712
  readonly location: Location.PublicRef;
1713
- readonly data: Integration.Info | undefined;
1713
+ readonly data: Integration.Info;
1714
1714
  };
1715
1715
  export type IntegrationGetOperation<E = never> = (input: IntegrationGetInput) => Effect.Effect<IntegrationGetOutput, E>;
1716
1716
  export type IntegrationWellknownAddInput = {
@@ -2299,7 +2299,7 @@ export declare const make: (options?: {
2299
2299
  };
2300
2300
  integration: {
2301
2301
  list: (input?: import("./api.js").IntegrationListInput) => Effect.Effect<import("./api.js").IntegrationListOutput, import("@opencode/protocol/errors").InvalidRequestError | import("@opencode/protocol/errors").UnauthorizedError | import("effect/Schema").SchemaError | ClientError | import("effect/unstable/http/HttpClientError").HttpClientError, never>;
2302
- get: (input: import("./api.js").IntegrationGetInput) => Effect.Effect<import("./api.js").IntegrationGetOutput, import("@opencode/protocol/errors").InvalidRequestError | import("@opencode/protocol/errors").UnauthorizedError | import("effect/Schema").SchemaError | ClientError | import("effect/unstable/http/HttpClientError").HttpClientError, never>;
2302
+ get: (input: import("./api.js").IntegrationGetInput) => Effect.Effect<import("./api.js").IntegrationGetOutput, import("@opencode/protocol/errors").InvalidRequestError | import("@opencode/protocol/errors").UnauthorizedError | import("@opencode/protocol/errors").IntegrationNotFoundError | import("effect/Schema").SchemaError | ClientError | import("effect/unstable/http/HttpClientError").HttpClientError, never>;
2303
2303
  wellknown: {
2304
2304
  add: (input: import("./api.js").IntegrationWellknownAddInput) => Effect.Effect<void, import("@opencode/protocol/errors").InvalidRequestError | import("@opencode/protocol/errors").UnauthorizedError | import("effect/Schema").SchemaError | ClientError | import("effect/unstable/http/HttpClientError").HttpClientError, never>;
2305
2305
  };
@@ -86,7 +86,7 @@ export declare const make: (options?: {
86
86
  };
87
87
  integration: {
88
88
  list: (input?: IntegrationListInput) => Effect.Effect<IntegrationListOutput, import("@opencode/protocol/errors").InvalidRequestError | import("@opencode/protocol/errors").UnauthorizedError | Schema.SchemaError | ClientError | HttpClientError.HttpClientError, never>;
89
- get: (input: IntegrationGetInput) => Effect.Effect<IntegrationGetOutput, import("@opencode/protocol/errors").InvalidRequestError | import("@opencode/protocol/errors").UnauthorizedError | Schema.SchemaError | ClientError | HttpClientError.HttpClientError, never>;
89
+ get: (input: IntegrationGetInput) => Effect.Effect<IntegrationGetOutput, import("@opencode/protocol/errors").InvalidRequestError | import("@opencode/protocol/errors").UnauthorizedError | import("@opencode/protocol/errors").IntegrationNotFoundError | Schema.SchemaError | ClientError | HttpClientError.HttpClientError, never>;
90
90
  wellknown: {
91
91
  add: (input: IntegrationWellknownAddInput) => Effect.Effect<void, import("@opencode/protocol/errors").InvalidRequestError | import("@opencode/protocol/errors").UnauthorizedError | Schema.SchemaError | ClientError | HttpClientError.HttpClientError, never>;
92
92
  };
@@ -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(ServerStatus);
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) => ({ response, body: (await response.json()) }))
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
  };
@@ -611,7 +611,7 @@ export function make(options) {
611
611
  path: `/api/integration/${encodeURIComponent(input.integrationID)}`,
612
612
  query: { location: input["location"] },
613
613
  successStatus: 200,
614
- declaredStatuses: [400, 401],
614
+ declaredStatuses: [400, 401, 404],
615
615
  empty: false,
616
616
  }, requestOptions),
617
617
  wellknown: {
@@ -3301,6 +3301,12 @@ export type ProviderNotFoundError = {
3301
3301
  readonly message: string;
3302
3302
  };
3303
3303
  export declare const isProviderNotFoundError: (value: unknown) => value is ProviderNotFoundError;
3304
+ export type IntegrationNotFoundError = {
3305
+ readonly _tag: "IntegrationNotFoundError";
3306
+ readonly integrationID: string;
3307
+ readonly message: string;
3308
+ };
3309
+ export declare const isIntegrationNotFoundError: (value: unknown) => value is IntegrationNotFoundError;
3304
3310
  export type McpServerNotFoundError = {
3305
3311
  readonly _tag: "McpServerNotFoundError";
3306
3312
  readonly server: string;
@@ -6158,7 +6164,7 @@ export type IntegrationGetInput = {
6158
6164
  };
6159
6165
  export type IntegrationGetOutput = {
6160
6166
  location: LocationPublicRef;
6161
- data: IntegrationInfo | null;
6167
+ data: IntegrationInfo;
6162
6168
  };
6163
6169
  export type IntegrationWellknownAddInput = {
6164
6170
  readonly location?: {
@@ -16,6 +16,7 @@ export const isInstructionEntryValueTooLargeError = (value) => typeof value ===
16
16
  "_tag" in value &&
17
17
  value["_tag"] === "InstructionEntryValueTooLargeError";
18
18
  export const isProviderNotFoundError = (value) => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ProviderNotFoundError";
19
+ export const isIntegrationNotFoundError = (value) => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "IntegrationNotFoundError";
19
20
  export const isMcpServerNotFoundError = (value) => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "McpServerNotFoundError";
20
21
  export const isProjectNotFoundError = (value) => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ProjectNotFoundError";
21
22
  export const isFormNotFoundError = (value) => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "FormNotFoundError";
@@ -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
- const body = result.value.body;
166
- if (body !== undefined && "version" in body && "pid" in body) {
167
- if (body.pid !== info.pid)
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 && body.version !== info.version)
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: body.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-19564",
4
+ "version": "0.0.0-dev-19566",
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-19564",
61
- "@opencode/protocol": "0.0.0-dev-19564"
60
+ "@opencode/schema": "0.0.0-dev-19566",
61
+ "@opencode/protocol": "0.0.0-dev-19566"
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-19564",
77
+ "@opencode/httpapi-codegen": "0.0.0-dev-19566",
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",