Skip to content

refactor: shared streaming pipes, typed wire JSON, and tooling hardening - #46

Merged
tzzs merged 10 commits into
mainfrom
refactor/streaming-tooling
Sep 20, 2026
Merged

tzzs merged 10 commits into
mainfrom
refactor/streaming-tooling

Conversation

@tzzs

@tzzs tzzs commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Streaming: pipes now share one scaffolding layer (src/streaming/), and usage capture goes through the provider usage mappers instead of parsing upstream payloads in the streaming layer — restoring the "core only knows TokenUsage" rule.
  • Server: the three client endpoints (/v1/messages, /v1/responses, /v1/chat/completions) are driven from a single route table instead of per-endpoint handlers.
  • Types: added src/json.ts (a typed JSON vocabulary: JsonValue/JsonRecord + accessors) and removed every any from src/; tsconfig gained noUnusedLocals, noUnusedParameters, noImplicitOverride, noUncheckedIndexedAccess.
  • Tooling: ESLint (flat config) with no-explicit-any as an error, npm run lint / typecheck / test:one, matching make targets, and CI split into a single lint/pack job plus a 3-OS × Node 20/22/24 test matrix.
  • Behavior change: the interactive client auto-install prompt is replaced by a plain install hint when claude/codex is missing.
  • Tests/docs: new coverage for src/json.ts, atomic writes and the shared converters (366 tests, all green); architecture, README (EN + zh-CN) and CLAUDE.md updated, and the shipped original spec moved to docs/plans/initial-product-spec.md.

Test plan

  • npm run lint — clean
  • npm test — 366 pass, 0 fail (no network, no API keys required)
  • npm pack --dry-run — 44 files, dist/src only (tests excluded)
  • Confirm agentx claude / agentx codex still launch against each upstream protocol after the streaming refactor
  • Confirm the missing-client hint prints the install command and exits non-zero

Closes the remaining items from docs/remaining-simplification-todos.md except item K.

tzzs added 10 commits September 20, 2026 20:43
Drops the interactive npm-install recovery flow (todo item G): a missing
claude/codex now prints the recommended install command and exits.
Also removes dead exports and narrows module-internal ones.
…hrough provider mappers

All eight SSE pipes now run under withSsePipe (reader/heartbeat/teardown),
emit protocol-appropriate terminal errors, and read token fields via the
providers/usage field mappers instead of parsing raw payloads in the core.
payloads/pipes/finish per (endpoint, upstream protocol) replace three
hand-written endpoint blocks that repeated the same non-stream tail.
…re path

Providers and clients hand us parsed JSON that can't be fully trusted, so
`any` let a misspelled field or a missing non-object check pass the compiler
and fail at runtime. Introduce src/json.ts (JsonRecord plus typed field
accessors) and read every payload through it, so those mistakes are now
compiler errors.

Also enables noUncheckedIndexedAccess, noUnusedLocals, noUnusedParameters and
noImplicitOverride, and makes registerCustomProvider report the single model
entry it always creates. Zero `any` remains under src/, and the strict flags
catch two real gaps: a custom-provider model read that could be undefined,
and unvalidated SQLite row fields.
Lint and type-check were the two gates a change could slip through without:
`npm test` only builds and runs the suite. Adds an ESLint flat config whose
rules cover what tsc can't see (a reintroduced `any` at a wire boundary, dead
code), a `typecheck` script, `test:one` for running one file, and a CI lint
job so the checks gate PRs. macOS joins the CI matrix because it is the
developer platform whose paths and shell-profile handling differ from Linux.

Also replaces the unused `text` accumulators in the streaming usage tests with
a shared fake response that the tests now assert forwarded content against.
Three modules had no test of their own: src/json.ts (the accessors every wire
payload is read through), src/fsutil.ts (atomicWriteFile, which is what keeps
a crashed run from leaving a torn state file behind), and the shared Anthropic
convert helpers. Their edge cases — a counter that arrives as "123", an image
source that isn't a URL, DeepSeek-only controls leaking to other providers —
are exactly the ones a caller can't see from the endpoint tests above them.

Also covers runUsageStats end to end against a temporary usage dir, and moves
lint, type-check and the package-content check into a CI job that runs once
instead of six times, with macOS joining the test matrix.
The root-level spec was the brief the project was built from, not something a
contributor reads today, so it moves to docs/plans/ marked as shipped with the
current state pointed at docs/architecture.md.

Documents what the last batch actually changed: src/json.ts as the only way to
read a wire payload, lint/typecheck/single-test commands, the CI gates, and the
completed auto-install removal. Also drops a code comment pointing at a todo
item that no longer exists.
Replacing a destination another handle still holds surfaces a transient
EPERM rather than a real failure, so back off and retry instead of
dropping the write. A failed rename now also removes its temp file, which
can otherwise leave a secret-bearing state payload on disk.
Keep PR #44's behaviour — tool-result images on every upstream protocol,
count_tokens, authenticated model catalog, per-model headers, concurrency
gate — while retaining this branch's typed wire payloads and single route
table, so the new endpoints arrive already expressed in that shape.
@tzzs
tzzs merged commit e75285b into main Sep 20, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant