feat(session): filter v2 session list by parent (#35037)
Co-authored-by: Dax <mail@thdxr.com>
This commit is contained in:
co-authored by
Dax
parent
7ebd344fa2
commit
6555df912a
@@ -45,6 +45,7 @@ type Endpoint4_0Input = {
|
|||||||
readonly limit?: Endpoint4_0Request["query"]["limit"]
|
readonly limit?: Endpoint4_0Request["query"]["limit"]
|
||||||
readonly order?: Endpoint4_0Request["query"]["order"]
|
readonly order?: Endpoint4_0Request["query"]["order"]
|
||||||
readonly search?: Endpoint4_0Request["query"]["search"]
|
readonly search?: Endpoint4_0Request["query"]["search"]
|
||||||
|
readonly parentID?: Endpoint4_0Request["query"]["parentID"]
|
||||||
readonly directory?: Endpoint4_0Request["query"]["directory"]
|
readonly directory?: Endpoint4_0Request["query"]["directory"]
|
||||||
readonly project?: Endpoint4_0Request["query"]["project"]
|
readonly project?: Endpoint4_0Request["query"]["project"]
|
||||||
readonly subpath?: Endpoint4_0Request["query"]["subpath"]
|
readonly subpath?: Endpoint4_0Request["query"]["subpath"]
|
||||||
@@ -57,6 +58,7 @@ const Endpoint4_0 = (raw: RawClient["server.session"]) => (input?: Endpoint4_0In
|
|||||||
limit: input?.["limit"],
|
limit: input?.["limit"],
|
||||||
order: input?.["order"],
|
order: input?.["order"],
|
||||||
search: input?.["search"],
|
search: input?.["search"],
|
||||||
|
parentID: input?.["parentID"],
|
||||||
directory: input?.["directory"],
|
directory: input?.["directory"],
|
||||||
project: input?.["project"],
|
project: input?.["project"],
|
||||||
subpath: input?.["subpath"],
|
subpath: input?.["subpath"],
|
||||||
|
|||||||
@@ -356,6 +356,7 @@ export function make(options: ClientOptions) {
|
|||||||
limit: input?.["limit"],
|
limit: input?.["limit"],
|
||||||
order: input?.["order"],
|
order: input?.["order"],
|
||||||
search: input?.["search"],
|
search: input?.["search"],
|
||||||
|
parentID: input?.["parentID"],
|
||||||
directory: input?.["directory"],
|
directory: input?.["directory"],
|
||||||
project: input?.["project"],
|
project: input?.["project"],
|
||||||
subpath: input?.["subpath"],
|
subpath: input?.["subpath"],
|
||||||
@@ -1361,7 +1362,11 @@ function encodePath(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function appendQuery(params: URLSearchParams, key: string, value: unknown): void {
|
function appendQuery(params: URLSearchParams, key: string, value: unknown): void {
|
||||||
if (value === undefined || value === null) return
|
if (value === undefined) return
|
||||||
|
if (value === null) {
|
||||||
|
params.append(key, "null")
|
||||||
|
return
|
||||||
|
}
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
for (const item of value) appendQuery(params, key, item)
|
for (const item of value) appendQuery(params, key, item)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ export type SessionListInput = {
|
|||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
readonly directory?: string | undefined
|
readonly directory?: string | undefined
|
||||||
readonly project?: string | undefined
|
readonly project?: string | undefined
|
||||||
readonly subpath?: string | undefined
|
readonly subpath?: string | undefined
|
||||||
@@ -228,6 +229,7 @@ export type SessionListInput = {
|
|||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
readonly directory?: string | undefined
|
readonly directory?: string | undefined
|
||||||
readonly project?: string | undefined
|
readonly project?: string | undefined
|
||||||
readonly subpath?: string | undefined
|
readonly subpath?: string | undefined
|
||||||
@@ -238,6 +240,7 @@ export type SessionListInput = {
|
|||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
readonly directory?: string | undefined
|
readonly directory?: string | undefined
|
||||||
readonly project?: string | undefined
|
readonly project?: string | undefined
|
||||||
readonly subpath?: string | undefined
|
readonly subpath?: string | undefined
|
||||||
@@ -248,16 +251,29 @@ export type SessionListInput = {
|
|||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
readonly directory?: string | undefined
|
readonly directory?: string | undefined
|
||||||
readonly project?: string | undefined
|
readonly project?: string | undefined
|
||||||
readonly subpath?: string | undefined
|
readonly subpath?: string | undefined
|
||||||
readonly cursor?: string | undefined
|
readonly cursor?: string | undefined
|
||||||
}["search"]
|
}["search"]
|
||||||
|
readonly parentID?: {
|
||||||
|
readonly workspace?: string | undefined
|
||||||
|
readonly limit?: number | undefined
|
||||||
|
readonly order?: "asc" | "desc" | undefined
|
||||||
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
|
readonly directory?: string | undefined
|
||||||
|
readonly project?: string | undefined
|
||||||
|
readonly subpath?: string | undefined
|
||||||
|
readonly cursor?: string | undefined
|
||||||
|
}["parentID"]
|
||||||
readonly directory?: {
|
readonly directory?: {
|
||||||
readonly workspace?: string | undefined
|
readonly workspace?: string | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
readonly directory?: string | undefined
|
readonly directory?: string | undefined
|
||||||
readonly project?: string | undefined
|
readonly project?: string | undefined
|
||||||
readonly subpath?: string | undefined
|
readonly subpath?: string | undefined
|
||||||
@@ -268,6 +284,7 @@ export type SessionListInput = {
|
|||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
readonly directory?: string | undefined
|
readonly directory?: string | undefined
|
||||||
readonly project?: string | undefined
|
readonly project?: string | undefined
|
||||||
readonly subpath?: string | undefined
|
readonly subpath?: string | undefined
|
||||||
@@ -278,6 +295,7 @@ export type SessionListInput = {
|
|||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
readonly directory?: string | undefined
|
readonly directory?: string | undefined
|
||||||
readonly project?: string | undefined
|
readonly project?: string | undefined
|
||||||
readonly subpath?: string | undefined
|
readonly subpath?: string | undefined
|
||||||
@@ -288,6 +306,7 @@ export type SessionListInput = {
|
|||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
|
readonly parentID?: string | null | undefined
|
||||||
readonly directory?: string | undefined
|
readonly directory?: string | undefined
|
||||||
readonly project?: string | undefined
|
readonly project?: string | undefined
|
||||||
readonly subpath?: string | undefined
|
readonly subpath?: string | undefined
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ test("session methods use the public HTTP contract", async () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const page = await client.session.list({ limit: 10, order: "desc" })
|
const page = await client.session.list({ limit: 10, order: "desc", parentID: null })
|
||||||
const active = await client.session.active()
|
const active = await client.session.active()
|
||||||
const created = await client.session.create({ location: { directory: "/tmp/project" } })
|
const created = await client.session.create({ location: { directory: "/tmp/project" } })
|
||||||
await client.session.switchAgent({ sessionID: "ses_test", agent: "build" })
|
await client.session.switchAgent({ sessionID: "ses_test", agent: "build" })
|
||||||
@@ -276,7 +276,7 @@ test("session methods use the public HTTP contract", async () => {
|
|||||||
expect(log).toEqual([modelSwitchedEvent, caughtUp])
|
expect(log).toEqual([modelSwitchedEvent, caughtUp])
|
||||||
expect(message).toEqual(modelSwitchedMessage)
|
expect(message).toEqual(modelSwitchedMessage)
|
||||||
expect(requests.map((request) => [request.init?.method, request.url])).toEqual([
|
expect(requests.map((request) => [request.init?.method, request.url])).toEqual([
|
||||||
["GET", "http://localhost:3000/api/session?limit=10&order=desc"],
|
["GET", "http://localhost:3000/api/session?limit=10&order=desc&parentID=null"],
|
||||||
["GET", "http://localhost:3000/api/session/active"],
|
["GET", "http://localhost:3000/api/session/active"],
|
||||||
["POST", "http://localhost:3000/api/session"],
|
["POST", "http://localhost:3000/api/session"],
|
||||||
["POST", "http://localhost:3000/api/session/ses_test/agent"],
|
["POST", "http://localhost:3000/api/session/ses_test/agent"],
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export * from "./session/schema"
|
|||||||
|
|
||||||
import { DateTime, Effect, Layer, Schema, Context, Stream, Scope } from "effect"
|
import { DateTime, Effect, Layer, Schema, Context, Stream, Scope } from "effect"
|
||||||
import { ListAnchor } from "@opencode-ai/schema/session"
|
import { ListAnchor } from "@opencode-ai/schema/session"
|
||||||
import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
|
import { and, asc, desc, eq, gt, isNull, like, lt, or, type SQL } from "drizzle-orm"
|
||||||
import { ProjectV2 } from "./project"
|
import { ProjectV2 } from "./project"
|
||||||
import { WorkspaceV2 } from "./workspace"
|
import { WorkspaceV2 } from "./workspace"
|
||||||
import { ModelV2 } from "./model"
|
import { ModelV2 } from "./model"
|
||||||
@@ -61,6 +61,7 @@ const ListInputBase = {
|
|||||||
search: Schema.String.pipe(Schema.optional),
|
search: Schema.String.pipe(Schema.optional),
|
||||||
limit: PositiveInt.pipe(Schema.optional),
|
limit: PositiveInt.pipe(Schema.optional),
|
||||||
order: Schema.Literals(["asc", "desc"]).pipe(Schema.optional),
|
order: Schema.Literals(["asc", "desc"]).pipe(Schema.optional),
|
||||||
|
parentID: Schema.NullOr(SessionSchema.ID).pipe(Schema.optional),
|
||||||
anchor: ListAnchor.pipe(Schema.optional),
|
anchor: ListAnchor.pipe(Schema.optional),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,12 +359,16 @@ const layer = Layer.effect(
|
|||||||
const direction = input.anchor?.direction ?? "next"
|
const direction = input.anchor?.direction ?? "next"
|
||||||
const requestedOrder = input.order ?? "desc"
|
const requestedOrder = input.order ?? "desc"
|
||||||
const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
|
const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
|
||||||
const sortColumn = SessionTable.time_created
|
const sortColumn = SessionTable.time_updated
|
||||||
const conditions: SQL[] = []
|
const conditions: SQL[] = []
|
||||||
if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
|
if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
|
||||||
if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
|
if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
|
||||||
if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
|
if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
|
||||||
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
|
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
|
||||||
|
if (input.parentID !== undefined)
|
||||||
|
conditions.push(
|
||||||
|
input.parentID === null ? isNull(SessionTable.parent_id) : eq(SessionTable.parent_id, input.parentID),
|
||||||
|
)
|
||||||
if (input.anchor) {
|
if (input.anchor) {
|
||||||
conditions.push(
|
conditions.push(
|
||||||
order === "asc"
|
order === "asc"
|
||||||
|
|||||||
@@ -130,6 +130,53 @@ describe("SessionV2.create", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("filters root sessions before applying the page limit", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const { db } = yield* Database.Service
|
||||||
|
const staleRoot = yield* session.create({ location, title: "stale root" })
|
||||||
|
const root = yield* session.create({ location, title: "root" })
|
||||||
|
const children = yield* Effect.forEach(Array.from({ length: 60 }), (_, index) =>
|
||||||
|
session.create({ parentID: root.id, title: `child ${index}` }),
|
||||||
|
)
|
||||||
|
|
||||||
|
yield* Effect.forEach(children, (item, index) =>
|
||||||
|
db
|
||||||
|
.update(SessionTable)
|
||||||
|
.set({ time_created: index + 100, time_updated: index + 20_000 })
|
||||||
|
.where(eq(SessionTable.id, item.id))
|
||||||
|
.run(),
|
||||||
|
)
|
||||||
|
yield* db
|
||||||
|
.update(SessionTable)
|
||||||
|
.set({ time_created: 2, time_updated: 5_000 })
|
||||||
|
.where(eq(SessionTable.id, staleRoot.id))
|
||||||
|
.run()
|
||||||
|
yield* db
|
||||||
|
.update(SessionTable)
|
||||||
|
.set({ time_created: 1, time_updated: 10_000 })
|
||||||
|
.where(eq(SessionTable.id, root.id))
|
||||||
|
.run()
|
||||||
|
|
||||||
|
const page = yield* session.list({ directory: location.directory, parentID: null, limit: 1, order: "desc" })
|
||||||
|
|
||||||
|
expect(page.data.map((item) => item.id)).toEqual([root.id])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("filters direct child sessions by parent ID", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const parent = yield* session.create({ location, title: "parent" })
|
||||||
|
const child = yield* session.create({ parentID: parent.id, title: "child" })
|
||||||
|
yield* session.create({ location, title: "other root" })
|
||||||
|
|
||||||
|
const page = yield* session.list({ parentID: parent.id })
|
||||||
|
|
||||||
|
expect(page.data.map((item) => item.id)).toEqual([child.id])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("forks a session by replaying a durable fork event into copied projected rows", () =>
|
it.effect("forks a session by replaying a durable fork event into copied projected rows", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -521,6 +521,39 @@ describe("HttpApiCodegen.generate", () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("serializes explicit null query values", async () => {
|
||||||
|
const output = emitPromise(
|
||||||
|
compileContract(
|
||||||
|
api(
|
||||||
|
HttpApiEndpoint.get("list", "/session", {
|
||||||
|
query: { parentID: Schema.optional(Schema.NullOr(Schema.String)) },
|
||||||
|
success: Schema.Struct({ data: Schema.Array(Schema.String) }),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
const directory = await mkdtemp(join(tmpdir(), "opencode-httpapi-codegen-"))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(output.files.map((file) => Bun.write(join(directory, file.path), file.content)))
|
||||||
|
const generated = await import(`${join(directory, "index.ts")}?t=${crypto.randomUUID()}`)
|
||||||
|
let request: Request | undefined
|
||||||
|
const client = generated.OpenCode.make({
|
||||||
|
baseUrl: "https://example.com",
|
||||||
|
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
request = input instanceof Request ? input : new Request(input, init)
|
||||||
|
return Response.json({ data: [] })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await client.session.list({ parentID: null })
|
||||||
|
|
||||||
|
expect(request?.url).toBe("https://example.com/session?parentID=null")
|
||||||
|
} finally {
|
||||||
|
await rm(directory, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test("rejects with declared tagged errors and exports a type guard", async () => {
|
test("rejects with declared tagged errors and exports a type guard", async () => {
|
||||||
const output = emitPromise(
|
const output = emitPromise(
|
||||||
compileContract(
|
compileContract(
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export type Endpoint4_0Input = {
|
|||||||
readonly limit?: Endpoint4_0Request["query"]["limit"]
|
readonly limit?: Endpoint4_0Request["query"]["limit"]
|
||||||
readonly order?: Endpoint4_0Request["query"]["order"]
|
readonly order?: Endpoint4_0Request["query"]["order"]
|
||||||
readonly search?: Endpoint4_0Request["query"]["search"]
|
readonly search?: Endpoint4_0Request["query"]["search"]
|
||||||
|
readonly parentID?: Endpoint4_0Request["query"]["parentID"]
|
||||||
readonly directory?: Endpoint4_0Request["query"]["directory"]
|
readonly directory?: Endpoint4_0Request["query"]["directory"]
|
||||||
readonly project?: Endpoint4_0Request["query"]["project"]
|
readonly project?: Endpoint4_0Request["query"]["project"]
|
||||||
readonly subpath?: Endpoint4_0Request["query"]["subpath"]
|
readonly subpath?: Endpoint4_0Request["query"]["subpath"]
|
||||||
|
|||||||
@@ -29,6 +29,18 @@ import { Revert } from "@opencode-ai/schema/revert"
|
|||||||
import { SessionEvent } from "@opencode-ai/schema/session-event"
|
import { SessionEvent } from "@opencode-ai/schema/session-event"
|
||||||
import { EventLog } from "@opencode-ai/schema/event-log"
|
import { EventLog } from "@opencode-ai/schema/event-log"
|
||||||
|
|
||||||
|
const ParentIDFilter = Schema.Union([
|
||||||
|
Session.ID,
|
||||||
|
Schema.Null.pipe(
|
||||||
|
Schema.encodeTo(Schema.Literal("null"), {
|
||||||
|
decode: SchemaGetter.transform(() => null),
|
||||||
|
encode: SchemaGetter.transform(() => "null" as const),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]).annotate({
|
||||||
|
description: "Filter by parent session. Use null to return only root sessions.",
|
||||||
|
})
|
||||||
|
|
||||||
const SessionsQueryFields = {
|
const SessionsQueryFields = {
|
||||||
workspace: Workspace.ID.pipe(Schema.optional),
|
workspace: Workspace.ID.pipe(Schema.optional),
|
||||||
limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
|
limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
|
||||||
@@ -38,6 +50,7 @@ const SessionsQueryFields = {
|
|||||||
description: "Session order for the first page. Use desc for newest first or asc for oldest first.",
|
description: "Session order for the first page. Use desc for newest first or asc for oldest first.",
|
||||||
}),
|
}),
|
||||||
search: Schema.optional(Schema.String),
|
search: Schema.optional(Schema.String),
|
||||||
|
parentID: ParentIDFilter.pipe(Schema.optional),
|
||||||
}
|
}
|
||||||
|
|
||||||
const SessionsDirectoryQuery = Schema.Struct({
|
const SessionsDirectoryQuery = Schema.Struct({
|
||||||
|
|||||||
@@ -104,6 +104,19 @@ if (logTypesPatched === generatedTypes) {
|
|||||||
}
|
}
|
||||||
await Bun.write("./src/v2/gen/types.gen.ts", logTypesPatched)
|
await Bun.write("./src/v2/gen/types.gen.ts", logTypesPatched)
|
||||||
|
|
||||||
|
const querySerializerPath = "./src/v2/gen/client/utils.gen.ts"
|
||||||
|
const querySerializerSource = await Bun.file(querySerializerPath).text()
|
||||||
|
const querySerializerPatched = querySerializerSource.replace(
|
||||||
|
/if \(value === undefined \|\| value === null\) \{\s*continue;?\s*\}/,
|
||||||
|
"if (value === undefined) {\n continue;\n }\n\n if (value === null) {\n search.push(`${name}=null`);\n continue;\n }",
|
||||||
|
)
|
||||||
|
if (querySerializerPatched === querySerializerSource) {
|
||||||
|
throw new Error(
|
||||||
|
`Query serializer null patch did not apply; @hey-api/openapi-ts output may have changed (${querySerializerPath})`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await Bun.write(querySerializerPath, querySerializerPatched)
|
||||||
|
|
||||||
const generatedSdk = await Bun.file("./src/v2/gen/sdk.gen.ts").text()
|
const generatedSdk = await Bun.file("./src/v2/gen/sdk.gen.ts").text()
|
||||||
const logSdkPatched = generatedSdk.replace(
|
const logSdkPatched = generatedSdk.replace(
|
||||||
/(Read the session log[\s\S]*?parameters: \{[\s\S]*?after\?: )string(\s*\|\s*null)?/,
|
/(Read the session log[\s\S]*?parameters: \{[\s\S]*?after\?: )string(\s*\|\s*null)?/,
|
||||||
@@ -196,5 +209,7 @@ function inlineTypedAllOfConstraints(value: unknown): void {
|
|||||||
|
|
||||||
function isConstraintSchema(value: unknown): value is Record<string, unknown> {
|
function isConstraintSchema(value: unknown): value is Record<string, unknown> {
|
||||||
if (typeof value !== "object" || value === null || Array.isArray(value)) return false
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false
|
||||||
return !Object.keys(value).some((key) => key === "$ref" || key === "type" || key === "allOf" || key === "anyOf" || key === "oneOf")
|
return !Object.keys(value).some(
|
||||||
|
(key) => key === "$ref" || key === "type" || key === "allOf" || key === "anyOf" || key === "oneOf",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ export const createQuerySerializer = <T = unknown>({ parameters = {}, ...args }:
|
|||||||
for (const name in queryParams) {
|
for (const name in queryParams) {
|
||||||
const value = queryParams[name]
|
const value = queryParams[name]
|
||||||
|
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
search.push(`${name}=null`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5742,6 +5742,7 @@ export class Session3 extends HeyApiClient {
|
|||||||
limit?: string | null
|
limit?: string | null
|
||||||
order?: "asc" | "desc" | null
|
order?: "asc" | "desc" | null
|
||||||
search?: string | null
|
search?: string | null
|
||||||
|
parentID?: string | "null" | null
|
||||||
directory?: string | null
|
directory?: string | null
|
||||||
project?: string | null
|
project?: string | null
|
||||||
subpath?: string | null
|
subpath?: string | null
|
||||||
@@ -5758,6 +5759,7 @@ export class Session3 extends HeyApiClient {
|
|||||||
{ in: "query", key: "limit" },
|
{ in: "query", key: "limit" },
|
||||||
{ in: "query", key: "order" },
|
{ in: "query", key: "order" },
|
||||||
{ in: "query", key: "search" },
|
{ in: "query", key: "search" },
|
||||||
|
{ in: "query", key: "parentID" },
|
||||||
{ in: "query", key: "directory" },
|
{ in: "query", key: "directory" },
|
||||||
{ in: "query", key: "project" },
|
{ in: "query", key: "project" },
|
||||||
{ in: "query", key: "subpath" },
|
{ in: "query", key: "subpath" },
|
||||||
|
|||||||
@@ -12540,6 +12540,7 @@ export type V2SessionListData = {
|
|||||||
*/
|
*/
|
||||||
order?: "asc" | "desc" | null
|
order?: "asc" | "desc" | null
|
||||||
search?: string | null
|
search?: string | null
|
||||||
|
parentID?: string | "null" | null
|
||||||
directory?: string | null
|
directory?: string | null
|
||||||
project?: string | null
|
project?: string | null
|
||||||
subpath?: string | null
|
subpath?: string | null
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||||||
...query,
|
...query,
|
||||||
anchor: {
|
anchor: {
|
||||||
id: first.id,
|
id: first.id,
|
||||||
time: DateTime.toEpochMillis(first.time.created),
|
time: DateTime.toEpochMillis(first.time.updated),
|
||||||
direction: "previous",
|
direction: "previous",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -61,7 +61,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||||||
...query,
|
...query,
|
||||||
anchor: {
|
anchor: {
|
||||||
id: last.id,
|
id: last.id,
|
||||||
time: DateTime.toEpochMillis(last.time.created),
|
time: DateTime.toEpochMillis(last.time.updated),
|
||||||
direction: "next",
|
direction: "next",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
+21
-11
@@ -531,23 +531,33 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
|||||||
|
|
||||||
let continued = false
|
let continued = false
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
// When using -c, session list is loaded in blocking phase, so we can navigate at "partial"
|
|
||||||
if (continued || sync.status === "loading" || !args.continue) return
|
if (continued || sync.status === "loading" || !args.continue) return
|
||||||
const match = data.session.list().find((session) => !session.parentID)?.id
|
continued = true
|
||||||
if (match) {
|
const location = data.location.default()
|
||||||
continued = true
|
void sdk.api.session
|
||||||
if (args.fork) {
|
.list({
|
||||||
|
limit: 1,
|
||||||
|
order: "desc",
|
||||||
|
parentID: null,
|
||||||
|
directory: location.directory,
|
||||||
|
workspace: location.workspaceID,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const match = response.data[0]?.id
|
||||||
|
if (!match) return
|
||||||
|
if (!args.fork) {
|
||||||
|
route.navigate({ type: "session", sessionID: match })
|
||||||
|
return
|
||||||
|
}
|
||||||
void sdk.client.session.fork({ sessionID: match }).then((result) => {
|
void sdk.client.session.fork({ sessionID: match }).then((result) => {
|
||||||
if (result.data?.id) {
|
if (result.data?.id) {
|
||||||
route.navigate({ type: "session", sessionID: result.data.id })
|
route.navigate({ type: "session", sessionID: result.data.id })
|
||||||
} else {
|
return
|
||||||
toast.show({ message: "Failed to fork session", variant: "error" })
|
|
||||||
}
|
}
|
||||||
|
toast.show({ message: "Failed to fork session", variant: "error" })
|
||||||
})
|
})
|
||||||
} else {
|
})
|
||||||
route.navigate({ type: "session", sessionID: match })
|
.catch(toast.error)
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle --session with --fork: wait for sync to be fully complete before forking
|
// Handle --session with --fork: wait for sync to be fully complete before forking
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export function DialogSessionList() {
|
|||||||
search: query,
|
search: query,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
order: "desc",
|
order: "desc",
|
||||||
|
parentID: null,
|
||||||
directory: location.directory,
|
directory: location.directory,
|
||||||
workspace: location.workspaceID,
|
workspace: location.workspaceID,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user