Add Effect-native core event system (#27415)
This commit is contained in:
@@ -5,7 +5,8 @@ import { produce, type Draft } from "immer"
|
|||||||
import { ModelV2 } from "./model"
|
import { ModelV2 } from "./model"
|
||||||
import { PluginV2 } from "./plugin"
|
import { PluginV2 } from "./plugin"
|
||||||
import { ProviderV2 } from "./provider"
|
import { ProviderV2 } from "./provider"
|
||||||
import { Instance } from "./instance"
|
import { Location } from "./location"
|
||||||
|
import { EventV2 } from "./event"
|
||||||
|
|
||||||
type ProviderRecord = {
|
type ProviderRecord = {
|
||||||
provider: ProviderV2.Info
|
provider: ProviderV2.Info
|
||||||
@@ -24,6 +25,15 @@ export class ModelNotFoundError extends Schema.TaggedErrorClass<ModelNotFoundErr
|
|||||||
modelID: ModelV2.ID,
|
modelID: ModelV2.ID,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
|
export const Event = {
|
||||||
|
ModelUpdated: EventV2.define({
|
||||||
|
type: "catalog.model.updated",
|
||||||
|
schema: {
|
||||||
|
model: ModelV2.Info,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly provider: {
|
readonly provider: {
|
||||||
readonly get: (providerID: ProviderV2.ID) => Effect.Effect<ProviderV2.Info, ProviderNotFoundError>
|
readonly get: (providerID: ProviderV2.ID) => Effect.Effect<ProviderV2.Info, ProviderNotFoundError>
|
||||||
@@ -57,10 +67,11 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
|
|||||||
export const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* Instance.Service
|
yield* Location.Service
|
||||||
let records = HashMap.empty<ProviderV2.ID, ProviderRecord>()
|
let records = HashMap.empty<ProviderV2.ID, ProviderRecord>()
|
||||||
let defaultModel: { providerID: ProviderV2.ID; modelID: ModelV2.ID } | undefined
|
let defaultModel: { providerID: ProviderV2.ID; modelID: ModelV2.ID } | undefined
|
||||||
const plugin = yield* PluginV2.Service
|
const plugin = yield* PluginV2.Service
|
||||||
|
const events = yield* EventV2.Service
|
||||||
|
|
||||||
const resolve = (model: ModelV2.Info) => {
|
const resolve = (model: ModelV2.Info) => {
|
||||||
const provider = Option.getOrThrow(HashMap.get(records, model.providerID)).provider
|
const provider = Option.getOrThrow(HashMap.get(records, model.providerID)).provider
|
||||||
@@ -157,14 +168,12 @@ export const layer = Layer.effect(
|
|||||||
)
|
)
|
||||||
const updated = yield* plugin.trigger("model.update", {}, { model, cancel: false })
|
const updated = yield* plugin.trigger("model.update", {}, { model, cancel: false })
|
||||||
if (updated.cancel) return
|
if (updated.cancel) return
|
||||||
|
const next = new ModelV2.Info({ ...updated.model, id: modelID, providerID })
|
||||||
records = HashMap.set(records, providerID, {
|
records = HashMap.set(records, providerID, {
|
||||||
provider: record.provider,
|
provider: record.provider,
|
||||||
models: HashMap.set(
|
models: HashMap.set(record.models, modelID, next),
|
||||||
record.models,
|
|
||||||
modelID,
|
|
||||||
new ModelV2.Info({ ...updated.model, id: modelID, providerID }),
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
yield* events.publish(Event.ModelUpdated, { model: resolve(next) })
|
||||||
return
|
return
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -257,4 +266,4 @@ export const layer = Layer.effect(
|
|||||||
|
|
||||||
const SMALL_MODEL_RE = /\b(nano|flash|lite|mini|haiku|small|fast)\b/
|
const SMALL_MODEL_RE = /\b(nano|flash|lite|mini|haiku|small|fast)\b/
|
||||||
|
|
||||||
export const defaultLayer = layer.pipe(Layer.provide(PluginV2.defaultLayer))
|
export const defaultLayer = layer.pipe(Layer.provideMerge(EventV2.defaultLayer), Layer.provide(PluginV2.defaultLayer))
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
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"
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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",
|
|
||||||
}) {}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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") {}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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",
|
||||||
|
}) {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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") {}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { SessionID } from "@/session/schema"
|
|
||||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
|
||||||
import { EventV2 } from "./event"
|
|
||||||
import { FileAttachment, Prompt } from "@opencode-ai/core/session-prompt"
|
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
|
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"
|
||||||
|
|
||||||
export { FileAttachment }
|
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({
|
export const Source = Schema.Struct({
|
||||||
start: NonNegativeInt,
|
start: NonNegativeInt,
|
||||||
@@ -15,92 +16,94 @@ export const Source = Schema.Struct({
|
|||||||
}).annotate({
|
}).annotate({
|
||||||
identifier: "session.next.event.source",
|
identifier: "session.next.event.source",
|
||||||
})
|
})
|
||||||
export type Source = Schema.Schema.Type<typeof Source>
|
export type Source = typeof Source.Type
|
||||||
|
|
||||||
const Base = {
|
const Base = {
|
||||||
timestamp: V2Schema.DateTimeUtcFromMillis,
|
timestamp: V2Schema.DateTimeUtcFromMillis,
|
||||||
sessionID: SessionID,
|
sessionID: Session.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
aggregate: "sessionID",
|
||||||
|
version: 1,
|
||||||
|
} as const
|
||||||
|
|
||||||
export const UnknownError = Schema.Struct({
|
export const UnknownError = Schema.Struct({
|
||||||
type: Schema.Literal("unknown"),
|
type: Schema.Literal("unknown"),
|
||||||
message: Schema.String,
|
message: Schema.String,
|
||||||
}).annotate({
|
}).annotate({
|
||||||
identifier: "Session.Error.Unknown",
|
identifier: "Session.Error.Unknown",
|
||||||
})
|
})
|
||||||
export type UnknownError = Schema.Schema.Type<typeof UnknownError>
|
export type UnknownError = typeof UnknownError.Type
|
||||||
|
|
||||||
export const AgentSwitched = EventV2.define({
|
export const AgentSwitched = EventV2.define({
|
||||||
type: "session.next.agent.switched",
|
type: "session.next.agent.switched",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
version: 1,
|
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
agent: Schema.String,
|
agent: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type AgentSwitched = Schema.Schema.Type<typeof AgentSwitched>
|
export type AgentSwitched = typeof AgentSwitched.Type
|
||||||
|
|
||||||
export const ModelSwitched = EventV2.define({
|
export const ModelSwitched = EventV2.define({
|
||||||
type: "session.next.model.switched",
|
type: "session.next.model.switched",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
version: 1,
|
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
model: ModelV2.Ref,
|
model: ModelV2.Ref,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type ModelSwitched = Schema.Schema.Type<typeof ModelSwitched>
|
export type ModelSwitched = typeof ModelSwitched.Type
|
||||||
|
|
||||||
export const Prompted = EventV2.define({
|
export const Prompted = EventV2.define({
|
||||||
type: "session.next.prompted",
|
type: "session.next.prompted",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
version: 1,
|
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
prompt: Prompt,
|
prompt: Prompt,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Prompted = Schema.Schema.Type<typeof Prompted>
|
export type Prompted = typeof Prompted.Type
|
||||||
|
|
||||||
export const Synthetic = EventV2.define({
|
export const Synthetic = EventV2.define({
|
||||||
type: "session.next.synthetic",
|
type: "session.next.synthetic",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
text: Schema.String,
|
text: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Synthetic = Schema.Schema.Type<typeof Synthetic>
|
export type Synthetic = typeof Synthetic.Type
|
||||||
|
|
||||||
export namespace Shell {
|
export namespace Shell {
|
||||||
export const Started = EventV2.define({
|
export const Started = EventV2.define({
|
||||||
type: "session.next.shell.started",
|
type: "session.next.shell.started",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
command: Schema.String,
|
command: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Started = Schema.Schema.Type<typeof Started>
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
export const Ended = EventV2.define({
|
export const Ended = EventV2.define({
|
||||||
type: "session.next.shell.ended",
|
type: "session.next.shell.ended",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
output: Schema.String,
|
output: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Ended = Schema.Schema.Type<typeof Ended>
|
export type Ended = typeof Ended.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Step {
|
export namespace Step {
|
||||||
export const Started = EventV2.define({
|
export const Started = EventV2.define({
|
||||||
type: "session.next.step.started",
|
type: "session.next.step.started",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
agent: Schema.String,
|
agent: Schema.String,
|
||||||
@@ -108,11 +111,11 @@ export namespace Step {
|
|||||||
snapshot: Schema.String.pipe(Schema.optional),
|
snapshot: Schema.String.pipe(Schema.optional),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Started = Schema.Schema.Type<typeof Started>
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
export const Ended = EventV2.define({
|
export const Ended = EventV2.define({
|
||||||
type: "session.next.step.ended",
|
type: "session.next.step.ended",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
finish: Schema.String,
|
finish: Schema.String,
|
||||||
@@ -129,123 +132,123 @@ export namespace Step {
|
|||||||
snapshot: Schema.String.pipe(Schema.optional),
|
snapshot: Schema.String.pipe(Schema.optional),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Ended = Schema.Schema.Type<typeof Ended>
|
export type Ended = typeof Ended.Type
|
||||||
|
|
||||||
export const Failed = EventV2.define({
|
export const Failed = EventV2.define({
|
||||||
type: "session.next.step.failed",
|
type: "session.next.step.failed",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
error: UnknownError,
|
error: UnknownError,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Failed = Schema.Schema.Type<typeof Failed>
|
export type Failed = typeof Failed.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Text {
|
export namespace Text {
|
||||||
export const Started = EventV2.define({
|
export const Started = EventV2.define({
|
||||||
type: "session.next.text.started",
|
type: "session.next.text.started",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Started = Schema.Schema.Type<typeof Started>
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
export const Delta = EventV2.define({
|
export const Delta = EventV2.define({
|
||||||
type: "session.next.text.delta",
|
type: "session.next.text.delta",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
delta: Schema.String,
|
delta: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Delta = Schema.Schema.Type<typeof Delta>
|
export type Delta = typeof Delta.Type
|
||||||
|
|
||||||
export const Ended = EventV2.define({
|
export const Ended = EventV2.define({
|
||||||
type: "session.next.text.ended",
|
type: "session.next.text.ended",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
text: Schema.String,
|
text: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Ended = Schema.Schema.Type<typeof Ended>
|
export type Ended = typeof Ended.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Reasoning {
|
export namespace Reasoning {
|
||||||
export const Started = EventV2.define({
|
export const Started = EventV2.define({
|
||||||
type: "session.next.reasoning.started",
|
type: "session.next.reasoning.started",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
reasoningID: Schema.String,
|
reasoningID: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Started = Schema.Schema.Type<typeof Started>
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
export const Delta = EventV2.define({
|
export const Delta = EventV2.define({
|
||||||
type: "session.next.reasoning.delta",
|
type: "session.next.reasoning.delta",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
reasoningID: Schema.String,
|
reasoningID: Schema.String,
|
||||||
delta: Schema.String,
|
delta: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Delta = Schema.Schema.Type<typeof Delta>
|
export type Delta = typeof Delta.Type
|
||||||
|
|
||||||
export const Ended = EventV2.define({
|
export const Ended = EventV2.define({
|
||||||
type: "session.next.reasoning.ended",
|
type: "session.next.reasoning.ended",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
reasoningID: Schema.String,
|
reasoningID: Schema.String,
|
||||||
text: Schema.String,
|
text: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Ended = Schema.Schema.Type<typeof Ended>
|
export type Ended = typeof Ended.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Tool {
|
export namespace Tool {
|
||||||
export namespace Input {
|
export namespace Input {
|
||||||
export const Started = EventV2.define({
|
export const Started = EventV2.define({
|
||||||
type: "session.next.tool.input.started",
|
type: "session.next.tool.input.started",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
name: Schema.String,
|
name: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Started = Schema.Schema.Type<typeof Started>
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
export const Delta = EventV2.define({
|
export const Delta = EventV2.define({
|
||||||
type: "session.next.tool.input.delta",
|
type: "session.next.tool.input.delta",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
delta: Schema.String,
|
delta: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Delta = Schema.Schema.Type<typeof Delta>
|
export type Delta = typeof Delta.Type
|
||||||
|
|
||||||
export const Ended = EventV2.define({
|
export const Ended = EventV2.define({
|
||||||
type: "session.next.tool.input.ended",
|
type: "session.next.tool.input.ended",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
text: Schema.String,
|
text: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Ended = Schema.Schema.Type<typeof Ended>
|
export type Ended = typeof Ended.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Called = EventV2.define({
|
export const Called = EventV2.define({
|
||||||
type: "session.next.tool.called",
|
type: "session.next.tool.called",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
@@ -257,11 +260,11 @@ export namespace Tool {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Called = Schema.Schema.Type<typeof Called>
|
export type Called = typeof Called.Type
|
||||||
|
|
||||||
export const Progress = EventV2.define({
|
export const Progress = EventV2.define({
|
||||||
type: "session.next.tool.progress",
|
type: "session.next.tool.progress",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
@@ -269,11 +272,11 @@ export namespace Tool {
|
|||||||
content: Schema.Array(ToolOutput.Content),
|
content: Schema.Array(ToolOutput.Content),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Progress = Schema.Schema.Type<typeof Progress>
|
export type Progress = typeof Progress.Type
|
||||||
|
|
||||||
export const Success = EventV2.define({
|
export const Success = EventV2.define({
|
||||||
type: "session.next.tool.success",
|
type: "session.next.tool.success",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
@@ -285,11 +288,11 @@ export namespace Tool {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Success = Schema.Schema.Type<typeof Success>
|
export type Success = typeof Success.Type
|
||||||
|
|
||||||
export const Failed = EventV2.define({
|
export const Failed = EventV2.define({
|
||||||
type: "session.next.tool.failed",
|
type: "session.next.tool.failed",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
callID: Schema.String,
|
callID: Schema.String,
|
||||||
@@ -300,7 +303,7 @@ export namespace Tool {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Failed = Schema.Schema.Type<typeof Failed>
|
export type Failed = typeof Failed.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RetryError = Schema.Struct({
|
export const RetryError = Schema.Struct({
|
||||||
@@ -313,49 +316,50 @@ export const RetryError = Schema.Struct({
|
|||||||
}).annotate({
|
}).annotate({
|
||||||
identifier: "session.next.retry_error",
|
identifier: "session.next.retry_error",
|
||||||
})
|
})
|
||||||
export type RetryError = Schema.Schema.Type<typeof RetryError>
|
export type RetryError = typeof RetryError.Type
|
||||||
|
|
||||||
export const Retried = EventV2.define({
|
export const Retried = EventV2.define({
|
||||||
type: "session.next.retried",
|
type: "session.next.retried",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
attempt: Schema.Finite,
|
attempt: Schema.Finite,
|
||||||
error: RetryError,
|
error: RetryError,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Retried = Schema.Schema.Type<typeof Retried>
|
export type Retried = typeof Retried.Type
|
||||||
|
|
||||||
export namespace Compaction {
|
export namespace Compaction {
|
||||||
export const Started = EventV2.define({
|
export const Started = EventV2.define({
|
||||||
type: "session.next.compaction.started",
|
type: "session.next.compaction.started",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")]),
|
reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")]),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Started = Schema.Schema.Type<typeof Started>
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
export const Delta = EventV2.define({
|
export const Delta = EventV2.define({
|
||||||
type: "session.next.compaction.delta",
|
type: "session.next.compaction.delta",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
text: Schema.String,
|
text: Schema.String,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
export type Delta = typeof Delta.Type
|
||||||
|
|
||||||
export const Ended = EventV2.define({
|
export const Ended = EventV2.define({
|
||||||
type: "session.next.compaction.ended",
|
type: "session.next.compaction.ended",
|
||||||
aggregate: "sessionID",
|
...options,
|
||||||
schema: {
|
schema: {
|
||||||
...Base,
|
...Base,
|
||||||
text: Schema.String,
|
text: Schema.String,
|
||||||
include: Schema.String.pipe(Schema.optional),
|
include: Schema.String.pipe(Schema.optional),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export type Ended = Schema.Schema.Type<typeof Ended>
|
export type Ended = typeof Ended.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
export const All = Schema.Union(
|
export const All = Schema.Union(
|
||||||
@@ -392,16 +396,7 @@ export const All = Schema.Union(
|
|||||||
},
|
},
|
||||||
).pipe(Schema.toTaggedUnion("type"))
|
).pipe(Schema.toTaggedUnion("type"))
|
||||||
|
|
||||||
// user
|
export type Event = typeof All.Type
|
||||||
// assistant
|
|
||||||
// assistant
|
|
||||||
// assistant
|
|
||||||
// user
|
|
||||||
// compaction marker
|
|
||||||
// -> text
|
|
||||||
// assistant
|
|
||||||
|
|
||||||
export type Event = Schema.Schema.Type<typeof All>
|
|
||||||
export type Type = Event["type"]
|
export type Type = Event["type"]
|
||||||
|
|
||||||
export * as SessionEvent from "./session-event"
|
export * as SessionEvent from "./session-event"
|
||||||
+12
-12
@@ -1,10 +1,10 @@
|
|||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { Prompt } from "@opencode-ai/core/session-prompt"
|
import { Prompt } from "./session-prompt"
|
||||||
import { SessionEvent } from "./session-event"
|
import { SessionEvent } from "./session-event"
|
||||||
import { EventV2 } from "./event"
|
import { EventV2 } from "./event"
|
||||||
import { ToolOutput } from "@opencode-ai/core/tool-output"
|
import { ToolOutput } from "./tool-output"
|
||||||
import { V2Schema } from "@opencode-ai/core/v2-schema"
|
import { V2Schema } from "./v2-schema"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "./model"
|
||||||
|
|
||||||
export const ID = EventV2.ID
|
export const ID = EventV2.ID
|
||||||
export type ID = Schema.Schema.Type<typeof 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")({
|
export class AgentSwitched extends Schema.Class<AgentSwitched>("Session.Message.AgentSwitched")({
|
||||||
...Base,
|
...Base,
|
||||||
type: Schema.Literal("agent-switched"),
|
type: Schema.Literal("agent-switched"),
|
||||||
agent: SessionEvent.AgentSwitched.fields.data.fields.agent,
|
agent: SessionEvent.AgentSwitched.data.fields.agent,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export class ModelSwitched extends Schema.Class<ModelSwitched>("Session.Message.ModelSwitched")({
|
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")({
|
export class Synthetic extends Schema.Class<Synthetic>("Session.Message.Synthetic")({
|
||||||
...Base,
|
...Base,
|
||||||
sessionID: SessionEvent.Synthetic.fields.data.fields.sessionID,
|
sessionID: SessionEvent.Synthetic.data.fields.sessionID,
|
||||||
text: SessionEvent.Synthetic.fields.data.fields.text,
|
text: SessionEvent.Synthetic.data.fields.text,
|
||||||
type: Schema.Literal("synthetic"),
|
type: Schema.Literal("synthetic"),
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export class Shell extends Schema.Class<Shell>("Session.Message.Shell")({
|
export class Shell extends Schema.Class<Shell>("Session.Message.Shell")({
|
||||||
...Base,
|
...Base,
|
||||||
type: Schema.Literal("shell"),
|
type: Schema.Literal("shell"),
|
||||||
callID: SessionEvent.Shell.Started.fields.data.fields.callID,
|
callID: SessionEvent.Shell.Started.data.fields.callID,
|
||||||
command: SessionEvent.Shell.Started.fields.data.fields.command,
|
command: SessionEvent.Shell.Started.data.fields.command,
|
||||||
output: Schema.String,
|
output: Schema.String,
|
||||||
time: Schema.Struct({
|
time: Schema.Struct({
|
||||||
created: V2Schema.DateTimeUtcFromMillis,
|
created: V2Schema.DateTimeUtcFromMillis,
|
||||||
@@ -130,7 +130,7 @@ export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistan
|
|||||||
...Base,
|
...Base,
|
||||||
type: Schema.Literal("assistant"),
|
type: Schema.Literal("assistant"),
|
||||||
agent: Schema.String,
|
agent: Schema.String,
|
||||||
model: SessionEvent.Step.Started.fields.data.fields.model,
|
model: SessionEvent.Step.Started.data.fields.model,
|
||||||
content: AssistantContent.pipe(Schema.Array),
|
content: AssistantContent.pipe(Schema.Array),
|
||||||
snapshot: Schema.Struct({
|
snapshot: Schema.Struct({
|
||||||
start: Schema.String.pipe(Schema.optional),
|
start: Schema.String.pipe(Schema.optional),
|
||||||
@@ -147,7 +147,7 @@ export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistan
|
|||||||
write: Schema.Finite,
|
write: Schema.Finite,
|
||||||
}),
|
}),
|
||||||
}).pipe(Schema.optional),
|
}).pipe(Schema.optional),
|
||||||
error: SessionEvent.Step.Failed.fields.data.fields.error.pipe(Schema.optional),
|
error: SessionEvent.Step.Failed.data.fields.error.pipe(Schema.optional),
|
||||||
time: Schema.Struct({
|
time: Schema.Struct({
|
||||||
created: V2Schema.DateTimeUtcFromMillis,
|
created: V2Schema.DateTimeUtcFromMillis,
|
||||||
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
|
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")({
|
export class Compaction extends Schema.Class<Compaction>("Session.Message.Compaction")({
|
||||||
type: Schema.Literal("compaction"),
|
type: Schema.Literal("compaction"),
|
||||||
reason: SessionEvent.Compaction.Started.fields.data.fields.reason,
|
reason: SessionEvent.Compaction.Started.data.fields.reason,
|
||||||
summary: Schema.String,
|
summary: Schema.String,
|
||||||
include: Schema.String.pipe(Schema.optional),
|
include: Schema.String.pipe(Schema.optional),
|
||||||
...Base,
|
...Base,
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { DateTime, Effect, Layer, Option } from "effect"
|
import { DateTime, Effect, Fiber, Layer, Option, Stream } from "effect"
|
||||||
import { Catalog } from "@opencode-ai/core/catalog"
|
import { Catalog } from "@opencode-ai/core/catalog"
|
||||||
import { Instance } from "@opencode-ai/core/instance"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
|
import { Location } from "@opencode-ai/core/location"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
|
|
||||||
const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" }))
|
const locationLayer = Layer.succeed(Location.Service, Location.Service.of({ directory: "test" }))
|
||||||
const it = testEffect(Catalog.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer), Layer.provide(instanceLayer)))
|
const it = testEffect(
|
||||||
|
Catalog.layer.pipe(
|
||||||
|
Layer.provideMerge(EventV2.defaultLayer),
|
||||||
|
Layer.provideMerge(PluginV2.defaultLayer),
|
||||||
|
Layer.provideMerge(locationLayer),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
describe("CatalogV2", () => {
|
describe("CatalogV2", () => {
|
||||||
it.effect("normalizes provider baseURL into endpoint url", () =>
|
it.effect("normalizes provider baseURL into endpoint url", () =>
|
||||||
@@ -69,6 +76,31 @@ 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", () =>
|
it.effect("resolves unknown model endpoint from provider endpoint", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
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 { describe, expect } from "bun:test"
|
||||||
import { DateTime, Effect, Layer, Option } from "effect"
|
import { DateTime, Effect, Layer, Option } from "effect"
|
||||||
import { Catalog } from "@opencode-ai/core/catalog"
|
import { Catalog } from "@opencode-ai/core/catalog"
|
||||||
import { Instance } from "@opencode-ai/core/instance"
|
import { Location } from "@opencode-ai/core/location"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||||
import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode"
|
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"
|
import { it, model, provider, withEnv } from "./provider-helper"
|
||||||
|
|
||||||
const cost = (input: number, output = 0) => [{ input, output, cache: { read: 0, write: 0 } }]
|
const cost = (input: number, output = 0) => [{ input, output, cache: { read: 0, write: 0 } }]
|
||||||
const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" }))
|
const locationLayer = Layer.succeed(Location.Service, Location.Service.of({ directory: "test" }))
|
||||||
|
|
||||||
describe("OpencodePlugin", () => {
|
describe("OpencodePlugin", () => {
|
||||||
it.effect("uses a public key and cancels paid models without credentials", () =>
|
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)
|
const selected = yield* catalog.model.small(providerID)
|
||||||
|
|
||||||
expect(Option.getOrUndefined(selected)?.id).toBe(ModelV2.ID.make("gpt-5-nano"))
|
expect(Option.getOrUndefined(selected)?.id).toBe(ModelV2.ID.make("gpt-5-nano"))
|
||||||
}).pipe(Effect.provide(Catalog.defaultLayer.pipe(Layer.provide(instanceLayer)))),
|
}).pipe(Effect.provide(Catalog.defaultLayer.pipe(Layer.provide(locationLayer)))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
|
|
||||||
export type Definition<Type extends string = string, Properties extends Schema.Top = Schema.Top> = {
|
export type Definition<Type extends string = string, Properties extends Schema.Top = Schema.Top> = {
|
||||||
type: Type
|
type: Type
|
||||||
@@ -17,16 +18,28 @@ export function define<Type extends string, Properties extends Schema.Top>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function effectPayloads() {
|
export function effectPayloads() {
|
||||||
return registry
|
return [
|
||||||
.entries()
|
...registry
|
||||||
.map(([type, def]) =>
|
.entries()
|
||||||
Schema.Struct({
|
.map(([type, def]) =>
|
||||||
id: Schema.String,
|
Schema.Struct({
|
||||||
type: Schema.Literal(type),
|
id: Schema.String,
|
||||||
properties: def.properties,
|
type: Schema.Literal(type),
|
||||||
}).annotate({ identifier: `Event.${type}` }),
|
properties: def.properties,
|
||||||
)
|
}).annotate({ identifier: `Event.${type}` }),
|
||||||
.toArray()
|
)
|
||||||
|
.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(),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as BusEvent from "./bus-event"
|
export * as BusEvent from "./bus-event"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Effect, Layer, Option } from "effect"
|
import { Effect, Layer, Option } from "effect"
|
||||||
import { Catalog } from "@opencode-ai/core/catalog"
|
import { Catalog } from "@opencode-ai/core/catalog"
|
||||||
import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
|
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
|
||||||
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
|
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
|
||||||
import { effectCmd } from "../../effect-cmd"
|
import { effectCmd } from "../../effect-cmd"
|
||||||
|
|
||||||
const Runtime = Layer.mergeAll(InstanceServiceMap.layer)
|
const Runtime = Layer.mergeAll(LocationServiceMap.layer)
|
||||||
|
|
||||||
export const V2Command = effectCmd({
|
export const V2Command = effectCmd({
|
||||||
command: "v2",
|
command: "v2",
|
||||||
@@ -37,7 +37,7 @@ export const V2Command = effectCmd({
|
|||||||
process.stdout.write(JSON.stringify(result, null, 2) + EOL)
|
process.stdout.write(JSON.stringify(result, null, 2) + EOL)
|
||||||
},
|
},
|
||||||
Effect.provide(
|
Effect.provide(
|
||||||
InstanceServiceMap.get({
|
LocationServiceMap.get({
|
||||||
directory: process.cwd(),
|
directory: process.cwd(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import { Npm } from "@opencode-ai/core/npm"
|
|||||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||||
import { DataMigration } from "@/data-migration"
|
import { DataMigration } from "@/data-migration"
|
||||||
import { BackgroundJob } from "@/background/job"
|
import { BackgroundJob } from "@/background/job"
|
||||||
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
export const AppLayer = Layer.mergeAll(
|
export const AppLayer = Layer.mergeAll(
|
||||||
@@ -111,6 +112,7 @@ export const AppLayer = Layer.mergeAll(
|
|||||||
ShareNext.defaultLayer,
|
ShareNext.defaultLayer,
|
||||||
SessionShare.defaultLayer,
|
SessionShare.defaultLayer,
|
||||||
SyncEvent.defaultLayer,
|
SyncEvent.defaultLayer,
|
||||||
|
EventV2Bridge.defaultLayer,
|
||||||
DataMigration.defaultLayer,
|
DataMigration.defaultLayer,
|
||||||
).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer))
|
).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
const syncDefinitions = new WeakMap<EventV2.Definition, SyncEvent.Definition>()
|
||||||
|
|
||||||
|
export function toSyncDefinition<D extends EventV2.Definition>(
|
||||||
|
definition: D,
|
||||||
|
): SyncEvent.Definition<D["type"], D["data"], D["data"]> {
|
||||||
|
const cached = syncDefinitions.get(definition)
|
||||||
|
if (cached) return cached as SyncEvent.Definition<D["type"], D["data"], D["data"]>
|
||||||
|
if (definition.version === undefined)
|
||||||
|
throw new Error(`Event.toSyncDefinition: version required for ${definition.type}`)
|
||||||
|
if (!definition.aggregate) throw new Error(`Event.toSyncDefinition: aggregate required for ${definition.type}`)
|
||||||
|
const result = {
|
||||||
|
type: definition.type,
|
||||||
|
version: definition.version,
|
||||||
|
aggregate: definition.aggregate,
|
||||||
|
schema: definition.data,
|
||||||
|
properties: definition.data,
|
||||||
|
}
|
||||||
|
syncDefinitions.set(definition, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
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,6 +1,7 @@
|
|||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { BusEvent } from "@/bus/bus-event"
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
|
import "@/event-v2-bridge"
|
||||||
import "@/server/event"
|
import "@/server/event"
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||||
|
|||||||
+18
-18
@@ -1,28 +1,28 @@
|
|||||||
import { Catalog } from "@opencode-ai/core/catalog"
|
import { Catalog } from "@opencode-ai/core/catalog"
|
||||||
import { Instance } from "@opencode-ai/core/instance"
|
import { Location } from "@opencode-ai/core/location"
|
||||||
import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
|
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
|
||||||
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
|
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Layer, Schema } from "effect"
|
||||||
import { HttpServerRequest } from "effect/unstable/http"
|
import { HttpServerRequest } from "effect/unstable/http"
|
||||||
import { HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi"
|
import { HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi"
|
||||||
|
|
||||||
export const InstanceQuery = Schema.Struct({
|
export const LocationQuery = Schema.Struct({
|
||||||
instance: Schema.optional(
|
location: Schema.optional(
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
directory: Schema.optional(Schema.String),
|
directory: Schema.optional(Schema.String),
|
||||||
workspace: Schema.optional(Schema.String),
|
workspace: Schema.optional(Schema.String),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}).annotate({ identifier: "V2InstanceQuery" })
|
}).annotate({ identifier: "V2LocationQuery" })
|
||||||
|
|
||||||
export const instanceQueryOpenApi = OpenApi.annotations({
|
export const locationQueryOpenApi = OpenApi.annotations({
|
||||||
transform: (operation) => {
|
transform: (operation) => {
|
||||||
const parameters = operation.parameters
|
const parameters = operation.parameters
|
||||||
if (!Array.isArray(parameters)) return operation
|
if (!Array.isArray(parameters)) return operation
|
||||||
return {
|
return {
|
||||||
...operation,
|
...operation,
|
||||||
parameters: parameters.map((parameter) =>
|
parameters: parameters.map((parameter) =>
|
||||||
parameter?.name === "instance" && parameter?.in === "query"
|
parameter?.name === "location" && parameter?.in === "query"
|
||||||
? { ...parameter, style: "deepObject", explode: true }
|
? { ...parameter, style: "deepObject", explode: true }
|
||||||
: parameter,
|
: parameter,
|
||||||
),
|
),
|
||||||
@@ -30,30 +30,30 @@ export const instanceQueryOpenApi = OpenApi.annotations({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export class V2InstanceMiddleware extends HttpApiMiddleware.Service<
|
export class V2LocationMiddleware extends HttpApiMiddleware.Service<
|
||||||
V2InstanceMiddleware,
|
V2LocationMiddleware,
|
||||||
{
|
{
|
||||||
provides: Catalog.Service | PluginBoot.Service
|
provides: Catalog.Service | PluginBoot.Service
|
||||||
}
|
}
|
||||||
>()("@opencode/ExperimentalHttpApiV2Instance") {}
|
>()("@opencode/ExperimentalHttpApiV2Location") {}
|
||||||
|
|
||||||
function ref(request: HttpServerRequest.HttpServerRequest): Instance.Ref {
|
function ref(request: HttpServerRequest.HttpServerRequest): Location.Ref {
|
||||||
const query = new URL(request.url, "http://localhost").searchParams
|
const query = new URL(request.url, "http://localhost").searchParams
|
||||||
return {
|
return {
|
||||||
directory: query.get("instance[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
|
directory: query.get("location[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
|
||||||
workspaceID: query.get("instance[workspace]") || request.headers["x-opencode-workspace"],
|
workspaceID: query.get("location[workspace]") || request.headers["x-opencode-workspace"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
V2InstanceMiddleware,
|
V2LocationMiddleware,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const instances = yield* InstanceServiceMap
|
const locations = yield* LocationServiceMap
|
||||||
return V2InstanceMiddleware.of((effect) =>
|
return V2LocationMiddleware.of((effect) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const request = yield* HttpServerRequest.HttpServerRequest
|
const request = yield* HttpServerRequest.HttpServerRequest
|
||||||
return yield* effect.pipe(Effect.provide(instances.get(ref(request))))
|
return yield* effect.pipe(Effect.provide(locations.get(ref(request))))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
).pipe(Layer.provide(InstanceServiceMap.layer))
|
).pipe(Layer.provide(LocationServiceMap.layer))
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SessionID } from "@/session/schema"
|
import { SessionID } from "@/session/schema"
|
||||||
import { SessionMessage } from "@/v2/session-message"
|
import { SessionMessage } from "@opencode-ai/core/session-message"
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
import { HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||||
import { Authorization } from "../../middleware/authorization"
|
import { Authorization } from "../../middleware/authorization"
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import { ModelV2 } from "@opencode-ai/core/model"
|
|||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||||
import { Authorization } from "../../middleware/authorization"
|
import { Authorization } from "../../middleware/authorization"
|
||||||
import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
|
import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location"
|
||||||
|
|
||||||
export const ModelGroup = HttpApiGroup.make("v2.model")
|
export const ModelGroup = HttpApiGroup.make("v2.model")
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.get("models", "/api/model", {
|
HttpApiEndpoint.get("models", "/api/model", {
|
||||||
query: InstanceQuery,
|
query: LocationQuery,
|
||||||
success: Schema.Array(ModelV2.Info),
|
success: Schema.Array(ModelV2.Info),
|
||||||
})
|
})
|
||||||
.annotateMerge(instanceQueryOpenApi)
|
.annotateMerge(locationQueryOpenApi)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.model.list",
|
identifier: "v2.model.list",
|
||||||
@@ -25,5 +25,5 @@ export const ModelGroup = HttpApiGroup.make("v2.model")
|
|||||||
description: "Experimental v2 model routes.",
|
description: "Experimental v2 model routes.",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.middleware(V2InstanceMiddleware)
|
.middleware(V2LocationMiddleware)
|
||||||
.middleware(Authorization)
|
.middleware(Authorization)
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import { Schema } from "effect"
|
|||||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||||
import { ApiNotFoundError } from "../../errors"
|
import { ApiNotFoundError } from "../../errors"
|
||||||
import { Authorization } from "../../middleware/authorization"
|
import { Authorization } from "../../middleware/authorization"
|
||||||
import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
|
import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location"
|
||||||
|
|
||||||
export const ProviderGroup = HttpApiGroup.make("v2.provider")
|
export const ProviderGroup = HttpApiGroup.make("v2.provider")
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.get("providers", "/api/provider", {
|
HttpApiEndpoint.get("providers", "/api/provider", {
|
||||||
query: InstanceQuery,
|
query: LocationQuery,
|
||||||
success: Schema.Array(ProviderV2.Info),
|
success: Schema.Array(ProviderV2.Info),
|
||||||
})
|
})
|
||||||
.annotateMerge(instanceQueryOpenApi)
|
.annotateMerge(locationQueryOpenApi)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.provider.list",
|
identifier: "v2.provider.list",
|
||||||
@@ -23,11 +23,11 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
|
|||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.get("provider", "/api/provider/:providerID", {
|
HttpApiEndpoint.get("provider", "/api/provider/:providerID", {
|
||||||
params: { providerID: ProviderV2.ID },
|
params: { providerID: ProviderV2.ID },
|
||||||
query: InstanceQuery,
|
query: LocationQuery,
|
||||||
success: ProviderV2.Info,
|
success: ProviderV2.Info,
|
||||||
error: ApiNotFoundError,
|
error: ApiNotFoundError,
|
||||||
})
|
})
|
||||||
.annotateMerge(instanceQueryOpenApi)
|
.annotateMerge(locationQueryOpenApi)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.provider.get",
|
identifier: "v2.provider.get",
|
||||||
@@ -43,5 +43,5 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
|
|||||||
description: "Experimental v2 provider routes.",
|
description: "Experimental v2 provider routes.",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.middleware(V2InstanceMiddleware)
|
.middleware(V2LocationMiddleware)
|
||||||
.middleware(Authorization)
|
.middleware(Authorization)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SessionID } from "@/session/schema"
|
import { SessionID } from "@/session/schema"
|
||||||
import { SessionMessage } from "@/v2/session-message"
|
import { SessionMessage } from "@opencode-ai/core/session-message"
|
||||||
import { Prompt } from "@opencode-ai/core/session-prompt"
|
import { Prompt } from "@opencode-ai/core/session-prompt"
|
||||||
import { SessionV2 } from "@/v2/session"
|
import { SessionV2 } from "@/v2/session"
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { SessionV2 } from "@/v2/session"
|
import { SessionV2 } from "@/v2/session"
|
||||||
import { Layer } from "effect"
|
import { Layer } from "effect"
|
||||||
import { layer as v2InstanceLayer } from "../groups/v2/instance"
|
import { layer as v2LocationLayer } from "../groups/v2/location"
|
||||||
import { messageHandlers } from "./v2/message"
|
import { messageHandlers } from "./v2/message"
|
||||||
import { modelHandlers } from "./v2/model"
|
import { modelHandlers } from "./v2/model"
|
||||||
import { providerHandlers } from "./v2/provider"
|
import { providerHandlers } from "./v2/provider"
|
||||||
import { sessionHandlers } from "./v2/session"
|
import { sessionHandlers } from "./v2/session"
|
||||||
|
|
||||||
export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers, modelHandlers, providerHandlers).pipe(
|
export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers, modelHandlers, providerHandlers).pipe(
|
||||||
Layer.provide(v2InstanceLayer),
|
Layer.provide(v2LocationLayer),
|
||||||
Layer.provide(SessionV2.defaultLayer),
|
Layer.provide(SessionV2.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SessionMessage } from "@/v2/session-message"
|
import { SessionMessage } from "@opencode-ai/core/session-message"
|
||||||
import { SessionV2 } from "@/v2/session"
|
import { SessionV2 } from "@/v2/session"
|
||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import * as DateTime from "effect/DateTime"
|
import * as DateTime from "effect/DateTime"
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import { SessionSummary } from "@/session/summary"
|
|||||||
import { Todo } from "@/session/todo"
|
import { Todo } from "@/session/todo"
|
||||||
import { SessionShare } from "@/share/session"
|
import { SessionShare } from "@/share/session"
|
||||||
import { ShareNext } from "@/share/share-next"
|
import { ShareNext } from "@/share/share-next"
|
||||||
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
import { Skill } from "@/skill"
|
import { Skill } from "@/skill"
|
||||||
import { Snapshot } from "@/snapshot"
|
import { Snapshot } from "@/snapshot"
|
||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
@@ -221,6 +222,7 @@ export function createRoutes(
|
|||||||
ShareNext.defaultLayer,
|
ShareNext.defaultLayer,
|
||||||
Snapshot.defaultLayer,
|
Snapshot.defaultLayer,
|
||||||
SyncEvent.defaultLayer,
|
SyncEvent.defaultLayer,
|
||||||
|
EventV2Bridge.defaultLayer,
|
||||||
Skill.defaultLayer,
|
Skill.defaultLayer,
|
||||||
Todo.defaultLayer,
|
Todo.defaultLayer,
|
||||||
ToolRegistry.defaultLayer,
|
ToolRegistry.defaultLayer,
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ import { isOverflow as overflow, usable } from "./overflow"
|
|||||||
import { makeRuntime } from "@/effect/run-service"
|
import { makeRuntime } from "@/effect/run-service"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@/effect/service-use"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
import { SyncEvent } from "@/sync"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { SessionEvent } from "@/v2/session-event"
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||||
|
|
||||||
const log = Log.create({ service: "session.compaction" })
|
const log = Log.create({ service: "session.compaction" })
|
||||||
|
|
||||||
@@ -210,19 +211,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Se
|
|||||||
|
|
||||||
export const use = serviceUse(Service)
|
export const use = serviceUse(Service)
|
||||||
|
|
||||||
export const layer: Layer.Layer<
|
export const layer = Layer.effect(
|
||||||
Service,
|
|
||||||
never,
|
|
||||||
| Bus.Service
|
|
||||||
| Config.Service
|
|
||||||
| Session.Service
|
|
||||||
| Agent.Service
|
|
||||||
| Plugin.Service
|
|
||||||
| SessionProcessor.Service
|
|
||||||
| Provider.Service
|
|
||||||
| SyncEvent.Service
|
|
||||||
| RuntimeFlags.Service
|
|
||||||
> = Layer.effect(
|
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const bus = yield* Bus.Service
|
const bus = yield* Bus.Service
|
||||||
@@ -232,7 +221,7 @@ export const layer: Layer.Layer<
|
|||||||
const plugin = yield* Plugin.Service
|
const plugin = yield* Plugin.Service
|
||||||
const processors = yield* SessionProcessor.Service
|
const processors = yield* SessionProcessor.Service
|
||||||
const provider = yield* Provider.Service
|
const provider = yield* Provider.Service
|
||||||
const sync = yield* SyncEvent.Service
|
const events = yield* EventV2Bridge.Service
|
||||||
const flags = yield* RuntimeFlags.Service
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
|
||||||
const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: {
|
const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: {
|
||||||
@@ -577,7 +566,7 @@ export const layer: Layer.Layer<
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Compaction.Ended.Sync, {
|
yield* events.publish(SessionEvent.Compaction.Ended, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||||
text: summary ?? "",
|
text: summary ?? "",
|
||||||
@@ -613,7 +602,7 @@ export const layer: Layer.Layer<
|
|||||||
overflow: input.overflow,
|
overflow: input.overflow,
|
||||||
})
|
})
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Compaction.Started.Sync, {
|
yield* events.publish(SessionEvent.Compaction.Started, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||||
reason: input.auto ? "auto" : "manual",
|
reason: input.auto ? "auto" : "manual",
|
||||||
@@ -639,8 +628,8 @@ export const defaultLayer = Layer.suspend(() =>
|
|||||||
Layer.provide(Plugin.defaultLayer),
|
Layer.provide(Plugin.defaultLayer),
|
||||||
Layer.provide(Bus.layer),
|
Layer.provide(Bus.layer),
|
||||||
Layer.provide(Config.defaultLayer),
|
Layer.provide(Config.defaultLayer),
|
||||||
Layer.provide(SyncEvent.defaultLayer),
|
|
||||||
Layer.provide(RuntimeFlags.defaultLayer),
|
Layer.provide(RuntimeFlags.defaultLayer),
|
||||||
|
Layer.provide(EventV2Bridge.defaultLayer),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ import { Question } from "@/question"
|
|||||||
import { errorMessage } from "@/util/error"
|
import { errorMessage } from "@/util/error"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { isRecord } from "@/util/record"
|
import { isRecord } from "@/util/record"
|
||||||
import { SyncEvent } from "@/sync"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { SessionEvent } from "@/v2/session-event"
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import * as DateTime from "effect/DateTime"
|
import * as DateTime from "effect/DateTime"
|
||||||
@@ -84,23 +85,7 @@ type StreamEvent = Event
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionProcessor") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionProcessor") {}
|
||||||
|
|
||||||
export const layer: Layer.Layer<
|
export const layer = Layer.effect(
|
||||||
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,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const session = yield* Session.Service
|
const session = yield* Session.Service
|
||||||
@@ -115,7 +100,7 @@ export const layer: Layer.Layer<
|
|||||||
const scope = yield* Scope.Scope
|
const scope = yield* Scope.Scope
|
||||||
const status = yield* SessionStatus.Service
|
const status = yield* SessionStatus.Service
|
||||||
const image = yield* Image.Service
|
const image = yield* Image.Service
|
||||||
const sync = yield* SyncEvent.Service
|
const events = yield* EventV2Bridge.Service
|
||||||
const flags = yield* RuntimeFlags.Service
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
|
||||||
const create = Effect.fn("SessionProcessor.create")(function* (input: Input) {
|
const create = Effect.fn("SessionProcessor.create")(function* (input: Input) {
|
||||||
@@ -236,7 +221,7 @@ export const layer: Layer.Layer<
|
|||||||
if (value.id in ctx.reasoningMap) return
|
if (value.id in ctx.reasoningMap) return
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Reasoning.Started.Sync, {
|
yield* events.publish(SessionEvent.Reasoning.Started, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
reasoningID: value.id,
|
reasoningID: value.id,
|
||||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||||
@@ -271,7 +256,7 @@ export const layer: Layer.Layer<
|
|||||||
if (!(value.id in ctx.reasoningMap)) return
|
if (!(value.id in ctx.reasoningMap)) return
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Reasoning.Ended.Sync, {
|
yield* events.publish(SessionEvent.Reasoning.Ended, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
reasoningID: value.id,
|
reasoningID: value.id,
|
||||||
text: ctx.reasoningMap[value.id].text,
|
text: ctx.reasoningMap[value.id].text,
|
||||||
@@ -292,7 +277,7 @@ export const layer: Layer.Layer<
|
|||||||
}
|
}
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Tool.Input.Started.Sync, {
|
yield* events.publish(SessionEvent.Tool.Input.Started, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
callID: value.id,
|
callID: value.id,
|
||||||
name: value.toolName,
|
name: value.toolName,
|
||||||
@@ -323,7 +308,7 @@ export const layer: Layer.Layer<
|
|||||||
case "tool-input-end": {
|
case "tool-input-end": {
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Tool.Input.Ended.Sync, {
|
yield* events.publish(SessionEvent.Tool.Input.Ended, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
callID: value.id,
|
callID: value.id,
|
||||||
text: "",
|
text: "",
|
||||||
@@ -340,7 +325,7 @@ export const layer: Layer.Layer<
|
|||||||
const toolCall = yield* readToolCall(value.toolCallId)
|
const toolCall = yield* readToolCall(value.toolCallId)
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Tool.Called.Sync, {
|
yield* events.publish(SessionEvent.Tool.Called, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
callID: value.toolCallId,
|
callID: value.toolCallId,
|
||||||
tool: value.toolName,
|
tool: value.toolName,
|
||||||
@@ -428,7 +413,7 @@ export const layer: Layer.Layer<
|
|||||||
}
|
}
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Tool.Success.Sync, {
|
yield* events.publish(SessionEvent.Tool.Success, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
callID: value.toolCallId,
|
callID: value.toolCallId,
|
||||||
structured: output.metadata,
|
structured: output.metadata,
|
||||||
@@ -458,7 +443,7 @@ export const layer: Layer.Layer<
|
|||||||
const toolCall = yield* readToolCall(value.toolCallId)
|
const toolCall = yield* readToolCall(value.toolCallId)
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Tool.Failed.Sync, {
|
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
callID: value.toolCallId,
|
callID: value.toolCallId,
|
||||||
error: {
|
error: {
|
||||||
@@ -483,7 +468,7 @@ export const layer: Layer.Layer<
|
|||||||
if (!ctx.assistantMessage.summary) {
|
if (!ctx.assistantMessage.summary) {
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Step.Started.Sync, {
|
yield* events.publish(SessionEvent.Step.Started, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
agent: input.assistantMessage.agent,
|
agent: input.assistantMessage.agent,
|
||||||
model: {
|
model: {
|
||||||
@@ -515,7 +500,7 @@ export const layer: Layer.Layer<
|
|||||||
if (!ctx.assistantMessage.summary) {
|
if (!ctx.assistantMessage.summary) {
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Step.Ended.Sync, {
|
yield* events.publish(SessionEvent.Step.Ended, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
finish: value.finishReason,
|
finish: value.finishReason,
|
||||||
cost: usage.cost,
|
cost: usage.cost,
|
||||||
@@ -572,7 +557,7 @@ export const layer: Layer.Layer<
|
|||||||
if (!ctx.assistantMessage.summary) {
|
if (!ctx.assistantMessage.summary) {
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Text.Started.Sync, {
|
yield* events.publish(SessionEvent.Text.Started, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||||
})
|
})
|
||||||
@@ -619,7 +604,7 @@ export const layer: Layer.Layer<
|
|||||||
if (!ctx.assistantMessage.summary) {
|
if (!ctx.assistantMessage.summary) {
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Text.Ended.Sync, {
|
yield* events.publish(SessionEvent.Text.Ended, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
text: ctx.currentText.text,
|
text: ctx.currentText.text,
|
||||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||||
@@ -715,7 +700,7 @@ export const layer: Layer.Layer<
|
|||||||
if (!ctx.assistantMessage.summary) {
|
if (!ctx.assistantMessage.summary) {
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Step.Failed.Sync, {
|
yield* events.publish(SessionEvent.Step.Failed, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
error: {
|
error: {
|
||||||
type: "unknown",
|
type: "unknown",
|
||||||
@@ -769,7 +754,7 @@ export const layer: Layer.Layer<
|
|||||||
set: (info) => {
|
set: (info) => {
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
const event = flags.experimentalEventSystem
|
const event = flags.experimentalEventSystem
|
||||||
? sync.run(SessionEvent.Retried.Sync, {
|
? events.publish(SessionEvent.Retried, {
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
attempt: info.attempt,
|
attempt: info.attempt,
|
||||||
error: {
|
error: {
|
||||||
@@ -830,8 +815,8 @@ export const defaultLayer = Layer.suspend(() =>
|
|||||||
Layer.provide(Image.defaultLayer),
|
Layer.provide(Image.defaultLayer),
|
||||||
Layer.provide(Bus.layer),
|
Layer.provide(Bus.layer),
|
||||||
Layer.provide(Config.defaultLayer),
|
Layer.provide(Config.defaultLayer),
|
||||||
Layer.provide(SyncEvent.defaultLayer),
|
|
||||||
Layer.provide(RuntimeFlags.defaultLayer),
|
Layer.provide(RuntimeFlags.defaultLayer),
|
||||||
|
Layer.provide(EventV2Bridge.defaultLayer),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { and, desc, eq } from "@/storage/db"
|
import { and, desc, eq } from "@/storage/db"
|
||||||
import type { Database } from "@/storage/db"
|
import type { Database } from "@/storage/db"
|
||||||
import { SessionMessage } from "@/v2/session-message"
|
import { SessionMessage } from "@opencode-ai/core/session-message"
|
||||||
import { SessionMessageUpdater } from "@/v2/session-message-updater"
|
import { SessionMessageUpdater } from "@opencode-ai/core/session-message-updater"
|
||||||
import { SessionEvent } from "@/v2/session-event"
|
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||||
import * as DateTime from "effect/DateTime"
|
import * as DateTime from "effect/DateTime"
|
||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
import { SessionMessageTable, SessionTable } from "./session.sql"
|
import { SessionMessageTable, SessionTable } from "./session.sql"
|
||||||
import type { SessionID } from "./schema"
|
import type { SessionID } from "./schema"
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
@@ -119,7 +120,7 @@ function update(db: Database.TxOrDb, event: SessionEvent.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
SyncEvent.project(SessionEvent.AgentSwitched.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.AgentSwitched), (db, data, event) => {
|
||||||
db.update(SessionTable)
|
db.update(SessionTable)
|
||||||
.set({
|
.set({
|
||||||
agent: data.agent,
|
agent: data.agent,
|
||||||
@@ -129,7 +130,7 @@ export default [
|
|||||||
.run()
|
.run()
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.agent.switched", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.agent.switched", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.ModelSwitched.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.ModelSwitched), (db, data, event) => {
|
||||||
db.update(SessionTable)
|
db.update(SessionTable)
|
||||||
.set({
|
.set({
|
||||||
model: data.model,
|
model: data.model,
|
||||||
@@ -139,65 +140,65 @@ export default [
|
|||||||
.run()
|
.run()
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.model.switched", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.model.switched", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Prompted.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Prompted), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.prompted", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.prompted", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Synthetic.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Synthetic), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.synthetic", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.synthetic", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Shell.Started.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Shell.Started), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.shell.started", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.shell.started", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Shell.Ended.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Shell.Ended), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.shell.ended", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.shell.ended", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Step.Started.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Started), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.started", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.started", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Step.Ended.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Ended), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.ended", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.ended", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Step.Failed.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Step.Failed), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.failed", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.step.failed", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Text.Started.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Started), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.text.started", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.text.started", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Text.Delta.Sync, () => {}),
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Delta), () => {}),
|
||||||
SyncEvent.project(SessionEvent.Text.Ended.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Text.Ended), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.text.ended", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.text.ended", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Tool.Input.Started.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Started), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.input.started", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.input.started", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Tool.Input.Delta.Sync, () => {}),
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Delta), () => {}),
|
||||||
SyncEvent.project(SessionEvent.Tool.Input.Ended.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Input.Ended), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.input.ended", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.input.ended", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Tool.Called.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Called), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.called", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.called", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Tool.Success.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Success), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.success", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.success", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Tool.Failed.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Tool.Failed), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.failed", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.tool.failed", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Reasoning.Started.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Started), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.reasoning.started", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.reasoning.started", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Reasoning.Delta.Sync, () => {}),
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Delta), () => {}),
|
||||||
SyncEvent.project(SessionEvent.Reasoning.Ended.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Reasoning.Ended), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.reasoning.ended", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.reasoning.ended", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Retried.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Retried), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.retried", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.retried", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Compaction.Started.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Started), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.compaction.started", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.compaction.started", data })
|
||||||
}),
|
}),
|
||||||
SyncEvent.project(SessionEvent.Compaction.Delta.Sync, () => {}),
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Delta), () => {}),
|
||||||
SyncEvent.project(SessionEvent.Compaction.Ended.Sync, (db, data, event) => {
|
SyncEvent.project(EventV2Bridge.toSyncDefinition(SessionEvent.Compaction.Ended), (db, data, event) => {
|
||||||
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.compaction.ended", data })
|
update(db, { id: SessionMessage.ID.make(event.id), type: "session.next.compaction.ended", data })
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -52,8 +52,9 @@ import { TaskTool, type TaskPromptOps } from "@/tool/task"
|
|||||||
import { SessionRunState } from "./run-state"
|
import { SessionRunState } from "./run-state"
|
||||||
import { EffectBridge } from "@/effect/bridge"
|
import { EffectBridge } from "@/effect/bridge"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
import { SyncEvent } from "@/sync"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { SessionEvent } from "@/v2/session-event"
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { AgentAttachment, FileAttachment, ReferenceAttachment, Source } from "@opencode-ai/core/session-prompt"
|
import { AgentAttachment, FileAttachment, ReferenceAttachment, Source } from "@opencode-ai/core/session-prompt"
|
||||||
@@ -204,7 +205,7 @@ export const layer = Layer.effect(
|
|||||||
const sys = yield* SystemPrompt.Service
|
const sys = yield* SystemPrompt.Service
|
||||||
const llm = yield* LLM.Service
|
const llm = yield* LLM.Service
|
||||||
const references = yield* Reference.Service
|
const references = yield* Reference.Service
|
||||||
const sync = yield* SyncEvent.Service
|
const events = yield* EventV2Bridge.Service
|
||||||
const flags = yield* RuntimeFlags.Service
|
const flags = yield* RuntimeFlags.Service
|
||||||
const runner = Effect.fn("SessionPrompt.runner")(function* () {
|
const runner = Effect.fn("SessionPrompt.runner")(function* () {
|
||||||
return yield* EffectBridge.make()
|
return yield* EffectBridge.make()
|
||||||
@@ -944,7 +945,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
|||||||
providerID: model.providerID,
|
providerID: model.providerID,
|
||||||
}
|
}
|
||||||
yield* sessions.updateMessage(msg)
|
yield* sessions.updateMessage(msg)
|
||||||
const callID = ulid()
|
|
||||||
const started = Date.now()
|
const started = Date.now()
|
||||||
const part: MessageV2.ToolPart = {
|
const part: MessageV2.ToolPart = {
|
||||||
type: "tool",
|
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)
|
yield* sessions.updatePart(part)
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Shell.Started.Sync, {
|
yield* events.publish(SessionEvent.Shell.Started, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(started),
|
timestamp: DateTime.makeUnsafe(started),
|
||||||
callID,
|
callID: part.callID,
|
||||||
command: input.command,
|
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()
|
const completed = Date.now()
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Shell.Ended.Sync, {
|
yield* events.publish(SessionEvent.Shell.Ended, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(completed),
|
timestamp: DateTime.makeUnsafe(completed),
|
||||||
callID: part.callID,
|
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) {
|
if (current?.agent !== info.agent) {
|
||||||
yield* sync.run(SessionEvent.AgentSwitched.Sync, {
|
yield* events.publish(SessionEvent.AgentSwitched, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(info.time.created),
|
timestamp: DateTime.makeUnsafe(info.time.created),
|
||||||
agent: info.agent,
|
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.id !== info.model.modelID ||
|
||||||
(current.model.variant === "default" ? undefined : current.model.variant) !== info.model.variant
|
(current.model.variant === "default" ? undefined : current.model.variant) !== info.model.variant
|
||||||
) {
|
) {
|
||||||
yield* sync.run(SessionEvent.ModelSwitched.Sync, {
|
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(info.time.created),
|
timestamp: DateTime.makeUnsafe(info.time.created),
|
||||||
model: {
|
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.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Prompted.Sync, {
|
yield* events.publish(SessionEvent.Prompted, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(info.time.created),
|
timestamp: DateTime.makeUnsafe(info.time.created),
|
||||||
prompt: {
|
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) {
|
for (const text of nextPrompt.synthetic) {
|
||||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||||
if (flags.experimentalEventSystem) {
|
if (flags.experimentalEventSystem) {
|
||||||
yield* sync.run(SessionEvent.Synthetic.Sync, {
|
yield* events.publish(SessionEvent.Synthetic, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(info.time.created),
|
timestamp: DateTime.makeUnsafe(info.time.created),
|
||||||
text,
|
text,
|
||||||
@@ -2038,13 +2038,13 @@ export const defaultLayer = Layer.suspend(() =>
|
|||||||
Layer.provide(Image.defaultLayer),
|
Layer.provide(Image.defaultLayer),
|
||||||
Layer.provide(
|
Layer.provide(
|
||||||
Layer.mergeAll(
|
Layer.mergeAll(
|
||||||
|
EventV2Bridge.defaultLayer,
|
||||||
Agent.defaultLayer,
|
Agent.defaultLayer,
|
||||||
SystemPrompt.defaultLayer,
|
SystemPrompt.defaultLayer,
|
||||||
LLM.defaultLayer,
|
LLM.defaultLayer,
|
||||||
Reference.defaultLayer,
|
Reference.defaultLayer,
|
||||||
Bus.layer,
|
Bus.layer,
|
||||||
CrossSpawnSpawner.defaultLayer,
|
CrossSpawnSpawner.defaultLayer,
|
||||||
SyncEvent.defaultLayer,
|
|
||||||
RuntimeFlags.defaultLayer,
|
RuntimeFlags.defaultLayer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
|
|
||||||
import { Identifier } from "@/id/id"
|
import { Identifier } from "@/id/id"
|
||||||
|
import { Session as CoreSession } from "@opencode-ai/core/session"
|
||||||
import { withStatics } from "@opencode-ai/core/schema"
|
import { withStatics } from "@opencode-ai/core/schema"
|
||||||
|
|
||||||
export const SessionID = Schema.String.check(Schema.isStartsWith("ses")).pipe(
|
export const SessionID = CoreSession.ID
|
||||||
Schema.brand("SessionID"),
|
|
||||||
withStatics((s) => ({
|
|
||||||
descending: (id?: string) => s.make(Identifier.descending("session", id)),
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
|
|
||||||
export type SessionID = Schema.Schema.Type<typeof SessionID>
|
export type SessionID = Schema.Schema.Type<typeof SessionID>
|
||||||
|
|
||||||
export const MessageID = Schema.String.check(Schema.isStartsWith("msg")).pipe(
|
export const MessageID = Schema.String.check(Schema.isStartsWith("msg")).pipe(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { sqliteTable, text, integer, index, primaryKey, real } from "drizzle-orm/sqlite-core"
|
import { sqliteTable, text, integer, index, primaryKey, real } from "drizzle-orm/sqlite-core"
|
||||||
import { ProjectTable } from "../project/project.sql"
|
import { ProjectTable } from "../project/project.sql"
|
||||||
import type { MessageV2 } from "./message-v2"
|
import type { MessageV2 } from "./message-v2"
|
||||||
import type { SessionMessage } from "../v2/session-message"
|
import type { SessionMessage } from "@opencode-ai/core/session-message"
|
||||||
import type { Snapshot } from "../snapshot"
|
import type { Snapshot } from "../snapshot"
|
||||||
import type { Permission } from "../permission"
|
import type { Permission } from "../permission"
|
||||||
import type { ProjectID } from "../project/schema"
|
import type { ProjectID } from "../project/schema"
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// 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 { Database } from "@/storage/db"
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { GlobalBus } from "@/bus/global"
|
import { GlobalBus } from "@/bus/global"
|
||||||
@@ -9,6 +13,7 @@ import type { WorkspaceID } from "@/control-plane/schema"
|
|||||||
import { EventID } from "./schema"
|
import { EventID } from "./schema"
|
||||||
import { Context, Effect, Layer, Schema as EffectSchema } from "effect"
|
import { Context, Effect, Layer, Schema as EffectSchema } from "effect"
|
||||||
import type { DeepMutable } from "@opencode-ai/core/schema"
|
import type { DeepMutable } from "@opencode-ai/core/schema"
|
||||||
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@/effect/service-use"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
@@ -221,6 +226,9 @@ export function reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; convertEvent?: ConvertEvent }) {
|
export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; convertEvent?: ConvertEvent }) {
|
||||||
|
for (const [def] of input.projectors) {
|
||||||
|
register(def)
|
||||||
|
}
|
||||||
projectors = new Map(input.projectors)
|
projectors = new Map(input.projectors)
|
||||||
|
|
||||||
// Install all the latest event defs to the bus. We only ever emit
|
// Install all the latest event defs to the bus. We only ever emit
|
||||||
@@ -269,9 +277,7 @@ export function define<
|
|||||||
properties: (input.busSchema ?? input.schema) as BusSchema,
|
properties: (input.busSchema ?? input.schema) as BusSchema,
|
||||||
}
|
}
|
||||||
|
|
||||||
versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0))
|
register(def)
|
||||||
|
|
||||||
registry.set(versionedType(def.type, def.version), def)
|
|
||||||
|
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
@@ -280,9 +286,15 @@ export function project<Def extends Definition>(
|
|||||||
def: Def,
|
def: Def,
|
||||||
func: (db: Database.TxOrDb, data: Event<Def>["data"], event: Event<Def>) => void,
|
func: (db: Database.TxOrDb, data: Event<Def>["data"], event: Event<Def>) => void,
|
||||||
): [Definition, ProjectorFunc] {
|
): [Definition, ProjectorFunc] {
|
||||||
|
register(def)
|
||||||
return [def, func as ProjectorFunc]
|
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>(
|
function process<Def extends Definition>(
|
||||||
def: Def,
|
def: Def,
|
||||||
event: Event<Def>,
|
event: Event<Def>,
|
||||||
@@ -355,19 +367,38 @@ function process<Def extends Definition>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function effectPayloads() {
|
export function effectPayloads() {
|
||||||
return registry
|
return [
|
||||||
.entries()
|
...registry
|
||||||
.map(([type, def]) =>
|
.entries()
|
||||||
EffectSchema.Struct({
|
.map(([type, def]) =>
|
||||||
type: EffectSchema.Literal("sync"),
|
EffectSchema.Struct({
|
||||||
name: EffectSchema.Literal(type),
|
type: EffectSchema.Literal("sync"),
|
||||||
id: EffectSchema.String,
|
name: EffectSchema.Literal(type),
|
||||||
seq: EffectSchema.Finite,
|
id: EffectSchema.String,
|
||||||
aggregateID: EffectSchema.Literal(def.aggregate),
|
seq: EffectSchema.Finite,
|
||||||
data: def.schema,
|
aggregateID: EffectSchema.Literal(def.aggregate),
|
||||||
}).annotate({ identifier: `SyncEvent.${type}` }),
|
data: def.schema,
|
||||||
)
|
}).annotate({ identifier: `SyncEvent.${type}` }),
|
||||||
.toArray()
|
)
|
||||||
|
.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.String,
|
||||||
|
data: definition.data,
|
||||||
|
}).annotate({ identifier: `SyncEvent.${definition.type}` }),
|
||||||
|
)
|
||||||
|
.toArray(),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as SyncEvent from "."
|
export * as SyncEvent from "."
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
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"
|
|
||||||
@@ -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 { and, asc, desc, eq, gt, gte, isNull, like, lt, or, type SQL } from "@/storage/db"
|
||||||
import * as Database from "@/storage/db"
|
import * as Database from "@/storage/db"
|
||||||
import { Context, DateTime, Effect, Layer, Option, Schema } from "effect"
|
import { Context, DateTime, Effect, Layer, Option, Schema } from "effect"
|
||||||
import { SessionMessage } from "./session-message"
|
import { SessionMessage } from "@opencode-ai/core/session-message"
|
||||||
import type { Prompt } from "@opencode-ai/core/session-prompt"
|
import type { Prompt } from "@opencode-ai/core/session-prompt"
|
||||||
import { EventV2 } from "./event"
|
|
||||||
import { ProjectID } from "@/project/schema"
|
import { ProjectID } from "@/project/schema"
|
||||||
import { SessionEvent } from "./session-event"
|
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||||
import { V2Schema } from "@opencode-ai/core/v2-schema"
|
import { V2Schema } from "@opencode-ai/core/v2-schema"
|
||||||
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||||
import { SyncEvent } from "@/sync"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
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(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const sync = yield* SyncEvent.Service
|
const events = yield* EventV2Bridge.Service
|
||||||
const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message)
|
const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message)
|
||||||
|
|
||||||
const decode = (row: typeof SessionMessageTable.$inferSelect) =>
|
const decode = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||||
@@ -292,14 +292,14 @@ export const layer = Layer.effect(
|
|||||||
shell: Effect.fn("V2Session.shell")(function* (_input) {}),
|
shell: Effect.fn("V2Session.shell")(function* (_input) {}),
|
||||||
skill: Effect.fn("V2Session.skill")(function* (_input) {}),
|
skill: Effect.fn("V2Session.skill")(function* (_input) {}),
|
||||||
switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
|
switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
|
||||||
yield* sync.run(SessionEvent.AgentSwitched.Sync, {
|
yield* events.publish(SessionEvent.AgentSwitched, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||||
agent: input.agent,
|
agent: input.agent,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
|
switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
|
||||||
yield* sync.run(SessionEvent.ModelSwitched.Sync, {
|
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
timestamp: DateTime.makeUnsafe(Date.now()),
|
timestamp: DateTime.makeUnsafe(Date.now()),
|
||||||
model: input.model,
|
model: input.model,
|
||||||
@@ -334,6 +334,6 @@ export const layer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const defaultLayer = layer.pipe(Layer.provide(SyncEvent.defaultLayer))
|
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer))
|
||||||
|
|
||||||
export * as SessionV2 from "./session"
|
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 { MessageV2 } from "../../src/session/message-v2"
|
||||||
import { Database } from "@/storage/db"
|
import { Database } from "@/storage/db"
|
||||||
import { SessionMessageTable, SessionTable } from "@/session/session.sql"
|
import { SessionMessageTable, SessionTable } from "@/session/session.sql"
|
||||||
import { SessionMessage } from "../../src/v2/session-message"
|
import { SessionMessage } from "@opencode-ai/core/session-message"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import * as DateTime from "effect/DateTime"
|
import * as DateTime from "effect/DateTime"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
|||||||
import { TestConfig } from "../fixture/config"
|
import { TestConfig } from "../fixture/config"
|
||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
|
||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
@@ -227,6 +228,7 @@ const deps = Layer.mergeAll(
|
|||||||
Config.defaultLayer,
|
Config.defaultLayer,
|
||||||
SyncEvent.defaultLayer,
|
SyncEvent.defaultLayer,
|
||||||
RuntimeFlags.layer({ experimentalEventSystem: true }),
|
RuntimeFlags.layer({ experimentalEventSystem: true }),
|
||||||
|
EventV2Bridge.defaultLayer,
|
||||||
)
|
)
|
||||||
|
|
||||||
const env = Layer.mergeAll(
|
const env = Layer.mergeAll(
|
||||||
@@ -276,6 +278,7 @@ function compactionProcessLayer(options?: CompactionProcessOptions) {
|
|||||||
Layer.provide(options?.config ?? Config.defaultLayer),
|
Layer.provide(options?.config ?? Config.defaultLayer),
|
||||||
Layer.provide(SyncEvent.defaultLayer),
|
Layer.provide(SyncEvent.defaultLayer),
|
||||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||||
|
Layer.provide(EventV2Bridge.defaultLayer),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { testEffect } from "../lib/effect"
|
|||||||
import { raw, reply, TestLLMServer } from "../lib/llm-server"
|
import { raw, reply, TestLLMServer } from "../lib/llm-server"
|
||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
|
||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
@@ -180,6 +181,7 @@ const deps = Layer.mergeAll(
|
|||||||
Provider.defaultLayer,
|
Provider.defaultLayer,
|
||||||
status,
|
status,
|
||||||
SyncEvent.defaultLayer,
|
SyncEvent.defaultLayer,
|
||||||
|
EventV2Bridge.defaultLayer,
|
||||||
).pipe(Layer.provideMerge(infra))
|
).pipe(Layer.provideMerge(infra))
|
||||||
const env = Layer.mergeAll(
|
const env = Layer.mergeAll(
|
||||||
TestLLMServer.layer,
|
TestLLMServer.layer,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import { awaitWithTimeout, pollWithTimeout, testEffect } from "../lib/effect"
|
|||||||
import { reply, TestLLMServer } from "../lib/llm-server"
|
import { reply, TestLLMServer } from "../lib/llm-server"
|
||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
|
||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
@@ -180,6 +181,7 @@ function makeHttp(input?: { processor?: "blocking" }) {
|
|||||||
BackgroundJob.defaultLayer,
|
BackgroundJob.defaultLayer,
|
||||||
status,
|
status,
|
||||||
SyncEvent.defaultLayer,
|
SyncEvent.defaultLayer,
|
||||||
|
EventV2Bridge.defaultLayer,
|
||||||
).pipe(Layer.provideMerge(infra))
|
).pipe(Layer.provideMerge(infra))
|
||||||
const question = Question.layer.pipe(Layer.provideMerge(deps))
|
const question = Question.layer.pipe(Layer.provideMerge(deps))
|
||||||
const todo = Todo.layer.pipe(Layer.provideMerge(deps))
|
const todo = Todo.layer.pipe(Layer.provideMerge(deps))
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import { Format } from "../../src/format"
|
|||||||
import { Reference } from "../../src/reference/reference"
|
import { Reference } from "../../src/reference/reference"
|
||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
|
||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
@@ -129,6 +130,7 @@ function makeHttp() {
|
|||||||
BackgroundJob.defaultLayer,
|
BackgroundJob.defaultLayer,
|
||||||
status,
|
status,
|
||||||
SyncEvent.defaultLayer,
|
SyncEvent.defaultLayer,
|
||||||
|
EventV2Bridge.defaultLayer,
|
||||||
).pipe(Layer.provideMerge(infra))
|
).pipe(Layer.provideMerge(infra))
|
||||||
const question = Question.layer.pipe(Layer.provideMerge(deps))
|
const question = Question.layer.pipe(Layer.provideMerge(deps))
|
||||||
const todo = Todo.layer.pipe(Layer.provideMerge(deps))
|
const todo = Todo.layer.pipe(Layer.provideMerge(deps))
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
import * as DateTime from "effect/DateTime"
|
import * as DateTime from "effect/DateTime"
|
||||||
import { SessionID } from "../../src/session/schema"
|
import { SessionID } from "../../src/session/schema"
|
||||||
import { EventV2 } from "../../src/v2/event"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { SessionEvent } from "../../src/v2/session-event"
|
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||||
import { SessionMessageUpdater } from "../../src/v2/session-message-updater"
|
import { SessionMessageUpdater } from "@opencode-ai/core/session-message-updater"
|
||||||
|
|
||||||
test("step snapshots carry over to assistant messages", () => {
|
test("step snapshots carry over to assistant messages", () => {
|
||||||
const state: SessionMessageUpdater.MemoryState = { messages: [] }
|
const state: SessionMessageUpdater.MemoryState = { messages: [] }
|
||||||
|
|||||||
@@ -4387,14 +4387,14 @@ export class Model extends HeyApiClient {
|
|||||||
*/
|
*/
|
||||||
public list<ThrowOnError extends boolean = false>(
|
public list<ThrowOnError extends boolean = false>(
|
||||||
parameters?: {
|
parameters?: {
|
||||||
instance?: {
|
location?: {
|
||||||
directory?: string
|
directory?: string
|
||||||
workspace?: string
|
workspace?: string
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
options?: Options<never, ThrowOnError>,
|
options?: Options<never, ThrowOnError>,
|
||||||
) {
|
) {
|
||||||
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }])
|
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }])
|
||||||
return (options?.client ?? this.client).get<V2ModelListResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this.client).get<V2ModelListResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/model",
|
url: "/api/model",
|
||||||
...options,
|
...options,
|
||||||
@@ -4411,14 +4411,14 @@ export class Provider2 extends HeyApiClient {
|
|||||||
*/
|
*/
|
||||||
public list<ThrowOnError extends boolean = false>(
|
public list<ThrowOnError extends boolean = false>(
|
||||||
parameters?: {
|
parameters?: {
|
||||||
instance?: {
|
location?: {
|
||||||
directory?: string
|
directory?: string
|
||||||
workspace?: string
|
workspace?: string
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
options?: Options<never, ThrowOnError>,
|
options?: Options<never, ThrowOnError>,
|
||||||
) {
|
) {
|
||||||
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }])
|
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }])
|
||||||
return (options?.client ?? this.client).get<V2ProviderListResponses, unknown, ThrowOnError>({
|
return (options?.client ?? this.client).get<V2ProviderListResponses, unknown, ThrowOnError>({
|
||||||
url: "/api/provider",
|
url: "/api/provider",
|
||||||
...options,
|
...options,
|
||||||
@@ -4434,7 +4434,7 @@ export class Provider2 extends HeyApiClient {
|
|||||||
public get<ThrowOnError extends boolean = false>(
|
public get<ThrowOnError extends boolean = false>(
|
||||||
parameters: {
|
parameters: {
|
||||||
providerID: string
|
providerID: string
|
||||||
instance?: {
|
location?: {
|
||||||
directory?: string
|
directory?: string
|
||||||
workspace?: string
|
workspace?: string
|
||||||
}
|
}
|
||||||
@@ -4447,7 +4447,7 @@ export class Provider2 extends HeyApiClient {
|
|||||||
{
|
{
|
||||||
args: [
|
args: [
|
||||||
{ in: "path", key: "providerID" },
|
{ in: "path", key: "providerID" },
|
||||||
{ in: "query", key: "instance" },
|
{ in: "query", key: "location" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user