model-mux 1.0.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/eslint.config.d.ts +3 -0
  4. package/dist/eslint.config.d.ts.map +1 -0
  5. package/dist/eslint.config.js +52 -0
  6. package/dist/eslint.config.js.map +1 -0
  7. package/dist/src/adapter-factory.d.ts +5 -0
  8. package/dist/src/adapter-factory.d.ts.map +1 -0
  9. package/dist/src/adapter-factory.js +11 -0
  10. package/dist/src/adapter-factory.js.map +1 -0
  11. package/dist/src/adapter.d.ts +19 -0
  12. package/dist/src/adapter.d.ts.map +1 -0
  13. package/dist/src/adapter.js +19 -0
  14. package/dist/src/adapter.js.map +1 -0
  15. package/dist/src/anthropic/anthropic-adapter.d.ts +8 -0
  16. package/dist/src/anthropic/anthropic-adapter.d.ts.map +1 -0
  17. package/dist/src/anthropic/anthropic-adapter.js +17 -0
  18. package/dist/src/anthropic/anthropic-adapter.js.map +1 -0
  19. package/dist/src/index.d.ts +15 -0
  20. package/dist/src/index.d.ts.map +1 -0
  21. package/dist/src/index.js +22 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/openai/openai-adapter.d.ts +10 -0
  24. package/dist/src/openai/openai-adapter.d.ts.map +1 -0
  25. package/dist/src/openai/openai-adapter.js +75 -0
  26. package/dist/src/openai/openai-adapter.js.map +1 -0
  27. package/dist/test/integration/openai/openai.it.spec.d.ts +2 -0
  28. package/dist/test/integration/openai/openai.it.spec.d.ts.map +1 -0
  29. package/dist/test/integration/openai/openai.it.spec.js +91 -0
  30. package/dist/test/integration/openai/openai.it.spec.js.map +1 -0
  31. package/dist/test/unit/adapter-factory.spec.d.ts +2 -0
  32. package/dist/test/unit/adapter-factory.spec.d.ts.map +1 -0
  33. package/dist/test/unit/adapter-factory.spec.js +89 -0
  34. package/dist/test/unit/adapter-factory.spec.js.map +1 -0
  35. package/dist/test/unit/adapter.spec.d.ts +2 -0
  36. package/dist/test/unit/adapter.spec.d.ts.map +1 -0
  37. package/dist/test/unit/adapter.spec.js +136 -0
  38. package/dist/test/unit/adapter.spec.js.map +1 -0
  39. package/dist/test/unit/index.spec.d.ts +2 -0
  40. package/dist/test/unit/index.spec.d.ts.map +1 -0
  41. package/dist/test/unit/index.spec.js +193 -0
  42. package/dist/test/unit/index.spec.js.map +1 -0
  43. package/dist/test/unit/openai/openai-adapter.spec.d.ts +2 -0
  44. package/dist/test/unit/openai/openai-adapter.spec.d.ts.map +1 -0
  45. package/dist/test/unit/openai/openai-adapter.spec.js +314 -0
  46. package/dist/test/unit/openai/openai-adapter.spec.js.map +1 -0
  47. package/package.json +70 -0
