Parent
Part of #25 (Screen Solver v1: implementation spec), itself under the map #1.
What to build
The single module the rest of the app calls to get a coding-exercise answer out of a screenshot — the seam a second vision provider would replace later without touching any other module.
createProvider(config) → Provider, where Provider.solve(image, {signal}) → AsyncIterable<Event>. Event is one of delta{text}, done{usage}, or error{kind} with kind ∈ {auth, refusal, transient}. The system prompt is passed in and fixed at construction, not per call — it is not this ticket's job to write the prompt's content (see the spec for the full text and rules: answer-first # heading → fenced code → two-to-three short paragraphs, the literal # No exercise on screen bail, the single > **Missing:** … line), only to carry it into the request unmodified and enable prompt caching on it (ttl: 1h). Transient errors (rate limit, overload, network) retry internally inside the seam with no caller involvement; auth and refusal surface on first occurrence with no retry. Model defaults: claude-sonnet-5, effort: medium, max_tokens: 8000.
This module has no dependency on the HTTP server, capture, or config — it is testable standalone against a fake Anthropic transport.
Acceptance criteria
Blocked by
None — can start immediately.
Parent
Part of #25 (Screen Solver v1: implementation spec), itself under the map #1.
What to build
The single module the rest of the app calls to get a coding-exercise answer out of a screenshot — the seam a second vision provider would replace later without touching any other module.
createProvider(config) → Provider, whereProvider.solve(image, {signal}) → AsyncIterable<Event>.Eventis one ofdelta{text},done{usage}, orerror{kind}withkind ∈ {auth, refusal, transient}. The system prompt is passed in and fixed at construction, not per call — it is not this ticket's job to write the prompt's content (see the spec for the full text and rules: answer-first# heading→ fenced code → two-to-three short paragraphs, the literal# No exercise on screenbail, the single> **Missing:** …line), only to carry it into the request unmodified and enable prompt caching on it (ttl: 1h). Transient errors (rate limit, overload, network) retry internally inside the seam with no caller involvement;authandrefusalsurface on first occurrence with no retry. Model defaults:claude-sonnet-5,effort: medium,max_tokens: 8000.This module has no dependency on the HTTP server, capture, or config — it is testable standalone against a fake Anthropic transport.
Acceptance criteria
solve(image, {signal})against a real or faked successful response yieldsdeltaevents followed by a terminaldone{usage}with token usage populated.auth-rejecting response surfaceserror{kind: 'auth'}immediately, with no retry attempted.error{kind: 'refusal'}immediately, with no retry attempted.error{kind: 'transient'}only after retries are exhausted.signalmid-stream stops the iterable without throwing an unhandled error.Blocked by
None — can start immediately.