Skip to content

Add reasoning effort selection and streaming responses - #21

Merged
rafaeelricco merged 38 commits into
mainfrom
add-effort-selection
Apr 25, 2026
Merged

Add reasoning effort selection and streaming responses#21
rafaeelricco merged 38 commits into
mainfrom
add-effort-selection

Conversation

@rafaeelricco

Copy link
Copy Markdown
Owner

Motivation

This PR introduces features and code-quality improvements. The most significant changes are the effort selector, new metrics added to the doctor command, and refactors in model search. Enjoy!

What's New

Reasoning Effort Selection

  • New src/domain/llm/effort.ts with provider-agnostic seedProviderConfig, withModel, and selectEffortForProvider
  • EffortSlider Ink component (src/infra/ui/effort-slider.tsx) with arrow-key navigation and a chalk palette per option count
  • OpenAIEffort, AnthropicEffort, GeminiEffort enums typed against SDK reasoning unions, defaulting to medium
  • setup and model commands now run the picker after model selection, persisting effort per-provider in Config
  • New top-level commit effort command (src/cli/effort.ts) updates effort for the current provider without re-selecting a model

Streaming LLM Responses

  • Anthropic switched to messages.stream(...).finalMessage() with thinking: { type: "adaptive" } and output_config.effort
  • OpenAI uses streaming responses across api_key, openai_oauth, and ChatGPT subscription auth paths with reasoning.effort
  • Gemini migrated from @google/generative-ai to @google/genai, accumulating SSE events from streamGenerateContent?alt=sse with thinkingConfig.thinkingLevel
  • extractResponse collapsed to a provider-agnostic { text: Maybe<string> } shape; provider-specific extraction now lives next to each client
  • Hardcoded output token limits removed; Anthropic max_tokens raised to 16384

Doctor Command Diagnostics

  • New checkGitContext step adds Branch, Base, and Pull Request rows alongside runtime/platform/auth checks
  • performance.now() timing renders as Done in X.XXs after the table
  • Provider row shows model (effort) when effort is configured
  • Graceful fallback "Outside" row when invoked outside a git repository

Model Search & Picker

  • New shared src/libs/fuzzy.ts exposes match and search with token-based scoring (boundary, consecutive, gap penalties)
  • ModelSelector adopts the shared fuzzy module, replacing its inline matcher
  • model-picker refactored from new Promise(...) wrapper to Future.create, with lazy Ink/React imports documented inline

Concurrency Primitives

  • New MVar<T> in src/libs/mvar.ts — FIFO async coordination with blocking put/take/read/modify and non-blocking tryPut/tryTake/tryRead; modify rolls back on rejection
  • New Queue<T> (immutable, persistent) and MQueue<T> (mutable, transient) in src/libs/queue.ts — two-list representation, amortised O(1) enqueue/dequeue
  • CONVENTIONS.md documents completion latch (MVar<Maybe<Error>>), end-of-stream latch (MVar<null>), and atomic-state (modify) patterns

Auth & OAuth

  • resolveAuthMethod lifted into src/domain/config/config.ts so token-refresh paths preserve effort
  • New unsupportedAuth helper in src/domain/llm/auth-error.ts replaces inline Future.reject for unsupported provider/auth combinations
  • Google OAuth callback serves a dedicated success page (GOOGLE_SUCCESS_HTML) instead of the generic template
  • gh pr view lookup distinguishes a new not-found state from unavailable/unauthenticated

Shell & Git Refactors

  • execBin returns Result<CommandFailure, CommandOutput>; callers pattern-match via result.either(...) instead of inspecting exitCode
  • findCurrentBranch, findBaseBranch, findCommitMetadata, findTrackingRemoteUrl return Future<Error, Maybe<T>> so push notes degrade gracefully when context is missing
  • Base-branch resolution via reflog branch: Created from <ref> parsing with named regex helpers
  • Commit metadata parsed from NUL-separated fields using Decoder rather than ad-hoc string slicing
  • gh failure classification uses isGhUnauthenticated and isGhPrNotFound named predicates

