pi-llama-cpp 0.15.0 → 0.16.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 +40 -3
- package/package.json +1 -1
- package/src/api/client.ts +59 -22
- package/src/enums/mode.ts +1 -0
- package/src/interfaces/endpoints/models.ts +6 -0
- package/src/interfaces/endpoints/props.ts +14 -0
- package/src/managers/command/models.ts +3 -4
- package/src/managers/settings.ts +9 -1
- package/src/models/legacyModel.ts +3 -3
- package/src/models/llamaSwapModel.ts +90 -0
- package/src/models/routerModel.ts +2 -2
- package/src/models/singleModel.ts +2 -2
- package/src/server.ts +36 -6
- package/src/ui/dialog/confirm.ts +1 -1
- package/src/ui/dialog/input.ts +1 -1
- package/src/ui/editors/override/entryEditor.ts +9 -9
- package/src/ui/editors/override/itemBuilder.ts +5 -2
- package/src/ui/editors/server/fields.ts +2 -2
- package/src/ui/editors/server/itemBuilder.ts +5 -2
- package/src/ui/editors/server/serverEditor.ts +7 -7
- package/src/utils/credentialResolver.ts +110 -0
- package/src/utils/health.ts +2 -3
- package/tests/mocks.ts +2 -0
- package/tests/models/llamaSwapModel.test.ts +327 -0
- package/tests/server/health.test.ts +2 -2
- package/tests/server/server.test.ts +52 -2
- package/tests/settings/settings.test.ts +54 -252
- package/tests/utils/credentialResolver.test.ts +66 -0
- package/tests/utils/urlResolver.test.ts +116 -0
|
@@ -48,309 +48,111 @@ vi.mock("node:fs/promises", () => ({
|
|
|
48
48
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
49
49
|
import { access } from "node:fs/promises";
|
|
50
50
|
|
|
51
|
-
describe("
|
|
52
|
-
const mockGetAgentDir = vi.mocked(getAgentDir);
|
|
53
|
-
const mockGetProjectSettings = vi.mocked(
|
|
54
|
-
mockSettingsManager.getProjectSettings,
|
|
55
|
-
);
|
|
56
|
-
const mockGetGlobalSettings = vi.mocked(
|
|
57
|
-
mockSettingsManager.getGlobalSettings,
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
afterEach(() => {
|
|
61
|
-
delete process.env.LLAMA_SERVER_URL;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
beforeEach(() => {
|
|
65
|
-
vi.clearAllMocks();
|
|
66
|
-
mockGetAgentDir.mockReturnValue("/fake/agent/dir");
|
|
67
|
-
// Default: no settings found
|
|
68
|
-
mockGetProjectSettings.mockReturnValue({});
|
|
69
|
-
mockGetGlobalSettings.mockReturnValue({});
|
|
70
|
-
manager = createSettingsManager();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("should return default URL when no config is found", async () => {
|
|
74
|
-
// Ensure env var is not set (and not inherited from environment)
|
|
75
|
-
delete process.env.LLAMA_SERVER_URL;
|
|
76
|
-
|
|
77
|
-
const result = await manager.resolveUrls();
|
|
78
|
-
|
|
79
|
-
expect(result).toEqual([LLAMA_SERVER_URL]);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("should prioritize env variable over project config", async () => {
|
|
83
|
-
mockGetProjectSettings.mockReturnValue({
|
|
84
|
-
llamaServerUrl: "http://localhost:9999",
|
|
85
|
-
});
|
|
86
|
-
process.env.LLAMA_SERVER_URL = "http://env-url:8080";
|
|
87
|
-
|
|
88
|
-
const result = await manager.resolveUrls();
|
|
89
|
-
|
|
90
|
-
expect(result).toEqual(["http://env-url:8080"]);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("should use env variable when no other config exists", async () => {
|
|
94
|
-
process.env.LLAMA_SERVER_URL = "http://env-url:8080";
|
|
95
|
-
|
|
96
|
-
const result = await manager.resolveUrls();
|
|
97
|
-
|
|
98
|
-
expect(result).toEqual(["http://env-url:8080"]);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("should use legacy llamaServerUrl when env is not set", async () => {
|
|
102
|
-
mockGetProjectSettings.mockReturnValue({
|
|
103
|
-
llamaServerUrl: "http://project:9999",
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const result = await manager.resolveUrls();
|
|
107
|
-
|
|
108
|
-
expect(result).toEqual(["http://project:9999"]);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("should use global settings when no project config or env exists", async () => {
|
|
112
|
-
mockGetGlobalSettings.mockReturnValue({
|
|
113
|
-
llamaServerUrl: "http://global:8080",
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const result = await manager.resolveUrls();
|
|
117
|
-
|
|
118
|
-
expect(result).toEqual(["http://global:8080"]);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("should strip trailing slashes from resolved URL", async () => {
|
|
122
|
-
process.env.LLAMA_SERVER_URL = "http://localhost:8080/";
|
|
123
|
-
|
|
124
|
-
const result = await manager.resolveUrls();
|
|
125
|
-
|
|
126
|
-
expect(result).toEqual(["http://localhost:8080"]);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("should cache the resolved URL on subsequent calls", async () => {
|
|
130
|
-
process.env.LLAMA_SERVER_URL = "http://first:8080";
|
|
131
|
-
|
|
132
|
-
const result1 = await manager.resolveUrls();
|
|
133
|
-
const result2 = await manager.resolveUrls();
|
|
134
|
-
|
|
135
|
-
expect(result1).toEqual(["http://first:8080"]);
|
|
136
|
-
expect(result2).toEqual(["http://first:8080"]);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("should handle multiple URLs separated by semicolons", async () => {
|
|
140
|
-
process.env.LLAMA_SERVER_URL = "http://first:8080;http://second:9090/";
|
|
141
|
-
|
|
142
|
-
const result = await manager.resolveUrls();
|
|
143
|
-
|
|
144
|
-
expect(result).toEqual(["http://first:8080", "http://second:9090"]);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("should drop env URLs without an http(s) scheme, warn, and fall through", async () => {
|
|
148
|
-
process.env.LLAMA_SERVER_URL = "127.0.0.1:8080";
|
|
149
|
-
|
|
150
|
-
const manager = createSettingsManager();
|
|
151
|
-
const result = await manager.resolveUrls();
|
|
152
|
-
|
|
153
|
-
expect(result).toEqual([LLAMA_SERVER_URL]);
|
|
154
|
-
expect(manager.takeWarnings()).toEqual([
|
|
155
|
-
"Ignoring invalid server URL '127.0.0.1:8080' (needs http(s)://)",
|
|
156
|
-
]);
|
|
157
|
-
expect(manager.takeWarnings()).toEqual([]); // drained
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("should drop server entries without an http(s) scheme and warn", async () => {
|
|
161
|
-
mockGetProjectSettings.mockReturnValue({
|
|
162
|
-
llamaSettings: {
|
|
163
|
-
servers: [{ url: "127.0.0.1:8080" }, { url: "http://good:8080/" }],
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const manager = createSettingsManager();
|
|
168
|
-
const result = await manager.resolveUrls();
|
|
169
|
-
|
|
170
|
-
expect(result).toEqual(["http://good:8080"]);
|
|
171
|
-
expect(manager.takeWarnings()).toEqual([
|
|
172
|
-
"Ignoring invalid server URL '127.0.0.1:8080' (needs http(s)://)",
|
|
173
|
-
]);
|
|
174
|
-
expect(manager.takeWarnings()).toEqual([]); // drained
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe("llamaSettings.servers resolution", () => {
|
|
51
|
+
describe("API key resolution", () => {
|
|
179
52
|
const mockGetAgentDir = vi.mocked(getAgentDir);
|
|
180
|
-
const mockGetProjectSettings = vi.mocked(
|
|
181
|
-
mockSettingsManager.getProjectSettings,
|
|
182
|
-
);
|
|
183
|
-
const mockGetGlobalSettings = vi.mocked(
|
|
184
|
-
mockSettingsManager.getGlobalSettings,
|
|
185
|
-
);
|
|
186
53
|
|
|
187
|
-
afterEach(() => {
|
|
188
|
-
delete process.env.LLAMA_SERVER_URL;
|
|
189
|
-
});
|
|
54
|
+
afterEach(() => {});
|
|
190
55
|
|
|
191
56
|
beforeEach(() => {
|
|
192
57
|
vi.clearAllMocks();
|
|
193
58
|
mockGetAgentDir.mockReturnValue("/fake/agent/dir");
|
|
194
|
-
|
|
195
|
-
mockGetGlobalSettings.mockReturnValue({});
|
|
59
|
+
mockReadStoredCredential.mockReturnValue(undefined);
|
|
196
60
|
manager = createSettingsManager();
|
|
197
61
|
});
|
|
198
62
|
|
|
199
|
-
it("should
|
|
200
|
-
|
|
201
|
-
llamaSettings: {
|
|
202
|
-
servers: [
|
|
203
|
-
{ url: "http://project-server:8080" },
|
|
204
|
-
{ url: "http://project-server2:9090" },
|
|
205
|
-
],
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const result = await manager.resolveUrls();
|
|
210
|
-
|
|
211
|
-
expect(result).toEqual([
|
|
212
|
-
"http://project-server:8080",
|
|
213
|
-
"http://project-server2:9090",
|
|
214
|
-
]);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it("should prioritize project servers over global servers", async () => {
|
|
218
|
-
mockGetProjectSettings.mockReturnValue({
|
|
219
|
-
llamaSettings: {
|
|
220
|
-
servers: [{ url: "http://project:8080" }],
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
mockGetGlobalSettings.mockReturnValue({
|
|
224
|
-
llamaSettings: {
|
|
225
|
-
servers: [{ url: "http://global:9090" }],
|
|
226
|
-
},
|
|
227
|
-
});
|
|
63
|
+
it("should return placeholder when credential is not found", () => {
|
|
64
|
+
mockReadStoredCredential.mockReturnValue(undefined);
|
|
228
65
|
|
|
229
|
-
const result =
|
|
66
|
+
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
230
67
|
|
|
231
|
-
expect(result).toEqual(
|
|
68
|
+
expect(result).toEqual(API_KEY_PLACEHOLDER);
|
|
232
69
|
});
|
|
233
70
|
|
|
234
|
-
it("should
|
|
235
|
-
|
|
236
|
-
llamaSettings: {
|
|
237
|
-
servers: [{ url: "http://global:8080" }],
|
|
238
|
-
},
|
|
239
|
-
});
|
|
71
|
+
it("should return placeholder when apiKey is missing from credential", () => {
|
|
72
|
+
mockReadStoredCredential.mockReturnValue({});
|
|
240
73
|
|
|
241
|
-
const result =
|
|
74
|
+
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
242
75
|
|
|
243
|
-
expect(result).toEqual(
|
|
76
|
+
expect(result).toEqual(API_KEY_PLACEHOLDER);
|
|
244
77
|
});
|
|
245
78
|
|
|
246
|
-
it("should
|
|
247
|
-
|
|
248
|
-
mockGetProjectSettings.mockReturnValue({
|
|
249
|
-
llamaSettings: {
|
|
250
|
-
servers: [{ url: "http://server:9090" }],
|
|
251
|
-
},
|
|
252
|
-
});
|
|
79
|
+
it("should return the key when present in credential", () => {
|
|
80
|
+
mockReadStoredCredential.mockReturnValue({ key: "test-api-key" });
|
|
253
81
|
|
|
254
|
-
const result =
|
|
82
|
+
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
255
83
|
|
|
256
|
-
expect(result).toEqual(
|
|
84
|
+
expect(result).toEqual("test-api-key");
|
|
257
85
|
});
|
|
258
86
|
|
|
259
|
-
it("should
|
|
260
|
-
|
|
261
|
-
llamaSettings: {
|
|
262
|
-
servers: [{ url: "http://server:9090" }],
|
|
263
|
-
},
|
|
264
|
-
});
|
|
87
|
+
it("should call readStoredCredential with the provider ID", () => {
|
|
88
|
+
mockReadStoredCredential.mockReturnValue({ key: "test-key" });
|
|
265
89
|
|
|
266
|
-
|
|
90
|
+
manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
267
91
|
|
|
268
|
-
expect(
|
|
92
|
+
expect(mockReadStoredCredential).toHaveBeenCalledWith(
|
|
93
|
+
"llama-server=http://127.0.0.1:8080",
|
|
94
|
+
);
|
|
269
95
|
});
|
|
270
96
|
|
|
271
|
-
it("should
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
llamaSettings: {
|
|
275
|
-
servers: [{ url: "http://server:9090" }],
|
|
276
|
-
},
|
|
277
|
-
});
|
|
97
|
+
it("should resolve $VAR env reference from process.env", () => {
|
|
98
|
+
mockReadStoredCredential.mockReturnValue({ key: "$MY_API_KEY" });
|
|
99
|
+
process.env.MY_API_KEY = "env-resolved-key";
|
|
278
100
|
|
|
279
|
-
const result =
|
|
101
|
+
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
280
102
|
|
|
281
|
-
expect(result).
|
|
103
|
+
expect(result).toBe("env-resolved-key");
|
|
104
|
+
delete process.env.MY_API_KEY;
|
|
282
105
|
});
|
|
283
106
|
|
|
284
|
-
it("should
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
llamaSettings: {
|
|
288
|
-
servers: [],
|
|
289
|
-
},
|
|
290
|
-
});
|
|
107
|
+
it("should resolve ${VAR} braced env reference from process.env", () => {
|
|
108
|
+
mockReadStoredCredential.mockReturnValue({ key: "${MY_API_KEY}" });
|
|
109
|
+
process.env.MY_API_KEY = "braced-resolved-key";
|
|
291
110
|
|
|
292
|
-
const result =
|
|
111
|
+
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
293
112
|
|
|
294
|
-
expect(result).
|
|
113
|
+
expect(result).toBe("braced-resolved-key");
|
|
114
|
+
delete process.env.MY_API_KEY;
|
|
295
115
|
});
|
|
296
116
|
|
|
297
|
-
it("should
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
},
|
|
117
|
+
it("should prefer credential.env over process.env", () => {
|
|
118
|
+
mockReadStoredCredential.mockReturnValue({
|
|
119
|
+
key: "$MY_KEY",
|
|
120
|
+
env: { MY_KEY: "credential-env-value" },
|
|
302
121
|
});
|
|
303
|
-
|
|
304
|
-
const result = await manager.resolveUrls();
|
|
305
|
-
|
|
306
|
-
expect(result).toEqual(["http://localhost:8080"]);
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
describe("API key resolution", () => {
|
|
311
|
-
const mockGetAgentDir = vi.mocked(getAgentDir);
|
|
312
|
-
|
|
313
|
-
afterEach(() => {});
|
|
314
|
-
|
|
315
|
-
beforeEach(() => {
|
|
316
|
-
vi.clearAllMocks();
|
|
317
|
-
mockGetAgentDir.mockReturnValue("/fake/agent/dir");
|
|
318
|
-
mockReadStoredCredential.mockReturnValue(undefined);
|
|
319
|
-
manager = createSettingsManager();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it("should return placeholder when credential is not found", () => {
|
|
323
|
-
mockReadStoredCredential.mockReturnValue(undefined);
|
|
122
|
+
process.env.MY_KEY = "process-env-value";
|
|
324
123
|
|
|
325
124
|
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
326
125
|
|
|
327
|
-
expect(result).
|
|
126
|
+
expect(result).toBe("credential-env-value");
|
|
127
|
+
delete process.env.MY_KEY;
|
|
328
128
|
});
|
|
329
129
|
|
|
330
|
-
it("should return placeholder when
|
|
331
|
-
mockReadStoredCredential.mockReturnValue({});
|
|
130
|
+
it("should return placeholder when env var is not set", () => {
|
|
131
|
+
mockReadStoredCredential.mockReturnValue({ key: "$UNSET_VAR" });
|
|
132
|
+
delete process.env.UNSET_VAR;
|
|
332
133
|
|
|
333
134
|
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
334
135
|
|
|
335
|
-
expect(result).
|
|
136
|
+
expect(result).toBe(API_KEY_PLACEHOLDER);
|
|
336
137
|
});
|
|
337
138
|
|
|
338
|
-
|
|
339
|
-
|
|
139
|
+
// Shell command tests are covered by Pi core's resolve-config-value tests.
|
|
140
|
+
// The local implementation delegates to execSync which is tested there.
|
|
141
|
+
|
|
142
|
+
it("should escape $$ as literal $", () => {
|
|
143
|
+
mockReadStoredCredential.mockReturnValue({ key: "$$LITERAL" });
|
|
340
144
|
|
|
341
145
|
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
342
146
|
|
|
343
|
-
expect(result).
|
|
147
|
+
expect(result).toBe("$LITERAL");
|
|
344
148
|
});
|
|
345
149
|
|
|
346
|
-
it("should
|
|
347
|
-
mockReadStoredCredential.mockReturnValue({ key: "
|
|
150
|
+
it("should escape $! as literal !", () => {
|
|
151
|
+
mockReadStoredCredential.mockReturnValue({ key: "$!LITERAL" });
|
|
348
152
|
|
|
349
|
-
manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
153
|
+
const result = manager.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
350
154
|
|
|
351
|
-
expect(
|
|
352
|
-
"llama-server=http://127.0.0.1:8080",
|
|
353
|
-
);
|
|
155
|
+
expect(result).toBe("!LITERAL");
|
|
354
156
|
});
|
|
355
157
|
});
|
|
356
158
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { CredentialResolver } from "../../src/utils/credentialResolver";
|
|
3
|
+
|
|
4
|
+
describe("CredentialResolver", () => {
|
|
5
|
+
const resolver = new CredentialResolver();
|
|
6
|
+
|
|
7
|
+
describe("resolve", () => {
|
|
8
|
+
it("returns placeholder for empty key", () => {
|
|
9
|
+
expect(resolver.resolve("")).toBe("sk-placeholder");
|
|
10
|
+
expect(resolver.resolve(undefined as any)).toBe("sk-placeholder");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns literal key as-is", () => {
|
|
14
|
+
expect(resolver.resolve("sk-abc123")).toBe("sk-abc123");
|
|
15
|
+
expect(resolver.resolve("plain-key-without-dollars")).toBe(
|
|
16
|
+
"plain-key-without-dollars",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("resolves escape sequences $$", () => {
|
|
21
|
+
expect(resolver.resolve("$$literal")).toBe("$literal");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("resolves escape sequences $!", () => {
|
|
25
|
+
expect(resolver.resolve("$!bang")).toBe("!bang");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("resolves $VAR from credential.env", () => {
|
|
29
|
+
expect(resolver.resolve("$MY_KEY", { MY_KEY: "from-env" })).toBe(
|
|
30
|
+
"from-env",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("resolves ${VAR} from credential.env", () => {
|
|
35
|
+
expect(resolver.resolve("${MY_KEY}", { MY_KEY: "from-env" })).toBe(
|
|
36
|
+
"from-env",
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("resolves $VAR from process.env as fallback", () => {
|
|
41
|
+
process.env.TEST_PROCESS_VAR = "process-value";
|
|
42
|
+
expect(resolver.resolve("$TEST_PROCESS_VAR")).toBe("process-value");
|
|
43
|
+
delete process.env.TEST_PROCESS_VAR;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("prioritizes credential.env over process.env", () => {
|
|
47
|
+
process.env.PREF_KEY = "process-value";
|
|
48
|
+
expect(
|
|
49
|
+
resolver.resolve("$PREF_KEY", { PREF_KEY: "credential-value" }),
|
|
50
|
+
).toBe("credential-value");
|
|
51
|
+
delete process.env.PREF_KEY;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns placeholder when env var is missing", () => {
|
|
55
|
+
expect(resolver.resolve("$UNSET_VAR")).toBe("sk-placeholder");
|
|
56
|
+
expect(resolver.resolve("${UNSET_VAR}", { OTHER_KEY: "value" })).toBe(
|
|
57
|
+
"sk-placeholder",
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns placeholder for invalid env var syntax", () => {
|
|
62
|
+
expect(resolver.resolve("$123invalid")).toBe("sk-placeholder");
|
|
63
|
+
expect(resolver.resolve("$has-dash")).toBe("sk-placeholder");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { UrlResolver } from "../../src/utils/urlResolver";
|
|
3
|
+
|
|
4
|
+
function createResolver(
|
|
5
|
+
getLlamaSettings: () => Promise<Record<string, unknown>>,
|
|
6
|
+
getMergedSettings: () => Promise<Record<string, unknown>>,
|
|
7
|
+
) {
|
|
8
|
+
return new UrlResolver({ getLlamaSettings, getMergedSettings });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("UrlResolver", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
delete process.env.LLAMA_SERVER_URL;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("resolveUrls", () => {
|
|
17
|
+
it("falls back to default when nothing is configured", async () => {
|
|
18
|
+
const resolver = createResolver(
|
|
19
|
+
() => Promise.resolve({}),
|
|
20
|
+
() => Promise.resolve({}),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const result = await resolver.resolveUrls();
|
|
24
|
+
expect(result).toEqual(["http://127.0.0.1:8080"]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("prioritizes env variable over all config sources", async () => {
|
|
28
|
+
process.env.LLAMA_SERVER_URL = "http://env:9090";
|
|
29
|
+
const resolver = createResolver(
|
|
30
|
+
() =>
|
|
31
|
+
Promise.resolve({
|
|
32
|
+
servers: [{ url: "http://servers:8080" }],
|
|
33
|
+
}),
|
|
34
|
+
() => Promise.resolve({ llamaServerUrl: "http://legacy:7070" }),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const result = await resolver.resolveUrls();
|
|
38
|
+
expect(result).toEqual(["http://env:9090"]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("prioritizes llamaSettings.servers over legacy llamaServerUrl", async () => {
|
|
42
|
+
const resolver = createResolver(
|
|
43
|
+
() =>
|
|
44
|
+
Promise.resolve({
|
|
45
|
+
servers: [{ url: "http://servers:8080" }],
|
|
46
|
+
}),
|
|
47
|
+
() => Promise.resolve({ llamaServerUrl: "http://legacy:7070" }),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const result = await resolver.resolveUrls();
|
|
51
|
+
expect(result).toEqual(["http://servers:8080"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("falls back to legacy when servers is empty", async () => {
|
|
55
|
+
const resolver = createResolver(
|
|
56
|
+
() => Promise.resolve({ servers: [] }),
|
|
57
|
+
() => Promise.resolve({ llamaServerUrl: "http://legacy:7070" }),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const result = await resolver.resolveUrls();
|
|
61
|
+
expect(result).toEqual(["http://legacy:7070"]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns empty arrays from settings when values are falsy", async () => {
|
|
65
|
+
const resolver = createResolver(
|
|
66
|
+
() => Promise.resolve({ servers: undefined }),
|
|
67
|
+
() => Promise.resolve({ llamaServerUrl: undefined }),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const result = await resolver.resolveUrls();
|
|
71
|
+
expect(result).toEqual(["http://127.0.0.1:8080"]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("warnings", () => {
|
|
76
|
+
it("collects warnings for invalid env URLs", async () => {
|
|
77
|
+
process.env.LLAMA_SERVER_URL = "invalid-url;http://good:8080";
|
|
78
|
+
const resolver = createResolver(
|
|
79
|
+
() => Promise.resolve({}),
|
|
80
|
+
() => Promise.resolve({}),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
await resolver.resolveUrls();
|
|
84
|
+
expect(resolver.takeWarnings()).toEqual([
|
|
85
|
+
"Ignoring invalid server URL 'invalid-url' (needs http(s)://)",
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("collects warnings for invalid server URLs", async () => {
|
|
90
|
+
const resolver = createResolver(
|
|
91
|
+
() =>
|
|
92
|
+
Promise.resolve({
|
|
93
|
+
servers: [{ url: "bad-url" }, { url: "http://good:8080" }],
|
|
94
|
+
}),
|
|
95
|
+
() => Promise.resolve({}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
await resolver.resolveUrls();
|
|
99
|
+
expect(resolver.takeWarnings()).toEqual([
|
|
100
|
+
"Ignoring invalid server URL 'bad-url' (needs http(s)://)",
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("clears warnings after takeWarnings()", async () => {
|
|
105
|
+
process.env.LLAMA_SERVER_URL = "bad;http://good:8080";
|
|
106
|
+
const resolver = createResolver(
|
|
107
|
+
() => Promise.resolve({}),
|
|
108
|
+
() => Promise.resolve({}),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
await resolver.resolveUrls();
|
|
112
|
+
expect(resolver.takeWarnings()).toHaveLength(1);
|
|
113
|
+
expect(resolver.takeWarnings()).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|