@opencode-ai/client 0.0.0-next-15769 → 0.0.0-next-15778

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.
@@ -12,6 +12,20 @@ export declare const discover: (options?: DiscoverOptions | undefined) => Effect
12
12
  password: string;
13
13
  } | undefined;
14
14
  } | undefined, never, FileSystem.FileSystem>;
15
+ /** Recognize an authenticated compatible service bound to an expected URL, including while it starts or fails. */
16
+ export declare const incumbent: (options: DiscoverOptions & {
17
+ readonly url: string;
18
+ }) => Effect.Effect<{
19
+ endpoint: {
20
+ url: string;
21
+ auth: {
22
+ type: "basic";
23
+ username: string;
24
+ password: string;
25
+ } | undefined;
26
+ };
27
+ state: "failed" | "ready" | "waiting";
28
+ } | undefined, never, FileSystem.FileSystem>;
15
29
  /** Ensure a healthy, compatible local service is running. */
16
30
  export declare const ensure: (options?: EnsureOptions | undefined) => Effect.Effect<Endpoint, Error, FileSystem.FileSystem>;
17
31
  /** Stop the registered local service. */
@@ -27,6 +41,7 @@ export declare const Info: Schema.Struct<{
27
41
  readonly url: Schema.String;
28
42
  readonly pid: Schema.Int;
29
43
  readonly password: Schema.optional<Schema.String>;
44
+ readonly startedAt: Schema.optional<Schema.Int>;
30
45
  }>;
31
46
  /** Effect-based local service lifecycle operations. */
32
47
  export declare const Service: {
@@ -38,6 +53,19 @@ export declare const Service: {
38
53
  password: string;
39
54
  } | undefined;
40
55
  } | undefined, never, FileSystem.FileSystem>;
56
+ incumbent: (options: DiscoverOptions & {
57
+ readonly url: string;
58
+ }) => Effect.Effect<{
59
+ endpoint: {
60
+ url: string;
61
+ auth: {
62
+ type: "basic";
63
+ username: string;
64
+ password: string;
65
+ } | undefined;
66
+ };
67
+ state: "failed" | "ready" | "waiting";
68
+ } | undefined, never, FileSystem.FileSystem>;
41
69
  ensure: (options?: EnsureOptions | undefined) => Effect.Effect<Endpoint, Error, FileSystem.FileSystem>;
42
70
  stop: (options?: StopOptions | undefined) => Effect.Effect<void, Error, FileSystem.FileSystem>;
43
71
  headers: typeof headers;
@@ -47,5 +75,6 @@ export declare const Service: {
47
75
  readonly url: Schema.String;
48
76
  readonly pid: Schema.Int;
49
77
  readonly password: Schema.optional<Schema.String>;
78
+ readonly startedAt: Schema.optional<Schema.Int>;
50
79
  }>;
51
80
  };
@@ -10,6 +10,16 @@ export * from "../service.js";
10
10
  export const discover = Effect.fn("service.discover")(function* (options = {}) {
11
11
  return (yield* discoverLocal(options))?.endpoint;
12
12
  });
13
+ /** Recognize an authenticated compatible service bound to an expected URL, including while it starts or fails. */
14
+ export const incumbent = Effect.fn("service.incumbent")(function* (options) {
15
+ const info = yield* read(options.file);
16
+ const found = info === undefined ? undefined : yield* probe({ ...info, url: options.url });
17
+ if (found === undefined || found.legacy)
18
+ return undefined;
19
+ if (options.version !== undefined && found.version !== options.version)
20
+ return undefined;
21
+ return { endpoint: found.endpoint, state: found.state };
22
+ });
13
23
  const discoverLocal = Effect.fnUntraced(function* (options) {
14
24
  const found = (yield* registered(options.file)).service;
15
25
  if (found?.state !== "ready")
@@ -88,8 +98,13 @@ export const ensure = Effect.fn("service.ensure")(function* (options = {}) {
88
98
  lastSpawn = Date.now();
89
99
  }
90
100
  return Option.none();
91
- }).pipe(Effect.repeat({ until: Option.isSome, schedule: Schedule.spaced("1 second") }));
92
- return Option.getOrThrow(found).endpoint;
101
+ }).pipe(Effect.repeat({
102
+ until: Option.isSome,
103
+ schedule: Schedule.max([Schedule.spaced("1 second"), Schedule.recurs(120)]),
104
+ }));
105
+ if (Option.isNone(found))
106
+ return yield* Effect.fail(new Error("Timed out waiting for the background service to start"));
107
+ return found.value.endpoint;
93
108
  });
