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/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
- }
@@ -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
- });