| import { describe, expect, it } from "vitest"; |
| import { sessionCodec as claudeSessionCodec } from "@paperclipai/adapter-claude-local/server"; |
| import { sessionCodec as codexSessionCodec, isCodexUnknownSessionError } from "@paperclipai/adapter-codex-local/server"; |
| import { |
| sessionCodec as cursorSessionCodec, |
| isCursorUnknownSessionError, |
| } from "@paperclipai/adapter-cursor-local/server"; |
| import { |
| sessionCodec as geminiSessionCodec, |
| isGeminiUnknownSessionError, |
| } from "@paperclipai/adapter-gemini-local/server"; |
| import { |
| sessionCodec as opencodeSessionCodec, |
| isOpenCodeUnknownSessionError, |
| } from "@paperclipai/adapter-opencode-local/server"; |
|
|
| describe("adapter session codecs", () => { |
| it("normalizes claude session params with cwd", () => { |
| const parsed = claudeSessionCodec.deserialize({ |
| session_id: "claude-session-1", |
| folder: "/tmp/workspace", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "claude-session-1", |
| cwd: "/tmp/workspace", |
| }); |
|
|
| const serialized = claudeSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "claude-session-1", |
| cwd: "/tmp/workspace", |
| }); |
| expect(claudeSessionCodec.getDisplayId?.(serialized ?? null)).toBe("claude-session-1"); |
| }); |
|
|
| it("normalizes codex session params with cwd", () => { |
| const parsed = codexSessionCodec.deserialize({ |
| sessionId: "codex-session-1", |
| cwd: "/tmp/codex", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "codex-session-1", |
| cwd: "/tmp/codex", |
| }); |
|
|
| const serialized = codexSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "codex-session-1", |
| cwd: "/tmp/codex", |
| }); |
| expect(codexSessionCodec.getDisplayId?.(serialized ?? null)).toBe("codex-session-1"); |
| }); |
|
|
| it("normalizes opencode session params with cwd", () => { |
| const parsed = opencodeSessionCodec.deserialize({ |
| sessionID: "opencode-session-1", |
| cwd: "/tmp/opencode", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "opencode-session-1", |
| cwd: "/tmp/opencode", |
| }); |
|
|
| const serialized = opencodeSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "opencode-session-1", |
| cwd: "/tmp/opencode", |
| }); |
| expect(opencodeSessionCodec.getDisplayId?.(serialized ?? null)).toBe("opencode-session-1"); |
| }); |
|
|
| it("normalizes cursor session params with cwd", () => { |
| const parsed = cursorSessionCodec.deserialize({ |
| session_id: "cursor-session-1", |
| cwd: "/tmp/cursor", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "cursor-session-1", |
| cwd: "/tmp/cursor", |
| }); |
|
|
| const serialized = cursorSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "cursor-session-1", |
| cwd: "/tmp/cursor", |
| }); |
| expect(cursorSessionCodec.getDisplayId?.(serialized ?? null)).toBe("cursor-session-1"); |
| }); |
|
|
| it("normalizes gemini session params with cwd", () => { |
| const parsed = geminiSessionCodec.deserialize({ |
| session_id: "gemini-session-1", |
| cwd: "/tmp/gemini", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "gemini-session-1", |
| cwd: "/tmp/gemini", |
| }); |
|
|
| const serialized = geminiSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "gemini-session-1", |
| cwd: "/tmp/gemini", |
| }); |
| expect(geminiSessionCodec.getDisplayId?.(serialized ?? null)).toBe("gemini-session-1"); |
| }); |
| }); |
|
|
| describe("codex resume recovery detection", () => { |
| it("detects unknown session errors from codex output", () => { |
| expect( |
| isCodexUnknownSessionError( |
| '{"type":"error","message":"Unknown session id abc"}', |
| "", |
| ), |
| ).toBe(true); |
| expect( |
| isCodexUnknownSessionError( |
| "", |
| "thread 123 not found", |
| ), |
| ).toBe(true); |
| expect( |
| isCodexUnknownSessionError( |
| '{"type":"result","ok":true}', |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("opencode resume recovery detection", () => { |
| it("detects unknown session errors from opencode output", () => { |
| expect( |
| isOpenCodeUnknownSessionError( |
| "", |
| "NotFoundError: Resource not found: /Users/test/.local/share/opencode/storage/session/proj/ses_missing.json", |
| ), |
| ).toBe(true); |
| expect( |
| isOpenCodeUnknownSessionError( |
| "{\"type\":\"step_finish\",\"part\":{\"reason\":\"stop\"}}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("cursor resume recovery detection", () => { |
| it("detects unknown session errors from cursor output", () => { |
| expect( |
| isCursorUnknownSessionError( |
| "", |
| "Error: unknown session id abc", |
| ), |
| ).toBe(true); |
| expect( |
| isCursorUnknownSessionError( |
| "", |
| "chat abc not found", |
| ), |
| ).toBe(true); |
| expect( |
| isCursorUnknownSessionError( |
| "{\"type\":\"result\",\"subtype\":\"success\"}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("gemini resume recovery detection", () => { |
| it("detects unknown session errors from gemini output", () => { |
| expect( |
| isGeminiUnknownSessionError( |
| "", |
| "unknown session id abc", |
| ), |
| ).toBe(true); |
| expect( |
| isGeminiUnknownSessionError( |
| "", |
| "checkpoint latest not found", |
| ), |
| ).toBe(true); |
| expect( |
| isGeminiUnknownSessionError( |
| "{\"type\":\"result\",\"subtype\":\"success\"}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|