Skip to content

feat: add optional topic param to /api/chat and /api/chat/generate#192

Merged
sweetmantech merged 2 commits intotestfrom
sweetmantech/myc-4108-api-apichat-and-apigenerate-add-optional-topic-param
Feb 3, 2026
Merged

feat: add optional topic param to /api/chat and /api/chat/generate#192
sweetmantech merged 2 commits intotestfrom
sweetmantech/myc-4108-api-apichat-and-apigenerate-add-optional-topic-param

Conversation

@sweetmantech
Copy link
Copy Markdown
Contributor

@sweetmantech sweetmantech commented Feb 3, 2026

Summary

  • Add optional topic parameter to /api/chat and /api/chat/generate endpoints
  • When provided, uses the given topic for new chat rooms instead of auto-generating from message content
  • Topic is ignored if room already exists (matches docs specification)

Changes

  • lib/chat/validateChatRequest.ts: Add topic to request schema
  • lib/chat/setupConversation.ts: Pass topic through to room creation
  • lib/chat/createNewRoom.ts: Use provided topic or fall back to generated title
  • Route files: Updated JSDoc comments

Test plan

  • Send chat request without topic - should auto-generate title as before
  • Send chat request with topic - should use provided topic
  • Send chat request with topic and existing roomId - topic should be ignored

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Users can now optionally provide a topic when creating new chat rooms. When specified, the topic will be used as the conversation title instead of auto-generating it from the message content.
  • Documentation

    • Updated API documentation across chat endpoints to describe the new optional topic parameter and its behavior.

Add topic parameter to chat request schema. When provided, uses the
given topic for new chat rooms instead of auto-generating one from
the message content. Topic is ignored if room already exists.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
recoup-api Ready Ready Preview Feb 3, 2026 1:45am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 3, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

An optional topic parameter is added to the chat API, threading through validation and conversation setup layers. When creating a new chat room, the provided topic becomes the conversation title if no message-derived title exists. Documentation is updated in route files.

Changes

Cohort / File(s) Summary
API Documentation
app/api/chat/generate/route.ts, app/api/chat/route.ts
Documentation-only updates describing the optional topic parameter in request body comments.
Chat Infrastructure
lib/chat/validateChatRequest.ts, lib/chat/setupConversation.ts, lib/chat/createNewRoom.ts
Topic parameter threaded through validation schema, conversation setup, and room creation. Title resolution now prefers provided topic over message-derived titles.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

A topic blooms in chat's design,
Through validation, setup, fine,
Threading softly layer by layer,
Each room now wears a custom prayer,
Where words become the conversation's name. 🎭✨

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sweetmantech/myc-4108-api-apichat-and-apigenerate-add-optional-topic-param

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 3, 2026

Braintrust eval report

Catalog Opportunity Analysis Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Catalog_availability 47.6% (+46pp) 4 🟢 -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 41.08s (+1.57s) 3 🟢 2 🔴

Catalog Songs Count Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Llm_calls 4 (+0) - -
Tool_calls 0 (+0) - -
Errors 2.67 (+2.67) - 3 🔴
Llm_errors 0.67 (+0.67) - 2 🔴
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 12.85s (-3.29s) 3 🟢 -

First Week Album Sales Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Llm_calls 1 (+0) - -
Tool_calls 0 (+0) - -
Errors 1 (+1) - 4 🔴
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 16.79s (+1.67s) 2 🟢 2 🔴

Memory & Storage Tools Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Tools_called 0% (+0pp) - -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 16.45s (-1.61s) 1 🟢 -

Monthly Listeners Tracking Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
AnswerSimilarity 79.1% (+1pp) 1 🟢 2 🔴
Llm_calls 2 (+0) - -
Tool_calls 0 (+0) - -
Errors 0.8 (+0.8) - 2 🔴
Llm_errors 0.4 (+0.4) - 2 🔴
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 14.98s (+2.68s) 1 🟢 4 🔴

Search Web Tool Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Llm_calls 3 (+0) - -
Tool_calls 0 (+0) - -
Errors 1.64 (+1.64) - 11 🔴
Llm_errors 0.64 (+0.64) - 7 🔴
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 24.51s (-0.1s) 6 🟢 5 🔴

