chore: generate
This commit is contained in:
@@ -50,11 +50,7 @@ export function make<
|
|||||||
const T extends Tag | undefined = undefined,
|
const T extends Tag | undefined = undefined,
|
||||||
>(
|
>(
|
||||||
input: MakeInput<Implementation, Items, T>,
|
input: MakeInput<Implementation, Items, T>,
|
||||||
): Node<
|
): Node<Layer.Success<Implementation>, Layer.Error<Implementation> | Error<Items[number]>, T> {
|
||||||
Layer.Success<Implementation>,
|
|
||||||
Layer.Error<Implementation> | Error<Items[number]>,
|
|
||||||
T
|
|
||||||
> {
|
|
||||||
return {
|
return {
|
||||||
kind: "layer",
|
kind: "layer",
|
||||||
name: input.service !== undefined ? input.service.key : input.name,
|
name: input.service !== undefined ? input.service.key : input.name,
|
||||||
@@ -65,10 +61,7 @@ export function make<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unbound<R, Shape, const T extends Tag>(
|
export function unbound<R, Shape, const T extends Tag>(service: Context.Key<R, Shape>, tag: T): Node<R, never, T> {
|
||||||
service: Context.Key<R, Shape>,
|
|
||||||
tag: T,
|
|
||||||
): Node<R, never, T> {
|
|
||||||
return {
|
return {
|
||||||
kind: "unbound",
|
kind: "unbound",
|
||||||
name: service.key,
|
name: service.key,
|
||||||
@@ -100,16 +93,12 @@ export interface Tags<Config extends TagConfig> {
|
|||||||
) => <const Implementation extends Layer.Any, const Items extends NodeList>(
|
) => <const Implementation extends Layer.Any, const Items extends NodeList>(
|
||||||
input: DistributiveOmit<MakeInput<Implementation, Items, Tag<Name>>, "tag"> &
|
input: DistributiveOmit<MakeInput<Implementation, Items, Tag<Name>>, "tag"> &
|
||||||
CheckTags<Items, Name | Extract<Config[Name][number], string>>,
|
CheckTags<Items, Name | Extract<Config[Name][number], string>>,
|
||||||
) => Node<
|
) => Node<Layer.Success<Implementation>, Layer.Error<Implementation> | Error<Items[number]>, Tag<Name>>
|
||||||
Layer.Success<Implementation>,
|
|
||||||
Layer.Error<Implementation> | Error<Items[number]>,
|
|
||||||
Tag<Name>
|
|
||||||
>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tags<
|
export function tags<const Config extends { readonly [Name in keyof Config]: readonly (keyof Config & string)[] }>(
|
||||||
const Config extends { readonly [Name in keyof Config]: readonly (keyof Config & string)[] },
|
config: Config,
|
||||||
>(config: Config): Tags<Config> {
|
): Tags<Config> {
|
||||||
const names = Object.keys(config) as TagNames<Config>[]
|
const names = Object.keys(config) as TagNames<Config>[]
|
||||||
const values = Object.fromEntries(names.map((name) => [name, makeTag(name)])) as Tags<Config>["values"]
|
const values = Object.fromEntries(names.map((name) => [name, makeTag(name)])) as Tags<Config>["values"]
|
||||||
return {
|
return {
|
||||||
|
|||||||
+260
-263
@@ -184,271 +184,268 @@ 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 database = yield* Database.Service
|
const database = yield* Database.Service
|
||||||
const db = database.db
|
const db = database.db
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const projects = yield* ProjectV2.Service
|
const projects = yield* ProjectV2.Service
|
||||||
const execution = yield* SessionExecution.Service
|
const execution = yield* SessionExecution.Service
|
||||||
const store = yield* SessionStore.Service
|
const store = yield* SessionStore.Service
|
||||||
const locations = yield* LocationServiceMap.Service
|
const locations = yield* LocationServiceMap.Service
|
||||||
const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message)
|
const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message)
|
||||||
const isDurableSessionEvent = Schema.is(SessionEvent.Durable)
|
const isDurableSessionEvent = Schema.is(SessionEvent.Durable)
|
||||||
const decode = (row: typeof SessionMessageTable.$inferSelect) =>
|
const decode = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||||
decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe(
|
decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe(
|
||||||
Effect.mapError(
|
Effect.mapError(
|
||||||
() =>
|
() =>
|
||||||
new MessageDecodeError({
|
new MessageDecodeError({
|
||||||
sessionID: SessionSchema.ID.make(row.session_id),
|
sessionID: SessionSchema.ID.make(row.session_id),
|
||||||
messageID: SessionMessage.ID.make(row.id),
|
messageID: SessionMessage.ID.make(row.id),
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = Service.of({
|
||||||
|
create: Effect.fn("V2Session.create")(function* (input) {
|
||||||
|
const sessionID = input.id ?? SessionSchema.ID.create()
|
||||||
|
const recorded = yield* store.get(sessionID)
|
||||||
|
if (recorded) return recorded
|
||||||
|
const project = yield* projects.resolve(input.location.directory)
|
||||||
|
yield* db
|
||||||
|
.insert(ProjectTable)
|
||||||
|
.values({ id: project.id, worktree: project.directory, vcs: project.vcs?.type, sandboxes: [] })
|
||||||
|
.onConflictDoNothing()
|
||||||
|
.run()
|
||||||
|
.pipe(Effect.orDie)
|
||||||
|
const now = Date.now()
|
||||||
|
const info = SessionV1.SessionInfo.make({
|
||||||
|
id: sessionID,
|
||||||
|
slug: Slug.create(),
|
||||||
|
version: InstallationVersion,
|
||||||
|
projectID: project.id,
|
||||||
|
directory: input.location.directory,
|
||||||
|
path: path.relative(project.directory, input.location.directory).replaceAll("\\", "/"),
|
||||||
|
workspaceID: input.location.workspaceID ? WorkspaceV2.ID.make(input.location.workspaceID) : undefined,
|
||||||
|
title: `New session - ${new Date(now).toISOString()}`,
|
||||||
|
agent: input.agent,
|
||||||
|
model: input.model
|
||||||
|
? {
|
||||||
|
id: ModelV2.ID.make(input.model.id),
|
||||||
|
providerID: input.model.providerID,
|
||||||
|
variant: input.model.variant,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
cost: 0,
|
||||||
|
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||||
|
time: { created: now, updated: now },
|
||||||
|
})
|
||||||
|
const projected = yield* events
|
||||||
|
.publish(SessionV1.Event.Created, { sessionID, info }, { location: input.location })
|
||||||
|
.pipe(
|
||||||
|
Effect.as({ type: "created" } as const),
|
||||||
|
Effect.catchDefect((defect) => {
|
||||||
|
if (!(defect instanceof SessionProjector.SessionAlreadyProjected)) {
|
||||||
|
return Effect.die(defect)
|
||||||
|
}
|
||||||
|
// Concurrent creation lost the projection race. The existing Session identity wins.
|
||||||
|
return store
|
||||||
|
.get(sessionID)
|
||||||
|
.pipe(
|
||||||
|
Effect.flatMap((session) =>
|
||||||
|
session ? Effect.succeed({ type: "existing", session } as const) : Effect.die(defect),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
if (projected.type === "existing") return projected.session
|
||||||
|
// TODO: Restore recorded sessions onto replacement synchronized workspaces in a future API slice.
|
||||||
|
return yield* result.get(sessionID).pipe(Effect.orDie)
|
||||||
|
}),
|
||||||
|
get: Effect.fn("V2Session.get")(function* (sessionID) {
|
||||||
|
const session = yield* store.get(sessionID)
|
||||||
|
if (!session) return yield* new NotFoundError({ sessionID })
|
||||||
|
return session
|
||||||
|
}),
|
||||||
|
list: Effect.fn("V2Session.list")(function* (input = {}) {
|
||||||
|
const direction = input.anchor?.direction ?? "next"
|
||||||
|
const requestedOrder = input.order ?? "desc"
|
||||||
|
const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
|
||||||
|
const sortColumn = SessionTable.time_created
|
||||||
|
const conditions: SQL[] = []
|
||||||
|
if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
|
||||||
|
if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
|
||||||
|
if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
|
||||||
|
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
|
||||||
|
if (input.anchor) {
|
||||||
|
conditions.push(
|
||||||
|
order === "asc"
|
||||||
|
? or(
|
||||||
|
gt(sortColumn, input.anchor.time),
|
||||||
|
and(eq(sortColumn, input.anchor.time), gt(SessionTable.id, input.anchor.id)),
|
||||||
|
)!
|
||||||
|
: or(
|
||||||
|
lt(sortColumn, input.anchor.time),
|
||||||
|
and(eq(sortColumn, input.anchor.time), lt(SessionTable.id, input.anchor.id)),
|
||||||
|
)!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const query = db
|
||||||
|
.select()
|
||||||
|
.from(SessionTable)
|
||||||
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
||||||
|
.orderBy(
|
||||||
|
order === "asc" ? asc(sortColumn) : desc(sortColumn),
|
||||||
|
order === "asc" ? asc(SessionTable.id) : desc(SessionTable.id),
|
||||||
|
)
|
||||||
|
const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
|
||||||
|
Effect.orDie,
|
||||||
|
)
|
||||||
|
return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
|
||||||
|
}),
|
||||||
|
messages: Effect.fn("V2Session.messages")(function* (input) {
|
||||||
|
yield* result.get(input.sessionID)
|
||||||
|
const direction = input.cursor?.direction ?? "next"
|
||||||
|
const requestedOrder = input.order ?? "desc"
|
||||||
|
const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
|
||||||
|
const anchor = input.cursor
|
||||||
|
? yield* db
|
||||||
|
.select({ seq: SessionMessageTable.seq })
|
||||||
|
.from(SessionMessageTable)
|
||||||
|
.where(
|
||||||
|
and(eq(SessionMessageTable.session_id, input.sessionID), eq(SessionMessageTable.id, input.cursor.id)),
|
||||||
|
)
|
||||||
|
.get()
|
||||||
|
.pipe(Effect.orDie)
|
||||||
|
: undefined
|
||||||
|
if (input.cursor && !anchor) return []
|
||||||
|
const boundary = anchor
|
||||||
|
? order === "asc"
|
||||||
|
? gt(SessionMessageTable.seq, anchor.seq)
|
||||||
|
: lt(SessionMessageTable.seq, anchor.seq)
|
||||||
|
: undefined
|
||||||
|
const where = boundary
|
||||||
|
? and(eq(SessionMessageTable.session_id, input.sessionID), boundary)
|
||||||
|
: eq(SessionMessageTable.session_id, input.sessionID)
|
||||||
|
const query = db
|
||||||
|
.select()
|
||||||
|
.from(SessionMessageTable)
|
||||||
|
.where(where)
|
||||||
|
.orderBy(order === "asc" ? asc(SessionMessageTable.seq) : desc(SessionMessageTable.seq))
|
||||||
|
const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
|
||||||
|
Effect.orDie,
|
||||||
|
)
|
||||||
|
return yield* Effect.forEach(direction === "previous" ? rows.toReversed() : rows, decode)
|
||||||
|
}),
|
||||||
|
message: Effect.fn("V2Session.message")(function* (input) {
|
||||||
|
const stored = yield* store.message(input.messageID)
|
||||||
|
return stored?.sessionID === input.sessionID ? stored.message : undefined
|
||||||
|
}),
|
||||||
|
context: Effect.fn("V2Session.context")(function* (sessionID) {
|
||||||
|
yield* result.get(sessionID)
|
||||||
|
return yield* store.context(sessionID)
|
||||||
|
}),
|
||||||
|
events: (input) =>
|
||||||
|
Stream.unwrap(
|
||||||
|
result
|
||||||
|
.get(input.sessionID)
|
||||||
|
.pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
|
||||||
|
).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
|
||||||
|
history: Effect.fn("V2Session.history")(function* (input) {
|
||||||
|
yield* result.get(input.sessionID)
|
||||||
|
return yield* EventV2.readAggregate(db, {
|
||||||
|
...input,
|
||||||
|
aggregateID: input.sessionID,
|
||||||
|
manifest: SessionDurable,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
prompt: Effect.fn("V2Session.prompt")((input) =>
|
||||||
|
Effect.uninterruptible(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* result.get(input.sessionID)
|
||||||
|
const prompt = resolvePrompt(input.prompt)
|
||||||
|
const messageID = input.id ?? SessionMessage.ID.create()
|
||||||
|
const delivery = input.delivery ?? "steer"
|
||||||
|
const expected = { sessionID: input.sessionID, messageID, prompt, delivery }
|
||||||
|
const admitted = yield* SessionInput.admit(db, events, {
|
||||||
|
id: messageID,
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
prompt,
|
||||||
|
delivery,
|
||||||
|
}).pipe(
|
||||||
|
Effect.catchDefect((defect) =>
|
||||||
|
defect instanceof SessionInput.LifecycleConflict
|
||||||
|
? new PromptConflictError({ sessionID: input.sessionID, messageID })
|
||||||
|
: Effect.die(defect),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
if (!SessionInput.equivalent(admitted, expected))
|
||||||
const result = Service.of({
|
return yield* new PromptConflictError({ sessionID: input.sessionID, messageID })
|
||||||
create: Effect.fn("V2Session.create")(function* (input) {
|
if (input.resume !== false) yield* execution.wake(admitted.sessionID)
|
||||||
const sessionID = input.id ?? SessionSchema.ID.create()
|
return admitted
|
||||||
const recorded = yield* store.get(sessionID)
|
}),
|
||||||
if (recorded) return recorded
|
),
|
||||||
const project = yield* projects.resolve(input.location.directory)
|
),
|
||||||
yield* db
|
shell: Effect.fn("V2Session.shell")(function* () {
|
||||||
.insert(ProjectTable)
|
return yield* new OperationUnavailableError({ operation: "shell" })
|
||||||
.values({ id: project.id, worktree: project.directory, vcs: project.vcs?.type, sandboxes: [] })
|
}),
|
||||||
.onConflictDoNothing()
|
skill: Effect.fn("V2Session.skill")(function* () {
|
||||||
.run()
|
return yield* new OperationUnavailableError({ operation: "skill" })
|
||||||
.pipe(Effect.orDie)
|
}),
|
||||||
const now = Date.now()
|
switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
|
||||||
const info = SessionV1.SessionInfo.make({
|
yield* result.get(input.sessionID)
|
||||||
id: sessionID,
|
yield* events.publish(SessionEvent.AgentSwitched, {
|
||||||
slug: Slug.create(),
|
sessionID: input.sessionID,
|
||||||
version: InstallationVersion,
|
messageID: SessionMessage.ID.create(),
|
||||||
projectID: project.id,
|
timestamp: yield* DateTime.now,
|
||||||
directory: input.location.directory,
|
agent: input.agent,
|
||||||
path: path.relative(project.directory, input.location.directory).replaceAll("\\", "/"),
|
})
|
||||||
workspaceID: input.location.workspaceID ? WorkspaceV2.ID.make(input.location.workspaceID) : undefined,
|
}),
|
||||||
title: `New session - ${new Date(now).toISOString()}`,
|
switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
|
||||||
agent: input.agent,
|
yield* result.get(input.sessionID)
|
||||||
model: input.model
|
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||||
? {
|
sessionID: input.sessionID,
|
||||||
id: ModelV2.ID.make(input.model.id),
|
messageID: SessionMessage.ID.create(),
|
||||||
providerID: input.model.providerID,
|
timestamp: yield* DateTime.now,
|
||||||
variant: input.model.variant,
|
model: input.model,
|
||||||
}
|
})
|
||||||
: undefined,
|
}),
|
||||||
cost: 0,
|
compact: Effect.fn("V2Session.compact")(function* (input) {
|
||||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
yield* result.get(input.sessionID)
|
||||||
time: { created: now, updated: now },
|
return yield* new OperationUnavailableError({ operation: "compact" })
|
||||||
})
|
}),
|
||||||
const projected = yield* events
|
wait: Effect.fn("V2Session.wait")(function* (sessionID) {
|
||||||
.publish(SessionV1.Event.Created, { sessionID, info }, { location: input.location })
|
yield* result.get(sessionID)
|
||||||
.pipe(
|
return yield* new OperationUnavailableError({ operation: "wait" })
|
||||||
Effect.as({ type: "created" } as const),
|
}),
|
||||||
Effect.catchDefect((defect) => {
|
active: execution.active,
|
||||||
if (!(defect instanceof SessionProjector.SessionAlreadyProjected)) {
|
resume: Effect.fn("V2Session.resume")(function* (sessionID) {
|
||||||
return Effect.die(defect)
|
yield* result.get(sessionID)
|
||||||
}
|
yield* execution.resume(sessionID)
|
||||||
// Concurrent creation lost the projection race. The existing Session identity wins.
|
}),
|
||||||
return store
|
interrupt: Effect.fn("V2Session.interrupt")((sessionID) =>
|
||||||
.get(sessionID)
|
Effect.uninterruptible(execution.interrupt(sessionID)),
|
||||||
.pipe(
|
),
|
||||||
Effect.flatMap((session) =>
|
revert: {
|
||||||
session ? Effect.succeed({ type: "existing", session } as const) : Effect.die(defect),
|
stage: Effect.fn("V2Session.revert.stage")(function* (input) {
|
||||||
),
|
const session = yield* result.get(input.sessionID)
|
||||||
)
|
return yield* SessionRevert.stage({ session, messageID: input.messageID, files: input.files }).pipe(
|
||||||
}),
|
Effect.provideService(Database.Service, database),
|
||||||
)
|
Effect.provideService(EventV2.Service, events),
|
||||||
if (projected.type === "existing") return projected.session
|
Effect.provide(locations.get(session.location)),
|
||||||
// TODO: Restore recorded sessions onto replacement synchronized workspaces in a future API slice.
|
)
|
||||||
return yield* result.get(sessionID).pipe(Effect.orDie)
|
}),
|
||||||
}),
|
clear: Effect.fn("V2Session.revert.clear")(function* (sessionID) {
|
||||||
get: Effect.fn("V2Session.get")(function* (sessionID) {
|
const session = yield* result.get(sessionID)
|
||||||
const session = yield* store.get(sessionID)
|
yield* SessionRevert.clear(session).pipe(
|
||||||
if (!session) return yield* new NotFoundError({ sessionID })
|
Effect.provideService(EventV2.Service, events),
|
||||||
return session
|
Effect.provide(locations.get(session.location)),
|
||||||
}),
|
)
|
||||||
list: Effect.fn("V2Session.list")(function* (input = {}) {
|
}),
|
||||||
const direction = input.anchor?.direction ?? "next"
|
commit: Effect.fn("V2Session.revert.commit")(function* (sessionID) {
|
||||||
const requestedOrder = input.order ?? "desc"
|
const session = yield* result.get(sessionID)
|
||||||
const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
|
yield* SessionRevert.commit(session).pipe(Effect.provideService(EventV2.Service, events))
|
||||||
const sortColumn = SessionTable.time_created
|
}),
|
||||||
const conditions: SQL[] = []
|
},
|
||||||
if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
|
})
|
||||||
if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
|
|
||||||
if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
|
|
||||||
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
|
|
||||||
if (input.anchor) {
|
|
||||||
conditions.push(
|
|
||||||
order === "asc"
|
|
||||||
? or(
|
|
||||||
gt(sortColumn, input.anchor.time),
|
|
||||||
and(eq(sortColumn, input.anchor.time), gt(SessionTable.id, input.anchor.id)),
|
|
||||||
)!
|
|
||||||
: or(
|
|
||||||
lt(sortColumn, input.anchor.time),
|
|
||||||
and(eq(sortColumn, input.anchor.time), lt(SessionTable.id, input.anchor.id)),
|
|
||||||
)!,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const query = db
|
|
||||||
.select()
|
|
||||||
.from(SessionTable)
|
|
||||||
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
||||||
.orderBy(
|
|
||||||
order === "asc" ? asc(sortColumn) : desc(sortColumn),
|
|
||||||
order === "asc" ? asc(SessionTable.id) : desc(SessionTable.id),
|
|
||||||
)
|
|
||||||
const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
|
|
||||||
Effect.orDie,
|
|
||||||
)
|
|
||||||
return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
|
|
||||||
}),
|
|
||||||
messages: Effect.fn("V2Session.messages")(function* (input) {
|
|
||||||
yield* result.get(input.sessionID)
|
|
||||||
const direction = input.cursor?.direction ?? "next"
|
|
||||||
const requestedOrder = input.order ?? "desc"
|
|
||||||
const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
|
|
||||||
const anchor = input.cursor
|
|
||||||
? yield* db
|
|
||||||
.select({ seq: SessionMessageTable.seq })
|
|
||||||
.from(SessionMessageTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(SessionMessageTable.session_id, input.sessionID),
|
|
||||||
eq(SessionMessageTable.id, input.cursor.id),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.get()
|
|
||||||
.pipe(Effect.orDie)
|
|
||||||
: undefined
|
|
||||||
if (input.cursor && !anchor) return []
|
|
||||||
const boundary = anchor
|
|
||||||
? order === "asc"
|
|
||||||
? gt(SessionMessageTable.seq, anchor.seq)
|
|
||||||
: lt(SessionMessageTable.seq, anchor.seq)
|
|
||||||
: undefined
|
|
||||||
const where = boundary
|
|
||||||
? and(eq(SessionMessageTable.session_id, input.sessionID), boundary)
|
|
||||||
: eq(SessionMessageTable.session_id, input.sessionID)
|
|
||||||
const query = db
|
|
||||||
.select()
|
|
||||||
.from(SessionMessageTable)
|
|
||||||
.where(where)
|
|
||||||
.orderBy(order === "asc" ? asc(SessionMessageTable.seq) : desc(SessionMessageTable.seq))
|
|
||||||
const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
|
|
||||||
Effect.orDie,
|
|
||||||
)
|
|
||||||
return yield* Effect.forEach(direction === "previous" ? rows.toReversed() : rows, decode)
|
|
||||||
}),
|
|
||||||
message: Effect.fn("V2Session.message")(function* (input) {
|
|
||||||
const stored = yield* store.message(input.messageID)
|
|
||||||
return stored?.sessionID === input.sessionID ? stored.message : undefined
|
|
||||||
}),
|
|
||||||
context: Effect.fn("V2Session.context")(function* (sessionID) {
|
|
||||||
yield* result.get(sessionID)
|
|
||||||
return yield* store.context(sessionID)
|
|
||||||
}),
|
|
||||||
events: (input) =>
|
|
||||||
Stream.unwrap(
|
|
||||||
result
|
|
||||||
.get(input.sessionID)
|
|
||||||
.pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
|
|
||||||
).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
|
|
||||||
history: Effect.fn("V2Session.history")(function* (input) {
|
|
||||||
yield* result.get(input.sessionID)
|
|
||||||
return yield* EventV2.readAggregate(db, {
|
|
||||||
...input,
|
|
||||||
aggregateID: input.sessionID,
|
|
||||||
manifest: SessionDurable,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
prompt: Effect.fn("V2Session.prompt")((input) =>
|
|
||||||
Effect.uninterruptible(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
yield* result.get(input.sessionID)
|
|
||||||
const prompt = resolvePrompt(input.prompt)
|
|
||||||
const messageID = input.id ?? SessionMessage.ID.create()
|
|
||||||
const delivery = input.delivery ?? "steer"
|
|
||||||
const expected = { sessionID: input.sessionID, messageID, prompt, delivery }
|
|
||||||
const admitted = yield* SessionInput.admit(db, events, {
|
|
||||||
id: messageID,
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
prompt,
|
|
||||||
delivery,
|
|
||||||
}).pipe(
|
|
||||||
Effect.catchDefect((defect) =>
|
|
||||||
defect instanceof SessionInput.LifecycleConflict
|
|
||||||
? new PromptConflictError({ sessionID: input.sessionID, messageID })
|
|
||||||
: Effect.die(defect),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (!SessionInput.equivalent(admitted, expected))
|
|
||||||
return yield* new PromptConflictError({ sessionID: input.sessionID, messageID })
|
|
||||||
if (input.resume !== false) yield* execution.wake(admitted.sessionID)
|
|
||||||
return admitted
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
shell: Effect.fn("V2Session.shell")(function* () {
|
|
||||||
return yield* new OperationUnavailableError({ operation: "shell" })
|
|
||||||
}),
|
|
||||||
skill: Effect.fn("V2Session.skill")(function* () {
|
|
||||||
return yield* new OperationUnavailableError({ operation: "skill" })
|
|
||||||
}),
|
|
||||||
switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
|
|
||||||
yield* result.get(input.sessionID)
|
|
||||||
yield* events.publish(SessionEvent.AgentSwitched, {
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: SessionMessage.ID.create(),
|
|
||||||
timestamp: yield* DateTime.now,
|
|
||||||
agent: input.agent,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
|
|
||||||
yield* result.get(input.sessionID)
|
|
||||||
yield* events.publish(SessionEvent.ModelSwitched, {
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: SessionMessage.ID.create(),
|
|
||||||
timestamp: yield* DateTime.now,
|
|
||||||
model: input.model,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
compact: Effect.fn("V2Session.compact")(function* (input) {
|
|
||||||
yield* result.get(input.sessionID)
|
|
||||||
return yield* new OperationUnavailableError({ operation: "compact" })
|
|
||||||
}),
|
|
||||||
wait: Effect.fn("V2Session.wait")(function* (sessionID) {
|
|
||||||
yield* result.get(sessionID)
|
|
||||||
return yield* new OperationUnavailableError({ operation: "wait" })
|
|
||||||
}),
|
|
||||||
active: execution.active,
|
|
||||||
resume: Effect.fn("V2Session.resume")(function* (sessionID) {
|
|
||||||
yield* result.get(sessionID)
|
|
||||||
yield* execution.resume(sessionID)
|
|
||||||
}),
|
|
||||||
interrupt: Effect.fn("V2Session.interrupt")((sessionID) =>
|
|
||||||
Effect.uninterruptible(execution.interrupt(sessionID)),
|
|
||||||
),
|
|
||||||
revert: {
|
|
||||||
stage: Effect.fn("V2Session.revert.stage")(function* (input) {
|
|
||||||
const session = yield* result.get(input.sessionID)
|
|
||||||
return yield* SessionRevert.stage({ session, messageID: input.messageID, files: input.files }).pipe(
|
|
||||||
Effect.provideService(Database.Service, database),
|
|
||||||
Effect.provideService(EventV2.Service, events),
|
|
||||||
Effect.provide(locations.get(session.location)),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
clear: Effect.fn("V2Session.revert.clear")(function* (sessionID) {
|
|
||||||
const session = yield* result.get(sessionID)
|
|
||||||
yield* SessionRevert.clear(session).pipe(
|
|
||||||
Effect.provideService(EventV2.Service, events),
|
|
||||||
Effect.provide(locations.get(session.location)),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
commit: Effect.fn("V2Session.revert.commit")(function* (sessionID) {
|
|
||||||
const session = yield* result.get(sessionID)
|
|
||||||
yield* SessionRevert.commit(session).pipe(Effect.provideService(EventV2.Service, events))
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ class OtherError {
|
|||||||
|
|
||||||
const tags = LayerNode.tags({ app: [] })
|
const tags = LayerNode.tags({ app: [] })
|
||||||
const make = tags.make("app")
|
const make = tags.make("app")
|
||||||
const build = <A, E>(root: LayerNode.Node<A, E, any>) =>
|
const build = <A, E>(root: LayerNode.Node<A, E, any>) => LayerNodeTree.compile(root) as Layer.Layer<A, E>
|
||||||
LayerNodeTree.compile(root) as Layer.Layer<A, E>
|
|
||||||
const aLayer = Layer.succeed(A, A.of({}))
|
const aLayer = Layer.succeed(A, A.of({}))
|
||||||
const bLayer = Layer.effect(B, Effect.as(A, B.of({})))
|
const bLayer = Layer.effect(B, Effect.as(A, B.of({})))
|
||||||
const cLayer = Layer.effect(
|
const cLayer = Layer.effect(
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ class App extends Context.Service<App, { readonly run: Effect.Effect<string[]> }
|
|||||||
const tags = LayerNode.tags({ app: [] })
|
const tags = LayerNode.tags({ app: [] })
|
||||||
const make = tags.make("app")
|
const make = tags.make("app")
|
||||||
const build = <A, E>(root: LayerNode.Node<A, E, any>, replacements?: readonly LayerNode.Replacement[]) =>
|
const build = <A, E>(root: LayerNode.Node<A, E, any>, replacements?: readonly LayerNode.Replacement[]) =>
|
||||||
LayerNodeTree.compile(
|
LayerNodeTree.compile(root, new Map(replacements?.map((item) => [item.source, item.replacement]))) as Layer.Layer<
|
||||||
root,
|
A,
|
||||||
new Map(replacements?.map((item) => [item.source, item.replacement])),
|
E
|
||||||
) as Layer.Layer<A, E>
|
>
|
||||||
const valueLayer = Layer.succeed(Value, Value.of({ value: "production" }))
|
const valueLayer = Layer.succeed(Value, Value.of({ value: "production" }))
|
||||||
const greetingLayer = Layer.effect(
|
const greetingLayer = Layer.effect(
|
||||||
Greeting,
|
Greeting,
|
||||||
|
|||||||
@@ -111,7 +111,9 @@ describe("LocationServiceMap", () => {
|
|||||||
}
|
}
|
||||||
}).pipe(
|
}).pipe(
|
||||||
Effect.scoped,
|
Effect.scoped,
|
||||||
Effect.provide(LocationServiceMap.Service.get(Location.Ref.make({ directory: AbsolutePath.make(directory) }))),
|
Effect.provide(
|
||||||
|
LocationServiceMap.Service.get(Location.Ref.make({ directory: AbsolutePath.make(directory) })),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const blockedState = yield* update(blocked.path)
|
const blockedState = yield* update(blocked.path)
|
||||||
|
|||||||
@@ -447,7 +447,11 @@ export const defaultLayer = layer.pipe(
|
|||||||
Layer.provide(locationServiceMapLayer),
|
Layer.provide(locationServiceMapLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
const locationServiceMapNode = LayerNode.make({ service: LocationServiceMap.Service, layer: locationServiceMapLayer, deps: [] })
|
const locationServiceMapNode = LayerNode.make({
|
||||||
|
service: LocationServiceMap.Service,
|
||||||
|
layer: locationServiceMapLayer,
|
||||||
|
deps: [],
|
||||||
|
})
|
||||||
|
|
||||||
export const node = LayerNode.make({
|
export const node = LayerNode.make({
|
||||||
service: Service,
|
service: Service,
|
||||||
|
|||||||
@@ -134,7 +134,11 @@ export const defaultLayer = layer.pipe(
|
|||||||
Layer.provide(locationServiceMapLayer),
|
Layer.provide(locationServiceMapLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
const locationServiceMapNode = LayerNode.make({ service: LocationServiceMap.Service, layer: locationServiceMapLayer, deps: [] })
|
const locationServiceMapNode = LayerNode.make({
|
||||||
|
service: LocationServiceMap.Service,
|
||||||
|
layer: locationServiceMapLayer,
|
||||||
|
deps: [],
|
||||||
|
})
|
||||||
|
|
||||||
export const node = LayerNode.make({
|
export const node = LayerNode.make({
|
||||||
service: Service,
|
service: Service,
|
||||||
|
|||||||
@@ -232,10 +232,7 @@ const fragmentFailureLLM = Layer.succeed(
|
|||||||
const fragmentFailureEnv = LayerNodeTree.compile(
|
const fragmentFailureEnv = LayerNodeTree.compile(
|
||||||
root,
|
root,
|
||||||
new Map(
|
new Map(
|
||||||
[...replacements, LayerNode.replace(LLM.layer, fragmentFailureLLM)].map((item) => [
|
[...replacements, LayerNode.replace(LLM.layer, fragmentFailureLLM)].map((item) => [item.source, item.replacement]),
|
||||||
item.source,
|
|
||||||
item.replacement,
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const itFragmentFailure = testEffect(fragmentFailureEnv)
|
const itFragmentFailure = testEffect(fragmentFailureEnv)
|
||||||
|
|||||||
@@ -92,9 +92,9 @@ const it = testEffect(
|
|||||||
root,
|
root,
|
||||||
new Map(
|
new Map(
|
||||||
[
|
[
|
||||||
LayerNode.replace(MCP.layer, mcp),
|
LayerNode.replace(MCP.layer, mcp),
|
||||||
LayerNode.replace(LSP.layer, lsp),
|
LayerNode.replace(LSP.layer, lsp),
|
||||||
LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })),
|
LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||||
].map((item) => [item.source, item.replacement]),
|
].map((item) => [item.source, item.replacement]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -45,7 +45,14 @@ function requestLayer(client: HttpClient.HttpClient) {
|
|||||||
function integrationLayer(client: HttpClient.HttpClient) {
|
function integrationLayer(client: HttpClient.HttpClient) {
|
||||||
const replacement = LayerNode.replace(FetchHttpClient.layer, Layer.succeed(HttpClient.HttpClient, client))
|
const replacement = LayerNode.replace(FetchHttpClient.layer, Layer.succeed(HttpClient.HttpClient, client))
|
||||||
return LayerNodeTree.compile(
|
return LayerNodeTree.compile(
|
||||||
LayerNode.group([ShareNext.node, EventV2Bridge.node, Session.node, SessionProjector.node, AccountRepo.node, Database.node]),
|
LayerNode.group([
|
||||||
|
ShareNext.node,
|
||||||
|
EventV2Bridge.node,
|
||||||
|
Session.node,
|
||||||
|
SessionProjector.node,
|
||||||
|
AccountRepo.node,
|
||||||
|
Database.node,
|
||||||
|
]),
|
||||||
new Map([[replacement.source, replacement.replacement]]),
|
new Map([[replacement.source, replacement.replacement]]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user