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-botaltimate-harness-botBot 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
@saravmajesticsaravmajestic changed the title feat: [AI-6949] add session transcript REST endpointfeat: [AI-6949] add session transcript REST endpoint for datamates extensionJun 15, 2026

@ralphstodomingoralphstodomingo 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
ContributorAuthor

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
ContributorAuthor

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
ContributorAuthor

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 mainJun 16, 2026
18 checks passed
anandgupta42 added a commit that referenced this pull request Jun 19, 2026
Actionable fixes surfaced by the multi-persona release review of the 8 PRs
shipping in v0.8.8. All are small, behavior-preserving except where noted.
- #937 `run`: treat a blank/whitespace `ALTIMATE_NON_INTERACTIVE` as unset
(`.trim()` guard) so a stray `export ALTIMATE_NON_INTERACTIVE=` no longer
silently reintroduces the headless hang the PR fixed.
- #941 transcript endpoint: broaden query-boolean coercion so `0`/`no`/`off`
(case-insensitive), not just `"false"`, map to false for `thinking`,
`toolDetails`, `assistantMetadata`.
- #940 serve startup upgrade: jitter the settle delay (base + random*base*5,
~1-6s) so a fleet restarting together doesn't stampede the unauthenticated
GitHub releases API (60 req/hr/IP) in the same instant.
- #893 `addMcpToConfig`: refuse to overwrite a config whose JSON/JSONC cannot
be parsed (parse-guard), mirroring the read helpers, instead of letting
jsonc-parser best-effort clobber a recoverable file.
- #893 `normalizeMcpConfig`: preserve `updatedAt` on local/remote MCP entries
instead of dropping it during normalization.
- #933 `stripAnsi`: match the general CSI escape family (cursor/erase), not
just SGR color codes, so dbt spinner output is fully cleaned.
Updates the release-validation tests that pinned the prior (now-corrected)
behavior: the `run` guard string, the jittered scheduler delay window, and
the transcript boolean falsiness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for freeto 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

@dev-punia-altimate@ralphstodomingo@saravmajestic