Social Scraping Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Tools_called 0% (+0pp) - -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 25.81s (+0.92s) 2 🟢 4 🔴

Spotify Followers Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Llm_calls 3 (+0) - -
Tool_calls 0 (+0) - -
Errors 1.8 (+1.8) - 5 🔴
Llm_errors 0.8 (+0.8) - 4 🔴
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 16.52s (+0.59s) 3 🟢 2 🔴

Spotify Tools Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Tools_called 0% (+0pp) - -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 36.75s (+3.84s) - 2 🔴

TikTok Analytics Questions Evaluation (HEAD-1770083091)

Score Average Improvements Regressions
Question_answered 0% (+0pp) - -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 18.01s (+3.71s) - 2 🔴

- Test topic is passed through schema validation
- Test topic is passed to setupConversation
- Test topic is passed to createNewRoom when creating new room
- Test topic is ignored when room already exists

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sweetmantech sweetmantech merged commit c1eeb0f into test Feb 3, 2026
3 of 5 checks passed
sweetmantech added a commit that referenced this pull request May 7, 2026
Addresses review feedback on PR #522 and the "missing from open-agents"
audit:

User-flagged review comments:
- SRP: extract `buildSource` to lib/sandbox/buildSource.ts
- YAGNI: drop `isNewBranch` from POST /api/sandbox — chat never sets it
  (note: docs PR #192 still documents it; will open follow-up docs PR
   to drop from sandbox.json)
- SRP: extract `isoToEpochMs` to lib/sandbox/isoToEpochMs.ts
- SRP: extract `buildLifecycle` to lib/sandbox/buildLifecycle.ts
- SRP: extract `isSandboxActive` to lib/sandbox/isSandboxActive.ts
- KISS: rename lib/supabase/sessions/updateSessionSandboxState.ts ->
  updateSession.ts, generalize signature to (id, TablesUpdate<"sessions">)

Tier 1 correctness gaps from the open-agents comparison:
1. GitHub URL validation via parseGitHubRepoUrl in
   validateCreateSandboxBody — bad URLs now return a clean 400 instead
   of falling through to a confusing 502 from the sandbox provider
2. Service GitHub token plumbed into connectSandbox options via new
   lib/github/getServiceGithubToken.ts — private repos can now clone
3. snapshot_url + snapshot_created_at cleared on fresh provision so
   GET /api/sandbox/status no longer surfaces stale snapshot URLs from
   prior runs

TDD red -> green:
- 5 new unit-test files for the extracted helpers (buildSource,
  isoToEpochMs, buildLifecycle, isSandboxActive, getServiceGithubToken)
- updateSession.test.ts replaces updateSessionSandboxState.test.ts
- Updated validator + handler tests for the contract changes
  (drop isNewBranch, add bad-URL 400 cases, assert githubToken plumbing,
   assert snapshot_url/snapshot_created_at: null in update payload)
- Confirmed RED before each implementation
- Suite: 2499 -> 2516 (+17 net new tests), pnpm lint:check clean

