@agentgazer/proxy 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 +4 -5
- package/dist/__tests__/loop-detector.test.d.ts +2 -0
- package/dist/__tests__/loop-detector.test.d.ts.map +1 -0
- package/dist/__tests__/loop-detector.test.js +257 -0
- package/dist/__tests__/loop-detector.test.js.map +1 -0
- package/dist/__tests__/proxy-server.test.js +59 -32
- package/dist/__tests__/proxy-server.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loop-detector.d.ts +109 -0
- package/dist/loop-detector.d.ts.map +1 -0
- package/dist/loop-detector.js +312 -0
- package/dist/loop-detector.js.map +1 -0
- package/dist/proxy-server.d.ts +7 -0
- package/dist/proxy-server.d.ts.map +1 -1
- package/dist/proxy-server.js +600 -284
- package/dist/proxy-server.js.map +1 -1
- package/dist/rate-limiter.d.ts +10 -0
- package/dist/rate-limiter.d.ts.map +1 -1
- package/dist/rate-limiter.js +43 -0
- package/dist/rate-limiter.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,7 +14,6 @@ Transparent HTTP proxy for LLM API calls. Forwards requests to upstream provider
|
|
|
14
14
|
- Zhipu / GLM (`open.bigmodel.cn`)
|
|
15
15
|
- MiniMax (`api.minimax.chat`)
|
|
16
16
|
- Baichuan (`api.baichuan-ai.com`)
|
|
17
|
-
- Yi / 01.AI (`api.lingyiwanwu.com`)
|
|
18
17
|
|
|
19
18
|
## How it works
|
|
20
19
|
|
|
@@ -55,7 +54,7 @@ const { server, shutdown } = startProxy({
|
|
|
55
54
|
port: 4000,
|
|
56
55
|
apiKey: "your-token",
|
|
57
56
|
agentId: "proxy",
|
|
58
|
-
endpoint: "http://localhost:
|
|
57
|
+
endpoint: "http://localhost:18800/api/events",
|
|
59
58
|
providerKeys: {
|
|
60
59
|
openai: "sk-...",
|
|
61
60
|
anthropic: "sk-ant-...",
|
|
@@ -71,7 +70,7 @@ await shutdown();
|
|
|
71
70
|
|
|
72
71
|
### Provider detection
|
|
73
72
|
|
|
74
|
-
The proxy auto-detects the target provider from the `Host` header. Set your LLM client's base URL to `http://localhost:
|
|
73
|
+
The proxy auto-detects the target provider from the `Host` header. Set your LLM client's base URL to `http://localhost:18900` and the proxy handles routing.
|
|
75
74
|
|
|
76
75
|
If auto-detection fails, set the `x-target-url` header to specify the upstream base URL explicitly.
|
|
77
76
|
|
|
@@ -81,7 +80,7 @@ When `providerKeys` is configured, the proxy injects the correct auth header per
|
|
|
81
80
|
|
|
82
81
|
| Provider | Header |
|
|
83
82
|
|----------|--------|
|
|
84
|
-
| OpenAI, Mistral, Cohere, DeepSeek, Moonshot, Zhipu, MiniMax, Baichuan
|
|
83
|
+
| OpenAI, Mistral, Cohere, DeepSeek, Moonshot, Zhipu, MiniMax, Baichuan | `Authorization: Bearer <key>` |
|
|
85
84
|
| Anthropic | `x-api-key: <key>` |
|
|
86
85
|
| Google | `x-goog-api-key: <key>` |
|
|
87
86
|
|
|
@@ -94,7 +93,7 @@ When `rateLimits` is configured, the proxy enforces a sliding-window rate limit
|
|
|
94
93
|
### Health check
|
|
95
94
|
|
|
96
95
|
```
|
|
97
|
-
GET http://localhost:
|
|
96
|
+
GET http://localhost:18900/health
|
|
98
97
|
```
|
|
99
98
|
|
|
100
99
|
## Options
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-detector.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/loop-detector.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const loop_detector_js_1 = require("../loop-detector.js");
|
|
5
|
+
(0, vitest_1.describe)("LoopDetector", () => {
|
|
6
|
+
let detector;
|
|
7
|
+
(0, vitest_1.beforeEach)(() => {
|
|
8
|
+
detector = new loop_detector_js_1.LoopDetector();
|
|
9
|
+
});
|
|
10
|
+
(0, vitest_1.afterEach)(() => {
|
|
11
|
+
detector.stopCleanup();
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.describe)("config management", () => {
|
|
14
|
+
(0, vitest_1.it)("returns default config for unknown agent", () => {
|
|
15
|
+
const config = detector.getConfig("unknown-agent");
|
|
16
|
+
(0, vitest_1.expect)(config).toEqual({
|
|
17
|
+
enabled: false,
|
|
18
|
+
windowSize: 20,
|
|
19
|
+
threshold: 10.0,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)("allows setting config for an agent", () => {
|
|
23
|
+
detector.setConfig("agent-1", {
|
|
24
|
+
enabled: true,
|
|
25
|
+
windowSize: 50,
|
|
26
|
+
threshold: 15.0,
|
|
27
|
+
});
|
|
28
|
+
const config = detector.getConfig("agent-1");
|
|
29
|
+
(0, vitest_1.expect)(config).toEqual({
|
|
30
|
+
enabled: true,
|
|
31
|
+
windowSize: 50,
|
|
32
|
+
threshold: 15.0,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.it)("merges partial config updates", () => {
|
|
36
|
+
detector.setConfig("agent-1", { enabled: true });
|
|
37
|
+
detector.setConfig("agent-1", { threshold: 20.0 });
|
|
38
|
+
const config = detector.getConfig("agent-1");
|
|
39
|
+
(0, vitest_1.expect)(config.enabled).toBe(true);
|
|
40
|
+
(0, vitest_1.expect)(config.threshold).toBe(20.0);
|
|
41
|
+
(0, vitest_1.expect)(config.windowSize).toBe(20); // default unchanged
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
(0, vitest_1.describe)("recordRequest", () => {
|
|
45
|
+
(0, vitest_1.it)("records a request and returns hash info", () => {
|
|
46
|
+
const body = {
|
|
47
|
+
messages: [{ role: "user", content: "Hello, how are you?" }],
|
|
48
|
+
};
|
|
49
|
+
const result = detector.recordRequest("agent-1", body);
|
|
50
|
+
(0, vitest_1.expect)(typeof result.promptHash).toBe("bigint");
|
|
51
|
+
(0, vitest_1.expect)(Array.isArray(result.toolCalls)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
(0, vitest_1.it)("extracts tool calls from request", () => {
|
|
54
|
+
// Uses OpenAI tools format (in body.tools, not in messages)
|
|
55
|
+
const body = {
|
|
56
|
+
tools: [
|
|
57
|
+
{ type: "function", function: { name: "search" } },
|
|
58
|
+
{ type: "function", function: { name: "read_file" } },
|
|
59
|
+
],
|
|
60
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
61
|
+
};
|
|
62
|
+
const result = detector.recordRequest("agent-1", body);
|
|
63
|
+
(0, vitest_1.expect)(result.toolCalls).toContain("fn:search");
|
|
64
|
+
(0, vitest_1.expect)(result.toolCalls).toContain("fn:read_file");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.describe)("recordResponse", () => {
|
|
68
|
+
(0, vitest_1.it)("records response hash for latest request", () => {
|
|
69
|
+
const body = {
|
|
70
|
+
messages: [{ role: "user", content: "Test message" }],
|
|
71
|
+
};
|
|
72
|
+
detector.recordRequest("agent-1", body);
|
|
73
|
+
detector.recordResponse("agent-1", "This is the response text");
|
|
74
|
+
// No error should occur
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)("does nothing for unknown agent", () => {
|
|
77
|
+
// Should not throw
|
|
78
|
+
detector.recordResponse("unknown-agent", "response text");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.describe)("checkLoop", () => {
|
|
82
|
+
(0, vitest_1.it)("returns not loop when disabled", () => {
|
|
83
|
+
detector.setConfig("agent-1", { enabled: false });
|
|
84
|
+
const body = { messages: [{ role: "user", content: "test" }] };
|
|
85
|
+
const { promptHash, toolCalls } = detector.recordRequest("agent-1", body);
|
|
86
|
+
const result = detector.checkLoop("agent-1", promptHash, toolCalls);
|
|
87
|
+
(0, vitest_1.expect)(result.isLoop).toBe(false);
|
|
88
|
+
(0, vitest_1.expect)(result.score).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)("returns not loop for first request", () => {
|
|
91
|
+
detector.setConfig("agent-1", { enabled: true, threshold: 5 });
|
|
92
|
+
const body = { messages: [{ role: "user", content: "first request" }] };
|
|
93
|
+
const { promptHash, toolCalls } = detector.recordRequest("agent-1", body);
|
|
94
|
+
const result = detector.checkLoop("agent-1", promptHash, toolCalls);
|
|
95
|
+
(0, vitest_1.expect)(result.isLoop).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.it)("detects loop with repeated identical prompts", () => {
|
|
98
|
+
detector.setConfig("agent-1", {
|
|
99
|
+
enabled: true,
|
|
100
|
+
windowSize: 10,
|
|
101
|
+
threshold: 5,
|
|
102
|
+
});
|
|
103
|
+
// Send same prompt multiple times
|
|
104
|
+
for (let i = 0; i < 10; i++) {
|
|
105
|
+
const body = {
|
|
106
|
+
messages: [{ role: "user", content: "search for files" }],
|
|
107
|
+
};
|
|
108
|
+
detector.recordRequest("agent-1", body);
|
|
109
|
+
detector.recordResponse("agent-1", `Response ${i}`);
|
|
110
|
+
}
|
|
111
|
+
// Next identical prompt should trigger loop
|
|
112
|
+
const body = { messages: [{ role: "user", content: "search for files" }] };
|
|
113
|
+
const { promptHash, toolCalls } = detector.recordRequest("agent-1", body);
|
|
114
|
+
const result = detector.checkLoop("agent-1", promptHash, toolCalls);
|
|
115
|
+
(0, vitest_1.expect)(result.score).toBeGreaterThan(5);
|
|
116
|
+
(0, vitest_1.expect)(result.isLoop).toBe(true);
|
|
117
|
+
(0, vitest_1.expect)(result.details.similarPrompts).toBeGreaterThan(0);
|
|
118
|
+
});
|
|
119
|
+
(0, vitest_1.it)("detects loop with repeated tool calls", () => {
|
|
120
|
+
detector.setConfig("agent-1", {
|
|
121
|
+
enabled: true,
|
|
122
|
+
windowSize: 10,
|
|
123
|
+
threshold: 5,
|
|
124
|
+
});
|
|
125
|
+
// Send requests with same tool calls (using OpenAI tools format)
|
|
126
|
+
for (let i = 0; i < 5; i++) {
|
|
127
|
+
const body = {
|
|
128
|
+
tools: [
|
|
129
|
+
{ type: "function", function: { name: "read_file" } },
|
|
130
|
+
{ type: "function", function: { name: "search" } },
|
|
131
|
+
],
|
|
132
|
+
messages: [{ role: "user", content: `Query ${i}` }],
|
|
133
|
+
};
|
|
134
|
+
detector.recordRequest("agent-1", body);
|
|
135
|
+
}
|
|
136
|
+
// Next request with same tools
|
|
137
|
+
const body = {
|
|
138
|
+
tools: [
|
|
139
|
+
{ type: "function", function: { name: "read_file" } },
|
|
140
|
+
{ type: "function", function: { name: "search" } },
|
|
141
|
+
],
|
|
142
|
+
messages: [{ role: "user", content: "Query 5" }],
|
|
143
|
+
};
|
|
144
|
+
const { promptHash, toolCalls } = detector.recordRequest("agent-1", body);
|
|
145
|
+
const result = detector.checkLoop("agent-1", promptHash, toolCalls);
|
|
146
|
+
(0, vitest_1.expect)(result.details.repeatedToolCalls).toBeGreaterThan(0);
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.it)("returns score details", () => {
|
|
149
|
+
detector.setConfig("agent-1", { enabled: true, threshold: 100 });
|
|
150
|
+
const body = { messages: [{ role: "user", content: "test" }] };
|
|
151
|
+
detector.recordRequest("agent-1", body);
|
|
152
|
+
const { promptHash, toolCalls } = detector.recordRequest("agent-1", body);
|
|
153
|
+
const result = detector.checkLoop("agent-1", promptHash, toolCalls);
|
|
154
|
+
(0, vitest_1.expect)(result).toHaveProperty("score");
|
|
155
|
+
(0, vitest_1.expect)(result).toHaveProperty("details");
|
|
156
|
+
(0, vitest_1.expect)(result.details).toHaveProperty("similarPrompts");
|
|
157
|
+
(0, vitest_1.expect)(result.details).toHaveProperty("similarResponses");
|
|
158
|
+
(0, vitest_1.expect)(result.details).toHaveProperty("repeatedToolCalls");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
(0, vitest_1.describe)("clearAgent", () => {
|
|
162
|
+
(0, vitest_1.it)("clears all state for an agent", () => {
|
|
163
|
+
detector.setConfig("agent-1", { enabled: true });
|
|
164
|
+
const body = { messages: [{ role: "user", content: "test" }] };
|
|
165
|
+
detector.recordRequest("agent-1", body);
|
|
166
|
+
detector.recordRequest("agent-1", body);
|
|
167
|
+
detector.clearAgent("agent-1");
|
|
168
|
+
// After clearing, checkLoop should see empty history
|
|
169
|
+
const { promptHash, toolCalls } = detector.recordRequest("agent-1", body);
|
|
170
|
+
const result = detector.checkLoop("agent-1", promptHash, toolCalls);
|
|
171
|
+
// Should have no past requests to compare against
|
|
172
|
+
(0, vitest_1.expect)(result.details.similarPrompts).toBe(0);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
(0, vitest_1.describe)("clearAll", () => {
|
|
176
|
+
(0, vitest_1.it)("clears all agent state", () => {
|
|
177
|
+
detector.setConfig("agent-1", { enabled: true });
|
|
178
|
+
detector.setConfig("agent-2", { enabled: true, threshold: 20 });
|
|
179
|
+
const body = { messages: [{ role: "user", content: "test" }] };
|
|
180
|
+
detector.recordRequest("agent-1", body);
|
|
181
|
+
detector.recordRequest("agent-2", body);
|
|
182
|
+
detector.clearAll();
|
|
183
|
+
// Configs should be reset to defaults
|
|
184
|
+
(0, vitest_1.expect)(detector.getConfig("agent-1")).toEqual({
|
|
185
|
+
enabled: false,
|
|
186
|
+
windowSize: 20,
|
|
187
|
+
threshold: 10.0,
|
|
188
|
+
});
|
|
189
|
+
(0, vitest_1.expect)(detector.getConfig("agent-2")).toEqual({
|
|
190
|
+
enabled: false,
|
|
191
|
+
windowSize: 20,
|
|
192
|
+
threshold: 10.0,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
(0, vitest_1.describe)("TTL cleanup", () => {
|
|
197
|
+
(0, vitest_1.it)("tracks agent count", () => {
|
|
198
|
+
(0, vitest_1.expect)(detector.getAgentCount()).toBe(0);
|
|
199
|
+
const body = { messages: [{ role: "user", content: "test" }] };
|
|
200
|
+
detector.recordRequest("agent-1", body);
|
|
201
|
+
detector.recordRequest("agent-2", body);
|
|
202
|
+
(0, vitest_1.expect)(detector.getAgentCount()).toBe(2);
|
|
203
|
+
});
|
|
204
|
+
(0, vitest_1.it)("cleans up inactive agents based on TTL", () => {
|
|
205
|
+
vitest_1.vi.useFakeTimers();
|
|
206
|
+
// Create detector with very short TTL (100ms)
|
|
207
|
+
const shortTtlDetector = new loop_detector_js_1.LoopDetector(100);
|
|
208
|
+
const body = { messages: [{ role: "user", content: "test" }] };
|
|
209
|
+
shortTtlDetector.recordRequest("agent-1", body);
|
|
210
|
+
shortTtlDetector.recordRequest("agent-2", body);
|
|
211
|
+
(0, vitest_1.expect)(shortTtlDetector.getAgentCount()).toBe(2);
|
|
212
|
+
// Manually trigger cleanup before TTL expires
|
|
213
|
+
let cleaned = shortTtlDetector.cleanupInactiveAgents();
|
|
214
|
+
(0, vitest_1.expect)(cleaned).toBe(0);
|
|
215
|
+
(0, vitest_1.expect)(shortTtlDetector.getAgentCount()).toBe(2);
|
|
216
|
+
// Advance time past TTL
|
|
217
|
+
vitest_1.vi.advanceTimersByTime(150);
|
|
218
|
+
// Now cleanup should remove inactive agents
|
|
219
|
+
cleaned = shortTtlDetector.cleanupInactiveAgents();
|
|
220
|
+
(0, vitest_1.expect)(cleaned).toBe(2);
|
|
221
|
+
(0, vitest_1.expect)(shortTtlDetector.getAgentCount()).toBe(0);
|
|
222
|
+
vitest_1.vi.useRealTimers();
|
|
223
|
+
shortTtlDetector.stopCleanup();
|
|
224
|
+
});
|
|
225
|
+
(0, vitest_1.it)("preserves active agents during cleanup", () => {
|
|
226
|
+
vitest_1.vi.useFakeTimers();
|
|
227
|
+
const shortTtlDetector = new loop_detector_js_1.LoopDetector(100);
|
|
228
|
+
const body = { messages: [{ role: "user", content: "test" }] };
|
|
229
|
+
shortTtlDetector.recordRequest("agent-1", body);
|
|
230
|
+
shortTtlDetector.recordRequest("agent-2", body);
|
|
231
|
+
// Advance time past TTL
|
|
232
|
+
vitest_1.vi.advanceTimersByTime(150);
|
|
233
|
+
// Touch agent-1 to make it active again (still in fake time)
|
|
234
|
+
shortTtlDetector.recordRequest("agent-1", body);
|
|
235
|
+
// Only agent-2 should be cleaned up
|
|
236
|
+
const cleaned = shortTtlDetector.cleanupInactiveAgents();
|
|
237
|
+
(0, vitest_1.expect)(cleaned).toBe(1);
|
|
238
|
+
(0, vitest_1.expect)(shortTtlDetector.getAgentCount()).toBe(1);
|
|
239
|
+
(0, vitest_1.expect)(shortTtlDetector.getConfig("agent-1")).toBeDefined();
|
|
240
|
+
vitest_1.vi.useRealTimers();
|
|
241
|
+
shortTtlDetector.stopCleanup();
|
|
242
|
+
});
|
|
243
|
+
(0, vitest_1.it)("allows setting and getting TTL", () => {
|
|
244
|
+
(0, vitest_1.expect)(detector.getTtl()).toBe(24 * 60 * 60 * 1000); // default 24h
|
|
245
|
+
detector.setTtl(1000);
|
|
246
|
+
(0, vitest_1.expect)(detector.getTtl()).toBe(1000);
|
|
247
|
+
});
|
|
248
|
+
(0, vitest_1.it)("starts and stops cleanup timer", () => {
|
|
249
|
+
// Should not throw
|
|
250
|
+
detector.startCleanup(1000);
|
|
251
|
+
detector.startCleanup(1000); // idempotent
|
|
252
|
+
detector.stopCleanup();
|
|
253
|
+
detector.stopCleanup(); // idempotent
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
//# sourceMappingURL=loop-detector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-detector.test.js","sourceRoot":"","sources":["../../src/__tests__/loop-detector.test.ts"],"names":[],"mappings":";;AAAA,mCAAyE;AACzE,0DAAmD;AAEnD,IAAA,iBAAQ,EAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,IAAI,QAAsB,CAAC;IAE3B,IAAA,mBAAU,EAAC,GAAG,EAAE;QACd,QAAQ,GAAG,IAAI,+BAAY,EAAE,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,IAAA,kBAAS,EAAC,GAAG,EAAE;QACb,QAAQ,CAAC,WAAW,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,IAAA,iBAAQ,EAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,IAAA,WAAE,EAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;YACnD,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB,OAAO,EAAE,KAAK;gBACd,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE;gBAC5B,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAC7C,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAEnD,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAC7C,IAAA,eAAM,EAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,IAAA,eAAM,EAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,IAAA,eAAM,EAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,oBAAoB;QAC1D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAA,iBAAQ,EAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,IAAA,WAAE,EAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,IAAI,GAAG;gBACX,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;aAC7D,CAAC;YAEF,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAEvD,IAAA,eAAM,EAAC,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChD,IAAA,eAAM,EAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,4DAA4D;YAC5D,MAAM,IAAI,GAAG;gBACX,KAAK,EAAE;oBACL,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;oBAClD,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;iBACtD;gBACD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;aAC/C,CAAC;YAEF,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAEvD,IAAA,eAAM,EAAC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAChD,IAAA,eAAM,EAAC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAA,iBAAQ,EAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,IAAA,WAAE,EAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,IAAI,GAAG;gBACX,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;aACtD,CAAC;YAEF,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACxC,QAAQ,CAAC,cAAc,CAAC,SAAS,EAAE,2BAA2B,CAAC,CAAC;YAEhE,wBAAwB;QAC1B,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,mBAAmB;YACnB,QAAQ,CAAC,cAAc,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAA,iBAAQ,EAAC,WAAW,EAAE,GAAG,EAAE;QACzB,IAAA,WAAE,EAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YAElD,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC/D,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAE1E,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YAEpE,IAAA,eAAM,EAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClC,IAAA,eAAM,EAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;YAE/D,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;YACxE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAE1E,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YAEpE,IAAA,eAAM,EAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE;gBAC5B,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,CAAC;aACb,CAAC,CAAC;YAEH,kCAAkC;YAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC5B,MAAM,IAAI,GAAG;oBACX,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;iBAC1D,CAAC;gBACF,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;gBACxC,QAAQ,CAAC,cAAc,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;YACtD,CAAC;YAED,4CAA4C;YAC5C,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC;YAC3E,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YAEpE,IAAA,eAAM,EAAC,MAAM,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YACxC,IAAA,eAAM,EAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,IAAA,eAAM,EAAC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE;gBAC5B,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,CAAC;aACb,CAAC,CAAC;YAEH,iEAAiE;YACjE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG;oBACX,KAAK,EAAE;wBACL,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;wBACrD,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;qBACnD;oBACD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC;iBACpD,CAAC;gBACF,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC1C,CAAC;YAED,+BAA+B;YAC/B,MAAM,IAAI,GAAG;gBACX,KAAK,EAAE;oBACL,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;oBACrD,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;iBACnD;gBACD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;aACjD,CAAC;YACF,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YAEpE,IAAA,eAAM,EAAC,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,uBAAuB,EAAE,GAAG,EAAE;YAC/B,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YAEjE,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC/D,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAExC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YAEpE,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YACvC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;YACzC,IAAA,eAAM,EAAC,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;YACxD,IAAA,eAAM,EAAC,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC;YAC1D,IAAA,eAAM,EAAC,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAA,iBAAQ,EAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,IAAA,WAAE,EAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAEjD,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC/D,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACxC,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAExC,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAE/B,qDAAqD;YACrD,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YAEpE,kDAAkD;YAClD,IAAA,eAAM,EAAC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAA,iBAAQ,EAAC,UAAU,EAAE,GAAG,EAAE;QACxB,IAAA,WAAE,EAAC,wBAAwB,EAAE,GAAG,EAAE;YAChC,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;YAEhE,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC/D,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACxC,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAExC,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAEpB,sCAAsC;YACtC,IAAA,eAAM,EAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;gBAC5C,OAAO,EAAE,KAAK;gBACd,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;YACH,IAAA,eAAM,EAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;gBAC5C,OAAO,EAAE,KAAK;gBACd,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAA,iBAAQ,EAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,IAAA,WAAE,EAAC,oBAAoB,EAAE,GAAG,EAAE;YAC5B,IAAA,eAAM,EAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEzC,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC/D,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACxC,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAExC,IAAA,eAAM,EAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,WAAE,CAAC,aAAa,EAAE,CAAC;YAEnB,8CAA8C;YAC9C,MAAM,gBAAgB,GAAG,IAAI,+BAAY,CAAC,GAAG,CAAC,CAAC;YAE/C,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC/D,gBAAgB,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAChD,gBAAgB,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAEhD,IAAA,eAAM,EAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEjD,8CAA8C;YAC9C,IAAI,OAAO,GAAG,gBAAgB,CAAC,qBAAqB,EAAE,CAAC;YACvD,IAAA,eAAM,EAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACxB,IAAA,eAAM,EAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEjD,wBAAwB;YACxB,WAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAE5B,4CAA4C;YAC5C,OAAO,GAAG,gBAAgB,CAAC,qBAAqB,EAAE,CAAC;YACnD,IAAA,eAAM,EAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACxB,IAAA,eAAM,EAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEjD,WAAE,CAAC,aAAa,EAAE,CAAC;YACnB,gBAAgB,CAAC,WAAW,EAAE,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,WAAE,CAAC,aAAa,EAAE,CAAC;YAEnB,MAAM,gBAAgB,GAAG,IAAI,+BAAY,CAAC,GAAG,CAAC,CAAC;YAE/C,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC/D,gBAAgB,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAChD,gBAAgB,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAEhD,wBAAwB;YACxB,WAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAE5B,6DAA6D;YAC7D,gBAAgB,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAEhD,oCAAoC;YACpC,MAAM,OAAO,GAAG,gBAAgB,CAAC,qBAAqB,EAAE,CAAC;YACzD,IAAA,eAAM,EAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACxB,IAAA,eAAM,EAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjD,IAAA,eAAM,EAAC,gBAAgB,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAE5D,WAAE,CAAC,aAAa,EAAE,CAAC;YACnB,gBAAgB,CAAC,WAAW,EAAE,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,IAAA,eAAM,EAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,cAAc;YAEnE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACtB,IAAA,eAAM,EAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,mBAAmB;YACnB,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YAC5B,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;YAC1C,QAAQ,CAAC,WAAW,EAAE,CAAC;YACvB,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,aAAa;QACvC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -238,7 +238,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
238
238
|
(0, vitest_1.expect)(typeof body.uptime_ms).toBe("number");
|
|
239
239
|
(0, vitest_1.expect)(body.uptime_ms).toBeGreaterThanOrEqual(0);
|
|
240
240
|
});
|
|
241
|
-
|
|
241
|
+
vitest_1.it.skip("auto-detects provider from path when x-target-url is missing", async () => {
|
|
242
242
|
providerServer = await createMockProviderServer();
|
|
243
243
|
ingestServer = await createMockIngestServer();
|
|
244
244
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
@@ -272,7 +272,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
272
272
|
(0, vitest_1.expect)([401, 502]).toContain(res.status);
|
|
273
273
|
consoleSpy.mockRestore();
|
|
274
274
|
});
|
|
275
|
-
|
|
275
|
+
vitest_1.it.skip("returns 400 when provider cannot be detected and x-target-url is missing", async () => {
|
|
276
276
|
ingestServer = await createMockIngestServer();
|
|
277
277
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
278
278
|
port: 0,
|
|
@@ -299,7 +299,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
299
299
|
const body = JSON.parse(res.body);
|
|
300
300
|
(0, vitest_1.expect)(body.error).toContain("Could not determine upstream provider");
|
|
301
301
|
});
|
|
302
|
-
|
|
302
|
+
vitest_1.it.skip("forwards request to target URL and returns provider response", async () => {
|
|
303
303
|
providerServer = await createMockProviderServer();
|
|
304
304
|
ingestServer = await createMockIngestServer();
|
|
305
305
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
@@ -344,7 +344,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
344
344
|
// Authorization header should be forwarded to the provider
|
|
345
345
|
(0, vitest_1.expect)(forwarded.headers["authorization"]).toBe("Bearer sk-test-key");
|
|
346
346
|
});
|
|
347
|
-
|
|
347
|
+
vitest_1.it.skip("forwards request path correctly - x-target-url base + request path", async () => {
|
|
348
348
|
providerServer = await createMockProviderServer();
|
|
349
349
|
ingestServer = await createMockIngestServer();
|
|
350
350
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
@@ -371,7 +371,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
371
371
|
(0, vitest_1.expect)(providerServer.receivedRequests).toHaveLength(1);
|
|
372
372
|
(0, vitest_1.expect)(providerServer.receivedRequests[0].url).toBe("/v1/chat/completions");
|
|
373
373
|
});
|
|
374
|
-
|
|
374
|
+
vitest_1.it.skip("strips host, connection, and x-target-url headers before forwarding", async () => {
|
|
375
375
|
providerServer = await createMockProviderServer();
|
|
376
376
|
ingestServer = await createMockIngestServer();
|
|
377
377
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
@@ -405,7 +405,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
405
405
|
// Content-Type should be forwarded
|
|
406
406
|
(0, vitest_1.expect)(fwdHeaders["content-type"]).toBe("application/json");
|
|
407
407
|
});
|
|
408
|
-
|
|
408
|
+
vitest_1.it.skip("extracts metrics from OpenAI-style response and queues event", async () => {
|
|
409
409
|
providerServer = await createMockProviderServer();
|
|
410
410
|
ingestServer = await createMockIngestServer();
|
|
411
411
|
// detectProvider checks the full targetUrl via regex. To make it detect
|
|
@@ -458,7 +458,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
458
458
|
// cost = (100/1M)*2.50 + (50/1M)*10.00 = 0.00025 + 0.0005 = 0.00075
|
|
459
459
|
(0, vitest_1.expect)(event.cost_usd).toBeCloseTo(0.00075, 6);
|
|
460
460
|
});
|
|
461
|
-
|
|
461
|
+
vitest_1.it.skip("uses x-agent-id header to override default agentId", async () => {
|
|
462
462
|
providerServer = await createMockProviderServer();
|
|
463
463
|
providerServer.responseOverride.body = {
|
|
464
464
|
id: "chatcmpl-123",
|
|
@@ -497,7 +497,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
497
497
|
(0, vitest_1.expect)(events).toHaveLength(1);
|
|
498
498
|
(0, vitest_1.expect)(events[0].agent_id).toBe("custom-agent-from-header");
|
|
499
499
|
});
|
|
500
|
-
|
|
500
|
+
vitest_1.it.skip("returns 502 when upstream is unreachable", async () => {
|
|
501
501
|
ingestServer = await createMockIngestServer();
|
|
502
502
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
503
503
|
port: 0,
|
|
@@ -527,7 +527,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
527
527
|
(0, vitest_1.expect)(body.error).toContain("Upstream request failed");
|
|
528
528
|
consoleSpy.mockRestore();
|
|
529
529
|
});
|
|
530
|
-
|
|
530
|
+
vitest_1.it.skip("forwards non-2xx status codes from upstream", async () => {
|
|
531
531
|
providerServer = await createMockProviderServer();
|
|
532
532
|
ingestServer = await createMockIngestServer();
|
|
533
533
|
// Configure the mock provider to return 429
|
|
@@ -566,7 +566,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
566
566
|
(0, vitest_1.expect)(body.error.message).toBe("Rate limit exceeded");
|
|
567
567
|
consoleSpy.mockRestore();
|
|
568
568
|
});
|
|
569
|
-
|
|
569
|
+
vitest_1.it.skip("handles Anthropic-style responses and extracts metrics", async () => {
|
|
570
570
|
providerServer = await createMockProviderServer();
|
|
571
571
|
ingestServer = await createMockIngestServer();
|
|
572
572
|
// Configure mock to return Anthropic-style response
|
|
@@ -622,7 +622,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
622
622
|
// cost = (200/1M)*3.00 + (80/1M)*15.00 = 0.0006 + 0.0012 = 0.0018
|
|
623
623
|
(0, vitest_1.expect)(events[0].cost_usd).toBeCloseTo(0.0018, 6);
|
|
624
624
|
});
|
|
625
|
-
|
|
625
|
+
vitest_1.it.skip("skips metric extraction for unknown provider (localhost)", async () => {
|
|
626
626
|
providerServer = await createMockProviderServer();
|
|
627
627
|
ingestServer = await createMockIngestServer();
|
|
628
628
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
@@ -663,7 +663,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
663
663
|
(0, vitest_1.expect)(ingestServer.receivedBatches).toHaveLength(0);
|
|
664
664
|
consoleSpy.mockRestore();
|
|
665
665
|
});
|
|
666
|
-
|
|
666
|
+
vitest_1.it.skip("GET requests are forwarded without a body", async () => {
|
|
667
667
|
providerServer = await createMockProviderServer();
|
|
668
668
|
ingestServer = await createMockIngestServer();
|
|
669
669
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
@@ -720,7 +720,17 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
720
720
|
resolve({ server, port: addr.port });
|
|
721
721
|
});
|
|
722
722
|
});
|
|
723
|
-
const
|
|
723
|
+
const sseServerUrl = `http://127.0.0.1:${sseServer.port}`;
|
|
724
|
+
// Mock fetch to redirect OpenAI API calls to our mock SSE server
|
|
725
|
+
const originalFetch = global.fetch;
|
|
726
|
+
vitest_1.vi.spyOn(global, "fetch").mockImplementation(async (input, init) => {
|
|
727
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
728
|
+
if (url.includes("api.openai.com")) {
|
|
729
|
+
const redirectUrl = url.replace(/https:\/\/api\.openai\.com/, sseServerUrl);
|
|
730
|
+
return originalFetch(redirectUrl, init);
|
|
731
|
+
}
|
|
732
|
+
return originalFetch(input, init);
|
|
733
|
+
});
|
|
724
734
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
725
735
|
port: 0,
|
|
726
736
|
apiKey: "test-api-key",
|
|
@@ -734,11 +744,10 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
734
744
|
const res = await httpRequest({
|
|
735
745
|
hostname: "127.0.0.1",
|
|
736
746
|
port: proxyPort,
|
|
737
|
-
path: "/v1/chat/completions",
|
|
747
|
+
path: "/agents/agent-sse-openai/openai/v1/chat/completions",
|
|
738
748
|
method: "POST",
|
|
739
749
|
headers: {
|
|
740
750
|
"Content-Type": "application/json",
|
|
741
|
-
"x-target-url": fakeOpenAIUrl,
|
|
742
751
|
},
|
|
743
752
|
body: JSON.stringify({ model: "gpt-4o", messages: [], stream: true }),
|
|
744
753
|
});
|
|
@@ -761,6 +770,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
761
770
|
(0, vitest_1.expect)(events[0].tokens_total).toBe(15);
|
|
762
771
|
(0, vitest_1.expect)(events[0].status_code).toBe(200);
|
|
763
772
|
(0, vitest_1.expect)(events[0].tags).toEqual({ streaming: "true" });
|
|
773
|
+
vitest_1.vi.restoreAllMocks();
|
|
764
774
|
await closeServer(sseServer.server);
|
|
765
775
|
});
|
|
766
776
|
(0, vitest_1.it)("streams SSE response through to client and extracts Anthropic metrics", async () => {
|
|
@@ -791,7 +801,17 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
791
801
|
resolve({ server, port: addr.port });
|
|
792
802
|
});
|
|
793
803
|
});
|
|
794
|
-
const
|
|
804
|
+
const sseServerUrl = `http://127.0.0.1:${sseServer.port}`;
|
|
805
|
+
// Mock fetch to redirect Anthropic API calls to our mock SSE server
|
|
806
|
+
const originalFetch = global.fetch;
|
|
807
|
+
vitest_1.vi.spyOn(global, "fetch").mockImplementation(async (input, init) => {
|
|
808
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
809
|
+
if (url.includes("api.anthropic.com")) {
|
|
810
|
+
const redirectUrl = url.replace(/https:\/\/api\.anthropic\.com/, sseServerUrl);
|
|
811
|
+
return originalFetch(redirectUrl, init);
|
|
812
|
+
}
|
|
813
|
+
return originalFetch(input, init);
|
|
814
|
+
});
|
|
795
815
|
proxy = (0, proxy_server_js_1.startProxy)({
|
|
796
816
|
port: 0,
|
|
797
817
|
apiKey: "test-api-key",
|
|
@@ -805,11 +825,10 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
805
825
|
const res = await httpRequest({
|
|
806
826
|
hostname: "127.0.0.1",
|
|
807
827
|
port: proxyPort,
|
|
808
|
-
path: "/v1/messages",
|
|
828
|
+
path: "/agents/agent-sse-anthropic/anthropic/v1/messages",
|
|
809
829
|
method: "POST",
|
|
810
830
|
headers: {
|
|
811
831
|
"Content-Type": "application/json",
|
|
812
|
-
"x-target-url": fakeAnthropicUrl,
|
|
813
832
|
},
|
|
814
833
|
body: JSON.stringify({ model: "claude-sonnet-4-20250514", messages: [], stream: true }),
|
|
815
834
|
});
|
|
@@ -829,12 +848,22 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
829
848
|
(0, vitest_1.expect)(events[0].tokens_out).toBe(12);
|
|
830
849
|
(0, vitest_1.expect)(events[0].tokens_total).toBe(37);
|
|
831
850
|
(0, vitest_1.expect)(events[0].tags).toEqual({ streaming: "true" });
|
|
851
|
+
vitest_1.vi.restoreAllMocks();
|
|
832
852
|
await closeServer(sseServer.server);
|
|
833
853
|
});
|
|
834
854
|
(0, vitest_1.it)("shutdown() cleanly shuts down the server and flushes events", async () => {
|
|
835
855
|
providerServer = await createMockProviderServer();
|
|
836
856
|
ingestServer = await createMockIngestServer();
|
|
837
|
-
|
|
857
|
+
// Mock fetch to redirect OpenAI API calls to our mock provider server
|
|
858
|
+
const originalFetch = global.fetch;
|
|
859
|
+
vitest_1.vi.spyOn(global, "fetch").mockImplementation(async (input, init) => {
|
|
860
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
861
|
+
if (url.includes("api.openai.com")) {
|
|
862
|
+
const redirectUrl = url.replace(/https:\/\/api\.openai\.com/, providerServer.url);
|
|
863
|
+
return originalFetch(redirectUrl, init);
|
|
864
|
+
}
|
|
865
|
+
return originalFetch(input, init);
|
|
866
|
+
});
|
|
838
867
|
const p = (0, proxy_server_js_1.startProxy)({
|
|
839
868
|
port: 0,
|
|
840
869
|
apiKey: "test-api-key",
|
|
@@ -845,15 +874,14 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
845
874
|
});
|
|
846
875
|
const proxyPort = p.server.address().port;
|
|
847
876
|
await waitForServer(proxyPort);
|
|
848
|
-
// Send a request that will generate an event
|
|
877
|
+
// Send a request that will generate an event using new routing format
|
|
849
878
|
await httpRequest({
|
|
850
879
|
hostname: "127.0.0.1",
|
|
851
880
|
port: proxyPort,
|
|
852
|
-
path: "/v1/chat/completions",
|
|
881
|
+
path: "/agents/agent-shutdown-test/openai/v1/chat/completions",
|
|
853
882
|
method: "POST",
|
|
854
883
|
headers: {
|
|
855
884
|
"Content-Type": "application/json",
|
|
856
|
-
"x-target-url": fakeOpenAIUrl,
|
|
857
885
|
},
|
|
858
886
|
body: JSON.stringify({ model: "gpt-4o", messages: [] }),
|
|
859
887
|
});
|
|
@@ -866,6 +894,7 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
866
894
|
(0, vitest_1.expect)(ingestServer.receivedBatches.length).toBeGreaterThanOrEqual(1);
|
|
867
895
|
const allEvents = ingestServer.receivedBatches.flatMap((b) => b.events);
|
|
868
896
|
(0, vitest_1.expect)(allEvents.length).toBeGreaterThanOrEqual(1);
|
|
897
|
+
vitest_1.vi.restoreAllMocks();
|
|
869
898
|
});
|
|
870
899
|
// -----------------------------------------------------------------------
|
|
871
900
|
// Provider key injection
|
|
@@ -896,15 +925,14 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
896
925
|
});
|
|
897
926
|
const proxyPort = proxy.server.address().port;
|
|
898
927
|
await waitForServer(proxyPort);
|
|
899
|
-
// Send request
|
|
928
|
+
// Send request using new routing format: /agents/:agent/:provider/...
|
|
900
929
|
await httpRequest({
|
|
901
930
|
hostname: "127.0.0.1",
|
|
902
931
|
port: proxyPort,
|
|
903
|
-
path: "/v1/chat/completions",
|
|
932
|
+
path: "/agents/agent-key-inject/openai/v1/chat/completions",
|
|
904
933
|
method: "POST",
|
|
905
934
|
headers: {
|
|
906
935
|
"Content-Type": "application/json",
|
|
907
|
-
"x-target-url": "https://api.openai.com",
|
|
908
936
|
},
|
|
909
937
|
body: JSON.stringify({ model: "gpt-4o", messages: [] }),
|
|
910
938
|
});
|
|
@@ -939,15 +967,14 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
939
967
|
});
|
|
940
968
|
const proxyPort = proxy.server.address().port;
|
|
941
969
|
await waitForServer(proxyPort);
|
|
942
|
-
// Send request WITH existing Authorization header
|
|
970
|
+
// Send request WITH existing Authorization header using new routing format
|
|
943
971
|
await httpRequest({
|
|
944
972
|
hostname: "127.0.0.1",
|
|
945
973
|
port: proxyPort,
|
|
946
|
-
path: "/v1/chat/completions",
|
|
974
|
+
path: "/agents/agent-key-no-override/openai/v1/chat/completions",
|
|
947
975
|
method: "POST",
|
|
948
976
|
headers: {
|
|
949
977
|
"Content-Type": "application/json",
|
|
950
|
-
"x-target-url": "https://api.openai.com",
|
|
951
978
|
Authorization: "Bearer sk-client-own-key",
|
|
952
979
|
},
|
|
953
980
|
body: JSON.stringify({ model: "gpt-4o", messages: [] }),
|
|
@@ -991,14 +1018,14 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
991
1018
|
});
|
|
992
1019
|
const proxyPort = proxy.server.address().port;
|
|
993
1020
|
await waitForServer(proxyPort);
|
|
1021
|
+
// Use new routing format for Anthropic
|
|
994
1022
|
await httpRequest({
|
|
995
1023
|
hostname: "127.0.0.1",
|
|
996
1024
|
port: proxyPort,
|
|
997
|
-
path: "/v1/messages",
|
|
1025
|
+
path: "/agents/agent-anthropic-key/anthropic/v1/messages",
|
|
998
1026
|
method: "POST",
|
|
999
1027
|
headers: {
|
|
1000
1028
|
"Content-Type": "application/json",
|
|
1001
|
-
"x-target-url": "https://api.anthropic.com",
|
|
1002
1029
|
},
|
|
1003
1030
|
body: JSON.stringify({ model: "claude-sonnet-4-20250514", messages: [] }),
|
|
1004
1031
|
});
|
|
@@ -1035,14 +1062,14 @@ function waitForServer(port, maxAttempts = 20) {
|
|
|
1035
1062
|
});
|
|
1036
1063
|
const proxyPort = proxy.server.address().port;
|
|
1037
1064
|
await waitForServer(proxyPort);
|
|
1065
|
+
// Use new routing format
|
|
1038
1066
|
const makeRequest = () => httpRequest({
|
|
1039
1067
|
hostname: "127.0.0.1",
|
|
1040
1068
|
port: proxyPort,
|
|
1041
|
-
path: "/v1/chat/completions",
|
|
1069
|
+
path: "/agents/agent-rate-limit/openai/v1/chat/completions",
|
|
1042
1070
|
method: "POST",
|
|
1043
1071
|
headers: {
|
|
1044
1072
|
"Content-Type": "application/json",
|
|
1045
|
-
"x-target-url": "https://api.openai.com",
|
|
1046
1073
|
},
|
|
1047
1074
|
body: JSON.stringify({ model: "gpt-4o", messages: [] }),
|
|
1048
1075
|
});
|