music: officer-music sidecar + /api/music streaming proxy

Adds an officer-music sidecar that owns an audio-streaming HTTP server, and a
thin authenticating proxy on the platform. All processing (path resolution,
byte-range streaming, ffprobe duration) is in the sidecar; the platform only
authenticates and forwards.

App-facing contract (handoff):
  GET /api/music/stream?path=<home-relative path>&token=<jwt>
    - auth via userMiddleware (Bearer or ?token= for media elements)
    - 200 full / 206 on Range, with Accept-Ranges, Content-Length,
      Content-Range, Content-Type, and X-Audio-Duration (seconds, ffprobe)
    - path resolved within HOME_DIR, traversal-guarded (400); 404 if missing
  Purpose: stream + seek without pre-downloading the whole file — the app can
  read X-Audio-Duration instead of scanning for VBR duration.

Pieces:
- sidecar/music/{index.ts,stream-audio.ts}: Bun.serve on a random port, /stream
  + /health, duration cached by path+mtime; reports its port via a new
  music:server sidecar event on connect.
- api/music/{sidecar-server.ts,router.ts}: capture the port; reverse-proxy
  /api/music/* → sidecar, streaming status + headers through.
- protocol.ts music:server event; hono.ts mounts /api/music; ecosystem adds
  officer-music.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 02:39:50 +00:00
co-authored by Claude Opus 4.8
parent d6b4b900ff
commit 9d01000578
7 changed files with 253 additions and 0 deletions
+2
View File
@@ -51,6 +51,8 @@ export type SidecarEvent =
| { type: 'opencode:event'; sessionKey: string; event: ChatEvent }
| { type: 'opencode:session'; sessionKey: string; sessionId: string }
| { type: 'opencode:error'; id: string; error: string }
// Music — the sidecar reports where its audio-streaming HTTP server is listening (random port) on connect
| { type: 'music:server'; port: number }
// Generic
| { type: 'error'; id?: string; error: string };