Files net delta: -241 / +70 lines (extractions + handler shrinks)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sweetmantech added a commit that referenced this pull request May 7, 2026
…open-agents (#522)

* feat(sandbox): port POST /api/sandbox + GET /api/sandbox/status from open-agents

Implements the two session-scoped sandbox endpoints required to drive
the chat "loading sandbox..." UX on session entry — matching the
contract documented in recoupable/docs#192 (now merged on main).

POST /api/sandbox provisions or resumes a Sandbox via the abstraction
inlined in #507. When sessionId is supplied, the deterministic
sandboxName ensures resume idempotency and the resolved sandbox state
is persisted onto the session row (sandbox_state, lifecycle_state =
"active", lifecycle_version bumped, sandbox_expires_at,
last_activity_at) so subsequent GET /api/sandbox/status calls report
the sandbox as active.

GET /api/sandbox/status is DB-only — reads the session row, computes
status as "active" when sandbox_state is set and not expired (10s
buffer to match open-agents), otherwise "no_sandbox". hasSnapshot is
true when snapshot_url is set. Mirrors the lifecycle envelope shape
from open-agents so the frontend cutover is byte-identical.

Files follow existing api conventions:
- Route shells in app/api/sandbox/ delegate to handlers in lib/sandbox/
- Auth via validateAuthContext (Privy Bearer or x-api-key)
- Validation via Zod (validateCreateSandboxBody)
- Supabase ops in lib/supabase/sessions/ (one fn per file)
- Error envelope { status: "error", error } matches sessions PRs

TDD red → green:
- 7 new test files added covering validator, helper, Supabase wrapper,
  both handlers, and the two route shells
- 30 new tests, all passing (was 2461, now 2491)
- pnpm lint:check clean

Out of scope (deferred to follow-up PRs):
- Org-snapshot lookup / kickBuildOrgSnapshotWorkflow (cold-start opt)
- Skill installation (installSessionGlobalSkills)
- Lifecycle workflow kick (no workflow infra in api yet)
- DELETE /api/sandbox + PUT /api/sandbox/snapshot (no UI callers
  identified during the open-agents grep audit)
- /api/sandbox/{extend,activity,reconnect,snapshot} sub-routes — to
  follow once these two land and the chat UX is validated

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sandbox): treat type-stub sandbox_state as no_sandbox in /status

Smoke test against the preview deployment caught a regression that
defeated the entire loading-state UX this PR exists to enable: GET
/api/sandbox/status reported `"active"` immediately after POST
/api/sessions, before any sandbox had been provisioned.

