refactor(session): simplify pending withdrawal
This commit is contained in:
parent
330ab9acae
commit
d7ffc7fec1
12 changed files with 143 additions and 130 deletions
|
|
@ -31,7 +31,7 @@ export function createV2SessionReducer() {
|
||||||
return result([...source])
|
return result([...source])
|
||||||
case "session.input.withdrawn":
|
case "session.input.withdrawn":
|
||||||
pending.delete(key(sessionID, event.data.inputID))
|
pending.delete(key(sessionID, event.data.inputID))
|
||||||
return result([...source])
|
return
|
||||||
case "session.input.promoted": {
|
case "session.input.promoted": {
|
||||||
const input = pending.get(key(sessionID, event.data.inputID))
|
const input = pending.get(key(sessionID, event.data.inputID))
|
||||||
pending.delete(key(sessionID, event.data.inputID))
|
pending.delete(key(sessionID, event.data.inputID))
|
||||||
|
|
|
||||||
|
|
@ -249,44 +249,38 @@ export type Endpoint5_21Input = { readonly sessionID: Session.ID }
|
||||||
export type Endpoint5_21Output = ReadonlyArray<SessionPending.Info>
|
export type Endpoint5_21Output = ReadonlyArray<SessionPending.Info>
|
||||||
export type SessionPendingListOperation<E = never> = (input: Endpoint5_21Input) => Effect.Effect<Endpoint5_21Output, E>
|
export type SessionPendingListOperation<E = never> = (input: Endpoint5_21Input) => Effect.Effect<Endpoint5_21Output, E>
|
||||||
|
|
||||||
export type Endpoint5_22Input = { readonly sessionID: Session.ID; readonly inputID: SessionMessage.ID }
|
export type Endpoint5_22Input = { readonly sessionID: Session.ID }
|
||||||
export type Endpoint5_22Output = boolean
|
export type Endpoint5_22Output = ReadonlyArray<InstructionEntry.Info>
|
||||||
export type SessionPendingWithdrawOperation<E = never> = (
|
export type SessionInstructionsEntryListOperation<E = never> = (
|
||||||
input: Endpoint5_22Input,
|
input: Endpoint5_22Input,
|
||||||
) => Effect.Effect<Endpoint5_22Output, E>
|
) => Effect.Effect<Endpoint5_22Output, E>
|
||||||
|
|
||||||
export type Endpoint5_23Input = { readonly sessionID: Session.ID }
|
export type Endpoint5_23Input = {
|
||||||
export type Endpoint5_23Output = ReadonlyArray<InstructionEntry.Info>
|
|
||||||
export type SessionInstructionsEntryListOperation<E = never> = (
|
|
||||||
input: Endpoint5_23Input,
|
|
||||||
) => Effect.Effect<Endpoint5_23Output, E>
|
|
||||||
|
|
||||||
export type Endpoint5_24Input = {
|
|
||||||
readonly sessionID: Session.ID
|
readonly sessionID: Session.ID
|
||||||
readonly key: InstructionEntry.Key
|
readonly key: InstructionEntry.Key
|
||||||
readonly value: Schema.Json
|
readonly value: Schema.Json
|
||||||
}
|
}
|
||||||
export type Endpoint5_24Output = void
|
export type Endpoint5_23Output = void
|
||||||
export type SessionInstructionsEntryPutOperation<E = never> = (
|
export type SessionInstructionsEntryPutOperation<E = never> = (
|
||||||
|
input: Endpoint5_23Input,
|
||||||
|
) => Effect.Effect<Endpoint5_23Output, E>
|
||||||
|
|
||||||
|
export type Endpoint5_24Input = { readonly sessionID: Session.ID; readonly key: InstructionEntry.Key }
|
||||||
|
export type Endpoint5_24Output = void
|
||||||
|
export type SessionInstructionsEntryRemoveOperation<E = never> = (
|
||||||
input: Endpoint5_24Input,
|
input: Endpoint5_24Input,
|
||||||
) => Effect.Effect<Endpoint5_24Output, E>
|
) => Effect.Effect<Endpoint5_24Output, E>
|
||||||
|
|
||||||
export type Endpoint5_25Input = { readonly sessionID: Session.ID; readonly key: InstructionEntry.Key }
|
export type Endpoint5_25Input = { readonly sessionID: Session.ID; readonly prompt: string }
|
||||||
export type Endpoint5_25Output = void
|
export type Endpoint5_25Output = { readonly text: string }
|
||||||
export type SessionInstructionsEntryRemoveOperation<E = never> = (
|
export type SessionGenerateOperation<E = never> = (input: Endpoint5_25Input) => Effect.Effect<Endpoint5_25Output, E>
|
||||||
input: Endpoint5_25Input,
|
|
||||||
) => Effect.Effect<Endpoint5_25Output, E>
|
|
||||||
|
|
||||||
export type Endpoint5_26Input = { readonly sessionID: Session.ID; readonly prompt: string }
|
export type Endpoint5_26Input = {
|
||||||
export type Endpoint5_26Output = { readonly text: string }
|
|
||||||
export type SessionGenerateOperation<E = never> = (input: Endpoint5_26Input) => Effect.Effect<Endpoint5_26Output, E>
|
|
||||||
|
|
||||||
export type Endpoint5_27Input = {
|
|
||||||
readonly sessionID: Session.ID
|
readonly sessionID: Session.ID
|
||||||
readonly after?: Event.Seq | undefined
|
readonly after?: Event.Seq | undefined
|
||||||
readonly follow?: boolean | undefined
|
readonly follow?: boolean | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint5_27Output =
|
export type Endpoint5_26Output =
|
||||||
| (
|
| (
|
||||||
| {
|
| {
|
||||||
readonly id: Event.ID
|
readonly id: Event.ID
|
||||||
|
|
@ -842,19 +836,25 @@ export type Endpoint5_27Output =
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
| EventLog.Synced
|
| EventLog.Synced
|
||||||
export type SessionLogOperation<E = never> = (input: Endpoint5_27Input) => Stream.Stream<Endpoint5_27Output, E>
|
export type SessionLogOperation<E = never> = (input: Endpoint5_26Input) => Stream.Stream<Endpoint5_26Output, E>
|
||||||
|
|
||||||
|
export type Endpoint5_27Input = { readonly sessionID: Session.ID }
|
||||||
|
export type Endpoint5_27Output = void
|
||||||
|
export type SessionInterruptOperation<E = never> = (input: Endpoint5_27Input) => Effect.Effect<Endpoint5_27Output, E>
|
||||||
|
|
||||||
export type Endpoint5_28Input = { readonly sessionID: Session.ID }
|
export type Endpoint5_28Input = { readonly sessionID: Session.ID }
|
||||||
export type Endpoint5_28Output = void
|
export type Endpoint5_28Output = void
|
||||||
export type SessionInterruptOperation<E = never> = (input: Endpoint5_28Input) => Effect.Effect<Endpoint5_28Output, E>
|
export type SessionBackgroundOperation<E = never> = (input: Endpoint5_28Input) => Effect.Effect<Endpoint5_28Output, E>
|
||||||
|
|
||||||
export type Endpoint5_29Input = { readonly sessionID: Session.ID }
|
export type Endpoint5_29Input = { readonly sessionID: Session.ID; readonly messageID: SessionMessage.ID }
|
||||||
export type Endpoint5_29Output = void
|
export type Endpoint5_29Output = SessionMessage.Info
|
||||||
export type SessionBackgroundOperation<E = never> = (input: Endpoint5_29Input) => Effect.Effect<Endpoint5_29Output, E>
|
export type SessionMessageOperation<E = never> = (input: Endpoint5_29Input) => Effect.Effect<Endpoint5_29Output, E>
|
||||||
|
|
||||||
export type Endpoint5_30Input = { readonly sessionID: Session.ID; readonly messageID: SessionMessage.ID }
|
export type Endpoint5_30Input = { readonly sessionID: Session.ID; readonly inputID: SessionMessage.ID }
|
||||||
export type Endpoint5_30Output = SessionMessage.Info
|
export type Endpoint5_30Output = boolean
|
||||||
export type SessionMessageOperation<E = never> = (input: Endpoint5_30Input) => Effect.Effect<Endpoint5_30Output, E>
|
export type SessionPendingWithdrawOperation<E = never> = (
|
||||||
|
input: Endpoint5_30Input,
|
||||||
|
) => Effect.Effect<Endpoint5_30Output, E>
|
||||||
|
|
||||||
export interface SessionApi<E = never> {
|
export interface SessionApi<E = never> {
|
||||||
readonly list: SessionListOperation<E>
|
readonly list: SessionListOperation<E>
|
||||||
|
|
|
||||||
|
|
@ -492,7 +492,7 @@ const Endpoint5_21 = (raw: RawClient["server.session"]) => (input: Endpoint5_21I
|
||||||
|
|
||||||
const Endpoint5_22 = (raw: RawClient["server.session"]) => (input: Endpoint5_22Input) =>
|
const Endpoint5_22 = (raw: RawClient["server.session"]) => (input: Endpoint5_22Input) =>
|
||||||
preserveEffect<Endpoint5_22Output>()(
|
preserveEffect<Endpoint5_22Output>()(
|
||||||
raw["session.pending.withdraw"]({ params: { sessionID: input["sessionID"], inputID: input["inputID"] } }).pipe(
|
raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
),
|
),
|
||||||
|
|
@ -500,37 +500,29 @@ const Endpoint5_22 = (raw: RawClient["server.session"]) => (input: Endpoint5_22I
|
||||||
|
|
||||||
const Endpoint5_23 = (raw: RawClient["server.session"]) => (input: Endpoint5_23Input) =>
|
const Endpoint5_23 = (raw: RawClient["server.session"]) => (input: Endpoint5_23Input) =>
|
||||||
preserveEffect<Endpoint5_23Output>()(
|
preserveEffect<Endpoint5_23Output>()(
|
||||||
raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
|
||||||
Effect.mapError(mapClientError),
|
|
||||||
Effect.map((value) => value.data),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const Endpoint5_24 = (raw: RawClient["server.session"]) => (input: Endpoint5_24Input) =>
|
|
||||||
preserveEffect<Endpoint5_24Output>()(
|
|
||||||
raw["session.instructions.entry.put"]({
|
raw["session.instructions.entry.put"]({
|
||||||
params: { sessionID: input["sessionID"], key: input["key"] },
|
params: { sessionID: input["sessionID"], key: input["key"] },
|
||||||
payload: { value: input["value"] },
|
payload: { value: input["value"] },
|
||||||
}).pipe(Effect.mapError(mapClientError)),
|
}).pipe(Effect.mapError(mapClientError)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const Endpoint5_25 = (raw: RawClient["server.session"]) => (input: Endpoint5_25Input) =>
|
const Endpoint5_24 = (raw: RawClient["server.session"]) => (input: Endpoint5_24Input) =>
|
||||||
preserveEffect<Endpoint5_25Output>()(
|
preserveEffect<Endpoint5_24Output>()(
|
||||||
raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
|
raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const Endpoint5_26 = (raw: RawClient["server.session"]) => (input: Endpoint5_26Input) =>
|
const Endpoint5_25 = (raw: RawClient["server.session"]) => (input: Endpoint5_25Input) =>
|
||||||
preserveEffect<Endpoint5_26Output>()(
|
preserveEffect<Endpoint5_25Output>()(
|
||||||
raw["session.generate"]({ params: { sessionID: input["sessionID"] }, payload: { prompt: input["prompt"] } }).pipe(
|
raw["session.generate"]({ params: { sessionID: input["sessionID"] }, payload: { prompt: input["prompt"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const Endpoint5_27 = (raw: RawClient["server.session"]) => (input: Endpoint5_27Input) =>
|
const Endpoint5_26 = (raw: RawClient["server.session"]) => (input: Endpoint5_26Input) =>
|
||||||
preserveStream<Endpoint5_27Output>()(
|
preserveStream<Endpoint5_26Output>()(
|
||||||
Stream.unwrap(
|
Stream.unwrap(
|
||||||
raw["session.log"]({
|
raw["session.log"]({
|
||||||
params: { sessionID: input["sessionID"] },
|
params: { sessionID: input["sessionID"] },
|
||||||
|
|
@ -542,19 +534,27 @@ const Endpoint5_27 = (raw: RawClient["server.session"]) => (input: Endpoint5_27I
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const Endpoint5_27 = (raw: RawClient["server.session"]) => (input: Endpoint5_27Input) =>
|
||||||
|
preserveEffect<Endpoint5_27Output>()(
|
||||||
|
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||||
|
)
|
||||||
|
|
||||||
const Endpoint5_28 = (raw: RawClient["server.session"]) => (input: Endpoint5_28Input) =>
|
const Endpoint5_28 = (raw: RawClient["server.session"]) => (input: Endpoint5_28Input) =>
|
||||||
preserveEffect<Endpoint5_28Output>()(
|
preserveEffect<Endpoint5_28Output>()(
|
||||||
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const Endpoint5_29 = (raw: RawClient["server.session"]) => (input: Endpoint5_29Input) =>
|
const Endpoint5_29 = (raw: RawClient["server.session"]) => (input: Endpoint5_29Input) =>
|
||||||
preserveEffect<Endpoint5_29Output>()(
|
preserveEffect<Endpoint5_29Output>()(
|
||||||
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
|
||||||
|
Effect.mapError(mapClientError),
|
||||||
|
Effect.map((value) => value.data),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const Endpoint5_30 = (raw: RawClient["server.session"]) => (input: Endpoint5_30Input) =>
|
const Endpoint5_30 = (raw: RawClient["server.session"]) => (input: Endpoint5_30Input) =>
|
||||||
preserveEffect<Endpoint5_30Output>()(
|
preserveEffect<Endpoint5_30Output>()(
|
||||||
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
|
raw["session.pending.withdraw"]({ params: { sessionID: input["sessionID"], inputID: input["inputID"] } }).pipe(
|
||||||
Effect.mapError(mapClientError),
|
Effect.mapError(mapClientError),
|
||||||
Effect.map((value) => value.data),
|
Effect.map((value) => value.data),
|
||||||
),
|
),
|
||||||
|
|
@ -580,13 +580,13 @@ const adaptGroup5 = (raw: RawClient["server.session"]) => ({
|
||||||
wait: Endpoint5_16(raw),
|
wait: Endpoint5_16(raw),
|
||||||
revert: { stage: Endpoint5_17(raw), clear: Endpoint5_18(raw), commit: Endpoint5_19(raw) },
|
revert: { stage: Endpoint5_17(raw), clear: Endpoint5_18(raw), commit: Endpoint5_19(raw) },
|
||||||
context: Endpoint5_20(raw),
|
context: Endpoint5_20(raw),
|
||||||
pending: { list: Endpoint5_21(raw), withdraw: Endpoint5_22(raw) },
|
pending: { list: Endpoint5_21(raw), withdraw: Endpoint5_30(raw) },
|
||||||
instructions: { entry: { list: Endpoint5_23(raw), put: Endpoint5_24(raw), remove: Endpoint5_25(raw) } },
|
instructions: { entry: { list: Endpoint5_22(raw), put: Endpoint5_23(raw), remove: Endpoint5_24(raw) } },
|
||||||
generate: Endpoint5_26(raw),
|
generate: Endpoint5_25(raw),
|
||||||
log: Endpoint5_27(raw),
|
log: Endpoint5_26(raw),
|
||||||
interrupt: Endpoint5_28(raw),
|
interrupt: Endpoint5_27(raw),
|
||||||
background: Endpoint5_29(raw),
|
background: Endpoint5_28(raw),
|
||||||
message: Endpoint5_30(raw),
|
message: Endpoint5_29(raw),
|
||||||
})
|
})
|
||||||
|
|
||||||
const Endpoint6_0 = (raw: RawClient["server.message"]) => (input: Endpoint6_0Input) =>
|
const Endpoint6_0 = (raw: RawClient["server.message"]) => (input: Endpoint6_0Input) =>
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,6 @@ import type {
|
||||||
SessionContextOutput,
|
SessionContextOutput,
|
||||||
SessionPendingListInput,
|
SessionPendingListInput,
|
||||||
SessionPendingListOutput,
|
SessionPendingListOutput,
|
||||||
SessionPendingWithdrawInput,
|
|
||||||
SessionPendingWithdrawOutput,
|
|
||||||
SessionInstructionsEntryListInput,
|
SessionInstructionsEntryListInput,
|
||||||
SessionInstructionsEntryListOutput,
|
SessionInstructionsEntryListOutput,
|
||||||
SessionInstructionsEntryPutInput,
|
SessionInstructionsEntryPutInput,
|
||||||
|
|
@ -72,6 +70,8 @@ import type {
|
||||||
SessionBackgroundOutput,
|
SessionBackgroundOutput,
|
||||||
SessionMessageInput,
|
SessionMessageInput,
|
||||||
SessionMessageOutput,
|
SessionMessageOutput,
|
||||||
|
SessionPendingWithdrawInput,
|
||||||
|
SessionPendingWithdrawOutput,
|
||||||
MessageListInput,
|
MessageListInput,
|
||||||
MessageListOutput,
|
MessageListOutput,
|
||||||
ModelListInput,
|
ModelListInput,
|
||||||
|
|
|
||||||
|
|
@ -3170,13 +3170,6 @@ export type SessionPendingListInput = { readonly sessionID: { readonly sessionID
|
||||||
|
|
||||||
export type SessionPendingListOutput = { data: Array<SessionPendingInfo> }["data"]
|
export type SessionPendingListOutput = { data: Array<SessionPendingInfo> }["data"]
|
||||||
|
|
||||||
export type SessionPendingWithdrawInput = {
|
|
||||||
readonly sessionID: { readonly sessionID: string; readonly inputID: string }["sessionID"]
|
|
||||||
readonly inputID: { readonly sessionID: string; readonly inputID: string }["inputID"]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SessionPendingWithdrawOutput = { data: boolean }["data"]
|
|
||||||
|
|
||||||
export type SessionInstructionsEntryListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
export type SessionInstructionsEntryListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||||
|
|
||||||
export type SessionInstructionsEntryListOutput = { data: Array<InstructionEntryInfo> }["data"]
|
export type SessionInstructionsEntryListOutput = { data: Array<InstructionEntryInfo> }["data"]
|
||||||
|
|
@ -3226,6 +3219,13 @@ export type SessionMessageInput = {
|
||||||
|
|
||||||
export type SessionMessageOutput = { data: SessionMessageInfo }["data"]
|
export type SessionMessageOutput = { data: SessionMessageInfo }["data"]
|
||||||
|
|
||||||
|
export type SessionPendingWithdrawInput = {
|
||||||
|
readonly sessionID: { readonly sessionID: string; readonly inputID: string }["sessionID"]
|
||||||
|
readonly inputID: { readonly sessionID: string; readonly inputID: string }["inputID"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionPendingWithdrawOutput = { data: boolean }["data"]
|
||||||
|
|
||||||
export type MessageListInput = {
|
export type MessageListInput = {
|
||||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||||
readonly limit?: {
|
readonly limit?: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export * as SessionPending from "./pending"
|
export * as SessionPending from "./pending"
|
||||||
|
|
||||||
import { and, asc, eq, or } from "drizzle-orm"
|
import { and, asc, eq, or, sql } from "drizzle-orm"
|
||||||
import { DateTime, Effect, Schema } from "effect"
|
import { DateTime, Effect, Schema } from "effect"
|
||||||
import {
|
import {
|
||||||
Compaction,
|
Compaction,
|
||||||
|
|
@ -113,20 +113,24 @@ const admittedFromHistory = Effect.fn("SessionPending.admittedFromHistory")(func
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
id: SessionMessage.ID,
|
id: SessionMessage.ID,
|
||||||
) {
|
) {
|
||||||
const rows = yield* db
|
const row = yield* db
|
||||||
.select()
|
.select()
|
||||||
.from(EventTable)
|
.from(EventTable)
|
||||||
.where(and(eq(EventTable.aggregate_id, sessionID), eq(EventTable.type, admittedEventType)))
|
.where(
|
||||||
.all()
|
and(
|
||||||
|
eq(EventTable.aggregate_id, sessionID),
|
||||||
|
eq(EventTable.type, admittedEventType),
|
||||||
|
sql`json_extract(${EventTable.data}, '$.inputID') = ${id}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
.get()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
for (const row of rows) {
|
if (row) {
|
||||||
const decoded = decodeAdmittedEvent(row.data)
|
const decoded = decodeAdmittedEvent(row.data)
|
||||||
if (decoded._tag !== "Some" || decoded.value.inputID !== id) continue
|
if (decoded._tag !== "Some" || decoded.value.inputID !== id)
|
||||||
const base = {
|
return yield* Effect.die(new LifecycleConflict({ id }))
|
||||||
id,
|
const base = { id, sessionID, timeCreated: DateTime.makeUnsafe(row.created) }
|
||||||
sessionID,
|
|
||||||
timeCreated: DateTime.makeUnsafe(row.created),
|
|
||||||
}
|
|
||||||
return decoded.value.input.type === "user"
|
return decoded.value.input.type === "user"
|
||||||
? User.make({ ...base, ...decoded.value.input })
|
? User.make({ ...base, ...decoded.value.input })
|
||||||
: Synthetic.make({ ...base, ...decoded.value.input })
|
: Synthetic.make({ ...base, ...decoded.value.input })
|
||||||
|
|
@ -164,16 +168,22 @@ const wasWithdrawn = Effect.fn("SessionPending.wasWithdrawn")(function* (
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
id: SessionMessage.ID,
|
id: SessionMessage.ID,
|
||||||
) {
|
) {
|
||||||
const rows = yield* db
|
const row = yield* db
|
||||||
.select({ data: EventTable.data })
|
.select({ data: EventTable.data })
|
||||||
.from(EventTable)
|
.from(EventTable)
|
||||||
.where(and(eq(EventTable.aggregate_id, sessionID), eq(EventTable.type, withdrawnEventType)))
|
.where(
|
||||||
.all()
|
and(
|
||||||
|
eq(EventTable.aggregate_id, sessionID),
|
||||||
|
eq(EventTable.type, withdrawnEventType),
|
||||||
|
sql`json_extract(${EventTable.data}, '$.inputID') = ${id}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
.get()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
return rows.some((row) => {
|
if (!row) return false
|
||||||
const decoded = decodeWithdrawnEvent(row.data)
|
const decoded = decodeWithdrawnEvent(row.data)
|
||||||
return decoded._tag === "Some" && decoded.value.inputID === id
|
return decoded._tag === "Some" && decoded.value.inputID === id
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const admit = Effect.fn("SessionPending.admit")(function* (
|
export const admit = Effect.fn("SessionPending.admit")(function* (
|
||||||
|
|
@ -341,14 +351,13 @@ export const projectCompactionAdmitted = Effect.fn("SessionPending.projectCompac
|
||||||
* message insert inside the same event transaction; the deleted row is what
|
* message insert inside the same event transaction; the deleted row is what
|
||||||
* makes the table pending-only.
|
* makes the table pending-only.
|
||||||
*/
|
*/
|
||||||
export const projectPromoted = Effect.fn("SessionPending.projectPromoted")(function* (
|
const consumeInput = Effect.fn("SessionPending.consumeInput")(function* (
|
||||||
db: DatabaseService,
|
db: DatabaseService,
|
||||||
input: {
|
input: {
|
||||||
readonly id: SessionMessage.ID
|
readonly id: SessionMessage.ID
|
||||||
readonly sessionID: SessionSchema.ID
|
readonly sessionID: SessionSchema.ID
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (yield* compaction(db, input.sessionID)) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
|
||||||
const deleted = yield* db
|
const deleted = yield* db
|
||||||
.delete(SessionPendingTable)
|
.delete(SessionPendingTable)
|
||||||
.where(and(eq(SessionPendingTable.id, input.id), eq(SessionPendingTable.session_id, input.sessionID)))
|
.where(and(eq(SessionPendingTable.id, input.id), eq(SessionPendingTable.session_id, input.sessionID)))
|
||||||
|
|
@ -361,6 +370,17 @@ export const projectPromoted = Effect.fn("SessionPending.projectPromoted")(funct
|
||||||
return stored
|
return stored
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const projectPromoted = Effect.fn("SessionPending.projectPromoted")(function* (
|
||||||
|
db: DatabaseService,
|
||||||
|
input: {
|
||||||
|
readonly id: SessionMessage.ID
|
||||||
|
readonly sessionID: SessionSchema.ID
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (yield* compaction(db, input.sessionID)) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||||
|
return yield* consumeInput(db, input)
|
||||||
|
})
|
||||||
|
|
||||||
export const projectWithdrawn = Effect.fn("SessionPending.projectWithdrawn")(function* (
|
export const projectWithdrawn = Effect.fn("SessionPending.projectWithdrawn")(function* (
|
||||||
db: DatabaseService,
|
db: DatabaseService,
|
||||||
input: {
|
input: {
|
||||||
|
|
@ -368,16 +388,7 @@ export const projectWithdrawn = Effect.fn("SessionPending.projectWithdrawn")(fun
|
||||||
readonly sessionID: SessionSchema.ID
|
readonly sessionID: SessionSchema.ID
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const deleted = yield* db
|
return yield* consumeInput(db, input)
|
||||||
.delete(SessionPendingTable)
|
|
||||||
.where(and(eq(SessionPendingTable.id, input.id), eq(SessionPendingTable.session_id, input.sessionID)))
|
|
||||||
.returning()
|
|
||||||
.get()
|
|
||||||
.pipe(Effect.orDie)
|
|
||||||
if (!deleted) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
|
||||||
const stored = fromRow(deleted)
|
|
||||||
if (stored.type === "compaction") return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
|
||||||
return stored
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const settleCompaction = Effect.fn("SessionPending.settleCompaction")(function* (
|
export const settleCompaction = Effect.fn("SessionPending.settleCompaction")(function* (
|
||||||
|
|
|
||||||
|
|
@ -494,22 +494,6 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.add(
|
|
||||||
HttpApiEndpoint.post("session.pending.withdraw", "/api/session/:sessionID/pending/:inputID/withdraw", {
|
|
||||||
params: { sessionID: Session.ID, inputID: SessionMessage.ID },
|
|
||||||
success: Schema.Struct({ data: Schema.Boolean }),
|
|
||||||
error: SessionNotFoundError,
|
|
||||||
})
|
|
||||||
.middleware(sessionLocationMiddleware)
|
|
||||||
.annotateMerge(
|
|
||||||
OpenApi.annotations({
|
|
||||||
identifier: "v2.session.pending.withdraw",
|
|
||||||
summary: "Withdraw pending session input",
|
|
||||||
description:
|
|
||||||
"Withdraw one admitted input before promotion. Returns true when the input was withdrawn or had already been withdrawn, and false when it is no longer pending so callers can fall back to reverting projected history.",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.get("session.instructions.entry.list", "/api/session/:sessionID/instructions/entries", {
|
HttpApiEndpoint.get("session.instructions.entry.list", "/api/session/:sessionID/instructions/entries", {
|
||||||
params: { sessionID: Session.ID },
|
params: { sessionID: Session.ID },
|
||||||
|
|
@ -644,6 +628,22 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.add(
|
||||||
|
HttpApiEndpoint.post("session.pending.withdraw", "/api/session/:sessionID/pending/:inputID/withdraw", {
|
||||||
|
params: { sessionID: Session.ID, inputID: SessionMessage.ID },
|
||||||
|
success: Schema.Struct({ data: Schema.Boolean }),
|
||||||
|
error: SessionNotFoundError,
|
||||||
|
})
|
||||||
|
.middleware(sessionLocationMiddleware)
|
||||||
|
.annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
identifier: "v2.session.pending.withdraw",
|
||||||
|
summary: "Withdraw pending session input",
|
||||||
|
description:
|
||||||
|
"Withdraw one admitted input before promotion. Returns true when the input was withdrawn or had already been withdrawn, and false when it is no longer pending so callers can fall back to reverting projected history.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
title: "session",
|
title: "session",
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,12 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
index.set(item.id, messages.length)
|
index.set(item.id, messages.length)
|
||||||
messages.push(item)
|
messages.push(item)
|
||||||
},
|
},
|
||||||
|
reindex(messages: SessionMessageInfo[], index: Map<string, number>, start: number) {
|
||||||
|
for (let position = start; position < messages.length; position++) {
|
||||||
|
const item = messages[position]
|
||||||
|
if (item) index.set(item.id, position)
|
||||||
|
}
|
||||||
|
},
|
||||||
activeAssistant(messages: SessionMessageInfo[]) {
|
activeAssistant(messages: SessionMessageInfo[]) {
|
||||||
const item = messages.findLast((item) => item.type === "assistant" && !item.time.completed)
|
const item = messages.findLast((item) => item.type === "assistant" && !item.time.completed)
|
||||||
return item?.type === "assistant" ? item : undefined
|
return item?.type === "assistant" ? item : undefined
|
||||||
|
|
@ -395,8 +401,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
existing.time.created = event.created
|
existing.time.created = event.created
|
||||||
draft.splice(position, 1)
|
draft.splice(position, 1)
|
||||||
draft.push(existing)
|
draft.push(existing)
|
||||||
index.clear()
|
message.reindex(draft, index, position)
|
||||||
draft.forEach((message, indexValue) => index.set(message.id, indexValue))
|
|
||||||
})
|
})
|
||||||
setStore(
|
setStore(
|
||||||
"session",
|
"session",
|
||||||
|
|
@ -445,15 +450,16 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
const position = index.get(event.data.inputID)
|
const position = index.get(event.data.inputID)
|
||||||
if (position === undefined) return
|
if (position === undefined) return
|
||||||
draft.splice(position, 1)
|
draft.splice(position, 1)
|
||||||
index.clear()
|
index.delete(event.data.inputID)
|
||||||
draft.forEach((message, indexValue) => index.set(message.id, indexValue))
|
message.reindex(draft, index, position)
|
||||||
})
|
})
|
||||||
setStore(
|
if (store.session.input[event.data.sessionID]?.includes(event.data.inputID))
|
||||||
"session",
|
setStore(
|
||||||
"input",
|
"session",
|
||||||
event.data.sessionID,
|
"input",
|
||||||
(store.session.input[event.data.sessionID] ?? []).filter((id) => id !== event.data.inputID),
|
event.data.sessionID,
|
||||||
)
|
(store.session.input[event.data.sessionID] ?? []).filter((id) => id !== event.data.inputID),
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "session.instructions.updated":
|
case "session.instructions.updated":
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ export function DialogMessage(props: {
|
||||||
void undoMessage(client.api, {
|
void undoMessage(client.api, {
|
||||||
sessionID: props.sessionID,
|
sessionID: props.sessionID,
|
||||||
messageID: props.messageID,
|
messageID: props.messageID,
|
||||||
pending: data.session.input.has(props.sessionID, props.messageID),
|
|
||||||
}).catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
|
}).catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -594,7 +594,6 @@ export function Session() {
|
||||||
void undoMessage(client.api, {
|
void undoMessage(client.api, {
|
||||||
sessionID: route.sessionID,
|
sessionID: route.sessionID,
|
||||||
messageID: message.id,
|
messageID: message.id,
|
||||||
pending: data.session.input.has(route.sessionID, message.id),
|
|
||||||
}).catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
|
}).catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
|
||||||
prompt()?.set({
|
prompt()?.set({
|
||||||
...projectedPromptInput(message),
|
...projectedPromptInput(message),
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ import type { OpenCodeClient } from "@opencode-ai/client"
|
||||||
|
|
||||||
export function undoMessage(
|
export function undoMessage(
|
||||||
client: OpenCodeClient,
|
client: OpenCodeClient,
|
||||||
input: { readonly sessionID: string; readonly messageID: string; readonly pending: boolean },
|
input: { readonly sessionID: string; readonly messageID: string },
|
||||||
) {
|
) {
|
||||||
const revert = () => client.session.revert.stage(input).then(() => undefined)
|
const revert = () => client.session.revert.stage(input).then(() => undefined)
|
||||||
if (!input.pending) return revert()
|
|
||||||
return client.session.pending
|
return client.session.pending
|
||||||
.withdraw({ sessionID: input.sessionID, inputID: input.messageID })
|
.withdraw({ sessionID: input.sessionID, inputID: input.messageID })
|
||||||
.then((withdrawn) => (withdrawn ? undefined : revert()))
|
.then((withdrawn) => (withdrawn ? undefined : revert()))
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,9 @@ import { OpenCode } from "@opencode-ai/client"
|
||||||
import { undoMessage } from "../../../src/routes/session/undo"
|
import { undoMessage } from "../../../src/routes/session/undo"
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
{ pending: true, withdrawn: true, expected: ["withdraw"] },
|
{ withdrawn: true, expected: ["withdraw"] },
|
||||||
{ pending: true, withdrawn: false, expected: ["withdraw", "revert"] },
|
{ withdrawn: false, expected: ["withdraw", "revert"] },
|
||||||
{ pending: false, withdrawn: false, expected: ["revert"] },
|
])("routes undo for withdrawn=$withdrawn", async ({ withdrawn, expected }) => {
|
||||||
])("routes undo for pending=$pending withdrawn=$withdrawn", async ({ pending, withdrawn, expected }) => {
|
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
const client = OpenCode.make({
|
const client = OpenCode.make({
|
||||||
baseUrl: "http://localhost:3000",
|
baseUrl: "http://localhost:3000",
|
||||||
|
|
@ -21,7 +20,7 @@ test.each([
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
await undoMessage(client, { sessionID: "ses_test", messageID: "msg_user", pending })
|
await undoMessage(client, { sessionID: "ses_test", messageID: "msg_user" })
|
||||||
|
|
||||||
expect(calls).toEqual([...expected])
|
expect(calls).toEqual([...expected])
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue