File size: 4,310 Bytes
eb1202c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { describe, expect, test } from "bun:test"
import type { SessionApi, SessionInfo, SessionListInput } from "@opencode-ai/client/promise"
import { listAllSessions, normalizeSessionInfo } from "./session"

describe("normalizeSessionInfo", () => {
  test("adapts a current session to the app session shape", () => {
    const result = normalizeSessionInfo({
      id: "session-1",
      projectID: "project-1",
      agent: "build",
      model: { id: "gpt-5", providerID: "openai", variant: "high" },
      cost: 0,
      tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
      time: { created: 1, updated: 1 },
      title: "New session",
      location: { directory: "/repo/worktree", workspaceID: "workspace-1" },
      subpath: "worktree",
      revert: { messageID: "message-1", partID: "part-1", snapshot: "snapshot", files: [] },
    } as SessionInfo)

    expect(result).toEqual({
      id: "session-1",
      slug: "session-1",
      projectID: "project-1",
      workspaceID: "workspace-1",
      directory: "/repo/worktree",
      path: "worktree",
      parentID: undefined,
      cost: 0,
      tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
      title: "New session",
      agent: "build",
      model: { id: "gpt-5", providerID: "openai", variant: "high" },
      version: "",
      time: { created: 1, updated: 1 },
      revert: { messageID: "message-1", partID: "part-1", snapshot: "snapshot" },
    })
  })

  test("supplies timestamped titles for untitled current sessions", () => {
    const root = currentSession("session-1")
    const child = currentSession("session-2", "session-1")

    expect(normalizeSessionInfo(root).title).toBe("New session - 1970-01-01T00:00:00.000Z")
    expect(normalizeSessionInfo(child).title).toBe("Child session - 1970-01-01T00:00:00.000Z")
  })
})

describe("listAllSessions", () => {
  test("loads every page in server order and retains the query", async () => {
    const calls: SessionListInput[] = []
    const pages = new Map<string | undefined, { data: SessionInfo[]; cursor: { next?: string } }>([
      [undefined, { data: [sessionInfo("session-3"), sessionInfo("session-2")], cursor: { next: "next" } }],
      ["next", { data: [sessionInfo("session-1", true)], cursor: {} }],
    ])
    const api = {
      list: async (query = {}) => {
        calls.push(query)
        return pages.get(query.cursor) ?? { data: [], cursor: {} }
      },
    } satisfies Pick<SessionApi, "list">

    const result = await listAllSessions(api, { directory: "/repo", order: "desc" })

    expect(result.map((session) => session.id)).toEqual(["session-3", "session-2", "session-1"])
    expect(result[2]?.time.archived).toBe(2)
    expect(calls).toEqual([
      { directory: "/repo", order: "desc", limit: 100, cursor: undefined },
      { directory: "/repo", order: "desc", limit: 100, cursor: "next" },
    ])
  })

  test("requests the terminal empty page when the server returns a next cursor", async () => {
    const cursors: Array<string | undefined> = []
    const api = {
      list: async (query = {}) => {
        cursors.push(query.cursor)
        if (query.cursor) return { data: [], cursor: { next: "unused" } }
        return { data: [sessionInfo("session-1")], cursor: { next: "terminal" } }
      },
    } satisfies Pick<SessionApi, "list">

    const result = await listAllSessions(api, { directory: "/repo", limit: 25 })

    expect(result.map((session) => session.id)).toEqual(["session-1"])
    expect(cursors).toEqual([undefined, "terminal"])
  })
})

function sessionInfo(id: string, archived = false) {
  return {
    id,
    projectID: "project-1",
    agent: "build",
    model: { id: "model-1", providerID: "provider-1" },
    cost: 0,
    tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
    time: { created: 1, updated: 1, archived: archived ? 2 : undefined },
    title: id,
    location: { directory: "/repo" },
  } as SessionInfo
}

function currentSession(id: string, parentID?: string) {
  return {
    id,
    parentID,
    projectID: "project-1",
    cost: 0,
    tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
    time: { created: 0, updated: 0 },
    location: { directory: "/repo" },
  } as SessionInfo
}