@@ -0,0 +1,136 @@
1
+ import Chance from 'chance';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { BaseAdapter } from '../../src/adapter.js';
4
+ // Concrete subclass to test the abstract BaseAdapter
5
+ class TestAdapter extends BaseAdapter {
6
+ constructor(model, baseUrl, headers, apiKey) {
7
+ super(model, baseUrl, headers, apiKey);
8
+ }
9
+ // Expose protected members for testing
10
+ get exposedModel() {
11
+ return this.model;
12
+ }
13
+ get exposedBaseUrl() {
14
+ return this.baseUrl;
15
+ }
16
+ get exposedHeaders() {
17
+ return this.headers;
18
+ }
19
+ get exposedApiKey() {
20
+ return this.apiKey;
21
+ }
22
+ exposedMapInput(req) {
23
+ return this.mapInput(req);
24
+ }
25
+ async *stream(_llmRequest) {
26
+ // stub
27
+ }
28
+ async generate(_llmRequest) {
29
+ return {};
30
+ }
31
+ async connect(_llmRequest) {
32
+ return {};
33
+ }
34
+ }
35
+ describe('BaseAdapter', () => {
36
+ const chance = new Chance();
37
+ let model;
38
+ let baseUrl;
39
+ let headers;
40
+ let apiKey;
41
+ let adapter;
42
+ beforeEach(() => {
43
+ model = chance.word();
44
+ baseUrl = chance.url();
45
+ headers = { [chance.word()]: chance.string(), [chance.word()]: chance.string() };
46
+ apiKey = chance.string();
47
+ adapter = new TestAdapter(model, baseUrl, headers, apiKey);
48
+ });
49
+ describe('constructor', () => {
50
+ it('should store model', () => {
51
+ // Assert
52
+ expect(adapter.exposedModel).toBe(model);
53
+ });
54
+ it('should store baseUrl', () => {
55
+ // Assert
56
+ expect(adapter.exposedBaseUrl).toBe(baseUrl);
57
+ });
58
+ it('should store headers', () => {
59
+ // Assert
60
+ expect(adapter.exposedHeaders).toBe(headers);
61
+ });
62
+ it('should store apiKey', () => {
63
+ // Assert
64
+ expect(adapter.exposedApiKey).toBe(apiKey);
65
+ });
66
+ });
67
+ describe('mapInput', () => {
68
+ it('should extract text from contents with parts', () => {
69
+ // Arrange
70
+ const text1 = chance.sentence();
71
+ const text2 = chance.sentence();
72
+ const req = {
73
+ contents: [{ parts: [{ text: text1 }] }, { parts: [{ text: text2 }] }],
74
+ };
75
+ // Act
76
+ const result = adapter.exposedMapInput(req);
77
+ // Assert
78
+ expect(result).toBe(text1 + text2);
79
+ });
80
+ it('should handle contents with multiple parts in a single content', () => {
81
+ // Arrange
82
+ const text1 = chance.sentence();
83
+ const text2 = chance.sentence();
84
+ const req = {
85
+ contents: [{ parts: [{ text: text1 }, { text: text2 }] }],
86
+ };
87
+ // Act
88
+ const result = adapter.exposedMapInput(req);
89
+ // Assert
90
+ expect(result).toBe(text1 + text2);
91
+ });
92
+ it('should handle parts with missing text by using empty string', () => {
93
+ // Arrange
94
+ const text1 = chance.sentence();
95
+ const req = {
96
+ contents: [{ parts: [{ text: text1 }, { notText: chance.word() }] }],
97
+ };
98
+ // Act
99
+ const result = adapter.exposedMapInput(req);
100
+ // Assert
101
+ expect(result).toBe(text1);
102
+ });
103
+ it('should handle contents with missing parts by flatMapping to empty', () => {
104
+ // Arrange
105
+ const text1 = chance.sentence();
106
+ const req = {
107
+ contents: [{ parts: [{ text: text1 }] }, {}],
108
+ };
109
+ // Act
110
+ const result = adapter.exposedMapInput(req);
111
+ // Assert
112
+ expect(result).toBe(text1);
113
+ });
114
+ it('should return empty string when contents has no text parts', () => {
115
+ // Arrange
116
+ const req = {
117
+ contents: [{ parts: [{ notText: chance.word() }] }],
118
+ };
119
+ // Act
120
+ const result = adapter.exposedMapInput(req);
121
+ // Assert
122
+ expect(result).toBe('');
123
+ });
124
+ it('should return empty string when contents is empty', () => {
125
+ // Arrange
126
+ const req = {
127
+ contents: [],
128
+ };
129
+ // Act
130
+ const result = adapter.exposedMapInput(req);
131
+ // Assert
132
+ expect(result).toBe('');
133
+ });
134
+ });
135
+ });
136
+ //# sourceMappingURL=adapter.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.spec.js","sourceRoot":"","sources":["../../../test/unit/adapter.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.spec.d.ts","sourceRoot":"","sources":["../../../test/unit/index.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,193 @@
1
+ import Chance from 'chance';
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { AdapterFactory } from '../../src/adapter-factory.js';
4
+ import { ModelMux } from '../../src/index.js';
5
+ vi.mock('../../src/adapter-factory.js', () => ({
6
+ AdapterFactory: { createAdapter: vi.fn() },
7
+ }));
8
+ describe('ModelMux', () => {
9
+ const chance = new Chance();
10
+ const fakeAdapter = {
11
+ generate: vi.fn(),
12
+ stream: vi.fn(),
13
+ connect: vi.fn(),
14
+ };
15
+ let options;
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ vi.mocked(AdapterFactory.createAdapter).mockReturnValue(fakeAdapter);
19
+ options = {
20
+ model: chance.word(),
21
+ baseUrl: chance.url(),
22
+ headers: { [chance.word()]: chance.string(), [chance.word()]: chance.string() },
23
+ apiKey: chance.string(),
24
+ };
25
+ });
26
+ describe('constructor', () => {
27
+ it('should pass model, baseUrl, headers, and apiKey to AdapterFactory.createAdapter', () => {
28
+ // Act
29
+ new ModelMux(options);
30
+ // Assert
31
+ expect(AdapterFactory.createAdapter).toHaveBeenCalledOnce();
32
+ expect(AdapterFactory.createAdapter).toHaveBeenCalledWith(options.model, options.baseUrl, options.headers, options.apiKey);
33
+ });
34
+ });
35
+ describe('generateContentAsync', () => {
36
+ it('should yield a single response when stream is false', async () => {
37
+ // Arrange
38
+ const mockResponse = { text: chance.sentence() };
39
+ vi.mocked(fakeAdapter.generate).mockResolvedValue(mockResponse);
40
+ const mux = new ModelMux(options);
41
+ const results = [];
42
+ // Act
43
+ for await (const chunk of mux.generateContentAsync({}, false)) {
44
+ results.push(chunk);
45
+ }
46
+ // Assert
47
+ expect(fakeAdapter.generate).toHaveBeenCalledOnce();
48
+ expect(results).toEqual([mockResponse]);
49
+ });
50
+ it('should yield a single response when stream is undefined', async () => {
51
+ // Arrange
52
+ const mockResponse = { text: chance.sentence() };
53
+ vi.mocked(fakeAdapter.generate).mockResolvedValue(mockResponse);
54
+ const mux = new ModelMux(options);
55
+ const results = [];
56
+ // Act
57
+ for await (const chunk of mux.generateContentAsync({})) {
58
+ results.push(chunk);
59
+ }
60
+ // Assert
61
+ expect(fakeAdapter.generate).toHaveBeenCalledOnce();
62
+ expect(results).toEqual([mockResponse]);
63
+ });
64
+ it('should pass llmRequest to adapter.generate when stream is false', async () => {
65
+ // Arrange
66
+ const llmRequest = { prompt: chance.sentence() };
67
+ const mockResponse = { text: chance.sentence() };
68
+ vi.mocked(fakeAdapter.generate).mockResolvedValue(mockResponse);
69
+ const mux = new ModelMux(options);
70
+ // Act
71
+ for await (const _ of mux.generateContentAsync(llmRequest, false)) {
72
+ // consume
73
+ }
74
+ // Assert
75
+ expect(fakeAdapter.generate).toHaveBeenCalledWith(llmRequest);
76
+ });
77
+ it('should delegate to adapter.stream when stream is true', async () => {
78
+ // Arrange
79
+ const chunk1 = { text: chance.sentence() };
80
+ const chunk2 = { text: chance.sentence() };
81
+ async function* fakeStream() {
82
+ yield chunk1;
83
+ yield chunk2;
84
+ }
85
+ vi.mocked(fakeAdapter.stream).mockReturnValue(fakeStream());
86
+ const mux = new ModelMux(options);
87
+ const results = [];
88
+ // Act
89
+ for await (const chunk of mux.generateContentAsync({}, true)) {
90
+ results.push(chunk);
91
+ }
92
+ // Assert
93
+ expect(fakeAdapter.stream).toHaveBeenCalledOnce();
94
+ expect(fakeAdapter.generate).not.toHaveBeenCalled();
95
+ expect(results).toEqual([chunk1, chunk2]);
96
+ });
97
+ it('should pass llmRequest to adapter.stream when stream is true', async () => {
98
+ // Arrange
99
+ const llmRequest = { prompt: chance.sentence() };
100
+ async function* fakeStream() {
101
+ // empty
102
+ }
103
+ vi.mocked(fakeAdapter.stream).mockReturnValue(fakeStream());
104
+ const mux = new ModelMux(options);
105
+ // Act
106
+ for await (const _ of mux.generateContentAsync(llmRequest, true)) {
107
+ // consume
108
+ }
109
+ // Assert
110
+ expect(fakeAdapter.stream).toHaveBeenCalledWith(llmRequest);
111
+ });
112
+ it('should yield no results when stream returns nothing', async () => {
113
+ // Arrange
114
+ async function* emptyStream() {
115
+ // yields nothing
116
+ }
117
+ vi.mocked(fakeAdapter.stream).mockReturnValue(emptyStream());
118
+ const mux = new ModelMux(options);
119
+ const results = [];
120
+ // Act
121
+ for await (const chunk of mux.generateContentAsync({}, true)) {
122
+ results.push(chunk);
123
+ }
124
+ // Assert
125
+ expect(results).toEqual([]);
126
+ });
127
+ it('should propagate errors from adapter.generate', async () => {
128
+ // Arrange
129
+ const errorMessage = chance.sentence();
130
+ vi.mocked(fakeAdapter.generate).mockRejectedValue(new Error(errorMessage));
131
+ const mux = new ModelMux(options);
132
+ // Act & Assert
133
+ await expect(async () => {
134
+ for await (const _ of mux.generateContentAsync({}, false)) {
135
+ // consume
136
+ }
137
+ }).rejects.toThrow(errorMessage);
138
+ });
139
+ it('should propagate errors from adapter.stream', async () => {
140
+ // Arrange
141
+ const errorMessage = chance.sentence();
142
+ // eslint-disable-next-line require-yield
143
+ async function* errorStream() {
144
+ throw new Error(errorMessage);
145
+ }
146
+ vi.mocked(fakeAdapter.stream).mockReturnValue(errorStream());
147
+ const mux = new ModelMux(options);
148
+ // Act & Assert
149
+ await expect(async () => {
150
+ for await (const _ of mux.generateContentAsync({}, true)) {
151
+ // consume
152
+ }
153
+ }).rejects.toThrow(errorMessage);
154
+ });
155
+ it('should not call adapter.stream when stream is false', async () => {
156
+ // Arrange
157
+ const mockResponse = { text: chance.sentence() };
158
+ vi.mocked(fakeAdapter.generate).mockResolvedValue(mockResponse);
159
+ const mux = new ModelMux(options);
160
+ // Act
161
+ for await (const _ of mux.generateContentAsync({}, false)) {
162
+ // consume
163
+ }
164
+ // Assert
165
+ expect(fakeAdapter.stream).not.toHaveBeenCalled();
166
+ });
167
+ });
168
+ describe('connect', () => {
169
+ it('should delegate to adapter.connect and return the connection', async () => {
170
+ // Arrange
171
+ const llmRequest = { prompt: chance.sentence() };
172
+ const mockConnection = { id: chance.guid() };
173
+ vi.mocked(fakeAdapter.connect).mockResolvedValue(mockConnection);
174
+ const mux = new ModelMux(options);
175
+ // Act
176
+ const result = await mux.connect(llmRequest);
177
+ // Assert
178
+ expect(fakeAdapter.connect).toHaveBeenCalledOnce();
179
+ expect(fakeAdapter.connect).toHaveBeenCalledWith(llmRequest);
180
+ expect(result).toBe(mockConnection);
181
+ });
182
+ it('should propagate errors from adapter.connect', async () => {
183
+ // Arrange
184
+ const errorMessage = chance.sentence();
185
+ const llmRequest = { prompt: chance.sentence() };
186
+ vi.mocked(fakeAdapter.connect).mockRejectedValue(new Error(errorMessage));
187
+ const mux = new ModelMux(options);
188
+ // Act & Assert
189
+ await expect(mux.connect(llmRequest)).rejects.toThrow(errorMessage);
190
+ });
191
+ });
192
+ });
193
+ //# sourceMappingURL=index.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.spec.js","sourceRoot":"","sources":["../../../test/unit/index.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=openai-adapter.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-adapter.spec.d.ts","sourceRoot":"","sources":["../../../../test/unit/openai/openai-adapter.spec.ts"],"names":[],"mappings":""}