openvisengine/agent-ctx/2-a-music-services-framework.md
2026-10-29 07:19:50 +00:00

5.3 KiB
Executable file

Task 2-a — v6.7 part A (server): resolve anti-spam semaphore + music-service framework

Task ID: 2-a Agent: full-stack-developer Status: COMPLETE — lint clean, tsc clean on all four files, verified live against the dev server (curl) and at the process level (ps snapshots).

Scope (ONLY these files — nothing else touched)

  1. src/lib/music-services.ts (modified)
  2. src/lib/server/music-providers.ts (NEW)
  3. src/app/api/resolve/route.ts (modified)
  4. src/components/ava/settings-tab.tsx (modified — Music services chips only)

What was built

1. src/lib/music-services.ts

  • export type StreamStatus = 'live' | 'coming-soon'
  • ServiceDef gained required streamStatus: StreamStatus + optional comingSoonNote?: string (required when coming-soon).
  • youtube / soundcloud → 'live'; spotify / apple → 'coming-soon' with notes like: "Spotify playback is coming in a later update — your saved Spotify login will be used automatically the moment it ships. Meanwhile, YouTube, SoundCloud and direct-file links work today."
  • All pre-existing fields/exports intact (SERVICES, serviceDef, serviceForUrl, isServiceId, ServiceId).

2. src/lib/server/music-providers.ts — exact exports

export type ProviderStatus = 'live' | 'coming-soon'

export interface ProviderAdapter {
  id: ServiceId | 'generic'
  label: string
  status: ProviderStatus
  comingSoonNote: string
  handlesUrl(url: string): boolean
  // commented v6.8+ extension points:
  // resolveTrack?(url, auth) / listPlaylist?(url, auth)
}

export const YTDLP_PROVIDER: ProviderAdapter    // id 'generic', live, handlesUrl → true
export const SPOTIFY_PROVIDER: ProviderAdapter  // id 'spotify', coming-soon
export const APPLE_PROVIDER: ProviderAdapter    // id 'apple', coming-soon
export function providerForUrl(url: string): ProviderAdapter
  • Domain matching mirrors serviceForUrl (host/subdomain, "www." ignored) scoped to the one service.
  • providerForUrl is status-agnostic: flip an adapter to 'live' and it still owns its URLs at the route's dispatch point.
  • Big "HOW TO SHIP A NEW SERVICE (v6.8+)" header comment: 3-step recipe (implement resolveTrack/listPlaylist with the exact payload shapes → flip streamStatus in music-services.ts + status here → extend the marked dispatch spot in the route).

3. src/app/api/resolve/route.ts

  • Coming-soon dispatch right after assertPublicHttpUrl, BEFORE cache/direct-audio/yt-dlp: { ok: false, code: 'coming-soon', service: provider.id, error: provider.comingSoonNote } (422). Spotify/Apple links never spawn yt-dlp. Generic "That site is not supported" 422 kept but without the DRM parenthetical.
  • Semaphore: YTDLP_MAX_CONCURRENCY = 2, YTDLP_QUEUE_TIMEOUT_MS = 40_000, class YtdlpSemaphore on globalThis (__avaYtdlpSemaphore). FIFO waiters, direct slot handoff in release() (active count unchanged). Acquire wrapped around runYtDlp with a held flag → release exactly once in the finally; busy → 503 "The resolver is busy right now — try again in a moment."; timed-out waiter that was granted a slot in the same tick does NOT double-release (queue membership is the single source of truth). Cache hits + direct-file links bypass the gate.
  • Fixed a pre-existing strict-type bug in buildCookieParts (pairs: string[] receiving [string,string] tuples → now [string, string][]; no runtime change).

4. src/components/ava/settings-tab.tsx

  • Coming-soon service chips get an amber "soon" badge: <span className="rounded bg-amber-400/15 px-1 py-px text-[8px] font-semibold uppercase tracking-wider text-amber-300">soon</span> (placed between name and the check/— marker). Nothing else changed; login buttons fully functional for coming-soon services.

Verification

  • bun run lint clean; bunx tsc --noEmit clean for all four files.
  • Live: spotify track/playlist + apple album → 422 coming-soon in ~6ms (zero yt-dlp); SSRF gate still first (localhost → 400); direct MP3 → 200; spotify+cookies → coming-soon before cookie staging; real YouTube resolve still passes through (bot-wall 422 as before).
  • End-to-end anti-spam: 6 concurrent resolves of the same YouTube link — ps snapshots show ≤2 top-level bin/yt-dlp processes at any moment (extra PIDs = the onefile binary's own self-exec children, not extra extractions); FIFO waves of 2; zero 503s; dev.log error-free.
  • Scratch semaphore tests (cap/FIFO/timeout/handoff/no-leak) all pass; scratch dir deleted.

Notes for the lead agent

  • apple.com is broad: the apple ServiceDef domains include apple.com, so ALL apple.com links (e.g. podcasts.apple.com, which yt-dlp's ApplePodcasts extractor could handle) now get the Apple Music coming-soon answer. Consistent with client-side matching; say the word to scope the adapter to music.apple.com only.
  • Worklog has no v6.6 section (the v6.6 music-login files were committed inside the v6.5 commit 8fa0eb9); my entry follows v6.5 directly.
  • Not-mine tsc errors (untouched): examples/websocket/* (missing deps), api/rust-scaffold (Uint8Array vs BodyInit), ava/audio-source.ts ('never' narrowing — the concurrent client agent's WIP).
  • Did NOT commit; did NOT touch version.ts / page.tsx / audio.ts / queue files / link-dialog / audio-source / queue-bar.