pi-llama-cpp 0.9.2 → 0.10.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 +97 -22
- package/package.json +3 -2
- package/src/constants.ts +13 -3
- package/src/index.ts +2 -8
- package/src/interfaces/settings.ts +60 -0
- package/src/managers/events.ts +28 -8
- package/src/managers/server.ts +6 -4
- package/src/managers/settings.ts +206 -0
- package/src/models/baseModel.ts +32 -9
- package/src/models/legacyModel.ts +2 -2
- package/src/models/routerModel.ts +3 -3
- package/src/server.ts +33 -9
- package/src/sse/manager.ts +2 -1
- package/tests/events.test.ts +145 -9
- package/tests/mocks.ts +1 -1
- package/tests/server.test.ts +46 -0
- package/tests/settings.test.ts +636 -0
- package/tests/sseManager.test.ts +21 -0
- package/src/resolver.ts +0 -149
- package/tests/resolver.test.ts +0 -184
package/src/resolver.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { ApiKeyCredential, ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
|
-
import {
|
|
3
|
-
getAgentDir,
|
|
4
|
-
readStoredCredential,
|
|
5
|
-
SettingsManager,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { readFile } from "node:fs/promises";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import {
|
|
10
|
-
API_KEY_PLACEHOLDER,
|
|
11
|
-
DEFAULT_LLAMA_SERVER_URL,
|
|
12
|
-
DEFAULT_THINKING_BUDGETS,
|
|
13
|
-
} from "./constants";
|
|
14
|
-
|
|
15
|
-
export class ConfigResolver {
|
|
16
|
-
private warnings: string[] = [];
|
|
17
|
-
|
|
18
|
-
private cachedUrls: string[] = [];
|
|
19
|
-
private settingsManager = SettingsManager.create(
|
|
20
|
-
process.cwd(),
|
|
21
|
-
getAgentDir(),
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Resolves the llama-server URL by searching in the global settings.json
|
|
26
|
-
*/
|
|
27
|
-
private async resolveGlobalUrl(): Promise<string | null> {
|
|
28
|
-
const settings = this.settingsManager.getGlobalSettings();
|
|
29
|
-
const { llamaServerUrl = null } = settings as Record<string, string>;
|
|
30
|
-
|
|
31
|
-
return llamaServerUrl;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Resolves the llama-server URL by searching in the project's .pi/settings.json
|
|
36
|
-
*/
|
|
37
|
-
private async resolveProjectUrl(): Promise<string | null> {
|
|
38
|
-
// Warn the user for deprecation
|
|
39
|
-
try {
|
|
40
|
-
const filePath = join(process.cwd(), ".pi", "llama-server.json");
|
|
41
|
-
const { url = null } = JSON.parse(await readFile(filePath, "utf-8"));
|
|
42
|
-
|
|
43
|
-
const messages = [
|
|
44
|
-
"[pi-llama-cpp]",
|
|
45
|
-
"The project-level `.pi/llama-server.json` file has been deprecated.",
|
|
46
|
-
"It will work for now, but you must follow these instructions as soon as possible:",
|
|
47
|
-
'- Move your url to the project-level `.pi/settings.json` file as {"llamaServerUrl": "<url>"}.',
|
|
48
|
-
"- Remove the old `.pi/llama-server.json` file.",
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
this.warnings.push(messages.join("\n"));
|
|
52
|
-
|
|
53
|
-
return url;
|
|
54
|
-
} catch {
|
|
55
|
-
// No old file available, continue as normal
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const settings = this.settingsManager.getProjectSettings();
|
|
59
|
-
const { llamaServerUrl = null } = settings as Record<string, string>;
|
|
60
|
-
|
|
61
|
-
return llamaServerUrl;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Resolves the llama-server URL from the environment
|
|
66
|
-
*/
|
|
67
|
-
private async resolveEnvUrl(): Promise<string | null> {
|
|
68
|
-
return process.env.LLAMA_SERVER_URL ?? null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Tries all possible ways to retrieve the llama-server URL(s)
|
|
73
|
-
*/
|
|
74
|
-
private async extractJoinedUrls(): Promise<string> {
|
|
75
|
-
// 1. per-project config
|
|
76
|
-
let response = await this.resolveProjectUrl();
|
|
77
|
-
if (response) return response;
|
|
78
|
-
|
|
79
|
-
// 2. env
|
|
80
|
-
response = await this.resolveEnvUrl();
|
|
81
|
-
if (response) return response;
|
|
82
|
-
|
|
83
|
-
// 3. global settings
|
|
84
|
-
response = await this.resolveGlobalUrl();
|
|
85
|
-
if (response) return response;
|
|
86
|
-
|
|
87
|
-
// 4. default
|
|
88
|
-
return DEFAULT_LLAMA_SERVER_URL;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Resolves URLs where llama-servers are running (cached)
|
|
93
|
-
*/
|
|
94
|
-
async resolveUrls(): Promise<string[]> {
|
|
95
|
-
if (this.cachedUrls.length > 0) return this.cachedUrls;
|
|
96
|
-
|
|
97
|
-
const raw = await this.extractJoinedUrls();
|
|
98
|
-
const urls = raw
|
|
99
|
-
.split(";")
|
|
100
|
-
.map((u) => u.trim())
|
|
101
|
-
.filter((u) => u.length > 0)
|
|
102
|
-
.map((u) => u.replace(/\/+$/, ""));
|
|
103
|
-
|
|
104
|
-
this.cachedUrls = urls;
|
|
105
|
-
return this.cachedUrls;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Resolves API key for the provider ID using Pi's stored credentials
|
|
110
|
-
*/
|
|
111
|
-
resolveApiKey(providerId: string): string {
|
|
112
|
-
const credential = readStoredCredential(providerId) as ApiKeyCredential;
|
|
113
|
-
return credential?.key ?? API_KEY_PLACEHOLDER;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Returns warnings collected during URL resolution.
|
|
118
|
-
*/
|
|
119
|
-
getWarnings(): string[] {
|
|
120
|
-
const warnings = [...this.warnings];
|
|
121
|
-
this.warnings.length = 0;
|
|
122
|
-
|
|
123
|
-
return warnings;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/*
|
|
127
|
-
* Resolves the current thinking level from Pi.
|
|
128
|
-
*
|
|
129
|
-
* @returns Selected level
|
|
130
|
-
*/
|
|
131
|
-
resolveThinkingLevel(): ModelThinkingLevel | undefined {
|
|
132
|
-
return this.settingsManager.getDefaultThinkingLevel();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Resolves the effective thinking budgets from settings
|
|
137
|
-
*
|
|
138
|
-
* @returns Thinking budgets
|
|
139
|
-
*/
|
|
140
|
-
resolveThinkingBudgets(): Record<ModelThinkingLevel, number> {
|
|
141
|
-
const settingsBudgets = this.settingsManager.getThinkingBudgets() ?? {};
|
|
142
|
-
const availableBudgets = {
|
|
143
|
-
...DEFAULT_THINKING_BUDGETS,
|
|
144
|
-
...settingsBudgets,
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
return availableBudgets;
|
|
148
|
-
}
|
|
149
|
-
}
|
package/tests/resolver.test.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
API_KEY_PLACEHOLDER,
|
|
4
|
-
DEFAULT_LLAMA_SERVER_URL,
|
|
5
|
-
} from "../src/constants";
|
|
6
|
-
|
|
7
|
-
// Hoisted mock instances — survives vi.resetModules()
|
|
8
|
-
const mockReadStoredCredential = vi.hoisted(() => vi.fn());
|
|
9
|
-
|
|
10
|
-
const mockSettingsManager = vi.hoisted(() => ({
|
|
11
|
-
getProjectSettings: vi.fn(),
|
|
12
|
-
getGlobalSettings: vi.fn(),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
// Mock getAgentDir, readStoredCredential, and SettingsManager before importing resolver
|
|
16
|
-
vi.mock("@earendil-works/pi-coding-agent", () => ({
|
|
17
|
-
getAgentDir: vi.fn().mockReturnValue("/fake/agent/dir"),
|
|
18
|
-
readStoredCredential: (...args: unknown[]) =>
|
|
19
|
-
mockReadStoredCredential(...args),
|
|
20
|
-
SettingsManager: {
|
|
21
|
-
create: vi.fn().mockReturnValue(mockSettingsManager),
|
|
22
|
-
},
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
vi.mock("node:fs/promises", () => ({
|
|
26
|
-
readFile: vi.fn(),
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
// Import mocked modules
|
|
30
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
31
|
-
import { readFile } from "node:fs/promises";
|
|
32
|
-
import { ConfigResolver } from "../src/resolver";
|
|
33
|
-
|
|
34
|
-
describe("URL resolution fallback chain", () => {
|
|
35
|
-
const mockReadFile = vi.mocked(readFile);
|
|
36
|
-
const mockGetAgentDir = vi.mocked(getAgentDir);
|
|
37
|
-
const mockGetProjectSettings = vi.mocked(
|
|
38
|
-
mockSettingsManager.getProjectSettings,
|
|
39
|
-
);
|
|
40
|
-
const mockGetGlobalSettings = vi.mocked(
|
|
41
|
-
mockSettingsManager.getGlobalSettings,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
afterEach(() => {
|
|
45
|
-
delete process.env.LLAMA_SERVER_URL;
|
|
46
|
-
vi.resetModules();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
beforeEach(() => {
|
|
50
|
-
vi.clearAllMocks();
|
|
51
|
-
mockGetAgentDir.mockReturnValue("/fake/agent/dir");
|
|
52
|
-
// Default: no settings found
|
|
53
|
-
mockGetProjectSettings.mockReturnValue({});
|
|
54
|
-
mockGetGlobalSettings.mockReturnValue({});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("should return default URL when no config is found", async () => {
|
|
58
|
-
const resolver = new ConfigResolver();
|
|
59
|
-
const result = await resolver.resolveUrls();
|
|
60
|
-
|
|
61
|
-
expect(result).toEqual([DEFAULT_LLAMA_SERVER_URL]);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("should prioritize project config over env variable", async () => {
|
|
65
|
-
mockGetProjectSettings.mockReturnValue({
|
|
66
|
-
llamaServerUrl: "http://localhost:9999",
|
|
67
|
-
});
|
|
68
|
-
process.env.LLAMA_SERVER_URL = "http://env-url:8080";
|
|
69
|
-
|
|
70
|
-
const resolver = new ConfigResolver();
|
|
71
|
-
const result = await resolver.resolveUrls();
|
|
72
|
-
|
|
73
|
-
expect(result).toEqual(["http://localhost:9999"]);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("should use env variable when no project config exists", async () => {
|
|
77
|
-
mockGetProjectSettings.mockReturnValue({});
|
|
78
|
-
process.env.LLAMA_SERVER_URL = "http://env-url:8080";
|
|
79
|
-
|
|
80
|
-
const resolver = new ConfigResolver();
|
|
81
|
-
const result = await resolver.resolveUrls();
|
|
82
|
-
|
|
83
|
-
expect(result).toEqual(["http://env-url:8080"]);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("should use global settings when no project config or env exists", async () => {
|
|
87
|
-
mockGetProjectSettings.mockReturnValue({});
|
|
88
|
-
mockGetGlobalSettings.mockReturnValue({
|
|
89
|
-
llamaServerUrl: "http://global:8080",
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const resolver = new ConfigResolver();
|
|
93
|
-
const result = await resolver.resolveUrls();
|
|
94
|
-
|
|
95
|
-
expect(result).toEqual(["http://global:8080"]);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("should strip trailing slashes from resolved URL", async () => {
|
|
99
|
-
mockGetProjectSettings.mockReturnValue({
|
|
100
|
-
llamaServerUrl: "http://localhost:8080/",
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const resolver = new ConfigResolver();
|
|
104
|
-
const result = await resolver.resolveUrls();
|
|
105
|
-
|
|
106
|
-
expect(result).toEqual(["http://localhost:8080"]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("should cache the resolved URL on subsequent calls", async () => {
|
|
110
|
-
mockGetProjectSettings.mockReturnValue({
|
|
111
|
-
llamaServerUrl: "http://first:8080",
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const resolver = new ConfigResolver();
|
|
115
|
-
const result1 = await resolver.resolveUrls();
|
|
116
|
-
const result2 = await resolver.resolveUrls();
|
|
117
|
-
|
|
118
|
-
expect(result1).toEqual(["http://first:8080"]);
|
|
119
|
-
expect(result2).toEqual(["http://first:8080"]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("should handle multiple URLs separated by semicolons", async () => {
|
|
123
|
-
mockGetProjectSettings.mockReturnValue({
|
|
124
|
-
llamaServerUrl: "http://first:8080;http://second:9090/",
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const resolver = new ConfigResolver();
|
|
128
|
-
const result = await resolver.resolveUrls();
|
|
129
|
-
|
|
130
|
-
expect(result).toEqual(["http://first:8080", "http://second:9090"]);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe("API key resolution", () => {
|
|
135
|
-
const mockGetAgentDir = vi.mocked(getAgentDir);
|
|
136
|
-
|
|
137
|
-
afterEach(() => {
|
|
138
|
-
vi.resetModules();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
beforeEach(() => {
|
|
142
|
-
vi.clearAllMocks();
|
|
143
|
-
mockGetAgentDir.mockReturnValue("/fake/agent/dir");
|
|
144
|
-
mockReadStoredCredential.mockReturnValue(undefined);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("should return placeholder when credential is not found", () => {
|
|
148
|
-
mockReadStoredCredential.mockReturnValue(undefined);
|
|
149
|
-
|
|
150
|
-
const resolver = new ConfigResolver();
|
|
151
|
-
const result = resolver.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
152
|
-
|
|
153
|
-
expect(result).toEqual(API_KEY_PLACEHOLDER);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("should return placeholder when apiKey is missing from credential", () => {
|
|
157
|
-
mockReadStoredCredential.mockReturnValue({});
|
|
158
|
-
|
|
159
|
-
const resolver = new ConfigResolver();
|
|
160
|
-
const result = resolver.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
161
|
-
|
|
162
|
-
expect(result).toEqual(API_KEY_PLACEHOLDER);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("should return the apiKey when present in credential", () => {
|
|
166
|
-
mockReadStoredCredential.mockReturnValue({ key: "test-api-key" });
|
|
167
|
-
|
|
168
|
-
const resolver = new ConfigResolver();
|
|
169
|
-
const result = resolver.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
170
|
-
|
|
171
|
-
expect(result).toEqual("test-api-key");
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("should call readStoredCredential with the provider ID", () => {
|
|
175
|
-
mockReadStoredCredential.mockReturnValue({ apiKey: "test-key" });
|
|
176
|
-
|
|
177
|
-
const resolver = new ConfigResolver();
|
|
178
|
-
resolver.resolveApiKey("llama-server=http://127.0.0.1:8080");
|
|
179
|
-
|
|
180
|
-
expect(mockReadStoredCredential).toHaveBeenCalledWith(
|
|
181
|
-
"llama-server=http://127.0.0.1:8080",
|
|
182
|
-
);
|
|
183
|
-
});
|
|
184
|
-
});
|