promise.test.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. import { expect, test } from "bun:test"
  2. import { isSessionNotFoundError, isUnauthorizedError, OpenCode } from "../src/promise/index"
  3. test("exposes every standard HTTP API group", () => {
  4. const client = OpenCode.make({ baseUrl: "http://localhost:3000" })
  5. expect(Object.keys(client)).toEqual([
  6. "health",
  7. "server",
  8. "location",
  9. "agent",
  10. "plugin",
  11. "session",
  12. "message",
  13. "model",
  14. "generate",
  15. "provider",
  16. "integration",
  17. "mcp",
  18. "credential",
  19. "project",
  20. "form",
  21. "permission",
  22. "file",
  23. "command",
  24. "skill",
  25. "event",
  26. "pty",
  27. "shell",
  28. "question",
  29. "reference",
  30. "projectCopy",
  31. "vcs",
  32. "debug",
  33. "websearch",
  34. "config",
  35. ])
  36. expect(Object.keys(client.debug)).toEqual(["location"])
  37. expect(Object.keys(client.debug.location)).toEqual(["list", "evict"])
  38. expect(Object.keys(client.message)).toEqual(["list"])
  39. expect(Object.keys(client.integration)).toEqual(["list", "get", "wellknown", "connect", "oauth", "command"])
  40. expect(Object.keys(client.integration.wellknown)).toEqual(["add"])
  41. expect(Object.keys(client.integration.connect)).toEqual(["key"])
  42. expect(Object.keys(client.integration.oauth)).toEqual(["connect", "status", "complete", "cancel"])
  43. expect(Object.keys(client.integration.command)).toEqual(["connect", "status", "cancel"])
  44. expect(Object.keys(client.websearch)).toEqual(["providers", "query"])
  45. expect(Object.keys(client.file)).toEqual(["read", "list", "find"])
  46. expect(Object.keys(client.vcs)).toEqual(["get", "status", "diff"])
  47. expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"])
  48. expect(Object.keys(client.shell)).toEqual(["list", "create", "get", "timeout", "output", "remove"])
  49. expect(Object.keys(client.project)).toEqual(["list", "current", "directories"])
  50. })
  51. test("config.get returns ordered config entries for a location", async () => {
  52. let request: Request | undefined
  53. const entries = [
  54. {
  55. type: "document" as const,
  56. path: "/tmp/project/opencode.json",
  57. info: {
  58. permissions: [
  59. { action: "shell", resource: "*", effect: "ask" as const },
  60. { action: "shell", resource: "git status", effect: "allow" as const },
  61. ],
  62. },
  63. },
  64. { type: "file" as const, path: "/tmp/project/opencode.json" },
  65. ]
  66. const client = OpenCode.make({
  67. baseUrl: "http://localhost:3000",
  68. fetch: async (input) => {
  69. request = input instanceof Request ? input : new Request(input)
  70. return Response.json(entries)
  71. },
  72. })
  73. expect(await client.config.get({ location: { directory: "/tmp/project" } })).toEqual(entries)
  74. expect(request?.method).toBe("GET")
  75. expect(request?.url).toBe("http://localhost:3000/api/config?location%5Bdirectory%5D=%2Ftmp%2Fproject")
  76. })
  77. test("websearch.query uses the public HTTP contract", async () => {
  78. let request: Request | undefined
  79. const client = OpenCode.make({
  80. baseUrl: "http://localhost:3000",
  81. fetch: async (input, init) => {
  82. request = input instanceof Request ? input : new Request(input, init)
  83. return Response.json({
  84. location: { directory: "/tmp/project", project: { id: "proj_test", directory: "/tmp/project" } },
  85. data: {
  86. providerID: "exa",
  87. results: [{ url: "https://example.com", title: "Result", content: "result", time: {} }],
  88. },
  89. })
  90. },
  91. })
  92. const result = await client.websearch.query({
  93. query: "opencode",
  94. providerID: "exa",
  95. location: { directory: "/tmp/project" },
  96. })
  97. expect(result.data).toEqual({
  98. providerID: "exa",
  99. results: [{ url: "https://example.com", title: "Result", content: "result", time: {} }],
  100. })
  101. expect(request?.method).toBe("POST")
  102. expect(request?.url).toBe("http://localhost:3000/api/websearch?location%5Bdirectory%5D=%2Ftmp%2Fproject")
  103. expect(await request?.json()).toEqual({ query: "opencode", providerID: "exa" })
  104. })
  105. test("server.get uses the public HTTP contract", async () => {
  106. let request: Request | undefined
  107. const client = OpenCode.make({
  108. baseUrl: "http://localhost:3000",
  109. fetch: async (input) => {
  110. request = input instanceof Request ? input : new Request(input)
  111. return Response.json({ urls: ["http://192.168.1.10:4096"] })
  112. },
  113. })
  114. expect(await client.server.get()).toEqual({ urls: ["http://192.168.1.10:4096"] })
  115. expect(request?.method).toBe("GET")
  116. expect(request?.url).toBe("http://localhost:3000/api/server")
  117. })
  118. test("experimental wellknown integration add uses the public HTTP contract", async () => {
  119. let request: Request | undefined
  120. const client = OpenCode.make({
  121. baseUrl: "http://localhost:3000",
  122. fetch: async (input, init) => {
  123. request = input instanceof Request ? input : new Request(input, init)
  124. return new Response(null, { status: 204 })
  125. },
  126. })
  127. await client.integration.wellknown.add({
  128. url: "https://example.com",
  129. location: { directory: "/tmp/project" },
  130. })
  131. expect(request?.method).toBe("POST")
  132. expect(request?.url).toBe(
  133. "http://localhost:3000/api/experimental/integration/wellknown?location%5Bdirectory%5D=%2Ftmp%2Fproject",
  134. )
  135. expect(await request?.json()).toEqual({ url: "https://example.com" })
  136. })
  137. test("health.stop sends exact replacement identity", async () => {
  138. let request: Request | undefined
  139. const client = OpenCode.make({
  140. baseUrl: "http://localhost:3000",
  141. fetch: async (input, init) => {
  142. request = input instanceof Request ? input : new Request(input, init)
  143. return Response.json({ accepted: true })
  144. },
  145. })
  146. expect(await client.health.stop({ instanceID: "instance" })).toEqual({ accepted: true })
  147. expect(request?.method).toBe("POST")
  148. expect(request?.url).toBe("http://localhost:3000/api/service/stop")
  149. expect(await request?.json()).toEqual({ instanceID: "instance" })
  150. })
  151. test("MCP resource catalog uses the public HTTP contract", async () => {
  152. let request: Request | undefined
  153. const client = OpenCode.make({
  154. baseUrl: "http://localhost:3000",
  155. fetch: async (input) => {
  156. request = input instanceof Request ? input : new Request(input)
  157. return Response.json({
  158. location: { directory: "/tmp/project", project: { id: "proj_test", directory: "/tmp/project" } },
  159. data: {
  160. resources: [{ server: "docs", name: "Readme", uri: "docs://readme" }],
  161. templates: [{ server: "docs", name: "File", uriTemplate: "docs://{path}" }],
  162. },
  163. })
  164. },
  165. })
  166. const result = await client.mcp.resource.catalog({ location: { directory: "/tmp/project" } })
  167. expect(result.data.resources[0]?.uri).toBe("docs://readme")
  168. expect(request?.method).toBe("GET")
  169. expect(request?.url).toBe("http://localhost:3000/api/mcp/resource?location%5Bdirectory%5D=%2Ftmp%2Fproject")
  170. })
  171. test("file.read returns binary content from the public HTTP contract", async () => {
  172. let request: Request | undefined
  173. const client = OpenCode.make({
  174. baseUrl: "http://localhost:3000",
  175. fetch: async (input) => {
  176. request = input instanceof Request ? input : new Request(input)
  177. return new Response(new Uint8Array([104, 105]))
  178. },
  179. })
  180. const content = await client.file.read({
  181. path: "src/a b#c.ts",
  182. location: { directory: "/tmp/project" },
  183. })
  184. expect(Array.from(content)).toEqual([104, 105])
  185. expect(request?.url).toBe(
  186. "http://localhost:3000/api/fs/read/src/a%20b%23c.ts?location%5Bdirectory%5D=%2Ftmp%2Fproject",
  187. )
  188. })
  189. test("project methods use the public HTTP contract", async () => {
  190. const requests: string[] = []
  191. const client = OpenCode.make({
  192. baseUrl: "http://localhost:3000",
  193. fetch: async (input) => {
  194. const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url
  195. requests.push(url)
  196. if (url.includes("/directories")) return Response.json([])
  197. return Response.json({ id: "proj_test", directory: "/tmp/project" })
  198. },
  199. })
  200. const current = await client.project.current({ location: { workspace: "wrk_test" } })
  201. const directories = await client.project.directories({
  202. projectID: current.id,
  203. location: { directory: current.directory },
  204. })
  205. expect(current).toEqual({ id: "proj_test", directory: "/tmp/project" })
  206. expect(directories).toEqual([])
  207. expect(requests).toEqual([
  208. "http://localhost:3000/api/project/current?location%5Bworkspace%5D=wrk_test",
  209. "http://localhost:3000/api/project/proj_test/directories?location%5Bdirectory%5D=%2Ftmp%2Fproject",
  210. ])
  211. })
  212. test("shell list and remove use the public HTTP contract", async () => {
  213. const requests: Array<{ method: string; url: string }> = []
  214. const shell = {
  215. id: "sh_test",
  216. status: "running",
  217. command: "pwd",
  218. cwd: "/tmp/project",
  219. shell: "/bin/zsh",
  220. file: "/tmp/opencode-shell",
  221. metadata: { sessionID: "ses_test" },
  222. time: { started: 1_717_171_717_000 },
  223. }
  224. const client = OpenCode.make({
  225. baseUrl: "http://localhost:3000",
  226. fetch: async (input, init) => {
  227. const request = input instanceof Request ? input : new Request(input, init)
  228. requests.push({ method: request.method, url: request.url })
  229. if (request.method === "DELETE") return new Response(null, { status: 204 })
  230. return Response.json({
  231. location: { directory: "/tmp/project", project: { id: "proj_test", directory: "/tmp/project" } },
  232. data: [shell],
  233. })
  234. },
  235. })
  236. const result = await client.shell.list({ location: { directory: "/tmp/project" } })
  237. await client.shell.remove({ id: shell.id })
  238. expect(result.data).toEqual([shell])
  239. expect(requests).toEqual([
  240. { method: "GET", url: "http://localhost:3000/api/shell?location%5Bdirectory%5D=%2Ftmp%2Fproject" },
  241. { method: "DELETE", url: "http://localhost:3000/api/shell/sh_test" },
  242. ])
  243. })
  244. test("session.get returns the wire projection", async () => {
  245. const client = OpenCode.make({
  246. baseUrl: "http://localhost:3000",
  247. fetch: async (input) => {
  248. expect(typeof input === "string" ? input : input instanceof URL ? input.href : input.url).toBe(
  249. "http://localhost:3000/api/session/ses_test",
  250. )
  251. return Response.json(session)
  252. },
  253. })
  254. const result = await client.session.get({ sessionID: "ses_test" })
  255. expect(result.time.created).toBe(1_717_171_717_000)
  256. })
  257. test("session instructions methods use the public HTTP contract", async () => {
  258. const requests: Array<{ method: string; url: string; body?: unknown }> = []
  259. const instructions = [{ key: "review-notes", value: { text: "Check the diff", priority: 1 } }]
  260. const client = OpenCode.make({
  261. baseUrl: "http://localhost:3000",
  262. fetch: async (input, init) => {
  263. const request = input instanceof Request ? input : new Request(input, init)
  264. requests.push({
  265. method: request.method,
  266. url: request.url,
  267. body: request.method === "PUT" ? await request.json() : undefined,
  268. })
  269. if (request.method === "GET") return Response.json({ data: instructions })
  270. return new Response(null, { status: 204 })
  271. },
  272. })
  273. const result = await client.session.instructions.entry.list({ sessionID: "ses_test" })
  274. await client.session.instructions.entry.put({
  275. sessionID: "ses_test",
  276. key: "review-notes",
  277. value: instructions[0].value,
  278. })
  279. await client.session.instructions.entry.remove({ sessionID: "ses_test", key: "review-notes" })
  280. expect(result).toEqual(instructions)
  281. expect(requests).toEqual([
  282. {
  283. method: "GET",
  284. url: "http://localhost:3000/api/session/ses_test/instructions/entries",
  285. body: undefined,
  286. },
  287. {
  288. method: "PUT",
  289. url: "http://localhost:3000/api/session/ses_test/instructions/entries/review-notes",
  290. body: { value: { text: "Check the diff", priority: 1 } },
  291. },
  292. {
  293. method: "DELETE",
  294. url: "http://localhost:3000/api/session/ses_test/instructions/entries/review-notes",
  295. body: undefined,
  296. },
  297. ])
  298. })
  299. test("session.pending.list uses the public HTTP contract", async () => {
  300. const requests: Array<{ method: string; url: string }> = []
  301. const pending = [
  302. {
  303. id: "msg_pending",
  304. sessionID: "ses_test",
  305. timeCreated: 1_717_171_717_000,
  306. type: "user",
  307. data: { text: "Fix the failing tests" },
  308. delivery: "steer",
  309. },
  310. ]
  311. const client = OpenCode.make({
  312. baseUrl: "http://localhost:3000",
  313. fetch: async (input, init) => {
  314. const request = input instanceof Request ? input : new Request(input, init)
  315. requests.push({ method: request.method, url: request.url })
  316. return Response.json({ data: pending })
  317. },
  318. })
  319. const result = await client.session.pending.list({ sessionID: "ses_test" })
  320. expect(result).toEqual(pending)
  321. expect(requests).toEqual([{ method: "GET", url: "http://localhost:3000/api/session/ses_test/pending" }])
  322. })
  323. test("event.subscribe exposes the Promise event stream wire projection", async () => {
  324. const client = OpenCode.make({
  325. baseUrl: "http://localhost:3000",
  326. fetch: async () =>
  327. new Response(
  328. `: heartbeat\n\ndata: ${JSON.stringify({ id: "evt_connected", created: 0, type: "server.connected", data: {} })}\n\n` +
  329. `data: ${JSON.stringify(modelSwitchedEvent)}\n\n`,
  330. { headers: { "content-type": "text/event-stream" } },
  331. ),
  332. })
  333. const events = []
  334. for await (const event of client.event.subscribe()) events.push(event)
  335. expect(events).toEqual([{ id: "evt_connected", created: 0, type: "server.connected", data: {} }, modelSwitchedEvent])
  336. expect(events[1]?.type === "session.model.selected" && events[1].created).toBe(1_717_171_717_000)
  337. })
  338. test("event.subscribe terminates on malformed Promise SSE data", async () => {
  339. const client = OpenCode.make({
  340. baseUrl: "http://localhost:3000",
  341. fetch: async () => new Response("data: {not-json}\n\n", { headers: { "content-type": "text/event-stream" } }),
  342. })
  343. await expect(client.event.subscribe()[Symbol.asyncIterator]().next()).rejects.toMatchObject({
  344. name: "ClientError",
  345. reason: "MalformedResponse",
  346. })
  347. })
  348. test("event.subscribe accepts a fragmented SSE event below the size limit", async () => {
  349. const event = { id: "evt_large", type: "test.large", data: { output: "x".repeat(12 * 1024 * 1024) } }
  350. const encoded = new TextEncoder().encode(`data: ${JSON.stringify(event)}\n\n`)
  351. const client = OpenCode.make({
  352. baseUrl: "http://localhost:3000",
  353. fetch: async () =>
  354. new Response(
  355. new ReadableStream({
  356. start(controller) {
  357. for (let offset = 0; offset < encoded.length; offset += 64 * 1024) {
  358. controller.enqueue(encoded.slice(offset, offset + 64 * 1024))
  359. }
  360. controller.close()
  361. },
  362. }),
  363. { headers: { "content-type": "text/event-stream" } },
  364. ),
  365. })
  366. await expect(client.event.subscribe()[Symbol.asyncIterator]().next()).resolves.toEqual({ done: false, value: event })
  367. })
  368. test("event.subscribe rejects an SSE event above the size limit", async () => {
  369. const client = OpenCode.make({
  370. baseUrl: "http://localhost:3000",
  371. fetch: async () =>
  372. new Response(`data: ${JSON.stringify({ output: "x".repeat(16 * 1024 * 1024) })}`, {
  373. headers: { "content-type": "text/event-stream" },
  374. }),
  375. })
  376. await expect(client.event.subscribe()[Symbol.asyncIterator]().next()).rejects.toMatchObject({
  377. name: "ClientError",
  378. reason: "SseEventTooLarge",
  379. })
  380. })
  381. test("session methods use the public HTTP contract", async () => {
  382. const requests: Array<{ url: string; init?: RequestInit }> = []
  383. const client = OpenCode.make({
  384. baseUrl: "http://localhost:3000",
  385. fetch: async (input, init) => {
  386. const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url
  387. requests.push({ url, init })
  388. if (url.includes("/event")) {
  389. return new Response(`data: ${JSON.stringify(modelSwitchedEvent)}\n\n`, {
  390. headers: { "content-type": "text/event-stream" },
  391. })
  392. }
  393. if (url.includes("/log")) {
  394. return new Response(`data: ${JSON.stringify(modelSwitchedEvent)}\n\ndata: ${JSON.stringify(synced)}\n\n`, {
  395. headers: { "content-type": "text/event-stream" },
  396. })
  397. }
  398. if (url.includes("/prompt")) return Response.json(admission)
  399. if (url.includes("/generate")) return Response.json({ data: { text: "A transient answer" } })
  400. if (url.includes("/synthetic")) return Response.json(syntheticAdmission)
  401. if (url.endsWith("/compact")) return Response.json(compactionAdmission)
  402. if (url.includes("/context")) return Response.json({ data: [] })
  403. if (url.includes("/message/")) return Response.json({ data: modelSwitchedMessage })
  404. if (url.endsWith("/api/session/active")) return Response.json({ data: { ses_test: { type: "running" } } })
  405. if (init?.method === "POST" && url.endsWith("/api/session")) return Response.json(session)
  406. if (init?.method === "POST") return new Response(null, { status: 204 })
  407. return Response.json({ data: [session.data], cursor: { next: "next" } })
  408. },
  409. })
  410. const page = await client.session.list({ limit: 10, order: "desc", parentID: null })
  411. const active = await client.session.active()
  412. const created = await client.session.create({ location: { directory: "/tmp/project" } })
  413. await client.session.switchAgent({ sessionID: "ses_test", agent: "build" })
  414. await client.session.switchModel({
  415. sessionID: "ses_test",
  416. model: { id: "claude", providerID: "anthropic" },
  417. })
  418. const admitted = await client.session.prompt({
  419. sessionID: "ses_test",
  420. text: "Hello",
  421. resume: false,
  422. })
  423. const generated = await client.session.generate({ sessionID: "ses_test", prompt: "Summarize this session" })
  424. const synthetic = await client.session.synthetic({
  425. sessionID: "ses_test",
  426. text: "Completed",
  427. delivery: "queue",
  428. resume: false,
  429. })
  430. await client.session.compact({ sessionID: "ses_test" })
  431. await client.session.wait({ sessionID: "ses_test" })
  432. const context = await client.session.context({ sessionID: "ses_test" })
  433. const log = []
  434. for await (const item of client.session.log({ sessionID: "ses_test", after: 0 })) log.push(item)
  435. await client.session.interrupt({ sessionID: "ses_test" })
  436. const message = await client.session.message({ sessionID: "ses_test", messageID: "msg_model" })
  437. expect(page.cursor.next).toBe("next")
  438. expect(active).toEqual({ ses_test: { type: "running" } })
  439. expect(created.id).toBe("ses_test")
  440. expect(admitted.id).toBe("msg_test")
  441. expect(generated.text).toBe("A transient answer")
  442. expect(synthetic).toMatchObject({ type: "synthetic", data: { text: "Completed" }, delivery: "queue" })
  443. expect(context).toEqual([])
  444. expect(log).toEqual([modelSwitchedEvent, synced])
  445. expect(message).toEqual(modelSwitchedMessage)
  446. expect(requests.map((request) => [request.init?.method, request.url])).toEqual([
  447. ["GET", "http://localhost:3000/api/session?limit=10&order=desc&parentID=null"],
  448. ["GET", "http://localhost:3000/api/session/active"],
  449. ["POST", "http://localhost:3000/api/session"],
  450. ["POST", "http://localhost:3000/api/session/ses_test/agent"],
  451. ["POST", "http://localhost:3000/api/session/ses_test/model"],
  452. ["POST", "http://localhost:3000/api/session/ses_test/prompt"],
  453. ["POST", "http://localhost:3000/api/session/ses_test/generate"],
  454. ["POST", "http://localhost:3000/api/session/ses_test/synthetic"],
  455. ["POST", "http://localhost:3000/api/session/ses_test/compact"],
  456. ["POST", "http://localhost:3000/api/session/ses_test/wait"],
  457. ["GET", "http://localhost:3000/api/session/ses_test/context"],
  458. ["GET", "http://localhost:3000/api/experimental/session/ses_test/log?after=0"],
  459. ["POST", "http://localhost:3000/api/session/ses_test/interrupt"],
  460. ["GET", "http://localhost:3000/api/session/ses_test/message/msg_model"],
  461. ])
  462. const body = requests.find((request) => request.url.endsWith("/api/session/ses_test/prompt"))?.init?.body
  463. if (typeof body !== "string") throw new Error("Expected JSON request body")
  464. expect(JSON.parse(body)).toEqual({
  465. text: "Hello",
  466. resume: false,
  467. })
  468. const syntheticBody = requests.find((request) => request.url.endsWith("/synthetic"))?.init?.body
  469. if (typeof syntheticBody !== "string") throw new Error("Expected JSON synthetic request body")
  470. expect(JSON.parse(syntheticBody)).toEqual({
  471. text: "Completed",
  472. delivery: "queue",
  473. resume: false,
  474. })
  475. })
  476. test("middleware errors remain declared client errors", async () => {
  477. const client = OpenCode.make({
  478. baseUrl: "http://localhost:3000",
  479. fetch: async () =>
  480. Response.json({ _tag: "UnauthorizedError", message: "Authentication required" }, { status: 401 }),
  481. })
  482. try {
  483. await client.session.create({})
  484. throw new Error("Expected request to fail")
  485. } catch (error) {
  486. expect(isUnauthorizedError(error)).toBe(true)
  487. }
  488. })
  489. test("session.log decodes SessionNotFoundError", async () => {
  490. const client = OpenCode.make({
  491. baseUrl: "http://localhost:3000",
  492. fetch: async () =>
  493. Response.json(
  494. { _tag: "SessionNotFoundError", sessionID: "ses_missing", message: "Session not found" },
  495. { status: 404 },
  496. ),
  497. })
  498. try {
  499. await client.session.log({ sessionID: "ses_missing" })[Symbol.asyncIterator]().next()
  500. throw new Error("Expected request to fail")
  501. } catch (error) {
  502. expect(isSessionNotFoundError(error)).toBe(true)
  503. }
  504. })
  505. const session = {
  506. data: {
  507. id: "ses_test",
  508. projectID: "project",
  509. cost: 0,
  510. tokens: {
  511. input: 1,
  512. output: 2,
  513. reasoning: 3,
  514. cache: { read: 4, write: 5 },
  515. },
  516. time: {
  517. created: 1_717_171_717_000,
  518. updated: 1_717_171_717_000,
  519. },
  520. title: "Test",
  521. location: { directory: "/tmp/project" },
  522. },
  523. }
  524. const admission = {
  525. data: {
  526. id: "msg_test",
  527. sessionID: "ses_test",
  528. type: "user",
  529. data: { text: "Hello" },
  530. delivery: "steer",
  531. timeCreated: 1_717_171_717_000,
  532. },
  533. }
  534. const syntheticAdmission = {
  535. data: {
  536. id: "msg_synthetic",
  537. sessionID: "ses_test",
  538. type: "synthetic",
  539. data: { text: "Completed" },
  540. delivery: "queue",
  541. timeCreated: 1_717_171_717_000,
  542. },
  543. }
  544. const compactionAdmission = {
  545. data: {
  546. type: "compaction",
  547. id: "msg_compaction",
  548. sessionID: "ses_test",
  549. timeCreated: 1_717_171_717_000,
  550. },
  551. }
  552. const modelSwitchedMessage = {
  553. id: "msg_model",
  554. type: "model-switched",
  555. time: { created: 1_717_171_717_000 },
  556. model: { id: "claude", providerID: "anthropic" },
  557. }
  558. const synced = { type: "log.synced", aggregateID: "ses_test", seq: 1 }
  559. const modelSwitchedEvent = {
  560. id: "evt_model",
  561. created: 1_717_171_717_000,
  562. type: "session.model.selected",
  563. durable: { aggregateID: "ses_test", seq: 1, version: 1 },
  564. data: {
  565. sessionID: "ses_test",
  566. model: { id: "claude", providerID: "anthropic" },
  567. },
  568. }