@opencode/sdk 0.0.0-reserved → 2.0.0
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/README.md +140 -2
- package/dist/contracts.d.ts +26 -0
- package/dist/contracts.js +26 -0
- package/dist/effect/index.d.ts +5 -0
- package/dist/effect/index.js +4 -0
- package/dist/effect/opencode.d.ts +31 -0
- package/dist/effect/opencode.js +23 -0
- package/dist/effect/tool.d.ts +3 -0
- package/dist/effect/tool.js +2 -0
- package/dist/effect/workerd.d.ts +15 -0
- package/dist/effect/workerd.js +8 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/internal/fetch.d.ts +7 -0
- package/dist/internal/fetch.js +106 -0
- package/dist/internal/host.d.ts +30 -0
- package/dist/internal/host.js +30 -0
- package/dist/internal/instances.d.ts +24 -0
- package/dist/internal/instances.js +45 -0
- package/dist/internal/workerd.d.ts +55 -0
- package/dist/internal/workerd.js +12 -0
- package/dist/logging.d.ts +15 -0
- package/dist/logging.js +46 -0
- package/dist/opencode.d.ts +7 -0
- package/dist/opencode.js +2 -0
- package/dist/promise.d.ts +26 -0
- package/dist/promise.js +30 -0
- package/dist/tool.d.ts +3 -0
- package/dist/tool.js +2 -0
- package/dist/workerd.d.ts +26 -0
- package/dist/workerd.js +7 -0
- package/package.json +50 -6
package/README.md
CHANGED
|
@@ -1,5 +1,143 @@
|
|
|
1
1
|
# @opencode/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
In-process OpenCode host for Promise and Effect applications. The SDK executes Server's assembled HTTP router in memory, opening no listener and adding no network hop.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```ts
|
|
6
|
+
import { OpenCode } from "@opencode/sdk"
|
|
7
|
+
|
|
8
|
+
await using opencode = await OpenCode.create()
|
|
9
|
+
const session = await opencode.sessions.create({
|
|
10
|
+
location: { directory: "/workspace" },
|
|
11
|
+
})
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Pass imported Promise plugins in `plugins`, or register one later with `await opencode.plugin(plugin)`.
|
|
15
|
+
|
|
16
|
+
The Promise API uses the same values, errors, request options, and `AsyncIterable` streams as `@opencode/client`.
|
|
17
|
+
|
|
18
|
+
Embedded hosts are silent by default. Set `log` to receive structured log entries:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
await using opencode = await OpenCode.create({
|
|
22
|
+
log: {
|
|
23
|
+
level: "warn",
|
|
24
|
+
emit: (entry) => console.error(entry.message, entry.attributes, entry.cause),
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`close()` and `Symbol.asyncDispose` release router resources, Location services, fibers, and scoped plugin registrations.
|
|
30
|
+
|
|
31
|
+
## Session-Selected Plugins
|
|
32
|
+
|
|
33
|
+
Use `instances` when Sessions in the same directory need different application plugins. The application selects a stable key from Session metadata; the SDK constructs and caches an instance for that key and the Session's current Location.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { OpenCode } from "@opencode/sdk"
|
|
37
|
+
import { threads } from "./threads"
|
|
38
|
+
import { slackPlugin } from "./slack-plugin"
|
|
39
|
+
|
|
40
|
+
await using opencode = await OpenCode.create({
|
|
41
|
+
database: { path: "./sessions.db" },
|
|
42
|
+
instances: {
|
|
43
|
+
key(session) {
|
|
44
|
+
const threadID = session.metadata?.threadID
|
|
45
|
+
if (typeof threadID !== "string") throw new Error("Session has no thread ID")
|
|
46
|
+
return threadID
|
|
47
|
+
},
|
|
48
|
+
configure: async (threadID) => ({
|
|
49
|
+
plugins: [slackPlugin(await threads.get(threadID))],
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const session = await opencode.sessions.create({
|
|
55
|
+
location: { directory: "/workspace" },
|
|
56
|
+
metadata: { threadID: "thread-42" },
|
|
57
|
+
})
|
|
58
|
+
await opencode.sessions.prompt({ sessionID: session.id, text: "Review the changes" })
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`threads` and `slackPlugin` are application-owned modules. `key` is synchronous and should only select identity, not initialize plugins. `configure` returns plugin definitions; their setup receives the selected instance's `ctx.location`.
|
|
62
|
+
|
|
63
|
+
- The same key and Location share one live instance. Different directories or workspace IDs always select separate instances, even with the same application key.
|
|
64
|
+
- `configure` runs on a cache miss, not on each prompt. Loaded instances live until the host closes; change the application key or restart the host to reconstruct their birth configuration. Plugin transforms and reloads remain available within that lifetime.
|
|
65
|
+
- Session metadata, message, inbox, and context reads do not initialize plugins; permission and form lists read instance services and therefore acquire the Session's instance. Configuration failure or a supplied plugin reported as failed after activation, including initial setup failure or an ID that collides with a host `plugins` entry (the host plugin keeps the ID), prevents capability acquisition, without falling back to another instance. A subsequent request can retry a failed construction.
|
|
66
|
+
- Existing HTTP prompt middleware also acquires capabilities for an idempotent retry. After restart, that retry can reconstruct plugins before returning the original admission; prompt preparation and hooks do not rerun. Configuration failure can therefore block the retry even when its input was already saved.
|
|
67
|
+
- Instance selection is not an authorization or storage-isolation boundary. Plugin Session APIs and the existing plugin-ID-based durable storage retain their normal scope.
|
|
68
|
+
- Omitting `instances` preserves default Location sharing. Host-wide plugins remain separate from Session-selected configuration; retain host-wide catalog policy when locationless generation needs it.
|
|
69
|
+
|
|
70
|
+
### Restart and Lifetime
|
|
71
|
+
|
|
72
|
+
The selector is installed before automatic recovery starts. Its callbacks must be able to load application data without depending on the returned `opencode` handle or a later registration call. Functions are reconstructed, not serialized.
|
|
73
|
+
|
|
74
|
+
Use a persistent `database.path` to recover Sessions after restart; the default database is in memory. Workerd uses its injected Durable Object storage. After restart, the next capability-dependent operation or recovery drain rebuilds the selected instance from saved Session metadata and application data.
|
|
75
|
+
|
|
76
|
+
Promise plugin resources should be acquired in `setup` and released by its cleanup function. Effect configuration can acquire resources in its supplied instance Scope and require services provided to the SDK entrypoint (see the Effect section).
|
|
77
|
+
|
|
78
|
+
## Workerd
|
|
79
|
+
|
|
80
|
+
Use the Workerd entrypoint inside a Cloudflare Durable Object. Hold one host for the lifetime of the object instance rather than creating one per request.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { OpenCodeWorkerd } from "@opencode/sdk/workerd"
|
|
84
|
+
import myPlugin from "./my-plugin"
|
|
85
|
+
|
|
86
|
+
export class OpenCodeDO {
|
|
87
|
+
private readonly opencode: Promise<OpenCodeWorkerd.Interface>
|
|
88
|
+
|
|
89
|
+
constructor(state: DurableObjectState) {
|
|
90
|
+
this.opencode = state.blockConcurrencyWhile(() =>
|
|
91
|
+
OpenCodeWorkerd.create({
|
|
92
|
+
storage: state.storage,
|
|
93
|
+
config: { default_agent: "build" },
|
|
94
|
+
plugins: [myPlugin],
|
|
95
|
+
}),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async fetch() {
|
|
100
|
+
const opencode = await this.opencode
|
|
101
|
+
return Response.json(await opencode.health.get())
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`blockConcurrencyWhile` keeps every Durable Object event out until the host is ready and resets the object if initialization fails. The retained Promise gives request handlers direct access to the same host after startup. Configuration is a typed JavaScript object, and plugins are imported values bundled with the Worker.
|
|
107
|
+
|
|
108
|
+
## Effect
|
|
109
|
+
|
|
110
|
+
The Effect-native API remains available from `@opencode/sdk/effect`:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { OpenCode } from "@opencode/sdk/effect"
|
|
114
|
+
|
|
115
|
+
const opencode = yield * OpenCode.create()
|
|
116
|
+
const session = yield * opencode.sessions.get({ sessionID })
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The Effect Workerd entrypoint is `@opencode/sdk/workerd/effect`.
|
|
120
|
+
|
|
121
|
+
Effect configuration uses the same keys and lifetime rules, with canonical `Session.Info` values and an Effect-returning factory. `configure` may require services; `OpenCode.create` and `OpenCode.layer` carry those requirements, so the application satisfies them where it builds the SDK, as with any other Effect callback:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { OpenCode } from "@opencode/sdk/effect"
|
|
125
|
+
import { Effect, Layer, Schema } from "effect"
|
|
126
|
+
import { Threads } from "./threads-effect"
|
|
127
|
+
import { slackPlugin } from "./slack-plugin-effect"
|
|
128
|
+
|
|
129
|
+
const threadMetadata = Schema.decodeUnknownSync(Schema.Struct({ threadID: Schema.String }))
|
|
130
|
+
const opencode = OpenCode.layer({
|
|
131
|
+
database: { path: "./sessions.db" },
|
|
132
|
+
instances: {
|
|
133
|
+
key: (session) => threadMetadata(session.metadata).threadID,
|
|
134
|
+
configure: (threadID) =>
|
|
135
|
+
Effect.gen(function* () {
|
|
136
|
+
const threads = yield* Threads
|
|
137
|
+
return { plugins: [slackPlugin(yield* threads.get(threadID))] }
|
|
138
|
+
}),
|
|
139
|
+
},
|
|
140
|
+
}).pipe(Layer.provide(Threads.layer))
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Resources acquired in `configure` still belong to the instance, not to the Scope the SDK was built in. Both Workerd entrypoints also accept `instances`. The public `OpenCode.InstanceOptions` and `OpenCode.InstanceConfiguration` types describe the corresponding Promise or Effect callbacks.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { Agent } from "@opencode/schema/agent";
|
|
2
|
+
export { Command } from "@opencode/schema/command";
|
|
3
|
+
export { Config } from "@opencode/schema/config";
|
|
4
|
+
export { Credential } from "@opencode/schema/credential";
|
|
5
|
+
export { Event } from "@opencode/schema/event";
|
|
6
|
+
export { FileSystem } from "@opencode/schema/filesystem";
|
|
7
|
+
export { Integration } from "@opencode/schema/integration";
|
|
8
|
+
export { Location } from "@opencode/schema/location";
|
|
9
|
+
export { Model } from "@opencode/schema/model";
|
|
10
|
+
export { Permission } from "@opencode/schema/permission";
|
|
11
|
+
export { PermissionSaved } from "@opencode/schema/permission-saved";
|
|
12
|
+
export { Project } from "@opencode/schema/project";
|
|
13
|
+
export { Worktree } from "@opencode/schema/worktree";
|
|
14
|
+
export { Prompt } from "@opencode/schema/prompt";
|
|
15
|
+
export { PromptInput } from "@opencode/schema/prompt-input";
|
|
16
|
+
export { Provider } from "@opencode/schema/provider";
|
|
17
|
+
export { Pty } from "@opencode/schema/pty";
|
|
18
|
+
export { Question } from "@opencode/schema/question";
|
|
19
|
+
export { Reference } from "@opencode/schema/reference";
|
|
20
|
+
export { WebSearch } from "@opencode/schema/websearch";
|
|
21
|
+
export { AbsolutePath, RelativePath } from "@opencode/schema/schema";
|
|
22
|
+
export { Session } from "@opencode/schema/session";
|
|
23
|
+
export { SessionInbox } from "@opencode/schema/session-inbox";
|
|
24
|
+
export { SessionMessage } from "@opencode/schema/session-message";
|
|
25
|
+
export { Skill } from "@opencode/schema/skill";
|
|
26
|
+
export { Workspace } from "@opencode/schema/workspace";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { Agent } from "@opencode/schema/agent";
|
|
2
|
+
export { Command } from "@opencode/schema/command";
|
|
3
|
+
export { Config } from "@opencode/schema/config";
|
|
4
|
+
export { Credential } from "@opencode/schema/credential";
|
|
5
|
+
export { Event } from "@opencode/schema/event";
|
|
6
|
+
export { FileSystem } from "@opencode/schema/filesystem";
|
|
7
|
+
export { Integration } from "@opencode/schema/integration";
|
|
8
|
+
export { Location } from "@opencode/schema/location";
|
|
9
|
+
export { Model } from "@opencode/schema/model";
|
|
10
|
+
export { Permission } from "@opencode/schema/permission";
|
|
11
|
+
export { PermissionSaved } from "@opencode/schema/permission-saved";
|
|
12
|
+
export { Project } from "@opencode/schema/project";
|
|
13
|
+
export { Worktree } from "@opencode/schema/worktree";
|
|
14
|
+
export { Prompt } from "@opencode/schema/prompt";
|
|
15
|
+
export { PromptInput } from "@opencode/schema/prompt-input";
|
|
16
|
+
export { Provider } from "@opencode/schema/provider";
|
|
17
|
+
export { Pty } from "@opencode/schema/pty";
|
|
18
|
+
export { Question } from "@opencode/schema/question";
|
|
19
|
+
export { Reference } from "@opencode/schema/reference";
|
|
20
|
+
export { WebSearch } from "@opencode/schema/websearch";
|
|
21
|
+
export { AbsolutePath, RelativePath } from "@opencode/schema/schema";
|
|
22
|
+
export { Session } from "@opencode/schema/session";
|
|
23
|
+
export { SessionInbox } from "@opencode/schema/session-inbox";
|
|
24
|
+
export { SessionMessage } from "@opencode/schema/session-message";
|
|
25
|
+
export { Skill } from "@opencode/schema/skill";
|
|
26
|
+
export { Workspace } from "@opencode/schema/workspace";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export * as OpenCode from "./opencode";
|
|
2
|
+
import { type OpenCodeClient } from "@opencode/client/effect";
|
|
3
|
+
import type { Workspace } from "@opencode/core/workspace";
|
|
4
|
+
import { Context, Effect, Layer } from "effect";
|
|
5
|
+
import type { Config, Scope } from "effect";
|
|
6
|
+
import { EmbeddedHost } from "../internal/host";
|
|
7
|
+
import type { SdkInstances } from "../internal/instances";
|
|
8
|
+
export type { LogEntry, LogLevel, LogOptions, LogWriter } from "../logging";
|
|
9
|
+
export type CreateOptions<R = never> = EmbeddedHost.CreateOptions<R>;
|
|
10
|
+
export type EmbedOptions = EmbeddedHost.EmbedOptions;
|
|
11
|
+
export type InstanceOptions<R = never> = SdkInstances.Options<R>;
|
|
12
|
+
export type InstanceConfiguration = SdkInstances.Configuration;
|
|
13
|
+
export type Interface = Omit<OpenCodeClient, "plugin" | "workspace"> & {
|
|
14
|
+
readonly sessions: OpenCodeClient["session"];
|
|
15
|
+
readonly events: OpenCodeClient["event"];
|
|
16
|
+
readonly workspace: {
|
|
17
|
+
readonly create: Workspace.Interface["create"];
|
|
18
|
+
readonly provision: (options: {
|
|
19
|
+
readonly workspaceID: Workspace.ID;
|
|
20
|
+
}) => ReturnType<Workspace.Interface["provision"]>;
|
|
21
|
+
readonly destroy: (options: {
|
|
22
|
+
readonly workspaceID: Workspace.ID;
|
|
23
|
+
}) => ReturnType<Workspace.Interface["destroy"]>;
|
|
24
|
+
};
|
|
25
|
+
readonly plugin: EmbeddedHost.Interface["plugins"]["register"] & OpenCodeClient["plugin"];
|
|
26
|
+
};
|
|
27
|
+
export declare const create: <R = never>(options?: CreateOptions<R>, embed?: EmbedOptions) => Effect.Effect<Interface, Config.ConfigError | Error, Scope.Scope | R>;
|
|
28
|
+
declare const Service_base: Context.ServiceClass<Service, "@opencode/sdk/OpenCode", Interface>;
|
|
29
|
+
export declare class Service extends Service_base {
|
|
30
|
+
}
|
|
31
|
+
export declare const layer: <R = never>(options?: CreateOptions<R>) => Layer.Layer<Service, Config.ConfigError | Error, Exclude<R, Scope.Scope>>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export * as OpenCode from "./opencode";
|
|
2
|
+
import { OpenCode } from "@opencode/client/effect";
|
|
3
|
+
import { Context, Effect, Layer } from "effect";
|
|
4
|
+
import { FetchHttpClient, HttpClient } from "effect/unstable/http";
|
|
5
|
+
import { EmbeddedHost } from "../internal/host";
|
|
6
|
+
export const create = Effect.fn("OpenCode.create")(function* (options = {}, embed = {}) {
|
|
7
|
+
const host = yield* Effect.acquireRelease(EmbeddedHost.create(options, embed), (host) => Effect.promise(host.close)), httpClient = yield* HttpClient.HttpClient.pipe(Effect.provide(FetchHttpClient.layer)), client = yield* OpenCode.make({ baseUrl: "http://opencode.local" }).pipe(Effect.provideService(HttpClient.HttpClient, HttpClient.transformResponse(httpClient, Effect.provideService(FetchHttpClient.Fetch, host.fetch))));
|
|
8
|
+
return {
|
|
9
|
+
...client,
|
|
10
|
+
sessions: client.session,
|
|
11
|
+
events: client.event,
|
|
12
|
+
workspace: {
|
|
13
|
+
create: host.workspace.create,
|
|
14
|
+
provision: ({ workspaceID }) => host.workspace.provision(workspaceID),
|
|
15
|
+
destroy: ({ workspaceID }) => host.workspace.destroy(workspaceID)
|
|
16
|
+
},
|
|
17
|
+
plugin: Object.assign(host.plugins.register, client.plugin)
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export class Service extends Context.Service()("@opencode/sdk/OpenCode") {
|
|
22
|
+
}
|
|
23
|
+
export const layer = (options = {}) => Layer.effect(Service, create(options));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * as OpenCodeWorkerd from "./workerd";
|
|
2
|
+
import { Layer } from "effect";
|
|
3
|
+
import type { Config, Scope } from "effect";
|
|
4
|
+
import { WorkerdProfile } from "../internal/workerd";
|
|
5
|
+
import { OpenCode } from "./opencode";
|
|
6
|
+
export type Configuration = WorkerdProfile.Configuration;
|
|
7
|
+
export interface CreateOptions<R = never> extends WorkerdProfile.Options {
|
|
8
|
+
readonly log?: OpenCode.CreateOptions["log"];
|
|
9
|
+
readonly workspaceProviders?: OpenCode.CreateOptions["workspaceProviders"];
|
|
10
|
+
readonly instances?: OpenCode.CreateOptions<R>["instances"];
|
|
11
|
+
}
|
|
12
|
+
export declare const create: <R = never>({ log, workspaceProviders, instances, ...options }: CreateOptions<R>) => import("effect/Effect").Effect<OpenCode.Interface, Error | Config.ConfigError, Scope.Scope | R>;
|
|
13
|
+
export declare const layer: <R = never>(options: CreateOptions<R>) => Layer.Layer<OpenCode.Service, Config.ConfigError | Error, Exclude<R, Scope.Scope>>;
|
|
14
|
+
export type Interface = OpenCode.Interface;
|
|
15
|
+
export type Requirements = Scope.Scope;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * as OpenCodeWorkerd from "./workerd";
|
|
2
|
+
import { Layer } from "effect";
|
|
3
|
+
import { WorkerdProfile } from "../internal/workerd";
|
|
4
|
+
import { OpenCode } from "./opencode";
|
|
5
|
+
export const create = ({ log, workspaceProviders, instances, ...options }) => {
|
|
6
|
+
const profile = WorkerdProfile.make(options);
|
|
7
|
+
return OpenCode.create({ ...profile.options, log, workspaceProviders, instances }, { overrides: profile.replacements });
|
|
8
|
+
}, layer = (options) => Layer.effect(OpenCode.Service, create(options));
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * as OwnedFetch from "./fetch";
|
|
2
|
+
export declare function make(handler: (request: Request) => Promise<Response>, dispose: () => Promise<void>): {
|
|
3
|
+
fetch: ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>) & {
|
|
4
|
+
preconnect: () => undefined;
|
|
5
|
+
};
|
|
6
|
+
close: () => Promise<void>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export * as OwnedFetch from "./fetch";
|
|
2
|
+
export function make(handler, dispose) {
|
|
3
|
+
const requests = new Set, shutdown = new AbortController, closed = Error("OpenCode host is closed");
|
|
4
|
+
let closePromise;
|
|
5
|
+
return { fetch: Object.assign((input, init) => {
|
|
6
|
+
if (closePromise)
|
|
7
|
+
return Promise.reject(closed);
|
|
8
|
+
const source = new Request(input, init);
|
|
9
|
+
if (source.signal.aborted)
|
|
10
|
+
return Promise.reject(source.signal.reason);
|
|
11
|
+
const request = new Request(source, { signal: AbortSignal.any([source.signal, shutdown.signal]) }), lifetime = Promise.withResolvers(), finish = () => {
|
|
12
|
+
requests.delete(lifetime.promise);
|
|
13
|
+
lifetime.resolve();
|
|
14
|
+
};
|
|
15
|
+
requests.add(lifetime.promise);
|
|
16
|
+
const handled = handler(request);
|
|
17
|
+
return rejectOnAbort(handled, request.signal).then((response) => trackResponse(response, request.signal, finish), (cause) => {
|
|
18
|
+
handled.then(finish, finish);
|
|
19
|
+
throw cause;
|
|
20
|
+
});
|
|
21
|
+
}, { preconnect: () => {
|
|
22
|
+
return;
|
|
23
|
+
} }), close: () => {
|
|
24
|
+
if (closePromise)
|
|
25
|
+
return closePromise;
|
|
26
|
+
closePromise = Promise.resolve().then(async () => {
|
|
27
|
+
shutdown.abort(closed);
|
|
28
|
+
await Promise.allSettled(requests);
|
|
29
|
+
await dispose();
|
|
30
|
+
});
|
|
31
|
+
return closePromise;
|
|
32
|
+
} };
|
|
33
|
+
}
|
|
34
|
+
function rejectOnAbort(promise, signal) {
|
|
35
|
+
if (signal.aborted)
|
|
36
|
+
return Promise.reject(signal.reason);
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const abort = () => reject(signal.reason);
|
|
39
|
+
signal.addEventListener("abort", abort, { once: !0 });
|
|
40
|
+
promise.then((value) => {
|
|
41
|
+
signal.removeEventListener("abort", abort);
|
|
42
|
+
resolve(value);
|
|
43
|
+
}, (cause) => {
|
|
44
|
+
signal.removeEventListener("abort", abort);
|
|
45
|
+
reject(cause);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function trackResponse(response, signal, finish) {
|
|
50
|
+
if (!response.body) {
|
|
51
|
+
finish();
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
54
|
+
const reader = response.body.getReader();
|
|
55
|
+
let done = !1, abort = () => {};
|
|
56
|
+
const complete = () => {
|
|
57
|
+
if (done)
|
|
58
|
+
return !1;
|
|
59
|
+
done = !0;
|
|
60
|
+
signal.removeEventListener("abort", abort);
|
|
61
|
+
return !0;
|
|
62
|
+
}, body = new ReadableStream({
|
|
63
|
+
start(controller) {
|
|
64
|
+
abort = () => {
|
|
65
|
+
if (!complete())
|
|
66
|
+
return;
|
|
67
|
+
controller.error(signal.reason);
|
|
68
|
+
reader.cancel(signal.reason).then(finish, finish);
|
|
69
|
+
};
|
|
70
|
+
if (signal.aborted)
|
|
71
|
+
abort();
|
|
72
|
+
else
|
|
73
|
+
signal.addEventListener("abort", abort, { once: !0 });
|
|
74
|
+
},
|
|
75
|
+
async pull(controller) {
|
|
76
|
+
try {
|
|
77
|
+
const next = await reader.read();
|
|
78
|
+
if (done)
|
|
79
|
+
return;
|
|
80
|
+
if (!next.done) {
|
|
81
|
+
controller.enqueue(next.value);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!complete())
|
|
85
|
+
return;
|
|
86
|
+
controller.close();
|
|
87
|
+
finish();
|
|
88
|
+
} catch (cause) {
|
|
89
|
+
if (!complete())
|
|
90
|
+
return;
|
|
91
|
+
controller.error(cause);
|
|
92
|
+
finish();
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
async cancel(reason) {
|
|
96
|
+
if (!complete())
|
|
97
|
+
return;
|
|
98
|
+
try {
|
|
99
|
+
await reader.cancel(reason);
|
|
100
|
+
} finally {
|
|
101
|
+
finish();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return new Response(body, response);
|
|
106
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export * as EmbeddedHost from "./host";
|
|
2
|
+
import { SdkPlugins } from "@opencode/core/plugin/sdk";
|
|
3
|
+
import { SessionRestart } from "@opencode/core/session/execution/restart";
|
|
4
|
+
import { Workspace } from "@opencode/core/workspace";
|
|
5
|
+
import { WorkspaceDriver } from "@opencode/core/workspace/driver";
|
|
6
|
+
import type { ServerOptions } from "@opencode/server/options";
|
|
7
|
+
import type { LayerNode } from "@opencode/util/effect/layer-node";
|
|
8
|
+
import { Effect, ManagedRuntime } from "effect";
|
|
9
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
10
|
+
import { type LogOptions } from "../logging";
|
|
11
|
+
import { SdkInstances } from "./instances";
|
|
12
|
+
export interface CreateOptions<R = never> extends Omit<ServerOptions, "hostname" | "port" | "password"> {
|
|
13
|
+
readonly log?: LogOptions;
|
|
14
|
+
readonly workspaceProviders?: Readonly<Record<string, WorkspaceDriver.Interface>>;
|
|
15
|
+
readonly instances?: SdkInstances.Options<R>;
|
|
16
|
+
}
|
|
17
|
+
/** Host hooks for embedding opencode on a non-default runtime profile. */
|
|
18
|
+
export interface EmbedOptions {
|
|
19
|
+
readonly overrides?: LayerNode.Replacements;
|
|
20
|
+
}
|
|
21
|
+
export declare const create: <R = never>(options?: CreateOptions<R> | undefined, embed?: EmbedOptions | undefined) => Effect.Effect<{
|
|
22
|
+
runtime: ManagedRuntime.ManagedRuntime<import("effect/unstable/http/HttpClient").HttpClient | import("@opencode/util/global").Service | import("@opencode/core/database/database").Service | import("@opencode/core/project").Service | import("@opencode/core/bus").Service | import("@opencode/core/persistent-pty/index").Service | import("@opencode/core/credential").Service | import("@opencode/core/wellknown").Service | Workspace.Service | import("@opencode/core/permission/saved").Service | SdkPlugins.Service | import("@opencode/core/plugin/update").Service | import("@opencode/core/session").Service | import("@opencode/core/job").Service | import("@opencode/core/instance").Service | import("@opencode/core/location-service-map").Service | SessionRestart.Service | import("@opencode/core/pty/ticket").Service | import("@opencode/core/session/transfer").Service | import("@opencode/core/location-activity").Service | import("@opencode/server/pty-environment").Service | HttpRouter.HttpRouter, Error | import("@opencode/core/persistent-pty/index").UnavailableError>;
|
|
23
|
+
fetch: ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>) & {
|
|
24
|
+
preconnect: () => undefined;
|
|
25
|
+
};
|
|
26
|
+
plugins: SdkPlugins.Interface;
|
|
27
|
+
workspace: Workspace.Interface;
|
|
28
|
+
close: () => Promise<void>;
|
|
29
|
+
}, Error, R>;
|
|
30
|
+
export type Interface = Effect.Success<ReturnType<typeof create>>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export * as EmbeddedHost from "./host";
|
|
2
|
+
import { SdkPlugins } from "@opencode/core/plugin/sdk";
|
|
3
|
+
import { SessionRestart } from "@opencode/core/session/execution/restart";
|
|
4
|
+
import { Workspace } from "@opencode/core/workspace";
|
|
5
|
+
import { WorkspaceDriver } from "@opencode/core/workspace/driver";
|
|
6
|
+
import { createEmbeddedRoutes } from "@opencode/server/routes";
|
|
7
|
+
import { Context, Effect, Layer, ManagedRuntime, Scope } from "effect";
|
|
8
|
+
import { HttpEffect, HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http";
|
|
9
|
+
import { context, layer } from "../logging";
|
|
10
|
+
import { OwnedFetch } from "./fetch";
|
|
11
|
+
import { SdkInstances } from "./instances";
|
|
12
|
+
export const create = Effect.fn("EmbeddedHost.create")(function* (options = {}, embed = {}) {
|
|
13
|
+
const { log, workspaceProviders, instances, ...server } = options, selector = instances ? SdkInstances.provide(instances, yield* Effect.context()) : void 0, runtime = ManagedRuntime.make(createEmbeddedRoutes({
|
|
14
|
+
...server,
|
|
15
|
+
app: { ...server.app, name: server.app?.name ?? "sdk" },
|
|
16
|
+
database: { path: ":memory:", ...server.database }
|
|
17
|
+
}, workspaceProviders ? [...embed.overrides ?? [], WorkspaceDriver.node.replace(WorkspaceDriver.registryNode(workspaceProviders))] : embed.overrides, selector ? (replacements) => SdkInstances.node(selector, replacements) : void 0).pipe(Layer.provide(HttpServer.layerServices), Layer.provideMerge(layer(log))));
|
|
18
|
+
return yield* Effect.gen(function* () {
|
|
19
|
+
const services = yield* runtime.contextEffect;
|
|
20
|
+
runtime.runFork(Context.get(services, SessionRestart.Service).resumeSuspendedSessions);
|
|
21
|
+
const handler = HttpEffect.toWebHandlerWith(context(services))(Context.get(services, HttpRouter.HttpRouter).asHttpEffect()), transport = OwnedFetch.make(handler, runtime.dispose);
|
|
22
|
+
return {
|
|
23
|
+
runtime,
|
|
24
|
+
fetch: transport.fetch,
|
|
25
|
+
plugins: Context.get(services, SdkPlugins.Service),
|
|
26
|
+
workspace: Context.get(services, Workspace.Service),
|
|
27
|
+
close: transport.close
|
|
28
|
+
};
|
|
29
|
+
}).pipe(Effect.onError(() => runtime.disposeEffect));
|
|
30
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export * as SdkInstances from "./instances";
|
|
2
|
+
import { Instance } from "@opencode/core/instance";
|
|
3
|
+
import { LocationServiceMap } from "@opencode/core/location-service-map";
|
|
4
|
+
import type { InstancePlugins } from "@opencode/core/plugin/instance";
|
|
5
|
+
import type { Session } from "@opencode/schema/session";
|
|
6
|
+
import type { LayerNode } from "@opencode/util/effect/layer-node";
|
|
7
|
+
import { Context, Effect, Layer, Scope } from "effect";
|
|
8
|
+
export interface Configuration {
|
|
9
|
+
readonly plugins: InstancePlugins.List;
|
|
10
|
+
}
|
|
11
|
+
export interface Options<R = never> {
|
|
12
|
+
/** Select a sharing key within the Session's current Location. Must not initialize plugins. */
|
|
13
|
+
readonly key: (session: Session.Info) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Reconstruct configuration on a cache miss. Resources belong to the instance Scope; other requirements
|
|
16
|
+
* are the services the SDK entrypoint was built with.
|
|
17
|
+
*/
|
|
18
|
+
readonly configure: (key: string) => Effect.Effect<Configuration, unknown, R | Scope.Scope>;
|
|
19
|
+
}
|
|
20
|
+
/** Closes `configure` over services captured where the SDK entrypoint was built; the host graph has none of its own. */
|
|
21
|
+
export declare function provide<R>(options: Options<R>, context: Context.Context<R>): Options;
|
|
22
|
+
/** Replaces the host's `Instance.node`; `replacements` resolves lazily so instances inherit the final host graph. */
|
|
23
|
+
export declare function node(options: Options, replacements: () => LayerNode.Replacements): LayerNode.Provider<Instance.Service, never, LayerNode.Tag<"global">>;
|
|
24
|
+
export declare function layer(options: Options, replacements: () => LayerNode.Replacements): Layer.Layer<Instance.Service, never, LocationServiceMap.Service>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export * as SdkInstances from "./instances";
|
|
2
|
+
import { Instance } from "@opencode/core/instance";
|
|
3
|
+
import { LocationServiceMap } from "@opencode/core/location-service-map";
|
|
4
|
+
import { Plugin } from "@opencode/core/plugin";
|
|
5
|
+
import { Location } from "@opencode/schema/location";
|
|
6
|
+
import { makeGlobalNode } from "@opencode/util/effect/app-node";
|
|
7
|
+
import { Context, Duration, Effect, Layer, LayerMap, Scope } from "effect";
|
|
8
|
+
export function provide(options, context) {
|
|
9
|
+
return {
|
|
10
|
+
key: options.key,
|
|
11
|
+
configure: (key) => options.configure(key).pipe(Effect.updateContext((ambient) => Context.merge(context, ambient)))
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function node(options, replacements) {
|
|
15
|
+
return makeGlobalNode({
|
|
16
|
+
service: Instance.Service,
|
|
17
|
+
layer: layer(options, replacements),
|
|
18
|
+
deps: [LocationServiceMap.node]
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function layer(options, replacements) {
|
|
22
|
+
return Layer.effect(Instance.Service, Effect.gen(function* () {
|
|
23
|
+
const scope = yield* Effect.scope, locations = yield* LocationServiceMap.Service, key = (session) => ({
|
|
24
|
+
key: options.key(session),
|
|
25
|
+
...LocationServiceMap.canonical(session.location)
|
|
26
|
+
}), provide = (session) => Effect.provide(instances.get(key(session))), instances = yield* LayerMap.make((input) => Layer.unwrap(Effect.gen(function* () {
|
|
27
|
+
const configuration = yield* options.configure(input.key).pipe(Effect.orDie);
|
|
28
|
+
return Instance.layer(Location.Ref.make({ directory: input.directory, workspaceID: input.workspaceID }), {
|
|
29
|
+
plugins: configuration.plugins,
|
|
30
|
+
replacements: [
|
|
31
|
+
...replacements(),
|
|
32
|
+
Instance.node.replace(Layer.succeed(Instance.Service, { provide })),
|
|
33
|
+
LocationServiceMap.node.replace(Layer.succeed(LocationServiceMap.Service, locations))
|
|
34
|
+
]
|
|
35
|
+
}).pipe(Layer.tap((context) => Effect.gen(function* () {
|
|
36
|
+
const plugins = yield* Plugin.Service;
|
|
37
|
+
yield* plugins.awaitActivation;
|
|
38
|
+
const failed = (yield* plugins.list()).filter((plugin) => plugin.state.status === "failed" && configuration.plugins.some((configured) => configured.id === plugin.id));
|
|
39
|
+
if (failed.length > 0)
|
|
40
|
+
yield* Effect.die(Error(`Instance plugin setup failed: ${failed.map((plugin) => plugin.id).join(", ")}`));
|
|
41
|
+
}).pipe(Effect.provide(context))));
|
|
42
|
+
})).pipe(Layer.tapCause(() => instances.invalidate(input).pipe(Effect.forkIn(scope, { startImmediately: !0 }), Effect.asVoid))), { idleTimeToLive: Duration.infinity });
|
|
43
|
+
return Instance.Service.of({ provide });
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export * as WorkerdProfile from "./workerd";
|
|
2
|
+
import type { Config } from "@opencode/schema/config";
|
|
3
|
+
import { ServerWorkerd } from "@opencode/server/workerd";
|
|
4
|
+
export type Configuration = Omit<typeof Config.Info.Encoded, "plugins">;
|
|
5
|
+
export interface Options extends Omit<ServerWorkerd.Options, "password" | "config"> {
|
|
6
|
+
readonly config?: Configuration;
|
|
7
|
+
}
|
|
8
|
+
export declare function make({ config, ...options }: Options): {
|
|
9
|
+
options: {
|
|
10
|
+
readonly models?: {
|
|
11
|
+
readonly url?: string | undefined;
|
|
12
|
+
readonly file?: string | undefined;
|
|
13
|
+
readonly snapshot?: boolean | undefined;
|
|
14
|
+
readonly fetch?: boolean | undefined;
|
|
15
|
+
} | undefined;
|
|
16
|
+
readonly pty?: {
|
|
17
|
+
readonly handoff?: {
|
|
18
|
+
readonly directory: string;
|
|
19
|
+
readonly instanceID: string;
|
|
20
|
+
readonly ticket: string;
|
|
21
|
+
readonly expiresAt: number;
|
|
22
|
+
} | undefined;
|
|
23
|
+
} | undefined;
|
|
24
|
+
readonly config?: {
|
|
25
|
+
readonly directory?: string | undefined;
|
|
26
|
+
readonly project?: boolean | undefined;
|
|
27
|
+
readonly content?: string | undefined;
|
|
28
|
+
readonly file?: string | undefined;
|
|
29
|
+
} | undefined;
|
|
30
|
+
readonly events?: {
|
|
31
|
+
readonly persist?: boolean | undefined;
|
|
32
|
+
} | undefined;
|
|
33
|
+
readonly port?: number | undefined;
|
|
34
|
+
readonly hostname?: string | undefined;
|
|
35
|
+
readonly cors?: readonly string[] | undefined;
|
|
36
|
+
readonly windows?: {
|
|
37
|
+
readonly gitbash?: string | undefined;
|
|
38
|
+
} | undefined;
|
|
39
|
+
readonly password?: string | undefined;
|
|
40
|
+
readonly app?: {
|
|
41
|
+
readonly version?: string | undefined;
|
|
42
|
+
readonly name?: string | undefined;
|
|
43
|
+
readonly channel?: string | undefined;
|
|
44
|
+
} | undefined;
|
|
45
|
+
readonly fs?: {
|
|
46
|
+
readonly fff?: boolean | undefined;
|
|
47
|
+
readonly filewatcher?: boolean | undefined;
|
|
48
|
+
} | undefined;
|
|
49
|
+
readonly simulation?: boolean | undefined;
|
|
50
|
+
readonly database?: {
|
|
51
|
+
readonly path?: string | undefined;
|
|
52
|
+
} | undefined;
|
|
53
|
+
};
|
|
54
|
+
replacements: import("@opencode/util/effect/layer-node").Replacements;
|
|
55
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * as WorkerdProfile from "./workerd";
|
|
2
|
+
import { ServerWorkerd } from "@opencode/server/workerd";
|
|
3
|
+
export function make({ config, ...options }) {
|
|
4
|
+
const server = {
|
|
5
|
+
...options,
|
|
6
|
+
config: config === void 0 ? void 0 : { content: JSON.stringify(config) }
|
|
7
|
+
};
|
|
8
|
+
return {
|
|
9
|
+
options: ServerWorkerd.serverOptions(server),
|
|
10
|
+
replacements: ServerWorkerd.replacements(server)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Context, Layer } from "effect";
|
|
2
|
+
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
3
|
+
export type LogEntry = {
|
|
4
|
+
readonly level: LogLevel;
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
7
|
+
readonly cause?: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type LogWriter = (entry: LogEntry) => void;
|
|
10
|
+
export type LogOptions = {
|
|
11
|
+
readonly level?: LogLevel;
|
|
12
|
+
readonly emit: LogWriter;
|
|
13
|
+
};
|
|
14
|
+
export declare function layer(log?: LogOptions): Layer.Layer<never, never, never>;
|
|
15
|
+
export declare function context(source: Context.Context<never>): Context.Context<never>;
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Context, Formatter, Layer, Logger, References } from "effect";
|
|
2
|
+
const levels = {
|
|
3
|
+
trace: "Trace",
|
|
4
|
+
debug: "Debug",
|
|
5
|
+
info: "Info",
|
|
6
|
+
warn: "Warn",
|
|
7
|
+
error: "Error",
|
|
8
|
+
fatal: "Fatal"
|
|
9
|
+
}, levelNames = new Map([
|
|
10
|
+
[levels.trace, "trace"],
|
|
11
|
+
[levels.debug, "debug"],
|
|
12
|
+
[levels.info, "info"],
|
|
13
|
+
[levels.warn, "warn"],
|
|
14
|
+
[levels.error, "error"],
|
|
15
|
+
[levels.fatal, "fatal"]
|
|
16
|
+
]);
|
|
17
|
+
function normalizeLevel(level) {
|
|
18
|
+
return levelNames.get(level);
|
|
19
|
+
}
|
|
20
|
+
export function layer(log) {
|
|
21
|
+
const logger = Logger.make((options) => {
|
|
22
|
+
if (!log)
|
|
23
|
+
return;
|
|
24
|
+
const level = normalizeLevel(options.logLevel);
|
|
25
|
+
if (!level)
|
|
26
|
+
return;
|
|
27
|
+
const entry = Logger.formatStructured.log(options), values = Array.isArray(entry.message) ? entry.message : [entry.message], [message, ...data] = values, details = data.length === 1 && !Array.isArray(data[0]) ? data[0] : void 0, { cause: detailCause, ...detailAttributes } = details ?? {}, attributes = {
|
|
28
|
+
...entry.annotations,
|
|
29
|
+
...detailAttributes,
|
|
30
|
+
...Object.keys(entry.spans).length > 0 ? { spans: entry.spans } : {},
|
|
31
|
+
...!details && data.length > 0 ? { data: data.length === 1 ? data[0] : data } : {}
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
log.emit({
|
|
35
|
+
level,
|
|
36
|
+
message: typeof message === "string" ? message : Formatter.format(message),
|
|
37
|
+
...Object.keys(attributes).length > 0 ? { attributes } : {},
|
|
38
|
+
...entry.cause === void 0 && detailCause === void 0 ? {} : { cause: entry.cause ?? detailCause }
|
|
39
|
+
});
|
|
40
|
+
} catch {}
|
|
41
|
+
});
|
|
42
|
+
return Layer.merge(Logger.layer([logger], { mergeWithExisting: !1 }), Layer.succeed(References.MinimumLogLevel, levels[log?.level ?? "info"]));
|
|
43
|
+
}
|
|
44
|
+
export function context(source) {
|
|
45
|
+
return Context.make(Logger.CurrentLoggers, Context.get(source, Logger.CurrentLoggers)).pipe(Context.add(References.MinimumLogLevel, Context.get(source, References.MinimumLogLevel)));
|
|
46
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PromiseSdk } from "./promise";
|
|
2
|
+
export type { LogEntry, LogLevel, LogOptions, LogWriter } from "./logging";
|
|
3
|
+
export type CreateOptions = PromiseSdk.CreateOptions;
|
|
4
|
+
export type InstanceOptions = PromiseSdk.InstanceOptions;
|
|
5
|
+
export type InstanceConfiguration = PromiseSdk.InstanceConfiguration;
|
|
6
|
+
export type Interface = PromiseSdk.Interface;
|
|
7
|
+
export declare const create: (options?: CreateOptions) => Promise<PromiseSdk.Interface>;
|
package/dist/opencode.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export * as PromiseSdk from "./promise";
|
|
2
|
+
import { type OpenCodeClient } from "@opencode/client";
|
|
3
|
+
import type { Plugin } from "@opencode/plugin";
|
|
4
|
+
import { Session } from "@opencode/schema/session";
|
|
5
|
+
import { EmbeddedHost } from "./internal/host";
|
|
6
|
+
export interface InstanceConfiguration {
|
|
7
|
+
readonly plugins: ReadonlyArray<Plugin.Plugin>;
|
|
8
|
+
}
|
|
9
|
+
export interface InstanceOptions {
|
|
10
|
+
/** Select a sharing key within the Session's current Location. Must not initialize plugins. */
|
|
11
|
+
readonly key: (session: typeof Session.Info.Encoded) => string;
|
|
12
|
+
/** Reconstruct configuration on a cache miss, including after a host restart. */
|
|
13
|
+
readonly configure: (key: string) => InstanceConfiguration | Promise<InstanceConfiguration>;
|
|
14
|
+
}
|
|
15
|
+
export interface CreateOptions extends Omit<EmbeddedHost.CreateOptions, "workspaceProviders" | "instances"> {
|
|
16
|
+
readonly plugins?: ReadonlyArray<Plugin.Plugin>;
|
|
17
|
+
readonly instances?: InstanceOptions;
|
|
18
|
+
}
|
|
19
|
+
export type Interface = Omit<OpenCodeClient, "plugin"> & {
|
|
20
|
+
readonly sessions: OpenCodeClient["session"];
|
|
21
|
+
readonly events: OpenCodeClient["event"];
|
|
22
|
+
readonly plugin: ((plugin: Plugin.Plugin) => Promise<void>) & OpenCodeClient["plugin"];
|
|
23
|
+
readonly close: () => Promise<void>;
|
|
24
|
+
readonly [Symbol.asyncDispose]: () => Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
export declare function create(options?: CreateOptions, embed?: EmbeddedHost.EmbedOptions): Promise<Interface>;
|
package/dist/promise.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export * as PromiseSdk from "./promise";
|
|
2
|
+
import { OpenCode } from "@opencode/client";
|
|
3
|
+
import { Session } from "@opencode/schema/session";
|
|
4
|
+
import { Effect, Schema } from "effect";
|
|
5
|
+
import { EmbeddedHost } from "./internal/host";
|
|
6
|
+
export async function create(options = {}, embed = {}) {
|
|
7
|
+
const { plugins, instances, ...hostOptions } = options, host = await Effect.runPromise(EmbeddedHost.create({
|
|
8
|
+
...hostOptions,
|
|
9
|
+
instances: instances ? {
|
|
10
|
+
key: (session) => instances.key(Schema.encodeSync(Session.Info)(session)),
|
|
11
|
+
configure: (key) => Effect.gen(function* () {
|
|
12
|
+
const { PluginPromise } = yield* Effect.promise(() => import("@opencode/core/plugin/promise"));
|
|
13
|
+
return { plugins: (yield* Effect.tryPromise(async () => instances.configure(key))).plugins.map(PluginPromise.fromPromise) };
|
|
14
|
+
})
|
|
15
|
+
} : void 0
|
|
16
|
+
}, embed)), client = OpenCode.make({ baseUrl: "http://opencode.local", fetch: host.fetch }), register = async (plugin) => {
|
|
17
|
+
const { PluginPromise } = await import("@opencode/core/plugin/promise");
|
|
18
|
+
return host.runtime.runPromise(host.plugins.register(PluginPromise.fromPromise(plugin)));
|
|
19
|
+
};
|
|
20
|
+
for (const plugin of plugins ?? [])
|
|
21
|
+
await register(plugin);
|
|
22
|
+
return {
|
|
23
|
+
...client,
|
|
24
|
+
sessions: client.session,
|
|
25
|
+
events: client.event,
|
|
26
|
+
plugin: Object.assign(register, client.plugin),
|
|
27
|
+
close: host.close,
|
|
28
|
+
[Symbol.asyncDispose]: host.close
|
|
29
|
+
};
|
|
30
|
+
}
|
package/dist/tool.d.ts
ADDED
package/dist/tool.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export * as OpenCodeWorkerd from "./workerd";
|
|
2
|
+
import { WorkerdProfile } from "./internal/workerd";
|
|
3
|
+
import type { LogOptions } from "./logging";
|
|
4
|
+
import { PromiseSdk } from "./promise";
|
|
5
|
+
export type Configuration = WorkerdProfile.Configuration;
|
|
6
|
+
export interface CreateOptions extends WorkerdProfile.Options {
|
|
7
|
+
readonly log?: LogOptions;
|
|
8
|
+
readonly plugins?: PromiseSdk.CreateOptions["plugins"];
|
|
9
|
+
readonly instances?: PromiseSdk.CreateOptions["instances"];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Boots the embedded opencode SDK on the workerd runtime profile: the full
|
|
13
|
+
* application graph inside a Cloudflare Durable Object, with the database on
|
|
14
|
+
* the injected `DurableObjectStorage` SQLite and every intentionally-local
|
|
15
|
+
* service replaced or disabled (see `ServerWorkerd.replacements`).
|
|
16
|
+
*
|
|
17
|
+
* Suspended Sessions resume on boot (as on every runtime) because a Durable
|
|
18
|
+
* Object can be evicted mid-turn with no teardown; the write-ahead execution
|
|
19
|
+
* claim marks the turn and the boot-time sweep replays it.
|
|
20
|
+
*
|
|
21
|
+
* Returns the same typed `OpenCode.Interface` as `OpenCode.create` — typed
|
|
22
|
+
* session operations plus the live `events.subscribe()` stream — served over
|
|
23
|
+
* an in-process fetch transport, so no request leaves the isolate.
|
|
24
|
+
*/
|
|
25
|
+
export declare const create: ({ log, plugins, instances, ...options }: CreateOptions) => Promise<PromiseSdk.Interface>;
|
|
26
|
+
export type Interface = PromiseSdk.Interface;
|
package/dist/workerd.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * as OpenCodeWorkerd from "./workerd";
|
|
2
|
+
import { WorkerdProfile } from "./internal/workerd";
|
|
3
|
+
import { PromiseSdk } from "./promise";
|
|
4
|
+
export const create = ({ log, plugins, instances, ...options }) => {
|
|
5
|
+
const profile = WorkerdProfile.make(options);
|
|
6
|
+
return PromiseSdk.create({ ...profile.options, log, plugins, instances }, { overrides: profile.replacements });
|
|
7
|
+
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,59 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"version": "2.0.0",
|
|
2
4
|
"name": "@opencode/sdk",
|
|
3
|
-
"
|
|
4
|
-
"description": "OpenCode package bootstrap — not a functional release",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/anomalyco/opencode.git"
|
|
9
|
+
"url": "git+https://github.com/anomalyco/opencode.git",
|
|
10
|
+
"directory": "packages/sdk"
|
|
9
11
|
},
|
|
10
12
|
"publishConfig": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"./effect": {
|
|
24
|
+
"import": "./dist/effect/index.js",
|
|
25
|
+
"types": "./dist/effect/index.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"./workerd": {
|
|
28
|
+
"import": "./dist/workerd.js",
|
|
29
|
+
"types": "./dist/workerd.d.ts"
|
|
30
|
+
},
|
|
31
|
+
"./workerd/effect": {
|
|
32
|
+
"import": "./dist/effect/workerd.js",
|
|
33
|
+
"types": "./dist/effect/workerd.d.ts"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "bun run script/build.ts",
|
|
38
|
+
"test": "bun test --timeout 5000",
|
|
39
|
+
"typecheck": "tsgo -b",
|
|
40
|
+
"verify:package": "bun run script/verify-package.ts"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@opencode/client": "2.0.0",
|
|
44
|
+
"@opencode/core": "2.0.0",
|
|
45
|
+
"@opencode/plugin": "2.0.0",
|
|
46
|
+
"@opencode/schema": "2.0.0",
|
|
47
|
+
"@opencode/server": "2.0.0",
|
|
48
|
+
"@opencode/util": "2.0.0",
|
|
49
|
+
"effect": "4.0.0-rc.112"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@opencode/ai": "2.0.0",
|
|
53
|
+
"@opencode/httpapi-codegen": "2.0.0",
|
|
54
|
+
"@opencode/protocol": "2.0.0",
|
|
55
|
+
"@tsconfig/bun": "1.0.9",
|
|
56
|
+
"@types/bun": "1.4.0",
|
|
57
|
+
"@typescript/native-preview": "7.0.0-dev.20251207.1"
|
|
14
58
|
}
|
|
15
59
|
}
|