Compare commits

..
1 Commits
Author SHA1 Message Date
opencode a462b1c101 release: v1.14.51 2026-05-15 00:39:43 +00:00
70 changed files with 880 additions and 3784 deletions
+17 -17
View File
@@ -29,7 +29,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -84,7 +84,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -119,7 +119,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -146,7 +146,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@ai-sdk/anthropic": "3.0.64",
"@ai-sdk/openai": "3.0.48",
@@ -168,7 +168,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -192,7 +192,7 @@
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "1.15.0",
"version": "1.14.51",
"bin": {
"opencode": "./bin/opencode",
},
@@ -253,7 +253,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -307,7 +307,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -337,7 +337,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -353,7 +353,7 @@
},
"packages/http-recorder": {
"name": "@opencode-ai/http-recorder",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@effect/platform-node": "catalog:",
"effect": "catalog:",
@@ -366,7 +366,7 @@
},
"packages/llm": {
"name": "@opencode-ai/llm",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
@@ -384,7 +384,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.15.0",
"version": "1.14.51",
"bin": {
"opencode": "./bin/opencode",
},
@@ -520,7 +520,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"effect": "catalog:",
@@ -558,7 +558,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -573,7 +573,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -608,7 +608,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -657,7 +657,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.15.0",
"version": "1.14.51",
"description": "",
"type": "module",
"exports": {
@@ -125,7 +125,6 @@ export function applyDirectoryEvent(input: {
const info = (event.properties as { info: Session }).info
const result = Binary.search(input.store.session, info.id, (s) => s.id)
if (info.time.archived) {
if (input.store.session[result.index]!.time.archived === info.time.archived) break
if (result.found) {
input.setStore(
"session",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.15.0",
"version": "1.14.51",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.15.0",
"version": "1.14.51",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.15.0",
"version": "1.14.51",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.15.0",
"version": "1.14.51",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.15.0",
"version": "1.14.51",
"name": "@opencode-ai/core",
"type": "module",
"license": "MIT",
+8 -17
View File
@@ -5,8 +5,7 @@ import { produce, type Draft } from "immer"
import { ModelV2 } from "./model"
import { PluginV2 } from "./plugin"
import { ProviderV2 } from "./provider"
import { Location } from "./location"
import { EventV2 } from "./event"
import { Instance } from "./instance"
type ProviderRecord = {
provider: ProviderV2.Info
@@ -25,15 +24,6 @@ export class ModelNotFoundError extends Schema.TaggedErrorClass<ModelNotFoundErr
modelID: ModelV2.ID,
}) {}
export const Event = {
ModelUpdated: EventV2.define({
type: "catalog.model.updated",
schema: {
model: ModelV2.Info,
},
}),
}
export interface Interface {
readonly provider: {
readonly get: (providerID: ProviderV2.ID) => Effect.Effect<ProviderV2.Info, ProviderNotFoundError>
@@ -67,11 +57,10 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
yield* Location.Service
yield* Instance.Service
let records = HashMap.empty<ProviderV2.ID, ProviderRecord>()
let defaultModel: { providerID: ProviderV2.ID; modelID: ModelV2.ID } | undefined
const plugin = yield* PluginV2.Service
const events = yield* EventV2.Service
const resolve = (model: ModelV2.Info) => {
const provider = Option.getOrThrow(HashMap.get(records, model.providerID)).provider
@@ -168,12 +157,14 @@ export const layer = Layer.effect(
)
const updated = yield* plugin.trigger("model.update", {}, { model, cancel: false })
if (updated.cancel) return
const next = new ModelV2.Info({ ...updated.model, id: modelID, providerID })
records = HashMap.set(records, providerID, {
provider: record.provider,
models: HashMap.set(record.models, modelID, next),
models: HashMap.set(
record.models,
modelID,
new ModelV2.Info({ ...updated.model, id: modelID, providerID }),
),
})
yield* events.publish(Event.ModelUpdated, { model: resolve(next) })
return
}),
@@ -266,4 +257,4 @@ export const layer = Layer.effect(
const SMALL_MODEL_RE = /\b(nano|flash|lite|mini|haiku|small|fast)\b/
export const defaultLayer = layer.pipe(Layer.provideMerge(EventV2.defaultLayer), Layer.provide(PluginV2.defaultLayer))
export const defaultLayer = layer.pipe(Layer.provide(PluginV2.defaultLayer))
-157
View File
@@ -1,157 +0,0 @@
import { Context, Effect, Layer, Option, PubSub, Schema, Stream } from "effect"
import { Location } from "./location"
import { withStatics } from "./schema"
import { Identifier } from "./util/identifier"
export const ID = Schema.String.pipe(
Schema.brand("Event.ID"),
withStatics((schema) => ({ create: () => schema.make("evt_" + Identifier.ascending()) })),
)
export type ID = typeof ID.Type
export type Definition<Type extends string = string, DataSchema extends Schema.Top = Schema.Top> = {
readonly type: Type
readonly version?: number
readonly aggregate?: string
readonly data: DataSchema
}
export type Data<D extends Definition> = Schema.Schema.Type<D["data"]>
export type Payload<D extends Definition = Definition> = {
readonly id: ID
readonly type: D["type"]
readonly data: Data<D>
readonly version?: number
readonly location?: Location.Ref
readonly metadata?: Record<string, unknown>
}
export type Sync = (event: Payload) => Effect.Effect<void>
export const registry = new Map<string, Definition>()
export function define<const Type extends string, Fields extends Schema.Struct.Fields>(input: {
readonly type: Type
readonly version?: number
readonly aggregate?: string
readonly schema: Fields
}): Schema.Schema<Payload<Definition<Type, Schema.Struct<Fields>>>> & Definition<Type, Schema.Struct<Fields>> {
const Data = Schema.Struct(input.schema)
const Payload = Schema.Struct({
id: ID,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
type: Schema.Literal(input.type),
version: Schema.optional(Schema.Number),
location: Schema.optional(Location.Ref),
data: Data,
}).annotate({ identifier: input.type })
const definition = Object.assign(Payload, {
type: input.type,
...(input.version === undefined ? {} : { version: input.version }),
...(input.aggregate === undefined ? {} : { aggregate: input.aggregate }),
data: Data,
})
registry.set(input.type, definition)
return definition as Schema.Schema<Payload<Definition<Type, Schema.Struct<Fields>>>> &
Definition<Type, Schema.Struct<Fields>>
}
export function definitions() {
return registry.values().toArray()
}
export interface PublishOptions {
readonly id?: ID
readonly metadata?: Record<string, unknown>
}
export type Unsubscribe = Effect.Effect<void>
export interface Interface {
readonly publish: <D extends Definition>(
definition: D,
data: Data<D>,
options?: PublishOptions,
) => Effect.Effect<Payload<D>>
readonly publishEvent: <D extends Definition>(event: Payload<D>) => Effect.Effect<Payload<D>>
readonly subscribe: <D extends Definition>(definition: D) => Stream.Stream<Payload<D>>
readonly all: () => Stream.Stream<Payload>
readonly sync: (handler: Sync) => Effect.Effect<Unsubscribe>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/Event") {}
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const all = yield* PubSub.unbounded<Payload>()
const typed = new Map<string, PubSub.PubSub<Payload>>()
const syncHandlers = new Array<Sync>()
const getOrCreate = (definition: Definition) =>
Effect.gen(function* () {
const existing = typed.get(definition.type)
if (existing) return existing
const pubsub = yield* PubSub.unbounded<Payload>()
typed.set(definition.type, pubsub)
return pubsub
})
yield* Effect.addFinalizer(() =>
Effect.gen(function* () {
yield* PubSub.shutdown(all)
yield* Effect.forEach(typed.values(), PubSub.shutdown, { discard: true })
}),
)
function publishEvent<D extends Definition>(event: Payload<D>) {
return Effect.gen(function* () {
for (const sync of syncHandlers) {
yield* sync(event as Payload)
}
const pubsub = typed.get(event.type)
if (pubsub) yield* PubSub.publish(pubsub, event as Payload)
yield* PubSub.publish(all, event as Payload)
return event
})
}
function publish<D extends Definition>(definition: D, data: Data<D>, options?: PublishOptions) {
return Effect.gen(function* () {
const location = Option.getOrUndefined(yield* Effect.serviceOption(Location.Service))
const event = {
id: options?.id ?? ID.create(),
...(options?.metadata ? { metadata: options.metadata } : {}),
type: definition.type,
...(definition.version === undefined ? {} : { version: definition.version }),
...(location ? { location } : {}),
data,
} as Payload<D>
return yield* publishEvent(event)
})
}
const subscribe = <D extends Definition>(definition: D): Stream.Stream<Payload<D>> =>
Stream.unwrap(getOrCreate(definition).pipe(Effect.map((pubsub) => Stream.fromPubSub(pubsub)))).pipe(
Stream.map((event) => event as Payload<D>),
)
const streamAll = (): Stream.Stream<Payload> => Stream.fromPubSub(all)
const sync = (handler: Sync): Effect.Effect<Unsubscribe> =>
Effect.sync(() => {
syncHandlers.push(handler)
return Effect.sync(() => {
const index = syncHandlers.indexOf(handler)
if (index >= 0) syncHandlers.splice(index, 1)
})
})
return Service.of({ publish, publishEvent, subscribe, all: streamAll, sync })
}),
)
export const defaultLayer = layer
export * as EventV2 from "./event"
+12
View File
@@ -0,0 +1,12 @@
import { Layer, LayerMap } from "effect"
import { Instance } from "./instance"
import { Catalog } from "./catalog"
import { PluginBoot } from "./plugin/boot"
export class InstanceServiceMap extends LayerMap.Service<InstanceServiceMap>()("@opencode/example/InstanceServiceMap", {
lookup: (ref: Instance.Ref) => {
const instance = Layer.succeed(Instance.Service, Instance.Service.of(ref))
return Layer.mergeAll(Catalog.defaultLayer, PluginBoot.defaultLayer).pipe(Layer.provide(instance))
},
idleTimeToLive: "5 minutes",
}) {}
+10
View File
@@ -0,0 +1,10 @@
import { Context } from "effect"
export * as Instance from "./instance"
export type Ref = {
readonly directory: string
readonly workspaceID?: string
}
export class Service extends Context.Service<Service, Ref>()("@opencode/Instance") {}
-12
View File
@@ -1,12 +0,0 @@
import { Layer, LayerMap } from "effect"
import { Location } from "./location"
import { Catalog } from "./catalog"
import { PluginBoot } from "./plugin/boot"
export class LocationServiceMap extends LayerMap.Service<LocationServiceMap>()("@opencode/example/LocationServiceMap", {
lookup: (ref: Location.Ref) => {
const location = Layer.succeed(Location.Service, Location.Service.of(ref))
return Layer.mergeAll(Catalog.defaultLayer, PluginBoot.defaultLayer).pipe(Layer.provide(location))
},
idleTimeToLive: "5 minutes",
}) {}
-11
View File
@@ -1,11 +0,0 @@
import { Context, Schema } from "effect"
export * as Location from "./location"
export const Ref = Schema.Struct({
directory: Schema.String,
workspaceID: Schema.optional(Schema.String),
}).annotate({ identifier: "Location.Ref" })
export type Ref = typeof Ref.Type
export class Service extends Context.Service<Service, Ref>()("@opencode/Location") {}
-13
View File
@@ -1,13 +0,0 @@
export * as Session from "./session"
import { Schema } from "effect"
import { withStatics } from "./schema"
import { Identifier } from "./util/identifier"
export const ID = Schema.String.check(Schema.isStartsWith("ses")).pipe(
Schema.brand("SessionID"),
withStatics((schema) => ({
descending: (id?: string) => schema.make(id ?? "ses_" + Identifier.descending()),
})),
)
export type ID = typeof ID.Type
+4 -36
View File
@@ -1,21 +1,14 @@
import { describe, expect } from "bun:test"
import { DateTime, Effect, Fiber, Layer, Option, Stream } from "effect"
import { DateTime, Effect, Layer, Option } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { EventV2 } from "@opencode-ai/core/event"
import { Location } from "@opencode-ai/core/location"
import { Instance } from "@opencode-ai/core/instance"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { testEffect } from "./lib/effect"
const locationLayer = Layer.succeed(Location.Service, Location.Service.of({ directory: "test" }))
const it = testEffect(
Catalog.layer.pipe(
Layer.provideMerge(EventV2.defaultLayer),
Layer.provideMerge(PluginV2.defaultLayer),
Layer.provideMerge(locationLayer),
),
)
const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" }))
const it = testEffect(Catalog.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer), Layer.provide(instanceLayer)))
describe("CatalogV2", () => {
it.effect("normalizes provider baseURL into endpoint url", () =>
@@ -76,31 +69,6 @@ describe("CatalogV2", () => {
}),
)
it.effect("publishes model updated events", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const events = yield* EventV2.Service
const providerID = ProviderV2.ID.make("test")
const modelID = ModelV2.ID.make("model")
const fiber = yield* events
.subscribe(Catalog.Event.ModelUpdated)
.pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
yield* Effect.yieldNow
yield* catalog.provider.update(providerID, () => {})
yield* catalog.model.update(providerID, modelID, (model) => {
model.name = "Updated Model"
})
const event = Array.from(yield* Fiber.join(fiber))[0]
expect(event?.type).toBe("catalog.model.updated")
expect(event?.data.model.providerID).toBe(providerID)
expect(event?.data.model.id).toBe(modelID)
expect(event?.data.model.name).toBe("Updated Model")
expect(event?.location).toEqual({ directory: "test" })
}),
)
it.effect("resolves unknown model endpoint from provider endpoint", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
-132
View File
@@ -1,132 +0,0 @@
import { describe, expect } from "bun:test"
import { Effect, Fiber, Layer, Schema, Stream } from "effect"
import { EventV2 } from "@opencode-ai/core/event"
import { Location } from "@opencode-ai/core/location"
import { testEffect } from "./lib/effect"
const locationLayer = Layer.succeed(
Location.Service,
Location.Service.of({ directory: "project", workspaceID: "workspace" }),
)
const it = testEffect(EventV2.layer.pipe(Layer.provideMerge(locationLayer)))
const itWithoutLocation = testEffect(EventV2.layer)
const Message = EventV2.define({
type: "test.message",
schema: {
text: Schema.String,
},
})
const GlobalMessage = EventV2.define({
type: "test.global",
schema: {
text: Schema.String,
},
})
const VersionedMessage = EventV2.define({
type: "test.versioned",
version: 2,
schema: {
text: Schema.String,
},
})
describe("EventV2", () => {
it.effect("publishes events with the current location", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const fiber = yield* events.subscribe(Message).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
yield* Effect.yieldNow
const event = yield* events.publish(Message, { text: "hello" })
const received = Array.from(yield* Fiber.join(fiber))
expect(received).toEqual([event])
expect(event.type).toBe("test.message")
expect(event).not.toHaveProperty("version")
expect(event.data).toEqual({ text: "hello" })
expect(event.location).toEqual({ directory: "project", workspaceID: "workspace" })
}),
)
itWithoutLocation.effect("omits location when no location is available", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const event = yield* events.publish(GlobalMessage, { text: "hello" })
expect(event).not.toHaveProperty("location")
expect(event.type).toBe("test.global")
}),
)
it.effect("publishes definition version", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const event = yield* events.publish(VersionedMessage, { text: "hello" })
expect(event.type).toBe("test.versioned")
expect(event.version).toBe(2)
}),
)
it.effect("stores definitions in the exported registry", () =>
Effect.sync(() => {
expect(EventV2.registry.get(Message.type)).toBe(Message)
}),
)
it.effect("publishes to typed and wildcard subscriptions", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const typed = yield* events.subscribe(Message).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
const wildcard = yield* events.all().pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
yield* Effect.yieldNow
const event = yield* events.publish(Message, { text: "hello" })
expect(Array.from(yield* Fiber.join(typed))).toEqual([event])
expect(Array.from(yield* Fiber.join(wildcard))).toEqual([event])
}),
)
it.effect("runs sync handlers inline", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const received = new Array<EventV2.Payload>()
const unsubscribe = yield* events.sync((event) =>
Effect.sync(() => {
received.push(event)
}),
)
const event = yield* events.publish(Message, { text: "hello" })
yield* unsubscribe
yield* events.publish(Message, { text: "after unsubscribe" })
expect(received).toEqual([event])
}),
)
it.effect("runs sync handlers before publishing to streams", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const received = new Array<string>()
const fiber = yield* events.all().pipe(
Stream.take(1),
Stream.runForEach(() => Effect.sync(() => received.push("stream"))),
Effect.forkScoped,
)
yield* events.sync((event) =>
Effect.sync(() => {
received.push(event.type)
}),
)
yield* Effect.yieldNow
yield* events.publish(Message, { text: "hello" })
yield* Fiber.join(fiber)
expect(received).toEqual([Message.type, "stream"])
}),
)
})
@@ -1,7 +1,7 @@
import { describe, expect } from "bun:test"
import { DateTime, Effect, Layer, Option } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Location } from "@opencode-ai/core/location"
import { Instance } from "@opencode-ai/core/instance"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode"
@@ -9,7 +9,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider"
import { it, model, provider, withEnv } from "./provider-helper"
const cost = (input: number, output = 0) => [{ input, output, cache: { read: 0, write: 0 } }]
const locationLayer = Layer.succeed(Location.Service, Location.Service.of({ directory: "test" }))
const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" }))
describe("OpencodePlugin", () => {
it.effect("uses a public key and cancels paid models without credentials", () =>
@@ -192,6 +192,6 @@ describe("OpencodePlugin", () => {
const selected = yield* catalog.model.small(providerID)
expect(Option.getOrUndefined(selected)?.id).toBe(ModelV2.ID.make("gpt-5-nano"))
}).pipe(Effect.provide(Catalog.defaultLayer.pipe(Layer.provide(locationLayer)))),
}).pipe(Effect.provide(Catalog.defaultLayer.pipe(Layer.provide(instanceLayer)))),
)
})
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.15.0",
"version": "1.14.51",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
-2
View File
@@ -84,7 +84,6 @@ export function createMainWindow() {
width: state.width,
height: state.height,
show: false,
autoHideMenuBar: true,
title: "OpenCode",
icon: iconPath(),
backgroundColor,
@@ -143,7 +142,6 @@ export function createLoadingWindow() {
resizable: false,
center: true,
show: true,
autoHideMenuBar: true,
icon: iconPath(),
backgroundColor,
...(process.platform === "darwin" ? { titleBarStyle: "hidden" as const } : {}),
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.15.0",
"version": "1.14.51",
"private": true,
"type": "module",
"license": "MIT",
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.15.0"
version = "1.14.51"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.0/opencode-darwin-arm64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.51/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.0/opencode-darwin-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.51/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.0/opencode-linux-arm64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.51/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.0/opencode-linux-x64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.51/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.0/opencode-windows-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.51/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.15.0",
"version": "1.14.51",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.15.0",
"version": "1.14.51",
"name": "@opencode-ai/http-recorder",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.15.0",
"version": "1.14.51",
"name": "@opencode-ai/llm",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.15.0",
"version": "1.14.51",
"name": "opencode",
"type": "module",
"license": "MIT",
+10 -23
View File
@@ -1,5 +1,4 @@
import { Schema } from "effect"
import { EventV2 } from "@opencode-ai/core/event"
export type Definition<Type extends string = string, Properties extends Schema.Top = Schema.Top> = {
type: Type
@@ -18,28 +17,16 @@ export function define<Type extends string, Properties extends Schema.Top>(
}
export function effectPayloads() {
return [
...registry
.entries()
.map(([type, def]) =>
Schema.Struct({
id: Schema.String,
type: Schema.Literal(type),
properties: def.properties,
}).annotate({ identifier: `Event.${type}` }),
)
.toArray(),
...EventV2.registry
.values()
.map((definition) =>
Schema.Struct({
id: Schema.String,
type: Schema.Literal(definition.type),
properties: definition.data,
}).annotate({ identifier: `Event.${definition.type}` }),
)
.toArray(),
]
return registry
.entries()
.map(([type, def]) =>
Schema.Struct({
id: Schema.String,
type: Schema.Literal(type),
properties: def.properties,
}).annotate({ identifier: `Event.${type}` }),
)
.toArray()
}
export * as BusEvent from "./bus-event"
+3 -3
View File
@@ -1,11 +1,11 @@
import { EOL } from "os"
import { Effect, Layer, Option } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { effectCmd } from "../../effect-cmd"
const Runtime = Layer.mergeAll(LocationServiceMap.layer)
const Runtime = Layer.mergeAll(InstanceServiceMap.layer)
export const V2Command = effectCmd({
command: "v2",
@@ -37,7 +37,7 @@ export const V2Command = effectCmd({
process.stdout.write(JSON.stringify(result, null, 2) + EOL)
},
Effect.provide(
LocationServiceMap.get({
InstanceServiceMap.get({
directory: process.cwd(),
}),
),
@@ -56,7 +56,6 @@ import { Npm } from "@opencode-ai/core/npm"
import { memoMap } from "@opencode-ai/core/effect/memo-map"
import { DataMigration } from "@/data-migration"
import { BackgroundJob } from "@/background/job"
import { EventV2Bridge } from "@/event-v2-bridge"
import { RuntimeFlags } from "@/effect/runtime-flags"
export const AppLayer = Layer.mergeAll(
@@ -112,7 +111,6 @@ export const AppLayer = Layer.mergeAll(
ShareNext.defaultLayer,
SessionShare.defaultLayer,
SyncEvent.defaultLayer,
EventV2Bridge.defaultLayer,
DataMigration.defaultLayer,
).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer))
-89
View File
@@ -1,89 +0,0 @@
// Temporary V2 bridge: core events are the publish path, but the rest of
// opencode and the HTTP event stream still expect legacy bus/sync payloads.
// This layer goes away once consumers subscribe to core EventV2 directly.
import { Bus as ProjectBus } from "@/bus"
import { GlobalBus } from "@/bus/global"
import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref"
import { InstanceStore } from "@/project/instance-store"
import { SyncEvent } from "@/sync"
import { EventV2 } from "@opencode-ai/core/event"
import "@opencode-ai/core/catalog"
import "@opencode-ai/core/session-event"
import { Context, Effect, Layer, Option } from "effect"
export function toSyncDefinition<D extends EventV2.Definition>(definition: D) {
const result = {
type: definition.type,
version: definition.version,
aggregate: definition.aggregate,
schema: definition.data,
properties: definition.data,
}
return result as SyncEvent.Definition<D["type"], D["data"], D["data"]>
}
export class Service extends Context.Service<Service, EventV2.Interface>()("@opencode/EventV2Bridge") {}
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const events = yield* EventV2.Service
const bus = yield* ProjectBus.Service
const sync = yield* SyncEvent.Service
const publishGlobal = (event: EventV2.Payload) =>
Effect.sync(() => {
GlobalBus.emit("event", {
workspace: event.location?.workspaceID,
payload: {
id: event.id,
type: event.type,
properties: event.data,
},
})
})
const provideEventLocation = <E, R>(event: EventV2.Payload, effect: Effect.Effect<void, E, R>) => {
return Effect.gen(function* () {
const ctx = yield* InstanceRef
if (ctx) return yield* effect
const store = Option.getOrUndefined(yield* Effect.serviceOption(InstanceStore.Service))
if (!event.location?.directory || !store) return yield* publishGlobal(event)
return yield* store.load({ directory: event.location.directory }).pipe(
Effect.flatMap((ctx) => {
const withInstance = effect.pipe(Effect.provideService(InstanceRef, ctx))
if (!event.location?.workspaceID) return withInstance
return withInstance.pipe(Effect.provideService(WorkspaceRef, event.location.workspaceID))
}),
)
})
}
const unsubscribe = yield* events.sync((event) => {
const definition = EventV2.registry.get(event.type)
if (!definition) return Effect.void
const aggregateID = definition.aggregate
? (event.data as Record<string, unknown>)[definition.aggregate]
: undefined
if (definition.version !== undefined && typeof aggregateID === "string") {
return provideEventLocation(event, sync.run(toSyncDefinition(definition), event.data))
}
return provideEventLocation(
event,
bus.publish({ type: definition.type, properties: definition.data }, event.data, { id: event.id }),
)
})
yield* Effect.addFinalizer(() => unsubscribe)
return Service.of(events)
}),
)
export const defaultLayer = layer.pipe(
Layer.provideMerge(EventV2.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
Layer.provide(ProjectBus.defaultLayer),
)
export * as EventV2Bridge from "./event-v2-bridge"
@@ -1,3 +0,0 @@
import { initProjectors } from "./projectors"
initProjectors()
@@ -1,28 +1,28 @@
import { Catalog } from "@opencode-ai/core/catalog"
import { Location } from "@opencode-ai/core/location"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { Instance } from "@opencode-ai/core/instance"
import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { Effect, Layer, Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"
import { HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi"
export const LocationQuery = Schema.Struct({
location: Schema.optional(
export const InstanceQuery = Schema.Struct({
instance: Schema.optional(
Schema.Struct({
directory: Schema.optional(Schema.String),
workspace: Schema.optional(Schema.String),
}),
),
}).annotate({ identifier: "V2LocationQuery" })
}).annotate({ identifier: "V2InstanceQuery" })
export const locationQueryOpenApi = OpenApi.annotations({
export const instanceQueryOpenApi = OpenApi.annotations({
transform: (operation) => {
const parameters = operation.parameters
if (!Array.isArray(parameters)) return operation
return {
...operation,
parameters: parameters.map((parameter) =>
parameter?.name === "location" && parameter?.in === "query"
parameter?.name === "instance" && parameter?.in === "query"
? { ...parameter, style: "deepObject", explode: true }
: parameter,
),
@@ -30,30 +30,30 @@ export const locationQueryOpenApi = OpenApi.annotations({
},
})
export class V2LocationMiddleware extends HttpApiMiddleware.Service<
V2LocationMiddleware,
export class V2InstanceMiddleware extends HttpApiMiddleware.Service<
V2InstanceMiddleware,
{
provides: Catalog.Service | PluginBoot.Service
}
>()("@opencode/ExperimentalHttpApiV2Location") {}
>()("@opencode/ExperimentalHttpApiV2Instance") {}
function ref(request: HttpServerRequest.HttpServerRequest): Location.Ref {
function ref(request: HttpServerRequest.HttpServerRequest): Instance.Ref {
const query = new URL(request.url, "http://localhost").searchParams
return {
directory: query.get("location[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
workspaceID: query.get("location[workspace]") || request.headers["x-opencode-workspace"],
directory: query.get("instance[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
workspaceID: query.get("instance[workspace]") || request.headers["x-opencode-workspace"],
}
}
export const layer = Layer.effect(
V2LocationMiddleware,
V2InstanceMiddleware,
Effect.gen(function* () {
const locations = yield* LocationServiceMap
return V2LocationMiddleware.of((effect) =>
const instances = yield* InstanceServiceMap
return V2InstanceMiddleware.of((effect) =>
Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest
return yield* effect.pipe(Effect.provide(locations.get(ref(request))))
return yield* effect.pipe(Effect.provide(instances.get(ref(request))))
}),
)
}),
).pipe(Layer.provide(LocationServiceMap.layer))
).pipe(Layer.provide(InstanceServiceMap.layer))
@@ -1,5 +1,5 @@
import { SessionID } from "@/session/schema"
import { SessionMessage } from "@opencode-ai/core/session-message"
import { SessionMessage } from "@/v2/session-message"
import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../../middleware/authorization"
@@ -2,15 +2,15 @@ import { ModelV2 } from "@opencode-ai/core/model"
import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../../middleware/authorization"
import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location"
import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
export const ModelGroup = HttpApiGroup.make("v2.model")
.add(
HttpApiEndpoint.get("models", "/api/model", {
query: LocationQuery,
query: InstanceQuery,
success: Schema.Array(ModelV2.Info),
})
.annotateMerge(locationQueryOpenApi)
.annotateMerge(instanceQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.model.list",
@@ -25,5 +25,5 @@ export const ModelGroup = HttpApiGroup.make("v2.model")
description: "Experimental v2 model routes.",
}),
)
.middleware(V2LocationMiddleware)
.middleware(V2InstanceMiddleware)
.middleware(Authorization)
@@ -3,15 +3,15 @@ import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { ApiNotFoundError } from "../../errors"
import { Authorization } from "../../middleware/authorization"
import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location"
import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
export const ProviderGroup = HttpApiGroup.make("v2.provider")
.add(
HttpApiEndpoint.get("providers", "/api/provider", {
query: LocationQuery,
query: InstanceQuery,
success: Schema.Array(ProviderV2.Info),
})
.annotateMerge(locationQueryOpenApi)
.annotateMerge(instanceQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.provider.list",
@@ -23,11 +23,11 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
.add(
HttpApiEndpoint.get("provider", "/api/provider/:providerID", {
params: { providerID: ProviderV2.ID },
query: LocationQuery,
query: InstanceQuery,
success: ProviderV2.Info,
error: ApiNotFoundError,
})
.annotateMerge(locationQueryOpenApi)
.annotateMerge(instanceQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.provider.get",
@@ -43,5 +43,5 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
description: "Experimental v2 provider routes.",
}),
)
.middleware(V2LocationMiddleware)
.middleware(V2InstanceMiddleware)
.middleware(Authorization)
@@ -1,5 +1,5 @@
import { SessionID } from "@/session/schema"
import { SessionMessage } from "@opencode-ai/core/session-message"
import { SessionMessage } from "@/v2/session-message"
import { Prompt } from "@opencode-ai/core/session-prompt"
import { SessionV2 } from "@/v2/session"
import { Schema } from "effect"
@@ -1,12 +1,12 @@
import { SessionV2 } from "@/v2/session"
import { Layer } from "effect"
import { layer as v2LocationLayer } from "../groups/v2/location"
import { layer as v2InstanceLayer } from "../groups/v2/instance"
import { messageHandlers } from "./v2/message"
import { modelHandlers } from "./v2/model"
import { providerHandlers } from "./v2/provider"
import { sessionHandlers } from "./v2/session"
export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers, modelHandlers, providerHandlers).pipe(
Layer.provide(v2LocationLayer),
Layer.provide(v2InstanceLayer),
Layer.provide(SessionV2.defaultLayer),
)
@@ -1,4 +1,4 @@
import { SessionMessage } from "@opencode-ai/core/session-message"
import { SessionMessage } from "@/v2/session-message"
import { SessionV2 } from "@/v2/session"
import { Effect, Schema } from "effect"
import * as DateTime from "effect/DateTime"
@@ -45,7 +45,6 @@ import { SessionSummary } from "@/session/summary"
import { Todo } from "@/session/todo"
import { SessionShare } from "@/share/session"
import { ShareNext } from "@/share/share-next"
import { EventV2Bridge } from "@/event-v2-bridge"
import { Skill } from "@/skill"
import { Snapshot } from "@/snapshot"
import { SyncEvent } from "@/sync"
@@ -222,7 +221,6 @@ export function createRoutes(
ShareNext.defaultLayer,
Snapshot.defaultLayer,
SyncEvent.defaultLayer,
EventV2Bridge.defaultLayer,
Skill.defaultLayer,
Todo.defaultLayer,
ToolRegistry.defaultLayer,
+3 -2
View File
@@ -1,5 +1,3 @@
import "./init-projectors"
import { NodeHttpServer } from "@effect/platform-node"
import * as Log from "@opencode-ai/core/util/log"
import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect"
@@ -7,6 +5,7 @@ import { HttpRouter, HttpServer } from "effect/unstable/http"
import { OpenApi } from "effect/unstable/httpapi"
import { createServer } from "node:http"
import { MDNS } from "./mdns"
import { initProjectors } from "./projectors"
import { HttpApiApp } from "./routes/instance/httpapi/server"
import { disposeMiddleware } from "./routes/instance/httpapi/lifecycle"
import { WebSocketTracker } from "./routes/instance/httpapi/websocket-tracker"
@@ -17,6 +16,8 @@ import { lazy } from "@/util/lazy"
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
globalThis.AI_SDK_LOG_WARNINGS = false
initProjectors()
const log = Log.create({ service: "server" })
export type Listener = {
+19 -8
View File
@@ -19,9 +19,8 @@ import { isOverflow as overflow, usable } from "./overflow"
import { makeRuntime } from "@/effect/run-service"
import { serviceUse } from "@/effect/service-use"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2 } from "@opencode-ai/core/event"
import { EventV2Bridge } from "@/event-v2-bridge"
import { SessionEvent } from "@opencode-ai/core/session-event"
import { SyncEvent } from "@/sync"
import { SessionEvent } from "@/v2/session-event"
const log = Log.create({ service: "session.compaction" })
@@ -211,7 +210,19 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Se
export const use = serviceUse(Service)
export const layer = Layer.effect(
export const layer: Layer.Layer<
Service,
never,
| Bus.Service
| Config.Service
| Session.Service
| Agent.Service
| Plugin.Service
| SessionProcessor.Service
| Provider.Service
| SyncEvent.Service
| RuntimeFlags.Service
> = Layer.effect(
Service,
Effect.gen(function* () {
const bus = yield* Bus.Service
@@ -221,7 +232,7 @@ export const layer = Layer.effect(
const plugin = yield* Plugin.Service
const processors = yield* SessionProcessor.Service
const provider = yield* Provider.Service
const events = yield* EventV2Bridge.Service
const sync = yield* SyncEvent.Service
const flags = yield* RuntimeFlags.Service
const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: {
@@ -566,7 +577,7 @@ export const layer = Layer.effect(
},
)
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Compaction.Ended, {
yield* sync.run(SessionEvent.Compaction.Ended.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(Date.now()),
text: summary ?? "",
@@ -602,7 +613,7 @@ export const layer = Layer.effect(
overflow: input.overflow,
})
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Compaction.Started, {
yield* sync.run(SessionEvent.Compaction.Started.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(Date.now()),
reason: input.auto ? "auto" : "manual",
@@ -628,8 +639,8 @@ export const defaultLayer = Layer.suspend(() =>
Layer.provide(Plugin.defaultLayer),
Layer.provide(Bus.layer),
Layer.provide(Config.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
Layer.provide(RuntimeFlags.defaultLayer),
Layer.provide(EventV2Bridge.defaultLayer),
),
)
+1 -3
View File
@@ -118,9 +118,7 @@ export const layer: Layer.Layer<
// The first project-level match wins so we don't stack AGENTS.md/CLAUDE.md from every ancestor.
if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
for (const file of FILES) {
const matches = yield* fs
.findUp(file, ctx.directory, ctx.worktree)
.pipe(Effect.catch(() => Effect.succeed([])))
const matches = yield* fs.findUp(file, ctx.directory, ctx.worktree)
if (matches.length > 0) {
matches.forEach((item) => paths.add(path.resolve(item)))
break
+34 -19
View File
@@ -21,9 +21,8 @@ import { Question } from "@/question"
import { errorMessage } from "@/util/error"
import * as Log from "@opencode-ai/core/util/log"
import { isRecord } from "@/util/record"
import { EventV2 } from "@opencode-ai/core/event"
import { EventV2Bridge } from "@/event-v2-bridge"
import { SessionEvent } from "@opencode-ai/core/session-event"
import { SyncEvent } from "@/sync"
import { SessionEvent } from "@/v2/session-event"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProviderV2 } from "@opencode-ai/core/provider"
import * as DateTime from "effect/DateTime"
@@ -85,7 +84,23 @@ type StreamEvent = Event
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionProcessor") {}
export const layer = Layer.effect(
export const layer: Layer.Layer<
Service,
never,
| Session.Service
| Config.Service
| Bus.Service
| Snapshot.Service
| Agent.Service
| LLM.Service
| Permission.Service
| Plugin.Service
| Image.Service
| SessionSummary.Service
| SessionStatus.Service
| SyncEvent.Service
| RuntimeFlags.Service
> = Layer.effect(
Service,
Effect.gen(function* () {
const session = yield* Session.Service
@@ -100,7 +115,7 @@ export const layer = Layer.effect(
const scope = yield* Scope.Scope
const status = yield* SessionStatus.Service
const image = yield* Image.Service
const events = yield* EventV2Bridge.Service
const sync = yield* SyncEvent.Service
const flags = yield* RuntimeFlags.Service
const create = Effect.fn("SessionProcessor.create")(function* (input: Input) {
@@ -221,7 +236,7 @@ export const layer = Layer.effect(
if (value.id in ctx.reasoningMap) return
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Reasoning.Started, {
yield* sync.run(SessionEvent.Reasoning.Started.Sync, {
sessionID: ctx.sessionID,
reasoningID: value.id,
timestamp: DateTime.makeUnsafe(Date.now()),
@@ -256,7 +271,7 @@ export const layer = Layer.effect(
if (!(value.id in ctx.reasoningMap)) return
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Reasoning.Ended, {
yield* sync.run(SessionEvent.Reasoning.Ended.Sync, {
sessionID: ctx.sessionID,
reasoningID: value.id,
text: ctx.reasoningMap[value.id].text,
@@ -277,7 +292,7 @@ export const layer = Layer.effect(
}
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Tool.Input.Started, {
yield* sync.run(SessionEvent.Tool.Input.Started.Sync, {
sessionID: ctx.sessionID,
callID: value.id,
name: value.toolName,
@@ -308,7 +323,7 @@ export const layer = Layer.effect(
case "tool-input-end": {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Tool.Input.Ended, {
yield* sync.run(SessionEvent.Tool.Input.Ended.Sync, {
sessionID: ctx.sessionID,
callID: value.id,
text: "",
@@ -325,7 +340,7 @@ export const layer = Layer.effect(
const toolCall = yield* readToolCall(value.toolCallId)
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Tool.Called, {
yield* sync.run(SessionEvent.Tool.Called.Sync, {
sessionID: ctx.sessionID,
callID: value.toolCallId,
tool: value.toolName,
@@ -413,7 +428,7 @@ export const layer = Layer.effect(
}
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Tool.Success, {
yield* sync.run(SessionEvent.Tool.Success.Sync, {
sessionID: ctx.sessionID,
callID: value.toolCallId,
structured: output.metadata,
@@ -443,7 +458,7 @@ export const layer = Layer.effect(
const toolCall = yield* readToolCall(value.toolCallId)
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Tool.Failed, {
yield* sync.run(SessionEvent.Tool.Failed.Sync, {
sessionID: ctx.sessionID,
callID: value.toolCallId,
error: {
@@ -468,7 +483,7 @@ export const layer = Layer.effect(
if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Step.Started, {
yield* sync.run(SessionEvent.Step.Started.Sync, {
sessionID: ctx.sessionID,
agent: input.assistantMessage.agent,
model: {
@@ -500,7 +515,7 @@ export const layer = Layer.effect(
if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Step.Ended, {
yield* sync.run(SessionEvent.Step.Ended.Sync, {
sessionID: ctx.sessionID,
finish: value.finishReason,
cost: usage.cost,
@@ -557,7 +572,7 @@ export const layer = Layer.effect(
if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Text.Started, {
yield* sync.run(SessionEvent.Text.Started.Sync, {
sessionID: ctx.sessionID,
timestamp: DateTime.makeUnsafe(Date.now()),
})
@@ -604,7 +619,7 @@ export const layer = Layer.effect(
if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Text.Ended, {
yield* sync.run(SessionEvent.Text.Ended.Sync, {
sessionID: ctx.sessionID,
text: ctx.currentText.text,
timestamp: DateTime.makeUnsafe(Date.now()),
@@ -700,7 +715,7 @@ export const layer = Layer.effect(
if (!ctx.assistantMessage.summary) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Step.Failed, {
yield* sync.run(SessionEvent.Step.Failed.Sync, {
sessionID: ctx.sessionID,
error: {
type: "unknown",
@@ -754,7 +769,7 @@ export const layer = Layer.effect(
set: (info) => {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
const event = flags.experimentalEventSystem
? events.publish(SessionEvent.Retried, {
? sync.run(SessionEvent.Retried.Sync, {
sessionID: ctx.sessionID,
attempt: info.attempt,
error: {
@@ -815,8 +830,8 @@ export const defaultLayer = Layer.suspend(() =>
Layer.provide(Image.defaultLayer),
Layer.provide(Bus.layer),
Layer.provide(Config.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
Layer.provide(RuntimeFlags.defaultLayer),
Layer.provide(EventV2Bridge.defaultLayer),
),
)
@@ -1,11 +1,10 @@
import { and, desc, eq } from "@/storage/db"
import type { Database } from "@/storage/db"
import { SessionMessage } from "@opencode-ai/core/session-message"
import { SessionMessageUpdater } from "@opencode-ai/core/session-message-updater"
import { SessionEvent } from "@opencode-ai/core/session-event"
import { SessionMessage } from "@/v2/session-message"
import { SessionMessageUpdater } from "@/v2/session-message-updater"
import { SessionEvent } from "@/v2/session-event"
import * as DateTime from "effect/DateTime"
import { SyncEvent } from "@/sync"
import { EventV2Bridge } from "@/event-v2-bridge"
import { SessionMessageTable, SessionTable } from "./session.sql"
import type { SessionID } from "./schema"
import { Schema } from "effect"
@@ -120,7 +119,7 @@ function update(db: Database.TxOrDb, event: SessionEvent.Event) {
}
export default [
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.AgentSwitched), (db, data, event) => {
SyncEvent.project(SessionEvent.AgentSwitched.Sync, (db, data, event) => {
db.update(SessionTable)
.set({
agent: data.agent,
@@ -130,7 +129,7 @@ export default [
.run()
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.agent.switched", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.ModelSwitched), (db, data, event) => {
SyncEvent.project(SessionEvent.ModelSwitched.Sync, (db, data, event) => {
db.update(SessionTable)
.set({
model: data.model,
@@ -140,65 +139,65 @@ export default [
.run()
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.model.switched", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Prompted), (db, data, event) => {
SyncEvent.project(SessionEvent.Prompted.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.prompted", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Synthetic), (db, data, event) => {
SyncEvent.project(SessionEvent.Synthetic.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.synthetic", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Shell.Started), (db, data, event) => {
SyncEvent.project(SessionEvent.Shell.Started.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.shell.started", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Shell.Ended), (db, data, event) => {
SyncEvent.project(SessionEvent.Shell.Ended.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.shell.ended", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Started), (db, data, event) => {
SyncEvent.project(SessionEvent.Step.Started.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.started", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Ended), (db, data, event) => {
SyncEvent.project(SessionEvent.Step.Ended.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.ended", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Failed), (db, data, event) => {
SyncEvent.project(SessionEvent.Step.Failed.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.failed", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Started), (db, data, event) => {
SyncEvent.project(SessionEvent.Text.Started.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.text.started", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Delta), () => {}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Ended), (db, data, event) => {
SyncEvent.project(SessionEvent.Text.Delta.Sync, () => {}),
SyncEvent.project(SessionEvent.Text.Ended.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.text.ended", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Started), (db, data, event) => {
SyncEvent.project(SessionEvent.Tool.Input.Started.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.input.started", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Delta), () => {}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Ended), (db, data, event) => {
SyncEvent.project(SessionEvent.Tool.Input.Delta.Sync, () => {}),
SyncEvent.project(SessionEvent.Tool.Input.Ended.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.input.ended", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Called), (db, data, event) => {
SyncEvent.project(SessionEvent.Tool.Called.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.called", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Success), (db, data, event) => {
SyncEvent.project(SessionEvent.Tool.Success.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.success", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Failed), (db, data, event) => {
SyncEvent.project(SessionEvent.Tool.Failed.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.failed", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Started), (db, data, event) => {
SyncEvent.project(SessionEvent.Reasoning.Started.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.reasoning.started", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Delta), () => {}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Ended), (db, data, event) => {
SyncEvent.project(SessionEvent.Reasoning.Delta.Sync, () => {}),
SyncEvent.project(SessionEvent.Reasoning.Ended.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.reasoning.ended", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Retried), (db, data, event) => {
SyncEvent.project(SessionEvent.Retried.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.retried", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Started), (db, data, event) => {
SyncEvent.project(SessionEvent.Compaction.Started.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.compaction.started", data })
}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Delta), () => {}),
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Ended), (db, data, event) => {
SyncEvent.project(SessionEvent.Compaction.Delta.Sync, () => {}),
SyncEvent.project(SessionEvent.Compaction.Ended.Sync, (db, data, event) => {
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.compaction.ended", data })
}),
]
+12 -12
View File
@@ -52,9 +52,8 @@ import { TaskTool, type TaskPromptOps } from "@/tool/task"
import { SessionRunState } from "./run-state"
import { EffectBridge } from "@/effect/bridge"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2 } from "@opencode-ai/core/event"
import { EventV2Bridge } from "@/event-v2-bridge"
import { SessionEvent } from "@opencode-ai/core/session-event"
import { SyncEvent } from "@/sync"
import { SessionEvent } from "@/v2/session-event"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { AgentAttachment, FileAttachment, ReferenceAttachment, Source } from "@opencode-ai/core/session-prompt"
@@ -205,7 +204,7 @@ export const layer = Layer.effect(
const sys = yield* SystemPrompt.Service
const llm = yield* LLM.Service
const references = yield* Reference.Service
const events = yield* EventV2Bridge.Service
const sync = yield* SyncEvent.Service
const flags = yield* RuntimeFlags.Service
const runner = Effect.fn("SessionPrompt.runner")(function* () {
return yield* EffectBridge.make()
@@ -945,6 +944,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
providerID: model.providerID,
}
yield* sessions.updateMessage(msg)
const callID = ulid()
const started = Date.now()
const part: MessageV2.ToolPart = {
type: "tool",
@@ -961,10 +961,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}
yield* sessions.updatePart(part)
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Shell.Started, {
yield* sync.run(SessionEvent.Shell.Started.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(started),
callID: part.callID,
callID,
command: input.command,
})
}
@@ -984,7 +984,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}
const completed = Date.now()
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Shell.Ended, {
yield* sync.run(SessionEvent.Shell.Ended.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(completed),
callID: part.callID,
@@ -1134,7 +1134,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}
if (current?.agent !== info.agent) {
yield* events.publish(SessionEvent.AgentSwitched, {
yield* sync.run(SessionEvent.AgentSwitched.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
agent: info.agent,
@@ -1145,7 +1145,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
current.model.id !== info.model.modelID ||
(current.model.variant === "default" ? undefined : current.model.variant) !== info.model.variant
) {
yield* events.publish(SessionEvent.ModelSwitched, {
yield* sync.run(SessionEvent.ModelSwitched.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
model: {
@@ -1586,7 +1586,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
)
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Prompted, {
yield* sync.run(SessionEvent.Prompted.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
prompt: {
@@ -1600,7 +1600,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
for (const text of nextPrompt.synthetic) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (flags.experimentalEventSystem) {
yield* events.publish(SessionEvent.Synthetic, {
yield* sync.run(SessionEvent.Synthetic.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
text,
@@ -2038,13 +2038,13 @@ export const defaultLayer = Layer.suspend(() =>
Layer.provide(Image.defaultLayer),
Layer.provide(
Layer.mergeAll(
EventV2Bridge.defaultLayer,
Agent.defaultLayer,
SystemPrompt.defaultLayer,
LLM.defaultLayer,
Reference.defaultLayer,
Bus.layer,
CrossSpawnSpawner.defaultLayer,
SyncEvent.defaultLayer,
RuntimeFlags.defaultLayer,
),
),
+7 -2
View File
@@ -1,10 +1,15 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { Session as CoreSession } from "@opencode-ai/core/session"
import { withStatics } from "@opencode-ai/core/schema"
export const SessionID = CoreSession.ID
export const SessionID = Schema.String.check(Schema.isStartsWith("ses")).pipe(
Schema.brand("SessionID"),
withStatics((s) => ({
descending: (id?: string) => s.make(Identifier.descending("session", id)),
})),
)
export type SessionID = Schema.Schema.Type<typeof SessionID>
export const MessageID = Schema.String.check(Schema.isStartsWith("msg")).pipe(
+1 -1
View File
@@ -1,7 +1,7 @@
import { sqliteTable, text, integer, index, primaryKey, real } from "drizzle-orm/sqlite-core"
import { ProjectTable } from "../project/project.sql"
import type { MessageV2 } from "./message-v2"
import type { SessionMessage } from "@opencode-ai/core/session-message"
import type { SessionMessage } from "../v2/session-message"
import type { Snapshot } from "../snapshot"
import type { Permission } from "../permission"
import type { ProjectID } from "../project/schema"
+21 -58
View File
@@ -1,7 +1,3 @@
// Legacy sync event system. It should stay unaware of core EventV2 execution;
// the only temporary V2 coupling here is exposing versioned core event schemas
// in effectPayloads() so existing HTTP/SDK schema generation remains stable.
// Remove that registry read when event schemas are generated from core directly.
import { Database } from "@/storage/db"
import { eq } from "drizzle-orm"
import { GlobalBus } from "@/bus/global"
@@ -13,7 +9,6 @@ import type { WorkspaceID } from "@/control-plane/schema"
import { EventID } from "./schema"
import { Context, Effect, Layer, Schema as EffectSchema } from "effect"
import type { DeepMutable } from "@opencode-ai/core/schema"
import { EventV2 } from "@opencode-ai/core/event"
import { serviceUse } from "@/effect/service-use"
import { InstanceState } from "@/effect/instance-state"
import { RuntimeFlags } from "@/effect/runtime-flags"
@@ -214,7 +209,7 @@ export const defaultLayer = layer.pipe(Layer.provide(RuntimeFlags.defaultLayer))
export const use = serviceUse(Service)
export const registry = new Map<string, Definition>()
let projectors: Map<string, ProjectorFunc> | undefined
let projectors: Map<Definition, ProjectorFunc> | undefined
const versions = new Map<string, number>()
let frozen = false
let convertEvent: ConvertEvent
@@ -226,17 +221,7 @@ export function reset() {
}
export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; convertEvent?: ConvertEvent }) {
projectors = new Map(input.projectors.map(([def, func]) => [versionedType(def.type, def.version), func]))
for (let entry of EventV2.registry.values()) {
if (!entry.version || !entry.aggregate) continue
register({
type: entry.type,
version: entry.version,
aggregate: entry.aggregate,
properties: entry.data,
schema: entry.data,
})
}
projectors = new Map(input.projectors)
// Install all the latest event defs to the bus. We only ever emit
// latest versions from code, and keep around old versions for
@@ -244,6 +229,7 @@ export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; co
// simplifies the bus to only use unversioned latest events
for (let [type, version] of versions.entries()) {
let def = registry.get(versionedType(type, version))!
BusEvent.define(def.type, def.properties)
}
@@ -283,7 +269,9 @@ export function define<
properties: (input.busSchema ?? input.schema) as BusSchema,
}
register(def)
versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0))
registry.set(versionedType(def.type, def.version), def)
return def
}
@@ -295,11 +283,6 @@ export function project<Def extends Definition>(
return [def, func as ProjectorFunc]
}
function register(def: Definition) {
versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0))
registry.set(versionedType(def.type, def.version), def)
}
function process<Def extends Definition>(
def: Def,
event: Event<Def>,
@@ -309,10 +292,9 @@ function process<Def extends Definition>(
throw new Error("No projectors available. Call `SyncEvent.init` to install projectors")
}
const projector = projectors.get(versionedType(def.type, def.version))
const projector = projectors.get(def)
if (!projector) {
if (!def.type.includes("next")) throw new Error(`Projector not found for event: ${def.type}`)
return
throw new Error(`Projector not found for event: ${def.type}`)
}
Database.transaction((tx) => {
@@ -373,38 +355,19 @@ function process<Def extends Definition>(
}
export function effectPayloads() {
return [
...registry
.entries()
.map(([type, def]) =>
EffectSchema.Struct({
type: EffectSchema.Literal("sync"),
name: EffectSchema.Literal(type),
id: EffectSchema.String,
seq: EffectSchema.Finite,
aggregateID: EffectSchema.Literal(def.aggregate),
data: def.schema,
}).annotate({ identifier: `SyncEvent.${type}` }),
)
.toArray(),
...EventV2.registry
.values()
.filter(
(definition) =>
definition.version !== undefined && !registry.has(versionedType(definition.type, definition.version)),
)
.map((definition) =>
EffectSchema.Struct({
type: EffectSchema.Literal("sync"),
name: EffectSchema.Literal(versionedType(definition.type, definition.version!)),
id: EffectSchema.String,
seq: EffectSchema.Finite,
aggregateID: EffectSchema.Literal(definition.aggregate!),
data: definition.data,
}).annotate({ identifier: `SyncEvent.${definition.type}` }),
)
.toArray(),
]
return registry
.entries()
.map(([type, def]) =>
EffectSchema.Struct({
type: EffectSchema.Literal("sync"),
name: EffectSchema.Literal(type),
id: EffectSchema.String,
seq: EffectSchema.Finite,
aggregateID: EffectSchema.Literal(def.aggregate),
data: def.schema,
}).annotate({ identifier: `SyncEvent.${type}` }),
)
.toArray()
}
export * as SyncEvent from "."
+1 -6
View File
@@ -201,8 +201,7 @@ export const layer: Layer.Layer<
// `match` is an absolute filesystem path from `Glob.scanSync(..., { absolute: true })`.
// Import it as `file://` so Node on Windows accepts the dynamic import.
const mod = yield* Effect.promise(() => import(pathToFileURL(match).href))
for (const [id, def] of Object.entries(mod)) {
if (!isPluginTool(def)) continue
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
}
}
@@ -397,10 +396,6 @@ function isZodType(value: unknown): value is z.ZodType {
return typeof value === "object" && value !== null && "_zod" in value
}
function isPluginTool(value: unknown): value is ToolDefinition {
return typeof value === "object" && value !== null && "args" in value && "description" in value && "execute" in value
}
function isJsonSchemaDefinition(value: unknown): value is JSONSchema7Definition {
return typeof value === "boolean" || (typeof value === "object" && value !== null && !Array.isArray(value))
}
+43
View File
@@ -0,0 +1,43 @@
import { Identifier } from "@/id/id"
import { SyncEvent } from "@/sync"
import { withStatics } from "@opencode-ai/core/schema"
import * as Schema from "effect/Schema"
export const ID = Schema.String.pipe(
Schema.brand("Event.ID"),
withStatics((s) => ({
create: () => s.make(Identifier.create("evt", "ascending")),
})),
)
export type ID = Schema.Schema.Type<typeof ID>
export function define<const Type extends string, Fields extends Schema.Struct.Fields>(input: {
type: Type
schema: Fields
aggregate: string
version?: number
}) {
const Payload = Schema.Struct({
id: ID,
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
type: Schema.Literal(input.type),
data: Schema.Struct(input.schema),
}).annotate({
identifier: input.type,
})
const Sync = SyncEvent.define({
type: input.type,
version: input.version ?? 1,
aggregate: input.aggregate,
schema: Payload.fields.data,
})
return Object.assign(Payload, {
Sync,
version: input.version,
aggregate: input.aggregate,
})
}
export * as EventV2 from "./event"
@@ -1,13 +1,12 @@
import { Schema } from "effect"
import { SessionID } from "@/session/schema"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { EventV2 } from "./event"
import { ModelV2 } from "./model"
import { NonNegativeInt } from "./schema"
import { Session } from "./session"
import { FileAttachment, Prompt } from "./session-prompt"
import { ToolOutput } from "./tool-output"
import { V2Schema } from "./v2-schema"
import { FileAttachment, Prompt } from "@opencode-ai/core/session-prompt"
import { Schema } from "effect"
export { FileAttachment }
import { ToolOutput } from "@opencode-ai/core/tool-output"
import { V2Schema } from "@opencode-ai/core/v2-schema"
import { ModelV2 } from "@opencode-ai/core/model"
export const Source = Schema.Struct({
start: NonNegativeInt,
@@ -16,94 +15,92 @@ export const Source = Schema.Struct({
}).annotate({
identifier: "session.next.event.source",
})
export type Source = typeof Source.Type
export type Source = Schema.Schema.Type<typeof Source>
const Base = {
timestamp: V2Schema.DateTimeUtcFromMillis,
sessionID: Session.ID,
sessionID: SessionID,
}
const options = {
aggregate: "sessionID",
version: 1,
} as const
export const UnknownError = Schema.Struct({
type: Schema.Literal("unknown"),
message: Schema.String,
}).annotate({
identifier: "Session.Error.Unknown",
})
export type UnknownError = typeof UnknownError.Type
export type UnknownError = Schema.Schema.Type<typeof UnknownError>
export const AgentSwitched = EventV2.define({
type: "session.next.agent.switched",
...options,
aggregate: "sessionID",
version: 1,
schema: {
...Base,
agent: Schema.String,
},
})
export type AgentSwitched = typeof AgentSwitched.Type
export type AgentSwitched = Schema.Schema.Type<typeof AgentSwitched>
export const ModelSwitched = EventV2.define({
type: "session.next.model.switched",
...options,
aggregate: "sessionID",
version: 1,
schema: {
...Base,
model: ModelV2.Ref,
},
})
export type ModelSwitched = typeof ModelSwitched.Type
export type ModelSwitched = Schema.Schema.Type<typeof ModelSwitched>
export const Prompted = EventV2.define({
type: "session.next.prompted",
...options,
aggregate: "sessionID",
version: 1,
schema: {
...Base,
prompt: Prompt,
},
})
export type Prompted = typeof Prompted.Type
export type Prompted = Schema.Schema.Type<typeof Prompted>
export const Synthetic = EventV2.define({
type: "session.next.synthetic",
...options,
aggregate: "sessionID",
schema: {
...Base,
text: Schema.String,
},
})
export type Synthetic = typeof Synthetic.Type
export type Synthetic = Schema.Schema.Type<typeof Synthetic>
export namespace Shell {
export const Started = EventV2.define({
type: "session.next.shell.started",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
command: Schema.String,
},
})
export type Started = typeof Started.Type
export type Started = Schema.Schema.Type<typeof Started>
export const Ended = EventV2.define({
type: "session.next.shell.ended",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
output: Schema.String,
},
})
export type Ended = typeof Ended.Type
export type Ended = Schema.Schema.Type<typeof Ended>
}
export namespace Step {
export const Started = EventV2.define({
type: "session.next.step.started",
...options,
aggregate: "sessionID",
schema: {
...Base,
agent: Schema.String,
@@ -111,11 +108,11 @@ export namespace Step {
snapshot: Schema.String.pipe(Schema.optional),
},
})
export type Started = typeof Started.Type
export type Started = Schema.Schema.Type<typeof Started>
export const Ended = EventV2.define({
type: "session.next.step.ended",
...options,
aggregate: "sessionID",
schema: {
...Base,
finish: Schema.String,
@@ -132,123 +129,123 @@ export namespace Step {
snapshot: Schema.String.pipe(Schema.optional),
},
})
export type Ended = typeof Ended.Type
export type Ended = Schema.Schema.Type<typeof Ended>
export const Failed = EventV2.define({
type: "session.next.step.failed",
...options,
aggregate: "sessionID",
schema: {
...Base,
error: UnknownError,
},
})
export type Failed = typeof Failed.Type
export type Failed = Schema.Schema.Type<typeof Failed>
}
export namespace Text {
export const Started = EventV2.define({
type: "session.next.text.started",
...options,
aggregate: "sessionID",
schema: {
...Base,
},
})
export type Started = typeof Started.Type
export type Started = Schema.Schema.Type<typeof Started>
export const Delta = EventV2.define({
type: "session.next.text.delta",
...options,
aggregate: "sessionID",
schema: {
...Base,
delta: Schema.String,
},
})
export type Delta = typeof Delta.Type
export type Delta = Schema.Schema.Type<typeof Delta>
export const Ended = EventV2.define({
type: "session.next.text.ended",
...options,
aggregate: "sessionID",
schema: {
...Base,
text: Schema.String,
},
})
export type Ended = typeof Ended.Type
export type Ended = Schema.Schema.Type<typeof Ended>
}
export namespace Reasoning {
export const Started = EventV2.define({
type: "session.next.reasoning.started",
...options,
aggregate: "sessionID",
schema: {
...Base,
reasoningID: Schema.String,
},
})
export type Started = typeof Started.Type
export type Started = Schema.Schema.Type<typeof Started>
export const Delta = EventV2.define({
type: "session.next.reasoning.delta",
...options,
aggregate: "sessionID",
schema: {
...Base,
reasoningID: Schema.String,
delta: Schema.String,
},
})
export type Delta = typeof Delta.Type
export type Delta = Schema.Schema.Type<typeof Delta>
export const Ended = EventV2.define({
type: "session.next.reasoning.ended",
...options,
aggregate: "sessionID",
schema: {
...Base,
reasoningID: Schema.String,
text: Schema.String,
},
})
export type Ended = typeof Ended.Type
export type Ended = Schema.Schema.Type<typeof Ended>
}
export namespace Tool {
export namespace Input {
export const Started = EventV2.define({
type: "session.next.tool.input.started",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
name: Schema.String,
},
})
export type Started = typeof Started.Type
export type Started = Schema.Schema.Type<typeof Started>
export const Delta = EventV2.define({
type: "session.next.tool.input.delta",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
delta: Schema.String,
},
})
export type Delta = typeof Delta.Type
export type Delta = Schema.Schema.Type<typeof Delta>
export const Ended = EventV2.define({
type: "session.next.tool.input.ended",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
text: Schema.String,
},
})
export type Ended = typeof Ended.Type
export type Ended = Schema.Schema.Type<typeof Ended>
}
export const Called = EventV2.define({
type: "session.next.tool.called",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
@@ -260,11 +257,11 @@ export namespace Tool {
}),
},
})
export type Called = typeof Called.Type
export type Called = Schema.Schema.Type<typeof Called>
export const Progress = EventV2.define({
type: "session.next.tool.progress",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
@@ -272,11 +269,11 @@ export namespace Tool {
content: Schema.Array(ToolOutput.Content),
},
})
export type Progress = typeof Progress.Type
export type Progress = Schema.Schema.Type<typeof Progress>
export const Success = EventV2.define({
type: "session.next.tool.success",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
@@ -288,11 +285,11 @@ export namespace Tool {
}),
},
})
export type Success = typeof Success.Type
export type Success = Schema.Schema.Type<typeof Success>
export const Failed = EventV2.define({
type: "session.next.tool.failed",
...options,
aggregate: "sessionID",
schema: {
...Base,
callID: Schema.String,
@@ -303,7 +300,7 @@ export namespace Tool {
}),
},
})
export type Failed = typeof Failed.Type
export type Failed = Schema.Schema.Type<typeof Failed>
}
export const RetryError = Schema.Struct({
@@ -316,50 +313,49 @@ export const RetryError = Schema.Struct({
}).annotate({
identifier: "session.next.retry_error",
})
export type RetryError = typeof RetryError.Type
export type RetryError = Schema.Schema.Type<typeof RetryError>
export const Retried = EventV2.define({
type: "session.next.retried",
...options,
aggregate: "sessionID",
schema: {
...Base,
attempt: Schema.Finite,
error: RetryError,
},
})
export type Retried = typeof Retried.Type
export type Retried = Schema.Schema.Type<typeof Retried>
export namespace Compaction {
export const Started = EventV2.define({
type: "session.next.compaction.started",
...options,
aggregate: "sessionID",
schema: {
...Base,
reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")]),
},
})
export type Started = typeof Started.Type
export type Started = Schema.Schema.Type<typeof Started>
export const Delta = EventV2.define({
type: "session.next.compaction.delta",
...options,
aggregate: "sessionID",
schema: {
...Base,
text: Schema.String,
},
})
export type Delta = typeof Delta.Type
export const Ended = EventV2.define({
type: "session.next.compaction.ended",
...options,
aggregate: "sessionID",
schema: {
...Base,
text: Schema.String,
include: Schema.String.pipe(Schema.optional),
},
})
export type Ended = typeof Ended.Type
export type Ended = Schema.Schema.Type<typeof Ended>
}
export const All = Schema.Union(
@@ -396,7 +392,16 @@ export const All = Schema.Union(
},
).pipe(Schema.toTaggedUnion("type"))
export type Event = typeof All.Type
// user
// assistant
// assistant
// assistant
// user
// compaction marker
// -> text
// assistant
export type Event = Schema.Schema.Type<typeof All>
export type Type = Event["type"]
export * as SessionEvent from "./session-event"
@@ -1,10 +1,10 @@
import { Schema } from "effect"
import { Prompt } from "./session-prompt"
import { Prompt } from "@opencode-ai/core/session-prompt"
import { SessionEvent } from "./session-event"
import { EventV2 } from "./event"
import { ToolOutput } from "./tool-output"
import { V2Schema } from "./v2-schema"
import { ModelV2 } from "./model"
import { ToolOutput } from "@opencode-ai/core/tool-output"
import { V2Schema } from "@opencode-ai/core/v2-schema"
import { ModelV2 } from "@opencode-ai/core/model"
export const ID = EventV2.ID
export type ID = Schema.Schema.Type<typeof ID>
@@ -20,7 +20,7 @@ const Base = {
export class AgentSwitched extends Schema.Class<AgentSwitched>("Session.Message.AgentSwitched")({
...Base,
type: Schema.Literal("agent-switched"),
agent: SessionEvent.AgentSwitched.data.fields.agent,
agent: SessionEvent.AgentSwitched.fields.data.fields.agent,
}) {}
export class ModelSwitched extends Schema.Class<ModelSwitched>("Session.Message.ModelSwitched")({
@@ -43,16 +43,16 @@ export class User extends Schema.Class<User>("Session.Message.User")({
export class Synthetic extends Schema.Class<Synthetic>("Session.Message.Synthetic")({
...Base,
sessionID: SessionEvent.Synthetic.data.fields.sessionID,
text: SessionEvent.Synthetic.data.fields.text,
sessionID: SessionEvent.Synthetic.fields.data.fields.sessionID,
text: SessionEvent.Synthetic.fields.data.fields.text,
type: Schema.Literal("synthetic"),
}) {}
export class Shell extends Schema.Class<Shell>("Session.Message.Shell")({
...Base,
type: Schema.Literal("shell"),
callID: SessionEvent.Shell.Started.data.fields.callID,
command: SessionEvent.Shell.Started.data.fields.command,
callID: SessionEvent.Shell.Started.fields.data.fields.callID,
command: SessionEvent.Shell.Started.fields.data.fields.command,
output: Schema.String,
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
@@ -130,7 +130,7 @@ export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistan
...Base,
type: Schema.Literal("assistant"),
agent: Schema.String,
model: SessionEvent.Step.Started.data.fields.model,
model: SessionEvent.Step.Started.fields.data.fields.model,
content: AssistantContent.pipe(Schema.Array),
snapshot: Schema.Struct({
start: Schema.String.pipe(Schema.optional),
@@ -147,7 +147,7 @@ export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistan
write: Schema.Finite,
}),
}).pipe(Schema.optional),
error: SessionEvent.Step.Failed.data.fields.error.pipe(Schema.optional),
error: SessionEvent.Step.Failed.fields.data.fields.error.pipe(Schema.optional),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
@@ -156,7 +156,7 @@ export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistan
export class Compaction extends Schema.Class<Compaction>("Session.Message.Compaction")({
type: Schema.Literal("compaction"),
reason: SessionEvent.Compaction.Started.data.fields.reason,
reason: SessionEvent.Compaction.Started.fields.data.fields.reason,
summary: Schema.String,
include: Schema.String.pipe(Schema.optional),
...Base,
+8 -8
View File
@@ -4,14 +4,14 @@ import { WorkspaceID } from "@/control-plane/schema"
import { and, asc, desc, eq, gt, gte, isNull, like, lt, or, type SQL } from "@/storage/db"
import * as Database from "@/storage/db"
import { Context, DateTime, Effect, Layer, Option, Schema } from "effect"
import { SessionMessage } from "@opencode-ai/core/session-message"
import { SessionMessage } from "./session-message"
import type { Prompt } from "@opencode-ai/core/session-prompt"
import { EventV2 } from "./event"
import { ProjectID } from "@/project/schema"
import { SessionEvent } from "@opencode-ai/core/session-event"
import { SessionEvent } from "./session-event"
import { V2Schema } from "@opencode-ai/core/v2-schema"
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
import { EventV2 } from "@opencode-ai/core/event"
import { EventV2Bridge } from "@/event-v2-bridge"
import { SyncEvent } from "@/sync"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProviderV2 } from "@opencode-ai/core/provider"
@@ -125,7 +125,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const events = yield* EventV2Bridge.Service
const sync = yield* SyncEvent.Service
const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message)
const decode = (row: typeof SessionMessageTable.$inferSelect) =>
@@ -292,14 +292,14 @@ export const layer = Layer.effect(
shell: Effect.fn("V2Session.shell")(function* (_input) {}),
skill: Effect.fn("V2Session.skill")(function* (_input) {}),
switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
yield* events.publish(SessionEvent.AgentSwitched, {
yield* sync.run(SessionEvent.AgentSwitched.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(Date.now()),
agent: input.agent,
})
}),
switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
yield* events.publish(SessionEvent.ModelSwitched, {
yield* sync.run(SessionEvent.ModelSwitched.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(Date.now()),
model: input.model,
@@ -334,6 +334,6 @@ export const layer = Layer.effect(
}),
)
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer))
export const defaultLayer = layer.pipe(Layer.provide(SyncEvent.defaultLayer))
export * as SessionV2 from "./session"
@@ -19,7 +19,7 @@ import { MessageID, PartID, SessionID, type SessionID as SessionIDType } from ".
import { MessageV2 } from "../../src/session/message-v2"
import { Database } from "@/storage/db"
import { SessionMessageTable, SessionTable } from "@/session/session.sql"
import { SessionMessage } from "@opencode-ai/core/session-message"
import { SessionMessage } from "../../src/v2/session-message"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProviderV2 } from "@opencode-ai/core/provider"
import * as DateTime from "effect/DateTime"
@@ -29,7 +29,6 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { TestConfig } from "../fixture/config"
import { SyncEvent } from "@/sync"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2Bridge } from "@/event-v2-bridge"
void Log.init({ print: false })
@@ -228,7 +227,6 @@ const deps = Layer.mergeAll(
Config.defaultLayer,
SyncEvent.defaultLayer,
RuntimeFlags.layer({ experimentalEventSystem: true }),
EventV2Bridge.defaultLayer,
)
const env = Layer.mergeAll(
@@ -278,7 +276,6 @@ function compactionProcessLayer(options?: CompactionProcessOptions) {
Layer.provide(options?.config ?? Config.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
Layer.provide(EventV2Bridge.defaultLayer),
)
}
@@ -26,7 +26,6 @@ import { testEffect } from "../lib/effect"
import { raw, reply, TestLLMServer } from "../lib/llm-server"
import { SyncEvent } from "@/sync"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2Bridge } from "@/event-v2-bridge"
void Log.init({ print: false })
@@ -181,7 +180,6 @@ const deps = Layer.mergeAll(
Provider.defaultLayer,
status,
SyncEvent.defaultLayer,
EventV2Bridge.defaultLayer,
).pipe(Layer.provideMerge(infra))
const env = Layer.mergeAll(
TestLLMServer.layer,
@@ -53,7 +53,6 @@ import { awaitWithTimeout, pollWithTimeout, testEffect } from "../lib/effect"
import { reply, TestLLMServer } from "../lib/llm-server"
import { SyncEvent } from "@/sync"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2Bridge } from "@/event-v2-bridge"
void Log.init({ print: false })
@@ -181,7 +180,6 @@ function makeHttp(input?: { processor?: "blocking" }) {
BackgroundJob.defaultLayer,
status,
SyncEvent.defaultLayer,
EventV2Bridge.defaultLayer,
).pipe(Layer.provideMerge(infra))
const question = Question.layer.pipe(Layer.provideMerge(deps))
const todo = Todo.layer.pipe(Layer.provideMerge(deps))
@@ -61,7 +61,6 @@ import { Format } from "../../src/format"
import { Reference } from "../../src/reference/reference"
import { SyncEvent } from "@/sync"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2Bridge } from "@/event-v2-bridge"
void Log.init({ print: false })
@@ -130,7 +129,6 @@ function makeHttp() {
BackgroundJob.defaultLayer,
status,
SyncEvent.defaultLayer,
EventV2Bridge.defaultLayer,
).pipe(Layer.provideMerge(infra))
const question = Question.layer.pipe(Layer.provideMerge(deps))
const todo = Todo.layer.pipe(Layer.provideMerge(deps))
@@ -158,33 +158,6 @@ describe("tool.registry", () => {
}),
)
it.instance("ignores non-tool exports in .opencode/tool files", () =>
Effect.gen(function* () {
const test = yield* TestInstance
const tool = path.join(test.directory, ".opencode", "tool")
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
yield* Effect.promise(() =>
Bun.write(
path.join(tool, "mixed.ts"),
[
"export const helper = 'not a tool'",
"export default {",
" description: 'mixed tool',",
" args: {},",
" execute: async () => 'ok',",
"}",
"",
].join("\n"),
),
)
const registry = yield* ToolRegistry.Service
const ids = yield* registry.ids()
expect(ids).toContain("mixed")
expect(ids).not.toContain("mixed_helper")
}),
)
it.instance("loads tools from .opencode/tools (plural)", () =>
Effect.gen(function* () {
const test = yield* TestInstance
@@ -1,11 +1,11 @@
import { expect, test } from "bun:test"
import * as DateTime from "effect/DateTime"
import { SessionID } from "../../src/session/schema"
import { EventV2 } from "@opencode-ai/core/event"
import { EventV2 } from "../../src/v2/event"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { SessionEvent } from "@opencode-ai/core/session-event"
import { SessionMessageUpdater } from "@opencode-ai/core/session-message-updater"
import { SessionEvent } from "../../src/v2/session-event"
import { SessionMessageUpdater } from "../../src/v2/session-message-updater"
test("step snapshots carry over to assistant messages", () => {
const state: SessionMessageUpdater.MemoryState = { messages: [] }
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.15.0",
"version": "1.14.51",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.15.0",
"version": "1.14.51",
"type": "module",
"license": "MIT",
"scripts": {
+6 -6
View File
@@ -4387,14 +4387,14 @@ export class Model extends HeyApiClient {
*/
public list<ThrowOnError extends boolean = false>(
parameters?: {
location?: {
instance?: {
directory?: string
workspace?: string
}
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }])
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }])
return (options?.client ?? this.client).get<V2ModelListResponses, unknown, ThrowOnError>({
url: "/api/model",
...options,
@@ -4411,14 +4411,14 @@ export class Provider2 extends HeyApiClient {
*/
public list<ThrowOnError extends boolean = false>(
parameters?: {
location?: {
instance?: {
directory?: string
workspace?: string
}
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }])
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }])
return (options?.client ?? this.client).get<V2ProviderListResponses, unknown, ThrowOnError>({
url: "/api/provider",
...options,
@@ -4434,7 +4434,7 @@ export class Provider2 extends HeyApiClient {
public get<ThrowOnError extends boolean = false>(
parameters: {
providerID: string
location?: {
instance?: {
directory?: string
workspace?: string
}
@@ -4447,7 +4447,7 @@ export class Provider2 extends HeyApiClient {
{
args: [
{ in: "path", key: "providerID" },
{ in: "query", key: "location" },
{ in: "query", key: "instance" },
],
},
],
+117 -676
View File
@@ -21,6 +21,7 @@ export type Event =
| EventTodoUpdated
| EventSessionStatus
| EventSessionIdle
| EventSessionCompacted
| EventTuiPromptAppend
| EventTuiCommandExecute
| EventTuiToastShow1
@@ -29,7 +30,6 @@ export type Event =
| EventMcpBrowserOpenFailed
| EventCommandExecuted
| EventProjectUpdated
| EventSessionCompacted
| EventVcsBranchUpdated
| EventWorkspaceReady
| EventWorkspaceFailed
@@ -42,42 +42,8 @@ export type Event =
| EventPtyDeleted
| EventInstallationUpdated
| EventInstallationUpdateAvailable
| EventMessageUpdated
| EventMessageRemoved
| EventMessagePartUpdated
| EventMessagePartRemoved
| EventSessionCreated
| EventSessionUpdated
| EventSessionDeleted
| EventSessionNextAgentSwitched
| EventSessionNextModelSwitched
| EventSessionNextPrompted
| EventSessionNextSynthetic
| EventSessionNextShellStarted
| EventSessionNextShellEnded
| EventSessionNextStepStarted
| EventSessionNextStepEnded
| EventSessionNextStepFailed
| EventSessionNextTextStarted
| EventSessionNextTextDelta
| EventSessionNextTextEnded
| EventSessionNextReasoningStarted
| EventSessionNextReasoningDelta
| EventSessionNextReasoningEnded
| EventSessionNextToolInputStarted
| EventSessionNextToolInputDelta
| EventSessionNextToolInputEnded
| EventSessionNextToolCalled
| EventSessionNextToolProgress
| EventSessionNextToolSuccess
| EventSessionNextToolFailed
| EventSessionNextRetried
| EventSessionNextCompactionStarted
| EventSessionNextCompactionDelta
| EventSessionNextCompactionEnded
| EventServerConnected
| EventGlobalDisposed
| EventCatalogModelUpdated
export type OAuth = {
type: "oauth"
@@ -806,6 +772,7 @@ export type GlobalEvent = {
| EventTodoUpdated
| EventSessionStatus
| EventSessionIdle
| EventSessionCompacted
| EventTuiPromptAppend
| EventTuiCommandExecute
| EventTuiToastShow
@@ -814,7 +781,6 @@ export type GlobalEvent = {
| EventMcpBrowserOpenFailed
| EventCommandExecuted
| EventProjectUpdated
| EventSessionCompacted
| EventVcsBranchUpdated
| EventWorkspaceReady
| EventWorkspaceFailed
@@ -827,42 +793,8 @@ export type GlobalEvent = {
| EventPtyDeleted
| EventInstallationUpdated
| EventInstallationUpdateAvailable
| EventMessageUpdated
| EventMessageRemoved
| EventMessagePartUpdated
| EventMessagePartRemoved
| EventSessionCreated
| EventSessionUpdated
| EventSessionDeleted
| EventSessionNextAgentSwitched
| EventSessionNextModelSwitched
| EventSessionNextPrompted
| EventSessionNextSynthetic
| EventSessionNextShellStarted
| EventSessionNextShellEnded
| EventSessionNextStepStarted
| EventSessionNextStepEnded
| EventSessionNextStepFailed
| EventSessionNextTextStarted
| EventSessionNextTextDelta
| EventSessionNextTextEnded
| EventSessionNextReasoningStarted
| EventSessionNextReasoningDelta
| EventSessionNextReasoningEnded
| EventSessionNextToolInputStarted
| EventSessionNextToolInputDelta
| EventSessionNextToolInputEnded
| EventSessionNextToolCalled
| EventSessionNextToolProgress
| EventSessionNextToolSuccess
| EventSessionNextToolFailed
| EventSessionNextRetried
| EventSessionNextCompactionStarted
| EventSessionNextCompactionDelta
| EventSessionNextCompactionEnded
| EventServerConnected
| EventGlobalDisposed
| EventCatalogModelUpdated
| SyncEventMessageUpdated
| SyncEventMessageRemoved
| SyncEventMessagePartUpdated
@@ -2542,6 +2474,14 @@ export type EventSessionIdle = {
}
}
export type EventSessionCompacted = {
id: string
type: "session.compacted"
properties: {
sessionID: string
}
}
export type EventMcpToolsChanged = {
id: string
type: "mcp.tools.changed"
@@ -2576,14 +2516,6 @@ export type EventProjectUpdated = {
properties: Project
}
export type EventSessionCompacted = {
id: string
type: "session.compacted"
properties: {
sessionID: string
}
}
export type EventVcsBranchUpdated = {
id: string
type: "vcs.branch.updated"
@@ -2683,92 +2615,19 @@ export type EventInstallationUpdateAvailable = {
}
}
export type EventMessageUpdated = {
export type EventServerConnected = {
id: string
type: "message.updated"
type: "server.connected"
properties: {
sessionID: string
info: Message
[key: string]: unknown
}
}
export type EventMessageRemoved = {
export type EventGlobalDisposed = {
id: string
type: "message.removed"
type: "global.disposed"
properties: {
sessionID: string
messageID: string
}
}
export type EventMessagePartUpdated = {
id: string
type: "message.part.updated"
properties: {
sessionID: string
part: Part
time: number
}
}
export type EventMessagePartRemoved = {
id: string
type: "message.part.removed"
properties: {
sessionID: string
messageID: string
partID: string
}
}
export type EventSessionCreated = {
id: string
type: "session.created"
properties: {
sessionID: string
info: Session
}
}
export type EventSessionUpdated = {
id: string
type: "session.updated"
properties: {
sessionID: string
info: Session
}
}
export type EventSessionDeleted = {
id: string
type: "session.deleted"
properties: {
sessionID: string
info: Session
}
}
export type EventSessionNextAgentSwitched = {
id: string
type: "session.next.agent.switched"
properties: {
timestamp: number
sessionID: string
agent: string
}
}
export type EventSessionNextModelSwitched = {
id: string
type: "session.next.model.switched"
properties: {
timestamp: number
sessionID: string
model: {
id: string
providerID: string
variant: string
}
[key: string]: unknown
}
}
@@ -2803,214 +2662,11 @@ export type PromptReferenceAttachment = {
source?: PromptSource
}
export type EventSessionNextPrompted = {
id: string
type: "session.next.prompted"
properties: {
timestamp: number
sessionID: string
prompt: Prompt
}
}
export type EventSessionNextSynthetic = {
id: string
type: "session.next.synthetic"
properties: {
timestamp: number
sessionID: string
text: string
}
}
export type EventSessionNextShellStarted = {
id: string
type: "session.next.shell.started"
properties: {
timestamp: number
sessionID: string
callID: string
command: string
}
}
export type EventSessionNextShellEnded = {
id: string
type: "session.next.shell.ended"
properties: {
timestamp: number
sessionID: string
callID: string
output: string
}
}
export type EventSessionNextStepStarted = {
id: string
type: "session.next.step.started"
properties: {
timestamp: number
sessionID: string
agent: string
model: {
id: string
providerID: string
variant: string
}
snapshot?: string
}
}
export type EventSessionNextStepEnded = {
id: string
type: "session.next.step.ended"
properties: {
timestamp: number
sessionID: string
finish: string
cost: number
tokens: {
input: number
output: number
reasoning: number
cache: {
read: number
write: number
}
}
snapshot?: string
}
}
export type SessionErrorUnknown = {
type: "unknown"
message: string
}
export type EventSessionNextStepFailed = {
id: string
type: "session.next.step.failed"
properties: {
timestamp: number
sessionID: string
error: SessionErrorUnknown
}
}
export type EventSessionNextTextStarted = {
id: string
type: "session.next.text.started"
properties: {
timestamp: number
sessionID: string
}
}
export type EventSessionNextTextDelta = {
id: string
type: "session.next.text.delta"
properties: {
timestamp: number
sessionID: string
delta: string
}
}
export type EventSessionNextTextEnded = {
id: string
type: "session.next.text.ended"
properties: {
timestamp: number
sessionID: string
text: string
}
}
export type EventSessionNextReasoningStarted = {
id: string
type: "session.next.reasoning.started"
properties: {
timestamp: number
sessionID: string
reasoningID: string
}
}
export type EventSessionNextReasoningDelta = {
id: string
type: "session.next.reasoning.delta"
properties: {
timestamp: number
sessionID: string
reasoningID: string
delta: string
}
}
export type EventSessionNextReasoningEnded = {
id: string
type: "session.next.reasoning.ended"
properties: {
timestamp: number
sessionID: string
reasoningID: string
text: string
}
}
export type EventSessionNextToolInputStarted = {
id: string
type: "session.next.tool.input.started"
properties: {
timestamp: number
sessionID: string
callID: string
name: string
}
}
export type EventSessionNextToolInputDelta = {
id: string
type: "session.next.tool.input.delta"
properties: {
timestamp: number
sessionID: string
callID: string
delta: string
}
}
export type EventSessionNextToolInputEnded = {
id: string
type: "session.next.tool.input.ended"
properties: {
timestamp: number
sessionID: string
callID: string
text: string
}
}
export type EventSessionNextToolCalled = {
id: string
type: "session.next.tool.called"
properties: {
timestamp: number
sessionID: string
callID: string
tool: string
input: {
[key: string]: unknown
}
provider: {
executed: boolean
metadata?: {
[key: string]: unknown
}
}
}
}
export type ToolTextContent = {
type: "text"
text: string
@@ -3023,57 +2679,6 @@ export type ToolFileContent = {
name?: string
}
export type EventSessionNextToolProgress = {
id: string
type: "session.next.tool.progress"
properties: {
timestamp: number
sessionID: string
callID: string
structured: {
[key: string]: unknown
}
content: Array<ToolTextContent | ToolFileContent>
}
}
export type EventSessionNextToolSuccess = {
id: string
type: "session.next.tool.success"
properties: {
timestamp: number
sessionID: string
callID: string
structured: {
[key: string]: unknown
}
content: Array<ToolTextContent | ToolFileContent>
provider: {
executed: boolean
metadata?: {
[key: string]: unknown
}
}
}
}
export type EventSessionNextToolFailed = {
id: string
type: "session.next.tool.failed"
properties: {
timestamp: number
sessionID: string
callID: string
error: SessionErrorUnknown
provider: {
executed: boolean
metadata?: {
[key: string]: unknown
}
}
}
}
export type SessionNextRetryError = {
message: string
statusCode?: number
@@ -3087,170 +2692,6 @@ export type SessionNextRetryError = {
}
}
export type EventSessionNextRetried = {
id: string
type: "session.next.retried"
properties: {
timestamp: number
sessionID: string
attempt: number
error: SessionNextRetryError
}
}
export type EventSessionNextCompactionStarted = {
id: string
type: "session.next.compaction.started"
properties: {
timestamp: number
sessionID: string
reason: "auto" | "manual"
}
}
export type EventSessionNextCompactionDelta = {
id: string
type: "session.next.compaction.delta"
properties: {
timestamp: number
sessionID: string
text: string
}
}
export type EventSessionNextCompactionEnded = {
id: string
type: "session.next.compaction.ended"
properties: {
timestamp: number
sessionID: string
text: string
include?: string
}
}
export type EventServerConnected = {
id: string
type: "server.connected"
properties: {
[key: string]: unknown
}
}
export type EventGlobalDisposed = {
id: string
type: "global.disposed"
properties: {
[key: string]: unknown
}
}
export type ModelV2Info = {
id: string
apiID: string
providerID: string
family?: string
name: string
endpoint:
| {
type: "unknown"
}
| {
type: "openai/responses"
url: string
websocket?: boolean
}
| {
type: "openai/completions"
url: string
reasoning?:
| {
type: "reasoning_content"
}
| {
type: "reasoning_details"
}
}
| {
type: "anthropic/messages"
url: string
}
| {
type: "aisdk"
package: string
url?: string
}
capabilities: {
tools: boolean
input: Array<string>
output: Array<string>
}
options: {
headers: {
[key: string]: string
}
body: {
[key: string]: unknown
}
aisdk: {
provider: {
[key: string]: unknown
}
request: {
[key: string]: unknown
}
}
variant?: string
}
variants: Array<{
id: string
headers: {
[key: string]: string
}
body: {
[key: string]: unknown
}
aisdk: {
provider: {
[key: string]: unknown
}
request: {
[key: string]: unknown
}
}
}>
time: {
released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
}
cost: Array<{
tier?: {
type: "context"
size: number
}
input: number
output: number
cache: {
read: number
write: number
}
}>
status: "alpha" | "beta" | "deprecated" | "active"
enabled: boolean
limit: {
context: number
input?: number
output: number
}
}
export type EventCatalogModelUpdated = {
id: string
type: "catalog.model.updated"
properties: {
model: ModelV2Info
}
}
export type SessionInfo = {
id: string
parentID?: string
@@ -3486,6 +2927,104 @@ export type SessionMessage =
| SessionMessageAssistant
| SessionMessageCompaction
export type ModelV2Info = {
id: string
apiID: string
providerID: string
family?: string
name: string
endpoint:
| {
type: "unknown"
}
| {
type: "openai/responses"
url: string
websocket?: boolean
}
| {
type: "openai/completions"
url: string
reasoning?:
| {
type: "reasoning_content"
}
| {
type: "reasoning_details"
}
}
| {
type: "anthropic/messages"
url: string
}
| {
type: "aisdk"
package: string
url?: string
}
capabilities: {
tools: boolean
input: Array<string>
output: Array<string>
}
options: {
headers: {
[key: string]: string
}
body: {
[key: string]: unknown
}
aisdk: {
provider: {
[key: string]: unknown
}
request: {
[key: string]: unknown
}
}
variant?: string
}
variants: Array<{
id: string
headers: {
[key: string]: string
}
body: {
[key: string]: unknown
}
aisdk: {
provider: {
[key: string]: unknown
}
request: {
[key: string]: unknown
}
}
}>
time: {
released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
}
cost: Array<{
tier?: {
type: "context"
size: number
}
input: number
output: number
cache: {
read: number
write: number
}
}>
status: "alpha" | "beta" | "deprecated" | "active"
enabled: boolean
limit: {
context: number
input?: number
output: number
}
}
export type ProviderV2Info = {
id: string
name: string
@@ -3564,104 +3103,6 @@ export type EventTuiToastShow1 = {
}
}
export type ModelV2Info1 = {
id: string
apiID: string
providerID: string
family?: string
name: string
endpoint:
| {
type: "unknown"
}
| {
type: "openai/responses"
url: string
websocket?: boolean
}
| {
type: "openai/completions"
url: string
reasoning?:
| {
type: "reasoning_content"
}
| {
type: "reasoning_details"
}
}
| {
type: "anthropic/messages"
url: string
}
| {
type: "aisdk"
package: string
url?: string
}
capabilities: {
tools: boolean
input: Array<string>
output: Array<string>
}
options: {
headers: {
[key: string]: string
}
body: {
[key: string]: unknown
}
aisdk: {
provider: {
[key: string]: unknown
}
request: {
[key: string]: unknown
}
}
variant?: string
}
variants: Array<{
id: string
headers: {
[key: string]: string
}
body: {
[key: string]: unknown
}
aisdk: {
provider: {
[key: string]: unknown
}
request: {
[key: string]: unknown
}
}
}>
time: {
released: number | "NaN" | "Infinity" | "-Infinity"
}
cost: Array<{
tier?: {
type: "context"
size: number
}
input: number
output: number
cache: {
read: number
write: number
}
}>
status: "alpha" | "beta" | "deprecated" | "active"
enabled: boolean
limit: {
context: number
input?: number
output: number
}
}
export type BadRequestError = {
name: "BadRequest"
data: {
@@ -6782,7 +6223,7 @@ export type V2ModelListData = {
body?: never
path?: never
query?: {
location?: {
instance?: {
directory?: string
workspace?: string
}
@@ -6803,7 +6244,7 @@ export type V2ProviderListData = {
body?: never
path?: never
query?: {
location?: {
instance?: {
directory?: string
workspace?: string
}
@@ -6826,7 +6267,7 @@ export type V2ProviderGetData = {
providerID: string
}
query?: {
location?: {
instance?: {
directory?: string
workspace?: string
}
+364 -2252
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.15.0",
"version": "1.14.51",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.15.0",
"version": "1.14.51",
"type": "module",
"license": "MIT",
"exports": {
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
"version": "1.15.0",
"version": "1.14.51",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.15.0",
"version": "1.14.51",
"publisher": "sst-dev",
"repository": {
"type": "git",