Add ElectronHub DevPass provider#5255
Conversation
7c60636 to
3e38417
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c606365ea
ℹ️ 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".
roboomp
left a comment
There was a problem hiding this comment.
P2: coherent new ElectronHub DevPass provider, with package changelogs, focused login/catalog tests, and generated catalog data; still a new provider surface requiring maintainer sign-off, and prior related PR #4872 was closed rather than landed.
One blocking inline finding: dynamic discovery can disable tool calling from metadata.function_call === false even though ElectronHub documents all DevPass models as function-calling capable.
Verification run locally: bun test packages/ai/test/electronhub-login.test.ts packages/catalog/test/electronhub-provider.test.ts passed (11 tests); bun run check:types passed in packages/ai and packages/catalog. Thanks for the contribution.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 76115e6b34
ℹ️ 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".
76115e6 to
2cc02be
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cc02becc7
ℹ️ 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".
2cc02be to
3fd2ea4
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3fd2ea4019
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88d363be2d
ℹ️ 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".
- Add gpt-oss-120b:dev, glm-5.2:dev, gemma-4-31b-it:dev, and qwen3.6-27b:dev to ELECTRONHUB_DEVPASS_STATIC_MODELS alongside the existing kimi-k2.6:dev / minimax-m2.7:dev entries, matching the live DevPass catalog confirmed via extensive OMP + direct API smoke testing on all six models. - Force reasoning: true on every DevPass model in the discovery mapper (docs.electronhub.ai/billing/coding-plan states all DevPass models support reasoning; qwen3.6-27b:dev's live metadata reports reasoning:false but has been observed returning non-zero reasoning_tokens). - Add a pricing.plan === "devpass" fallback signal to isElectronHubDevpassModel, since two of the six live records omit metadata.devpass_only entirely. - Keep (do not drop) the existing GLM/Z.AI thinking compat (thinkingFormat: "zai", reasoningDisableMode: "omit", reasoningContentField: "reasoning_content") and the metadata.function_call === false -> supportsTools: false guard, both load-bearing given ElectronHub's DevPass integration has changed behavior mid-flight during testing. - Add a live-snapshot fixture (test/fixtures/electronhub-devpass-models.json) plus contract tests asserting the static seed's ids/contextWindow match it, addressing the "unverifiable extra models" review concern raised on PR can1357#4872. - Regenerate models.json (electronhub section only; unrelated provider churn from this run was discarded).
roboomp (blocking): mapElectronHubDevpassModel no longer sets supportsTools: false from metadata.function_call === false. ElectronHub's Coding Plan docs state every DevPass model supports function calling; trusting a possibly stale/bad /v1/models record over that would silently strip tool use from a coding agent, the same failure mode the reasoning:true override already guards against. Codex (P2): electronHubCompatForModel now forces thinkingFormat: "openai" for Qwen-family DevPass ids (qwen3.6-27b:dev today). Without an explicit override, buildOpenAICompat's id-based detection classifies any "qwen"-named id into the native Qwen dialect (top-level enable_thinking boolean), but ElectronHub's gateway expects the generic OpenAI-shaped reasoning_effort surface for every proxied model regardless of backend -- the same reason Fireworks already carries an isQwen && isFireworks -> "openai" carve-out in buildOpenAICompat. Applied to both the static seed entry and the dynamic-discovery mapper so the override holds pre- and post-login. Regenerated models.json (electronhub section only) and added tests covering both fixes.
…nHub
Codex (P2): thinkingFormat "zai" made getModelDefinedEfforts
(model-thinking.ts) restrict glm-5.2:dev to the Z.ai-native two-tier
high/max effort ladder and let the unsupported "max" value reach the
wire. ElectronHub's /v1/chat/completions documents a flat
reasoning_effort enum (none/minimal/low/medium/high/xhigh) for every
proxied model -- confirmed directly against
docs.electronhub.ai/api-reference/chat/completions's OpenAPI schema.
Fix: switch glm-5.2:dev's ElectronHub compat to thinkingFormat
"openai" (matching the rest of the DevPass fleet), which resolves to
the wider five-tier ladder, and add reasoningEffortMap: { max: "xhigh" }
so the remaining top tier still lands on a value ElectronHub actually
accepts instead of "max".
Moving off "zai" also silently drops its reasoning_content
continuation-replay behavior -- openai-completions.ts only fires that
branch for thinkingFormat === "zai". ElectronHub is exactly the
documented "custom proxy setup" escape hatch for this
(compat/openai.ts's PROXY_OPENAI_COMPAT_PROVIDERS comment), so
glm-5.2:dev now opts back in explicitly via
replayReasoningContent: true to keep cross-turn reasoning replay
unchanged by the dialect switch.
Regenerated models.json (electronhub section only) and extended the
GLM contract test to cover the new dialect, effort remap, and replay
flag.
A prior rebase onto main picked up an unrelated upstream change that stamps stored api_key credentials with a `source: "login"` field (see openrouter-login.test.ts, auth-storage-api-key-login.test.ts, auth-storage-email-dedupe.test.ts for the same shape already updated elsewhere). This PR's electronhub-login test predates that change and still asserted the old two-field shape, so it started failing silently -- packages/ai wasn't in this PR's routine verification scope until now. Bring the assertion in line with the established sibling-provider pattern.
…ub disable Codex (P2): when a caller disables reasoning (Thinking Off), the shared ElectronHub compat fell back to generic per-dialect behavior instead of an ElectronHub-specific mapping: - Five of six DevPass models default to thinkingFormat "openai" -> reasoningDisableMode "lowest-effort", which sends the ladder's floor tier (e.g. reasoning_effort: "minimal") -- reasoning is reduced, not disabled. - glm-5.2:dev explicitly used "omit", which drops the field entirely, falling back to ElectronHub's own server-side default rather than honoring the caller's disable choice. ElectronHub's /v1/chat/completions documents reasoning_effort: "none" as its own distinct wire value (below "minimal") for fully hiding reasoning output. Neither existing behavior reaches it. No existing OpenAIReasoningDisableMode sends an explicit "none" -- added a new "reasoning-effort-none" mode: - packages/catalog/src/types.ts: new union member. - packages/ai/src/providers/openai-shared.ts: encodeChatCompletions DisabledReasoning sets reasoning_effort = "none" for the new mode. - packages/catalog/src/provider-models/openai-compat.ts: set on the shared ELECTRONHUB_OPENAI_COMPAT base so all six DevPass models inherit it (dynamic discovery included, since mapElectronHubDevpass Model routes through the same helper); dropped glm-5.2:dev's now- redundant "omit" override so it inherits the same base value. Considered reusing the existing reasoningEffortMap mechanism (as Fireworks does: FIREWORKS_REASONING_EFFORT_MAP remaps its lowest selectable tier's wire value to "none") instead of a new disable mode, but rejected it -- that only works because Fireworks' own vocabulary treats "none" as a rename of its floor tier. ElectronHub's "none" is a genuinely separate, lower value than "minimal" in a six-tier ladder; remapping the floor tier to "none" would incorrectly disable reasoning whenever a caller explicitly selected the lowest non-off effort. Regenerated models.json (electronhub section only). Added a shared reasoningDisableMode assertion across all six bundled DevPass models in packages/catalog/test/electronhub-provider.test.ts, and a wire- level test in packages/ai/test/openai-completions-disable-reasoning .test.ts asserting reasoning_effort: "none" reaches the request body when disableReasoning: true.
An earlier rebase onto main (which had cut real 16.3.x releases in the interim) mechanically placed this branch's changelog additions inside now-released `## [16.3.12]` sections instead of `## [Unreleased]`, also leaving a duplicate `### Added` heading in each file. Confirmed via `git log -S` that every displaced line traces only to this branch's own commits, never to genuine upstream 16.3.12 content, so moving them is a correction, not an edit to released history. Moved to `## [Unreleased]` and removed the duplicate headings in: - packages/ai/CHANGELOG.md (ElectronHub `/login` entry; also added a new entry for the reasoning-effort-none disable mode) - packages/catalog/CHANGELOG.md (ElectronHub DevPass catalog entry; also added entries for the accumulated review-round dialect fixes: GLM/Qwen thinkingFormat, reasoningEffortMap, reasoning-effort-none, and the function_call guard removal) - packages/coding-agent/CHANGELOG.md (ELECTRONHUB_DEV_API_KEY docs entry) Verified with `bun scripts/fix-changelogs.ts` — zero further changes, confirming the sections are already in the format the release tooling expects.
…rt-none
Codex (P2): minimax-m2.7:dev is a MiniMax M2-family id, so
isMinimaxM2FamilyModelId backfills thinking.requiresEffort, and
normalizeMandatoryReasoningOptions (packages/ai/src/stream.ts) clamps
a Thinking Off request to the ladder's lowest effort ("low") before
the compat-level reasoning-effort-none branch ever runs. Correct
observation: the new disable mapping genuinely is unreachable for
this one bundled model.
Investigated whether that's an actual regression before changing
anything. Live-probed ElectronHub's real /v1/chat/completions directly
for minimax-m2.7:dev across reasoning_effort "none", "minimal", "low",
and an omitted field (2 trials each): reasoning content length stayed
in the same noisy 200-600+ character range for every value, with no
measurable suppression from "none" versus "low" (330 vs 250 chars on
the first pair, no consistent gap across repeats). The gateway does
not make this architecturally mandatory-reasoning model honor "off"
either -- confirming the family.ts predicate's existing rationale
("Underlying model accepts only low|medium|high ... 400s on minimal,
xhigh, or none") describes a real constraint that ElectronHub's proxy
doesn't paper over, and matching the exact same requiresEffort:true
treatment minimax-m2.7 already gets on Fireworks (see the "MiniMax M2
is a reasoning-first architecture" precedent in
packages/catalog/test/model-thinking.test.ts).
Setting thinking.suppressWhenOff to route "off" to reasoning_effort:
"none" here would not achieve the reviewer's actual goal (suppressing
reasoning) and would break consistency with every other MiniMax M2
host in the catalog for a change that is empirically a no-op. Left the
mandatory-reasoning clamp as-is; added a code comment at the model
definition (packages/catalog/src/provider-models/openai-compat.ts)
documenting the investigation, and a regression test locking in
requiresEffort: true / suppressWhenOff not set for minimax-m2.7:dev
specifically, so this isn't "fixed" into a regression later without
re-deriving the same empirical finding.
Rebasing onto origin/main (67 new commits, including a real 16.5.0 release cut on both packages/ai and packages/coding-agent) mechanically displaced this branch's Unreleased changelog additions into the new `## [16.5.0]` sections again, each leaving a duplicate/misplaced `### Added` entry. Same class of issue fixed twice already this PR -- moved both entries back to `## [Unreleased]`: - packages/ai/CHANGELOG.md (ElectronHub `/login` entry, reasoning- effort-none entry) - packages/coding-agent/CHANGELOG.md (ELECTRONHUB_DEV_API_KEY docs entry) packages/catalog/CHANGELOG.md had no upstream commits touching it this round and needed no changes. Verified with `bun scripts/fix-changelogs.ts` -- zero further changes needed. Rebased branch retested clean: bun --cwd=packages/ai run test (2822 pass) and bun --cwd=packages/catalog run test (430 pass), both check:types clean.
…mode Codex (P2): the shared ElectronHub base compat's reasoning-effort-none disable mode also applies to gpt-oss-120b:dev, sending reasoning_effort: "none" on Thinking Off. The existing GPT-OSS handling in model-thinking.ts restricts this family to low/medium/high, and the issue can1357#2315 regression coverage (packages/ai/test/issue-2315-repro.test.ts) keeps GPT-OSS disable requests on the lowest supported tier specifically because Harmony rejects minimal/xhigh/none -- this entry bypassed that protection by opting the model into ElectronHub's provider-wide mode. Unlike minimax-m2.7:dev (previous round: thinking.requiresEffort already makes the new mode structurally unreachable, confirmed as a harmless no-op), gpt-oss-120b:dev has no such interception -- reasoning-effort-none genuinely reaches the wire for this model. Live- probed ElectronHub's /v1/chat/completions directly and reasoning_effort: "none" did not 400 (3 trials, all 200s, reasoning length noisy but comparable across none/minimal/low/omitted) -- but that's weaker evidence than an established cross-provider regression test built from a real prior production failure, and there's no guarantee the gateway won't tighten validation to match the underlying Harmony backend later. Overrode gpt-oss-120b:dev to explicitly retain reasoningDisableMode: "lowest-effort" (the same tested-safe path used by every other GPT-OSS host in the catalog) rather than the shared reasoning-effort-none mode, in both the static seed and the dynamic-discovery mapper (both route through electronHubCompatForModel). Updated the shared "bundles all six" test to no longer assert a uniform reasoningDisableMode across all six models (no longer true), split it into a shared assertion for the four models that do use reasoning- effort-none (kimi/glm/gemma/qwen) plus dedicated static- and dynamic- discovery tests for gpt-oss-120b:dev's lowest-effort exception. Also corrected the catalog changelog's "five of the six" wording -- it's four of six now that both minimax and gpt-oss are excluded, each for a distinct documented reason.
88d363b to
28099ea
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 28099eab07
ℹ️ 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".
Codex (P2): the shared ElectronHub compat left maxTokensField unset, so buildOpenAICompat defaulted it to max_completion_tokens (compat/ openai.ts's useMaxTokens allowlist -- Mistral, Moonshot-native, Z.ai/ Zhipu, Chutes, Fireworks, direct DeepSeek -- does not include ElectronHub). ElectronHub's /v1/chat/completions documents max_tokens as the output cap field. For kimi-k2.6:dev specifically, the existing Kimi-family alwaysSendMaxTokens flag (isKimiModel, compat/openai.ts) means the wrong field gets sent on every request, not just capped ones. Live-probed this directly against ElectronHub's real endpoint before changing anything: a request with max_completion_tokens:5 was silently ignored (the model produced its full ~100-line uncapped response), while the identical request with max_tokens:5 correctly capped output (finish_reason "length", completion_tokens 5). This confirms the bug is real and was silently defeating kimi-k2.6:dev's runaway-reasoning protection on ElectronHub specifically -- that safeguard exists (per its own code comment) precisely because Kimi can otherwise emit very long reasoning traces before the final answer. Set maxTokensField: "max_tokens" on the shared ELECTRONHUB_OPENAI_COMPAT base so all six DevPass models inherit the correct field (both the static seed, via the bare constant, and dynamic discovery, via electronHubCompatForModel). Regenerated models.json (electronhub section only). Added a maxTokensField assertion to the existing shared six-model bundle test, plus a dedicated wire-level test that builds the real bundled kimi-k2.6:dev model, drives it through streamOpenAICompletions with a mock fetch, and asserts the captured request body carries max_tokens (not max_completion_tokens) -- proving the fix end-to-end rather than only at the config level.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3a5c0f408
ℹ️ 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".
| const ELECTRONHUB_OPENAI_COMPAT = { | ||
| supportsStore: false, | ||
| supportsDeveloperRole: false, |
There was a problem hiding this comment.
Map ElectronHub forced tool choices to auto
When the coding-agent forces a tool (for example guided setup or the final retry forcing respond), this new ElectronHub compat falls through to buildOpenAICompat's default supportsForcedToolChoice: true, so openai-completions.ts can send tool_choice: "required" or a named function object. ElectronHub's chat-completions docs only advertise tool_choice values none and auto, so those forced-tool requests can be rejected instead of letting the model choose; set supportsForcedToolChoice: false for this provider so the existing mapper downgrades forced choices to auto.
Useful? React with 👍 / 👎.
Remakes closed PR #4858 (auto-closed: author not vouched). Adds ElectronHub Coding Plan (DevPass) as a login/model provider with all six live DevPass models (kimi-k2.6:dev, minimax-m2.7:dev, gpt-oss-120b:dev, glm-5.2:dev, gemma-4-31b-it:dev, qwen3.6-27b:dev), dynamic discovery filtered to DevPass-only records, GLM/Z.AI thinking compat, and a fixture-backed contract test asserting the static seed matches a live /v1/models snapshot. See #4858 and #4872 for prior context/review discussion.