core: add session diff API to show file changes between snapshots

This commit is contained in:
Dax Raad
2025-10-20 17:58:48 -04:00
parent f3f21194ae
commit a0a09f421c
6 changed files with 317 additions and 46 deletions

View File

@@ -1,6 +1,12 @@
import { Log } from "../util/log"
import { Bus } from "../bus"
import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi"
import {
describeRoute,
generateSpecs,
validator,
resolver,
openAPIRouteHandler,
} from "hono-openapi"
import { Hono } from "hono"
import { cors } from "hono/cors"
import { streamSSE } from "hono/streaming"
@@ -35,6 +41,7 @@ import { InstanceBootstrap } from "../project/bootstrap"
import { MCP } from "../mcp"
import { Storage } from "../storage/storage"
import type { ContentfulStatusCode } from "hono/utils/http-status"
import { Snapshot } from "@/snapshot"
const ERRORS = {
400: {
@@ -66,7 +73,9 @@ const ERRORS = {
} as const
function errors(...codes: number[]) {
return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]]))
return Object.fromEntries(
codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]]),
)
}
export namespace Server {
@@ -90,7 +99,8 @@ export namespace Server {
else status = 500
return c.json(err.toObject(), { status })
}
const message = err instanceof Error && err.stack ? err.stack : err.toString()
const message =
err instanceof Error && err.stack ? err.stack : err.toString()
return c.json(new NamedError.Unknown({ message }).toObject(), {
status: 500,
})
@@ -184,14 +194,17 @@ export namespace Server {
.get(
"/experimental/tool/ids",
describeRoute({
description: "List all tool IDs (including built-in and dynamically registered)",
description:
"List all tool IDs (including built-in and dynamically registered)",
operationId: "tool.ids",
responses: {
200: {
description: "Tool IDs",
content: {
"application/json": {
schema: resolver(z.array(z.string()).meta({ ref: "ToolIDs" })),
schema: resolver(
z.array(z.string()).meta({ ref: "ToolIDs" }),
),
},
},
},
@@ -205,7 +218,8 @@ export namespace Server {
.get(
"/experimental/tool",
describeRoute({
description: "List tools with JSON schema parameters for a provider/model",
description:
"List tools with JSON schema parameters for a provider/model",
operationId: "tool.list",
responses: {
200: {
@@ -246,7 +260,9 @@ export namespace Server {
id: t.id,
description: t.description,
// Handle both Zod schemas and plain JSON schemas
parameters: (t.parameters as any)?._def ? zodToJsonSchema(t.parameters as any) : t.parameters,
parameters: (t.parameters as any)?._def
? zodToJsonSchema(t.parameters as any)
: t.parameters,
})),
)
},
@@ -608,6 +624,44 @@ export namespace Server {
return c.json(session)
},
)
.get(
"/session/:id/diff",
describeRoute({
description: "Get the diff that resulted from this user message",
operationId: "session.diff",
responses: {
200: {
description: "Successfully retrieved diff",
content: {
"application/json": {
schema: resolver(Snapshot.FileDiff.array()),
},
},
},
},
}),
validator(
"param",
z.object({
id: Session.diff.schema.shape.sessionID,
}),
),
validator(
"query",
z.object({
messageID: Session.diff.schema.shape.messageID,
}),
),
async (c) => {
const query = c.req.valid("query")
const params = c.req.valid("param")
const result = await Session.diff({
sessionID: params.id,
messageID: query.messageID,
})
return c.json(result)
},
)
.delete(
"/session/:id/share",
describeRoute({
@@ -734,7 +788,10 @@ export namespace Server {
),
async (c) => {
const params = c.req.valid("param")
const message = await Session.getMessage({ sessionID: params.id, messageID: params.messageID })
const message = await Session.getMessage({
sessionID: params.id,
messageID: params.messageID,
})
return c.json(message)
},
)
@@ -868,7 +925,10 @@ export namespace Server {
async (c) => {
const id = c.req.valid("param").id
log.info("revert", c.req.valid("json"))
const session = await SessionRevert.revert({ sessionID: id, ...c.req.valid("json") })
const session = await SessionRevert.revert({
sessionID: id,
...c.req.valid("json"),
})
return c.json(session)
},
)
@@ -929,7 +989,11 @@ export namespace Server {
const params = c.req.valid("param")
const id = params.id
const permissionID = params.permissionID
Permission.respond({ sessionID: id, permissionID, response: c.req.valid("json").response })
Permission.respond({
sessionID: id,
permissionID,
response: c.req.valid("json").response,
})
return c.json(true)
},
)
@@ -976,10 +1040,15 @@ export namespace Server {
},
}),
async (c) => {
const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info))
const providers = await Provider.list().then((x) =>
mapValues(x, (item) => item.info),
)
return c.json({
providers: Object.values(providers),
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
default: mapValues(
providers,
(item) => Provider.sort(Object.values(item.models))[0].id,
),
})
},
)
@@ -1174,8 +1243,12 @@ export namespace Server {
validator(
"json",
z.object({
service: z.string().meta({ description: "Service name for the log entry" }),
level: z.enum(["debug", "info", "error", "warn"]).meta({ description: "Log level" }),
service: z
.string()
.meta({ description: "Service name for the log entry" }),
level: z
.enum(["debug", "info", "error", "warn"])
.meta({ description: "Log level" }),
message: z.string().meta({ description: "Log message" }),
extra: z
.record(z.string(), z.any())