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

105 lines
5.3 KiB
Markdown
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
```ts
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.