Conventions & Tooling

  • CONVENTIONS.md documents MVar/Queue/MQueue; immutable-snapshot guidance removed
  • Prettier printWidth raised from 120 to 154; codebase reformatted accordingly
  • @anthropic-ai/sdk@0.90.0, @google/genai@1.50.1, openai@6.34.0 pinned
  • package.json version bumped to 0.2.5; CLI --version now reads from package.json via the @/* alias (extended in tsconfig.json to resolve root)
  • README updated for the commit effort command and bumped version badge
  • Obsolete PLAN.md and withAuthMethod TODO removed

Reasoning Effort Flow

graph TD
  CLI[CLI: setup / model / effort] --> Effort[selectEffortForProvider]
  Effort --> Slider[EffortSlider Ink UI]
  Slider --> Provider[ProviderConfig<br/>effort: Maybe&lt;Effort&gt;]
  Provider --> Save[saveConfig]
  Commit[CLI: commit] --> Resolve[resolveProvider / resolveAuthMethod]
  Save --> Resolve
  Resolve --> Router[generateContent router]
  Router --> Anthropic[Anthropic stream<br/>output_config.effort]
  Router --> OpenAI[OpenAI stream<br/>reasoning.effort]
  Router --> Gemini[Gemini stream<br/>thinkingConfig.thinkingLevel]
  Anthropic --> Parser[extractResponse<br/>text: Maybe&lt;string&gt;]
  OpenAI --> Parser
  Gemini --> Parser
  style Effort fill:#dbeafe,stroke:#1e40af
  style Slider fill:#dbeafe,stroke:#1e40af
  style Router fill:#fef3c7,stroke:#a16207
  style Parser fill:#dcfce7,stroke:#166534
Loading

Changed Files

File Change Type Summary
.prettierrc Modified Raise printWidth from 120 to 154
CONVENTIONS.md Modified Document MVar/Queue/MQueue; remove immutable-snapshot guidance
README.md Modified Bump version badge to 0.2.5; document commit effort command
index.ts Modified Wire EffortCommand into the CLI dispatcher
package.json Modified Bump to 0.2.5; swap @google/generative-ai@google/genai; pin AI SDKs
pnpm-lock.yaml Modified Lockfile sync for SDK migration
tsconfig.json Modified Extend @/* paths to resolve root-level imports (e.g. package.json)
src/cli/commit.ts Modified Adopt Maybe-returning git lookups; allow partial push-note metadata
src/cli/doctor.ts Modified Add git/base/PR rows and elapsed-time reporting; show effort beside model
src/cli/effort.ts Added New commit effort command updating reasoning effort for current provider
src/cli/model.ts Modified Invoke selectEffortForProvider after model selection
src/cli/parser.ts Modified Add effort command, read CLI version from package.json
src/cli/setup.ts Modified Seed ProviderConfig and prompt for effort during setup
src/domain/commit/models.ts Modified Reflow (Prettier)
src/domain/commit/prompts.ts Modified Reflow (Prettier)
src/domain/config/config.ts Modified Add OpenAIEffort/AnthropicEffort/GeminiEffort, schemas, and resolveAuthMethod
src/domain/llm/auth-error.ts Added unsupportedAuth helper for provider/auth-method mismatches
src/domain/llm/auth-resolver.ts Modified Use resolveAuthMethod to preserve effort across token refresh
src/domain/llm/effort.ts Added Provider-agnostic seedProviderConfig, withModel, selectEffortForProvider
src/domain/llm/response-parser.ts Modified Collapse to { text: Maybe<string> }; remove provider branches
src/domain/llm/router.ts Modified Pass ProviderConfig through; clients own effort wiring
src/infra/auth/google.ts Modified Use GOOGLE_SUCCESS_HTML; reflow OAuth bracket
src/infra/auth/openai.ts Modified Adopt Result-based execBin; reflow templates
src/infra/auth/templates.ts Modified Add Google-specific OAuth success page
src/infra/git/repo.ts Modified Add find* Maybe lookups, reflog-based getBaseBranch, NUL-separated metadata, Result execBin migration
src/infra/github/pr.ts Modified New not-found PrLookup variant; named regex helpers; consume CommandFailure
src/infra/llm/anthropic.ts Modified Streaming messages.stream(...).finalMessage() with adaptive thinking and output_config.effort
src/infra/llm/gemini.ts Modified Migrate to @google/genai; SSE streaming with thinkingLevel
src/infra/llm/openai.ts Modified Streaming responses across all auth methods with reasoning.effort
src/infra/shell.ts Modified execBin returns Result<CommandFailure, CommandOutput>
src/infra/storage/config.ts Modified Refactor token-update helpers via resolveAuthMethod
src/infra/ui/effort-picker.ts Added Per-provider effort prompts with lazy Ink/React imports
src/infra/ui/effort-slider.tsx Added EffortSlider Ink component (arrow-key nav, chalk palette)
src/infra/ui/model-picker.ts Modified Adopt Future.create; lazy Ink/React import comments expanded
src/infra/ui/model-selector.tsx Modified Use shared libs/fuzzy for filtering
src/infra/ui/push-note.ts Modified Render with Maybe metadata; add base line; handle not-found PR case
src/libs/future.ts Modified Reflow (Prettier)
src/libs/fuzzy.ts Added Token-scored fuzzy search shared by model selector
src/libs/helpers/object.ts Modified Reflow (Prettier)
src/libs/json/decoder.ts Modified Reflow (Prettier)
src/libs/json/encoder.ts Modified Reflow (Prettier)
src/libs/json/schema.ts Modified Reflow (Prettier)
src/libs/maybe.ts Modified Reflow (Prettier)
src/libs/mvar.ts Added MVar<T> async coordination primitive with modify rollback
src/libs/queue.ts Added Queue<T> immutable persistent queue and MQueue<T> companion
src/libs/remote-data.ts Modified Reflow (Prettier)
src/libs/router.ts Modified Reflow (Prettier)
src/libs/time.ts Modified Reflow (Prettier)
src/libs/trampoline.ts Modified Reflow (Prettier)
src/libs/types.ts Modified Reflow (Prettier)

Testing & Feedback

  • Run commit-tools setup, commit-tools model, and commit-tools effort and confirm the slider appears in each flow and the chosen value persists in the config file.
  • Run commit-tools --version and confirm it reflects the version from package.json (currently 0.2.5).
  • Run commit-tools doctor inside and outside a git repository — verify the new Branch/Base/Pull Request rows and the Done in X.XXs footer.
  • Exercise commit generation against each provider (anthropic, openai, gemini) with both api_key and OAuth auth paths to validate streaming and the new effort flag.
  • Push to a branch with and without an open GitHub PR to verify the push-note rendering with Maybe-typed metadata and the new not-found classification.
  • Confirm commit-tools startup time is unaffected on non-interactive entrypoints (lazy Ink/React import path).

If you find any bugs or have recommendations for improvements, please open an issue and assign it to me.

- Upgrade `@anthropic-ai/sdk` from ^0.87.0 to 0.90.0.
- Replace `@google/generative-ai` with `@google/genai` 1.50.1.
- Upgrade `openai` from ^6.25.0 to 6.34.0.
- Pin all three AI SDK versions instead of using caret ranges.
- Add transitive dependencies including `protobufjs`, `p-retry`, and `google-auth-library`.
- Introduce per-provider `effort` field on `ProviderConfig` for OpenAI, Anthropic, and Gemini, with SDK-backed value lists and compile-time exhaustiveness checks in `domain/config/config`.
- Add `domain/llm/effort` module with param builders (`openaiReasoningParam`, `anthropicAdaptiveParam`, `anthropicEnabledParam`, `geminiLevelConfig`, `geminiBudgetConfig`) and helpers to seed and update provider config.
- Add `infra/llm/effort-fallback` with `tryWithEffort` to retry API calls when the SDK rejects effort parameters.
- Wire effort into `infra/llm/openai`, `infra/llm/anthropic`, and `infra/llm/gemini` call paths, including adaptive/enabled thinking stages for Anthropic and level/budget stages for Gemini.
- Migrate Gemini integration from `@google/generative-ai` to `@google/genai` and adjust `response-parser` to the new SDK shape.
- Add `infra/ui/effort-picker` and `infra/ui/effort-slider` Ink component, and prompt for effort during `setup` and `model` commands.
- Preserve `effort` when refreshing OAuth tokens in `auth-resolver` and `infra/storage/config`.
- Add `repo.getBaseBranch` using reflog parsing with fallback to `origin/HEAD`, and a `not-found` variant to `pr.PrLookup`.
- Extend `doctor` with a git context section showing branch, base, and open PR rows.
- Make push metadata fields optional in `cli/commit` and `infra/ui/push-note`, warning per field and skipping the note when nothing is available.
- Surface the selected effort next to the model in `doctor` output via `renderModelInfo`.
- Add `findCurrentBranch`, `findBaseBranch`, `findCommitMetadata`, and `findTrackingRemoteUrl` to return `Nothing` instead of failing on missing git metadata.
- Update commit and doctor flows to use the new lookup helpers and remove duplicated local error recovery logic.
- Let push note rendering consume `Maybe` values directly with `maybe(...)` instead of checking `Just` instances manually.
- Remove fallback warning handling around push note metadata collection now that lookups recover gracefully.
- Change `ExecResult` to `Result<CommandFailure, CommandOutput>` with explicit success and failure branches.
- Introduce `CommandOutput` and `CommandFailure` types carrying stdout, stderr, and an `Error` with exit-code context.
- Update all `execBin` callers in `repo.ts` and `pr.ts` to consume results via `result.either(...)` instead of inspecting `exitCode`.
- Add `commandFailureMessage` helper to build error messages from stderr, stdout, or the underlying error.
- Extract `formatCommitOutput` helper for post-commit stdout formatting.
- Rename `reflog-cmd-failed.stderr` to `message` and use `commandFailureMessage` when constructing it.
- Update `classifyFailure` in `pr.ts` to accept a `CommandFailure` and match patterns against combined failure text.
- Wrap spawn `error` events with a clearer "Failed to start process" message.
- Bump `printWidth` from 120 to 154 in `.prettierrc` and update the `CONVENTIONS.md` example accordingly.
- Reflow `pnpm-lock.yaml` `resolution` entries onto single lines under the new width.
- Collapse multi-line function signatures, conditionals, and expressions across `src/` to fit the wider line limit.
- Replace provider-tagged `RawResponse` union with a single `{ text: Maybe<string> }` type in `domain/llm/response-parser`.
- Move Anthropic content extraction (`extractAnthropicText`) into `infra/llm/anthropic.ts`.
- Move OpenAI stream text extraction (`extractStreamText`) into `infra/llm/openai.ts`.
- Wrap Gemini SDK and REST text into `Maybe<string>` via `fromOptional` at the call sites.
- Drop `finalizeText`, provider-specific types, and the switch-based dispatch from the domain layer.
- Remove `anthropicAdaptiveParam`, `anthropicEnabledParam`, and the `BUDGET_BY_EFFORT` map from `src/domain/llm/effort.ts`.
- Drop the multi-stage effort fallback (`adaptive`/`enabled`/`off`) and `tryWithEffort` usage in the Anthropic client.
- Consolidate API key and setup token request building into a single `buildParams` helper that always enables adaptive thinking and defaults effort to `medium`.
- Extract `buildSetupTokenSystem` to compose the Claude Code system prompt with the optional user instruction via `Maybe`.
- Replace the generic `toError` with inline error wrapping that surfaces a clearer `Failed to create Anthropic message` message.
- Replace custom `TextBlock` type with `Anthropic.ContentBlock` and `Anthropic.TextBlock` in `extractAnthropicText`.
- Switch `buildSetupTokenSystem` to return `Anthropic.TextBlockParam[]` and inline the Claude Code system block.
- Drop the standalone `claudeCodeBlock` constant.
- Increase `BASE_MAX_TOKENS` from 4096 to 16384.
- Remove `effort-fallback` module and its `tryWithEffort` retry mechanism.
- Add shared `unsupportedAuth` helper in `domain/llm/auth-error` for consistent rejection of unsupported auth methods.
- Simplify Gemini integration to use a single `thinkingLevel` config with a `MEDIUM` default, dropping multi-stage attempts and budget-based fallbacks.
- Simplify OpenAI integration to build request params directly via SDK types, replacing reasoning fallback attempts with a single call.
- Remove now-unused `openaiReasoningParam`, `geminiLevelConfig`, and `geminiBudgetConfig` from `domain/llm/effort`.
- Use `absurd` for exhaustive auth method handling in Anthropic, Gemini, and OpenAI providers.
- Standardize error messages for failed Gemini and OpenAI requests and introduce a shared `BASE_MAX_TOKENS` cap.
- Measure and display elapsed time in `doctor` run output.
- Pass `elapsedMs` through `renderTable` to report duration on success.
- Strip `refs/remotes/<remote>/` prefix in `normalizeBranchRef`.
- Fall back to default remote branch in `getBaseBranch` when the resolved base matches the current branch.
- Stop sending `maxOutputTokens` for Gemini SDK and OAuth requests.
- Stop sending `max_output_tokens` for OpenAI API key and OAuth requests.
- Guard OpenAI stream text extraction against non-string candidates before trimming.
- Add immutable `Queue` with amortised O(1) `enqueue` and `dequeue` using front/back list pair.
- Add mutable `MQueue` wrapper around the immutable `Queue`.
- Add `MVar` for state-based coordination of concurrent operations, blocking on full or empty state.
- Support blocking `put`, `take`, `modify`, `modify_`, and `read` operations with FIFO fairness.
- Provide non-blocking `tryPut`, `tryTake`, and `tryRead` variants returning success or `Maybe` results.
- Update `generateContentWithApiKey` to use the SDK's `generateContentStream` method.
- Update `generateContentWithOAuth` to call the `streamGenerateContent` endpoint and parse SSE responses.
- Add `accumulateSSEText` and `extractSSEEventText` helpers to handle stream data processing.
- Remove redundant provider check in `getAuthCredentials`.
- Wrap single-parameter arrow function params in parentheses across `mvar.ts` and `queue.ts`.
- Remove trailing commas from function call argument lists.
- Collapse some multi-line expressions onto a single line.
- Replace terse inline comments with JSDoc on `MVar`, `Queue`, and `MQueue` methods, covering blocking semantics, return types, and rollback behaviour of `modify`.
- Rewrite the `MVar` section of `CONVENTIONS.md` with concrete usage patterns: completion/error latch, end-of-stream latch via `AsyncIterable`, and atomic state mutation through `modify`.
- Add a new `Queue` & `MQueue` conventions section explaining the persistent vs. transient distinction and the amortised O(1) two-list representation.
- Drop `BoundedBuffer` guidance from the conventions in favour of the updated `MVar` patterns.
- Replace newline-based git log format with `%x00`-separated fields to avoid ambiguity from multi-line subjects.
- Introduce `commitMetadataDecoder` along with `nonEmptyString` and `isoDate` decoders for stricter field validation.
- Add `splitCommitFields` helper to map raw git output to a structured record before decoding.
- Surface decoder error messages in the rejected `Error` for better diagnostics.
- Replace `new Promise` inside `Future.attemptP` with `Future.create` chained after async imports in both `effort-picker` and `model-picker`.
- Remove error-based cancellation workaround in `effort-picker`; resolve with `Nothing` directly on cancel instead of rejecting and catching in `chainRej`.
- Return `unmount` as the teardown function from `Future.create` for proper cleanup.
- Relocate `resolveAuthMethod` from `auth-resolver.ts` to `config.ts` so it can be reused across modules.
- Refactor `updateGoogleTokens` and `updateOpenAITokens` to use exhaustive `switch` statements with `absurd` for type-safe handling of all auth methods.
- Replace ad-hoc object spreads with `resolveAuthMethod` when persisting refreshed tokens.
- Document why `selectEffort` uses dynamic `import()` to avoid Ink/React startup cost on non-interactive CLI paths.
- Add the same rationale to `selectModelInteractively`, calling out scripted runs, `--yes` flows, piped stdin, and git hook integrations.
- Add an optional notice area to the success auth template.
- Add a Google OAuth notice explaining that terminal completion can take 1-2 minutes.
- Use `GOOGLE_SUCCESS_HTML` for the Google OAuth callback response.
- Add reusable `match` and `search` helpers for scored fuzzy matching.
- Update the model selector to rank results across model IDs and descriptions.
- Register the `effort` CLI command and route it to a new interactive effort update flow.
- Persist the selected reasoning effort for the current provider in the stored AI config.
- Update help output, version reporting, and README docs for `commit effort`.
- Read `package.json` version in `showVersion` instead of hardcoding the CLI version.
- Extend the `@/*` path alias to resolve root-level imports such as `package.json`.
@rafaeelricco rafaeelricco self-assigned this Apr 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd4514cd8b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/libs/mvar.ts Outdated
Comment thread src/infra/ui/effort-picker.ts Outdated
@rafaeelricco
rafaeelricco merged commit 779ebe0 into main Apr 25, 2026
4 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