diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index 64133af72..00f300828 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -4,6 +4,7 @@ import { ProgressCircle } from "@opencode-ai/ui/progress-circle" import { Button } from "@opencode-ai/ui/button" import { useParams } from "@solidjs/router" import { AssistantMessage } from "@opencode-ai/sdk/v2/client" +import { findLast } from "@opencode-ai/util/array" import { useLayout } from "@/context/layout" import { useSync } from "@/context/sync" @@ -36,7 +37,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { const context = createMemo(() => { const locale = language.locale() - const last = messages().findLast((x) => { + const last = findLast(messages(), (x) => { if (x.role !== "assistant") return false const total = x.tokens.input + x.tokens.output + x.tokens.reasoning + x.tokens.cache.read + x.tokens.cache.write return total > 0 diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index 57648c380..4c672af3e 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -5,6 +5,7 @@ import { DateTime } from "luxon" import { useSync } from "@/context/sync" import { useLayout } from "@/context/layout" import { checksum } from "@opencode-ai/util/encode" +import { findLast } from "@opencode-ai/util/array" import { Icon } from "@opencode-ai/ui/icon" import { Accordion } from "@opencode-ai/ui/accordion" import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header" @@ -26,7 +27,7 @@ export function SessionContextTab(props: SessionContextTabProps) { const language = useLanguage() const ctx = createMemo(() => { - const last = props.messages().findLast((x) => { + const last = findLast(props.messages(), (x) => { if (x.role !== "assistant") return false const total = x.tokens.input + x.tokens.output + x.tokens.reasoning + x.tokens.cache.read + x.tokens.cache.write return total > 0 @@ -81,7 +82,7 @@ export function SessionContextTab(props: SessionContextTabProps) { }) const systemPrompt = createMemo(() => { - const msg = props.visibleUserMessages().findLast((m) => !!m.system) + const msg = findLast(props.visibleUserMessages(), (m) => !!m.system) const system = msg?.system if (!system) return const trimmed = system.trim() diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 71ab03331..5bf337bfa 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -40,6 +40,7 @@ import { useTerminal, type LocalPTY } from "@/context/terminal" import { useLayout } from "@/context/layout" import { Terminal } from "@/components/terminal" import { checksum, base64Encode, base64Decode } from "@opencode-ai/util/encode" +import { findLast } from "@opencode-ai/util/array" import { getFilename } from "@opencode-ai/util/path" import { useDialog } from "@opencode-ai/ui/context/dialog" import { DialogSelectFile } from "@/components/dialog-select-file" @@ -748,7 +749,7 @@ export default function Page() { } const revert = info()?.revert?.messageID // Find the last user message that's not already reverted - const message = userMessages().findLast((x) => !revert || x.id < revert) + const message = findLast(userMessages(), (x) => !revert || x.id < revert) if (!message) return await sdk.client.session.revert({ sessionID, messageID: message.id }) // Restore the prompt from the reverted message @@ -758,7 +759,7 @@ export default function Page() { prompt.set(restored) } // Navigate to the message before the reverted one (which will be the new last visible message) - const priorMessage = userMessages().findLast((x) => x.id < message.id) + const priorMessage = findLast(userMessages(), (x) => x.id < message.id) setActiveMessage(priorMessage) }, }, @@ -780,14 +781,14 @@ export default function Page() { await sdk.client.session.unrevert({ sessionID }) prompt.reset() // Navigate to the last message (the one that was at the revert point) - const lastMsg = userMessages().findLast((x) => x.id >= revertMessageID) + const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID) setActiveMessage(lastMsg) return } // Partial redo - move forward to next message await sdk.client.session.revert({ sessionID, messageID: nextMessage.id }) // Navigate to the message before the new revert point - const priorMsg = userMessages().findLast((x) => x.id < nextMessage.id) + const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id) setActiveMessage(priorMsg) }, }, diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 8129e13a7..194d5148a 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -42,6 +42,7 @@ import { Checkbox } from "./checkbox" import { DiffChanges } from "./diff-changes" import { Markdown } from "./markdown" import { ImagePreview } from "./image-preview" +import { findLast } from "@opencode-ai/util/array" import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/util/path" import { checksum } from "@opencode-ai/util/encode" import { Tooltip } from "./tooltip" @@ -891,7 +892,7 @@ ToolRegistry.register({ if (!sessionId) return undefined // Find the tool part that matches the permission's callID const messages = data.store.message[sessionId] ?? [] - const message = messages.findLast((m) => m.id === perm.tool!.messageID) + const message = findLast(messages, (m) => m.id === perm.tool!.messageID) if (!message) return undefined const parts = data.store.part[message.id] ?? [] for (const part of parts) { diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index fe53c0939..4556b1f12 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -11,6 +11,7 @@ import { type FileDiff } from "@opencode-ai/sdk/v2" import { useData } from "../context" import { useDiffComponent } from "../context/diff" import { type UiI18nKey, type UiI18nParams, useI18n } from "../context/i18n" +import { findLast } from "@opencode-ai/util/array" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { Binary } from "@opencode-ai/util/binary" @@ -266,7 +267,7 @@ export function SessionTurn( const next = nextPermission() if (!next || !next.tool) return emptyPermissionParts - const message = assistantMessages().findLast((m) => m.id === next.tool!.messageID) + const message = findLast(assistantMessages(), (m) => m.id === next.tool!.messageID) if (!message) return emptyPermissionParts const parts = data.store.part[message.id] ?? emptyParts diff --git a/packages/util/src/array.ts b/packages/util/src/array.ts new file mode 100644 index 000000000..1fb8ac69e --- /dev/null +++ b/packages/util/src/array.ts @@ -0,0 +1,10 @@ +export function findLast( + items: readonly T[], + predicate: (item: T, index: number, items: readonly T[]) => boolean, +): T | undefined { + for (let i = items.length - 1; i >= 0; i -= 1) { + const item = items[i] + if (predicate(item, i, items)) return item + } + return undefined +}