Root cause: POST /api/sessions (PR #515) inserts `sandbox_state` as
the type stub `{ type: "vercel" }`. The previous `isSandboxActive`
check `if (!row.sandbox_state) return false` saw a truthy object and
fell through; with `sandbox_expires_at = null` (no expiry yet), the
function returned true.

Fix: introduce `hasRuntimeSandboxState(state)` that distinguishes the
type stub from real runtime metadata by requiring a non-empty
`sandboxName` (set by `getSessionSandboxName(sessionId)` in POST
/api/sandbox and preserved by the abstraction's `getState()`).
Mirrors open-agents' equivalent helper.

TDD red → green:
- Regression test pinned to the exact production scenario
  (sandbox_state = {type:"vercel"}, sandbox_expires_at = null,
  lifecycle_state = "provisioning") asserting status=no_sandbox
- Companion test asserting status=active once sandboxName is set
- 6 unit tests for the new helper covering null/undefined, scalars,
  type stub, populated state, and empty-string sandboxName edge case
- Confirmed RED before implementing, GREEN after
- Suite: 2491 → 2499 (+8 new tests), pnpm lint:check clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(sandbox): SRP/KISS extractions + Tier 1 correctness fixes

Addresses review feedback on PR #522 and the "missing from open-agents"
audit:

User-flagged review comments:
- SRP: extract `buildSource` to lib/sandbox/buildSource.ts
- YAGNI: drop `isNewBranch` from POST /api/sandbox — chat never sets it
  (note: docs PR #192 still documents it; will open follow-up docs PR
   to drop from sandbox.json)
- SRP: extract `isoToEpochMs` to lib/sandbox/isoToEpochMs.ts
- SRP: extract `buildLifecycle` to lib/sandbox/buildLifecycle.ts
- SRP: extract `isSandboxActive` to lib/sandbox/isSandboxActive.ts
- KISS: rename lib/supabase/sessions/updateSessionSandboxState.ts ->
  updateSession.ts, generalize signature to (id, TablesUpdate<"sessions">)

Tier 1 correctness gaps from the open-agents comparison:
1. GitHub URL validation via parseGitHubRepoUrl in
   validateCreateSandboxBody — bad URLs now return a clean 400 instead
   of falling through to a confusing 502 from the sandbox provider
2. Service GitHub token plumbed into connectSandbox options via new
   lib/github/getServiceGithubToken.ts — private repos can now clone
3. snapshot_url + snapshot_created_at cleared on fresh provision so
   GET /api/sandbox/status no longer surfaces stale snapshot URLs from
   prior runs

TDD red -> green:
- 5 new unit-test files for the extracted helpers (buildSource,
  isoToEpochMs, buildLifecycle, isSandboxActive, getServiceGithubToken)
- updateSession.test.ts replaces updateSessionSandboxState.test.ts
- Updated validator + handler tests for the contract changes
  (drop isNewBranch, add bad-URL 400 cases, assert githubToken plumbing,
   assert snapshot_url/snapshot_created_at: null in update payload)
- Confirmed RED before each implementation
- Suite: 2499 -> 2516 (+17 net new tests), pnpm lint:check clean

Files net delta: -241 / +70 lines (extractions + handler shrinks)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(sandbox): drop branch from POST /api/sandbox contract

YAGNI/KISS per review feedback — chat always works off the repo's
default branch, so the explicit `branch` input adds no value.

- Drop `branch` from createSandboxBodySchema
- Inline the now-trivial source object in createSandboxHandler
  (`{ repo: body.repoUrl }`) and delete `lib/sandbox/buildSource.ts`
  + its test
- Read `currentBranch` for the response from the sandbox handle's
  own `currentBranch` property (whatever the SDK actually checked
  out), falling back to "main" — no longer derives from a request
  field that no longer exists

Tests: TDD red -> green.
- Validator test asserts `branch` is stripped from the body even
  if a client still sends it
- Handler test asserts `currentBranch` in the response comes from
  `sandbox.currentBranch` (mocked to "release/v2") not from input
- Suite stays at 2516 (-1 from buildSource.test deletion +1 new
  currentBranch test)

Pairs with docs PR recoupable/docs#194 (merged) which already
removed `branch` and `isNewBranch` from the published OpenAPI spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sweetmantech added a commit that referenced this pull request May 7, 2026
…open-agents (#522) (#524)

* feat(sandbox): port POST /api/sandbox + GET /api/sandbox/status from open-agents

Implements the two session-scoped sandbox endpoints required to drive
the chat "loading sandbox..." UX on session entry — matching the
contract documented in recoupable/docs#192 (now merged on main).

POST /api/sandbox provisions or resumes a Sandbox via the abstraction
inlined in #507. When sessionId is supplied, the deterministic
sandboxName ensures resume idempotency and the resolved sandbox state
is persisted onto the session row (sandbox_state, lifecycle_state =
"active", lifecycle_version bumped, sandbox_expires_at,
last_activity_at) so subsequent GET /api/sandbox/status calls report
the sandbox as active.

GET /api/sandbox/status is DB-only — reads the session row, computes
status as "active" when sandbox_state is set and not expired (10s
buffer to match open-agents), otherwise "no_sandbox". hasSnapshot is
true when snapshot_url is set. Mirrors the lifecycle envelope shape
from open-agents so the frontend cutover is byte-identical.

Files follow existing api conventions:
- Route shells in app/api/sandbox/ delegate to handlers in lib/sandbox/
- Auth via validateAuthContext (Privy Bearer or x-api-key)
- Validation via Zod (validateCreateSandboxBody)
- Supabase ops in lib/supabase/sessions/ (one fn per file)
- Error envelope { status: "error", error } matches sessions PRs

TDD red → green:
- 7 new test files added covering validator, helper, Supabase wrapper,
  both handlers, and the two route shells
- 30 new tests, all passing (was 2461, now 2491)
- pnpm lint:check clean

Out of scope (deferred to follow-up PRs):
- Org-snapshot lookup / kickBuildOrgSnapshotWorkflow (cold-start opt)
- Skill installation (installSessionGlobalSkills)
- Lifecycle workflow kick (no workflow infra in api yet)
- DELETE /api/sandbox + PUT /api/sandbox/snapshot (no UI callers
  identified during the open-agents grep audit)
- /api/sandbox/{extend,activity,reconnect,snapshot} sub-routes — to
  follow once these two land and the chat UX is validated



* fix(sandbox): treat type-stub sandbox_state as no_sandbox in /status

Smoke test against the preview deployment caught a regression that
defeated the entire loading-state UX this PR exists to enable: GET
/api/sandbox/status reported `"active"` immediately after POST
/api/sessions, before any sandbox had been provisioned.

Root cause: POST /api/sessions (PR #515) inserts `sandbox_state` as
the type stub `{ type: "vercel" }`. The previous `isSandboxActive`
check `if (!row.sandbox_state) return false` saw a truthy object and
fell through; with `sandbox_expires_at = null` (no expiry yet), the
function returned true.

Fix: introduce `hasRuntimeSandboxState(state)` that distinguishes the
type stub from real runtime metadata by requiring a non-empty
`sandboxName` (set by `getSessionSandboxName(sessionId)` in POST
/api/sandbox and preserved by the abstraction's `getState()`).
Mirrors open-agents' equivalent helper.

TDD red → green:
- Regression test pinned to the exact production scenario
  (sandbox_state = {type:"vercel"}, sandbox_expires_at = null,
  lifecycle_state = "provisioning") asserting status=no_sandbox
- Companion test asserting status=active once sandboxName is set
- 6 unit tests for the new helper covering null/undefined, scalars,
  type stub, populated state, and empty-string sandboxName edge case
- Confirmed RED before implementing, GREEN after
- Suite: 2491 → 2499 (+8 new tests), pnpm lint:check clean



* refactor(sandbox): SRP/KISS extractions + Tier 1 correctness fixes

Addresses review feedback on PR #522 and the "missing from open-agents"
audit:

User-flagged review comments:
- SRP: extract `buildSource` to lib/sandbox/buildSource.ts
- YAGNI: drop `isNewBranch` from POST /api/sandbox — chat never sets it
  (note: docs PR #192 still documents it; will open follow-up docs PR
   to drop from sandbox.json)
- SRP: extract `isoToEpochMs` to lib/sandbox/isoToEpochMs.ts
- SRP: extract `buildLifecycle` to lib/sandbox/buildLifecycle.ts
- SRP: extract `isSandboxActive` to lib/sandbox/isSandboxActive.ts
- KISS: rename lib/supabase/sessions/updateSessionSandboxState.ts ->
  updateSession.ts, generalize signature to (id, TablesUpdate<"sessions">)

Tier 1 correctness gaps from the open-agents comparison:
1. GitHub URL validation via parseGitHubRepoUrl in
   validateCreateSandboxBody — bad URLs now return a clean 400 instead
   of falling through to a confusing 502 from the sandbox provider
2. Service GitHub token plumbed into connectSandbox options via new
   lib/github/getServiceGithubToken.ts — private repos can now clone
3. snapshot_url + snapshot_created_at cleared on fresh provision so
   GET /api/sandbox/status no longer surfaces stale snapshot URLs from
   prior runs

TDD red -> green:
- 5 new unit-test files for the extracted helpers (buildSource,
  isoToEpochMs, buildLifecycle, isSandboxActive, getServiceGithubToken)
- updateSession.test.ts replaces updateSessionSandboxState.test.ts
- Updated validator + handler tests for the contract changes
  (drop isNewBranch, add bad-URL 400 cases, assert githubToken plumbing,
   assert snapshot_url/snapshot_created_at: null in update payload)
- Confirmed RED before each implementation
- Suite: 2499 -> 2516 (+17 net new tests), pnpm lint:check clean

Files net delta: -241 / +70 lines (extractions + handler shrinks)



* refactor(sandbox): drop branch from POST /api/sandbox contract

YAGNI/KISS per review feedback — chat always works off the repo's
default branch, so the explicit `branch` input adds no value.

- Drop `branch` from createSandboxBodySchema
- Inline the now-trivial source object in createSandboxHandler
  (`{ repo: body.repoUrl }`) and delete `lib/sandbox/buildSource.ts`
  + its test
- Read `currentBranch` for the response from the sandbox handle's
  own `currentBranch` property (whatever the SDK actually checked
  out), falling back to "main" — no longer derives from a request
  field that no longer exists

Tests: TDD red -> green.
- Validator test asserts `branch` is stripped from the body even
  if a client still sends it
- Handler test asserts `currentBranch` in the response comes from
  `sandbox.currentBranch` (mocked to "release/v2") not from input
- Suite stays at 2516 (-1 from buildSource.test deletion +1 new
  currentBranch test)

Pairs with docs PR recoupable/docs#194 (merged) which already
removed `branch` and `isNewBranch` from the published OpenAPI spec.



---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant