diff --git a/packages/opencode/src/cli/cmd/export.ts b/packages/opencode/src/cli/cmd/export.ts index 4e040abd..27f46050 100644 --- a/packages/opencode/src/cli/cmd/export.ts +++ b/packages/opencode/src/cli/cmd/export.ts @@ -67,7 +67,7 @@ export const ExportCommand = cmd({ try { const sessionInfo = await Session.get(sessionID!) - const messages = await Session.messages(sessionID!) + const messages = await Session.messages({ sessionID: sessionID! }) const exportData = { info: sessionInfo, diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts index 5abee45f..d7afbe33 100644 --- a/packages/opencode/src/cli/cmd/stats.ts +++ b/packages/opencode/src/cli/cmd/stats.ts @@ -142,7 +142,7 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro const batch = filteredSessions.slice(i, i + BATCH_SIZE) const batchPromises = batch.map(async (session) => { - const messages = await Session.messages(session.id) + const messages = await Session.messages({ sessionID: session.id }) let sessionCost = 0 let sessionTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 8ba73a4b..3fe1a4f5 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -159,6 +159,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ event.properties.info.sessionID, produce((draft) => { draft.splice(result.index, 0, event.properties.info) + if (draft.length > 100) draft.shift() }), ) break @@ -272,7 +273,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ console.log("syncing", sessionID) const [session, messages, todo, diff] = await Promise.all([ sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }), - sdk.client.session.messages({ path: { id: sessionID } }), + sdk.client.session.messages({ path: { id: sessionID }, query: { limit: 100 } }), sdk.client.session.todo({ path: { id: sessionID } }), sdk.client.session.diff({ path: { id: sessionID } }), ]) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 308ed438..c7265006 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -753,9 +753,19 @@ export namespace Server { id: z.string().meta({ description: "Session ID" }), }), ), + validator( + "query", + z.object({ + limit: z.coerce.number().optional(), + }), + ), async (c) => { - const messages = await Session.messages(c.req.valid("param").id) - return c.json(messages.slice(-100)) + const query = c.req.valid("query") + const messages = await Session.messages({ + sessionID: c.req.valid("param").id, + limit: query.limit, + }) + return c.json(messages) }, ) .get( diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 021b544e..6159fb33 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -55,7 +55,7 @@ export namespace SessionCompaction { export async function prune(input: { sessionID: string }) { if (Flag.OPENCODE_DISABLE_PRUNE) return log.info("pruning") - const msgs = await Session.messages(input.sessionID) + const msgs = await Session.messages({ sessionID: input.sessionID }) let total = 0 let pruned = 0 const toPrune = [] @@ -111,7 +111,7 @@ export namespace SessionCompaction { draft.time.compacting = undefined }) }) - const toSummarize = await Session.messages(input.sessionID).then(MessageV2.filterCompacted) + const toSummarize = await Session.messages({ sessionID: input.sessionID }).then(MessageV2.filterCompacted) const model = await Provider.getModel(input.providerID, input.modelID) const system = [ ...SystemPrompt.summarize(model.providerID), diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 4b4d1f5a..dc04f183 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -144,7 +144,7 @@ export namespace Session { const session = await createNext({ directory: Instance.directory, }) - const msgs = await messages(input.sessionID) + const msgs = await messages({ sessionID: input.sessionID }) for (const msg of msgs) { if (input.messageID && msg.info.id >= input.messageID) break const cloned = await updateMessage({ @@ -237,7 +237,7 @@ export namespace Session { }) await Storage.write(["share", id], share) await Share.sync("session/info/" + id, session) - for (const msg of await messages(id)) { + for (const msg of await messages({ sessionID: id })) { await Share.sync("session/message/" + id + "/" + msg.info.id, msg.info) for (const part of msg.parts) { await Share.sync("session/part/" + id + "/" + msg.info.id + "/" + part.id, part) @@ -273,18 +273,26 @@ export namespace Session { return diffs ?? [] }) - export const messages = fn(Identifier.schema("session"), async (sessionID) => { - const result = [] as MessageV2.WithParts[] - for (const p of await Storage.list(["message", sessionID])) { - const read = await Storage.read(p) - result.push({ - info: read, - parts: await getParts(read.id), - }) - } - result.sort((a, b) => (a.info.id > b.info.id ? 1 : -1)) - return result - }) + export const messages = fn( + z.object({ + sessionID: Identifier.schema("session"), + limit: z.number().optional(), + }), + async (input) => { + const result = [] as MessageV2.WithParts[] + const list = (await Array.fromAsync(await Storage.list(["message", input.sessionID]))) + .toSorted((a, b) => a.at(-1)!.localeCompare(b.at(-1)!)) + .slice(-1 * (input.limit ?? 1_000_000)) + for (const p of list) { + const read = await Storage.read(p) + result.push({ + info: read, + parts: await getParts(read.id), + }) + } + return result + }, + ) export const getMessage = fn( z.object({ diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 9072135f..27435658 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -434,7 +434,7 @@ export namespace SessionPrompt { providerID: string signal: AbortSignal }) { - let msgs = await Session.messages(input.sessionID).then(MessageV2.filterCompacted) + let msgs = await Session.messages({ sessionID: input.sessionID }).then(MessageV2.filterCompacted) const lastAssistant = msgs.findLast((msg) => msg.info.role === "assistant") if ( lastAssistant?.info.role === "assistant" && diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index 7439d59c..cd8e34f8 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -25,7 +25,7 @@ export namespace SessionRevert { sessionID: input.sessionID, }) - const all = await Session.messages(input.sessionID) + const all = await Session.messages({ sessionID: input.sessionID }) let lastUser: MessageV2.User | undefined const session = await Session.get(input.sessionID) @@ -88,7 +88,7 @@ export namespace SessionRevert { export async function cleanup(session: Session.Info) { if (!session.revert) return const sessionID = session.id - let msgs = await Session.messages(sessionID) + let msgs = await Session.messages({ sessionID }) const messageID = session.revert.messageID const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID) msgs = preserve diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 377e75c7..8783a36e 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -23,7 +23,7 @@ export namespace SessionSummary { messageID: z.string(), }), async (input) => { - const all = await Session.messages(input.sessionID) + const all = await Session.messages({ sessionID: input.sessionID }) await Promise.all([ summarizeSession({ sessionID: input.sessionID, messages: all }), summarizeMessage({ messageID: input.messageID, messages: all }), @@ -151,7 +151,7 @@ export namespace SessionSummary { messageID: Identifier.schema("message").optional(), }), async (input) => { - let all = await Session.messages(input.sessionID) + let all = await Session.messages({ sessionID: input.sessionID }) if (input.messageID) all = all.filter( (x) => diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index ac0b204b..642611f8 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -93,7 +93,7 @@ export const TaskTool = Tool.define("task", async () => { }) unsub() let all - all = await Session.messages(session.id) + all = await Session.messages({ sessionID: session.id }) all = all.filter((x) => x.info.role === "assistant") all = all.flatMap( (msg) => msg.parts.filter((x: any) => x.type === "tool") as MessageV2.ToolPart[], diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 78e98be5..5f565df5 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -650,7 +650,6 @@ export type AssistantMessage = { | MessageOutputLengthError | MessageAbortedError | ApiError - system: Array parentID: string modelID: string providerID: string @@ -1982,6 +1981,7 @@ export type SessionMessagesData = { } query?: { directory?: string + limit?: number } url: "/session/{id}/message" }