Download src/utils/transcript-export/index.test.ts from SaylorTwift/openhands: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/SaylorTwift/openhands/resolve/main/src/utils/transcript-export/index.test.ts
- Command line
-
hf download hf://SaylorTwift/openhands/src/utils/transcript-export/index.test.ts
-
curl -L -o index.test.ts https://huggingface.co/SaylorTwift/openhands/resolve/main/src/utils/transcript-export/index.test.ts
11 kB
| import { describe, expect, it, vi } from "vitest"; | |
| import type { | |
| ActionEvent, | |
| AgentErrorEvent, | |
| MessageEvent, | |
| ObservationEvent, | |
| } from "#/types/agent-server/core"; | |
| import type { | |
| ExecuteBashAction, | |
| MCPToolAction, | |
| TaskAction, | |
| } from "#/types/agent-server/core/base/action"; | |
| import type { | |
| ExecuteBashObservation, | |
| TaskObservation, | |
| } from "#/types/agent-server/core/base/observation"; | |
| import type { StreamingDeltaEvent } from "#/types/agent-server/core/events/streaming-delta-event"; | |
| import { SecurityRisk } from "#/types/agent-server/core/base/common"; | |
| import { eventsToHtml, eventsToMarkdown } from "."; | |
| vi.mock("#/i18n", () => ({ | |
| default: { | |
| exists: () => false, | |
| language: "en", | |
| resolvedLanguage: "en", | |
| t: (key: string, values?: Record<string, string>) => { | |
| const translations: Record<string, string> = { | |
| ACTION_MESSAGE$ACP_TOOL: "Use tool", | |
| CHAT_INTERFACE$ASSISTANT: "Assistant", | |
| COMMON$ERROR: "Error", | |
| TASK$QUERY: "Query", | |
| TASK$RESULT: "Result", | |
| TASK$SUBAGENT: "Subagent", | |
| TASK$TASK_ID: "Task ID", | |
| TRANSCRIPT_EXPORT$DEFAULT_TITLE: "Conversation transcript", | |
| TRANSCRIPT_EXPORT$MODEL: "Model", | |
| TRANSCRIPT_EXPORT$TOOL: "Tool", | |
| TRANSCRIPT_EXPORT$USER: "User", | |
| }; | |
| return translations[key] ?? values?.name ?? key; | |
| }, | |
| }, | |
| })); | |
| const timestamp = "2026-07-10T12:34:56.000Z"; | |
| const userMessage: MessageEvent = { | |
| id: "user-1", | |
| timestamp, | |
| source: "user", | |
| llm_message: { | |
| role: "user", | |
| content: [{ type: "text", text: "Please diagnose **the failure**." }], | |
| }, | |
| activated_skills: [], | |
| extended_content: [], | |
| }; | |
| const assistantMessage: MessageEvent = { | |
| id: "assistant-1", | |
| timestamp: "2026-07-10T12:35:00.000Z", | |
| source: "agent", | |
| llm_message: { | |
| role: "assistant", | |
| content: [{ type: "text", text: "The service is healthy now." }], | |
| }, | |
| activated_skills: [], | |
| extended_content: [], | |
| }; | |
| const terminalAction: ActionEvent<ExecuteBashAction> = { | |
| id: "action-1", | |
| timestamp: "2026-07-10T12:34:57.000Z", | |
| source: "agent", | |
| thought: [], | |
| thinking_blocks: [], | |
| action: { | |
| kind: "ExecuteBashAction", | |
| command: "npm test", | |
| is_input: false, | |
| timeout: null, | |
| reset: false, | |
| }, | |
| tool_name: "terminal", | |
| tool_call_id: "tool-1", | |
| tool_call: { | |
| id: "tool-1", | |
| type: "function", | |
| function: { name: "terminal", arguments: "{}" }, | |
| }, | |
| llm_response_id: "response-1", | |
| security_risk: SecurityRisk.LOW, | |
| summary: "Run the unit tests", | |
| }; | |
| const terminalObservation = ( | |
| output: string, | |
| ): ObservationEvent<ExecuteBashObservation> => ({ | |
| id: "observation-1", | |
| timestamp: "2026-07-10T12:34:58.000Z", | |
| source: "environment", | |
| tool_name: "terminal", | |
| tool_call_id: "tool-1", | |
| action_id: "action-1", | |
| observation: { | |
| kind: "ExecuteBashObservation", | |
| command: "npm test", | |
| content: [{ type: "text", text: output }], | |
| exit_code: 0, | |
| error: false, | |
| timeout: false, | |
| metadata: { | |
| exit_code: 0, | |
| pid: 123, | |
| username: "openhands", | |
| hostname: "sandbox", | |
| working_dir: "/workspace/project", | |
| py_interpreter_path: null, | |
| prefix: "", | |
| suffix: "", | |
| }, | |
| }, | |
| }); | |
| const defaultOptions = { | |
| includeToolDetails: true, | |
| includeTimestamps: true, | |
| title: "Debug session", | |
| model: "anthropic/claude-sonnet", | |
| }; | |
| describe("conversation transcript export", () => { | |
| it("exports messages and collapsed tool details as GitHub-ready Markdown", () => { | |
| const markdown = eventsToMarkdown( | |
| [ | |
| userMessage, | |
| terminalAction, | |
| terminalObservation("3 tests passed"), | |
| assistantMessage, | |
| ], | |
| defaultOptions, | |
| ); | |
| expect(markdown).toContain("# Debug session"); | |
| expect(markdown).toContain("**Model:** anthropic/claude-sonnet"); | |
| expect(markdown).toContain("## User"); | |
| expect(markdown).toContain("Please diagnose **the failure**."); | |
| expect(markdown).toContain("<details>"); | |
| expect(markdown).toContain("Run the unit tests"); | |
| expect(markdown).toContain("3 tests passed"); | |
| expect(markdown).toContain("## Assistant"); | |
| }); | |
| it("honors tool-detail and timestamp options in both formats", () => { | |
| const events = [userMessage, terminalAction, terminalObservation("passed")]; | |
| const options = { | |
| ...defaultOptions, | |
| includeToolDetails: false, | |
| includeTimestamps: false, | |
| }; | |
| const markdown = eventsToMarkdown(events, options); | |
| const html = eventsToHtml(events, options); | |
| expect(markdown).toContain("<strong>Tool:</strong> Run the unit tests"); | |
| expect(markdown).not.toContain("<details>"); | |
| expect(markdown).not.toContain("passed"); | |
| expect(markdown).not.toContain(timestamp); | |
| expect(html).not.toContain("<details>"); | |
| expect(html).not.toContain("passed"); | |
| expect(html).not.toContain("<time"); | |
| }); | |
| it("exports agent errors and uses the UI's tool-output truncation", () => { | |
| const errorEvent: AgentErrorEvent = { | |
| id: "error-1", | |
| timestamp, | |
| kind: "AgentErrorEvent", | |
| source: "agent", | |
| tool_name: "terminal", | |
| tool_call_id: "tool-error", | |
| error: "Command failed with exit code 1", | |
| }; | |
| const markdown = eventsToMarkdown( | |
| [terminalAction, terminalObservation("x".repeat(1200)), errorEvent], | |
| defaultOptions, | |
| ); | |
| expect(markdown).toContain("## Error"); | |
| expect(markdown).toContain("> Command failed with exit code 1"); | |
| expect(markdown).toContain(`${"x".repeat(1000)}...`); | |
| expect(markdown).not.toContain("x".repeat(1001)); | |
| }); | |
| it("exports assistant narration once when an observation replaces its action", () => { | |
| const narratedAction: ActionEvent<ExecuteBashAction> = { | |
| ...terminalAction, | |
| thought: [{ type: "text", text: "I checked the failing service." }], | |
| reasoning_content: "The logs point to a stale process.", | |
| }; | |
| const markdown = eventsToMarkdown( | |
| [narratedAction, terminalObservation("passed")], | |
| defaultOptions, | |
| ); | |
| expect(markdown.match(/I checked the failing service\./g)).toHaveLength(1); | |
| expect(markdown.match(/The logs point to a stale process\./g)).toHaveLength( | |
| 1, | |
| ); | |
| }); | |
| it("exports streaming reasoning with the same inline-think split as chat", () => { | |
| const streamingEvent: StreamingDeltaEvent = { | |
| id: "streaming-1", | |
| timestamp, | |
| source: "agent", | |
| kind: "StreamingDeltaEvent", | |
| reasoning_content: "Separate reasoning", | |
| content: "<think>Inline reasoning</think>Visible answer", | |
| }; | |
| const markdown = eventsToMarkdown([streamingEvent], defaultOptions); | |
| expect(markdown).toContain("Separate reasoning"); | |
| expect(markdown).toContain("Inline reasoning"); | |
| expect(markdown).toContain("Visible answer"); | |
| expect(markdown).not.toContain("<think>"); | |
| }); | |
| it("splits inline reasoning from finalized assistant messages", () => { | |
| const messageEvent: MessageEvent = { | |
| ...assistantMessage, | |
| llm_message: { | |
| role: "assistant", | |
| content: [ | |
| { | |
| type: "text", | |
| text: "<think>Reloaded reasoning</think>Reloaded answer", | |
| }, | |
| ], | |
| }, | |
| }; | |
| const markdown = eventsToMarkdown([messageEvent], defaultOptions); | |
| expect(markdown).toContain("Reloaded reasoning"); | |
| expect(markdown).toContain("Reloaded answer"); | |
| expect(markdown).not.toContain("<think>"); | |
| }); | |
| it("exports task inputs and results through their display projection", () => { | |
| const taskAction: ActionEvent<TaskAction> = { | |
| ...terminalAction, | |
| id: "task-action", | |
| tool_name: "task", | |
| tool_call_id: "task-call", | |
| tool_call: { | |
| id: "task-call", | |
| type: "function", | |
| function: { name: "task", arguments: "{}" }, | |
| }, | |
| action: { | |
| kind: "TaskAction", | |
| prompt: "Inspect the authentication flow", | |
| subagent_type: "security", | |
| description: "Inspect authentication", | |
| }, | |
| summary: "Delegate authentication review", | |
| }; | |
| const taskObservation: ObservationEvent<TaskObservation> = { | |
| id: "task-observation", | |
| timestamp: "2026-07-10T12:34:59.000Z", | |
| source: "environment", | |
| tool_name: "task", | |
| tool_call_id: "task-call", | |
| action_id: "task-action", | |
| observation: { | |
| kind: "TaskObservation", | |
| content: [{ type: "text", text: "No credential leak found." }], | |
| task_id: "task-123", | |
| subagent: "security", | |
| status: "completed", | |
| is_error: false, | |
| }, | |
| }; | |
| const markdown = eventsToMarkdown( | |
| [taskAction, taskObservation], | |
| defaultOptions, | |
| ); | |
| expect(markdown).toContain("Inspect the authentication flow"); | |
| expect(markdown).toContain("No credential leak found."); | |
| expect(markdown).toContain("task-123"); | |
| }); | |
| it("only exports the redacted display projection, never hidden raw tool arguments", () => { | |
| const redactedAction: ActionEvent<MCPToolAction> = { | |
| ...terminalAction, | |
| id: "secret-action", | |
| tool_call_id: "secret-tool", | |
| action: { | |
| kind: "MCPToolAction", | |
| data: { api_key: "**********" }, | |
| }, | |
| tool_call: { | |
| id: "secret-tool", | |
| type: "function", | |
| function: { | |
| name: "mcp_call", | |
| arguments: '{"api_key":"raw-secret-must-not-export"}', | |
| }, | |
| }, | |
| tool_name: "mcp_call", | |
| summary: "Call the configured service", | |
| }; | |
| const markdown = eventsToMarkdown([redactedAction], defaultOptions); | |
| const html = eventsToHtml([redactedAction], defaultOptions); | |
| expect(markdown).toContain("**********"); | |
| expect(html).toContain("**********"); | |
| expect(markdown).not.toContain("raw-secret-must-not-export"); | |
| expect(html).not.toContain("raw-secret-must-not-export"); | |
| }); | |
| it("neutralizes active content in Markdown and HTML", () => { | |
| const unsafeMessage: MessageEvent = { | |
| ...userMessage, | |
| llm_message: { | |
| role: "user", | |
| content: [ | |
| { | |
| type: "text", | |
| text: "<script>alert(1)</script> [click](javascript:alert(1))\n[ref]: javascript:alert(2)", | |
| }, | |
| ], | |
| }, | |
| }; | |
| const markdown = eventsToMarkdown([unsafeMessage], defaultOptions); | |
| const html = eventsToHtml([unsafeMessage], defaultOptions); | |
| expect(markdown).not.toContain("<script>"); | |
| expect(markdown).toContain("<script>"); | |
| expect(markdown).toContain("\\[click\\](javascript:alert(1))"); | |
| expect(markdown).toContain("\\[ref\\]: javascript:alert(2)"); | |
| expect(html).not.toContain("<script>"); | |
| expect(html).toContain("<script>"); | |
| }); | |
| it("renders a self-contained HTML document", () => { | |
| const html = eventsToHtml( | |
| [ | |
| userMessage, | |
| terminalAction, | |
| terminalObservation("3 tests passed"), | |
| assistantMessage, | |
| ], | |
| defaultOptions, | |
| ); | |
| expect(html).not.toMatch(/<(?:link|script)[^>]+(?:href|src)=/i); | |
| expect(html).toMatchSnapshot(); | |
| }); | |
| }); | |