fix(tui): distinguish variant switch notices (#35315)
This commit is contained in:
parent
5b44e5bf41
commit
62af66a74f
14 changed files with 98 additions and 21 deletions
|
|
@ -540,7 +540,8 @@ const layer = Layer.effect(
|
|||
const started = yield* Effect.gen(function* () {
|
||||
const shell = yield* Shell.Service
|
||||
return yield* shell.create({ command: input.command, cwd: session.location.directory })
|
||||
}).pipe(Effect.provide(locations.get(session.location)))
|
||||
})
|
||||
.pipe(Effect.provide(locations.get(session.location)))
|
||||
yield* events.publish(
|
||||
SessionEvent.Shell.Started,
|
||||
{
|
||||
|
|
@ -563,8 +564,7 @@ const layer = Layer.effect(
|
|||
.pipe(Effect.catchTag("Shell.NotFoundError", () => Effect.succeed(missingShellOutput())))
|
||||
: missingShellOutput()
|
||||
return { shell: terminal.info, output }
|
||||
})
|
||||
.pipe(Effect.provide(locations.get(session.location)))
|
||||
}).pipe(Effect.provide(locations.get(session.location)))
|
||||
yield* events.publish(SessionEvent.Shell.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
shell: completed.shell,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export type MemoryState = {
|
|||
}
|
||||
|
||||
export interface Adapter {
|
||||
readonly getModel: () => Effect.Effect<SessionMessage.ModelSelected["model"] | undefined, never, never>
|
||||
readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined, never, never>
|
||||
readonly getAssistant: (
|
||||
messageID: SessionMessage.ID,
|
||||
|
|
@ -29,6 +30,15 @@ export function memory(state: MemoryState): Adapter {
|
|||
const latestAssistantIndex = () => state.messages.findLastIndex((message) => message.type === "assistant")
|
||||
|
||||
return {
|
||||
getModel() {
|
||||
return Effect.sync(
|
||||
() =>
|
||||
state.messages.findLast(
|
||||
(message): message is SessionMessage.ModelSelected | SessionMessage.Assistant =>
|
||||
message.type === "model-switched" || message.type === "assistant",
|
||||
)?.model,
|
||||
)
|
||||
},
|
||||
getCurrentAssistant() {
|
||||
return Effect.sync(() => {
|
||||
const index = latestAssistantIndex()
|
||||
|
|
@ -115,15 +125,19 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
)
|
||||
},
|
||||
"session.model.selected": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.ModelSelected.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "model-switched",
|
||||
metadata: event.metadata,
|
||||
model: event.data.model,
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
return Effect.gen(function* () {
|
||||
const previous = yield* adapter.getModel()
|
||||
yield* adapter.appendMessage(
|
||||
SessionMessage.ModelSelected.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "model-switched",
|
||||
metadata: event.metadata,
|
||||
model: event.data.model,
|
||||
previous,
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
"session.moved": () => Effect.void,
|
||||
"session.renamed": () => Effect.void,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { DateTime, Effect, Layer, Schema } from "effect"
|
|||
import { Database } from "../database/database"
|
||||
import { EventV2 } from "../event"
|
||||
import { makeGlobalNode } from "../effect/app-node"
|
||||
import { ModelV2 } from "../model"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionV1 } from "../v1/session"
|
||||
import { WorkspaceTable } from "../control-plane/workspace.sql"
|
||||
|
|
@ -356,6 +357,17 @@ function run(db: DatabaseService, event: MessageEvent) {
|
|||
}
|
||||
const appendMessage = (message: SessionMessage.Message) => insertMessage(db, event, message)
|
||||
const adapter: SessionMessageUpdater.Adapter = {
|
||||
getModel() {
|
||||
return db
|
||||
.select({ model: SessionTable.model })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.get()
|
||||
.pipe(
|
||||
Effect.orDie,
|
||||
Effect.map((row) => (row?.model ? Schema.decodeUnknownSync(ModelV2.Ref)(row.model) : undefined)),
|
||||
)
|
||||
},
|
||||
getCurrentAssistant() {
|
||||
return Effect.gen(function* () {
|
||||
// A newer step supersedes stale incomplete rows; never resume an older assistant projection.
|
||||
|
|
@ -570,13 +582,13 @@ const layer = Layer.effectDiscard(
|
|||
)
|
||||
yield* events.project(SessionEvent.ModelSelected, (event) =>
|
||||
Effect.gen(function* () {
|
||||
yield* run(db, event)
|
||||
yield* db
|
||||
.update(SessionTable)
|
||||
.set({ model: event.data.model, time_updated: DateTime.toEpochMillis(event.created) })
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* run(db, event)
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.Renamed, (event) =>
|
||||
|
|
|
|||
|
|
@ -562,9 +562,9 @@ describe("SessionV2.create", () => {
|
|||
yield* session.switchModel({ sessionID: created.id, model })
|
||||
|
||||
expect(yield* session.get(created.id)).toMatchObject({ model })
|
||||
expect(
|
||||
Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(1), Stream.runCollect)),
|
||||
).toMatchObject([{ type: "session.model.selected", data: { model } }])
|
||||
const events = Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(1), Stream.runCollect))
|
||||
expect(events).toMatchObject([{ type: "session.model.selected" }])
|
||||
expect(events[0]?.data).toEqual({ sessionID: created.id, model })
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const sessionsLayer = AppNodeBuilder.build(SessionV2.node, [[SessionExecution.no
|
|||
const sessionID = SessionV2.ID.make("ses_projector_test")
|
||||
const created = DateTime.makeUnsafe(0)
|
||||
const model = { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") }
|
||||
const previousModel = { ...model, variant: ModelV2.VariantID.make("medium") }
|
||||
const encodeMessage = Schema.encodeSync(SessionMessage.Message)
|
||||
|
||||
const assistantRow = (
|
||||
|
|
@ -238,6 +239,7 @@ describe("SessionProjector", () => {
|
|||
directory: "/project",
|
||||
title: "test",
|
||||
version: "test",
|
||||
model: previousModel,
|
||||
})
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
|
|
@ -337,6 +339,7 @@ describe("SessionProjector", () => {
|
|||
text: "synthetic context",
|
||||
metadata: { source: "projector-test" },
|
||||
})
|
||||
expect(messages.find((message) => message.type === "model-switched")).toMatchObject({ previous: previousModel })
|
||||
expect(messages.find((message) => message.type === "shell")).toMatchObject({
|
||||
shell: { command: "pwd", status: "exited", exit: 0 },
|
||||
output: { output: "/project", truncated: false },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue