@opencode-ai/client
Advanced tools
@@ -8,2 +8,5 @@ import { Effect, FileSystem, Option, Schedule, Schema } from "effect"; | ||
| export const discover = Effect.fn("service.discover")(function* (options = {}) { | ||
| return (yield* discoverLocal(options))?.transport; | ||
| }); | ||
| const discoverLocal = Effect.fnUntraced(function* (options) { | ||
| const info = yield* read(options.file); | ||
@@ -14,4 +17,3 @@ if (info === undefined) | ||
| return undefined; | ||
| const found = yield* probe(info); | ||
| return found?.transport; | ||
| return yield* probe(info, options.version); | ||
| }); | ||
@@ -30,9 +32,15 @@ // Idempotent ensure-running: reuses a healthy compatible server, replaces a | ||
| return yield* Effect.fail(new Error("Missing service command")); | ||
| yield* Effect.try({ | ||
| const child = yield* Effect.try({ | ||
| try: () => { | ||
| spawn(command, args, { detached: true, stdio: "ignore" }).unref(); | ||
| const child = spawn(command, args, { detached: true, stdio: "ignore" }); | ||
| child.unref(); | ||
| return child; | ||
| }, | ||
| catch: (cause) => new Error("Failed to start server", { cause }), | ||
| }); | ||
| return yield* discover(options).pipe(Effect.flatMap((found) => found === undefined ? Effect.fail(new Error("Server is not ready")) : Effect.succeed(found)), Effect.retry(poll), Effect.mapError(() => new Error("Failed to start server"))); | ||
| 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 | ||
| ? Effect.void | ||
| : Effect.sync(() => { | ||
| child.kill("SIGTERM"); | ||
| })), 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"))); | ||
| }); | ||
@@ -61,2 +69,3 @@ export const stop = Effect.fn("service.stop")(function* (options = {}) { | ||
| const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Info)); | ||
| const decodeHealth = Schema.decodeUnknownEffect(Schema.Struct({ healthy: Schema.Literal(true), version: Schema.String, pid: Schema.Int })); | ||
| // A missing or corrupt file means no valid info; callers treat both | ||
@@ -71,10 +80,17 @@ // the same (the registering server self-evicts, clients rediscover). | ||
| }); | ||
| const probe = Effect.fnUntraced(function* (info) { | ||
| const probe = Effect.fnUntraced(function* (info, version) { | ||
| const headers = info.password === undefined ? undefined : auth(info.password); | ||
| const healthy = yield* Effect.tryPromise(() => fetch(new URL("/api/health", info.url), { | ||
| const response = yield* Effect.tryPromise(() => fetch(new URL("/api/health", info.url), { | ||
| headers, | ||
| signal: AbortSignal.timeout(2_000), | ||
| })).pipe(Effect.map((response) => response.ok), Effect.orElseSucceed(() => false)); | ||
| if (!healthy) | ||
| })).pipe(Effect.option, Effect.map(Option.getOrUndefined)); | ||
| if (response === undefined || !response.ok) | ||
| return undefined; | ||
| const health = yield* Effect.tryPromise(() => response.json()).pipe(Effect.flatMap(decodeHealth), Effect.option, Effect.map(Option.getOrUndefined)); | ||
| if (health?.pid !== info.pid) | ||
| return undefined; | ||
| if (info.version !== undefined && health.version !== info.version) | ||
| return undefined; | ||
| if (version !== undefined && health.version !== version) | ||
| return undefined; | ||
| return { info, transport: { url: info.url, headers } }; | ||
@@ -81,0 +97,0 @@ }); |
+4
-4
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "name": "@opencode-ai/client", | ||
| "version": "0.0.0-next-14962", | ||
| "version": "0.0.0-next-14971", | ||
| "type": "module", | ||
@@ -44,4 +44,4 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@opencode-ai/schema": "0.0.0-next-14962", | ||
| "@opencode-ai/protocol": "0.0.0-next-14962" | ||
| "@opencode-ai/schema": "0.0.0-next-14971", | ||
| "@opencode-ai/protocol": "0.0.0-next-14971" | ||
| }, | ||
@@ -58,3 +58,3 @@ "peerDependencies": { | ||
| "@effect/platform-node": "4.0.0-beta.83", | ||
| "@opencode-ai/httpapi-codegen": "0.0.0-next-14962", | ||
| "@opencode-ai/httpapi-codegen": "0.0.0-next-14971", | ||
| "@tsconfig/bun": "1.0.9", | ||
@@ -61,0 +61,0 @@ "@types/bun": "1.3.13", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
784702
0.15%18281
0.11%+ Added
+ Added
- Removed
- Removed