refactor: unwrap SessionEntry namespace + self-reexport (#22977)
This commit is contained in:
@@ -2,317 +2,317 @@ import { Schema } from "effect"
|
|||||||
import { SessionEvent } from "./session-event"
|
import { SessionEvent } from "./session-event"
|
||||||
import { produce } from "immer"
|
import { produce } from "immer"
|
||||||
|
|
||||||
export namespace SessionEntry {
|
export const ID = SessionEvent.ID
|
||||||
export const ID = SessionEvent.ID
|
export type ID = Schema.Schema.Type<typeof ID>
|
||||||
export type ID = Schema.Schema.Type<typeof ID>
|
|
||||||
|
|
||||||
const Base = {
|
const Base = {
|
||||||
id: SessionEvent.ID,
|
id: SessionEvent.ID,
|
||||||
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||||
time: Schema.Struct({
|
time: Schema.Struct({
|
||||||
created: Schema.DateTimeUtc,
|
created: Schema.DateTimeUtc,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
export class User extends Schema.Class<User>("Session.Entry.User")({
|
export class User extends Schema.Class<User>("Session.Entry.User")({
|
||||||
...Base,
|
...Base,
|
||||||
text: SessionEvent.Prompt.fields.text,
|
text: SessionEvent.Prompt.fields.text,
|
||||||
files: SessionEvent.Prompt.fields.files,
|
files: SessionEvent.Prompt.fields.files,
|
||||||
agents: SessionEvent.Prompt.fields.agents,
|
agents: SessionEvent.Prompt.fields.agents,
|
||||||
type: Schema.Literal("user"),
|
type: Schema.Literal("user"),
|
||||||
time: Schema.Struct({
|
time: Schema.Struct({
|
||||||
created: Schema.DateTimeUtc,
|
created: Schema.DateTimeUtc,
|
||||||
}),
|
}),
|
||||||
}) {
|
}) {
|
||||||
static fromEvent(event: SessionEvent.Prompt) {
|
static fromEvent(event: SessionEvent.Prompt) {
|
||||||
return new User({
|
return new User({
|
||||||
id: event.id,
|
id: event.id,
|
||||||
type: "user",
|
type: "user",
|
||||||
metadata: event.metadata,
|
metadata: event.metadata,
|
||||||
text: event.text,
|
text: event.text,
|
||||||
files: event.files,
|
files: event.files,
|
||||||
agents: event.agents,
|
agents: event.agents,
|
||||||
time: { created: event.timestamp },
|
time: { created: event.timestamp },
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Synthetic extends Schema.Class<Synthetic>("Session.Entry.Synthetic")({
|
|
||||||
...SessionEvent.Synthetic.fields,
|
|
||||||
...Base,
|
|
||||||
type: Schema.Literal("synthetic"),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class ToolStatePending extends Schema.Class<ToolStatePending>("Session.Entry.ToolState.Pending")({
|
|
||||||
status: Schema.Literal("pending"),
|
|
||||||
input: Schema.String,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class ToolStateRunning extends Schema.Class<ToolStateRunning>("Session.Entry.ToolState.Running")({
|
|
||||||
status: Schema.Literal("running"),
|
|
||||||
input: Schema.Record(Schema.String, Schema.Unknown),
|
|
||||||
title: Schema.String.pipe(Schema.optional),
|
|
||||||
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class ToolStateCompleted extends Schema.Class<ToolStateCompleted>("Session.Entry.ToolState.Completed")({
|
|
||||||
status: Schema.Literal("completed"),
|
|
||||||
input: Schema.Record(Schema.String, Schema.Unknown),
|
|
||||||
output: Schema.String,
|
|
||||||
title: Schema.String,
|
|
||||||
metadata: Schema.Record(Schema.String, Schema.Unknown),
|
|
||||||
attachments: SessionEvent.FileAttachment.pipe(Schema.Array, Schema.optional),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class ToolStateError extends Schema.Class<ToolStateError>("Session.Entry.ToolState.Error")({
|
|
||||||
status: Schema.Literal("error"),
|
|
||||||
input: Schema.Record(Schema.String, Schema.Unknown),
|
|
||||||
error: Schema.String,
|
|
||||||
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
|
|
||||||
export type ToolState = Schema.Schema.Type<typeof ToolState>
|
|
||||||
|
|
||||||
export class AssistantTool extends Schema.Class<AssistantTool>("Session.Entry.Assistant.Tool")({
|
|
||||||
type: Schema.Literal("tool"),
|
|
||||||
callID: Schema.String,
|
|
||||||
name: Schema.String,
|
|
||||||
state: ToolState,
|
|
||||||
time: Schema.Struct({
|
|
||||||
created: Schema.DateTimeUtc,
|
|
||||||
ran: Schema.DateTimeUtc.pipe(Schema.optional),
|
|
||||||
completed: Schema.DateTimeUtc.pipe(Schema.optional),
|
|
||||||
pruned: Schema.DateTimeUtc.pipe(Schema.optional),
|
|
||||||
}),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class AssistantText extends Schema.Class<AssistantText>("Session.Entry.Assistant.Text")({
|
|
||||||
type: Schema.Literal("text"),
|
|
||||||
text: Schema.String,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class AssistantReasoning extends Schema.Class<AssistantReasoning>("Session.Entry.Assistant.Reasoning")({
|
|
||||||
type: Schema.Literal("reasoning"),
|
|
||||||
text: Schema.String,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, AssistantTool])
|
|
||||||
export type AssistantContent = Schema.Schema.Type<typeof AssistantContent>
|
|
||||||
|
|
||||||
export class Assistant extends Schema.Class<Assistant>("Session.Entry.Assistant")({
|
|
||||||
...Base,
|
|
||||||
type: Schema.Literal("assistant"),
|
|
||||||
content: AssistantContent.pipe(Schema.Array),
|
|
||||||
cost: Schema.Number.pipe(Schema.optional),
|
|
||||||
tokens: Schema.Struct({
|
|
||||||
input: Schema.Number,
|
|
||||||
output: Schema.Number,
|
|
||||||
reasoning: Schema.Number,
|
|
||||||
cache: Schema.Struct({
|
|
||||||
read: Schema.Number,
|
|
||||||
write: Schema.Number,
|
|
||||||
}),
|
|
||||||
}).pipe(Schema.optional),
|
|
||||||
error: Schema.String.pipe(Schema.optional),
|
|
||||||
time: Schema.Struct({
|
|
||||||
created: Schema.DateTimeUtc,
|
|
||||||
completed: Schema.DateTimeUtc.pipe(Schema.optional),
|
|
||||||
}),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export class Compaction extends Schema.Class<Compaction>("Session.Entry.Compaction")({
|
|
||||||
...SessionEvent.Compacted.fields,
|
|
||||||
type: Schema.Literal("compaction"),
|
|
||||||
...Base,
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export const Entry = Schema.Union([User, Synthetic, Assistant, Compaction])
|
|
||||||
|
|
||||||
export type Entry = Schema.Schema.Type<typeof Entry>
|
|
||||||
|
|
||||||
export type Type = Entry["type"]
|
|
||||||
|
|
||||||
export type History = {
|
|
||||||
entries: Entry[]
|
|
||||||
pending: Entry[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function step(old: History, event: SessionEvent.Event): History {
|
|
||||||
return produce(old, (draft) => {
|
|
||||||
const lastAssistant = draft.entries.findLast((x) => x.type === "assistant")
|
|
||||||
const pendingAssistant = lastAssistant && !lastAssistant.time.completed ? lastAssistant : undefined
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case "prompt": {
|
|
||||||
if (pendingAssistant) {
|
|
||||||
// @ts-expect-error
|
|
||||||
draft.pending.push(User.fromEvent(event))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// @ts-expect-error
|
|
||||||
draft.entries.push(User.fromEvent(event))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "step.started": {
|
|
||||||
if (pendingAssistant) pendingAssistant.time.completed = event.timestamp
|
|
||||||
draft.entries.push({
|
|
||||||
id: event.id,
|
|
||||||
type: "assistant",
|
|
||||||
time: {
|
|
||||||
created: event.timestamp,
|
|
||||||
},
|
|
||||||
content: [],
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "text.started": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
pendingAssistant.content.push({
|
|
||||||
type: "text",
|
|
||||||
text: "",
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "text.delta": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
const match = pendingAssistant.content.findLast((x) => x.type === "text")
|
|
||||||
if (match) match.text += event.delta
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "text.ended": {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "tool.input.started": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
pendingAssistant.content.push({
|
|
||||||
type: "tool",
|
|
||||||
callID: event.callID,
|
|
||||||
name: event.name,
|
|
||||||
time: {
|
|
||||||
created: event.timestamp,
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
status: "pending",
|
|
||||||
input: "",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "tool.input.delta": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
|
|
||||||
// oxlint-disable-next-line no-base-to-string -- event.delta is a Schema.String (runtime string)
|
|
||||||
if (match) match.state.input += event.delta
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "tool.input.ended": {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "tool.called": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
|
|
||||||
if (match) {
|
|
||||||
match.time.ran = event.timestamp
|
|
||||||
match.state = {
|
|
||||||
status: "running",
|
|
||||||
input: event.input,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "tool.success": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
|
|
||||||
if (match && match.state.status === "running") {
|
|
||||||
match.state = {
|
|
||||||
status: "completed",
|
|
||||||
input: match.state.input,
|
|
||||||
output: event.output ?? "",
|
|
||||||
title: event.title,
|
|
||||||
metadata: event.metadata ?? {},
|
|
||||||
// @ts-expect-error
|
|
||||||
attachments: event.attachments ?? [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "tool.error": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
|
|
||||||
if (match && match.state.status === "running") {
|
|
||||||
match.state = {
|
|
||||||
status: "error",
|
|
||||||
error: event.error,
|
|
||||||
input: match.state.input,
|
|
||||||
metadata: event.metadata ?? {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "reasoning.started": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
pendingAssistant.content.push({
|
|
||||||
type: "reasoning",
|
|
||||||
text: "",
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "reasoning.delta": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
const match = pendingAssistant.content.findLast((x) => x.type === "reasoning")
|
|
||||||
if (match) match.text += event.delta
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "reasoning.ended": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
const match = pendingAssistant.content.findLast((x) => x.type === "reasoning")
|
|
||||||
if (match) match.text = event.text
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "step.ended": {
|
|
||||||
if (!pendingAssistant) break
|
|
||||||
pendingAssistant.time.completed = event.timestamp
|
|
||||||
pendingAssistant.cost = event.cost
|
|
||||||
pendingAssistant.tokens = event.tokens
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
export interface Interface {
|
|
||||||
readonly decode: (row: typeof SessionEntryTable.$inferSelect) => Entry
|
|
||||||
readonly fromSession: (sessionID: SessionID) => Effect.Effect<Entry[], never>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionEntry") {}
|
|
||||||
|
|
||||||
export const layer: Layer.Layer<Service, never, never> = Layer.effect(
|
|
||||||
Service,
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const decodeEntry = Schema.decodeUnknownSync(Entry)
|
|
||||||
|
|
||||||
const decode: (typeof Service.Service)["decode"] = (row) => decodeEntry({ ...row, id: row.id, type: row.type })
|
|
||||||
|
|
||||||
const fromSession = Effect.fn("SessionEntry.fromSession")(function* (sessionID: SessionID) {
|
|
||||||
return Database.use((db) =>
|
|
||||||
db
|
|
||||||
.select()
|
|
||||||
.from(SessionEntryTable)
|
|
||||||
.where(eq(SessionEntryTable.session_id, sessionID))
|
|
||||||
.orderBy(SessionEntryTable.id)
|
|
||||||
.all()
|
|
||||||
.map((row) => decode(row)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return Service.of({
|
|
||||||
decode,
|
|
||||||
fromSession,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Synthetic extends Schema.Class<Synthetic>("Session.Entry.Synthetic")({
|
||||||
|
...SessionEvent.Synthetic.fields,
|
||||||
|
...Base,
|
||||||
|
type: Schema.Literal("synthetic"),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class ToolStatePending extends Schema.Class<ToolStatePending>("Session.Entry.ToolState.Pending")({
|
||||||
|
status: Schema.Literal("pending"),
|
||||||
|
input: Schema.String,
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class ToolStateRunning extends Schema.Class<ToolStateRunning>("Session.Entry.ToolState.Running")({
|
||||||
|
status: Schema.Literal("running"),
|
||||||
|
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||||
|
title: Schema.String.pipe(Schema.optional),
|
||||||
|
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class ToolStateCompleted extends Schema.Class<ToolStateCompleted>("Session.Entry.ToolState.Completed")({
|
||||||
|
status: Schema.Literal("completed"),
|
||||||
|
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||||
|
output: Schema.String,
|
||||||
|
title: Schema.String,
|
||||||
|
metadata: Schema.Record(Schema.String, Schema.Unknown),
|
||||||
|
attachments: SessionEvent.FileAttachment.pipe(Schema.Array, Schema.optional),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class ToolStateError extends Schema.Class<ToolStateError>("Session.Entry.ToolState.Error")({
|
||||||
|
status: Schema.Literal("error"),
|
||||||
|
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||||
|
error: Schema.String,
|
||||||
|
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
|
||||||
|
export type ToolState = Schema.Schema.Type<typeof ToolState>
|
||||||
|
|
||||||
|
export class AssistantTool extends Schema.Class<AssistantTool>("Session.Entry.Assistant.Tool")({
|
||||||
|
type: Schema.Literal("tool"),
|
||||||
|
callID: Schema.String,
|
||||||
|
name: Schema.String,
|
||||||
|
state: ToolState,
|
||||||
|
time: Schema.Struct({
|
||||||
|
created: Schema.DateTimeUtc,
|
||||||
|
ran: Schema.DateTimeUtc.pipe(Schema.optional),
|
||||||
|
completed: Schema.DateTimeUtc.pipe(Schema.optional),
|
||||||
|
pruned: Schema.DateTimeUtc.pipe(Schema.optional),
|
||||||
|
}),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class AssistantText extends Schema.Class<AssistantText>("Session.Entry.Assistant.Text")({
|
||||||
|
type: Schema.Literal("text"),
|
||||||
|
text: Schema.String,
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class AssistantReasoning extends Schema.Class<AssistantReasoning>("Session.Entry.Assistant.Reasoning")({
|
||||||
|
type: Schema.Literal("reasoning"),
|
||||||
|
text: Schema.String,
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, AssistantTool])
|
||||||
|
export type AssistantContent = Schema.Schema.Type<typeof AssistantContent>
|
||||||
|
|
||||||
|
export class Assistant extends Schema.Class<Assistant>("Session.Entry.Assistant")({
|
||||||
|
...Base,
|
||||||
|
type: Schema.Literal("assistant"),
|
||||||
|
content: AssistantContent.pipe(Schema.Array),
|
||||||
|
cost: Schema.Number.pipe(Schema.optional),
|
||||||
|
tokens: Schema.Struct({
|
||||||
|
input: Schema.Number,
|
||||||
|
output: Schema.Number,
|
||||||
|
reasoning: Schema.Number,
|
||||||
|
cache: Schema.Struct({
|
||||||
|
read: Schema.Number,
|
||||||
|
write: Schema.Number,
|
||||||
|
}),
|
||||||
|
}).pipe(Schema.optional),
|
||||||
|
error: Schema.String.pipe(Schema.optional),
|
||||||
|
time: Schema.Struct({
|
||||||
|
created: Schema.DateTimeUtc,
|
||||||
|
completed: Schema.DateTimeUtc.pipe(Schema.optional),
|
||||||
|
}),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class Compaction extends Schema.Class<Compaction>("Session.Entry.Compaction")({
|
||||||
|
...SessionEvent.Compacted.fields,
|
||||||
|
type: Schema.Literal("compaction"),
|
||||||
|
...Base,
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export const Entry = Schema.Union([User, Synthetic, Assistant, Compaction])
|
||||||
|
|
||||||
|
export type Entry = Schema.Schema.Type<typeof Entry>
|
||||||
|
|
||||||
|
export type Type = Entry["type"]
|
||||||
|
|
||||||
|
export type History = {
|
||||||
|
entries: Entry[]
|
||||||
|
pending: Entry[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function step(old: History, event: SessionEvent.Event): History {
|
||||||
|
return produce(old, (draft) => {
|
||||||
|
const lastAssistant = draft.entries.findLast((x) => x.type === "assistant")
|
||||||
|
const pendingAssistant = lastAssistant && !lastAssistant.time.completed ? lastAssistant : undefined
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case "prompt": {
|
||||||
|
if (pendingAssistant) {
|
||||||
|
// @ts-expect-error
|
||||||
|
draft.pending.push(User.fromEvent(event))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// @ts-expect-error
|
||||||
|
draft.entries.push(User.fromEvent(event))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "step.started": {
|
||||||
|
if (pendingAssistant) pendingAssistant.time.completed = event.timestamp
|
||||||
|
draft.entries.push({
|
||||||
|
id: event.id,
|
||||||
|
type: "assistant",
|
||||||
|
time: {
|
||||||
|
created: event.timestamp,
|
||||||
|
},
|
||||||
|
content: [],
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "text.started": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
pendingAssistant.content.push({
|
||||||
|
type: "text",
|
||||||
|
text: "",
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "text.delta": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
const match = pendingAssistant.content.findLast((x) => x.type === "text")
|
||||||
|
if (match) match.text += event.delta
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "text.ended": {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "tool.input.started": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
pendingAssistant.content.push({
|
||||||
|
type: "tool",
|
||||||
|
callID: event.callID,
|
||||||
|
name: event.name,
|
||||||
|
time: {
|
||||||
|
created: event.timestamp,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
status: "pending",
|
||||||
|
input: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "tool.input.delta": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
|
||||||
|
// oxlint-disable-next-line no-base-to-string -- event.delta is a Schema.String (runtime string)
|
||||||
|
if (match) match.state.input += event.delta
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "tool.input.ended": {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "tool.called": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
|
||||||
|
if (match) {
|
||||||
|
match.time.ran = event.timestamp
|
||||||
|
match.state = {
|
||||||
|
status: "running",
|
||||||
|
input: event.input,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "tool.success": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
|
||||||
|
if (match && match.state.status === "running") {
|
||||||
|
match.state = {
|
||||||
|
status: "completed",
|
||||||
|
input: match.state.input,
|
||||||
|
output: event.output ?? "",
|
||||||
|
title: event.title,
|
||||||
|
metadata: event.metadata ?? {},
|
||||||
|
// @ts-expect-error
|
||||||
|
attachments: event.attachments ?? [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "tool.error": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
const match = pendingAssistant.content.findLast((x) => x.type === "tool")
|
||||||
|
if (match && match.state.status === "running") {
|
||||||
|
match.state = {
|
||||||
|
status: "error",
|
||||||
|
error: event.error,
|
||||||
|
input: match.state.input,
|
||||||
|
metadata: event.metadata ?? {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "reasoning.started": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
pendingAssistant.content.push({
|
||||||
|
type: "reasoning",
|
||||||
|
text: "",
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "reasoning.delta": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
const match = pendingAssistant.content.findLast((x) => x.type === "reasoning")
|
||||||
|
if (match) match.text += event.delta
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "reasoning.ended": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
const match = pendingAssistant.content.findLast((x) => x.type === "reasoning")
|
||||||
|
if (match) match.text = event.text
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "step.ended": {
|
||||||
|
if (!pendingAssistant) break
|
||||||
|
pendingAssistant.time.completed = event.timestamp
|
||||||
|
pendingAssistant.cost = event.cost
|
||||||
|
pendingAssistant.tokens = event.tokens
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
export interface Interface {
|
||||||
|
readonly decode: (row: typeof SessionEntryTable.$inferSelect) => Entry
|
||||||
|
readonly fromSession: (sessionID: SessionID) => Effect.Effect<Entry[], never>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionEntry") {}
|
||||||
|
|
||||||
|
export const layer: Layer.Layer<Service, never, never> = Layer.effect(
|
||||||
|
Service,
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const decodeEntry = Schema.decodeUnknownSync(Entry)
|
||||||
|
|
||||||
|
const decode: (typeof Service.Service)["decode"] = (row) => decodeEntry({ ...row, id: row.id, type: row.type })
|
||||||
|
|
||||||
|
const fromSession = Effect.fn("SessionEntry.fromSession")(function* (sessionID: SessionID) {
|
||||||
|
return Database.use((db) =>
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(SessionEntryTable)
|
||||||
|
.where(eq(SessionEntryTable.session_id, sessionID))
|
||||||
|
.orderBy(SessionEntryTable.id)
|
||||||
|
.all()
|
||||||
|
.map((row) => decode(row)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Service.of({
|
||||||
|
decode,
|
||||||
|
fromSession,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * as SessionEntry from "./session-entry"
|
||||||
|
|||||||
Reference in New Issue
Block a user