mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-06 01:00:54 -04:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b234b3321 | |||
| 7f47d258e9 | |||
| d311e2b822 |
@@ -321,7 +321,6 @@
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@npmcli/config": "10.8.1",
|
||||
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
|
||||
"@opencode-ai/effect-sqlite-node": "workspace:*",
|
||||
"@opencode-ai/llm": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
@@ -447,18 +446,6 @@
|
||||
"@typescript/native-preview": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "1.18.14",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.18.14",
|
||||
@@ -482,6 +469,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/luxon": "catalog:",
|
||||
@@ -1968,8 +1956,6 @@
|
||||
|
||||
"@opencode-ai/effect-drizzle-sqlite": ["@opencode-ai/effect-drizzle-sqlite@workspace:packages/effect-drizzle-sqlite"],
|
||||
|
||||
"@opencode-ai/effect-sqlite-node": ["@opencode-ai/effect-sqlite-node@workspace:packages/effect-sqlite-node"],
|
||||
|
||||
"@opencode-ai/enterprise": ["@opencode-ai/enterprise@workspace:packages/enterprise"],
|
||||
|
||||
"@opencode-ai/function": ["@opencode-ai/function@workspace:packages/function"],
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
@@ -90,7 +90,6 @@
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@npmcli/config": "10.8.1",
|
||||
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
|
||||
"@opencode-ai/effect-sqlite-node": "workspace:*",
|
||||
"@opencode-ai/llm": "workspace:*",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export * as BackgroundJob from "./background-job"
|
||||
|
||||
import { Cause, Clock, Context, Deferred, Effect, Exit, Layer, Scope, SynchronizedRef } from "effect"
|
||||
import { Identifier } from "./id/id"
|
||||
import { JobID } from "@opencode-ai/schema/job-id"
|
||||
import { makeGlobalNode } from "./effect/app-node"
|
||||
|
||||
export type Status = "running" | "completed" | "error" | "cancelled"
|
||||
@@ -202,7 +202,7 @@ export const make = Effect.gen(function* () {
|
||||
const start: Interface["start"] = Effect.fn("BackgroundJob.start")(function* (input) {
|
||||
return yield* Effect.uninterruptibleMask((restore) =>
|
||||
Effect.gen(function* () {
|
||||
const id = input.id ?? Identifier.ascending("job")
|
||||
const id = input.id ?? JobID.create()
|
||||
const started_at = yield* Clock.currentTimeMillis
|
||||
const done = yield* Deferred.make<Info>()
|
||||
const promoted = yield* Deferred.make<Info>()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
File to save in: ~/.local/share/opencode/worktree/012780/location-layer-tiers/packages/core/src/effect/
|
||||
@@ -191,8 +191,6 @@ function sameBytes(left: Uint8Array, right: Uint8Array) {
|
||||
return left.every((byte, index) => byte === right[index])
|
||||
}
|
||||
|
||||
export const locationLayer = layer
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [FSUtil.node] })
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,13 +31,6 @@ const DARWIN_LIBRARY = [
|
||||
const DARWIN_ROOT = ["/.DocumentRevisions-V100", "/.Spotlight-V100", "/.Trashes", "/.fseventsd"]
|
||||
const WIN32_HOME = ["AppData", "Downloads", "Desktop", "Documents", "Pictures", "Music", "Videos", "OneDrive"]
|
||||
|
||||
/** Directory basenames to skip when scanning the home directory. */
|
||||
export function names(): ReadonlySet<string> {
|
||||
if (process.platform === "darwin") return new Set(DARWIN_HOME)
|
||||
if (process.platform === "win32") return new Set(WIN32_HOME)
|
||||
return new Set()
|
||||
}
|
||||
|
||||
/** Absolute paths that should never be watched, stated, or scanned. */
|
||||
export function paths(): string[] {
|
||||
if (process.platform === "darwin")
|
||||
|
||||
@@ -234,6 +234,4 @@ export const fffLayer = Layer.effect(
|
||||
|
||||
const layer = Layer.unwrap(Effect.sync(() => (Flag.OPENCODE_DISABLE_FFF || !Fff.available() ? ripgrepLayer : fffLayer)))
|
||||
|
||||
export const locationLayer = layer
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [FSUtil.node, Location.node, Ripgrep.node] })
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { create as createIdentifier } from "@opencode-ai/schema/identifier"
|
||||
|
||||
const prefixes = {
|
||||
job: "job",
|
||||
event: "evt",
|
||||
session: "ses",
|
||||
message: "msg",
|
||||
permission: "per",
|
||||
question: "que",
|
||||
part: "prt",
|
||||
pty: "pty",
|
||||
tool: "tool",
|
||||
workspace: "wrk",
|
||||
} as const
|
||||
|
||||
export function ascending(prefix: keyof typeof prefixes, given?: string) {
|
||||
return generateID(prefix, "ascending", given)
|
||||
}
|
||||
|
||||
export function descending(prefix: keyof typeof prefixes, given?: string) {
|
||||
return generateID(prefix, "descending", given)
|
||||
}
|
||||
|
||||
function generateID(prefix: keyof typeof prefixes, direction: "descending" | "ascending", given?: string): string {
|
||||
if (!given) {
|
||||
return create(prefixes[prefix], direction)
|
||||
}
|
||||
|
||||
if (!given.startsWith(prefixes[prefix])) {
|
||||
throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`)
|
||||
}
|
||||
return given
|
||||
}
|
||||
|
||||
export function create(prefix: string, direction: "descending" | "ascending", timestamp?: number): string {
|
||||
return prefix + "_" + createIdentifier(direction === "descending", timestamp)
|
||||
}
|
||||
|
||||
/** Extract timestamp from an ascending ID. Does not work with descending IDs. */
|
||||
export function timestamp(id: string): number {
|
||||
const prefix = id.split("_")[0]
|
||||
const hex = id.slice(prefix.length + 1, prefix.length + 13)
|
||||
const encoded = BigInt("0x" + hex)
|
||||
return Number(encoded / BigInt(0x1000))
|
||||
}
|
||||
|
||||
export * as Identifier from "./id"
|
||||
@@ -76,6 +76,4 @@ const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer.pipe(Layer.provide(Config.locationLayer))
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [Config.node] })
|
||||
|
||||
@@ -153,8 +153,6 @@ const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer.pipe(Layer.orDie),
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
export * as LayerMapExample from "./layer-map.example"
|
||||
|
||||
import { Context, Effect, Layer, LayerMap } from "effect"
|
||||
import { Npm } from "../npm"
|
||||
|
||||
/**
|
||||
* Tutorial: split global services from context-specific services.
|
||||
*
|
||||
* Use this pattern when part of the app should be constructed once at the app edge,
|
||||
* while another part should be cached per request/project/workspace key.
|
||||
*
|
||||
* In this example:
|
||||
* - Npm.Service is the global service. It is not keyed by request context and should
|
||||
* be provided once by the application runtime.
|
||||
* - ConfigService is context-specific. It is built from a RequestContext key and is
|
||||
* cached by LayerMap for that key.
|
||||
* - ConfigServiceMap.layer owns the cache. Provide it once globally, then each
|
||||
* request can provide ConfigServiceMap.get(context) to select the right instance.
|
||||
*
|
||||
* Lifetime model:
|
||||
* - ConfigServiceMap.layer has the app/global lifetime and depends on Npm.Service.
|
||||
* - ConfigServiceMap.get(context) has the request/context lifetime and provides
|
||||
* ConfigService for exactly that context key.
|
||||
* - The cached ConfigService entry stays alive while something is using it. Once idle,
|
||||
* it remains cached for idleTimeToLive, then its scope is finalized.
|
||||
* - invalidate(context) removes the cache entry for future lookups. Active users keep
|
||||
* running on the old instance; the next lookup can create a fresh instance.
|
||||
*
|
||||
* Key model:
|
||||
* - Keys can be strings, structs, classes, arrays, etc.
|
||||
* - Prefer primitive or immutable keys. Effect uses Hash / Equal semantics for cache
|
||||
* lookup, so mutating an object after it has been used as a key is a bug.
|
||||
*/
|
||||
|
||||
export type RequestContext = {
|
||||
readonly directory: string
|
||||
readonly workspace: string
|
||||
}
|
||||
|
||||
export class RequestContextRef extends Context.Service<RequestContextRef, RequestContext>()(
|
||||
"@opencode/example/RequestContextRef",
|
||||
) {}
|
||||
|
||||
export interface ConfigServiceShape {
|
||||
readonly directory: string
|
||||
readonly workspace: string
|
||||
readonly nextUse: () => Effect.Effect<number>
|
||||
readonly which: Npm.Interface["which"]
|
||||
}
|
||||
|
||||
export class ConfigService extends Context.Service<ConfigService, ConfigServiceShape>()(
|
||||
"@opencode/example/ConfigService",
|
||||
) {}
|
||||
|
||||
const configServiceLayer = Layer.effect(
|
||||
ConfigService,
|
||||
Effect.gen(function* () {
|
||||
const context = yield* RequestContextRef
|
||||
const npm = yield* Npm.Service
|
||||
|
||||
let useCount = 0
|
||||
|
||||
return ConfigService.of({
|
||||
directory: context.directory,
|
||||
workspace: context.workspace,
|
||||
nextUse: () => Effect.succeed(++useCount),
|
||||
which: npm.which,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
export class ConfigServiceMap extends LayerMap.Service<ConfigServiceMap>()("@opencode/example/ConfigServiceMap", {
|
||||
lookup: (context: RequestContext) =>
|
||||
configServiceLayer.pipe(Layer.provide(Layer.succeed(RequestContextRef, RequestContextRef.of(context)))),
|
||||
idleTimeToLive: "5 minutes",
|
||||
}) {}
|
||||
|
||||
export const appLayer = ConfigServiceMap.layer
|
||||
|
||||
export const readConfig = Effect.fn("LayerMapExample.readConfig")(function* () {
|
||||
const config = yield* ConfigService
|
||||
|
||||
return {
|
||||
directory: config.directory,
|
||||
workspace: config.workspace,
|
||||
useCount: yield* config.nextUse(),
|
||||
}
|
||||
})
|
||||
|
||||
export const handleRequest = Effect.fn("LayerMapExample.handleRequest")(function* (context: RequestContext) {
|
||||
return yield* readConfig().pipe(Effect.provide(ConfigServiceMap.get(context)))
|
||||
})
|
||||
|
||||
export const invalidateContext = (context: RequestContext) => ConfigServiceMap.invalidate(context)
|
||||
@@ -278,7 +278,6 @@ const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
||||
@@ -313,6 +313,4 @@ const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer.pipe(Layer.provide(Config.locationLayer))
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [EventV2.node, Location.node, Config.node] })
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export * as PublicEventManifest from "./public-event-manifest"
|
||||
|
||||
import { Event } from "@opencode-ai/schema/event"
|
||||
import { EventManifest } from "@opencode-ai/schema/event-manifest"
|
||||
|
||||
export const Definitions = EventManifest.ServerDefinitions
|
||||
export const Latest = Event.latest(Definitions)
|
||||
@@ -148,6 +148,4 @@ const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [EventV2.node] })
|
||||
|
||||
@@ -64,6 +64,4 @@ const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [Reference.node] })
|
||||
|
||||
@@ -31,14 +31,6 @@ export type EnsureInput = {
|
||||
readonly branch?: string
|
||||
}
|
||||
|
||||
export class InvalidRepositoryError extends Schema.TaggedErrorClass<InvalidRepositoryError>()(
|
||||
"RepositoryCacheInvalidRepositoryError",
|
||||
{
|
||||
repository: Schema.String,
|
||||
message: Schema.String,
|
||||
},
|
||||
) {}
|
||||
|
||||
export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchError>()(
|
||||
"RepositoryCacheInvalidBranchError",
|
||||
{
|
||||
@@ -86,7 +78,6 @@ export class CacheOperationError extends Schema.TaggedErrorClass<CacheOperationE
|
||||
) {}
|
||||
|
||||
export type Error =
|
||||
| InvalidRepositoryError
|
||||
| InvalidBranchError
|
||||
| CloneFailedError
|
||||
| FetchFailedError
|
||||
@@ -101,9 +92,8 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/RepositoryCache") {}
|
||||
|
||||
export function isError(error: unknown): error is Error {
|
||||
function isError(error: unknown): error is Error {
|
||||
return (
|
||||
error instanceof InvalidRepositoryError ||
|
||||
error instanceof InvalidBranchError ||
|
||||
error instanceof CloneFailedError ||
|
||||
error instanceof FetchFailedError ||
|
||||
@@ -114,14 +104,7 @@ export function isError(error: unknown): error is Error {
|
||||
)
|
||||
}
|
||||
|
||||
export const parseRemote = Effect.fn("RepositoryCache.parseRemote")(function* (repository: string) {
|
||||
return yield* Effect.try({
|
||||
try: () => Repository.parseRemote(repository),
|
||||
catch: (error) => new InvalidRepositoryError({ repository, message: errorMessage(error) }),
|
||||
})
|
||||
})
|
||||
|
||||
export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
|
||||
const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
|
||||
return yield* Effect.try({
|
||||
try: () => Repository.validateBranch(branch),
|
||||
catch: (error) => new InvalidBranchError({ branch, message: errorMessage(error) }),
|
||||
|
||||
@@ -46,14 +46,6 @@ export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchErr
|
||||
|
||||
export type Error = InvalidReferenceError | UnsupportedLocalRepositoryError | InvalidBranchError
|
||||
|
||||
export function isError(error: unknown): error is Error {
|
||||
return (
|
||||
error instanceof InvalidReferenceError ||
|
||||
error instanceof UnsupportedLocalRepositoryError ||
|
||||
error instanceof InvalidBranchError
|
||||
)
|
||||
}
|
||||
|
||||
export function parse(input: string): Reference | undefined {
|
||||
const cleaned = normalizeInput(input)
|
||||
if (!cleaned) return
|
||||
|
||||
@@ -71,6 +71,4 @@ const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [SkillV2.node] })
|
||||
|
||||
@@ -227,8 +227,6 @@ const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const locationLayer = layer.pipe(Layer.provideMerge(Config.locationLayer))
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
|
||||
@@ -2,12 +2,12 @@ export * as ToolOutputStore from "./tool-output-store"
|
||||
|
||||
import path from "path"
|
||||
import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect"
|
||||
import { ascending } from "@opencode-ai/schema/identifier"
|
||||
import { Config } from "./config"
|
||||
import { FSUtil } from "./fs-util"
|
||||
import { Global } from "./global"
|
||||
import { makeGlobalNode, makeLocationNode } from "./effect/app-node"
|
||||
import { SessionSchema } from "./session/schema"
|
||||
import { Identifier } from "./util/identifier"
|
||||
import type { ToolOutput } from "@opencode-ai/llm"
|
||||
|
||||
export const MAX_LINES = 2_000
|
||||
@@ -127,7 +127,7 @@ const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const write = Effect.fn("ToolOutputStore.write")(function* (content: string) {
|
||||
const file = path.join(directory, `tool_${Identifier.ascending()}`)
|
||||
const file = path.join(directory, `tool_${ascending()}`)
|
||||
yield* fs.ensureDir(directory).pipe(Effect.mapError((cause) => new StorageError({ operation: "write", cause })))
|
||||
yield* fs
|
||||
.writeFileString(file, content, { flag: "wx" })
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * as Identifier from "@opencode-ai/schema/identifier"
|
||||
@@ -12,12 +12,6 @@ export function getDirectory(path: string | undefined) {
|
||||
return parts.slice(0, parts.length - 1).join("/") + "/"
|
||||
}
|
||||
|
||||
export function getFileExtension(path: string | undefined) {
|
||||
if (!path) return ""
|
||||
const parts = path.split(".")
|
||||
return parts[parts.length - 1]
|
||||
}
|
||||
|
||||
export function getFilenameTruncated(path: string | undefined, maxLength: number = 20) {
|
||||
const filename = getFilename(path)
|
||||
if (filename.length <= maxLength) return filename
|
||||
@@ -27,11 +21,3 @@ export function getFilenameTruncated(path: string | undefined, maxLength: number
|
||||
if (available <= 0) return filename.slice(0, maxLength - 1) + "…"
|
||||
return filename.slice(0, available) + "…" + ext
|
||||
}
|
||||
|
||||
export function truncateMiddle(text: string, maxLength: number = 20) {
|
||||
if (text.length <= maxLength) return text
|
||||
const available = maxLength - 1 // -1 for ellipsis
|
||||
const start = Math.ceil(available / 2)
|
||||
const end = Math.floor(available / 2)
|
||||
return text.slice(0, start) + "…" + text.slice(-end)
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export * as V2Schema from "./v2-schema"
|
||||
|
||||
export { DateTimeUtcFromMillis } from "@opencode-ai/schema/schema"
|
||||
@@ -4,7 +4,6 @@ import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Repository } from "@opencode-ai/core/repository"
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache"
|
||||
@@ -105,9 +104,6 @@ describe("RepositoryCache", () => {
|
||||
withRemote((fixture) =>
|
||||
Effect.gen(function* () {
|
||||
const cache = yield* RepositoryCache.Service
|
||||
const invalidRepository = yield* Effect.flip(RepositoryCache.parseRemote("not-a-repo"))
|
||||
expect(invalidRepository).toBeInstanceOf(RepositoryCache.InvalidRepositoryError)
|
||||
|
||||
const invalidBranch = yield* Effect.flip(cache.ensure({ reference: fixture.reference, branch: "../unsafe" }))
|
||||
expect(invalidBranch).toBeInstanceOf(RepositoryCache.InvalidBranchError)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import { Pty } from "@opencode-ai/schema/pty"
|
||||
import { Reference } from "@opencode-ai/schema/reference"
|
||||
import { SessionTodo } from "@opencode-ai/schema/session-todo"
|
||||
import { Skill } from "@opencode-ai/schema/skill"
|
||||
import { AbsolutePath, DateTimeUtcFromMillis, optional, statics } from "@opencode-ai/schema/schema"
|
||||
import { AbsolutePath, optional, statics } from "@opencode-ai/schema/schema"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||
|
||||
@@ -51,7 +51,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||
coreSessionTodo,
|
||||
corePrompt,
|
||||
coreSkill,
|
||||
coreV2Schema,
|
||||
coreSchema,
|
||||
coreWorkspace,
|
||||
] = await Promise.all([
|
||||
@@ -73,7 +72,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||
import("@opencode-ai/core/session/todo"),
|
||||
import("@opencode-ai/core/session/prompt"),
|
||||
import("@opencode-ai/core/skill"),
|
||||
import("@opencode-ai/core/v2-schema"),
|
||||
import("@opencode-ai/core/schema"),
|
||||
import("@opencode-ai/core/workspace"),
|
||||
])
|
||||
@@ -173,7 +171,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||
[coreSkill.EmbeddedSource, Skill.EmbeddedSource],
|
||||
[coreSkill.Source, Skill.Source],
|
||||
[coreSkill.Info, Skill.Info],
|
||||
[coreV2Schema.DateTimeUtcFromMillis, DateTimeUtcFromMillis],
|
||||
[coreSchema.optional, optional],
|
||||
[coreSchema.statics, statics],
|
||||
[coreWorkspace.ID, Workspace.ID],
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { $ } from "bun"
|
||||
import * as path from "node:path"
|
||||
|
||||
import { RUST_TARGET } from "./utils"
|
||||
|
||||
if (!RUST_TARGET) throw new Error("RUST_TARGET not defined")
|
||||
|
||||
const BUNDLE_DIR = "dist"
|
||||
const BUNDLES_OUT_DIR = path.join(process.cwd(), "dist/bundles")
|
||||
|
||||
await $`mkdir -p ${BUNDLES_OUT_DIR}`
|
||||
await $`cp -r ${BUNDLE_DIR}/* ${BUNDLES_OUT_DIR}`
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.14",
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"effect": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
export * as NodeSqliteClient from "./index"
|
||||
|
||||
import { DatabaseSync, type SQLInputValue } from "node:sqlite"
|
||||
import { identity } from "effect/Function"
|
||||
import * as Context from "effect/Context"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Fiber from "effect/Fiber"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as Scope from "effect/Scope"
|
||||
import * as Semaphore from "effect/Semaphore"
|
||||
import * as Stream from "effect/Stream"
|
||||
import * as Reactivity from "effect/unstable/reactivity/Reactivity"
|
||||
import * as Client from "effect/unstable/sql/SqlClient"
|
||||
import type { Connection } from "effect/unstable/sql/SqlConnection"
|
||||
import { classifySqliteError, SqlError } from "effect/unstable/sql/SqlError"
|
||||
import * as Statement from "effect/unstable/sql/Statement"
|
||||
|
||||
const ATTR_DB_SYSTEM_NAME = "db.system.name"
|
||||
|
||||
export const TypeId: TypeId = "~@opencode-ai/effect-sqlite-node/NodeSqliteClient"
|
||||
export type TypeId = "~@opencode-ai/effect-sqlite-node/NodeSqliteClient"
|
||||
|
||||
export interface SqliteClient extends Client.SqlClient {
|
||||
readonly [TypeId]: TypeId
|
||||
readonly config: SqliteClientConfig
|
||||
readonly loadExtension: (path: string) => Effect.Effect<void, SqlError>
|
||||
readonly updateValues: never
|
||||
}
|
||||
|
||||
export const SqliteClient = Context.Service<SqliteClient>("@opencode-ai/effect-sqlite-node/NodeSqliteClient")
|
||||
|
||||
export interface SqliteClientConfig {
|
||||
readonly filename: string
|
||||
readonly readonly?: boolean | undefined
|
||||
readonly create?: boolean | undefined
|
||||
readonly readwrite?: boolean | undefined
|
||||
readonly disableWAL?: boolean | undefined
|
||||
readonly timeout?: number | undefined
|
||||
readonly allowExtension?: boolean | undefined
|
||||
readonly spanAttributes?: Record<string, unknown> | undefined
|
||||
readonly transformResultNames?: ((str: string) => string) | undefined
|
||||
readonly transformQueryNames?: ((str: string) => string) | undefined
|
||||
}
|
||||
|
||||
interface SqliteConnection extends Connection {
|
||||
readonly loadExtension: (path: string) => Effect.Effect<void, SqlError>
|
||||
}
|
||||
|
||||
export const make = (
|
||||
options: SqliteClientConfig,
|
||||
): Effect.Effect<SqliteClient, never, Scope.Scope | Reactivity.Reactivity> =>
|
||||
Effect.gen(function* () {
|
||||
const compiler = Statement.makeCompilerSqlite(options.transformQueryNames)
|
||||
const transformRows = options.transformResultNames
|
||||
? Statement.defaultTransforms(options.transformResultNames).array
|
||||
: undefined
|
||||
|
||||
const makeConnection = Effect.gen(function* () {
|
||||
const db = new DatabaseSync(options.filename, {
|
||||
readOnly: options.readonly,
|
||||
timeout: options.timeout,
|
||||
allowExtension: options.allowExtension,
|
||||
enableForeignKeyConstraints: true,
|
||||
open: true,
|
||||
})
|
||||
yield* Effect.addFinalizer(() => Effect.sync(() => db.close()))
|
||||
|
||||
if (options.disableWAL !== true && options.readonly !== true) {
|
||||
db.exec("PRAGMA journal_mode = WAL;")
|
||||
}
|
||||
|
||||
const run = (sql: string, params: ReadonlyArray<unknown> = []) =>
|
||||
Effect.withFiber<Array<Record<string, unknown>>, SqlError>((fiber) => {
|
||||
const statement = db.prepare(sql)
|
||||
statement.setReadBigInts(Context.get(fiber.context, Client.SafeIntegers))
|
||||
try {
|
||||
return Effect.succeed(statement.all(...(params as SQLInputValue[])) as Array<Record<string, unknown>>)
|
||||
} catch (cause) {
|
||||
return Effect.fail(
|
||||
new SqlError({
|
||||
reason: classifySqliteError(cause, { message: "Failed to execute statement", operation: "execute" }),
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const runValues = (sql: string, params: ReadonlyArray<unknown> = []) =>
|
||||
Effect.withFiber<ReadonlyArray<ReadonlyArray<unknown>>, SqlError>((fiber) => {
|
||||
const statement = db.prepare(sql)
|
||||
statement.setReadBigInts(Context.get(fiber.context, Client.SafeIntegers))
|
||||
statement.setReturnArrays(true)
|
||||
try {
|
||||
return Effect.succeed(
|
||||
statement.all(...(params as SQLInputValue[])) as unknown as ReadonlyArray<ReadonlyArray<unknown>>,
|
||||
)
|
||||
} catch (cause) {
|
||||
return Effect.fail(
|
||||
new SqlError({
|
||||
reason: classifySqliteError(cause, { message: "Failed to execute statement", operation: "execute" }),
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return identity<SqliteConnection>({
|
||||
execute(sql, params, transformRows) {
|
||||
return transformRows ? Effect.map(run(sql, params), transformRows) : run(sql, params)
|
||||
},
|
||||
executeRaw(sql, params) {
|
||||
return run(sql, params)
|
||||
},
|
||||
executeValues(sql, params) {
|
||||
return runValues(sql, params)
|
||||
},
|
||||
executeUnprepared(sql, params, transformRows) {
|
||||
return this.execute(sql, params, transformRows)
|
||||
},
|
||||
executeStream() {
|
||||
return Stream.die("executeStream not implemented")
|
||||
},
|
||||
loadExtension: (path) =>
|
||||
Effect.try({
|
||||
try: () => db.loadExtension(path),
|
||||
catch: (cause) =>
|
||||
new SqlError({
|
||||
reason: classifySqliteError(cause, { message: "Failed to load extension", operation: "loadExtension" }),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
const semaphore = yield* Semaphore.make(1)
|
||||
const connection = yield* makeConnection
|
||||
const acquirer = semaphore.withPermits(1)(Effect.succeed(connection))
|
||||
const transactionAcquirer = Effect.uninterruptibleMask((restore) => {
|
||||
const fiber = Fiber.getCurrent()!
|
||||
const scope = Context.getUnsafe(fiber.context, Scope.Scope)
|
||||
return Effect.as(
|
||||
Effect.tap(restore(semaphore.take(1)), () => Scope.addFinalizer(scope, semaphore.release(1))),
|
||||
connection,
|
||||
)
|
||||
})
|
||||
|
||||
return Object.assign(
|
||||
(yield* Client.make({
|
||||
acquirer,
|
||||
compiler,
|
||||
transactionAcquirer,
|
||||
spanAttributes: [
|
||||
...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
|
||||
[ATTR_DB_SYSTEM_NAME, "sqlite"],
|
||||
],
|
||||
transformRows,
|
||||
})) as SqliteClient,
|
||||
{
|
||||
[TypeId]: TypeId as TypeId,
|
||||
config: options,
|
||||
loadExtension: (path: string) => Effect.flatMap(acquirer, (_) => _.loadExtension(path)),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
export const layer = (config: SqliteClientConfig): Layer.Layer<SqliteClient | Client.SqlClient> =>
|
||||
Layer.effectContext(
|
||||
Effect.map(make(config), (client) =>
|
||||
Context.make(SqliteClient, client).pipe(Context.add(Client.SqlClient, client)),
|
||||
),
|
||||
).pipe(Layer.provide(Reactivity.layer))
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@effect/language-service",
|
||||
"transform": "@effect/language-service/transform",
|
||||
"namespaceImportPackages": ["effect", "@effect/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { descending } from "@opencode-ai/schema/identifier"
|
||||
import { SessionID } from "@opencode-ai/schema/session-id"
|
||||
import { Share } from "../../src/core/share"
|
||||
import { Storage } from "../../src/core/storage"
|
||||
import { Identifier } from "@opencode-ai/core/util/identifier"
|
||||
|
||||
describe.concurrent("core.share", () => {
|
||||
test("should create a share", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
expect(share.sessionID).toBe(sessionID)
|
||||
@@ -15,7 +16,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should remove a share as admin", async () => {
|
||||
const share = await Share.create({ sessionID: Identifier.descending() })
|
||||
const share = await Share.create({ sessionID: SessionID.create() })
|
||||
|
||||
await Share.removeAdmin({ id: share.id })
|
||||
|
||||
@@ -23,7 +24,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should sync data to a share", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data: Share.Data[] = [
|
||||
@@ -45,7 +46,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should sync multiple batches of data", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data1: Share.Data[] = [
|
||||
@@ -79,7 +80,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should retrieve synced data", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data: Share.Data[] = [
|
||||
@@ -108,7 +109,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should retrieve data from multiple syncs", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data1: Share.Data[] = [
|
||||
@@ -154,7 +155,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should return latest data when syncing duplicate parts", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data1: Share.Data[] = [
|
||||
@@ -192,7 +193,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should return empty array for share with no data", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const result = await Share.data(share.id)
|
||||
@@ -203,7 +204,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should migrate legacy event data into the snapshot", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
const data: Share.Data[] = [
|
||||
{
|
||||
@@ -213,7 +214,7 @@ describe.concurrent("core.share", () => {
|
||||
]
|
||||
|
||||
await Storage.remove(["share_snapshot", share.id])
|
||||
await Storage.write(["share_event", share.id, Identifier.descending()], data)
|
||||
await Storage.write(["share_event", share.id, descending()], data)
|
||||
|
||||
const result = await Share.data(share.id)
|
||||
const snapshot = await Storage.read<{ data: Share.Data[] }>(["share_snapshot", share.id])
|
||||
@@ -225,7 +226,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should throw error for invalid secret", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data: Share.Data[] = [
|
||||
@@ -246,7 +247,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should throw error for non-existent share", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const data: Share.Data[] = [
|
||||
{
|
||||
type: "part",
|
||||
@@ -263,7 +264,7 @@ describe.concurrent("core.share", () => {
|
||||
})
|
||||
|
||||
test("should handle different data types", async () => {
|
||||
const sessionID = Identifier.descending()
|
||||
const sessionID = SessionID.create()
|
||||
const share = await Share.create({ sessionID })
|
||||
|
||||
const data: Share.Data[] = [
|
||||
|
||||
@@ -1,591 +0,0 @@
|
||||
# LLM Call Site Sketches
|
||||
|
||||
Scratchpad for examples first, abstractions second. Current direction: routes
|
||||
execute, provider facades organize configured route sets, and models carry route
|
||||
values directly.
|
||||
|
||||
## Conversation Summary
|
||||
|
||||
Kit and Aidan want provider-specific LLM behavior to move out of opencode's AI
|
||||
SDK transform path and into `packages/llm` where possible. The goal is not a big
|
||||
generic transform layer; the goal is small composable route definitions backed by
|
||||
recorded golden tests.
|
||||
|
||||
Things to keep testing against:
|
||||
|
||||
- Cache placement: `cache: "auto"`, manual cache breakpoints, provider cache usage.
|
||||
- Images: golden image tests for providers/protocols that claim image support.
|
||||
- Reasoning: canonical reasoning parts/events versus provider-native knobs.
|
||||
- Auth: bearer, custom headers, multiple credentials, query auth, SigV4, OAuth, no auth.
|
||||
- OpenAI-compatible providers: DeepSeek, Together, Groq, Alibaba/DashScope, custom routers.
|
||||
- Provider switching: stale signatures, encrypted reasoning, provider metadata, incompatible parts.
|
||||
- Error quality: typed errors instead of generic SDK/server failures.
|
||||
|
||||
## Final Guide: Routes Execute, Providers Organize
|
||||
|
||||
Do not introduce a first-class `Deployment` abstraction unless it gains real
|
||||
semantics. Provider facades are ergonomic configured route groups, not execution
|
||||
registries. The executable/composable thing is still a route. Do not make route
|
||||
construction publish to a global registry; models should carry their route value
|
||||
directly.
|
||||
|
||||
Keep durable identity separate from runtime capability:
|
||||
|
||||
- Durable identity is small serializable data like `{ providerID, modelID }` for
|
||||
config, sessions, logs, and catalogs.
|
||||
- Runtime capability is a `Model` with a route value, protocol, transport, auth,
|
||||
and defaults. It is allowed to contain functions and schemas.
|
||||
- If persisted identity needs to become executable, resolve it through an app
|
||||
boundary first. Do not make `LLMRequest` recover behavior from a global route
|
||||
side table.
|
||||
|
||||
Keep unconfigured behavior values as values, not factories. A transport like
|
||||
`HttpTransport.sseJson` should be a reusable immutable value. Use a function only
|
||||
when the caller supplies options or when construction needs fresh state.
|
||||
|
||||
Use constants to remove repetition before inventing abstractions. Provider ids
|
||||
are branded once per provider facade and reused across routes; a plain exported
|
||||
object is enough for the provider-facing API unless a helper earns its keep by
|
||||
removing repeated route projection.
|
||||
|
||||
Expose default configured provider instances, and put provider-specific setup on
|
||||
`.configure(...)`. Model selectors stay pure: `model(id)`, `responses(id)`,
|
||||
`chat(id)`, etc. Endpoint/auth/resource/api-version configuration happens before
|
||||
model selection, not as a second argument to model selection.
|
||||
|
||||
Use provider/product facades consistently:
|
||||
|
||||
- One coherent provider/product config surface gets one top-level facade.
|
||||
- APIs/model kinds that share that config are methods on the facade.
|
||||
- Different products with different required config get separate top-level
|
||||
facades, not a shared namespace with unrelated children.
|
||||
- Default facades are exposed only when concrete defaults or lazy env/credential
|
||||
defaults make the facade valid.
|
||||
|
||||
Examples:
|
||||
|
||||
```ts
|
||||
OpenAI.responses("gpt-4o")
|
||||
OpenAI.chat("gpt-4o")
|
||||
OpenAI.responsesWebSocket("gpt-4o")
|
||||
|
||||
Azure.configure({ resourceName, apiKey }).responses("my-deployment")
|
||||
AmazonBedrock.configure({ region, credentials }).model("anthropic.claude-3-5-sonnet-20241022-v2:0")
|
||||
|
||||
CloudflareAIGateway.configure({ accountId, gatewayId, gatewayApiKey, apiKey }).model("openai/gpt-4o")
|
||||
CloudflareWorkersAI.configure({ accountId, apiKey }).model("@cf/meta/llama-3.1-8b-instruct")
|
||||
|
||||
OpenAICompatible.configure({
|
||||
provider: "custom",
|
||||
baseURL: "https://custom.example/v1",
|
||||
auth: Auth.bearer(apiKey),
|
||||
}).model("custom-model")
|
||||
```
|
||||
|
||||
Standardize the provider facade contract before abstracting construction. A
|
||||
plain object is enough at first; add a helper only if repeated route projection
|
||||
starts hiding the real provider-specific config.
|
||||
|
||||
`Route.with(...)` patch semantics should be boring and explicit:
|
||||
|
||||
- Omitted fields inherit from the original route.
|
||||
- `endpoint` patches merge with the existing endpoint, so overriding `baseURL`
|
||||
keeps the existing `path`.
|
||||
- `endpoint.query` merges by default; later values win.
|
||||
- `auth` replaces.
|
||||
- `headers` merge by default; undefined values are omitted.
|
||||
- `id` is optional in patches. Route ids are diagnostic/provider API labels, not
|
||||
global runtime registry keys.
|
||||
|
||||
1. **Route**
|
||||
- route id
|
||||
- provider id
|
||||
- protocol
|
||||
- body schema
|
||||
- body builder
|
||||
- stream event schema
|
||||
- parser/state machine
|
||||
- transport
|
||||
- method / IO shape
|
||||
- framing
|
||||
- request preparation
|
||||
- constants when unconfigured; functions only when configured
|
||||
- endpoint
|
||||
- base URL
|
||||
- static path
|
||||
- body/model-derived path
|
||||
- query params
|
||||
- auth
|
||||
- bearer
|
||||
- custom header
|
||||
- multiple credentials
|
||||
- SigV4
|
||||
- none
|
||||
- defaults
|
||||
- headers
|
||||
- generation defaults
|
||||
- provider options
|
||||
- limits
|
||||
2. **Provider Facade**
|
||||
- default configured provider instance
|
||||
- provider-specific `.configure(...)`
|
||||
- plain object/function facade over one or more routes
|
||||
- top-level export only when it represents one coherent config surface
|
||||
- no passive `Provider.make(...)` wrapper unless it gains runtime behavior
|
||||
3. **Model Selector**
|
||||
- route/provider-owned selector
|
||||
- accepts model id only
|
||||
- returns executable models
|
||||
- does not accept endpoint/auth/deployment overrides
|
||||
4. **Model**
|
||||
- model id
|
||||
- route value
|
||||
- provider id
|
||||
- configured route value at selection time
|
||||
5. **LLM Request**
|
||||
- model
|
||||
- messages/tools
|
||||
- generation/cache/reasoning/response-format options
|
||||
- request-level HTTP overlays for per-request headers/query/body additions,
|
||||
not provider endpoint/auth reconfiguration
|
||||
6. **Compile**
|
||||
- read route from model
|
||||
- merge route defaults and request overrides
|
||||
- build final URL from route endpoint
|
||||
- apply auth from the configured route
|
||||
- build body with protocol
|
||||
- execute with transport and parse with protocol
|
||||
|
||||
## Provider Facade Shape
|
||||
|
||||
The provider abstraction is a facade over configured routes, not the runtime
|
||||
execution mechanism:
|
||||
|
||||
```ts
|
||||
type ProviderFacade<APIs, Config> = {
|
||||
readonly id: ProviderID
|
||||
readonly model: (id: string) => Model
|
||||
readonly configure: (input?: Config) => ProviderFacade<APIs, Config>
|
||||
} & APIs
|
||||
```
|
||||
|
||||
Manual construction is fine and should be the default until duplication earns a
|
||||
helper:
|
||||
|
||||
```ts
|
||||
export const OpenAI = {
|
||||
id: openAIProvider,
|
||||
model: openAIResponses.model,
|
||||
responses: openAIResponses.model,
|
||||
chat: openAIChat.model,
|
||||
configure: configureOpenAI,
|
||||
} satisfies ProviderFacade<
|
||||
{
|
||||
responses: (id: string) => Model
|
||||
chat: (id: string) => Model
|
||||
},
|
||||
OpenAIConfig
|
||||
>
|
||||
```
|
||||
|
||||
If several providers repeat the same projection from route values to model
|
||||
methods, the helper can stay deliberately tiny:
|
||||
|
||||
```ts
|
||||
const configureOpenAI = (input: OpenAIConfig = {}) =>
|
||||
Provider.define({
|
||||
id: openAIProvider,
|
||||
routes: {
|
||||
responses: openAIResponses.with(openAIConfig(input)),
|
||||
chat: openAIChat.with(openAIConfig(input)),
|
||||
},
|
||||
default: "responses",
|
||||
configure: configureOpenAI,
|
||||
})
|
||||
|
||||
export const OpenAI = configureOpenAI()
|
||||
```
|
||||
|
||||
`Provider.define(...)` would only project route methods and preserve types:
|
||||
|
||||
```ts
|
||||
OpenAI.model("gpt-4o")
|
||||
OpenAI.responses("gpt-4o")
|
||||
OpenAI.chat("gpt-4o")
|
||||
OpenAI.configure({ apiKey }).responses("gpt-4o")
|
||||
```
|
||||
|
||||
It must not register routes, select routes dynamically, or participate in
|
||||
execution. Execution still reads the route value carried by the model.
|
||||
|
||||
## Ideal Call Sites
|
||||
|
||||
Define concrete routes for a native provider, then project them through a
|
||||
provider facade:
|
||||
|
||||
```ts
|
||||
const openAIProvider = ProviderID.make("openai")
|
||||
|
||||
const openAIResponses = Route.make({
|
||||
id: "openai-responses",
|
||||
provider: openAIProvider,
|
||||
protocol: OpenAIResponses.protocol,
|
||||
transport: HttpTransport.sseJson,
|
||||
endpoint: {
|
||||
baseURL: "https://api.openai.com/v1",
|
||||
path: "/responses",
|
||||
},
|
||||
auth: Auth.envBearer("OPENAI_API_KEY"),
|
||||
})
|
||||
|
||||
const openAIChat = Route.make({
|
||||
id: "openai-chat",
|
||||
provider: openAIProvider,
|
||||
protocol: OpenAIChat.protocol,
|
||||
transport: HttpTransport.sseJson,
|
||||
endpoint: {
|
||||
baseURL: "https://api.openai.com/v1",
|
||||
path: "/chat/completions",
|
||||
},
|
||||
auth: Auth.envBearer("OPENAI_API_KEY"),
|
||||
})
|
||||
|
||||
const openAIResponsesWebSocket = openAIResponses.with({
|
||||
id: "openai-responses-websocket",
|
||||
transport: WebSocketTransport.json,
|
||||
})
|
||||
|
||||
const openAIConfig = (input: OpenAIConfig) => ({
|
||||
endpoint: input.endpoint,
|
||||
auth: input.auth ?? (input.apiKey ? Auth.bearer(input.apiKey) : undefined),
|
||||
headers: {
|
||||
"OpenAI-Organization": input.organization,
|
||||
"OpenAI-Project": input.project,
|
||||
},
|
||||
})
|
||||
|
||||
const configureOpenAI = (input: OpenAIConfig = {}) => {
|
||||
const responses = openAIResponses.with(openAIConfig(input))
|
||||
const responsesWebSocket = openAIResponsesWebSocket.with(openAIConfig(input))
|
||||
const chat = openAIChat.with(openAIConfig(input))
|
||||
|
||||
return {
|
||||
id: openAIProvider,
|
||||
responses: responses.model,
|
||||
responsesWebSocket: responsesWebSocket.model,
|
||||
chat: chat.model,
|
||||
model: responses.model,
|
||||
configure: configureOpenAI,
|
||||
}
|
||||
}
|
||||
|
||||
export const OpenAI = configureOpenAI()
|
||||
```
|
||||
|
||||
Specialize it functionally for concrete providers:
|
||||
|
||||
```ts
|
||||
const deepSeekProvider = ProviderID.make("deepseek")
|
||||
|
||||
const deepseekChat = openAIChat.with({
|
||||
id: "deepseek-chat",
|
||||
provider: deepSeekProvider,
|
||||
endpoint: {
|
||||
baseURL: "https://api.deepseek.com/v1",
|
||||
},
|
||||
auth: Auth.envBearer("DEEPSEEK_API_KEY"),
|
||||
})
|
||||
|
||||
const configureDeepSeek = (input: OpenAICompatibleConfig = {}) => {
|
||||
const route = deepseekChat.with({
|
||||
endpoint: input.endpoint,
|
||||
auth: input.auth ?? (input.apiKey ? Auth.bearer(input.apiKey) : undefined),
|
||||
})
|
||||
|
||||
return {
|
||||
id: deepSeekProvider,
|
||||
model: route.model,
|
||||
configure: configureDeepSeek,
|
||||
}
|
||||
}
|
||||
|
||||
export const DeepSeek = {
|
||||
id: deepSeekProvider,
|
||||
model: deepseekChat.model,
|
||||
configure: configureDeepSeek,
|
||||
}
|
||||
```
|
||||
|
||||
Provider-specific configuration happens before model selection:
|
||||
|
||||
```ts
|
||||
const deepseek = DeepSeek.configure({
|
||||
endpoint: {
|
||||
baseURL: "https://proxy.example.com/v1",
|
||||
},
|
||||
auth: Auth.bearer(apiKey),
|
||||
})
|
||||
|
||||
const model = deepseek.model("deepseek-chat")
|
||||
```
|
||||
|
||||
Final request call site stays boring:
|
||||
|
||||
```ts
|
||||
const response =
|
||||
yield *
|
||||
LLM.generate(
|
||||
LLM.request({
|
||||
model: DeepSeek.model("deepseek-chat"),
|
||||
prompt: "Hello.",
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
HTTP versus WebSocket is represented as named route selectors, not as model or
|
||||
request overrides. Same protocol, different transport, different route:
|
||||
|
||||
```ts
|
||||
OpenAI.responses("gpt-4o")
|
||||
OpenAI.responsesWebSocket("gpt-4o")
|
||||
```
|
||||
|
||||
The client should not require a different public layer just because a selected
|
||||
route uses WebSocket. Use one `LLMClient.layer` with HTTP and WebSocket runtime
|
||||
capabilities available; routes that do not need WebSocket simply never touch it.
|
||||
If a WebSocket route is selected in an environment without WebSocket support,
|
||||
fail with a typed transport configuration error.
|
||||
|
||||
Azure is a route specialization with auth/path/default changes plus input
|
||||
mapping. The public API configures the Azure resource once, then selects
|
||||
deployment ids with pure model selectors:
|
||||
|
||||
```ts
|
||||
const azureProvider = ProviderID.make("azure")
|
||||
|
||||
const azureResponses = openAIResponses.with({
|
||||
id: "azure-openai-responses",
|
||||
provider: azureProvider,
|
||||
auth: Auth.envHeader("api-key", "AZURE_OPENAI_API_KEY"),
|
||||
})
|
||||
|
||||
const configureAzure = (input: AzureConfig = {}) => {
|
||||
const route = azureResponses.with({
|
||||
endpoint: {
|
||||
baseURL:
|
||||
input.baseURL ??
|
||||
Endpoint.envBaseURL(
|
||||
"AZURE_RESOURCE_NAME",
|
||||
(resourceName) => `https://${resourceName}.openai.azure.com/openai/v1`,
|
||||
),
|
||||
query: { "api-version": input.apiVersion ?? "v1" },
|
||||
},
|
||||
auth: input.apiKey ? Auth.header("api-key", input.apiKey) : Auth.envHeader("api-key", "AZURE_OPENAI_API_KEY"),
|
||||
})
|
||||
|
||||
return {
|
||||
id: azureProvider,
|
||||
model: route.model,
|
||||
responses: route.model,
|
||||
configure: configureAzure,
|
||||
}
|
||||
}
|
||||
|
||||
export const Azure = configureAzure()
|
||||
|
||||
const azure = Azure.configure({
|
||||
resourceName: "my-resource",
|
||||
apiVersion: "v1",
|
||||
})
|
||||
|
||||
const model = azure.responses("my-deployment")
|
||||
```
|
||||
|
||||
Default provider facades are only valid when required configuration has a lazy
|
||||
default source. `Azure.responses("my-deployment")` can be valid if endpoint
|
||||
resolution reads `AZURE_RESOURCE_NAME` lazily and fails with a typed
|
||||
configuration error when missing. If a provider has no sensible lazy default,
|
||||
do not expose a default model selector; expose only a configured entrypoint.
|
||||
|
||||
Cloudflare AI Gateway and Workers AI are separate product facades because their
|
||||
configuration surfaces differ. Do not make a root `Cloudflare.configure(...)`
|
||||
pretend there is one coherent Cloudflare provider configuration:
|
||||
|
||||
```ts
|
||||
const cloudflareProvider = ProviderID.make("cloudflare-ai-gateway")
|
||||
|
||||
const cloudflareOpenAIChat = openAIChat.with({
|
||||
id: "cloudflare-ai-gateway-openai-chat",
|
||||
provider: cloudflareProvider,
|
||||
auth: Auth.bearerHeader("cf-aig-authorization").andThen(Auth.bearer()),
|
||||
})
|
||||
|
||||
const configureCloudflareAIGateway = (input: CloudflareAIGatewayConfig) => {
|
||||
const route = cloudflareOpenAIChat.with({
|
||||
endpoint: {
|
||||
baseURL: `https://gateway.ai.cloudflare.com/v1/${input.accountId}/${input.gatewayId}/openai`,
|
||||
},
|
||||
auth: Auth.bearerHeader("cf-aig-authorization", input.gatewayApiKey).andThen(Auth.bearer(input.apiKey)),
|
||||
})
|
||||
|
||||
return {
|
||||
id: cloudflareProvider,
|
||||
model: (modelID: string) => route.model({ id: modelID }),
|
||||
configure: configureCloudflareAIGateway,
|
||||
}
|
||||
}
|
||||
|
||||
export const CloudflareAIGateway = {
|
||||
id: cloudflareProvider,
|
||||
configure: configureCloudflareAIGateway,
|
||||
}
|
||||
|
||||
const gateway = CloudflareAIGateway.configure({
|
||||
accountId: "account",
|
||||
gatewayId: "gateway",
|
||||
gatewayApiKey,
|
||||
apiKey,
|
||||
})
|
||||
|
||||
const model = gateway.model("openai/gpt-4o")
|
||||
```
|
||||
|
||||
If a Cloudflare product gains a full lazy env default, it can expose a direct
|
||||
selector too. Until then, omitting `CloudflareAIGateway.model(...)` makes missing
|
||||
account/gateway configuration unrepresentable.
|
||||
|
||||
opencode's dynamic runtime should construct executable models at its app
|
||||
boundary instead of exposing a giant unstructured public model constructor or a
|
||||
generic dynamic resolver:
|
||||
|
||||
```ts
|
||||
const model =
|
||||
providerID === "azure"
|
||||
? Azure.configure(resolvedAzureConfig).responses(apiModelID)
|
||||
: endpoint.websocket
|
||||
? OpenAI.responsesWebSocket(apiModelID)
|
||||
: OpenAI.responses(apiModelID)
|
||||
```
|
||||
|
||||
That boundary can branch on durable config/catalog metadata and call typed
|
||||
provider APIs directly. Transport selection belongs there too: map metadata like
|
||||
`endpoint.websocket` to `OpenAI.responsesWebSocket(apiModelID)`; otherwise use
|
||||
the normal `OpenAI.responses(apiModelID)` route. The client runtime only executes
|
||||
the route carried by the model.
|
||||
|
||||
## Competitive Shape
|
||||
|
||||
This follows the strongest parts of adjacent libraries:
|
||||
|
||||
- AI SDK: configured provider instances expose provider-specific model methods.
|
||||
- Effect AI: executable models carry provider requirements and can be resolved by
|
||||
an app boundary.
|
||||
- LiteLLM/opencode config: dynamic `providerID/modelID` branching belongs at the
|
||||
app boundary, not in the typed public provider API or a global runtime
|
||||
resolver.
|
||||
- LangChain/LlamaIndex: constructor-style config plus model id is convenient,
|
||||
but we avoid making model selection also configure endpoint/auth.
|
||||
|
||||
The chosen split is:
|
||||
|
||||
```txt
|
||||
Route = execution mechanics
|
||||
Provider facade = configured route group
|
||||
Model = selected executable model carrying route value
|
||||
App boundary = explicit durable-config -> typed-provider call
|
||||
```
|
||||
|
||||
## What This Removes
|
||||
|
||||
- No `Provider.make(...)` as a core abstraction.
|
||||
- No `Provider.make(...)` wrapper just to bind an id to model functions. Use a
|
||||
branded provider id constant and a plain exported provider facade.
|
||||
- No `Deployment.define(...)` unless future examples force it.
|
||||
- No global route registry as the normal execution path.
|
||||
- No import side effects required before a model can execute.
|
||||
- No duplicate `provider.id` object when selected models already carry provider
|
||||
id.
|
||||
- No `model(id, overrides)` escape hatch. Model selection takes the model id;
|
||||
endpoint/auth/deployment customization happens by configuring the route first.
|
||||
- No transport override on model/request. HTTP SSE versus WebSocket is a named
|
||||
route selector such as `responses` versus `responsesWebSocket`.
|
||||
- No separate public `LLMClient.layerWithWebSocket`. The runtime should expose one
|
||||
client layer with the available transport capabilities.
|
||||
- No executable `ModelRef`. The executable handle is `Model`; durable model
|
||||
identity stays separate and cannot execute on its own.
|
||||
|
||||
## Implementation Todo
|
||||
|
||||
- [x] Replace the current executable `ModelRef` with `Model`.
|
||||
- [x] Change `Model.route` to carry a route value, not a `RouteID` string.
|
||||
- [ ] Keep a separate durable model identity type for persisted/session/catalog
|
||||
data, likely `{ providerID, modelID }`, and make it clear that it cannot
|
||||
execute without resolver context.
|
||||
- [x] Change route model selectors so `route.model(id)` returns an executable
|
||||
model with the route value attached, not a globally registered route id.
|
||||
- [x] Remove the standalone `Route.model(route, defaults, mapInput)` helper;
|
||||
configured route instances own model selection.
|
||||
- [x] Remove endpoint/auth escape hatches from route model selection; callers must
|
||||
configure endpoint/auth through `route.with(...)` or provider facades before
|
||||
calling `.model(...)`.
|
||||
- [x] Remove request-shaping defaults from `Model`; selected models now carry only
|
||||
id, provider, and configured route while defaults live on routes or requests.
|
||||
- [x] Rework `LLMClient.prepare` / `stream` / `generate` to read
|
||||
`request.model.route` directly instead of calling `registeredRoute(...)`.
|
||||
- [x] Remove `Route.make(...)` global registration from the normal execution
|
||||
path; keep route ids only as diagnostics/provider API labels.
|
||||
- [x] Model endpoint as `{ baseURL, path, query }` on routes, then remove the
|
||||
current split where host/query live on the model and path lives in route
|
||||
transport setup.
|
||||
- [x] Define `Route.with(...)` with explicit patch semantics for endpoint merge,
|
||||
query merge, header merge, auth replacement, and optional diagnostic id.
|
||||
- [x] Make unconfigured transports reusable constants such as
|
||||
`HttpTransport.sseJson`; keep transport functions only for configured/fresh
|
||||
state construction.
|
||||
- [x] Collapse the public WebSocket runtime split so one `LLMClient.layer`
|
||||
exposes available transport capabilities and selected routes fail with typed
|
||||
transport config errors when a required capability is missing.
|
||||
- [x] Convert OpenAI provider APIs to provider-facade shape:
|
||||
`OpenAI.configure(config).responses(id)`, `.chat(id)`, and
|
||||
`.responsesWebSocket(id)`.
|
||||
- [x] Convert Azure to a configured facade where resource/base URL/api version
|
||||
setup happens before selecting deployment ids.
|
||||
- [x] Split Cloudflare products into separate facades such as
|
||||
`CloudflareAIGateway` and `CloudflareWorkersAI`; do not expose a shared root
|
||||
config surface unless one product actually exists.
|
||||
- [x] Migrate remaining built-in provider facades one at a time so configuration
|
||||
happens before model selection and selectors accept only ids:
|
||||
xAI, GitHub Copilot, OpenRouter, OpenAI-compatible families, Anthropic,
|
||||
Google/Gemini, and Amazon Bedrock now use configured facades such as
|
||||
`Provider.configure(options).model(id)` with named selectors where needed.
|
||||
- [ ] Decide whether a tiny `Provider.define(...)` helper is warranted after two
|
||||
or three provider conversions; start with plain objects if duplication is not
|
||||
yet painful.
|
||||
- [x] Update `packages/opencode/src/session/llm/native-request.ts` to construct
|
||||
executable models at the session boundary with explicit provider facade
|
||||
calls, mapping catalog metadata such as `endpoint.websocket` to the correct
|
||||
named route selector.
|
||||
- [ ] Update tests so direct route/provider tests assert route values are carried
|
||||
by executable models, and opencode/native tests assert boundary-based route
|
||||
selection.
|
||||
- [ ] Remove compatibility exports or stale docs only after internal call sites
|
||||
are migrated; do not keep duplicate constructor paths without an external
|
||||
compatibility need.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Default facades with required setup: should providers like Azure and Bedrock
|
||||
expose default model selectors only when all required setup has lazy env or
|
||||
credential-chain defaults? If not, omit the default selector so missing config
|
||||
is impossible at the type/API level.
|
||||
- Lazy endpoint/auth values: should `Endpoint.envBaseURL(...)` and env-backed
|
||||
auth produce typed configuration/authentication errors at compile/prepare time
|
||||
or only when executing the transport?
|
||||
- `Route.with(...)` clearing semantics: endpoint/query/header patches merge by
|
||||
default, but what is the explicit way to remove an inherited value?
|
||||
- Provider facade helper: keep plain objects until duplication hurts, or add a
|
||||
tiny `Provider.define(...)` immediately to enforce shape and method projection?
|
||||
- Auth shape: should auth stay as today's composable `Auth`, or split into an
|
||||
auth placement/strategy and credential sources?
|
||||
- Naming: is `baseURL` still the right endpoint field name, or should it be
|
||||
`origin` / `urlPrefix` to clarify that route `path` is appended?
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { Plugin } from "@opencode-ai/plugin"
|
||||
import { mkdir, rm } from "node:fs/promises"
|
||||
|
||||
export const FolderWorkspacePlugin: Plugin = async ({ experimental_workspace }) => {
|
||||
experimental_workspace.register("folder", {
|
||||
name: "Folder",
|
||||
description: "Create a blank folder",
|
||||
configure(config) {
|
||||
const rand = "" + Math.random()
|
||||
|
||||
return {
|
||||
...config,
|
||||
directory: `/tmp/folder/folder-${rand}`,
|
||||
}
|
||||
},
|
||||
async create(config) {
|
||||
if (!config.directory) return
|
||||
await mkdir(config.directory, { recursive: true })
|
||||
},
|
||||
async remove(config) {
|
||||
await rm(config.directory!, { recursive: true, force: true })
|
||||
},
|
||||
target(config) {
|
||||
return {
|
||||
type: "local",
|
||||
directory: config.directory!,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
export default FolderWorkspacePlugin
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Plugin } from "./index.js"
|
||||
import { tool } from "./tool.js"
|
||||
|
||||
export const ExamplePlugin: Plugin = async (_ctx) => {
|
||||
return {
|
||||
tool: {
|
||||
mytool: tool({
|
||||
description: "This is a custom tool",
|
||||
args: {
|
||||
foo: tool.schema.string().describe("foo"),
|
||||
},
|
||||
async execute(args) {
|
||||
return `Hello ${args.foo}!`
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { Event as SDKEvent } from "@opencode-ai/sdk/v2/types"
|
||||
import type { Stream } from "effect"
|
||||
|
||||
export type EventMap = {
|
||||
[Item in SDKEvent as Item["type"]]: Item
|
||||
}
|
||||
|
||||
export interface Event {
|
||||
subscribe<Type extends keyof EventMap>(type: Type): Stream.Stream<EventMap[Type]>
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { FileSystemEntry } from "@opencode-ai/sdk/v2/types"
|
||||
import type { Effect } from "effect"
|
||||
|
||||
export interface FileSystem {
|
||||
read(input: { readonly path: string }): Effect.Effect<{ readonly content: Uint8Array; readonly mime: string }>
|
||||
list(input?: { readonly path?: string }): Effect.Effect<FileSystemEntry[]>
|
||||
find(input: {
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory"
|
||||
readonly limit?: number
|
||||
}): Effect.Effect<FileSystemEntry[]>
|
||||
glob(input: {
|
||||
readonly pattern: string
|
||||
readonly path?: string
|
||||
readonly limit?: number
|
||||
}): Effect.Effect<readonly FileSystemEntry[]>
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export interface Location {
|
||||
readonly directory: string
|
||||
readonly project: {
|
||||
readonly directory: string
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { Effect } from "effect"
|
||||
|
||||
export interface Npm {
|
||||
add(pkg: string): Effect.Effect<
|
||||
{
|
||||
readonly directory: string
|
||||
readonly entrypoint?: string
|
||||
},
|
||||
unknown
|
||||
>
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface Path {
|
||||
readonly home: string
|
||||
readonly data: string
|
||||
readonly cache: string
|
||||
readonly config: string
|
||||
readonly state: string
|
||||
readonly temp: string
|
||||
}
|
||||
@@ -95,7 +95,7 @@ const SessionsQueryCursor = SessionsCursor.annotate({
|
||||
description: "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response.",
|
||||
})
|
||||
|
||||
export const SessionsQuery = Schema.Struct({
|
||||
const SessionsQuery = Schema.Struct({
|
||||
...SessionsQueryFields,
|
||||
directory: AbsolutePath.pipe(Schema.optional),
|
||||
project: Project.ID.pipe(Schema.optional),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Schema } from "effect"
|
||||
import { ascending } from "./identifier"
|
||||
import { statics } from "./schema"
|
||||
|
||||
export const JobID = Schema.String.check(Schema.isStartsWith("job_")).pipe(
|
||||
Schema.brand("JobID"),
|
||||
statics((schema) => ({ create: () => schema.make("job_" + ascending()) })),
|
||||
)
|
||||
export type JobID = typeof JobID.Type
|
||||
@@ -2,12 +2,12 @@ import { describe, expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import { Agent } from "../src/agent"
|
||||
import { FileSystem } from "../src/filesystem"
|
||||
import { JobID } from "../src/job-id"
|
||||
import { Model } from "../src/model"
|
||||
import { Project } from "../src/project"
|
||||
import { Pty } from "../src/pty"
|
||||
import { Question } from "../src/question"
|
||||
import { Session } from "../src/session"
|
||||
import { SessionEvent } from "../src/session-event"
|
||||
import { SessionTodo } from "../src/session-todo"
|
||||
import { optional } from "../src/schema"
|
||||
|
||||
@@ -29,6 +29,7 @@ describe("contract hygiene", () => {
|
||||
})
|
||||
|
||||
test("current ID constructors expose create", () => {
|
||||
expect(JobID.create()).toStartWith("job_")
|
||||
expect(Question.ID.create()).toStartWith("que_")
|
||||
expect(Pty.ID.create()).toStartWith("pty_")
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Effect } from "effect"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { InvalidRequestError } from "@opencode-ai/protocol/errors"
|
||||
import { SchemaErrorMiddleware } from "@opencode-ai/protocol/middleware/schema-error"
|
||||
export { SchemaErrorMiddleware } from "@opencode-ai/protocol/middleware/schema-error"
|
||||
|
||||
const REASON_LIMIT = 1024
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||
import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
|
||||
import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
import { HttpRouter, HttpServer } from "effect/unstable/http"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Layer, Option } from "effect"
|
||||
import { Api } from "./api"
|
||||
@@ -61,8 +60,3 @@ function makeRoutes<AuthError, AuthServices>(auth: Layer.Layer<ServerAuth.Config
|
||||
Layer.provide(serviceLayer),
|
||||
)
|
||||
}
|
||||
|
||||
export const routes = createRoutes()
|
||||
|
||||
export const webHandler = () =>
|
||||
HttpRouter.toWebHandler(routes.pipe(Layer.provide(HttpServer.layerServices)), { disableLogger: true })
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
[14:25:48.462] [INFO] storybook v10.2.10
|
||||
[14:25:48.749] [DEBUG] Getting package.json info for /Users/davidhill/Documents/Local/opencode/packages/storybook/package.json...
|
||||
[14:25:48.997] [INFO] Starting...
|
||||
[14:25:49.095] [DEBUG] Starting preview..
|
||||
[14:25:49.098] [WARN] 🚨 Unable to index files:
|
||||
- ./../ui/src/components/accordion.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/accordion.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/app-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/app-icon.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/avatar.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/avatar.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/basic-tool.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/basic-tool.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/checkbox.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/checkbox.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/code.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/code.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/collapsible.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/collapsible.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/context-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/context-menu.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/dialog.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dialog.stories.tsx (line 10, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/diff-changes.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-changes.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/diff-ssr.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-ssr.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/diff.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/dock-prompt.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dock-prompt.stories.tsx (line 15, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/dropdown-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dropdown-menu.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/favicon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/favicon.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/file-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/file-icon.stories.tsx (line 13, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/font.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/font.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/hover-card.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/hover-card.stories.tsx (line 13, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/icon-button.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon-button.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/image-preview.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/image-preview.stories.tsx (line 13, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/inline-input.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/inline-input.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/keybind.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/keybind.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/line-comment.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/line-comment.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/list.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/list.stories.tsx (line 15, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/logo.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/logo.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/markdown.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/markdown.stories.tsx (line 12, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/message-nav.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-nav.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/message-part.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-part.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/popover.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/popover.stories.tsx (line 16, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/progress-circle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress-circle.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/progress.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress.stories.tsx (line 15, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/provider-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/provider-icon.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/radio-group.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/radio-group.stories.tsx (line 13, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/resize-handle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/resize-handle.stories.tsx (line 17, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/select.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/select.stories.tsx (line 16, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/session-review.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-review.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/session-turn.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-turn.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/spinner.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/spinner.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/sticky-accordion-header.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/sticky-accordion-header.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/switch.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/switch.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/tabs.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tabs.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/tag.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tag.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/text-field.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-field.stories.tsx (line 14, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/text-shimmer.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-shimmer.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/toast.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/toast.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/tooltip.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tooltip.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/typewriter.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/typewriter.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
[14:25:49.109] [ERROR] Failed to build the preview
|
||||
[14:25:49.110] [ERROR] Error: [38;2;241;97;97mUnable to index files:
|
||||
- ./../ui/src/components/accordion.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/accordion.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/app-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/app-icon.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/avatar.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/avatar.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/basic-tool.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/basic-tool.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/checkbox.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/checkbox.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/code.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/code.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/collapsible.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/collapsible.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/context-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/context-menu.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/dialog.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dialog.stories.tsx (line 10, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/diff-changes.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-changes.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/diff-ssr.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-ssr.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/diff.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/dock-prompt.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dock-prompt.stories.tsx (line 15, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/dropdown-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dropdown-menu.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/favicon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/favicon.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/file-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/file-icon.stories.tsx (line 13, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/font.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/font.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/hover-card.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/hover-card.stories.tsx (line 13, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/icon-button.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon-button.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/image-preview.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/image-preview.stories.tsx (line 13, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/inline-input.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/inline-input.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/keybind.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/keybind.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/line-comment.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/line-comment.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/list.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/list.stories.tsx (line 15, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/logo.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/logo.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/markdown.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/markdown.stories.tsx (line 12, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/message-nav.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-nav.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/message-part.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-part.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/popover.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/popover.stories.tsx (line 16, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/progress-circle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress-circle.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/progress.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress.stories.tsx (line 15, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/provider-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/provider-icon.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/radio-group.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/radio-group.stories.tsx (line 13, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/resize-handle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/resize-handle.stories.tsx (line 17, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/select.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/select.stories.tsx (line 16, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/session-review.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-review.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/session-turn.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-turn.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/spinner.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/spinner.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/sticky-accordion-header.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/sticky-accordion-header.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/switch.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/switch.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/tabs.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tabs.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/tag.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tag.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/text-field.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-field.stories.tsx (line 14, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/text-shimmer.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-shimmer.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/toast.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/toast.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/tooltip.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tooltip.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
|
||||
- ./../ui/src/components/typewriter.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/typewriter.stories.tsx (line 6, col 0)
|
||||
|
||||
More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export[39m
|
||||
at _StoryIndexGenerator.getIndexAndStats (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:6085:15)
|
||||
at async _StoryIndexGenerator.getIndex (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:6074:13)
|
||||
at async getOptimizeDeps (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@storybook+builder-vite@10.2.10+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1862:15)
|
||||
at async createViteServer (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@storybook+builder-vite@10.2.10+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1888:19)
|
||||
at async Module.start (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@storybook+builder-vite@10.2.10+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1923:17)
|
||||
at async storybookDevServer (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:7241:83)
|
||||
at async buildOrThrow (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:4504:12)
|
||||
at async buildDevStandalone (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:7611:66)
|
||||
at async withTelemetry (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/_node-chunks/chunk-S3MWHNYJ.js:218:12)
|
||||
at async dev (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/bin/core.js:2734:3)
|
||||
[14:25:49.118] [WARN] Broken build, fix the error above.
|
||||
You may need to refresh the browser.
|
||||
Reference in New Issue
Block a user