@jeffusion/bungee-core 5.19.0 → 5.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config-worker/private-transport.d.ts +2 -0
- package/dist/data-admission/errors.d.ts +6 -1
- package/dist/gateway/contracts.d.ts +16 -3
- package/dist/gateway/forward-plugin.d.ts +2 -0
- package/dist/gateway/plugin.js +12 -2
- package/dist/gateway/request-plugin.d.ts +6 -0
- package/dist/gateway/responses-websocket-session.d.ts +18 -0
- package/dist/gateway/runtime.d.ts +1 -0
- package/dist/gateway/websocket-contracts.d.ts +1 -0
- package/dist/hooks/plugin-hooks.d.ts +2 -0
- package/dist/logger/access-log-writer.d.ts +2 -2
- package/dist/main.js +1234 -191
- package/dist/plugins/ai-transformer/index.js +11 -1
- package/dist/plugins/ai-transformer/index.js.map +5 -3
- package/dist/plugins/codex-router/control.js +677 -2
- package/dist/plugins/codex-router/control.js.map +19 -4
- package/dist/plugins/codex-router/index.js +4123 -33
- package/dist/plugins/codex-router/index.js.map +25 -7
- package/dist/plugins/codex-router/manifest.json +139 -9
- package/dist/plugins/key-access/index.js +12 -2
- package/dist/plugins/key-access/index.js.map +3 -3
- package/dist/plugins/key-access/ingress.js +12 -2
- package/dist/plugins/key-access/ingress.js.map +3 -3
- package/dist/plugins/openai-messages-to-chat/index.js +693 -543
- package/dist/plugins/openai-messages-to-chat/index.js.map +8 -4
- package/dist/plugins/token-metering/index.js +652 -71
- package/dist/plugins/token-metering/index.js.map +5 -3
- package/dist/plugins/token-stats/manifest.json +16 -6
- package/dist/plugins/token-stats/ui/ClientModelPicker.svelte +13 -61
- package/dist/plugins/token-stats/ui/PriceModelPicker.svelte +18 -74
- package/dist/plugins/token-stats/ui/TokenStatsSettings.svelte +3 -2
- package/dist/plugins/token-stats/ui/model-search.ts +2 -45
- package/dist/plugins/token-stats/ui/pricing-catalog.ts +2 -1
- package/dist/scoped-plugin-registry.d.ts +11 -2
- package/dist/websocket/index.d.ts +1 -1
- package/dist/websocket/transport.d.ts +40 -18
- package/package.json +2 -2
- package/dist/plugins/chatgpt-oauth/ui/account-model.test.js +0 -69
- package/dist/plugins/local-accounts/ui/login-errors.test.ts +0 -22
- package/dist/plugins/token-stats/ui/client-model-search.test.ts +0 -58
- package/dist/plugins/token-stats/ui/pricing-catalog.test.ts +0 -114
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { createClientModelSearch, type ClientModelPage, type ClientModelSearchState } from './client-model-search';
|
|
3
|
-
|
|
4
|
-
const page = (models: string[], current = 1): ClientModelPage => ({ models, page: current, pageSize: 50, total: 101 });
|
|
5
|
-
const deferred = () => { let resolve!: (page: ClientModelPage) => void; const promise = new Promise<ClientModelPage>(done => resolve = done); return { resolve, promise }; };
|
|
6
|
-
const tick = () => new Promise(resolve => setTimeout(resolve, 10));
|
|
7
|
-
|
|
8
|
-
describe('client model search lifecycle', () => {
|
|
9
|
-
test('debounces typing and preserves exact search case and query characters', async () => {
|
|
10
|
-
const paths: string[] = [];
|
|
11
|
-
const states: ClientModelSearchState[] = [];
|
|
12
|
-
const search = createClientModelSearch(async path => { paths.push(path); return page(['DeepSeek/V4 & 中文']); }, state => states.push(state), 5);
|
|
13
|
-
search.search('first'); search.search('DeepSeek/V4 & 中文');
|
|
14
|
-
expect(paths).toHaveLength(0);
|
|
15
|
-
await tick();
|
|
16
|
-
expect(paths).toEqual(['/models?search=DeepSeek%2FV4+%26+%E4%B8%AD%E6%96%87&page=1&pageSize=50']);
|
|
17
|
-
expect(states.at(-1)?.models).toEqual(['DeepSeek/V4 & 中文']);
|
|
18
|
-
search.destroy();
|
|
19
|
-
});
|
|
20
|
-
test('aborts old requests during debounce and rejects late responses', async () => {
|
|
21
|
-
const pending = [deferred(), deferred()];
|
|
22
|
-
const signals: AbortSignal[] = [];
|
|
23
|
-
const states: ClientModelSearchState[] = [];
|
|
24
|
-
const search = createClientModelSearch((_path, signal) => { signals.push(signal); return pending[signals.length - 1].promise; }, state => states.push(state), 5);
|
|
25
|
-
search.search('old', 1, false);
|
|
26
|
-
search.search('new');
|
|
27
|
-
expect(signals[0].aborted).toBe(true);
|
|
28
|
-
pending[0].resolve(page(['OLD'])); await tick();
|
|
29
|
-
expect(states.at(-1)?.models).toEqual([]);
|
|
30
|
-
pending[1].resolve(page(['New'])); await tick();
|
|
31
|
-
expect(states.at(-1)?.models).toEqual(['New']);
|
|
32
|
-
search.destroy();
|
|
33
|
-
});
|
|
34
|
-
test('requests later pages and aborts on component destruction without publishing', async () => {
|
|
35
|
-
const pending = deferred();
|
|
36
|
-
let signal!: AbortSignal;
|
|
37
|
-
let path = '';
|
|
38
|
-
const states: ClientModelSearchState[] = [];
|
|
39
|
-
const search = createClientModelSearch((requested, incoming) => { path = requested; signal = incoming; return pending.promise; }, state => states.push(state));
|
|
40
|
-
search.search('MixedCase', 3, false);
|
|
41
|
-
expect(path).toBe('/models?search=MixedCase&page=3&pageSize=50');
|
|
42
|
-
search.destroy(); expect(signal.aborted).toBe(true);
|
|
43
|
-
pending.resolve(page(['Late'], 3)); await tick();
|
|
44
|
-
expect(states).toHaveLength(1);
|
|
45
|
-
search.search('ignored', 1, false); expect(states).toHaveLength(1);
|
|
46
|
-
});
|
|
47
|
-
test('reports search failure and accepts another query after failure', async () => {
|
|
48
|
-
const states: ClientModelSearchState[] = [];
|
|
49
|
-
let fail = true;
|
|
50
|
-
const search = createClientModelSearch(async () => { if (fail) throw new Error('offline'); return page(['ExactCASE']); }, state => states.push(state));
|
|
51
|
-
search.search('', 1, false); await tick();
|
|
52
|
-
expect(states.at(-1)?.error).toBe(true);
|
|
53
|
-
fail = false; search.search('ExactCASE', 1, false); await tick();
|
|
54
|
-
expect(states.at(-1)?.error).toBe(false);
|
|
55
|
-
expect(states.at(-1)?.models).toEqual(['ExactCASE']);
|
|
56
|
-
search.destroy();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'bun:test';
|
|
2
|
-
import { createPricingModelSearch, type PricingModelPage, type PricingModelSearchState } from './pricing-catalog';
|
|
3
|
-
|
|
4
|
-
const option = (model: string) => ({ provider: 'custom-provider', providerName: 'Custom provider', model, name: model });
|
|
5
|
-
const page = (models: string[], current = 1): PricingModelPage => ({ models: models.map(option), total: 8394, page: current, pageSize: 50 });
|
|
6
|
-
const deferred = () => { let resolve!: (page: PricingModelPage) => void; const promise = new Promise<PricingModelPage>(done => resolve = done); return { resolve, promise }; };
|
|
7
|
-
const tick = () => new Promise(resolve => setTimeout(resolve, 10));
|
|
8
|
-
|
|
9
|
-
test('does not preload and requests only the selected provider page', async () => {
|
|
10
|
-
const paths: string[] = [];
|
|
11
|
-
const states: PricingModelSearchState[] = [];
|
|
12
|
-
const search = createPricingModelSearch(async path => { paths.push(path); return page(['ExactCase']); }, state => states.push(state));
|
|
13
|
-
expect(paths).toEqual([]);
|
|
14
|
-
search.search({ provider: 'custom-provider', search: '' }, 1, false);
|
|
15
|
-
await tick();
|
|
16
|
-
expect(paths).toEqual(['/pricing/models?provider=custom-provider&search=&page=1&pageSize=50']);
|
|
17
|
-
expect(states.at(-1)?.models[0]?.model).toBe('ExactCase');
|
|
18
|
-
// A large total never triggers another page automatically.
|
|
19
|
-
expect(paths).toHaveLength(1);
|
|
20
|
-
search.search({ provider: 'custom-provider', search: '' }, 2, false);
|
|
21
|
-
await tick();
|
|
22
|
-
expect(paths.at(-1)).toContain('page=2&pageSize=50');
|
|
23
|
-
search.destroy();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('debounces remote searches and preserves query characters and model case', async () => {
|
|
27
|
-
const paths: string[] = [];
|
|
28
|
-
const states: PricingModelSearchState[] = [];
|
|
29
|
-
const search = createPricingModelSearch(async path => { paths.push(path); return page(['DeepSeek/V4 & 中文']); }, state => states.push(state), 5);
|
|
30
|
-
search.search({ provider: 'custom-provider', search: 'first' });
|
|
31
|
-
search.search({ provider: 'custom-provider', search: 'DeepSeek/V4 & 中文' });
|
|
32
|
-
await tick();
|
|
33
|
-
expect(paths).toEqual(['/pricing/models?provider=custom-provider&search=DeepSeek%2FV4+%26+%E4%B8%AD%E6%96%87&page=1&pageSize=50']);
|
|
34
|
-
expect(states.at(-1)?.models[0]?.model).toBe('DeepSeek/V4 & 中文');
|
|
35
|
-
search.destroy();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('provider changes, closure and destruction reject late results', async () => {
|
|
39
|
-
const requests = [deferred(), deferred(), deferred()];
|
|
40
|
-
const signals: AbortSignal[] = [];
|
|
41
|
-
const states: PricingModelSearchState[] = [];
|
|
42
|
-
const search = createPricingModelSearch((_path, signal) => { signals.push(signal); return requests[signals.length - 1]!.promise; }, state => states.push(state));
|
|
43
|
-
search.search({ provider: 'old', search: '' }, 1, false);
|
|
44
|
-
search.search({ provider: 'new', search: '' }, 1, false);
|
|
45
|
-
expect(signals[0]!.aborted).toBe(true);
|
|
46
|
-
requests[0]!.resolve(page(['OLD'])); await tick();
|
|
47
|
-
expect(states.at(-1)?.models).toEqual([]);
|
|
48
|
-
requests[1]!.resolve(page(['New'])); await tick();
|
|
49
|
-
expect(states.at(-1)?.models[0]?.model).toBe('New');
|
|
50
|
-
search.search({ provider: 'new', search: '' }, 1, false);
|
|
51
|
-
search.cancel(); expect(signals[2]!.aborted).toBe(true);
|
|
52
|
-
const count = states.length;
|
|
53
|
-
requests[2]!.resolve(page(['Late'])); await tick();
|
|
54
|
-
expect(states).toHaveLength(count);
|
|
55
|
-
search.destroy(); search.search({ provider: 'ignored', search: '' }, 1, false);
|
|
56
|
-
expect(signals).toHaveLength(3);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// Exercise the actual Svelte load functions with controlled response ordering.
|
|
61
|
-
async function settingsFixture(loadRequest: (plugin: string, path: string) => Promise<unknown>) {
|
|
62
|
-
const source = await Bun.file(new URL('./TokenStatsSettings.svelte', import.meta.url)).text();
|
|
63
|
-
const providers = source.slice(source.indexOf(' async function loadProviders('), source.indexOf(' async function loadMappings('));
|
|
64
|
-
const load = source.slice(source.indexOf(' async function load()'), source.indexOf(' onMount('));
|
|
65
|
-
const code = `function fixture(requestPluginControl: any) {
|
|
66
|
-
let alive = true, polling = false, loading = true, error = '', providersError = false;
|
|
67
|
-
let status: any = null, catalogVersion: number | null = null, providers: any[] = [];
|
|
68
|
-
let providersVersion: number | null | undefined, providersPendingVersion: number | null | undefined, providersGeneration = 0;
|
|
69
|
-
const controller = new AbortController();
|
|
70
|
-
${providers}
|
|
71
|
-
${load}
|
|
72
|
-
return { load, state: () => ({ status, providers, catalogVersion, loading, providersError }) };
|
|
73
|
-
}`;
|
|
74
|
-
const javascript = new Bun.Transpiler({ loader: 'ts' }).transformSync(code);
|
|
75
|
-
return new Function(`${javascript}
|
|
76
|
-
return fixture;`)()(loadRequest);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
test('settings displays status without waiting for providers and ignores a superseded directory response', async () => {
|
|
80
|
-
let version = 1;
|
|
81
|
-
const pending: Array<(value: unknown) => void> = [];
|
|
82
|
-
let calls = 0;
|
|
83
|
-
const settings = await settingsFixture(async (_plugin, path) => {
|
|
84
|
-
if (path === '/pricing') return { version, modelCount: 100 };
|
|
85
|
-
calls++;
|
|
86
|
-
return new Promise(resolve => pending.push(resolve));
|
|
87
|
-
});
|
|
88
|
-
await settings.load();
|
|
89
|
-
expect(settings.state()).toMatchObject({ loading: false, catalogVersion: 1 });
|
|
90
|
-
// A poll during a slow provider request does not keep aborting/restarting it.
|
|
91
|
-
await settings.load(); expect(calls).toBe(1);
|
|
92
|
-
version = 2;
|
|
93
|
-
await settings.load(); expect(calls).toBe(2);
|
|
94
|
-
pending[1]!({ providers: [{ provider: 'new' }] }); await tick();
|
|
95
|
-
pending[0]!({ providers: [] }); await tick();
|
|
96
|
-
expect(settings.state().providers).toEqual([{ provider: 'new' }]);
|
|
97
|
-
await settings.load(); expect(calls).toBe(2);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('settings recovers provider selection when an initially empty directory gets its first version', async () => {
|
|
101
|
-
let version: number | null = null;
|
|
102
|
-
let calls = 0;
|
|
103
|
-
const settings = await settingsFixture(async (_plugin, path) => {
|
|
104
|
-
if (path === '/pricing') return { version, modelCount: version === null ? 0 : 100 };
|
|
105
|
-
calls++;
|
|
106
|
-
return { providers: version === null ? [] : [{ provider: 'available' }] };
|
|
107
|
-
});
|
|
108
|
-
await settings.load(); await tick();
|
|
109
|
-
expect(settings.state().providers).toEqual([]);
|
|
110
|
-
version = 1;
|
|
111
|
-
await settings.load(); await tick();
|
|
112
|
-
expect(settings.state().providers).toEqual([{ provider: 'available' }]);
|
|
113
|
-
expect(calls).toBe(2);
|
|
114
|
-
});
|