94
109
  function contenderFailure(contender) {
95
110
  const error = contender.error();
@@ -127,6 +142,7 @@ export const Info = Schema.Struct({
127
142
  url: Schema.String,
128
143
  pid: Schema.Int.check(Schema.isGreaterThan(0)),
129
144
  password: Schema.optional(Schema.String),
145
+ startedAt: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))),
130
146
  });
131
147
  const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Info));
132
148
  const decodeHealth = Schema.decodeUnknownOption(ServiceStatus.Health);
@@ -238,4 +254,4 @@ const requestStop = Effect.fnUntraced(function* (service) {
238
254
  return "accepted";
239
255
  });
240
256
  /** Effect-based local service lifecycle operations. */
241
- export const Service = { discover, ensure, stop, headers, Info };
257
+ export const Service = { discover, incumbent, ensure, stop, headers, Info };
@@ -17,6 +17,7 @@ async function discoverLocal(options) {
17
17
  }
18
18
  /** Ensure a healthy, compatible local service is running. */
19
19
  export async function ensure(options = {}) {
20
+ const deadline = Date.now() + 120_000;
20
21
  const contenders = new Set();
21
22
  let announced = false;
22
23
  let lastSpawn = 0;
@@ -46,6 +47,8 @@ export async function ensure(options = {}) {
46
47
  }
47
48
  };
48
49
  while (true) {
50
+ if (Date.now() >= deadline)
51
+ throw new Error("Timed out waiting for the background service to start");
49
52
  const registration = await registered(options.file, true);
50
53
  if (registration.service !== undefined) {
51
54
  ownerHeld = false;
@@ -110,7 +113,9 @@ function fallback() {
110
113
  export function headers(endpoint) {
111
114
  if (endpoint.auth === undefined)
112
115
  return undefined;
113
- return { authorization: "Basic " + Buffer.from(endpoint.auth.username + ":" + endpoint.auth.password).toString("base64") };
116
+ return {
117
+ authorization: "Basic " + Buffer.from(endpoint.auth.username + ":" + endpoint.auth.password).toString("base64"),
118
+ };
114
119
  }
115
120
  async function read(file) {
116
121
  const text = await readFile(file ?? fallback(), "utf8").catch(() => undefined);
package/dist/service.d.ts CHANGED
@@ -45,4 +45,6 @@ export type Info = {
45
45
  readonly pid: number;
46
46
  /** Private service password, when authentication is enabled. */
47
47
  readonly password?: string;
48
+ /** Registration generation used to resolve owner write races. */
49
+ readonly startedAt?: number;
48
50
  };
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-15769",
4
+ "version": "0.0.0-next-15778",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -53,8 +53,8 @@
53
53
  "typecheck": "tsgo --noEmit"
54
54
  },
55
55
  "dependencies": {
56
- "@opencode-ai/schema": "0.0.0-next-15769",
57
- "@opencode-ai/protocol": "0.0.0-next-15769"
56
+ "@opencode-ai/schema": "0.0.0-next-15778",
57
+ "@opencode-ai/protocol": "0.0.0-next-15778"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "effect": "4.0.0-beta.98"
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "devDependencies": {
68
68
  "@effect/platform-node": "4.0.0-beta.98",
69
- "@opencode-ai/httpapi-codegen": "0.0.0-next-15769",
69
+ "@opencode-ai/httpapi-codegen": "0.0.0-next-15778",
70
70
  "@tsconfig/bun": "1.0.9",
71
71
  "@types/bun": "1.3.13",
72
72
  "@typescript/native-preview": "7.0.0-dev.20251207.1",