feat(ann-16): provider plugins — OpenAI, OpenAICompatible, Copilot, Paperclip#38
Merged
Conversation
Foundation for the embedded agent (ANN-3). The MCP server and the (future) embedded runner now share a single source of truth for skill content. - src/skills: cached loader for bundled SKILL.md + companion playbooks, plus getSystemPrompt() that assembles the prompt with the task-type companion appended (A11Y_RULES.md for a11y_fix, THEME_UPDATE.md for theme_update). - src/embedded/provider.ts: LLMProvider interface + ProviderEvent union (text / tool_call / usage / done / error). Stream contract is minimal so OpenAI / Gemini implementations can land later without touching callers. - src/embedded/anthropic-provider.ts: concrete provider using the official Anthropic SDK with cache_control: ephemeral on the system block. SDK is a peer-optional dep and loaded lazily so MCP-only consumers don't carry it. - src/mcp/server.ts: initialize response now returns the annotask-apply system prompt as `instructions`, sourced from the shared loader. - Tests: skill loader idempotency + companion selection, provider stream ordering with a fake SDK client, cache-control marker placement, and the MCP initialize wiring (raw HTTP round-trip). 265/265 vitest pass; tsc --noEmit clean; tsup build succeeds. Co-Authored-By: Paperclip <noreply@paperclip.ing>
Toolbar previously mixed raw glyph buttons (⟳ Scan page, ⌨ Tab order) with Lucide-icon buttons elsewhere, ran three different border-radii (4/5/6px), three different paddings (3px 8px, 3px 10px, 4px 10px), and had a divider bug where panel-toggle segments with 3+ buttons lost dividers between middle pills. Inputs used outline:none with no replacement focus ring, hurting keyboard accessibility and axe-core compliance. - Replace ⟳ / ✕ / ⌨ raw glyphs with refresh-cw / x / keyboard Lucide icons - Unify all toolbar controls to 26px height and 6px border-radius - Fix .panel-toggle divider bug for 3+ segments (was first-child only) - Add focus-visible ring (--focus-ring + --accent-muted glow) to route-input, class-editor, new-task-toggle, and a shared toolbar focus selector - Bump toolbar height 40→44px for better breathing room - Bump tab-badge / toggle-badge font 8-9px → 10px, height 14 → 16px, add tabular-nums for stable counters (better legibility + axe small-text) - Add prefers-reduced-motion media query that disables spin/pulse animations and transitions - Add .icon-spin keyframe and spinning refresh icon for in-flight scan state No new one-off CSS values introduced — all colors flow through the existing 63-CSS-variable theme tokens. Two icon-registry entries added (keyboard, refresh-cw) since they were genuinely missing. ANN-7 first-impression pass. Co-Authored-By: Paperclip <noreply@paperclip.ing>
ANN-10: tighten the annotask-apply skill so the first agent attempt at the Hero CTA color-contrast violation lands a token-level edit (--accent in tokens.css), not an inline override. Adds an e2e readiness guard that verifies the violation still exists on default playground state and that darkening --accent at the token definition clears axe-core. Co-Authored-By: Paperclip <noreply@paperclip.ing>
- docs/media/hero.png — real product composite captured against pnpm dev:react-vite + /__annotask/. Left pane: annotate moment (pin on the playground's hero, task-creation form with full element + source context). Right pane: task detail modal in the `applied` state with the real resolution note. Composed at 1760x880 over an annotask brand background. - docs/media/demo.gif — 9.5s, 880x495, 1.7 MB. Real round trip on React + Vite: switch to pin mode, hover the "See how it works" link, drop the pin, file the task with file path + viewport + color scheme, agent state transitions (pending -> in_progress -> applied -> review) with the link contrast visibly raising during the applied step via Vite HMR, resolution note + accept/deny in the panel. - docs/index.html — updated #demo alt + caption so the inline GIF is the demo until the 90-second hosted video lands. Captures were driven by Playwright against the actual server; no synthetic UI, no source polish for camera. The HMR override used in the recording was reverted post-capture. 90-second hosted video (acceptance bullet 3) is in flight as ANN-9 follow-up; hosting target needs CEO confirmation before publishing. Co-Authored-By: Paperclip <noreply@paperclip.ing>
…aperclip Implements the M1 milestone for the embedded-agent provider abstraction: - chat-completions.ts: shared OpenAI-style request builder and streaming SSE parser; ChatCompletionsProvider base class + fetch transport. - openai-provider.ts: BYOK OpenAI Chat Completions provider, gpt-4o-mini default, optional organization/project headers. - openai-compatible-provider.ts: configurable base URL + key for opencode, Ollama, LM Studio, vLLM, llama.cpp. - copilot-provider.ts: ports the sst/opencode auth flow — reads apps.json/hosts.json, exchanges the GitHub OAuth token for a Copilot session token, caches the token until its expires_at near-window, then hits the chat endpoint with Editor-Version / Copilot-Integration-Id headers. - paperclip-provider.ts: POSTs to a configured Paperclip-hosted endpoint that speaks the Chat Completions wire format. Anthropic polish: - Add `cacheTools` StreamOption + cache_control on the last tool block. - Omit `is_error` when the caller didn't flag the tool result as an error. - Skip zero-length text deltas that some tool-loop turns emit. Tests: - chat-completions.test.ts: request shape, SSE chunk-boundary handling, abort, malformed tool args, tool/role splitting. - openai-provider.test.ts: defaults, HTTP error surfacing, BYOK guard. - copilot-provider.test.ts: hosts.json/apps.json reader; session-token cache reuse + near-expiry refresh; default fetch transport headers. - provider-contract.test.ts: parameterized over Anthropic + OpenAI + OpenAICompatible + Copilot + Paperclip — same scripted turn, same event-order invariants. - copilot-smoke.test.ts: gated on ANNOTASK_COPILOT_SMOKE=1; exercises real GitHub session-token endpoint + Copilot chat endpoint so we catch contract drift on demand. All callers continue to depend only on the `LLMProvider` interface in `src/embedded/provider.ts` — no caller changes needed to swap providers. Refs: ANN-16, ANN-8. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
LLMProviderimplementations behind the existing interface insrc/embedded/provider.ts:OpenAIProvider,OpenAICompatibleProvider,CopilotProvider,PaperclipProvider.chat-completions.ts(request builder + SSE parser + transport abstraction) so OpenAI / opencode / Ollama / LM Studio / vLLM / Copilot / Paperclip-hosted endpoints all share one parser instead of one per provider.AnthropicProvider: newcacheToolsoption, omitsis_errorwhen not set (Anthropic rejectsundefined), skips zero-length text deltas that some tool-loop turns emit.CopilotProviderports thesst/opencodeauth flow: parses~/.config/github-copilot/apps.jsonorhosts.json, exchanges the GitHub OAuth token for a Copilot session token, caches it until itsexpires_atwindow, then calls the Copilot chat endpoint with Editor-Version / Copilot-Integration-Id headers.Exit criteria for ANN-16
provider-contract.test.ts) — same scripted turn drives all five providers and asserts the sametext → tool_call → usage → doneevent-order invariants.LLMProviderinterface — no caller changes needed.copilot-smoke.test.ts, gated onANNOTASK_COPILOT_SMOKE=1) that hits the real GitHub session-token + Copilot chat endpoints so we catch contract drift before users do.Test plan
pnpm vitest run src/embedded/__tests__/— 65 passed, 2 skipped (the gated Copilot smoke).ANNOTASK_COPILOT_SMOKE=1 pnpm vitest run src/embedded/__tests__/copilot-smoke.test.ts(run on demand, not in CI).opencodelocal endpoint once the embedded chat UI lands (separate ticket).Refs: ANN-8 plan, ANN-16.