Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/releases/0.31.5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Thinker 0.31.5

```bash
docker pull tmsoftbrasil/thinker:0.31.5
```

## Mudanças

- fix(proxy): a connection base URL no longer forwards the catalog prefix (opencode-go/glm-5.3) to the upstream
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,25 @@ describe('resolveForwardEndpoint', () => {
expect(account.customEndpoint?.allowPrivateDestination).toBe(false);
});

it('strips the catalog prefix when a connection base URL overrides a native endpoint', () => {
const go = resolveForwardEndpoint({
provider: 'opencode-go',
authType: 'api_key',
model: 'opencode-go/glm-5.3',
providerBaseUrl: 'http://wwz:8000/opencode/inference/go/openai',
});
expect(go.customEndpoint).toBeDefined();
expect(go.forwardModel).toBe('glm-5.3');
// OpenRouter ids keep their vendor segment, as on the native endpoint.
const or = resolveForwardEndpoint({
provider: 'openrouter',
authType: 'api_key',
model: 'white-wizard/wwz-fast',
providerBaseUrl: 'http://wwz:8000/openrouter',
});
expect(or.forwardModel).toBe('white-wizard/wwz-fast');
});

it('sets no override for a custom provider whose row is missing', () => {
const out = resolveForwardEndpoint({
provider: 'custom:gone',
Expand Down
11 changes: 7 additions & 4 deletions packages/backend/src/routing/proxy/forward-endpoint-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
type ProviderEndpoint,
} from './provider-endpoints';
import { CustomProviderService } from '../custom-provider/custom-provider.service';
import { stripModelPrefix } from './model-prefix';
import { normalizeMinimaxSubscriptionBaseUrl } from '../provider-base-url';
import {
getBedrockMantleBaseUrl,
Expand Down Expand Up @@ -160,10 +161,12 @@ export function resolveForwardEndpoint(
}
} else if (providerBaseUrl && authType === 'api_key' && resolveEndpointKey(provider)) {
const key = resolveEndpointKey(provider)!;
customEndpoint = buildEndpointOverride(
providerBaseUrl,
key === 'bedrock' ? resolveBedrockEndpointKey(model, apiMode) : key,
);
const overrideKey = key === 'bedrock' ? resolveBedrockEndpointKey(model, apiMode) : key;
customEndpoint = buildEndpointOverride(providerBaseUrl, overrideKey);
// The override reaches ProviderClient as a custom endpoint, which forwards
// the model untouched; strip the catalog prefix (opencode-go/glm-5.3) here,
// exactly as the native endpoint would have.
forwardModel = stripModelPrefix(forwardModel, overrideKey);
customEndpoint.allowPrivateDestination = allowsPrivateDestination(providerBaseUrlAllowsPrivate);
} else if (resolveEndpointKey(provider) === 'bedrock' && isBedrockRegion(providerRegion)) {
const bedrockEndpointKey = resolveBedrockEndpointKey(model, apiMode);
Expand Down
36 changes: 36 additions & 0 deletions packages/backend/src/routing/proxy/model-prefix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { stripVendorPrefix } from '../../common/constants/openai-models';

/**
* Strip vendor prefix from model name (e.g. "anthropic/claude-sonnet-4" → "claude-sonnet-4").
* Models synced from OpenRouter use vendor prefixes, but native APIs expect bare names.
*/
export function stripModelPrefix(model: string, endpointKey: string): string {
// OpenRouter expects vendor prefixes.
if (endpointKey === 'openrouter') return model;
if (endpointKey === 'commandcode' || endpointKey === 'commandcode-anthropic') {
return model.startsWith('commandcode/') ? model.slice('commandcode/'.length) : model;
}
// Custom providers, Fireworks, Groq, Hugging Face, Kilo, Nous, NVIDIA NIM, Ollama, Pioneer, and ClinePass: model IDs from these APIs contain
// legitimate slash segments (e.g. "accounts/fireworks/models/deepseek-v3p1",
// "MiniMaxAI/MiniMax-2.7", "meta-llama/llama-guard-4-12b", "anthropic/claude-sonnet-4.5",
// "cline-pass/deepseek-v4-flash"). Stripping would mangle the name the upstream API expects.
if (
endpointKey === 'custom' ||
endpointKey === 'fireworks' ||
endpointKey === 'groq' ||
endpointKey === 'huggingface' ||
endpointKey === 'kilo' ||
endpointKey === 'nous' ||
endpointKey === 'nvidia' ||
endpointKey === 'cline-pass' ||
endpointKey === 'ollama' ||
endpointKey === 'ollama-cloud' ||
// LM Studio serves hub ids such as `qwen/qwen3-8b`; llama-server echoes
// whatever `--alias` or file path it was started with.
endpointKey === 'lmstudio' ||
endpointKey === 'llamacpp' ||
endpointKey === 'pioneer'
)
return model;
return stripVendorPrefix(model);
}
36 changes: 1 addition & 35 deletions packages/backend/src/routing/proxy/provider-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from './proxy-transport';
import { resolveSubscriptionEndpointKey } from './provider-hooks';
import { isAnthropicHost, mergeAnthropicBeta } from './anthropic-beta';
import { stripModelPrefix } from './model-prefix';
import { mergeExtraHeaders } from './extra-headers';
import { injectOpenAiMessageCacheControl, injectOpenRouterCacheControl } from './cache-injection';
import {
Expand Down Expand Up @@ -363,41 +364,6 @@ function openRouterCacheMode(model: string): 'anthropic' | 'message' | null {
return null;
}

/**
* Strip vendor prefix from model name (e.g. "anthropic/claude-sonnet-4" → "claude-sonnet-4").
* Models synced from OpenRouter use vendor prefixes, but native APIs expect bare names.
*/
function stripModelPrefix(model: string, endpointKey: string): string {
// OpenRouter expects vendor prefixes.
if (endpointKey === 'openrouter') return model;
if (endpointKey === 'commandcode' || endpointKey === 'commandcode-anthropic') {
return model.startsWith('commandcode/') ? model.slice('commandcode/'.length) : model;
}
// Custom providers, Fireworks, Groq, Hugging Face, Kilo, Nous, NVIDIA NIM, Ollama, Pioneer, and ClinePass: model IDs from these APIs contain
// legitimate slash segments (e.g. "accounts/fireworks/models/deepseek-v3p1",
// "MiniMaxAI/MiniMax-2.7", "meta-llama/llama-guard-4-12b", "anthropic/claude-sonnet-4.5",
// "cline-pass/deepseek-v4-flash"). Stripping would mangle the name the upstream API expects.
if (
endpointKey === 'custom' ||
endpointKey === 'fireworks' ||
endpointKey === 'groq' ||
endpointKey === 'huggingface' ||
endpointKey === 'kilo' ||
endpointKey === 'nous' ||
endpointKey === 'nvidia' ||
endpointKey === 'cline-pass' ||
endpointKey === 'ollama' ||
endpointKey === 'ollama-cloud' ||
// LM Studio serves hub ids such as `qwen/qwen3-8b`; llama-server echoes
// whatever `--alias` or file path it was started with.
endpointKey === 'lmstudio' ||
endpointKey === 'llamacpp' ||
endpointKey === 'pioneer'
)
return model;
return stripVendorPrefix(model);
}

@Injectable()
export class ProviderClient {
private readonly logger = new Logger(ProviderClient.name);
Expand Down
2 changes: 1 addition & 1 deletion packages/thinker/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "thinker",
"version": "0.31.4",
"version": "0.31.5",
"private": true,
"description": "Canonical release version for the Thinker Docker image. This package holds no code — the real backend and frontend live in ../backend and ../frontend. See README.md for details."
}
Loading