forked from boos/BoosAPI
feat: Implement compatibility strategies for responses and chat endpoints
- Added `routeCompatibility.ts` to handle response compatibility for different endpoints. - Introduced `chatEndpointStrategy.ts` to manage chat endpoint recovery and downgrading logic. - Created tests for chat formats and input file handling in `chatFormatsCore.test.ts` and `inputFile.test.ts`. - Developed `endpointCompatibility.ts` to define compatibility checks and header building for various endpoints. - Implemented protocol lifecycle management in `protocolLifecycle.ts` and corresponding tests in `protocolModel.test.ts`. - Enhanced tooltip functionality with a new `TooltipLayer` component and associated tests. - Refactored existing components to improve tooltip handling and ensure proper rendering in the application.
This commit is contained in:
@@ -119,6 +119,44 @@ describe('/api/models/token-candidates', () => {
|
||||
expect(body.modelsWithoutToken['claude-haiku-4-5-20251001']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not report apikey connections as missing account tokens', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'site-apikey',
|
||||
url: 'https://site-apikey.example.com',
|
||||
platform: 'new-api',
|
||||
status: 'active',
|
||||
}).returning().get();
|
||||
|
||||
const account = await db.insert(schema.accounts).values({
|
||||
siteId: site.id,
|
||||
username: 'shenmo-direct',
|
||||
accessToken: '',
|
||||
apiToken: 'sk-shenmo-direct',
|
||||
status: 'active',
|
||||
extraConfig: JSON.stringify({ credentialMode: 'apikey' }),
|
||||
}).returning().get();
|
||||
|
||||
await db.insert(schema.modelAvailability).values({
|
||||
accountId: account.id,
|
||||
modelName: 'gpt-5.2-codex',
|
||||
available: true,
|
||||
}).run();
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/models/token-candidates',
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const body = response.json() as {
|
||||
modelsWithoutToken: Record<string, Array<{ accountId: number }>>;
|
||||
modelsMissingTokenGroups: Record<string, Array<{ accountId: number }>>;
|
||||
};
|
||||
|
||||
expect(body.modelsWithoutToken['gpt-5.2-codex']).toBeUndefined();
|
||||
expect(body.modelsMissingTokenGroups['gpt-5.2-codex']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns modelsMissingTokenGroups when account has partial group token coverage', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'site-b',
|
||||
@@ -364,7 +402,7 @@ describe('/api/models/token-candidates', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not pass deprecated site apiKey into pricing catalog lookup', async () => {
|
||||
it('does not request token-group hints for apikey connections', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'site-e',
|
||||
url: 'https://site-e.example.com',
|
||||
@@ -425,19 +463,11 @@ describe('/api/models/token-candidates', () => {
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(fetchModelPricingCatalogMock).toHaveBeenCalled();
|
||||
expect(fetchModelPricingCatalogMock.mock.calls[0]?.[0]).toMatchObject({
|
||||
site: {
|
||||
id: site.id,
|
||||
url: site.url,
|
||||
platform: site.platform,
|
||||
},
|
||||
account: {
|
||||
id: account.id,
|
||||
accessToken: '',
|
||||
apiToken: null,
|
||||
},
|
||||
});
|
||||
expect(fetchModelPricingCatalogMock.mock.calls[0]?.[0]?.site?.apiKey).toBeUndefined();
|
||||
const body = response.json() as {
|
||||
modelsMissingTokenGroups: Record<string, Array<{ accountId: number }>>;
|
||||
};
|
||||
|
||||
expect(fetchModelPricingCatalogMock).not.toHaveBeenCalled();
|
||||
expect(body.modelsMissingTokenGroups['claude-opus-4-6']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
parseProxyLogBillingDetails,
|
||||
withProxyLogSelectFields,
|
||||
} from '../../services/proxyLogStore.js';
|
||||
import { getCredentialModeFromExtraConfig } from '../../services/accountExtraConfig.js';
|
||||
import {
|
||||
formatUtcSqlDateTime,
|
||||
getLocalDayRangeUtc,
|
||||
@@ -30,6 +31,12 @@ function parseBooleanFlag(raw?: string): boolean {
|
||||
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
||||
}
|
||||
|
||||
function isApiKeyConnection(account: { accessToken?: string | null; extraConfig?: string | null }): boolean {
|
||||
const explicit = getCredentialModeFromExtraConfig(account.extraConfig);
|
||||
if (explicit && explicit !== 'auto') return explicit === 'apikey';
|
||||
return !(account.accessToken || '').trim();
|
||||
}
|
||||
|
||||
const MODELS_MARKETPLACE_BASE_TTL_MS = 15_000;
|
||||
const MODELS_MARKETPLACE_PRICING_TTL_MS = 90_000;
|
||||
|
||||
@@ -711,6 +718,8 @@ export async function statsRoutes(app: FastifyInstance) {
|
||||
username: schema.accounts.username,
|
||||
siteId: schema.sites.id,
|
||||
siteName: schema.sites.name,
|
||||
accessToken: schema.accounts.accessToken,
|
||||
extraConfig: schema.accounts.extraConfig,
|
||||
})
|
||||
.from(schema.modelAvailability)
|
||||
.innerJoin(schema.accounts, eq(schema.modelAvailability.accountId, schema.accounts.id))
|
||||
@@ -788,6 +797,7 @@ export async function statsRoutes(app: FastifyInstance) {
|
||||
}
|
||||
|
||||
for (const row of availableModelRows) {
|
||||
if (isApiKeyConnection(row)) continue;
|
||||
const modelName = (row.modelName || '').trim();
|
||||
if (!modelName) continue;
|
||||
const coverageKey = `${row.accountId}::${modelName.toLowerCase()}`;
|
||||
@@ -802,7 +812,11 @@ export async function statsRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
const accountIdsForGroupHints = new Set(availableModelRows.map((row) => row.accountId));
|
||||
const accountIdsForGroupHints = new Set(
|
||||
availableModelRows
|
||||
.filter((row) => !isApiKeyConnection(row))
|
||||
.map((row) => row.accountId),
|
||||
);
|
||||
const requiredGroupsByAccountModel = new Map<string, Map<string, string>>();
|
||||
const hasPotentialGroupHints = hasAnyTokenGroupSignals || unknownGroupCoverageByAccountModel.size > 0;
|
||||
|
||||
@@ -862,6 +876,7 @@ export async function statsRoutes(app: FastifyInstance) {
|
||||
}
|
||||
|
||||
for (const row of availableModelRows) {
|
||||
if (isApiKeyConnection(row)) continue;
|
||||
const modelName = (row.modelName || '').trim();
|
||||
if (!modelName) continue;
|
||||
const accountModelKey = `${row.accountId}::${modelName.toLowerCase()}`;
|
||||
|
||||
Reference in New Issue
Block a user