Merge pull request #236 from cita-777/codex/claude-count-models
Fix Claude count_tokens compatibility and OAuth model discovery
This commit is contained in:
@@ -156,4 +156,44 @@ describe('claude count_tokens proxy route', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('supports /v1/messages/count_tokens for openai-platform gateways that expose Claude messages endpoints', async () => {
|
||||
selectChannelMock.mockReturnValue({
|
||||
channel: { id: 12, routeId: 23 },
|
||||
site: { name: 'gateway-site', url: 'https://gateway.example.com', platform: 'openai' },
|
||||
account: { id: 34, username: 'gateway-user@example.com' },
|
||||
tokenName: 'default',
|
||||
tokenValue: 'sk-gateway',
|
||||
actualModel: 'claude-sonnet-4-5-20250929',
|
||||
});
|
||||
fetchMock.mockResolvedValue(new Response(JSON.stringify({
|
||||
input_tokens: 9,
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}));
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/messages/count_tokens',
|
||||
payload: {
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: 'count through a compatible gateway' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json()).toEqual({ input_tokens: 9 });
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [targetUrl, options] = fetchMock.mock.calls[0] as [string, any];
|
||||
expect(targetUrl).toBe('https://gateway.example.com/v1/messages/count_tokens?beta=true');
|
||||
expect(options.headers['x-api-key']).toBe('sk-gateway');
|
||||
expect(options.headers['anthropic-version']).toBe('2023-06-01');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1542,32 +1542,60 @@ export function buildClaudeCountTokensUpstreamRequest(input: {
|
||||
delete sanitizedBody.maxTokens;
|
||||
delete sanitizedBody.stream;
|
||||
const providerProfile = resolveProviderProfile(sitePlatform);
|
||||
if (providerProfile?.id !== 'claude') {
|
||||
throw new Error(`missing claude provider profile for platform: ${sitePlatform || 'unknown'}`);
|
||||
const effectiveClaudeHeaders = {
|
||||
...claudeHeaders,
|
||||
...(betas.length > 0 ? { 'anthropic-beta': betas.join(',') } : {}),
|
||||
};
|
||||
|
||||
if (providerProfile?.id === 'claude') {
|
||||
const prepared = providerProfile.prepareRequest({
|
||||
endpoint: 'messages',
|
||||
modelName: input.modelName,
|
||||
stream: false,
|
||||
tokenValue: input.tokenValue,
|
||||
oauthProvider: input.oauthProvider,
|
||||
sitePlatform,
|
||||
baseHeaders: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
claudeHeaders: effectiveClaudeHeaders,
|
||||
body: sanitizedBody,
|
||||
action: 'countTokens',
|
||||
});
|
||||
|
||||
return {
|
||||
path: prepared.path,
|
||||
headers: prepared.headers,
|
||||
body: prepared.body,
|
||||
runtime: {
|
||||
executor: 'claude',
|
||||
modelName: input.modelName,
|
||||
stream: false,
|
||||
action: 'countTokens',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const prepared = providerProfile.prepareRequest({
|
||||
endpoint: 'messages',
|
||||
modelName: input.modelName,
|
||||
stream: false,
|
||||
tokenValue: input.tokenValue,
|
||||
oauthProvider: input.oauthProvider,
|
||||
sitePlatform,
|
||||
const anthropicVersion = (
|
||||
effectiveClaudeHeaders['anthropic-version']
|
||||
|| '2023-06-01'
|
||||
);
|
||||
const isClaudeOauthUpstream = sitePlatform === 'claude' && input.oauthProvider === 'claude';
|
||||
const headers = buildClaudeRuntimeHeaders({
|
||||
baseHeaders: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
claudeHeaders: {
|
||||
...claudeHeaders,
|
||||
...(betas.length > 0 ? { 'anthropic-beta': betas.join(',') } : {}),
|
||||
},
|
||||
body: sanitizedBody,
|
||||
action: 'countTokens',
|
||||
claudeHeaders: effectiveClaudeHeaders,
|
||||
anthropicVersion,
|
||||
stream: false,
|
||||
isClaudeOauthUpstream,
|
||||
tokenValue: input.tokenValue,
|
||||
});
|
||||
|
||||
return {
|
||||
path: prepared.path,
|
||||
headers: prepared.headers,
|
||||
body: prepared.body,
|
||||
path: '/v1/messages/count_tokens?beta=true',
|
||||
headers,
|
||||
body: sanitizedBody,
|
||||
runtime: {
|
||||
executor: 'claude',
|
||||
modelName: input.modelName,
|
||||
|
||||
Reference in New Issue
Block a user