phantomllm 0.1.0 → 0.3.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/README.md +52 -3
- package/dist/index.cjs +33 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +33 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ await mock.stop();
|
|
|
32
32
|
- [Streaming](#streaming)
|
|
33
33
|
- [Embeddings](#embeddings)
|
|
34
34
|
- [Error Simulation](#error-simulation)
|
|
35
|
+
- [API Key Validation](#api-key-validation)
|
|
35
36
|
- [Stub Matching](#stub-matching)
|
|
36
37
|
- [Integration Examples](#integration-examples)
|
|
37
38
|
- [OpenAI Node.js SDK](#openai-nodejs-sdk)
|
|
@@ -56,7 +57,7 @@ await mock.stop();
|
|
|
56
57
|
- **Works with any client** — OpenAI SDK, Vercel AI SDK, LangChain, opencode, Python, curl — anything that speaks the OpenAI API protocol.
|
|
57
58
|
- **Streaming support** — SSE chunked responses work exactly like the real OpenAI API.
|
|
58
59
|
- **Fast** — ~1s container cold start, sub-millisecond response latency, 4,000+ req/s throughput.
|
|
59
|
-
- **Simple API** — fluent `given
|
|
60
|
+
- **Simple API** — fluent `given`/`require` pattern: `mock.given.chatCompletion.forModel('gpt-4').willReturn('Hello')`.
|
|
60
61
|
|
|
61
62
|
## Prerequisites
|
|
62
63
|
|
|
@@ -151,8 +152,9 @@ const mock = new MockLLM({
|
|
|
151
152
|
| `await mock.stop()` | `void` | Stop and remove the container. Idempotent. |
|
|
152
153
|
| `mock.baseUrl` | `string` | Server URL without `/v1`, e.g. `http://localhost:55123`. |
|
|
153
154
|
| `mock.apiBaseUrl` | `string` | Server URL with `/v1`, e.g. `http://localhost:55123/v1`. Pass this to SDK clients. |
|
|
154
|
-
| `mock.given` | `GivenStubs` | Entry point for
|
|
155
|
-
| `
|
|
155
|
+
| `mock.given` | `GivenStubs` | Entry point for stubbing responses. |
|
|
156
|
+
| `mock.expect` | `ExpectConditions` | Entry point for configuring server behavior (API key validation, etc.). |
|
|
157
|
+
| `await mock.clear()` | `void` | Remove all stubs and reset server config (including API key). Call between tests. |
|
|
156
158
|
|
|
157
159
|
`MockLLM` implements `Symbol.asyncDispose` for automatic cleanup:
|
|
158
160
|
|
|
@@ -262,6 +264,53 @@ Error responses follow the OpenAI error format:
|
|
|
262
264
|
}
|
|
263
265
|
```
|
|
264
266
|
|
|
267
|
+
### API Key Validation
|
|
268
|
+
|
|
269
|
+
Test that your code sends the correct API key by requiring authentication on the mock server.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
mock.expect.apiKey('sk-test-key-123');
|
|
273
|
+
|
|
274
|
+
// Requests without a key or with the wrong key get 401
|
|
275
|
+
// { error: { message: "...", type: "authentication_error", code: "invalid_api_key" } }
|
|
276
|
+
|
|
277
|
+
// Only requests with the correct key succeed
|
|
278
|
+
const openai = new OpenAI({
|
|
279
|
+
baseURL: mock.apiBaseUrl,
|
|
280
|
+
apiKey: 'sk-test-key-123', // must match exactly
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`mock.expect` configures server constraints at runtime — no container restart needed. API key validation applies to all `/v1/*` endpoints (chat completions, embeddings, models). Admin endpoints (`/_admin/*`) are always accessible.
|
|
285
|
+
|
|
286
|
+
Calling `mock.clear()` resets the API key requirement along with all stubs.
|
|
287
|
+
|
|
288
|
+
| Method | Description |
|
|
289
|
+
|---|---|
|
|
290
|
+
| `mock.expect.apiKey(key)` | Require `Authorization: Bearer <key>` on all `/v1/*` requests. Single call, no chaining needed. |
|
|
291
|
+
|
|
292
|
+
**Testing auth error handling:**
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
it('handles invalid API key', async () => {
|
|
296
|
+
mock.expect.apiKey('correct-key');
|
|
297
|
+
mock.given.chatCompletion.willReturn('Hello');
|
|
298
|
+
|
|
299
|
+
// Client configured with wrong key
|
|
300
|
+
const badClient = new OpenAI({
|
|
301
|
+
baseURL: mock.apiBaseUrl,
|
|
302
|
+
apiKey: 'wrong-key',
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
await expect(
|
|
306
|
+
badClient.chat.completions.create({
|
|
307
|
+
model: 'gpt-4o',
|
|
308
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
309
|
+
}),
|
|
310
|
+
).rejects.toThrow();
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
265
314
|
### Stub Matching
|
|
266
315
|
|
|
267
316
|
When multiple stubs are registered, the most specific match wins:
|
package/dist/index.cjs
CHANGED
|
@@ -19,10 +19,10 @@ var ContainerManager = class {
|
|
|
19
19
|
}
|
|
20
20
|
container = null;
|
|
21
21
|
async start() {
|
|
22
|
-
const
|
|
22
|
+
const builder = new testcontainers.GenericContainer(this.config.image).withExposedPorts(this.config.containerPort).withWaitStrategy(
|
|
23
23
|
testcontainers.Wait.forHttp("/_admin/health", this.config.containerPort).forStatusCode(200)
|
|
24
24
|
).withStartupTimeout(this.config.startupTimeout).withLabels({ "com.phantomllm": "true" });
|
|
25
|
-
this.container = await
|
|
25
|
+
this.container = await builder.start();
|
|
26
26
|
return {
|
|
27
27
|
host: this.container.getHost(),
|
|
28
28
|
port: this.container.getMappedPort(this.config.containerPort)
|
|
@@ -83,13 +83,24 @@ var AdminClient = class {
|
|
|
83
83
|
this.baseUrl = baseUrl;
|
|
84
84
|
}
|
|
85
85
|
pendingStubs = [];
|
|
86
|
+
pendingConfigs = [];
|
|
86
87
|
enqueueStub(stub) {
|
|
87
88
|
this.pendingStubs.push(stub);
|
|
88
89
|
}
|
|
90
|
+
enqueueConfig(config) {
|
|
91
|
+
this.pendingConfigs.push(config);
|
|
92
|
+
}
|
|
89
93
|
async flush() {
|
|
90
|
-
if (this.
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
if (this.pendingConfigs.length > 0) {
|
|
95
|
+
const configs = this.pendingConfigs.splice(0);
|
|
96
|
+
for (const config of configs) {
|
|
97
|
+
await this.post("/_admin/config", config);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (this.pendingStubs.length > 0) {
|
|
101
|
+
const stubs = this.pendingStubs.splice(0);
|
|
102
|
+
await this.post("/_admin/stubs/batch", { stubs });
|
|
103
|
+
}
|
|
93
104
|
}
|
|
94
105
|
async clearStubs() {
|
|
95
106
|
await this.flush();
|
|
@@ -242,6 +253,16 @@ var GivenStubs = class {
|
|
|
242
253
|
}
|
|
243
254
|
};
|
|
244
255
|
|
|
256
|
+
// src/stubs/expect.ts
|
|
257
|
+
var ExpectConditions = class {
|
|
258
|
+
constructor(adminClient) {
|
|
259
|
+
this.adminClient = adminClient;
|
|
260
|
+
}
|
|
261
|
+
apiKey(key) {
|
|
262
|
+
this.adminClient.enqueueConfig({ apiKey: key });
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
245
266
|
// src/errors/lifecycle.errors.ts
|
|
246
267
|
var ContainerNotStartedError = class extends MockLLMError {
|
|
247
268
|
code = "CONTAINER_NOT_STARTED";
|
|
@@ -260,6 +281,7 @@ var MockLLM = class {
|
|
|
260
281
|
containerManager;
|
|
261
282
|
adminClient = null;
|
|
262
283
|
_given = null;
|
|
284
|
+
_expect = null;
|
|
263
285
|
_baseUrl = null;
|
|
264
286
|
constructor(options) {
|
|
265
287
|
const config = resolveContainerConfig(options);
|
|
@@ -277,6 +299,7 @@ var MockLLM = class {
|
|
|
277
299
|
this._baseUrl = `http://${host}:${port}`;
|
|
278
300
|
this.adminClient = new AdminClient(this._baseUrl);
|
|
279
301
|
this._given = new GivenStubs(this.adminClient);
|
|
302
|
+
this._expect = new ExpectConditions(this.adminClient);
|
|
280
303
|
this.state = "running";
|
|
281
304
|
}
|
|
282
305
|
async stop() {
|
|
@@ -288,6 +311,7 @@ var MockLLM = class {
|
|
|
288
311
|
this.state = "stopped";
|
|
289
312
|
this.adminClient = null;
|
|
290
313
|
this._given = null;
|
|
314
|
+
this._expect = null;
|
|
291
315
|
this._baseUrl = null;
|
|
292
316
|
}
|
|
293
317
|
}
|
|
@@ -302,6 +326,10 @@ var MockLLM = class {
|
|
|
302
326
|
this.assertRunning();
|
|
303
327
|
return this._given;
|
|
304
328
|
}
|
|
329
|
+
get expect() {
|
|
330
|
+
this.assertRunning();
|
|
331
|
+
return this._expect;
|
|
332
|
+
}
|
|
305
333
|
async clear() {
|
|
306
334
|
this.assertRunning();
|
|
307
335
|
await this.adminClient.clearStubs();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/driver/container.config.ts","../src/driver/container.manager.ts","../src/errors/base.ts","../src/errors/admin.errors.ts","../src/driver/admin.client.ts","../src/stubs/chat.builder.ts","../src/stubs/embedding.builder.ts","../src/stubs/models.builder.ts","../src/stubs/given.ts","../src/errors/lifecycle.errors.ts","../src/driver/mock-llm.ts","../src/errors/stub.errors.ts"],"names":["GenericContainer","Wait"],"mappings":";;;;;AAEA,IAAM,aAAA,GAAgB,0BAAA;AACtB,IAAM,YAAA,GAAe,IAAA;AAEd,SAAS,uBAAuB,OAAA,EAA2C;AAChF,EAAA,OAAO;AAAA,IACL,OAAO,OAAA,EAAS,KAAA,IAAS,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAA,IAAK,aAAA;AAAA,IAC5D,aAAA,EAAe,SAAS,aAAA,IAAiB,YAAA;AAAA,IACzC,KAAA,EAAO,SAAS,KAAA,IAAS,IAAA;AAAA,IACzB,cAAA,EAAgB,SAAS,cAAA,IAAkB;AAAA,GAC7C;AACF;ACRO,IAAM,mBAAN,MAAuB;AAAA,EAG5B,YAA6B,MAAA,EAAyB;AAAzB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAA0B;AAAA,EAF/C,SAAA,GAAyC,IAAA;AAAA,EAIjD,MAAM,KAAA,GAAiD;AACrD,IAAA,MAAM,SAAA,GAAY,IAAIA,+BAAA,CAAiB,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,CACrD,gBAAA,CAAiB,IAAA,CAAK,MAAA,CAAO,aAAa,CAAA,CAC1C,gBAAA;AAAA,MACCC,mBAAA,CAAK,QAAQ,gBAAA,EAAkB,IAAA,CAAK,OAAO,aAAa,CAAA,CACrD,cAAc,GAAG;AAAA,KACtB,CACC,kBAAA,CAAmB,IAAA,CAAK,MAAA,CAAO,cAAc,EAC7C,UAAA,CAAW,EAAE,gBAAA,EAAkB,MAAA,EAAQ,CAAA;AAE1C,IAAA,IAAA,CAAK,SAAA,GAAY,MAAM,SAAA,CAAU,KAAA,EAAM;AAEvC,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAA,EAAQ;AAAA,MAC7B,MAAM,IAAA,CAAK,SAAA,CAAU,aAAA,CAAc,IAAA,CAAK,OAAO,aAAa;AAAA,KAC9D;AAAA,EACF;AAAA,EAEA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAI,KAAK,SAAA,EAAW;AAClB,MAAA,MAAM,IAAA,CAAK,UAAU,IAAA,EAAK;AAC1B,MAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AAAA,IACnB;AAAA,EACF;AACF,CAAA;;;ACjCO,IAAe,YAAA,GAAf,cAAoC,KAAA,CAAM;AAAA,EAE/C,WAAA,CAAY,SAAiB,OAAA,EAA+B;AAC1D,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,KAAK,WAAA,CAAY,IAAA;AAAA,EAC/B;AACF;;;ACJO,IAAM,aAAA,GAAN,cAA4B,YAAA,CAAa;AAAA,EAE9C,WAAA,CACkB,UAAA,EACA,YAAA,EAChB,OAAA,EACA;AACA,IAAA,KAAA;AAAA,MACE,CAAA,wBAAA,EAA2B,UAAU,CAAA,EAAA,EAAK,YAAY,CAAA,CAAA;AAAA,MACtD;AAAA,KACF;AAPgB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AAAA,EAOlB;AAAA,EAVS,IAAA,GAAO,iBAAA;AAWlB,CAAA;AAEO,IAAM,2BAAA,GAAN,cAA0C,YAAA,CAAa;AAAA,EACnD,IAAA,GAAO,0BAAA;AAAA,EAChB,YAAY,OAAA,EAA+B;AACzC,IAAA,KAAA;AAAA,MACE,sEAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF,CAAA;AAEO,IAAM,iBAAA,GAAN,cAAgC,YAAA,CAAa;AAAA,EAElD,WAAA,CACkB,WAChB,OAAA,EACA;AACA,IAAA,KAAA;AAAA,MACE,oCAAoC,SAAS,CAAA,GAAA,CAAA;AAAA,MAC7C;AAAA,KACF;AANgB,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAAA,EAOlB;AAAA,EATS,IAAA,GAAO,eAAA;AAUlB,CAAA;;;AC1BA,IAAM,kBAAA,GAAqB,GAAA;AAEpB,IAAM,cAAN,MAAkB;AAAA,EAGvB,YAA6B,OAAA,EAAiB;AAAjB,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,EAAkB;AAAA,EAFvC,eAAsC,EAAC;AAAA,EAI/C,YAAY,IAAA,EAAiC;AAC3C,IAAA,IAAA,CAAK,YAAA,CAAa,KAAK,IAAI,CAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAI,IAAA,CAAK,YAAA,CAAa,MAAA,KAAW,CAAA,EAAG;AACpC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,YAAA,CAAa,MAAA,CAAO,CAAC,CAAA;AACxC,IAAA,MAAM,IAAA,CAAK,IAAA,CAAK,qBAAA,EAAuB,EAAE,OAAO,CAAA;AAAA,EAClD;AAAA,EAEA,MAAM,UAAA,GAA4B;AAChC,IAAA,MAAM,KAAK,KAAA,EAAM;AACjB,IAAA,MAAM,IAAA,CAAK,OAAO,eAAe,CAAA;AAAA,EACnC;AAAA,EAEA,MAAM,SAAA,GAAyC;AAC7C,IAAA,OAAO,IAAA,CAAK,IAAwB,gBAAgB,CAAA;AAAA,EACtD;AAAA,EAEA,MAAM,WAAA,GAAgE;AACpE,IAAA,MAAM,KAAK,KAAA,EAAM;AACjB,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,GAAA,CAAiC,kBAAkB,CAAA;AAC3E,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA,EAEA,MAAc,IAAO,IAAA,EAA0B;AAC7C,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,KAAA,CAAM,MAAM,EAAE,MAAA,EAAQ,OAAO,CAAA;AACzD,IAAA,OAAQ,MAAM,SAAS,IAAA,EAAK;AAAA,EAC9B;AAAA,EAEA,MAAc,IAAA,CAAQ,IAAA,EAAc,IAAA,EAA2B;AAC7D,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM;AAAA,MACtC,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI;AAAA,KAC1B,CAAA;AACD,IAAA,OAAQ,MAAM,SAAS,IAAA,EAAK;AAAA,EAC9B;AAAA,EAEA,MAAc,OAAO,IAAA,EAA6B;AAChD,IAAA,MAAM,KAAK,KAAA,CAAM,IAAA,EAAM,EAAE,MAAA,EAAQ,UAAU,CAAA;AAAA,EAC7C;AAAA,EAEA,MAAc,KAAA,CAAM,IAAA,EAAc,IAAA,EAAsC;AACtE,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,GAAG,IAAI,CAAA,CAAA;AAClC,IAAA,IAAI,QAAA;AAEJ,IAAA,IAAI;AACF,MAAA,QAAA,GAAW,MAAM,MAAM,GAAA,EAAK;AAAA,QAC1B,GAAG,IAAA;AAAA,QACH,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,kBAAkB;AAAA,OAC/C,CAAA;AAAA,IACH,SAAS,KAAA,EAAgB;AACvB,MAAA,IAAI,iBAAiB,SAAA,EAAW;AAC9B,QAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,QAAA,IAAI,KAAA,EAAO,SAAS,cAAA,EAAgB;AAClC,UAAA,MAAM,IAAI,2BAAA,CAA4B,EAAE,KAAA,EAAO,OAAO,CAAA;AAAA,QACxD;AAAA,MACF;AACA,MAAA,IAAI,KAAA,YAAiB,YAAA,IAAgB,KAAA,CAAM,IAAA,KAAS,cAAA,EAAgB;AAClE,QAAA,MAAM,IAAI,iBAAA,CAAkB,kBAAA,EAAoB,EAAE,KAAA,EAAO,OAAO,CAAA;AAAA,MAClE;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,MAAA,MAAM,IAAI,aAAA,CAAc,QAAA,CAAS,MAAA,EAAQ,IAAI,CAAA;AAAA,IAC/C;AAEA,IAAA,OAAO,QAAA;AAAA,EACT;AACF,CAAA;;;ACvFO,IAAM,4BAAN,MAAgC;AAAA,EAGrC,YAA6B,WAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA,EAA2B;AAAA,EAFvC,OAAA,GAA4B,EAAE,QAAA,EAAU,MAAA,EAAO;AAAA,EAIhE,SAAS,KAAA,EAAqB;AAC5B,IAAA,IAAA,CAAK,QAAQ,KAAA,GAAQ,KAAA;AACrB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,sBAAsB,SAAA,EAAyB;AAC7C,IAAA,IAAA,CAAK,QAAQ,OAAA,GAAU,SAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,WAAW,OAAA,EAAuB;AAChC,IAAA,IAAA,CAAK,YAAY,WAAA,CAAY;AAAA,MAC3B,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,QAAA,EAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAM,OAAA;AAAQ,KACzC,CAAA;AAAA,EACH;AAAA,EAEA,WAAW,MAAA,EAAwB;AACjC,IAAA,IAAA,CAAK,YAAY,WAAA,CAAY;AAAA,MAC3B,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,QAAA,EAAU,EAAE,IAAA,EAAM,gBAAA,EAAkB,MAAA;AAAO,KAC5C,CAAA;AAAA,EACH;AAAA,EAEA,SAAA,CAAU,YAAoB,OAAA,EAAuB;AACnD,IAAA,IAAA,CAAK,YAAY,WAAA,CAAY;AAAA,MAC3B,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,QAAA,EAAU;AAAA,QACR,IAAA,EAAM,OAAA;AAAA,QACN,MAAA,EAAQ,UAAA;AAAA,QACR,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,WAAA,EAAa,MAAM,IAAA;AAAK;AAClD,KACD,CAAA;AAAA,EACH;AACF,CAAA;;;ACvCO,IAAM,uBAAN,MAA2B;AAAA,EAGhC,YAA6B,WAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA,EAA2B;AAAA,EAFvC,OAAA,GAA4B,EAAE,QAAA,EAAU,YAAA,EAAa;AAAA,EAItE,SAAS,KAAA,EAAqB;AAC5B,IAAA,IAAA,CAAK,QAAQ,KAAA,GAAQ,KAAA;AACrB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,WAAW,MAAA,EAAqC;AAC9C,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAC,CAAA,GAClC,MAAA,GACD,CAAC,MAAkB,CAAA;AAEvB,IAAA,IAAA,CAAK,YAAY,WAAA,CAAY;AAAA,MAC3B,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,QAAA,EAAU,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA;AAAQ,KACxC,CAAA;AAAA,EACH;AAAA,EAEA,SAAA,CAAU,YAAoB,OAAA,EAAuB;AACnD,IAAA,IAAA,CAAK,YAAY,WAAA,CAAY;AAAA,MAC3B,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,QAAA,EAAU;AAAA,QACR,IAAA,EAAM,OAAA;AAAA,QACN,MAAA,EAAQ,UAAA;AAAA,QACR,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,WAAA,EAAa,MAAM,IAAA;AAAK;AAClD,KACD,CAAA;AAAA,EACH;AACF,CAAA;;;AChCO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,YAA6B,WAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA,EAA2B;AAAA,EAExD,WAAW,MAAA,EAAuD;AAChE,IAAA,IAAA,CAAK,YAAY,WAAA,CAAY;AAAA,MAC3B,SAAS,EAAC;AAAA,MACV,QAAA,EAAU,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA;AAAO,KACpC,CAAA;AAAA,EACH;AACF,CAAA;;;ACNO,IAAM,aAAN,MAAiB;AAAA,EACtB,YAA6B,WAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA,EAA2B;AAAA,EAExD,IAAI,cAAA,GAA4C;AAC9C,IAAA,OAAO,IAAI,yBAAA,CAA0B,IAAA,CAAK,WAAW,CAAA;AAAA,EACvD;AAAA,EAEA,IAAI,SAAA,GAAkC;AACpC,IAAA,OAAO,IAAI,oBAAA,CAAqB,IAAA,CAAK,WAAW,CAAA;AAAA,EAClD;AAAA,EAEA,IAAI,MAAA,GAA4B;AAC9B,IAAA,OAAO,IAAI,iBAAA,CAAkB,IAAA,CAAK,WAAW,CAAA;AAAA,EAC/C;AACF,CAAA;;;ACjBO,IAAM,wBAAA,GAAN,cAAuC,YAAA,CAAa;AAAA,EAChD,IAAA,GAAO,uBAAA;AAAA,EAChB,YAAY,OAAA,EAA+B;AACzC,IAAA,KAAA;AAAA,MACE,uFAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;;;ACDO,IAAM,UAAN,MAAc;AAAA,EACX,KAAA,GAAsB,MAAA;AAAA,EACtB,YAAA,GAAqC,IAAA;AAAA,EACrC,gBAAA;AAAA,EACA,WAAA,GAAkC,IAAA;AAAA,EAClC,MAAA,GAA4B,IAAA;AAAA,EAC5B,QAAA,GAA0B,IAAA;AAAA,EAElC,YAAY,OAAA,EAA0B;AACpC,IAAA,MAAM,MAAA,GAAS,uBAAuB,OAAO,CAAA;AAC7C,IAAA,IAAA,CAAK,gBAAA,GAAmB,IAAI,gBAAA,CAAiB,MAAM,CAAA;AAAA,EACrD;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAI,IAAA,CAAK,UAAU,SAAA,EAAW;AAC9B,IAAA,IAAI,KAAK,KAAA,KAAU,UAAA,IAAc,IAAA,CAAK,YAAA,SAAqB,IAAA,CAAK,YAAA;AAEhE,IAAA,IAAA,CAAK,KAAA,GAAQ,UAAA;AACb,IAAA,IAAA,CAAK,YAAA,GAAe,KAAK,OAAA,EAAQ;AACjC,IAAA,OAAO,IAAA,CAAK,YAAA;AAAA,EACd;AAAA,EAEA,MAAc,OAAA,GAAyB;AACrC,IAAA,MAAM,EAAE,IAAA,EAAM,IAAA,KAAS,MAAM,IAAA,CAAK,iBAAiB,KAAA,EAAM;AACzD,IAAA,IAAA,CAAK,QAAA,GAAW,CAAA,OAAA,EAAU,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AACtC,IAAA,IAAA,CAAK,WAAA,GAAc,IAAI,WAAA,CAAY,IAAA,CAAK,QAAQ,CAAA;AAChD,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,UAAA,CAAW,IAAA,CAAK,WAAW,CAAA;AAC7C,IAAA,IAAA,CAAK,KAAA,GAAQ,SAAA;AAAA,EACf;AAAA,EAEA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,SAAA,IAAa,IAAA,CAAK,UAAU,MAAA,EAAQ;AACvD,IAAA,IAAA,CAAK,KAAA,GAAQ,UAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,iBAAiB,IAAA,EAAK;AAAA,IACnC,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,KAAA,GAAQ,SAAA;AACb,MAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AACnB,MAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AACd,MAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,IAAI,OAAA,GAAkB;AACpB,IAAA,IAAA,CAAK,aAAA,EAAc;AACnB,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA,EAEA,IAAI,UAAA,GAAqB;AACvB,IAAA,OAAO,CAAA,EAAG,KAAK,OAAO,CAAA,GAAA,CAAA;AAAA,EACxB;AAAA,EAEA,IAAI,KAAA,GAAoB;AACtB,IAAA,IAAA,CAAK,aAAA,EAAc;AACnB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAA,CAAK,aAAA,EAAc;AACnB,IAAA,MAAM,IAAA,CAAK,YAAa,UAAA,EAAW;AAAA,EACrC;AAAA,EAEA,OAAO,MAAA,CAAO,YAAY,CAAA,GAAmB;AAC3C,IAAA,MAAM,KAAK,IAAA,EAAK;AAAA,EAClB;AAAA,EAEQ,aAAA,GAAsB;AAC5B,IAAA,IAAI,IAAA,CAAK,UAAU,SAAA,EAAW;AAC5B,MAAA,MAAM,IAAI,wBAAA,EAAyB;AAAA,IACrC;AAAA,EACF;AACF;;;AC9EO,IAAM,sBAAA,GAAN,cAAqC,YAAA,CAAa;AAAA,EAC9C,IAAA,GAAO,4BAAA;AAAA,EAChB,WAAA,CAAY,SAAiB,OAAA,EAA+B;AAC1D,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AAAA,EACxB;AACF","file":"index.cjs","sourcesContent":["import type { MockLLMOptions, ContainerConfig } from \"../types/config.js\";\n\nconst DEFAULT_IMAGE = \"phantomllm-server:latest\";\nconst DEFAULT_PORT = 8080;\n\nexport function resolveContainerConfig(options?: MockLLMOptions): ContainerConfig {\n return {\n image: options?.image ?? process.env[\"PHANTOMLLM_IMAGE\"] ?? DEFAULT_IMAGE,\n containerPort: options?.containerPort ?? DEFAULT_PORT,\n reuse: options?.reuse ?? true,\n startupTimeout: options?.startupTimeout ?? 30_000,\n };\n}\n","import { GenericContainer, Wait } from \"testcontainers\";\nimport type { StartedTestContainer } from \"testcontainers\";\nimport type { ContainerConfig } from \"../types/config.js\";\n\nexport class ContainerManager {\n private container: StartedTestContainer | null = null;\n\n constructor(private readonly config: ContainerConfig) {}\n\n async start(): Promise<{ host: string; port: number }> {\n const container = new GenericContainer(this.config.image)\n .withExposedPorts(this.config.containerPort)\n .withWaitStrategy(\n Wait.forHttp(\"/_admin/health\", this.config.containerPort)\n .forStatusCode(200),\n )\n .withStartupTimeout(this.config.startupTimeout)\n .withLabels({ \"com.phantomllm\": \"true\" });\n\n this.container = await container.start();\n\n return {\n host: this.container.getHost(),\n port: this.container.getMappedPort(this.config.containerPort),\n };\n }\n\n async stop(): Promise<void> {\n if (this.container) {\n await this.container.stop();\n this.container = null;\n }\n }\n}\n","export abstract class MockLLMError extends Error {\n abstract readonly code: string;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = this.constructor.name;\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class AdminAPIError extends MockLLMError {\n readonly code = 'ADMIN_API_ERROR' as const;\n constructor(\n public readonly statusCode: number,\n public readonly responseBody: string,\n options?: { cause?: unknown },\n ) {\n super(\n `Admin API returned HTTP ${statusCode}: ${responseBody}`,\n options,\n );\n }\n}\n\nexport class AdminConnectionRefusedError extends MockLLMError {\n readonly code = 'ADMIN_CONNECTION_REFUSED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'Cannot connect to MockLLM admin API. The container may have crashed.',\n options,\n );\n }\n}\n\nexport class AdminTimeoutError extends MockLLMError {\n readonly code = 'ADMIN_TIMEOUT' as const;\n constructor(\n public readonly timeoutMs: number,\n options?: { cause?: unknown },\n ) {\n super(\n `Admin API did not respond within ${timeoutMs}ms.`,\n options,\n );\n }\n}\n","import {\n AdminConnectionRefusedError,\n AdminTimeoutError,\n AdminAPIError,\n} from \"../errors/admin.errors.js\";\nimport type {\n AdminStubDefinition,\n AdminHealthPayload,\n AdminRecordedRequestPayload,\n} from \"./admin.client.types.js\";\n\nconst REQUEST_TIMEOUT_MS = 5_000;\n\nexport class AdminClient {\n private pendingStubs: AdminStubDefinition[] = [];\n\n constructor(private readonly baseUrl: string) {}\n\n enqueueStub(stub: AdminStubDefinition): void {\n this.pendingStubs.push(stub);\n }\n\n async flush(): Promise<void> {\n if (this.pendingStubs.length === 0) return;\n const stubs = this.pendingStubs.splice(0);\n await this.post(\"/_admin/stubs/batch\", { stubs });\n }\n\n async clearStubs(): Promise<void> {\n await this.flush();\n await this.delete(\"/_admin/stubs\");\n }\n\n async getHealth(): Promise<AdminHealthPayload> {\n return this.get<AdminHealthPayload>(\"/_admin/health\");\n }\n\n async getRequests(): Promise<AdminRecordedRequestPayload[\"requests\"]> {\n await this.flush();\n const data = await this.get<AdminRecordedRequestPayload>(\"/_admin/requests\");\n return data.requests;\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await this.fetch(path, { method: \"GET\" });\n return (await response.json()) as T;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const response = await this.fetch(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return (await response.json()) as T;\n }\n\n private async delete(path: string): Promise<void> {\n await this.fetch(path, { method: \"DELETE\" });\n }\n\n private async fetch(path: string, init: RequestInit): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n let response: Response;\n\n try {\n response = await fetch(url, {\n ...init,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n if (error instanceof TypeError) {\n const cause = error.cause as { code?: string } | undefined;\n if (cause?.code === \"ECONNREFUSED\") {\n throw new AdminConnectionRefusedError({ cause: error });\n }\n }\n if (error instanceof DOMException && error.name === \"TimeoutError\") {\n throw new AdminTimeoutError(REQUEST_TIMEOUT_MS, { cause: error });\n }\n throw error;\n }\n\n if (!response.ok) {\n const body = await response.text();\n throw new AdminAPIError(response.status, body);\n }\n\n return response;\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport type { AdminStubMatcher } from \"../driver/admin.client.types.js\";\n\nexport class ChatCompletionStubBuilder {\n private readonly matcher: AdminStubMatcher = { endpoint: \"chat\" };\n\n constructor(private readonly adminClient: AdminClient) {}\n\n forModel(model: string): this {\n this.matcher.model = model;\n return this;\n }\n\n withMessageContaining(substring: string): this {\n this.matcher.content = substring;\n return this;\n }\n\n willReturn(content: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"chat\", body: content },\n });\n }\n\n willStream(chunks: string[]): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"streaming-chat\", chunks },\n });\n }\n\n willError(statusCode: number, message: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: {\n type: \"error\",\n status: statusCode,\n error: { message, type: \"api_error\", code: null },\n },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport type { AdminStubMatcher } from \"../driver/admin.client.types.js\";\n\nexport class EmbeddingStubBuilder {\n private readonly matcher: AdminStubMatcher = { endpoint: \"embeddings\" };\n\n constructor(private readonly adminClient: AdminClient) {}\n\n forModel(model: string): this {\n this.matcher.model = model;\n return this;\n }\n\n willReturn(vector: number[] | number[][]): void {\n const vectors = Array.isArray(vector[0])\n ? (vector as number[][])\n : [vector as number[]];\n\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"embedding\", vectors },\n });\n }\n\n willError(statusCode: number, message: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: {\n type: \"error\",\n status: statusCode,\n error: { message, type: \"api_error\", code: null },\n },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\n\nexport class ModelsStubBuilder {\n constructor(private readonly adminClient: AdminClient) {}\n\n willReturn(models: Array<{ id: string; ownedBy?: string }>): void {\n this.adminClient.enqueueStub({\n matcher: {},\n response: { type: \"models\", models },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport { ChatCompletionStubBuilder } from \"./chat.builder.js\";\nimport { EmbeddingStubBuilder } from \"./embedding.builder.js\";\nimport { ModelsStubBuilder } from \"./models.builder.js\";\n\nexport class GivenStubs {\n constructor(private readonly adminClient: AdminClient) {}\n\n get chatCompletion(): ChatCompletionStubBuilder {\n return new ChatCompletionStubBuilder(this.adminClient);\n }\n\n get embedding(): EmbeddingStubBuilder {\n return new EmbeddingStubBuilder(this.adminClient);\n }\n\n get models(): ModelsStubBuilder {\n return new ModelsStubBuilder(this.adminClient);\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class ContainerNotStartedError extends MockLLMError {\n readonly code = 'CONTAINER_NOT_STARTED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'MockLLM container is not running. Call `await mock.start()` before configuring stubs.',\n options,\n );\n }\n}\n\nexport class ContainerAlreadyStartedError extends MockLLMError {\n readonly code = 'CONTAINER_ALREADY_STARTED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'MockLLM container is already running. Call `await mock.stop()` first if you need to restart.',\n options,\n );\n }\n}\n","import type { MockLLMOptions } from \"../types/config.js\";\nimport { resolveContainerConfig } from \"./container.config.js\";\nimport { ContainerManager } from \"./container.manager.js\";\nimport { AdminClient } from \"./admin.client.js\";\nimport { GivenStubs } from \"../stubs/given.js\";\nimport { ContainerNotStartedError } from \"../errors/lifecycle.errors.js\";\n\ntype MockLLMState = \"idle\" | \"starting\" | \"running\" | \"stopping\" | \"stopped\";\n\nexport class MockLLM {\n private state: MockLLMState = \"idle\";\n private startPromise: Promise<void> | null = null;\n private containerManager: ContainerManager;\n private adminClient: AdminClient | null = null;\n private _given: GivenStubs | null = null;\n private _baseUrl: string | null = null;\n\n constructor(options?: MockLLMOptions) {\n const config = resolveContainerConfig(options);\n this.containerManager = new ContainerManager(config);\n }\n\n async start(): Promise<void> {\n if (this.state === \"running\") return;\n if (this.state === \"starting\" && this.startPromise) return this.startPromise;\n\n this.state = \"starting\";\n this.startPromise = this.doStart();\n return this.startPromise;\n }\n\n private async doStart(): Promise<void> {\n const { host, port } = await this.containerManager.start();\n this._baseUrl = `http://${host}:${port}`;\n this.adminClient = new AdminClient(this._baseUrl);\n this._given = new GivenStubs(this.adminClient);\n this.state = \"running\";\n }\n\n async stop(): Promise<void> {\n if (this.state === \"stopped\" || this.state === \"idle\") return;\n this.state = \"stopping\";\n try {\n await this.containerManager.stop();\n } finally {\n this.state = \"stopped\";\n this.adminClient = null;\n this._given = null;\n this._baseUrl = null;\n }\n }\n\n get baseUrl(): string {\n this.assertRunning();\n return this._baseUrl!;\n }\n\n get apiBaseUrl(): string {\n return `${this.baseUrl}/v1`;\n }\n\n get given(): GivenStubs {\n this.assertRunning();\n return this._given!;\n }\n\n async clear(): Promise<void> {\n this.assertRunning();\n await this.adminClient!.clearStubs();\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.stop();\n }\n\n private assertRunning(): void {\n if (this.state !== \"running\") {\n throw new ContainerNotStartedError();\n }\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class StubConfigurationError extends MockLLMError {\n readonly code = 'STUB_CONFIGURATION_INVALID' as const;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/driver/container.config.ts","../src/driver/container.manager.ts","../src/errors/base.ts","../src/errors/admin.errors.ts","../src/driver/admin.client.ts","../src/stubs/chat.builder.ts","../src/stubs/embedding.builder.ts","../src/stubs/models.builder.ts","../src/stubs/given.ts","../src/stubs/expect.ts","../src/errors/lifecycle.errors.ts","../src/driver/mock-llm.ts","../src/errors/stub.errors.ts"],"names":["GenericContainer","Wait"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { MockLLMOptions, ContainerConfig } from \"../types/config.js\";\n\nconst DEFAULT_IMAGE = \"phantomllm-server:latest\";\nconst DEFAULT_PORT = 8080;\n\nexport function resolveContainerConfig(options?: MockLLMOptions): ContainerConfig {\n return {\n image: options?.image ?? process.env[\"PHANTOMLLM_IMAGE\"] ?? DEFAULT_IMAGE,\n containerPort: options?.containerPort ?? DEFAULT_PORT,\n reuse: options?.reuse ?? true,\n startupTimeout: options?.startupTimeout ?? 30_000,\n };\n}\n","import { GenericContainer, Wait } from \"testcontainers\";\nimport type { StartedTestContainer } from \"testcontainers\";\nimport type { ContainerConfig } from \"../types/config.js\";\n\nexport class ContainerManager {\n private container: StartedTestContainer | null = null;\n\n constructor(private readonly config: ContainerConfig) {}\n\n async start(): Promise<{ host: string; port: number }> {\n const builder = new GenericContainer(this.config.image)\n .withExposedPorts(this.config.containerPort)\n .withWaitStrategy(\n Wait.forHttp(\"/_admin/health\", this.config.containerPort)\n .forStatusCode(200),\n )\n .withStartupTimeout(this.config.startupTimeout)\n .withLabels({ \"com.phantomllm\": \"true\" });\n\n this.container = await builder.start();\n\n return {\n host: this.container.getHost(),\n port: this.container.getMappedPort(this.config.containerPort),\n };\n }\n\n async stop(): Promise<void> {\n if (this.container) {\n await this.container.stop();\n this.container = null;\n }\n }\n}\n","export abstract class MockLLMError extends Error {\n abstract readonly code: string;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = this.constructor.name;\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class AdminAPIError extends MockLLMError {\n readonly code = 'ADMIN_API_ERROR' as const;\n constructor(\n public readonly statusCode: number,\n public readonly responseBody: string,\n options?: { cause?: unknown },\n ) {\n super(\n `Admin API returned HTTP ${statusCode}: ${responseBody}`,\n options,\n );\n }\n}\n\nexport class AdminConnectionRefusedError extends MockLLMError {\n readonly code = 'ADMIN_CONNECTION_REFUSED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'Cannot connect to MockLLM admin API. The container may have crashed.',\n options,\n );\n }\n}\n\nexport class AdminTimeoutError extends MockLLMError {\n readonly code = 'ADMIN_TIMEOUT' as const;\n constructor(\n public readonly timeoutMs: number,\n options?: { cause?: unknown },\n ) {\n super(\n `Admin API did not respond within ${timeoutMs}ms.`,\n options,\n );\n }\n}\n","import {\n AdminConnectionRefusedError,\n AdminTimeoutError,\n AdminAPIError,\n} from \"../errors/admin.errors.js\";\nimport type {\n AdminStubDefinition,\n AdminHealthPayload,\n AdminRecordedRequestPayload,\n} from \"./admin.client.types.js\";\n\nconst REQUEST_TIMEOUT_MS = 5_000;\n\nexport class AdminClient {\n private pendingStubs: AdminStubDefinition[] = [];\n private pendingConfigs: Array<{ apiKey: string }> = [];\n\n constructor(private readonly baseUrl: string) {}\n\n enqueueStub(stub: AdminStubDefinition): void {\n this.pendingStubs.push(stub);\n }\n\n enqueueConfig(config: { apiKey: string }): void {\n this.pendingConfigs.push(config);\n }\n\n async flush(): Promise<void> {\n if (this.pendingConfigs.length > 0) {\n const configs = this.pendingConfigs.splice(0);\n for (const config of configs) {\n await this.post(\"/_admin/config\", config);\n }\n }\n\n if (this.pendingStubs.length > 0) {\n const stubs = this.pendingStubs.splice(0);\n await this.post(\"/_admin/stubs/batch\", { stubs });\n }\n }\n\n async clearStubs(): Promise<void> {\n await this.flush();\n await this.delete(\"/_admin/stubs\");\n }\n\n async getHealth(): Promise<AdminHealthPayload> {\n return this.get<AdminHealthPayload>(\"/_admin/health\");\n }\n\n async getRequests(): Promise<AdminRecordedRequestPayload[\"requests\"]> {\n await this.flush();\n const data = await this.get<AdminRecordedRequestPayload>(\"/_admin/requests\");\n return data.requests;\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await this.fetch(path, { method: \"GET\" });\n return (await response.json()) as T;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const response = await this.fetch(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return (await response.json()) as T;\n }\n\n private async delete(path: string): Promise<void> {\n await this.fetch(path, { method: \"DELETE\" });\n }\n\n private async fetch(path: string, init: RequestInit): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n let response: Response;\n\n try {\n response = await fetch(url, {\n ...init,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n if (error instanceof TypeError) {\n const cause = error.cause as { code?: string } | undefined;\n if (cause?.code === \"ECONNREFUSED\") {\n throw new AdminConnectionRefusedError({ cause: error });\n }\n }\n if (error instanceof DOMException && error.name === \"TimeoutError\") {\n throw new AdminTimeoutError(REQUEST_TIMEOUT_MS, { cause: error });\n }\n throw error;\n }\n\n if (!response.ok) {\n const body = await response.text();\n throw new AdminAPIError(response.status, body);\n }\n\n return response;\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport type { AdminStubMatcher } from \"../driver/admin.client.types.js\";\n\nexport class ChatCompletionStubBuilder {\n private readonly matcher: AdminStubMatcher = { endpoint: \"chat\" };\n\n constructor(private readonly adminClient: AdminClient) {}\n\n forModel(model: string): this {\n this.matcher.model = model;\n return this;\n }\n\n withMessageContaining(substring: string): this {\n this.matcher.content = substring;\n return this;\n }\n\n willReturn(content: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"chat\", body: content },\n });\n }\n\n willStream(chunks: string[]): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"streaming-chat\", chunks },\n });\n }\n\n willError(statusCode: number, message: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: {\n type: \"error\",\n status: statusCode,\n error: { message, type: \"api_error\", code: null },\n },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport type { AdminStubMatcher } from \"../driver/admin.client.types.js\";\n\nexport class EmbeddingStubBuilder {\n private readonly matcher: AdminStubMatcher = { endpoint: \"embeddings\" };\n\n constructor(private readonly adminClient: AdminClient) {}\n\n forModel(model: string): this {\n this.matcher.model = model;\n return this;\n }\n\n willReturn(vector: number[] | number[][]): void {\n const vectors = Array.isArray(vector[0])\n ? (vector as number[][])\n : [vector as number[]];\n\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"embedding\", vectors },\n });\n }\n\n willError(statusCode: number, message: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: {\n type: \"error\",\n status: statusCode,\n error: { message, type: \"api_error\", code: null },\n },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\n\nexport class ModelsStubBuilder {\n constructor(private readonly adminClient: AdminClient) {}\n\n willReturn(models: Array<{ id: string; ownedBy?: string }>): void {\n this.adminClient.enqueueStub({\n matcher: {},\n response: { type: \"models\", models },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport { ChatCompletionStubBuilder } from \"./chat.builder.js\";\nimport { EmbeddingStubBuilder } from \"./embedding.builder.js\";\nimport { ModelsStubBuilder } from \"./models.builder.js\";\n\nexport class GivenStubs {\n constructor(private readonly adminClient: AdminClient) {}\n\n get chatCompletion(): ChatCompletionStubBuilder {\n return new ChatCompletionStubBuilder(this.adminClient);\n }\n\n get embedding(): EmbeddingStubBuilder {\n return new EmbeddingStubBuilder(this.adminClient);\n }\n\n get models(): ModelsStubBuilder {\n return new ModelsStubBuilder(this.adminClient);\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\n\nexport class ExpectConditions {\n constructor(private readonly adminClient: AdminClient) {}\n\n apiKey(key: string): void {\n this.adminClient.enqueueConfig({ apiKey: key });\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class ContainerNotStartedError extends MockLLMError {\n readonly code = 'CONTAINER_NOT_STARTED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'MockLLM container is not running. Call `await mock.start()` before configuring stubs.',\n options,\n );\n }\n}\n\nexport class ContainerAlreadyStartedError extends MockLLMError {\n readonly code = 'CONTAINER_ALREADY_STARTED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'MockLLM container is already running. Call `await mock.stop()` first if you need to restart.',\n options,\n );\n }\n}\n","import type { MockLLMOptions } from \"../types/config.js\";\nimport { resolveContainerConfig } from \"./container.config.js\";\nimport { ContainerManager } from \"./container.manager.js\";\nimport { AdminClient } from \"./admin.client.js\";\nimport { GivenStubs } from \"../stubs/given.js\";\nimport { ExpectConditions } from \"../stubs/expect.js\";\nimport { ContainerNotStartedError } from \"../errors/lifecycle.errors.js\";\n\ntype MockLLMState = \"idle\" | \"starting\" | \"running\" | \"stopping\" | \"stopped\";\n\nexport class MockLLM {\n private state: MockLLMState = \"idle\";\n private startPromise: Promise<void> | null = null;\n private containerManager: ContainerManager;\n private adminClient: AdminClient | null = null;\n private _given: GivenStubs | null = null;\n private _expect: ExpectConditions | null = null;\n private _baseUrl: string | null = null;\n\n constructor(options?: MockLLMOptions) {\n const config = resolveContainerConfig(options);\n this.containerManager = new ContainerManager(config);\n }\n\n async start(): Promise<void> {\n if (this.state === \"running\") return;\n if (this.state === \"starting\" && this.startPromise) return this.startPromise;\n\n this.state = \"starting\";\n this.startPromise = this.doStart();\n return this.startPromise;\n }\n\n private async doStart(): Promise<void> {\n const { host, port } = await this.containerManager.start();\n this._baseUrl = `http://${host}:${port}`;\n this.adminClient = new AdminClient(this._baseUrl);\n this._given = new GivenStubs(this.adminClient);\n this._expect = new ExpectConditions(this.adminClient);\n this.state = \"running\";\n }\n\n async stop(): Promise<void> {\n if (this.state === \"stopped\" || this.state === \"idle\") return;\n this.state = \"stopping\";\n try {\n await this.containerManager.stop();\n } finally {\n this.state = \"stopped\";\n this.adminClient = null;\n this._given = null;\n this._expect = null;\n this._baseUrl = null;\n }\n }\n\n get baseUrl(): string {\n this.assertRunning();\n return this._baseUrl!;\n }\n\n get apiBaseUrl(): string {\n return `${this.baseUrl}/v1`;\n }\n\n get given(): GivenStubs {\n this.assertRunning();\n return this._given!;\n }\n\n get expect(): ExpectConditions {\n this.assertRunning();\n return this._expect!;\n }\n\n async clear(): Promise<void> {\n this.assertRunning();\n await this.adminClient!.clearStubs();\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.stop();\n }\n\n private assertRunning(): void {\n if (this.state !== \"running\") {\n throw new ContainerNotStartedError();\n }\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class StubConfigurationError extends MockLLMError {\n readonly code = 'STUB_CONFIGURATION_INVALID' as const;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -64,8 +64,12 @@ interface AdminRecordedRequestPayload {
|
|
|
64
64
|
declare class AdminClient {
|
|
65
65
|
private readonly baseUrl;
|
|
66
66
|
private pendingStubs;
|
|
67
|
+
private pendingConfigs;
|
|
67
68
|
constructor(baseUrl: string);
|
|
68
69
|
enqueueStub(stub: AdminStubDefinition): void;
|
|
70
|
+
enqueueConfig(config: {
|
|
71
|
+
apiKey: string;
|
|
72
|
+
}): void;
|
|
69
73
|
flush(): Promise<void>;
|
|
70
74
|
clearStubs(): Promise<void>;
|
|
71
75
|
getHealth(): Promise<AdminHealthPayload>;
|
|
@@ -113,12 +117,19 @@ declare class GivenStubs {
|
|
|
113
117
|
get models(): ModelsStubBuilder;
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
declare class ExpectConditions {
|
|
121
|
+
private readonly adminClient;
|
|
122
|
+
constructor(adminClient: AdminClient);
|
|
123
|
+
apiKey(key: string): void;
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
declare class MockLLM {
|
|
117
127
|
private state;
|
|
118
128
|
private startPromise;
|
|
119
129
|
private containerManager;
|
|
120
130
|
private adminClient;
|
|
121
131
|
private _given;
|
|
132
|
+
private _expect;
|
|
122
133
|
private _baseUrl;
|
|
123
134
|
constructor(options?: MockLLMOptions);
|
|
124
135
|
start(): Promise<void>;
|
|
@@ -127,6 +138,7 @@ declare class MockLLM {
|
|
|
127
138
|
get baseUrl(): string;
|
|
128
139
|
get apiBaseUrl(): string;
|
|
129
140
|
get given(): GivenStubs;
|
|
141
|
+
get expect(): ExpectConditions;
|
|
130
142
|
clear(): Promise<void>;
|
|
131
143
|
[Symbol.asyncDispose](): Promise<void>;
|
|
132
144
|
private assertRunning;
|
package/dist/index.d.ts
CHANGED
|
@@ -64,8 +64,12 @@ interface AdminRecordedRequestPayload {
|
|
|
64
64
|
declare class AdminClient {
|
|
65
65
|
private readonly baseUrl;
|
|
66
66
|
private pendingStubs;
|
|
67
|
+
private pendingConfigs;
|
|
67
68
|
constructor(baseUrl: string);
|
|
68
69
|
enqueueStub(stub: AdminStubDefinition): void;
|
|
70
|
+
enqueueConfig(config: {
|
|
71
|
+
apiKey: string;
|
|
72
|
+
}): void;
|
|
69
73
|
flush(): Promise<void>;
|
|
70
74
|
clearStubs(): Promise<void>;
|
|
71
75
|
getHealth(): Promise<AdminHealthPayload>;
|
|
@@ -113,12 +117,19 @@ declare class GivenStubs {
|
|
|
113
117
|
get models(): ModelsStubBuilder;
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
declare class ExpectConditions {
|
|
121
|
+
private readonly adminClient;
|
|
122
|
+
constructor(adminClient: AdminClient);
|
|
123
|
+
apiKey(key: string): void;
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
declare class MockLLM {
|
|
117
127
|
private state;
|
|
118
128
|
private startPromise;
|
|
119
129
|
private containerManager;
|
|
120
130
|
private adminClient;
|
|
121
131
|
private _given;
|
|
132
|
+
private _expect;
|
|
122
133
|
private _baseUrl;
|
|
123
134
|
constructor(options?: MockLLMOptions);
|
|
124
135
|
start(): Promise<void>;
|
|
@@ -127,6 +138,7 @@ declare class MockLLM {
|
|
|
127
138
|
get baseUrl(): string;
|
|
128
139
|
get apiBaseUrl(): string;
|
|
129
140
|
get given(): GivenStubs;
|
|
141
|
+
get expect(): ExpectConditions;
|
|
130
142
|
clear(): Promise<void>;
|
|
131
143
|
[Symbol.asyncDispose](): Promise<void>;
|
|
132
144
|
private assertRunning;
|
package/dist/index.js
CHANGED
|
@@ -17,10 +17,10 @@ var ContainerManager = class {
|
|
|
17
17
|
}
|
|
18
18
|
container = null;
|
|
19
19
|
async start() {
|
|
20
|
-
const
|
|
20
|
+
const builder = new GenericContainer(this.config.image).withExposedPorts(this.config.containerPort).withWaitStrategy(
|
|
21
21
|
Wait.forHttp("/_admin/health", this.config.containerPort).forStatusCode(200)
|
|
22
22
|
).withStartupTimeout(this.config.startupTimeout).withLabels({ "com.phantomllm": "true" });
|
|
23
|
-
this.container = await
|
|
23
|
+
this.container = await builder.start();
|
|
24
24
|
return {
|
|
25
25
|
host: this.container.getHost(),
|
|
26
26
|
port: this.container.getMappedPort(this.config.containerPort)
|
|
@@ -81,13 +81,24 @@ var AdminClient = class {
|
|
|
81
81
|
this.baseUrl = baseUrl;
|
|
82
82
|
}
|
|
83
83
|
pendingStubs = [];
|
|
84
|
+
pendingConfigs = [];
|
|
84
85
|
enqueueStub(stub) {
|
|
85
86
|
this.pendingStubs.push(stub);
|
|
86
87
|
}
|
|
88
|
+
enqueueConfig(config) {
|
|
89
|
+
this.pendingConfigs.push(config);
|
|
90
|
+
}
|
|
87
91
|
async flush() {
|
|
88
|
-
if (this.
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
if (this.pendingConfigs.length > 0) {
|
|
93
|
+
const configs = this.pendingConfigs.splice(0);
|
|
94
|
+
for (const config of configs) {
|
|
95
|
+
await this.post("/_admin/config", config);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (this.pendingStubs.length > 0) {
|
|
99
|
+
const stubs = this.pendingStubs.splice(0);
|
|
100
|
+
await this.post("/_admin/stubs/batch", { stubs });
|
|
101
|
+
}
|
|
91
102
|
}
|
|
92
103
|
async clearStubs() {
|
|
93
104
|
await this.flush();
|
|
@@ -240,6 +251,16 @@ var GivenStubs = class {
|
|
|
240
251
|
}
|
|
241
252
|
};
|
|
242
253
|
|
|
254
|
+
// src/stubs/expect.ts
|
|
255
|
+
var ExpectConditions = class {
|
|
256
|
+
constructor(adminClient) {
|
|
257
|
+
this.adminClient = adminClient;
|
|
258
|
+
}
|
|
259
|
+
apiKey(key) {
|
|
260
|
+
this.adminClient.enqueueConfig({ apiKey: key });
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
243
264
|
// src/errors/lifecycle.errors.ts
|
|
244
265
|
var ContainerNotStartedError = class extends MockLLMError {
|
|
245
266
|
code = "CONTAINER_NOT_STARTED";
|
|
@@ -258,6 +279,7 @@ var MockLLM = class {
|
|
|
258
279
|
containerManager;
|
|
259
280
|
adminClient = null;
|
|
260
281
|
_given = null;
|
|
282
|
+
_expect = null;
|
|
261
283
|
_baseUrl = null;
|
|
262
284
|
constructor(options) {
|
|
263
285
|
const config = resolveContainerConfig(options);
|
|
@@ -275,6 +297,7 @@ var MockLLM = class {
|
|
|
275
297
|
this._baseUrl = `http://${host}:${port}`;
|
|
276
298
|
this.adminClient = new AdminClient(this._baseUrl);
|
|
277
299
|
this._given = new GivenStubs(this.adminClient);
|
|
300
|
+
this._expect = new ExpectConditions(this.adminClient);
|
|
278
301
|
this.state = "running";
|
|
279
302
|
}
|
|
280
303
|
async stop() {
|
|
@@ -286,6 +309,7 @@ var MockLLM = class {
|
|
|
286
309
|
this.state = "stopped";
|
|
287
310
|
this.adminClient = null;
|
|
288
311
|
this._given = null;
|
|
312
|
+
this._expect = null;
|
|
289
313
|
this._baseUrl = null;
|
|
290
314
|
}
|
|
291
315
|
}
|
|
@@ -300,6 +324,10 @@ var MockLLM = class {
|
|
|
300
324
|
this.assertRunning();
|
|
301
325
|
return this._given;
|
|
302
326
|
}
|
|
327
|
+
get expect() {
|
|
328
|
+
this.assertRunning();
|
|
329
|
+
return this._expect;
|
|
330
|
+
}
|
|
303
331
|
async clear() {
|
|
304
332
|
this.assertRunning();
|
|
305
333
|
await this.adminClient.clearStubs();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/driver/container.config.ts","../src/driver/container.manager.ts","../src/errors/base.ts","../src/errors/admin.errors.ts","../src/driver/admin.client.ts","../src/stubs/chat.builder.ts","../src/stubs/embedding.builder.ts","../src/stubs/models.builder.ts","../src/stubs/given.ts","../src/errors/lifecycle.errors.ts","../src/driver/mock-llm.ts","../src/errors/stub.errors.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { MockLLMOptions, ContainerConfig } from \"../types/config.js\";\n\nconst DEFAULT_IMAGE = \"phantomllm-server:latest\";\nconst DEFAULT_PORT = 8080;\n\nexport function resolveContainerConfig(options?: MockLLMOptions): ContainerConfig {\n return {\n image: options?.image ?? process.env[\"PHANTOMLLM_IMAGE\"] ?? DEFAULT_IMAGE,\n containerPort: options?.containerPort ?? DEFAULT_PORT,\n reuse: options?.reuse ?? true,\n startupTimeout: options?.startupTimeout ?? 30_000,\n };\n}\n","import { GenericContainer, Wait } from \"testcontainers\";\nimport type { StartedTestContainer } from \"testcontainers\";\nimport type { ContainerConfig } from \"../types/config.js\";\n\nexport class ContainerManager {\n private container: StartedTestContainer | null = null;\n\n constructor(private readonly config: ContainerConfig) {}\n\n async start(): Promise<{ host: string; port: number }> {\n const container = new GenericContainer(this.config.image)\n .withExposedPorts(this.config.containerPort)\n .withWaitStrategy(\n Wait.forHttp(\"/_admin/health\", this.config.containerPort)\n .forStatusCode(200),\n )\n .withStartupTimeout(this.config.startupTimeout)\n .withLabels({ \"com.phantomllm\": \"true\" });\n\n this.container = await container.start();\n\n return {\n host: this.container.getHost(),\n port: this.container.getMappedPort(this.config.containerPort),\n };\n }\n\n async stop(): Promise<void> {\n if (this.container) {\n await this.container.stop();\n this.container = null;\n }\n }\n}\n","export abstract class MockLLMError extends Error {\n abstract readonly code: string;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = this.constructor.name;\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class AdminAPIError extends MockLLMError {\n readonly code = 'ADMIN_API_ERROR' as const;\n constructor(\n public readonly statusCode: number,\n public readonly responseBody: string,\n options?: { cause?: unknown },\n ) {\n super(\n `Admin API returned HTTP ${statusCode}: ${responseBody}`,\n options,\n );\n }\n}\n\nexport class AdminConnectionRefusedError extends MockLLMError {\n readonly code = 'ADMIN_CONNECTION_REFUSED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'Cannot connect to MockLLM admin API. The container may have crashed.',\n options,\n );\n }\n}\n\nexport class AdminTimeoutError extends MockLLMError {\n readonly code = 'ADMIN_TIMEOUT' as const;\n constructor(\n public readonly timeoutMs: number,\n options?: { cause?: unknown },\n ) {\n super(\n `Admin API did not respond within ${timeoutMs}ms.`,\n options,\n );\n }\n}\n","import {\n AdminConnectionRefusedError,\n AdminTimeoutError,\n AdminAPIError,\n} from \"../errors/admin.errors.js\";\nimport type {\n AdminStubDefinition,\n AdminHealthPayload,\n AdminRecordedRequestPayload,\n} from \"./admin.client.types.js\";\n\nconst REQUEST_TIMEOUT_MS = 5_000;\n\nexport class AdminClient {\n private pendingStubs: AdminStubDefinition[] = [];\n\n constructor(private readonly baseUrl: string) {}\n\n enqueueStub(stub: AdminStubDefinition): void {\n this.pendingStubs.push(stub);\n }\n\n async flush(): Promise<void> {\n if (this.pendingStubs.length === 0) return;\n const stubs = this.pendingStubs.splice(0);\n await this.post(\"/_admin/stubs/batch\", { stubs });\n }\n\n async clearStubs(): Promise<void> {\n await this.flush();\n await this.delete(\"/_admin/stubs\");\n }\n\n async getHealth(): Promise<AdminHealthPayload> {\n return this.get<AdminHealthPayload>(\"/_admin/health\");\n }\n\n async getRequests(): Promise<AdminRecordedRequestPayload[\"requests\"]> {\n await this.flush();\n const data = await this.get<AdminRecordedRequestPayload>(\"/_admin/requests\");\n return data.requests;\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await this.fetch(path, { method: \"GET\" });\n return (await response.json()) as T;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const response = await this.fetch(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return (await response.json()) as T;\n }\n\n private async delete(path: string): Promise<void> {\n await this.fetch(path, { method: \"DELETE\" });\n }\n\n private async fetch(path: string, init: RequestInit): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n let response: Response;\n\n try {\n response = await fetch(url, {\n ...init,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n if (error instanceof TypeError) {\n const cause = error.cause as { code?: string } | undefined;\n if (cause?.code === \"ECONNREFUSED\") {\n throw new AdminConnectionRefusedError({ cause: error });\n }\n }\n if (error instanceof DOMException && error.name === \"TimeoutError\") {\n throw new AdminTimeoutError(REQUEST_TIMEOUT_MS, { cause: error });\n }\n throw error;\n }\n\n if (!response.ok) {\n const body = await response.text();\n throw new AdminAPIError(response.status, body);\n }\n\n return response;\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport type { AdminStubMatcher } from \"../driver/admin.client.types.js\";\n\nexport class ChatCompletionStubBuilder {\n private readonly matcher: AdminStubMatcher = { endpoint: \"chat\" };\n\n constructor(private readonly adminClient: AdminClient) {}\n\n forModel(model: string): this {\n this.matcher.model = model;\n return this;\n }\n\n withMessageContaining(substring: string): this {\n this.matcher.content = substring;\n return this;\n }\n\n willReturn(content: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"chat\", body: content },\n });\n }\n\n willStream(chunks: string[]): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"streaming-chat\", chunks },\n });\n }\n\n willError(statusCode: number, message: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: {\n type: \"error\",\n status: statusCode,\n error: { message, type: \"api_error\", code: null },\n },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport type { AdminStubMatcher } from \"../driver/admin.client.types.js\";\n\nexport class EmbeddingStubBuilder {\n private readonly matcher: AdminStubMatcher = { endpoint: \"embeddings\" };\n\n constructor(private readonly adminClient: AdminClient) {}\n\n forModel(model: string): this {\n this.matcher.model = model;\n return this;\n }\n\n willReturn(vector: number[] | number[][]): void {\n const vectors = Array.isArray(vector[0])\n ? (vector as number[][])\n : [vector as number[]];\n\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"embedding\", vectors },\n });\n }\n\n willError(statusCode: number, message: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: {\n type: \"error\",\n status: statusCode,\n error: { message, type: \"api_error\", code: null },\n },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\n\nexport class ModelsStubBuilder {\n constructor(private readonly adminClient: AdminClient) {}\n\n willReturn(models: Array<{ id: string; ownedBy?: string }>): void {\n this.adminClient.enqueueStub({\n matcher: {},\n response: { type: \"models\", models },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport { ChatCompletionStubBuilder } from \"./chat.builder.js\";\nimport { EmbeddingStubBuilder } from \"./embedding.builder.js\";\nimport { ModelsStubBuilder } from \"./models.builder.js\";\n\nexport class GivenStubs {\n constructor(private readonly adminClient: AdminClient) {}\n\n get chatCompletion(): ChatCompletionStubBuilder {\n return new ChatCompletionStubBuilder(this.adminClient);\n }\n\n get embedding(): EmbeddingStubBuilder {\n return new EmbeddingStubBuilder(this.adminClient);\n }\n\n get models(): ModelsStubBuilder {\n return new ModelsStubBuilder(this.adminClient);\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class ContainerNotStartedError extends MockLLMError {\n readonly code = 'CONTAINER_NOT_STARTED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'MockLLM container is not running. Call `await mock.start()` before configuring stubs.',\n options,\n );\n }\n}\n\nexport class ContainerAlreadyStartedError extends MockLLMError {\n readonly code = 'CONTAINER_ALREADY_STARTED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'MockLLM container is already running. Call `await mock.stop()` first if you need to restart.',\n options,\n );\n }\n}\n","import type { MockLLMOptions } from \"../types/config.js\";\nimport { resolveContainerConfig } from \"./container.config.js\";\nimport { ContainerManager } from \"./container.manager.js\";\nimport { AdminClient } from \"./admin.client.js\";\nimport { GivenStubs } from \"../stubs/given.js\";\nimport { ContainerNotStartedError } from \"../errors/lifecycle.errors.js\";\n\ntype MockLLMState = \"idle\" | \"starting\" | \"running\" | \"stopping\" | \"stopped\";\n\nexport class MockLLM {\n private state: MockLLMState = \"idle\";\n private startPromise: Promise<void> | null = null;\n private containerManager: ContainerManager;\n private adminClient: AdminClient | null = null;\n private _given: GivenStubs | null = null;\n private _baseUrl: string | null = null;\n\n constructor(options?: MockLLMOptions) {\n const config = resolveContainerConfig(options);\n this.containerManager = new ContainerManager(config);\n }\n\n async start(): Promise<void> {\n if (this.state === \"running\") return;\n if (this.state === \"starting\" && this.startPromise) return this.startPromise;\n\n this.state = \"starting\";\n this.startPromise = this.doStart();\n return this.startPromise;\n }\n\n private async doStart(): Promise<void> {\n const { host, port } = await this.containerManager.start();\n this._baseUrl = `http://${host}:${port}`;\n this.adminClient = new AdminClient(this._baseUrl);\n this._given = new GivenStubs(this.adminClient);\n this.state = \"running\";\n }\n\n async stop(): Promise<void> {\n if (this.state === \"stopped\" || this.state === \"idle\") return;\n this.state = \"stopping\";\n try {\n await this.containerManager.stop();\n } finally {\n this.state = \"stopped\";\n this.adminClient = null;\n this._given = null;\n this._baseUrl = null;\n }\n }\n\n get baseUrl(): string {\n this.assertRunning();\n return this._baseUrl!;\n }\n\n get apiBaseUrl(): string {\n return `${this.baseUrl}/v1`;\n }\n\n get given(): GivenStubs {\n this.assertRunning();\n return this._given!;\n }\n\n async clear(): Promise<void> {\n this.assertRunning();\n await this.adminClient!.clearStubs();\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.stop();\n }\n\n private assertRunning(): void {\n if (this.state !== \"running\") {\n throw new ContainerNotStartedError();\n }\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class StubConfigurationError extends MockLLMError {\n readonly code = 'STUB_CONFIGURATION_INVALID' as const;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/driver/container.config.ts","../src/driver/container.manager.ts","../src/errors/base.ts","../src/errors/admin.errors.ts","../src/driver/admin.client.ts","../src/stubs/chat.builder.ts","../src/stubs/embedding.builder.ts","../src/stubs/models.builder.ts","../src/stubs/given.ts","../src/stubs/expect.ts","../src/errors/lifecycle.errors.ts","../src/driver/mock-llm.ts","../src/errors/stub.errors.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { MockLLMOptions, ContainerConfig } from \"../types/config.js\";\n\nconst DEFAULT_IMAGE = \"phantomllm-server:latest\";\nconst DEFAULT_PORT = 8080;\n\nexport function resolveContainerConfig(options?: MockLLMOptions): ContainerConfig {\n return {\n image: options?.image ?? process.env[\"PHANTOMLLM_IMAGE\"] ?? DEFAULT_IMAGE,\n containerPort: options?.containerPort ?? DEFAULT_PORT,\n reuse: options?.reuse ?? true,\n startupTimeout: options?.startupTimeout ?? 30_000,\n };\n}\n","import { GenericContainer, Wait } from \"testcontainers\";\nimport type { StartedTestContainer } from \"testcontainers\";\nimport type { ContainerConfig } from \"../types/config.js\";\n\nexport class ContainerManager {\n private container: StartedTestContainer | null = null;\n\n constructor(private readonly config: ContainerConfig) {}\n\n async start(): Promise<{ host: string; port: number }> {\n const builder = new GenericContainer(this.config.image)\n .withExposedPorts(this.config.containerPort)\n .withWaitStrategy(\n Wait.forHttp(\"/_admin/health\", this.config.containerPort)\n .forStatusCode(200),\n )\n .withStartupTimeout(this.config.startupTimeout)\n .withLabels({ \"com.phantomllm\": \"true\" });\n\n this.container = await builder.start();\n\n return {\n host: this.container.getHost(),\n port: this.container.getMappedPort(this.config.containerPort),\n };\n }\n\n async stop(): Promise<void> {\n if (this.container) {\n await this.container.stop();\n this.container = null;\n }\n }\n}\n","export abstract class MockLLMError extends Error {\n abstract readonly code: string;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = this.constructor.name;\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class AdminAPIError extends MockLLMError {\n readonly code = 'ADMIN_API_ERROR' as const;\n constructor(\n public readonly statusCode: number,\n public readonly responseBody: string,\n options?: { cause?: unknown },\n ) {\n super(\n `Admin API returned HTTP ${statusCode}: ${responseBody}`,\n options,\n );\n }\n}\n\nexport class AdminConnectionRefusedError extends MockLLMError {\n readonly code = 'ADMIN_CONNECTION_REFUSED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'Cannot connect to MockLLM admin API. The container may have crashed.',\n options,\n );\n }\n}\n\nexport class AdminTimeoutError extends MockLLMError {\n readonly code = 'ADMIN_TIMEOUT' as const;\n constructor(\n public readonly timeoutMs: number,\n options?: { cause?: unknown },\n ) {\n super(\n `Admin API did not respond within ${timeoutMs}ms.`,\n options,\n );\n }\n}\n","import {\n AdminConnectionRefusedError,\n AdminTimeoutError,\n AdminAPIError,\n} from \"../errors/admin.errors.js\";\nimport type {\n AdminStubDefinition,\n AdminHealthPayload,\n AdminRecordedRequestPayload,\n} from \"./admin.client.types.js\";\n\nconst REQUEST_TIMEOUT_MS = 5_000;\n\nexport class AdminClient {\n private pendingStubs: AdminStubDefinition[] = [];\n private pendingConfigs: Array<{ apiKey: string }> = [];\n\n constructor(private readonly baseUrl: string) {}\n\n enqueueStub(stub: AdminStubDefinition): void {\n this.pendingStubs.push(stub);\n }\n\n enqueueConfig(config: { apiKey: string }): void {\n this.pendingConfigs.push(config);\n }\n\n async flush(): Promise<void> {\n if (this.pendingConfigs.length > 0) {\n const configs = this.pendingConfigs.splice(0);\n for (const config of configs) {\n await this.post(\"/_admin/config\", config);\n }\n }\n\n if (this.pendingStubs.length > 0) {\n const stubs = this.pendingStubs.splice(0);\n await this.post(\"/_admin/stubs/batch\", { stubs });\n }\n }\n\n async clearStubs(): Promise<void> {\n await this.flush();\n await this.delete(\"/_admin/stubs\");\n }\n\n async getHealth(): Promise<AdminHealthPayload> {\n return this.get<AdminHealthPayload>(\"/_admin/health\");\n }\n\n async getRequests(): Promise<AdminRecordedRequestPayload[\"requests\"]> {\n await this.flush();\n const data = await this.get<AdminRecordedRequestPayload>(\"/_admin/requests\");\n return data.requests;\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await this.fetch(path, { method: \"GET\" });\n return (await response.json()) as T;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const response = await this.fetch(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return (await response.json()) as T;\n }\n\n private async delete(path: string): Promise<void> {\n await this.fetch(path, { method: \"DELETE\" });\n }\n\n private async fetch(path: string, init: RequestInit): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n let response: Response;\n\n try {\n response = await fetch(url, {\n ...init,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n if (error instanceof TypeError) {\n const cause = error.cause as { code?: string } | undefined;\n if (cause?.code === \"ECONNREFUSED\") {\n throw new AdminConnectionRefusedError({ cause: error });\n }\n }\n if (error instanceof DOMException && error.name === \"TimeoutError\") {\n throw new AdminTimeoutError(REQUEST_TIMEOUT_MS, { cause: error });\n }\n throw error;\n }\n\n if (!response.ok) {\n const body = await response.text();\n throw new AdminAPIError(response.status, body);\n }\n\n return response;\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport type { AdminStubMatcher } from \"../driver/admin.client.types.js\";\n\nexport class ChatCompletionStubBuilder {\n private readonly matcher: AdminStubMatcher = { endpoint: \"chat\" };\n\n constructor(private readonly adminClient: AdminClient) {}\n\n forModel(model: string): this {\n this.matcher.model = model;\n return this;\n }\n\n withMessageContaining(substring: string): this {\n this.matcher.content = substring;\n return this;\n }\n\n willReturn(content: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"chat\", body: content },\n });\n }\n\n willStream(chunks: string[]): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"streaming-chat\", chunks },\n });\n }\n\n willError(statusCode: number, message: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: {\n type: \"error\",\n status: statusCode,\n error: { message, type: \"api_error\", code: null },\n },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport type { AdminStubMatcher } from \"../driver/admin.client.types.js\";\n\nexport class EmbeddingStubBuilder {\n private readonly matcher: AdminStubMatcher = { endpoint: \"embeddings\" };\n\n constructor(private readonly adminClient: AdminClient) {}\n\n forModel(model: string): this {\n this.matcher.model = model;\n return this;\n }\n\n willReturn(vector: number[] | number[][]): void {\n const vectors = Array.isArray(vector[0])\n ? (vector as number[][])\n : [vector as number[]];\n\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: { type: \"embedding\", vectors },\n });\n }\n\n willError(statusCode: number, message: string): void {\n this.adminClient.enqueueStub({\n matcher: this.matcher,\n response: {\n type: \"error\",\n status: statusCode,\n error: { message, type: \"api_error\", code: null },\n },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\n\nexport class ModelsStubBuilder {\n constructor(private readonly adminClient: AdminClient) {}\n\n willReturn(models: Array<{ id: string; ownedBy?: string }>): void {\n this.adminClient.enqueueStub({\n matcher: {},\n response: { type: \"models\", models },\n });\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\nimport { ChatCompletionStubBuilder } from \"./chat.builder.js\";\nimport { EmbeddingStubBuilder } from \"./embedding.builder.js\";\nimport { ModelsStubBuilder } from \"./models.builder.js\";\n\nexport class GivenStubs {\n constructor(private readonly adminClient: AdminClient) {}\n\n get chatCompletion(): ChatCompletionStubBuilder {\n return new ChatCompletionStubBuilder(this.adminClient);\n }\n\n get embedding(): EmbeddingStubBuilder {\n return new EmbeddingStubBuilder(this.adminClient);\n }\n\n get models(): ModelsStubBuilder {\n return new ModelsStubBuilder(this.adminClient);\n }\n}\n","import type { AdminClient } from \"../driver/admin.client.js\";\n\nexport class ExpectConditions {\n constructor(private readonly adminClient: AdminClient) {}\n\n apiKey(key: string): void {\n this.adminClient.enqueueConfig({ apiKey: key });\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class ContainerNotStartedError extends MockLLMError {\n readonly code = 'CONTAINER_NOT_STARTED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'MockLLM container is not running. Call `await mock.start()` before configuring stubs.',\n options,\n );\n }\n}\n\nexport class ContainerAlreadyStartedError extends MockLLMError {\n readonly code = 'CONTAINER_ALREADY_STARTED' as const;\n constructor(options?: { cause?: unknown }) {\n super(\n 'MockLLM container is already running. Call `await mock.stop()` first if you need to restart.',\n options,\n );\n }\n}\n","import type { MockLLMOptions } from \"../types/config.js\";\nimport { resolveContainerConfig } from \"./container.config.js\";\nimport { ContainerManager } from \"./container.manager.js\";\nimport { AdminClient } from \"./admin.client.js\";\nimport { GivenStubs } from \"../stubs/given.js\";\nimport { ExpectConditions } from \"../stubs/expect.js\";\nimport { ContainerNotStartedError } from \"../errors/lifecycle.errors.js\";\n\ntype MockLLMState = \"idle\" | \"starting\" | \"running\" | \"stopping\" | \"stopped\";\n\nexport class MockLLM {\n private state: MockLLMState = \"idle\";\n private startPromise: Promise<void> | null = null;\n private containerManager: ContainerManager;\n private adminClient: AdminClient | null = null;\n private _given: GivenStubs | null = null;\n private _expect: ExpectConditions | null = null;\n private _baseUrl: string | null = null;\n\n constructor(options?: MockLLMOptions) {\n const config = resolveContainerConfig(options);\n this.containerManager = new ContainerManager(config);\n }\n\n async start(): Promise<void> {\n if (this.state === \"running\") return;\n if (this.state === \"starting\" && this.startPromise) return this.startPromise;\n\n this.state = \"starting\";\n this.startPromise = this.doStart();\n return this.startPromise;\n }\n\n private async doStart(): Promise<void> {\n const { host, port } = await this.containerManager.start();\n this._baseUrl = `http://${host}:${port}`;\n this.adminClient = new AdminClient(this._baseUrl);\n this._given = new GivenStubs(this.adminClient);\n this._expect = new ExpectConditions(this.adminClient);\n this.state = \"running\";\n }\n\n async stop(): Promise<void> {\n if (this.state === \"stopped\" || this.state === \"idle\") return;\n this.state = \"stopping\";\n try {\n await this.containerManager.stop();\n } finally {\n this.state = \"stopped\";\n this.adminClient = null;\n this._given = null;\n this._expect = null;\n this._baseUrl = null;\n }\n }\n\n get baseUrl(): string {\n this.assertRunning();\n return this._baseUrl!;\n }\n\n get apiBaseUrl(): string {\n return `${this.baseUrl}/v1`;\n }\n\n get given(): GivenStubs {\n this.assertRunning();\n return this._given!;\n }\n\n get expect(): ExpectConditions {\n this.assertRunning();\n return this._expect!;\n }\n\n async clear(): Promise<void> {\n this.assertRunning();\n await this.adminClient!.clearStubs();\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.stop();\n }\n\n private assertRunning(): void {\n if (this.state !== \"running\") {\n throw new ContainerNotStartedError();\n }\n }\n}\n","import { MockLLMError } from './base.js';\n\nexport class StubConfigurationError extends MockLLMError {\n readonly code = 'STUB_CONFIGURATION_INVALID' as const;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phantomllm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Dockerized mock server for OpenAI-compatible APIs. Test your LLM integrations with a real HTTP server.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
"typescript": "^5.9.3",
|
|
53
53
|
"vitest": "^4.1.0"
|
|
54
54
|
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "https://github.com/galElmalah/phantomllm.git"
|
|
58
|
+
},
|
|
55
59
|
"keywords": [
|
|
56
60
|
"openai",
|
|
57
61
|
"mock",
|