mirror of
https://github.com/hanzoai/chat.git
synced 2026-09-30 20:51:17 +00:00
Titles are generated with the endpoint's configured title model, which is priced. A conversation that opens on the free route belongs to an account with no balance, so every one of those titles was refused at the wallet and the chat kept the name it was born with. The conversation's own model is already the starting point; for a free one, leaving it there is the answer. Also pins that reordering the catalog copies it. The list it reorders is the cached one every caller is served, and doing it in place would let one free caller move the default for everybody.
87 lines
3.2 KiB
JavaScript
87 lines
3.2 KiB
JavaScript
const { FREE_MODEL } = require('@hanzo/ai');
|
|
|
|
jest.mock('@hanzochat/data-schemas', () => ({ logger: { error: jest.fn() } }));
|
|
jest.mock('~/server/services/Config', () => ({
|
|
loadDefaultModels: jest.fn(),
|
|
loadConfigModels: jest.fn(),
|
|
}));
|
|
jest.mock('~/cache', () => ({ getLogStores: jest.fn() }));
|
|
jest.mock('~/server/services/guestConfig', () => ({
|
|
buildGuestModelsConfig: jest.fn(() => ({ Hanzo: [require('@hanzo/ai').FREE_MODEL] })),
|
|
}));
|
|
jest.mock('~/server/services/plan', () => ({ planOf: jest.fn(), FREE: 'free' }));
|
|
|
|
const { getLogStores } = require('~/cache');
|
|
const { loadDefaultModels, loadConfigModels } = require('~/server/services/Config');
|
|
const { planOf } = require('~/server/services/plan');
|
|
const { modelController, leadFree } = require('./ModelController');
|
|
|
|
/** The deployed shape: priced rungs first, the free route last. */
|
|
const CATALOG = {
|
|
Hanzo: ['enso', 'enso-flash', 'zen5', FREE_MODEL],
|
|
Anthropic: ['claude-sonnet'],
|
|
};
|
|
|
|
const answer = () => {
|
|
const res = { send: jest.fn(), status: jest.fn(() => res) };
|
|
return res;
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
getLogStores.mockReturnValue({ get: jest.fn().mockResolvedValue(CATALOG), set: jest.fn() });
|
|
loadDefaultModels.mockResolvedValue({});
|
|
loadConfigModels.mockResolvedValue(CATALOG);
|
|
});
|
|
|
|
describe('leadFree', () => {
|
|
it('puts the free route first without dropping a priced one', () => {
|
|
expect(leadFree(CATALOG).Hanzo).toEqual(['enso-free', 'enso', 'enso-flash', 'zen5']);
|
|
});
|
|
|
|
it('leaves an endpoint that has no free route exactly as it was', () => {
|
|
expect(leadFree(CATALOG).Anthropic).toEqual(['claude-sonnet']);
|
|
});
|
|
|
|
// The catalog it reorders is the CACHED one, shared by every caller. Reordering
|
|
// it in place would let one free caller's request move the default for
|
|
// everybody, paying callers included.
|
|
it('does not touch the catalog it was given', () => {
|
|
const before = JSON.stringify(CATALOG);
|
|
leadFree(CATALOG);
|
|
expect(JSON.stringify(CATALOG)).toBe(before);
|
|
});
|
|
});
|
|
|
|
describe('modelController', () => {
|
|
it('leads a free plan with the model it can actually run', async () => {
|
|
planOf.mockResolvedValue('free');
|
|
const res = answer();
|
|
await modelController({ user: {} }, res);
|
|
expect(res.send.mock.calls[0][0].Hanzo[0]).toBe(FREE_MODEL);
|
|
});
|
|
|
|
it('leaves a paid plan on the model it pays for', async () => {
|
|
planOf.mockResolvedValue('pro');
|
|
const res = answer();
|
|
await modelController({ user: {} }, res);
|
|
expect(res.send.mock.calls[0][0].Hanzo[0]).toBe('enso');
|
|
});
|
|
|
|
// A plan that could not be read is not a free plan. Reading silence as free
|
|
// would hand the free lane to every caller cloud failed to name.
|
|
it('leaves an unreadable plan on the paid default', async () => {
|
|
planOf.mockResolvedValue(null);
|
|
const res = answer();
|
|
await modelController({ user: {} }, res);
|
|
expect(res.send.mock.calls[0][0].Hanzo[0]).toBe('enso');
|
|
});
|
|
|
|
it('narrows a guest to the one model they are pinned to, and asks no plan', async () => {
|
|
const res = answer();
|
|
await modelController({ user: { guest: true } }, res);
|
|
expect(res.send).toHaveBeenCalledWith({ Hanzo: [FREE_MODEL] });
|
|
expect(planOf).not.toHaveBeenCalled();
|
|
});
|
|
});
|