Skip to content

feat: [AI-6949] add session transcript REST endpoint for datamates extension#941

Merged
saravmajestic merged 3 commits into
mainfrom
feat/AI-6949-session-transcript-endpoint
Jun 16, 2026
Merged

feat: [AI-6949] add session transcript REST endpoint for datamates extension#941
saravmajestic merged 3 commits into
mainfrom
feat/AI-6949-session-transcript-endpoint

Conversation

@altimate-harness-bot

@altimate-harness-bot altimate-harness-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds GET /session/:sessionID/transcript REST endpoint to altimate-code server
  • Returns the session conversation as a formatted markdown transcript (text/plain; charset=utf-8)
  • Reuses formatTranscript from cli/cmd/tui/util/transcript.ts (same function powering the TUI /export command)
  • Supports optional query params: thinking (bool), toolDetails (bool), assistantMetadata (bool) — all default false

Endpoint

GET /session/:sessionID/transcript[?thinking=true&toolDetails=true&assistantMetadata=true]

Response: text/plain; charset=utf-8 — markdown string starting with # <session title>

Example response:

# My session title

**Session ID:** ses_xxx
**Created:** 6/15/2026, 7:04:58 AM
**Updated:** 6/15/2026, 7:04:58 AM

---

Test plan

  • Created session via POST /session, called GET /session/:id/transcript
  • Response is 200 OK, Content-Type: text/plain; charset=UTF-8
  • Body starts with # (markdown session title)
  • Query params (thinking, toolDetails, assistantMetadata) accepted without error
  • Verify with a session that has messages (tool calls, reasoning) to confirm option flags work

Files changed

  • packages/opencode/src/server/routes/session.ts — added import + route handler

Requested by @saravmajestic via harness


Summary by cubic

Adds a new REST endpoint to export a session’s conversation as a markdown transcript for easy sharing and offline viewing. Implements Linear AI-6949.

  • New Features

    • Adds GET /session/:sessionID/transcript returning markdown (text/plain; charset=utf-8); reuses formatTranscript to match the TUI /export; supports thinking, toolDetails, and assistantMetadata flags (default false); includes tests for 200/404, empty sessions, and flag behavior.
  • Bug Fixes

    • Fixes boolean parsing for query flags using z.preprocess so ?thinking=false is respected; aligns import to @/cli/...; strengthens tests to assert reasoning is included/excluded.

Written for commit b7c1757. Summary will update on new commits.

Review in cubic

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@saravmajestic saravmajestic marked this pull request as ready for review June 15, 2026 07:13
@saravmajestic saravmajestic changed the title feat: [AI-6949] add session transcript REST endpoint feat: [AI-6949] add session transcript REST endpoint for datamates extension Jun 15, 2026

@ralphstodomingo ralphstodomingo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the session-transcript endpoint. Mirrors the existing Session.get/messages pattern well. One boolean-parsing footgun plus the test gap that hides it.

validator(
"query",
z.object({
thinking: z.coerce.boolean().optional().default(false).meta({ description: "Include reasoning/thinking parts" }),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (latent): z.coerce.boolean() uses JS Boolean() semantics, so ?thinking=false, =0, =off all coerce to true — only absent/empty yields false. For flags presented as real toggles defaulting to false, the only way to get false is to omit the param. Latent because the current consumer (#366) only ever sends =true or omits, but it'll bite any client passing explicit =false. Suggest z.enum(["true","false"]).optional().default("false").transform(v => v === "true") (or a stringbool helper) for all three flags.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b7c1757 using z.preprocess(v => v === "false" ? false : v, z.coerce.boolean()) — handles the "false" string explicitly before coerce runs. Preserves the z.boolean() output type so the rest of the route chain stays unchanged.

fn: async () => {
const session = await Session.create({ title: "Options Session" })
await addUserMessage(session.id, "test message")
const app = Server.Default()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concern: this flag test only sends =true and asserts status 200 + # prefix, so it can't catch the coercion bug above and doesn't prove the flags change output. formatTranscript emits _Thinking:_ / **Input:** / **Output:** — build a session with reasoning + tool parts and assert =true includes those markers while =false/default exclude them. That also closes the PR's own unchecked "verify flags work" item.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b7c1757. Replaced the smoke test with a session that has a real reasoning part. Now asserts ?thinking=true includes _Thinking:_, ?thinking=false excludes it (catches the coerce bug), and default also excludes it. 17 assertions, all pass.

@@ -1,4 +1,5 @@
import { Hono } from "hono"
import { formatTranscript } from "../../cli/cmd/tui/util/transcript"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this imports formatTranscript via deep relative path ../../cli/cmd/tui/util/transcript, but the only other server→cli import (routes/tui.ts) uses the @/cli/… alias, and this file already uses @/ elsewhere. Use @/cli/cmd/tui/util/transcript (or @tui/util/transcript). Server importing from cli/ is a layering smell, but there's precedent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b7c1757 — changed to @/cli/cmd/tui/util/transcript.

…trengthen flag tests

- Use z.preprocess to handle explicit "false" string correctly: z.coerce.boolean()
  coerces Boolean("false") = true, so ?thinking=false was silently treated as true
- Fix import to use @/cli alias consistent with other server routes
- Update flag test to build a session with real reasoning parts and assert
  both ?thinking=true includes _Thinking:_ and ?thinking=false excludes it
@dev-punia-altimate

Copy link
Copy Markdown
Contributor

❌ Tests — Failures Detected

TypeScript — 15 failure(s)

  • connection_refused
  • timeout
  • permission_denied
  • parse_error
  • network_error
  • auth_failure
  • rate_limit
  • internal_error
  • empty_error
  • connection_refused
  • timeout
  • permission_denied
  • parse_error
  • network_error
  • auth_failure

Next Step

Please address the failing cases above and re-run verification.

cc @app/altimate-harness-bot

@saravmajestic saravmajestic merged commit ae69109 into main Jun 16, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants