Feature: ai-gateway-v2#24
Conversation
…te provider parsers (#25) Refactor @hypaware/ai-gateway into a generic HTTP/SSE capture and row-storage owner. All client/protocol semantics move into adapter plugins through the new full exchange projector API. Capability bumped to breaking 2.0.0. New surface: - registerUpstreamPreset(preset) — adapter-owned routing via optional preset.match() (replaces hardcoded detectProvider()). - registerExchangeProjector(projector) — adapter-owned exchange → conversation messages projection. - localEndpoint, getClient, listClients, registerClient unchanged. Removed from the capability surface: registerExchangeContextProjector and registerMessageEnricher. Projector dispatch in message_projector.js: - Matching projectors sorted by descending priority, then registration order. - First successful non-empty projection wins. - Throws / undefined / invalid output: warn and skip. - No projector matches: zero rows, but pass-through telemetry (aigw.exchange log, aigw.exchange_bytes meter) still fires. Fallback identity (post-projection): when an adapter returns normalized messages without message_id, compute hash id, linearize previous_message_id (only if absent), and stamp attributes.gateway.identity_source = "gateway_fallback". previous_message_id supplied as [] is preserved (root marker). Projector-supplied identity is authoritative — never overridden. Deleted from the gateway package: - Anthropic HTTP+SSE parsing (message_projector.js). - OpenAI Chat and Responses + SSE reconstruction. - Codex header / path / turn-metadata parsing. - Generic context-fallback path, provider inference, Claude-specific attributes.client.claude_version. - /_hypaware/session-context endpoint (proxy.js). - ClaudeSessionContext, localContextForRequest, request-body session lookup, recorder cwd/git_branch injection. Schema unchanged: ai_gateway_messages, SCHEMA_VERSION = 4, proxy_messages_v4. No migration, no backfill. Tests: - ai-gateway-session-context.test.js deleted (endpoint removed). - ai-gateway-message-projector.test.js rewritten to cover the new dispatch surface (priority/seq ordering, error/empty skipping, fallback identity stamping, schema strip) plus the schema column contract. Provider-specific assertions moved out of the gateway and will return in phases 2/3 plugin tests. - ai_gateway_passthrough smoke updated to assert: capability registered at 2.0.0, no cache.append, zero rows for the dev_run_id, and pass-through telemetry (aigw.exchange log with rows_written=0, aigw.exchange_bytes meter) still fires. This phase intentionally breaks Claude + Codex capture (their manifests still require ^1.0.0) until phases 2 and 3 land adapter exchange projectors on the same integration branch.
Port OpenAI Chat, OpenAI Responses (JSON + SSE), and ChatGPT Codex (SSE + turn metadata) projection into a single `AiGatewayExchangeProjector` owned by `@hypaware/codex`. Bumps the plugin's capability requirement to `hypaware.ai-gateway@^2.0.0` and adds the new projector to the activate() wiring. Projector behavior: - Matches `/v1/chat/completions`, `/v1/responses`, `/v1/models`, `/backend-api/codex/*`, or any request tagged with the `x-codex-turn-metadata` header. - Parses Chat-shaped requests (`messages: []`) and Responses-shaped requests (`input: ...`) into normalized user-role blocks. Tool calls and tool results map onto `tool_use` / `tool_result` blocks. - Reconstructs streamed assistant messages from `response.output_text.delta` events; falls back to body-level `output_text` or `output` arrays. - Projects Codex headers + turn metadata into both first-class columns (`cwd`, `client_version`, `entrypoint`, `user_type`, `permission_mode`, `is_sidechain`, `request_id`, `prompt_id`) and the `attributes.codex.*` namespace (thread/session/turn ids, workspace, git origin + commit, sandbox, originator, window id). - Always stamps `attributes.codex.identity_source = "gateway_fallback"` because the projector never supplies `message_id` today; the gateway computes hashes and linearizes history for symmetry with the @hypaware/claude adapter. - Reserves a Codex SQLite / log-reader hook behind the `HYPAWARE_CODEX_SQLITE_READS=1` env flag; today no readers are shipped (no-op stub), keeping the real reader out of this bead. Smoke + tests: - `gateway_codex_capture` flow activates `@hypaware/codex` and renames its local fake upstreams to `local-openai` / `local-chatgpt` so they don't collide with the plugin's `openai` / `chatgpt` preset names. The local config entries appear first in the merged routing table and outrank the plugin presets at routing time. - New `test/plugins/codex-exchange-projector.test.js` covers match surface, OpenAI Chat tool-call mapping, Responses SSE reconstruction, Codex header/metadata projection, subagent flipping is_sidechain, identity_source stamping symmetry, log-reader gating, and conversation-id fallback determinism. Gateway core remains free of provider parsing (only `chatgpt-account-id` appears as a default-redacted header, which is a security setting rather than parsing logic). \`npm test\` passes (280 tests). \`npm run typecheck\` clean. \`npm run smoke -- gateway_codex_capture\` and \`npm run smoke -- ai_gateway_passthrough\` both green. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…xt (#30) Phase 2 of the ai-gateway-v2 feature flow. Ports the Anthropic HTTP+SSE parsing that phase 1 deleted from the gateway into the `@hypaware/claude` adapter as a single `AiGatewayExchangeProjector`, and moves the session-context channel from the (now-removed) HTTP endpoint to a JSONL file under the plugin's state directory. Plugin (`@hypaware/claude`, bumped to 2.0.0): - New `projector.js` registers `AiGatewayExchangeProjector` and `AiGatewayUpstreamPreset` against `hypaware.ai-gateway@^2.0.0`. Match surface: `/v1/messages*` path OR `anthropic-version` / `x-api-key` / `Authorization: Bearer sk-ant-*` header signature. - `anthropic.js` carries the ported Anthropic Messages HTTP body parse, SSE assistant reconstruction (`message_start`, `content_block_*`, `message_delta`, `message_stop`), conversation id / user id / system text / tools / client-version extraction. - `transcripts.js` reads `<HOME>/.claude/projects/<repo>/<session>.jsonl` (or the exact `transcript_path` when the session-context record supplies one) for native DAG identity. Matches by uuid → projected message: `message_id = provider_uuid = uuid`, `previous_message_id = parentUuid ? [parentUuid] : []`. - `session_context.js` reads/writes `<stateDir>/session-context.jsonl`. `pickLatestMatching` prefers `transcript_path` then `session_id`; newest matching line wins. - On a missing transcript the projector returns messages without `message_id`, the gateway computes hash identity + stamps `attributes.gateway.identity_source="gateway_fallback"`, and the projector adds `attributes.claude.identity_source="gateway_fallback"` for Claude-specific debuggability. - `settings.js` swaps the managed hook command from `--port <port>` to `--state-file <abspath>`; the `_hypaware` marker carries both port (still needed for `ANTHROPIC_BASE_URL`) and state_file. - Deleted `enricher.js` (gateway 2.0 removed `registerMessageEnricher`; the projector inlines what enrichment did). CLI: - `hyp claude-hook session-context --state-file <path>` appends one JSONL record per hook event (session_id, cwd, optional transcript_path / git_branch / ts). No daemon required — the file is read at projection time. Caller-side cleanup of stale `^1.0.0` ranges (phase 1 missed these): - `src/core/cli/core_commands.js` and `src/core/cli/walkthrough.js` require `^2.0.0`. - `src/core/config/validate.js` first-party metadata updated for ai-gateway (provides 2.0.0), claude and codex (both require ^2.0.0; codex projector landed in #28 alongside this). - `walkthrough_*_to_first_query` smokes require `^2.0.0`; their remaining contract assertions (zero ai_gateway_messages rows because they POST to /v1/echo, not /v1/messages) are phase-4 work per the shared-harness scope. Tests: - `test/plugins/claude-projector-identity.test.js` covers native uuid identity, root `[]` previous_message_id, missing-log fallback marker, cwd/git_branch propagation from the state file, and the three `match()` paths (header signature, /v1/messages path, none). - `test/plugins/claude-session-context-hook.test.js` is the hook→state-file→projector roundtrip — replaces the deleted `ai-gateway-session-context.test.js`. - `test/core/command-dispatch.test.js` updated for the `--state-file` flag and the fake gateway capability bumped to 2.0.0 surface (with `registerExchangeProjector`). - `hypaware-core/smoke/flows/gateway_claude_capture.js` rewritten to activate both plugins, stage a JSONL transcript fixture, drive the hook, and assert both native-DAG and fallback-identity rows. - `claude_attach_detach` smoke updated for the new marker shape and the `--state-file` hook command line. 277 tests / typecheck / lint clean. Smokes green: - gateway_claude_capture - claude_attach_detach - ai_gateway_passthrough - daemon_foreground_start_stop - core_boot_noop Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds dedicated gateway-package unit tests for the post-2.0 capability API and proxy routing now that provider-specific projection logic lives in the Claude and Codex plugins: - test/plugins/ai-gateway-api.test.js covers registerExchangeProjector validation (name/match/project required, missing-priority preserved), registration-order _seq assignment, plus registerUpstreamPreset, registerClient/getClient/listClients, and localEndpoint shape. - test/plugins/ai-gateway-proxy-routing.test.js covers compileUpstreams ordering (priority desc, then prefix length, then registration order), base_url validation, matchUpstream first-match-wins + short-circuit + throw-as-non-match + path-prefix fallback + lowercased array-valued header view. - test/plugins/ai-gateway-message-projector.test.js gains three tests covering invalid-shape-skipped, all-projectors-fail returns zero rows with aigw.projector_error/_invalid_output/message_projection_skipped warnings, and skipping a non-matching projector without calling its project(). proxy.js exports matchUpstream and compileUpstreams so the routing tests can exercise them in isolation; the runtime call sites are unchanged. The gateway package no longer asserts Anthropic/OpenAI/Codex protocol details - phase 1 already stripped them and phases 2/3 added the plugin-side coverage in claude-projector-identity, claude-session- context-hook, and codex-exchange-projector. Pass-through telemetry on the all-projectors-fail path is gated end-to-end by the existing ai_gateway_passthrough smoke (no projector -> zero rows + aigw.exchange log emitted). Final check: npm test (319 pass), ai_gateway_passthrough, gateway_codex_capture, and gateway_claude_capture smokes all green. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dual-agent review —
|
| Source | Finding (severity, evidence) | Intersects |
|---|---|---|
| codex.md | Contract & Interface Fidelity — part_type wrongly derived from provider_type (user/assistant) for transcript-matched Claude messages (major, high) — ai-gateway/src/message_projector.js:434, claude/src/projector.js:174, claude/src/transcripts.js:192 |
Targets (message_projector.js −1273 net dispatcher rewrite; claude/projector.js new file with createClaudeExchangeProjector); Concurrency surface (Claude transcript walk in transcripts.js) |
| codex.md | Behavioral Correctness — listener startup compiles/validates config before merging presets, so preset-only gateways can fail at boot (major, medium) — ai-gateway/src/source.js:95, ai-gateway/src/source.js:151, ai-gateway/src/config.js:6 |
Targets (ai-gateway/source.js, ai-gateway/config.js); Config field chain (config.js declares priority, match, path_prefix for new upstream contract) |
| codex.md | Change Impact / Blast Radius — presets silently overwrite same-name TOML upstreams; local/enterprise endpoints get replaced by adapter defaults (major, high) — ai-gateway/src/source.js:180, ai-gateway/src/source.js:195, hypaware-core/smoke/flows/gateway_claude_capture.js:105 |
Targets (ai-gateway/source.js; gateway_claude_capture.js smoke flow); Direct callers (gateway_claude_capture.js exercises the rewritten attach + projector wiring path) |
| claude.md | selectCodexWorkspace no longer accepts recorded cwd as tiebreaker; tagged rows attribute to V8-first workspace key, not the user's actual workspace (unmarked severity → treated as major per rubric) — hypaware-core/plugins-workspace/codex/src/exchange-projector.js:503-514, called at :410 |
Targets (codex/src/exchange-projector.js, +798, new file housing createCodexExchangeProjector) |
| claude.md | readSessionContext invoked per-exchange with no cache/tail-N/rotation; daemon I/O grows O(records-ever-seen) (unmarked severity → treated as major per rubric) — hypaware-core/plugins-workspace/claude/src/projector.js:110-120, hypaware-core/plugins-workspace/claude/src/session_context.js:62-86 |
Targets (claude/projector.js, claude/session_context.js — both new files); Concurrency surface (session-context.jsonl: concurrent writes, unbounded growth, reader scans end-to-end); Risks #5 (blast-radius explicitly flags unbounded growth and recommends a soak smoke) |
Blast radius
-
Capability bump is a hard break for any out-of-tree adapter still pinning
^1.0.0. Internal plugins are bumped, but the dep-graph will reject third-party plugins on first boot post-upgrade. There is no shim or deprecation period — a third-party adapter is just incompatible. -
Zero-projector default is silent.
ai_gateway_passthrough.jsasserts the gateway emits zero rows when no projector is registered. Operators running the gateway plugin alone (no@hypaware/claudeor@hypaware/codex) will see emptyai_gateway_messagesand no warning. The old built-in projector behavior is gone. -
Hook-command format change is breaking for hand-installed v1 hooks. Users who copied the old
hyp claude-hook session-context --port <int>invocation into their~/.claude/settings.jsonoutside ofattachwill keep invoking that form. The new binary still parses--portbut never writes to a state file, so session context silently stops being captured. Re-runningattachwill rewrite to the new form, but there is no first-boot upgrade hint that this is needed. -
Recorder no longer fills
cwd/git_branchdirectly. Those columns now depend on the session-context hook having fired before the captured exchange. The first exchange of a brand-new Claude session — beforeSessionStartis appended — will be projected without these fields. Historical analytics overcwd/git_branchwill see NULL gaps that did not exist in 1.x. No documentation of this gap surfaces in.feature-flow/ai-gateway-v2.md. -
session-context.jsonlgrows unbounded. No rotation, no compaction, no cap. Long-lived Claude users will accumulate thousands of entries; the projector'sreadSessionContextSafereads the file end-to-end every exchange, picking the newest match. Worst-case projection latency is O(history size). At minimum, a soak smoke that drives 10k hook events and asserts bounded projection latency would catch a regression here. -
Upstream/projector routing has no conflict detection. Two adapters can register
match()functions that both return true for the same request — for upstreams, first-registered wins (after priority sort); for projectors, first matching by priority wins. Silent wrong-routing is possible if a third-party adapter overlaps an Anthropic preset. There's no test that two presets/projectors can't legally co-match. -
client_attach_idempotent.jssmoke is NOT in PR scope despite exercising the rewritten attach path. If it wasn't re-run against this branch as part of CI, attach/detach regressions for the new state-file contract are uncovered. Worth confirming the smoke green-lights againstd647fb9e. -
Synchronous
readdirSyncon hot path.transcripts.js:walkJsonlFilesuses sync directory reads inside the projector. On a system with hundreds of Claude projects this blocks the daemon event loop per-exchange. Not a bug in itself, but a latency regression compared to 1.x where transcript-walking didn't happen on the projection path. -
Codex review: (no findings reported)
Claude review
Code review
No issues found. Checked for bugs and CLAUDE.md compliance.
🤖 Generated with Claude Code
Reports: /Users/phil/testcity/.gc/pr-pipeline/reviews/pr-24 · Bead: hy-i31mv · Blast-radius: hy-99x61
Auto-generated by
/feature-launchto host the integration branch for feature ai-gateway-v2.Work beads file individual sub-PRs into this branch via the refinery feature-flow loop. When all work + review + ship beads complete, the ship formula flips this PR to ready-for-review.
See
/feature-flowfor the flow architecture.