Providers Bug Fix/Feature: add Chutes API-key auth and dynamic model discovery#37822
Providers Bug Fix/Feature: add Chutes API-key auth and dynamic model discovery#37822Veightor wants to merge 15 commits intoopenclaw:mainfrom
Conversation
…resolve conflicts Made-with: Cursor
Greptile SummaryThis PR adds a Issues found:
Confidence Score: 3/5
Last reviewed commit: 1f34bd6 |
| // but init.sh uses 'chutes/' + m.id. | ||
|
|
||
| // Add aliases from init.sh | ||
| models["chutes-fast"] = { alias: "chutes/zai-org/GLM-4.7-Flash" }; |
There was a problem hiding this comment.
chutes-fast alias references a non-existent model
"zai-org/GLM-4.7-Flash" does not exist in CHUTES_MODEL_CATALOG. The catalog only contains "zai-org/GLM-4.7-TEE" (line 216) and "zai-org/GLM-4.7-FP8" (line 378). Any user who invokes chutes-fast will get a model-not-found error at runtime. Based on the other two aliases, the likely intended model is one of those two — TEE for the TEE-attested secure variant or FP8 for the quantized variant.
| models["chutes-fast"] = { alias: "chutes/zai-org/GLM-4.7-Flash" }; | |
| models["chutes-fast"] = { alias: "chutes/zai-org/GLM-4.7-TEE" }; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/onboard-auth.config-core.ts
Line: 322
Comment:
**`chutes-fast` alias references a non-existent model**
`"zai-org/GLM-4.7-Flash"` does not exist in `CHUTES_MODEL_CATALOG`. The catalog only contains `"zai-org/GLM-4.7-TEE"` (line 216) and `"zai-org/GLM-4.7-FP8"` (line 378). Any user who invokes `chutes-fast` will get a model-not-found error at runtime. Based on the other two aliases, the likely intended model is one of those two — `TEE` for the TEE-attested secure variant or `FP8` for the quantized variant.
```suggestion
models["chutes-fast"] = { alias: "chutes/zai-org/GLM-4.7-TEE" };
```
How can I resolve this? If you propose a fix, please make it concise.| export async function setChutesApiKey(key: string, agentDir?: string) { | ||
| // Write to resolved agent dir so gateway finds credentials on startup. | ||
| upsertAuthProfile({ | ||
| profileId: "chutes:default", | ||
| credential: { | ||
| type: "api_key", | ||
| provider: "chutes", | ||
| key, | ||
| }, | ||
| agentDir: resolveAuthAgentDir(agentDir), | ||
| }); | ||
| } |
There was a problem hiding this comment.
Inconsistent signature vs other set*ApiKey helpers
Every other set*ApiKey function in this file accepts a SecretInput type (not plain string) and an optional ApiKeyStorageOptions, then delegates to buildApiKeyCredential. This new function accepts a bare string and constructs the credential inline, bypassing:
- Encrypted/obfuscated storage modes supported by
buildApiKeyCredential. - Any future
ApiKeyStorageOptionsfeatures (e.g., key rotation hints) which will apply to all other providers but silently not Chutes.
It should follow the same shape as setVeniceApiKey and setSyntheticApiKey immediately above it in this file.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/onboard-auth.credentials.ts
Line: 332-343
Comment:
**Inconsistent signature vs other `set*ApiKey` helpers**
Every other `set*ApiKey` function in this file accepts a `SecretInput` type (not plain `string`) and an optional `ApiKeyStorageOptions`, then delegates to `buildApiKeyCredential`. This new function accepts a bare `string` and constructs the credential inline, bypassing:
1. Encrypted/obfuscated storage modes supported by `buildApiKeyCredential`.
2. Any future `ApiKeyStorageOptions` features (e.g., key rotation hints) which will apply to all other providers but silently not Chutes.
It should follow the same shape as `setVeniceApiKey` and `setSyntheticApiKey` immediately above it in this file.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1f34bd639b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| export async function setChutesApiKey(key: string, agentDir?: string) { | ||
| // Write to resolved agent dir so gateway finds credentials on startup. | ||
| upsertAuthProfile({ | ||
| profileId: "chutes:default", | ||
| credential: { |
There was a problem hiding this comment.
Preserve SecretRef storage for Chutes API credentials
setChutesApiKey stores the credential as a raw key string instead of using the shared buildApiKeyCredential path, so onboarding flows that pass a SecretRef (for secret-input-mode=ref) end up serializing an object into key rather than keyRef. That breaks the expected secret-ref behavior and can fail later when auth resolution treats key as a string (e.g., trimming/normalization paths). This makes Chutes onboarding uniquely unsafe/unstable compared with other API-key providers.
Useful? React with 👍 / 👎.
| return null; | ||
| } | ||
| if (resolved.source !== "profile") { | ||
| await setChutesApiKey(resolved.key); |
There was a problem hiding this comment.
Honor ref-mode in non-interactive Chutes API-key onboarding
The non-interactive chutes-api-key branch writes resolved.key directly whenever the source is not profile, bypassing the maybeSetResolvedApiKey helper used by other providers. With --secret-input-mode ref, this stores the plaintext secret value instead of an env SecretRef, violating the caller’s explicit storage mode and leaking secrets into auth profiles.
Useful? React with 👍 / 👎.
| // but init.sh uses 'chutes/' + m.id. | ||
|
|
||
| // Add aliases from init.sh | ||
| models["chutes-fast"] = { alias: "chutes/zai-org/GLM-4.7-Flash" }; |
There was a problem hiding this comment.
Define Chutes model aliases in canonical direction
These alias entries are inverted for this config schema: the map key should be the real model ref and alias should be the shortcut. Here the shortcut is used as the key and the full model ref is stored as the alias, so alias resolution maps the wrong way (e.g., toward chutes/chutes-fast) and users cannot reliably select the intended canonical model via chutes-fast.
Useful? React with 👍 / 👎.
- Correct chutes-fast alias to point to a valid model (GLM-4.7-FP8) - Align setChutesApiKey signature with other providers (accept SecretInput) - Use maybeSetResolvedApiKey in non-interactive Chutes onboarding - Fix test mock data to match ChutesModelEntry API shape Made-with: Cursor
…t detections) - Fix Feishu media download/upload type errors by removing redundant timeout parameter (handled by client) - Add 'pragma: allowlist secret' to false-positive test tokens and translations Made-with: Cursor
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b8192f110
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Write to resolved agent dir so gateway finds credentials on startup. | ||
| upsertAuthProfile({ | ||
| profileId: "chutes:default", | ||
| credential: buildApiKeyCredential("chutes", key, undefined, options), |
There was a problem hiding this comment.
Add Chutes env-var mapping for ref-mode API key storage
setChutesApiKey now delegates to buildApiKeyCredential("chutes", ...), and that path calls resolveProviderDefaultEnvSecretRef when secret-input-mode=ref is used with a plaintext token; however PROVIDER_ENV_VARS has no chutes entry, so onboarding can throw Provider "chutes" does not have a default env var mapping... instead of saving credentials. This breaks chutes-api-key onboarding for ref-mode inputs (for example --auth-choice chutes-api-key --secret-input-mode ref with a direct token value) and should be fixed by wiring a default env var such as CHUTES_API_KEY.
Useful? React with 👍 / 👎.
…/chutesai/openclaw into feat/chutes-provider-integration
Replace complex state-mutation helper with direct applyChutesConfig call after OAuth success, matching the pattern used by the API key flow. This reliably sets the Chutes default model so the model picker shows a Chutes model as current instead of falling back to the previous anthropic default. Made-with: Cursor
The timeout parameter was removed from the source code in a previous commit because it was causing type errors with the SDK, but the tests were not updated. This brings the tests in sync with the source. Made-with: Cursor
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c265f21f8a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (cachedModels && now - lastDiscoveryTime < CACHE_TTL) { | ||
| return cachedModels; |
There was a problem hiding this comment.
Key Chutes model cache by auth token
discoverChutesModels reuses a single global cache entry based only on TTL, before it inspects the incoming accessToken, so any subsequent discovery within 5 minutes returns the first fetched catalog regardless of which credential is being resolved. Since resolveImplicitProviders can run for different agent dirs/profiles in the same process, this can serve the wrong model set after switching Chutes accounts or credentials (for example, stale or account-mismatched model availability).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
👎
Model discovery is the same for everyone, no matter the account or a credential
Conflict resolutions: - src/secrets/target-registry-pattern.ts: take upstream's named-variable style for sibling_ref check; preserve pragma allowlist comment - extensions/feishu/src/media.test.ts: keep upstream's expectMediaTimeoutClientConfigured() + add PR's imageCreateMock assertion for image_type verification - src/agents/models-config.providers.ts: adopt upstream's refactored provider-loader pattern (SIMPLE/PROFILE_IMPLICIT_PROVIDER_LOADERS); wire Chutes into both API-key (withApiKey) and OAuth profile (withProfilePresence) loader slots using buildChutesProvider from the new models-config.providers.discovery.ts boundary Supporting changes: - src/agents/models-config.providers.discovery.ts: add buildChutesProvider (dynamic model discovery via discoverChutesModels) - src/agents/model-auth-markers.ts: add CHUTES_OAUTH_MARKER and register it in isNonSecretApiKeyMarker Made-with: Cursor
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
5 similar comments
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
12 similar comments
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb3223a453
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }, | ||
| imageModel: { | ||
| primary: "chutes/chutesai/Mistral-Small-3.2-24B-Instruct-2506", | ||
| fallbacks: ["chutes/Qwen/Qwen3-32B"], |
There was a problem hiding this comment.
Use image-capable model for Chutes image fallback
applyChutesConfig sets agents.defaults.imageModel.fallbacks to chutes/Qwen/Qwen3-32B, but that model is defined as text-only (input: ["text"] in src/agents/chutes-models.ts). Because image fallback resolution does not filter candidates by modality, any image request that falls back from the primary Chutes vision model (for example due to outage/rate-limit/model removal) can be retried against a text-only model and fail with provider-side image-input errors.
Useful? React with 👍 / 👎.
Summary
chutes-api-keyonboarding auth choice wired through all the relevant routing/credential tables; wrotechutes-models.tswith liveGET /v1/modelsdiscovery (5-min cache, fallback to a broad static catalog), streaming-compat flag, and retry-on-401 logic; wrotechutes-oauth.tswith full PKCE generation, authorization-code exchange, token refresh, userinfo fetch, and strict OAuth state validation; added unit and flow tests for both modules.origin/mainis preserved and supplemented rather than replaced.Change Type (select all)
Scope (select all touched areas)
AuthChoiceunion type extended; onboard routing updated)Linked Issue/PR
User-visible / Behavior Changes
chutes-api-key) appears inopenclaw onboardalongside the existing OAuth flow.openclaw onboard --auth chutes-api-keystores the key and sets the Chutes provider with the full model list./v1/modelsAPI at startup (5-min TTL cache); if discovery fails or returns empty, it falls back to a curated static catalog of ~50 models.contextWindow,maxTokens, and per-model pricing from the API.supportsUsageInStreaming: falsecompat flag).chutes(OAuth) onboarding path now enforces full redirect-URL paste with CSRF state validation; bare code-only pastes are rejected.Security Impact (required)
New permissions/capabilities? No
Secrets/tokens handling changed? Yes — API keys are stored via the same
upsertAuthProfile/setChutesApiKeycredential pipeline used by all other providers. OAuth tokens (access + refresh) are stored viawriteOAuthCredentials. No new storage location or format is introduced.New/changed network calls? Yes —
GET https://llm.chutes.ai/v1/modelsfor model discovery at startup (respectsVITEST/NODE_ENV=testguard and has a 10-second timeout + fallback).POST https://api.chutes.ai/idp/tokenfor code exchange and refresh.GET https://api.chutes.ai/idp/userinfofor username resolution.Command/tool execution surface changed? No
Data access scope changed? No
Risk + mitigation for network calls: Discovery endpoint is called only when a Chutes key/profile is configured, with a hard timeout and a static-catalog fallback — no user data is sent, only the Bearer token if present. On 401, it retries unauthenticated (the endpoint is public), so a stale/invalid key never blocks startup.
Risk + mitigation for token storage: Refresh tokens are stored in the existing auth-profiles store;
CHUTES_CLIENT_ID/CHUTES_CLIENT_SECRETare read from env vars at refresh time, following the same pattern as other OAuth providers (MiniMax, Qwen, etc.) — no new secret-handling code paths introduced.Repro + Verification
Environment
llm.chutes.ai) — OAuth and API-key pathsopenclaw onboard)CHUTES_CLIENT_ID=<redacted>,CHUTES_API_KEY=<redacted>Steps (API-key path)
openclaw onboard --auth chutes-api-keychutesprovider appears inopenclaw modelsSteps (OAuth path)
openclaw onboard --auth chutes?code=...&state=...) back into the CLIopenclaw modelslists Chutes modelsExpected
openclaw modelslists dynamically discovered Chutes models (or the static catalog on network failure)Actual
Evidence
src/agents/chutes-models.test.ts— covers static catalog, API mapping, 401 retry, compat flagsrc/agents/chutes-oauth.test.ts— covers PKCE generation, state validation, URL/querystring parsing, CSRF rejectionsrc/agents/chutes-oauth.flow.test.ts— covers full token exchange (with userinfo), refresh (with/without new refresh token)Human Verification (required)
resolveImplicitProvidersstate=) is rejected with a clear errorrefresh_tokenCompatibility / Migration
chutesOAuth credential profiles are unchanged; only an additive API-key path and model-discovery layer are added.CHUTES_CLIENT_IDandCHUTES_CLIENT_SECRETenv vars are read at OAuth refresh time (same as the existing Chutes OAuth setup; no new requirement for API-key users).Failure Recovery (if this breaks)
chutesprovider entry from~/.openclaw/models.json(or delete thechutesauth profile viaopenclaw config); the Chutes provider will not load without a key/profile.~/.openclaw/models.json,~/.openclaw/agents/<id>/auth-profiles.jsonresolveImplicitProviders(would indicate theAbortSignal.timeout(10_000)is not firing — unlikely but checkllm.chutes.aireachability)OAuth state mismatcherrors on valid callbacks (check that the redirect URL is being pasted in full)llm.chutes.ai)Risks and Mitigations
llm.chutes.aiis slow.AbortSignal.timeout, 5-minute in-process cache so only the first boot per run pays the cost, and immediate fallback to the static catalog on any failure.clientIdin the OAuth credential struct is a minor schema addition that could break deserialization if the field is later renamed.clientId?) on the existingOAuthCredentialsshape; reading code gracefully falls back toCHUTES_CLIENT_IDenv var if missing.