feat(session): define explicit fork boundaries
This commit is contained in:
parent
2a85c861e0
commit
fc11ed3838
29 changed files with 821 additions and 386 deletions
|
|
@ -69,7 +69,7 @@ export const DialogFork: Component = () => {
|
|||
const dir = base64Encode(sdk().directory)
|
||||
|
||||
sdk()
|
||||
.api.session.fork({ sessionID, messageID: item.id })
|
||||
.api.session.fork({ sessionID, boundary: { type: "before", messageID: item.id } })
|
||||
.then((forked) => {
|
||||
dialog.close()
|
||||
prompt.set(restored, undefined, { dir, id: forked.id })
|
||||
|
|
|
|||
|
|
@ -244,7 +244,10 @@ export function make(input: { readonly client: OpenCodeClient; readonly connecti
|
|||
return {}
|
||||
},
|
||||
forkSession: async (params) => {
|
||||
const forked = await input.client.session.fork({ sessionID: params.sessionId })
|
||||
const forked = await input.client.session.fork({
|
||||
sessionID: params.sessionId,
|
||||
boundary: { type: "through" },
|
||||
})
|
||||
const state = await attach(forked, forked.location.directory, params.mcpServers ?? [])
|
||||
await replay(state)
|
||||
return { sessionId: state.id, configOptions: configOptions(state) }
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ async function selectSession(input: {
|
|||
return {
|
||||
session: input.fork
|
||||
? await input.client.session
|
||||
.fork({ sessionID: explicit.id }, ...requestOptions(input.signal))
|
||||
.fork({ sessionID: explicit.id, boundary: { type: "through" } }, ...requestOptions(input.signal))
|
||||
.catch((error) => {
|
||||
throw new SessionTargetMutationError(error)
|
||||
})
|
||||
|
|
@ -118,9 +118,11 @@ async function selectSession(input: {
|
|||
if (!selected) return { session: undefined, location }
|
||||
return {
|
||||
session: input.fork
|
||||
? await input.client.session.fork({ sessionID: selected.id }, ...requestOptions(input.signal)).catch((error) => {
|
||||
throw new SessionTargetMutationError(error)
|
||||
})
|
||||
? await input.client.session
|
||||
.fork({ sessionID: selected.id, boundary: { type: "through" } }, ...requestOptions(input.signal))
|
||||
.catch((error) => {
|
||||
throw new SessionTargetMutationError(error)
|
||||
})
|
||||
: selected,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export type Endpoint5_4Input = { readonly sessionID: Session.ID }
|
|||
export type Endpoint5_4Output = void
|
||||
export type SessionRemoveOperation<E = never> = (input: Endpoint5_4Input) => Effect.Effect<Endpoint5_4Output, E>
|
||||
|
||||
export type Endpoint5_5Input = { readonly sessionID: Session.ID; readonly messageID?: SessionMessage.ID | undefined }
|
||||
export type Endpoint5_5Input = { readonly sessionID: Session.ID; readonly boundary: Session.ForkRequestBoundary }
|
||||
export type Endpoint5_5Output = Session.Info
|
||||
export type SessionForkOperation<E = never> = (input: Endpoint5_5Input) => Effect.Effect<Endpoint5_5Output, E>
|
||||
|
||||
|
|
@ -342,8 +342,10 @@ export type Endpoint5_26Output =
|
|||
readonly data: {
|
||||
readonly sessionID: Session.ID
|
||||
readonly parentID: Session.ID
|
||||
readonly parentSeq: number
|
||||
readonly from?: SessionMessage.ID | undefined
|
||||
readonly boundary: Session.ForkBoundary
|
||||
readonly instructions?:
|
||||
| { readonly [x: string & Brand.Brand<"Instruction.Key">]: string & Brand.Brand<"Instruction.Hash"> }
|
||||
| undefined
|
||||
}
|
||||
}
|
||||
| {
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ const Endpoint5_4 = (raw: RawClient["server.session"]) => (input: Endpoint5_4Inp
|
|||
|
||||
const Endpoint5_5 = (raw: RawClient["server.session"]) => (input: Endpoint5_5Input) =>
|
||||
preserveEffect<Endpoint5_5Output>()(
|
||||
raw["session.fork"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"] } }).pipe(
|
||||
raw["session.fork"]({ params: { sessionID: input["sessionID"] }, payload: { boundary: input["boundary"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@ export function make(options: ClientOptions) {
|
|||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/fork`,
|
||||
body: { messageID: input["messageID"] },
|
||||
body: { boundary: input["boundary"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: false,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ export type PermissionEffect = "allow" | "deny" | "ask"
|
|||
|
||||
export type PluginInfo = { id: string }
|
||||
|
||||
export type SessionForkBoundary = { type: "before"; messageID: string } | { type: "through"; messageID: string }
|
||||
|
||||
export type MoneyUSD = number
|
||||
|
||||
export type TokenUsageInfo = {
|
||||
|
|
@ -622,7 +624,7 @@ export type SessionForked = {
|
|||
type: "session.forked"
|
||||
durable: { aggregateID: string; seq: number; version: 2 }
|
||||
location?: LocationRef
|
||||
data: { sessionID: string; parentID: string; parentSeq: number; from?: string }
|
||||
data: { sessionID: string; parentID: string; boundary: SessionForkBoundary; instructions?: { [x: string]: string } }
|
||||
}
|
||||
|
||||
export type SessionInputPromoted = {
|
||||
|
|
@ -1748,7 +1750,7 @@ export type PermissionRuleset = Array<PermissionRule>
|
|||
export type SessionInfo = {
|
||||
id: string
|
||||
parentID?: string
|
||||
fork?: { sessionID: string; messageID?: string }
|
||||
fork?: { sessionID: string; boundary: SessionForkBoundary }
|
||||
projectID: string
|
||||
agent?: string
|
||||
model?: ModelRef
|
||||
|
|
@ -2694,7 +2696,9 @@ export type SessionRemoveOutput = void
|
|||
|
||||
export type SessionForkInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly messageID?: { readonly messageID?: string | undefined }["messageID"]
|
||||
readonly boundary: {
|
||||
readonly boundary: { readonly type: "before"; readonly messageID: string } | { readonly type: "through" }
|
||||
}["boundary"]
|
||||
}
|
||||
|
||||
export type SessionForkOutput = { data: SessionInfo }["data"]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"id": "a4ba73b4-21bc-41ab-a415-94e2ca38d798",
|
||||
"id": "db37a97f-9b5e-4c87-be8b-4feace35136c",
|
||||
"prevIds": [
|
||||
"5f0a1db8-d4bf-42c3-becb-96b46fe66bed"
|
||||
"a4ba73b4-21bc-41ab-a415-94e2ca38d798"
|
||||
],
|
||||
"ddl": [
|
||||
{
|
||||
|
|
@ -1266,17 +1266,7 @@
|
|||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "fork_message_id",
|
||||
"entityType": "columns",
|
||||
"table": "session"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "fork_seq",
|
||||
"name": "fork_boundary",
|
||||
"entityType": "columns",
|
||||
"table": "session"
|
||||
},
|
||||
|
|
|
|||
1
packages/core/src/database/migration.gen.ts
generated
1
packages/core/src/database/migration.gen.ts
generated
|
|
@ -57,5 +57,6 @@ export const migrations = (
|
|||
import("./migration/20260716020354_kv"),
|
||||
import("./migration/20260722011141_delete_tool_progress_events"),
|
||||
import("./migration/20260722170000_canonical_tool_results"),
|
||||
import("./migration/20260729022634_session_fork_boundary"),
|
||||
])
|
||||
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import { Effect } from "effect"
|
||||
import type { DatabaseMigration } from "../migration"
|
||||
|
||||
export default {
|
||||
id: "20260729022634_session_fork_boundary",
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
yield* tx.run(`ALTER TABLE \`session\` ADD \`fork_boundary\` text;`)
|
||||
yield* tx.run(`ALTER TABLE \`session\` DROP COLUMN \`fork_message_id\`;`)
|
||||
yield* tx.run(`ALTER TABLE \`session\` DROP COLUMN \`fork_seq\`;`)
|
||||
})
|
||||
},
|
||||
} satisfies DatabaseMigration.Migration
|
||||
|
|
@ -213,8 +213,7 @@ export default {
|
|||
\`workspace_id\` text,
|
||||
\`parent_id\` text,
|
||||
\`fork_session_id\` text,
|
||||
\`fork_message_id\` text,
|
||||
\`fork_seq\` integer,
|
||||
\`fork_boundary\` text,
|
||||
\`slug\` text NOT NULL,
|
||||
\`directory\` text NOT NULL,
|
||||
\`path\` text,
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ import { fromRow } from "./session/info"
|
|||
import { SessionRunner } from "./session/runner/index"
|
||||
import { SessionStore } from "./session/store"
|
||||
import { SessionExecution } from "./session/execution"
|
||||
import { MessageDecodeError, NotFoundError } from "./session/error"
|
||||
import { ForkEmptyError, MessageDecodeError, NotFoundError } from "./session/error"
|
||||
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { LocationServiceMap } from "./location-service-map"
|
||||
import { SessionEvent } from "./session/event"
|
||||
import { SessionPending } from "./session/pending"
|
||||
import { InstructionState } from "./session/instruction-state"
|
||||
import { SessionGenerate } from "./session/generate"
|
||||
import { Snapshot } from "./snapshot"
|
||||
import { SessionRevert } from "./session/revert"
|
||||
|
|
@ -106,7 +107,7 @@ type CompactInput = {
|
|||
|
||||
type ForkInput = {
|
||||
sessionID: SessionSchema.ID
|
||||
messageID?: SessionMessage.ID
|
||||
boundary: Session.ForkRequestBoundary
|
||||
}
|
||||
|
||||
export class OperationUnavailableError extends Schema.TaggedErrorClass<OperationUnavailableError>()(
|
||||
|
|
@ -181,7 +182,9 @@ export interface Interface {
|
|||
readonly data: SessionSchema.Info[]
|
||||
}>
|
||||
readonly create: (input: CreateInput) => Effect.Effect<SessionSchema.Info, NotFoundError>
|
||||
readonly fork: (input: ForkInput) => Effect.Effect<SessionSchema.Info, NotFoundError | MessageNotFoundError>
|
||||
readonly fork: (
|
||||
input: ForkInput,
|
||||
) => Effect.Effect<SessionSchema.Info, NotFoundError | MessageNotFoundError | ForkEmptyError>
|
||||
readonly get: (sessionID: SessionSchema.ID) => Effect.Effect<SessionSchema.Info, NotFoundError>
|
||||
readonly remove: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError>
|
||||
readonly messages: (input: {
|
||||
|
|
@ -395,25 +398,33 @@ const layer = Layer.effect(
|
|||
}),
|
||||
fork: Effect.fn("Session.fork")(function* (input) {
|
||||
const parent = yield* result.get(input.sessionID)
|
||||
const boundary = input.messageID
|
||||
? yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
.where(
|
||||
and(eq(SessionMessageTable.session_id, input.sessionID), eq(SessionMessageTable.id, input.messageID)),
|
||||
)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
: undefined
|
||||
if (input.messageID && !boundary)
|
||||
return yield* new MessageNotFoundError({ sessionID: input.sessionID, messageID: input.messageID })
|
||||
const boundary = yield* db
|
||||
.select({ id: SessionMessageTable.id, seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionMessageTable.session_id, input.sessionID),
|
||||
input.boundary.type === "before" ? eq(SessionMessageTable.id, input.boundary.messageID) : undefined,
|
||||
),
|
||||
)
|
||||
.orderBy(desc(SessionMessageTable.seq))
|
||||
.limit(1)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!boundary && input.boundary.type === "before")
|
||||
return yield* new MessageNotFoundError({
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.boundary.messageID,
|
||||
})
|
||||
if (!boundary) return yield* new ForkEmptyError({ sessionID: input.sessionID })
|
||||
const sessionID = SessionSchema.ID.create()
|
||||
const parentSeq = boundary ? boundary.seq - 1 : yield* Bus.latestSequence(db, parent.id)
|
||||
const instructionThrough =
|
||||
input.boundary.type === "before" ? boundary.seq - 1 : yield* Bus.latestSequence(db, parent.id)
|
||||
yield* bus.publish(SessionEvent.Forked, {
|
||||
sessionID,
|
||||
parentID: parent.id,
|
||||
parentSeq,
|
||||
from: input.messageID,
|
||||
boundary: { ...input.boundary, messageID: boundary.id },
|
||||
instructions: yield* InstructionState.valuesAt(db, parent.id, instructionThrough),
|
||||
})
|
||||
return yield* result.get(sessionID).pipe(Effect.orDie)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Ses
|
|||
sessionID: SessionSchema.ID,
|
||||
}) {}
|
||||
|
||||
export class ForkEmptyError extends Schema.TaggedErrorClass<ForkEmptyError>()("Session.ForkEmptyError", {
|
||||
sessionID: SessionSchema.ID,
|
||||
}) {
|
||||
override get message() {
|
||||
return `Cannot fork empty session: ${this.sessionID}`
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageDecodeError extends Schema.TaggedErrorClass<MessageDecodeError>()("Session.MessageDecodeError", {
|
||||
sessionID: SessionSchema.ID,
|
||||
messageID: SessionMessage.ID,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { AbsolutePath, RelativePath } from "../schema"
|
|||
import { Workspace } from "../workspace"
|
||||
import { SessionSchema } from "./schema"
|
||||
import { SessionTable } from "./sql"
|
||||
import { SessionMessage } from "./message"
|
||||
import { PersistedRevert } from "@opencode-ai/schema/session-revert"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
|
||||
|
|
@ -20,12 +19,13 @@ export function fromRow(row: typeof SessionTable.$inferSelect): SessionSchema.In
|
|||
projectID: Project.ID.make(row.project_id),
|
||||
title: row.title,
|
||||
parentID: row.parent_id ? SessionSchema.ID.make(row.parent_id) : undefined,
|
||||
fork: row.fork_session_id
|
||||
? {
|
||||
sessionID: SessionSchema.ID.make(row.fork_session_id),
|
||||
messageID: row.fork_message_id ? SessionMessage.ID.make(row.fork_message_id) : undefined,
|
||||
}
|
||||
: undefined,
|
||||
fork:
|
||||
row.fork_session_id && row.fork_boundary
|
||||
? {
|
||||
sessionID: SessionSchema.ID.make(row.fork_session_id),
|
||||
boundary: row.fork_boundary,
|
||||
}
|
||||
: undefined,
|
||||
agent: row.agent ? Agent.ID.make(row.agent) : undefined,
|
||||
model: row.model
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ import { SessionEvent } from "./event"
|
|||
import { SessionMessage } from "./message"
|
||||
import { Event } from "@opencode-ai/schema/event"
|
||||
import { SessionSchema } from "./schema"
|
||||
import { InstructionBlobTable, InstructionStateTable, SessionTable } from "./sql"
|
||||
import { InstructionBlobTable, InstructionStateTable } from "./sql"
|
||||
|
||||
type DatabaseService = Database.Interface["db"]
|
||||
|
||||
const decodeInstructionsUpdated = Schema.decodeUnknownSync(SessionEvent.InstructionsUpdated.data)
|
||||
const decodeForked = Schema.decodeUnknownSync(SessionEvent.Forked.data)
|
||||
|
||||
export interface Observation extends Instructions.Admission {
|
||||
readonly sessionID: SessionSchema.ID
|
||||
|
|
@ -94,6 +95,26 @@ export const apply = Effect.fn("InstructionState.apply")(function* (
|
|||
.pipe(Effect.orDie)
|
||||
})
|
||||
|
||||
export const initialize = Effect.fn("InstructionState.initialize")(function* (
|
||||
db: DatabaseService,
|
||||
sessionID: SessionSchema.ID,
|
||||
seq: number,
|
||||
values: Instructions.Values,
|
||||
) {
|
||||
yield* db
|
||||
.insert(InstructionStateTable)
|
||||
.values({
|
||||
session_id: sessionID,
|
||||
epoch_start: seq,
|
||||
through_seq: seq,
|
||||
initial_values: values,
|
||||
current_values: values,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
})
|
||||
|
||||
export const advanceEpoch = Effect.fn("InstructionState.advanceEpoch")(function* (
|
||||
db: DatabaseService,
|
||||
sessionID: SessionSchema.ID,
|
||||
|
|
@ -255,6 +276,14 @@ const stateFromEvents = Effect.fnUntraced(function* (db: DatabaseService, sessio
|
|||
return folded ? foldedState(sessionID, folded) : undefined
|
||||
})
|
||||
|
||||
export const valuesAt = Effect.fn("InstructionState.valuesAt")(function* (
|
||||
db: DatabaseService,
|
||||
sessionID: SessionSchema.ID,
|
||||
through: number,
|
||||
) {
|
||||
return fold(yield* instructionEvents(db, sessionID, through))?.current
|
||||
})
|
||||
|
||||
const latestRelevantSequence = Effect.fnUntraced(function* (db: DatabaseService, sessionID: SessionSchema.ID) {
|
||||
return yield* db
|
||||
.select({ seq: EventTable.seq })
|
||||
|
|
@ -324,15 +353,23 @@ const revertedEventType = Bus.versionedType(
|
|||
SessionEvent.RevertEvent.Committed.type,
|
||||
SessionEvent.RevertEvent.Committed.durable.version,
|
||||
)
|
||||
const relevantEventTypes = [instructionEventType, compactionEventType, movedEventType, revertedEventType]
|
||||
const forkedEventType = Bus.versionedType(SessionEvent.Forked.type, SessionEvent.Forked.durable.version)
|
||||
const relevantEventTypes = [
|
||||
forkedEventType,
|
||||
instructionEventType,
|
||||
compactionEventType,
|
||||
movedEventType,
|
||||
revertedEventType,
|
||||
]
|
||||
|
||||
type InstructionEventRow = typeof EventTable.$inferSelect
|
||||
|
||||
const instructionEvents = Effect.fnUntraced(function* (
|
||||
db: DatabaseService,
|
||||
sessionID: SessionSchema.ID,
|
||||
through?: number,
|
||||
): Effect.fn.Return<ReadonlyArray<InstructionEventRow>> {
|
||||
return yield* eventRows(db, sessionID, relevantEventTypes)
|
||||
return yield* eventRows(db, sessionID, relevantEventTypes, undefined, through)
|
||||
})
|
||||
|
||||
const instructionUpdatesAfter = Effect.fnUntraced(function* (
|
||||
|
|
@ -348,48 +385,22 @@ const eventRows = Effect.fnUntraced(function* (
|
|||
sessionID: SessionSchema.ID,
|
||||
types: ReadonlyArray<string>,
|
||||
after?: number,
|
||||
): Effect.fn.Return<ReadonlyArray<InstructionEventRow>> {
|
||||
const segments = (yield* lineage(db, sessionID)).filter(
|
||||
(segment) => after === undefined || segment.through === undefined || segment.through > after,
|
||||
)
|
||||
return (yield* Effect.forEach(segments, (segment) =>
|
||||
db
|
||||
.select()
|
||||
.from(EventTable)
|
||||
.where(
|
||||
and(
|
||||
eq(EventTable.aggregate_id, segment.sessionID),
|
||||
inArray(EventTable.type, types),
|
||||
segment.through === undefined ? undefined : lte(EventTable.seq, segment.through),
|
||||
after === undefined ? undefined : gt(EventTable.seq, after),
|
||||
),
|
||||
)
|
||||
.orderBy(asc(EventTable.seq))
|
||||
.all()
|
||||
.pipe(Effect.orDie),
|
||||
)).flat()
|
||||
})
|
||||
|
||||
const lineage = Effect.fnUntraced(function* (
|
||||
db: DatabaseService,
|
||||
sessionID: SessionSchema.ID,
|
||||
through?: number,
|
||||
): Effect.fn.Return<ReadonlyArray<{ readonly sessionID: SessionSchema.ID; readonly through?: number }>> {
|
||||
const session = yield* db
|
||||
.select({ parentID: SessionTable.fork_session_id, forkSeq: SessionTable.fork_seq })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.get()
|
||||
): Effect.fn.Return<ReadonlyArray<InstructionEventRow>> {
|
||||
return yield* db
|
||||
.select()
|
||||
.from(EventTable)
|
||||
.where(
|
||||
and(
|
||||
eq(EventTable.aggregate_id, sessionID),
|
||||
inArray(EventTable.type, types),
|
||||
after === undefined ? undefined : gt(EventTable.seq, after),
|
||||
through === undefined ? undefined : lte(EventTable.seq, through),
|
||||
),
|
||||
)
|
||||
.orderBy(asc(EventTable.seq))
|
||||
.all()
|
||||
.pipe(Effect.orDie)
|
||||
const inherited =
|
||||
session?.parentID && session.forkSeq !== null
|
||||
? yield* lineage(
|
||||
db,
|
||||
session.parentID,
|
||||
through === undefined ? session.forkSeq : Math.min(session.forkSeq, through),
|
||||
)
|
||||
: []
|
||||
return [...inherited, { sessionID, ...(through === undefined ? {} : { through }) }]
|
||||
})
|
||||
|
||||
function fold(rows: ReadonlyArray<InstructionEventRow>) {
|
||||
|
|
@ -402,6 +413,12 @@ function fold(rows: ReadonlyArray<InstructionEventRow>) {
|
|||
}
|
||||
| undefined
|
||||
>((state, row) => {
|
||||
if (row.type === forkedEventType) {
|
||||
const instructions = decodeForked(row.data).instructions
|
||||
return instructions
|
||||
? { epochStart: row.seq, throughSeq: row.seq, initial: instructions, current: instructions }
|
||||
: undefined
|
||||
}
|
||||
if (row.type === movedEventType || row.type === revertedEventType) return undefined
|
||||
if (row.type === compactionEventType)
|
||||
return state
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as SessionProjector from "./projector"
|
||||
|
||||
import { and, asc, desc, eq, gt, gte, inArray, lt, sql } from "drizzle-orm"
|
||||
import { and, asc, desc, eq, gt, gte, inArray, lt, lte, sql } from "drizzle-orm"
|
||||
import { DateTime, Effect, Layer, Schema, Stream } from "effect"
|
||||
import { Database } from "../database/database"
|
||||
import { Bus } from "../bus"
|
||||
|
|
@ -174,25 +174,28 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
|||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!parent) return yield* Effect.die(new Error(`Fork parent session not found: ${event.data.parentID}`))
|
||||
const boundary = event.data.from
|
||||
? yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
.where(
|
||||
and(eq(SessionMessageTable.session_id, event.data.parentID), eq(SessionMessageTable.id, event.data.from)),
|
||||
)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
: undefined
|
||||
if (event.data.from && !boundary)
|
||||
return yield* Effect.die(new Error(`Fork boundary message not found: ${event.data.from}`))
|
||||
const boundary = yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionMessageTable.session_id, event.data.parentID),
|
||||
eq(SessionMessageTable.id, event.data.boundary.messageID),
|
||||
),
|
||||
)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!boundary)
|
||||
return yield* Effect.die(new Error(`Fork boundary message not found: ${event.data.boundary.messageID}`))
|
||||
const copied = yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionMessageTable.session_id, event.data.parentID),
|
||||
boundary === undefined ? undefined : lt(SessionMessageTable.seq, boundary.seq),
|
||||
event.data.boundary.type === "before"
|
||||
? lt(SessionMessageTable.seq, boundary.seq)
|
||||
: lte(SessionMessageTable.seq, boundary.seq),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(SessionMessageTable.seq))
|
||||
|
|
@ -207,8 +210,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
|||
id: event.data.sessionID,
|
||||
parent_id: null,
|
||||
fork_session_id: event.data.parentID,
|
||||
fork_message_id: event.data.from,
|
||||
fork_seq: event.data.parentSeq,
|
||||
fork_boundary: event.data.boundary,
|
||||
project_id: parent.project_id,
|
||||
workspace_id: parent.workspace_id,
|
||||
slug: Slug.create(),
|
||||
|
|
@ -314,8 +316,9 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
|||
|
||||
cursor = rows.at(-1)!.seq
|
||||
}
|
||||
yield* Bus.reserveSequence(db, event.data.sessionID, event.data.parentSeq)
|
||||
yield* InstructionState.rebuild(db, event.data.sessionID)
|
||||
if (copiedSeq !== undefined) yield* Bus.reserveSequence(db, event.data.sessionID, copiedSeq)
|
||||
if (event.data.instructions)
|
||||
yield* InstructionState.initialize(db, event.data.sessionID, event.durable.seq, event.data.instructions)
|
||||
})
|
||||
|
||||
function run(db: DatabaseService, event: MessageEvent) {
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ export const SessionTable = sqliteTable(
|
|||
workspace_id: text().$type<Workspace.ID>(),
|
||||
parent_id: text().$type<SessionSchema.ID>(),
|
||||
fork_session_id: text().$type<SessionSchema.ID>(),
|
||||
fork_message_id: text().$type<SessionMessage.ID>(),
|
||||
fork_seq: integer(),
|
||||
fork_boundary: text({ mode: "json" }).$type<Session.ForkBoundary>(),
|
||||
slug: text().notNull(),
|
||||
directory: directoryColumn().notNull(),
|
||||
path: pathColumn(),
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ describe("Session.create", () => {
|
|||
yield* session.synthetic({ sessionID: parent.id, text: "parent note", resume: false })
|
||||
yield* SessionPending.promote(db, bus, parent.id, "steer")
|
||||
|
||||
const forked = yield* session.fork({ sessionID: parent.id })
|
||||
const forked = yield* session.fork({ sessionID: parent.id, boundary: { type: "through" } })
|
||||
const parentContext = yield* session.context(parent.id)
|
||||
const forkContext = yield* session.context(forked.id)
|
||||
const history = Array.from(yield* Stream.runCollect(logEvents(session, forked.id)))
|
||||
|
|
@ -252,6 +252,17 @@ describe("Session.create", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects forking an empty session", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* Session.Service
|
||||
const parent = yield* session.create({ location })
|
||||
|
||||
expect(
|
||||
yield* session.fork({ sessionID: parent.id, boundary: { type: "through" } }).pipe(Effect.flip),
|
||||
).toMatchObject({ _tag: "Session.ForkEmptyError", sessionID: parent.id })
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("forks before the selected boundary message", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* Session.Service
|
||||
|
|
@ -286,16 +297,27 @@ describe("Session.create", () => {
|
|||
tokens: { input: 6, output: 3, reasoning: 1, cache: { read: 2, write: 1 } },
|
||||
})
|
||||
|
||||
const forked = yield* session.fork({ sessionID: parent.id, messageID: second.id })
|
||||
const beforeFirst = yield* session.fork({ sessionID: parent.id, messageID: first.id })
|
||||
const complete = yield* session.fork({ sessionID: parent.id })
|
||||
const forked = yield* session.fork({
|
||||
sessionID: parent.id,
|
||||
boundary: { type: "before", messageID: second.id },
|
||||
})
|
||||
const beforeFirst = yield* session.fork({
|
||||
sessionID: parent.id,
|
||||
boundary: { type: "before", messageID: first.id },
|
||||
})
|
||||
const complete = yield* session.fork({ sessionID: parent.id, boundary: { type: "through" } })
|
||||
|
||||
const context = yield* session.context(forked.id)
|
||||
const history = Array.from(yield* Stream.runCollect(logEvents(session, forked.id)))
|
||||
expect(forked.fork).toEqual({ sessionID: parent.id, messageID: second.id })
|
||||
expect(forked.fork).toEqual({
|
||||
sessionID: parent.id,
|
||||
boundary: { type: "before", messageID: second.id },
|
||||
})
|
||||
expect(context).toMatchObject([{ text: "First" }])
|
||||
expect(context[0]?.id).not.toBe(first.id)
|
||||
expect(history[0]).toMatchObject({ data: { from: second.id } })
|
||||
expect(history[0]).toMatchObject({
|
||||
data: { boundary: { type: "before", messageID: second.id } },
|
||||
})
|
||||
expect(forked).toMatchObject({ cost: 0, tokens: { input: 0, output: 0, reasoning: 0 } })
|
||||
expect(yield* session.context(beforeFirst.id)).toEqual([])
|
||||
expect(beforeFirst).toMatchObject({ cost: 0, tokens: { input: 0, output: 0, reasoning: 0 } })
|
||||
|
|
|
|||
|
|
@ -1107,7 +1107,7 @@ describe("SessionRunnerLLM", () => {
|
|||
systemBaseline = "Latest context"
|
||||
yield* runPrompt(session, "Third")
|
||||
|
||||
const forked = yield* session.fork({ sessionID, messageID: second.id })
|
||||
const forked = yield* session.fork({ sessionID, boundary: { type: "before", messageID: second.id } })
|
||||
expect(
|
||||
yield* (yield* Database.Service).db
|
||||
.select()
|
||||
|
|
@ -1115,14 +1115,13 @@ describe("SessionRunnerLLM", () => {
|
|||
.where(eq(InstructionStateTable.session_id, forked.id))
|
||||
.get(),
|
||||
).toMatchObject({
|
||||
initial_values: { "test/context": Instructions.hash("Initial context") },
|
||||
initial_values: { "test/context": Instructions.hash("Changed context") },
|
||||
current_values: { "test/context": Instructions.hash("Changed context") },
|
||||
})
|
||||
yield* session.prompt({ sessionID: forked.id, text: "Forked", resume: false })
|
||||
yield* session.resume(forked.id)
|
||||
|
||||
expect(requests.at(-1)?.system.map((part) => part.text)).toEqual([defaultSystem, "Initial context"])
|
||||
expect(systemTexts(requests.at(-1)!)).toContain("Changed context")
|
||||
expect(requests.at(-1)?.system.map((part) => part.text)).toEqual([defaultSystem, "Changed context"])
|
||||
expect(systemTexts(requests.at(-1)!)).toContain("Latest context")
|
||||
|
||||
const { db } = yield* Database.Service
|
||||
|
|
@ -1151,19 +1150,22 @@ describe("SessionRunnerLLM", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("caps nested fork instruction ancestry at the selected message", () =>
|
||||
it.effect("keeps nested forks self-contained", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
yield* runPrompt(session, "First")
|
||||
systemBaseline = "Changed context"
|
||||
const second = yield* runPrompt(session, "Second")
|
||||
|
||||
const child = yield* session.fork({ sessionID, messageID: second.id })
|
||||
const child = yield* session.fork({ sessionID, boundary: { type: "before", messageID: second.id } })
|
||||
const inheritedFirst = (yield* session.messages({ sessionID: child.id })).find(
|
||||
(message) => message.type === "user" && message.text === "First",
|
||||
)
|
||||
if (!inheritedFirst) return yield* Effect.die(new Error("Nested fork boundary message not found"))
|
||||
const grandchild = yield* session.fork({ sessionID: child.id, messageID: inheritedFirst.id })
|
||||
const grandchild = yield* session.fork({
|
||||
sessionID: child.id,
|
||||
boundary: { type: "before", messageID: inheritedFirst.id },
|
||||
})
|
||||
|
||||
expect(
|
||||
yield* (yield* Database.Service).db
|
||||
|
|
@ -1172,8 +1174,8 @@ describe("SessionRunnerLLM", () => {
|
|||
.where(eq(InstructionStateTable.session_id, grandchild.id))
|
||||
.get(),
|
||||
).toMatchObject({
|
||||
initial_values: { "test/context": Instructions.hash("Initial context") },
|
||||
current_values: { "test/context": Instructions.hash("Initial context") },
|
||||
initial_values: { "test/context": Instructions.hash("Changed context") },
|
||||
current_values: { "test/context": Instructions.hash("Changed context") },
|
||||
})
|
||||
return undefined
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1145,7 +1145,14 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestError1"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1181,7 +1188,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a child session by copying projected history from the parent. When messageID is supplied, copy messages before that boundary.",
|
||||
"description": "Create a child session by copying projected history through or before a message boundary.",
|
||||
"summary": "Fork session",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
|
|
@ -1189,22 +1196,13 @@
|
|||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"messageID": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkRequestBoundary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
|
|
@ -12223,6 +12221,58 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Session.ForkBoundary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"before"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"through"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"Money.USD": {
|
||||
"type": "number"
|
||||
},
|
||||
|
|
@ -12385,17 +12435,13 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkBoundary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionID"
|
||||
"sessionID",
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
|
@ -12595,6 +12641,49 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Session.ForkRequestBoundary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"before"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"through"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"MessageNotFoundError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -12783,14 +12872,6 @@
|
|||
"SessionPending.User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -12828,7 +12909,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -12957,14 +13037,6 @@
|
|||
"SessionPending.Synthetic": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -13002,7 +13074,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -13015,14 +13086,6 @@
|
|||
"SessionPending.Compaction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -13050,7 +13113,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -13705,7 +13767,14 @@
|
|||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -13715,7 +13784,7 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"LLM.ToolContent": {
|
||||
"Tool.Content": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.TextContent"
|
||||
|
|
@ -13741,12 +13810,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -13795,12 +13864,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -14829,27 +14898,27 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"parentSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": -1
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkBoundary"
|
||||
},
|
||||
"from": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
"instructions": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-z0-9][a-z0-9._-]*\\/[a-z0-9][a-z0-9._/-]*$": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^[a-f0-9]{64}$"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionID",
|
||||
"parentID",
|
||||
"parentSeq"
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
|
@ -17114,6 +17183,49 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Tool.FileContent1": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"mime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"uri",
|
||||
"mime"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Tool.Content1": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.TextContent"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.FileContent1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Session.Message.ProviderState8": {
|
||||
"type": "object"
|
||||
},
|
||||
|
|
@ -17197,12 +17309,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -17320,12 +17432,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
|
|||
|
|
@ -207,17 +207,16 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
|||
.add(
|
||||
HttpApiEndpoint.post("session.fork", "/api/session/:sessionID/fork", {
|
||||
params: { sessionID: Session.ID },
|
||||
payload: Schema.Struct({ messageID: SessionMessage.ID.pipe(Schema.optional) }),
|
||||
payload: Schema.Struct({ boundary: Session.ForkRequestBoundary }),
|
||||
success: Schema.Struct({ data: Session.Info }),
|
||||
error: [SessionNotFoundError, MessageNotFoundError],
|
||||
error: [SessionNotFoundError, MessageNotFoundError, InvalidRequestError],
|
||||
})
|
||||
.middleware(sessionLocationMiddleware)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.fork",
|
||||
summary: "Fork session",
|
||||
description:
|
||||
"Create a child session by copying projected history from the parent. When messageID is supplied, copy messages before that boundary.",
|
||||
description: "Create a child session by copying projected history through or before a message boundary.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { Snapshot } from "./snapshot.js"
|
|||
import { TokenUsage } from "./token-usage.js"
|
||||
import { SessionPending } from "./session-pending.js"
|
||||
import { Project } from "./project.js"
|
||||
import { SessionFork } from "./session-fork.js"
|
||||
|
||||
export { FileAttachment }
|
||||
|
||||
|
|
@ -127,8 +128,8 @@ export const Forked = Event.durable({
|
|||
schema: {
|
||||
...Base,
|
||||
parentID: SessionID,
|
||||
parentSeq: Schema.Int.check(Schema.isGreaterThanOrEqualTo(-1)),
|
||||
from: SessionMessage.ID.pipe(optional),
|
||||
boundary: SessionFork.Boundary,
|
||||
instructions: Instruction.Values.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Forked = typeof Forked.Type
|
||||
|
|
|
|||
16
packages/schema/src/session-fork.ts
Normal file
16
packages/schema/src/session-fork.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export * as SessionFork from "./session-fork.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { SessionMessage } from "./session-message.js"
|
||||
|
||||
export const Boundary = Schema.Union([
|
||||
Schema.Struct({ type: Schema.Literal("before"), messageID: SessionMessage.ID }),
|
||||
Schema.Struct({ type: Schema.Literal("through"), messageID: SessionMessage.ID }),
|
||||
]).annotate({ identifier: "Session.ForkBoundary" })
|
||||
export type Boundary = typeof Boundary.Type
|
||||
|
||||
export const RequestBoundary = Schema.Union([
|
||||
Schema.Struct({ type: Schema.Literal("before"), messageID: SessionMessage.ID }),
|
||||
Schema.Struct({ type: Schema.Literal("through") }),
|
||||
]).annotate({ identifier: "Session.ForkRequestBoundary" })
|
||||
export type RequestBoundary = typeof RequestBoundary.Type
|
||||
|
|
@ -8,10 +8,10 @@ import { Project } from "./project.js"
|
|||
import { DateTimeUtcFromMillis, optional, RelativePath } from "./schema.js"
|
||||
import { SessionEvent } from "./session-event.js"
|
||||
import { SessionID } from "./session-id.js"
|
||||
import { SessionMessage } from "./session-message.js"
|
||||
import { Money } from "./money.js"
|
||||
import { TokenUsage } from "./token-usage.js"
|
||||
import { Revert } from "./session-revert.js"
|
||||
import { SessionFork } from "./session-fork.js"
|
||||
|
||||
export const ID = SessionID
|
||||
export type ID = SessionID
|
||||
|
|
@ -19,6 +19,10 @@ export type ID = SessionID
|
|||
export const Event = SessionEvent
|
||||
|
||||
export { Revert }
|
||||
export const ForkBoundary = SessionFork.Boundary
|
||||
export type ForkBoundary = SessionFork.Boundary
|
||||
export const ForkRequestBoundary = SessionFork.RequestBoundary
|
||||
export type ForkRequestBoundary = SessionFork.RequestBoundary
|
||||
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
export const Info = Schema.Struct({
|
||||
|
|
@ -26,8 +30,7 @@ export const Info = Schema.Struct({
|
|||
parentID: ID.pipe(optional),
|
||||
fork: Schema.Struct({
|
||||
sessionID: ID,
|
||||
/** Messages before this exclusive boundary are copied into the fork. */
|
||||
messageID: SessionMessage.ID.pipe(optional),
|
||||
boundary: ForkBoundary,
|
||||
}).pipe(optional),
|
||||
projectID: Project.ID,
|
||||
agent: Agent.ID.pipe(optional),
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||
"session.fork",
|
||||
Effect.fn(function* (ctx) {
|
||||
return {
|
||||
data: yield* session.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID }).pipe(
|
||||
data: yield* session.fork({ sessionID: ctx.params.sessionID, boundary: ctx.payload.boundary }).pipe(
|
||||
Effect.catchTag(
|
||||
"Session.NotFoundError",
|
||||
(error) =>
|
||||
|
|
@ -149,6 +149,10 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||
message: `Message not found: ${error.messageID}`,
|
||||
}),
|
||||
),
|
||||
Effect.catchTag(
|
||||
"Session.ForkEmptyError",
|
||||
(error) => new InvalidRequestError({ message: error.message, kind: "empty_session" }),
|
||||
),
|
||||
),
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -583,7 +583,7 @@ function App(props: { pair?: DialogPairCredentials }) {
|
|||
return
|
||||
}
|
||||
void client.api.session
|
||||
.fork({ sessionID: match })
|
||||
.fork({ sessionID: match, boundary: { type: "through" } })
|
||||
.then((result) => route.navigate({ type: "session", sessionID: result.id, prompt: startupPrompt }))
|
||||
.catch(toast.error)
|
||||
})
|
||||
|
|
@ -596,7 +596,7 @@ function App(props: { pair?: DialogPairCredentials }) {
|
|||
if (forked || !args.sessionID || !args.fork) return
|
||||
forked = true
|
||||
void client.api.session
|
||||
.fork({ sessionID: args.sessionID })
|
||||
.fork({ sessionID: args.sessionID, boundary: { type: "through" } })
|
||||
.then((result) => route.navigate({ type: "session", sessionID: result.id, prompt: startupPrompt }))
|
||||
.catch(toast.error)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,24 +20,28 @@ export function DialogFork(props: { sessionID: string; messageID?: string; onMov
|
|||
|
||||
const fork = async (messageID?: string) => {
|
||||
setPending(true)
|
||||
const result = await client.api.session.fork({ sessionID: props.sessionID, messageID }).catch((error) => {
|
||||
toast.show({ message: errorMessage(error), variant: "error", duration: 5000 })
|
||||
return undefined
|
||||
})
|
||||
const result = await client.api.session
|
||||
.fork({
|
||||
sessionID: props.sessionID,
|
||||
boundary: messageID ? { type: "before", messageID } : { type: "through" },
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.show({ message: errorMessage(error), variant: "error", duration: 5000 })
|
||||
return undefined
|
||||
})
|
||||
if (!result) return dialog.clear()
|
||||
const message = messageID ? data.session.message.get(props.sessionID, messageID) : undefined
|
||||
const prompt = message?.type === "user" ? projectedPromptInput(message) : undefined
|
||||
route.navigate({
|
||||
sessionID: result.id,
|
||||
type: "session",
|
||||
prompt:
|
||||
prompt
|
||||
? {
|
||||
...prompt,
|
||||
agents: prompt.agents ?? [],
|
||||
pasted: [],
|
||||
}
|
||||
: undefined,
|
||||
prompt: prompt
|
||||
? {
|
||||
...prompt,
|
||||
agents: prompt.agents ?? [],
|
||||
pasted: [],
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
dialog.clear()
|
||||
toast.show({ message: "Forked session", variant: "success", duration: 4000 })
|
||||
|
|
@ -75,11 +79,7 @@ export function DialogFork(props: { sessionID: string; messageID?: string; onMov
|
|||
</box>
|
||||
}
|
||||
>
|
||||
<DialogSelect
|
||||
onMove={(option) => props.onMove?.(option.value)}
|
||||
title="Fork session"
|
||||
options={options()}
|
||||
/>
|
||||
<DialogSelect onMove={(option) => props.onMove?.(option.value)} title="Fork session" options={options()} />
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1145,7 +1145,14 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestError1"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1181,7 +1188,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a child session by copying projected history from the parent. When messageID is supplied, copy messages before that boundary.",
|
||||
"description": "Create a child session by copying projected history through or before a message boundary.",
|
||||
"summary": "Fork session",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
|
|
@ -1189,22 +1196,13 @@
|
|||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"messageID": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkRequestBoundary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
|
|
@ -12223,6 +12221,58 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Session.ForkBoundary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"before"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"through"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"Money.USD": {
|
||||
"type": "number"
|
||||
},
|
||||
|
|
@ -12385,17 +12435,13 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkBoundary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionID"
|
||||
"sessionID",
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
|
@ -12595,6 +12641,49 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Session.ForkRequestBoundary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"before"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"through"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"MessageNotFoundError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -12783,14 +12872,6 @@
|
|||
"SessionPending.User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -12828,7 +12909,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -12957,14 +13037,6 @@
|
|||
"SessionPending.Synthetic": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -13002,7 +13074,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -13015,14 +13086,6 @@
|
|||
"SessionPending.Compaction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -13050,7 +13113,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -13705,7 +13767,14 @@
|
|||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -13715,7 +13784,7 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"LLM.ToolContent": {
|
||||
"Tool.Content": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.TextContent"
|
||||
|
|
@ -13741,12 +13810,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -13795,12 +13864,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -14829,27 +14898,27 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"parentSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": -1
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkBoundary"
|
||||
},
|
||||
"from": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
"instructions": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-z0-9][a-z0-9._-]*\\/[a-z0-9][a-z0-9._/-]*$": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^[a-f0-9]{64}$"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionID",
|
||||
"parentID",
|
||||
"parentSeq"
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
|
@ -17114,6 +17183,49 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Tool.FileContent1": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"mime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"uri",
|
||||
"mime"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Tool.Content1": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.TextContent"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.FileContent1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Session.Message.ProviderState8": {
|
||||
"type": "object"
|
||||
},
|
||||
|
|
@ -17197,12 +17309,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -17320,12 +17432,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
|
|||
|
|
@ -1145,7 +1145,14 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestError1"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1181,7 +1188,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"description": "Create a child session by copying projected history from the parent. When messageID is supplied, copy messages before that boundary.",
|
||||
"description": "Create a child session by copying projected history through or before a message boundary.",
|
||||
"summary": "Fork session",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
|
|
@ -1189,22 +1196,13 @@
|
|||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"messageID": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkRequestBoundary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
|
|
@ -12223,6 +12221,58 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Session.ForkBoundary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"before"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"through"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"Money.USD": {
|
||||
"type": "number"
|
||||
},
|
||||
|
|
@ -12385,17 +12435,13 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkBoundary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionID"
|
||||
"sessionID",
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
|
@ -12595,6 +12641,49 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Session.ForkRequestBoundary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"before"
|
||||
]
|
||||
},
|
||||
"messageID": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"messageID"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"through"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"MessageNotFoundError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -12783,14 +12872,6 @@
|
|||
"SessionPending.User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -12828,7 +12909,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -12957,14 +13037,6 @@
|
|||
"SessionPending.Synthetic": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -13002,7 +13074,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -13015,14 +13086,6 @@
|
|||
"SessionPending.Compaction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admittedSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
|
|
@ -13050,7 +13113,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"admittedSeq",
|
||||
"id",
|
||||
"sessionID",
|
||||
"timeCreated",
|
||||
|
|
@ -13705,7 +13767,14 @@
|
|||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -13715,7 +13784,7 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"LLM.ToolContent": {
|
||||
"Tool.Content": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.TextContent"
|
||||
|
|
@ -13741,12 +13810,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -13795,12 +13864,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -14829,27 +14898,27 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"parentSeq": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": -1
|
||||
}
|
||||
]
|
||||
"boundary": {
|
||||
"$ref": "#/components/schemas/Session.ForkBoundary"
|
||||
},
|
||||
"from": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^msg_"
|
||||
"instructions": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-z0-9][a-z0-9._-]*\\/[a-z0-9][a-z0-9._/-]*$": {
|
||||
"type": "string",
|
||||
"allOf": [
|
||||
{
|
||||
"pattern": "^[a-f0-9]{64}$"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionID",
|
||||
"parentID",
|
||||
"parentSeq"
|
||||
"boundary"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
|
@ -17114,6 +17183,49 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Tool.FileContent1": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"mime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"uri",
|
||||
"mime"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Tool.Content1": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.TextContent"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Tool.FileContent1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Session.Message.ProviderState8": {
|
||||
"type": "object"
|
||||
},
|
||||
|
|
@ -17197,12 +17309,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
@ -17320,12 +17432,12 @@
|
|||
"type": "array",
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LLM.ToolContent"
|
||||
"$ref": "#/components/schemas/Tool.Content1"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue