pi-llama-cpp 0.5.1 → 0.7.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 +96 -30
- package/package.json +6 -5
- package/src/constants.ts +27 -5
- package/src/enums/action.ts +3 -2
- package/src/enums/mode.ts +1 -0
- package/src/enums/serverStatus.ts +6 -0
- package/src/enums/status.ts +1 -0
- package/src/index.ts +53 -31
- package/src/interfaces/auth.ts +1 -5
- package/src/interfaces/endpoints/props.ts +1 -0
- package/src/interfaces/levels.ts +7 -0
- package/src/managers/command.ts +290 -0
- package/src/managers/events.ts +101 -0
- package/src/managers/server.ts +136 -0
- package/src/models/baseModel.ts +75 -20
- package/src/models/legacyModel.ts +45 -0
- package/src/models/routerModel.ts +7 -30
- package/src/models/singleModel.ts +9 -6
- package/src/resolver.ts +152 -0
- package/src/server.ts +187 -0
- package/tests/commandManager.test.ts +182 -133
- package/tests/events.test.ts +256 -0
- package/tests/legacyModel.test.ts +112 -0
- package/tests/mocks.ts +100 -0
- package/tests/resolver.test.ts +143 -106
- package/tests/routerModel.test.ts +46 -68
- package/tests/server.test.ts +176 -0
- package/tests/serverManager.test.ts +130 -0
- package/tests/singleModel.test.ts +21 -29
- package/src/commands/models.ts +0 -228
- package/src/events.ts +0 -26
- package/src/manager.ts +0 -96
- package/src/tools/resolver.ts +0 -136
- package/src/tools/retriever.ts +0 -71
- package/tests/handlers.test.ts +0 -164
- package/tests/modelsCommand.test.ts +0 -270
package/src/commands/models.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionCommandContext,
|
|
4
|
-
ExtensionContext,
|
|
5
|
-
SessionBeforeSwitchEvent,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { PROVIDER_ID, PROVIDER_NAME, READABLE_TIMEOUT } from "../constants";
|
|
8
|
-
import { Action } from "../enums/action";
|
|
9
|
-
import { Mode } from "../enums/mode";
|
|
10
|
-
import { Status } from "../enums/status";
|
|
11
|
-
import { BaseModel } from "../models/baseModel";
|
|
12
|
-
import { resolveUrl } from "../tools/resolver";
|
|
13
|
-
|
|
14
|
-
// In-flight model reference — handler gates on this.
|
|
15
|
-
let inflightModel: BaseModel | null = null;
|
|
16
|
-
|
|
17
|
-
export const resetInflightModel = () => (inflightModel = null);
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Session-switch handler. Registered once at extension init.
|
|
21
|
-
* Only notifies if a model load is actually in-flight.
|
|
22
|
-
*/
|
|
23
|
-
export const onSessionBeforeSwitch = async (
|
|
24
|
-
_event: SessionBeforeSwitchEvent,
|
|
25
|
-
ctx: ExtensionContext,
|
|
26
|
-
) => {
|
|
27
|
-
if (!inflightModel) return;
|
|
28
|
-
|
|
29
|
-
const messages = [
|
|
30
|
-
`Session change detected while model '${inflightModel.name}' was still loading.`,
|
|
31
|
-
"Model load will continue in the background, but UI might not update.",
|
|
32
|
-
"",
|
|
33
|
-
"Verify that your new model is loaded, or use /models to re-select it afterwards.",
|
|
34
|
-
];
|
|
35
|
-
ctx.ui.notify(messages.join("\n"), "warning");
|
|
36
|
-
|
|
37
|
-
// Show the notification for a reasonable amount of time
|
|
38
|
-
await new Promise((r) => setTimeout(r, READABLE_TIMEOUT));
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Select a model from the list. Returns null if user cancels.
|
|
43
|
-
*
|
|
44
|
-
* @param ctx Pi context
|
|
45
|
-
* @param models A list of models
|
|
46
|
-
* @returns The selected model
|
|
47
|
-
*/
|
|
48
|
-
const selectModel = async (
|
|
49
|
-
ctx: ExtensionCommandContext,
|
|
50
|
-
models: BaseModel[],
|
|
51
|
-
): Promise<BaseModel | null> => {
|
|
52
|
-
const labels = await Promise.all(models.map((m) => m.getLabel()));
|
|
53
|
-
const choice = await ctx.ui.select(`${PROVIDER_NAME} models:`, labels);
|
|
54
|
-
if (!choice) return null;
|
|
55
|
-
const idx = labels.indexOf(choice);
|
|
56
|
-
return models[idx];
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get available actions for a model based on its mode and status.
|
|
61
|
-
*
|
|
62
|
-
* @param model The selected model
|
|
63
|
-
* @returns The array of available actions for the given model status
|
|
64
|
-
*/
|
|
65
|
-
const getActionsForModel = async (model: BaseModel): Promise<Array<Action>> => {
|
|
66
|
-
const routerModeActions: Record<Status, Array<Action>> = {
|
|
67
|
-
[Status.LOADED]: [Action.SWITCH, Action.UNLOAD, Action.INFO, Action.CANCEL],
|
|
68
|
-
[Status.LOADING]: [Action.INFO, Action.CANCEL],
|
|
69
|
-
[Status.FAILED]: [Action.RETRY, Action.CANCEL],
|
|
70
|
-
[Status.SLEEPING]: [
|
|
71
|
-
Action.SWITCH,
|
|
72
|
-
Action.UNLOAD,
|
|
73
|
-
Action.INFO,
|
|
74
|
-
Action.CANCEL,
|
|
75
|
-
],
|
|
76
|
-
[Status.UNLOADED]: [Action.LOAD, Action.CANCEL],
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const singleModeActions: Record<Status, Array<Action>> = {
|
|
80
|
-
[Status.LOADED]: [Action.INFO, Action.CANCEL],
|
|
81
|
-
[Status.LOADING]: [Action.CANCEL],
|
|
82
|
-
[Status.FAILED]: [Action.CANCEL],
|
|
83
|
-
[Status.SLEEPING]: [Action.INFO, Action.CANCEL],
|
|
84
|
-
[Status.UNLOADED]: [Action.CANCEL],
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const allActions =
|
|
88
|
-
model.mode === Mode.ROUTER ? routerModeActions : singleModeActions;
|
|
89
|
-
|
|
90
|
-
const status = await model.getStatus();
|
|
91
|
-
return allActions[status];
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Selects an action for a model.
|
|
96
|
-
*
|
|
97
|
-
* @param ctx Pi context
|
|
98
|
-
* @param model The selected model
|
|
99
|
-
* @param actions Possible actions to execute
|
|
100
|
-
* @returns The action, or null if user cancels
|
|
101
|
-
*/
|
|
102
|
-
const selectAction = async (
|
|
103
|
-
ctx: ExtensionCommandContext,
|
|
104
|
-
model: BaseModel,
|
|
105
|
-
actions: Array<Action>,
|
|
106
|
-
): Promise<Action | null> => {
|
|
107
|
-
const labels = actions.map((a) => String(a));
|
|
108
|
-
const choice = await ctx.ui.select(`${model.name}`, labels);
|
|
109
|
-
if (!choice) return null;
|
|
110
|
-
|
|
111
|
-
const idx = labels.indexOf(choice);
|
|
112
|
-
return actions[idx];
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Handles the menu for model selection
|
|
117
|
-
* Loops: select model → select action → handle action.
|
|
118
|
-
*
|
|
119
|
-
* Escape on actions menu goes back to model selection.
|
|
120
|
-
* Escape on model selection exits.
|
|
121
|
-
*
|
|
122
|
-
* @param ctx Pi context
|
|
123
|
-
* @returns The action and model, if detected
|
|
124
|
-
*/
|
|
125
|
-
const modelSelectionHandler = async (
|
|
126
|
-
ctx: ExtensionCommandContext,
|
|
127
|
-
models: BaseModel[],
|
|
128
|
-
): Promise<{ action: Action; model: BaseModel } | null> => {
|
|
129
|
-
while (true) {
|
|
130
|
-
// Select the model
|
|
131
|
-
const model = await selectModel(ctx, models);
|
|
132
|
-
if (!model) return null;
|
|
133
|
-
|
|
134
|
-
// Select the action
|
|
135
|
-
const actions = await getActionsForModel(model);
|
|
136
|
-
const action = await selectAction(ctx, model, actions);
|
|
137
|
-
if (action === null) {
|
|
138
|
-
// Escape key pressed => back to model selection
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Return the selected action and model
|
|
143
|
-
return { action, model };
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Handles the /models command when the server is unreachable.
|
|
149
|
-
*
|
|
150
|
-
* @param ctx The context used by Pi
|
|
151
|
-
*/
|
|
152
|
-
export const notFoundCommand = async (
|
|
153
|
-
ctx: ExtensionCommandContext,
|
|
154
|
-
): Promise<void> => {
|
|
155
|
-
const url = await resolveUrl(ctx.cwd);
|
|
156
|
-
ctx.ui.notify(`${PROVIDER_NAME} unreachable at ${url}`, "error");
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Handles the /models command
|
|
161
|
-
*
|
|
162
|
-
* @param args Arguments passed to the command
|
|
163
|
-
* @param ctx The context used by Pi
|
|
164
|
-
* @param pi The Pi extension
|
|
165
|
-
* @param models List of available models
|
|
166
|
-
*/
|
|
167
|
-
export const modelsCommand = async (
|
|
168
|
-
ctx: ExtensionCommandContext,
|
|
169
|
-
pi: ExtensionAPI,
|
|
170
|
-
models: BaseModel[],
|
|
171
|
-
): Promise<void> => {
|
|
172
|
-
const event = await modelSelectionHandler(ctx, models);
|
|
173
|
-
if (!event) return;
|
|
174
|
-
|
|
175
|
-
// Detect the model
|
|
176
|
-
const { action, model } = event;
|
|
177
|
-
|
|
178
|
-
// Action: Cancel
|
|
179
|
-
if (!action || action === Action.CANCEL) return;
|
|
180
|
-
|
|
181
|
-
// Action: Info
|
|
182
|
-
if (action === Action.INFO) {
|
|
183
|
-
const info = await model.getInfo();
|
|
184
|
-
ctx.ui.notify(`${info}`, "info");
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Action: Unload
|
|
189
|
-
if (action === Action.UNLOAD) {
|
|
190
|
-
await model.unload();
|
|
191
|
-
ctx.ui.notify(`Unloaded ${model.name}`, "info");
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Actions: Load/Switch/Retry
|
|
196
|
-
const loadActions = [Action.LOAD, Action.SWITCH, Action.RETRY];
|
|
197
|
-
if (loadActions.includes(action)) {
|
|
198
|
-
ctx.ui.notify(`Loading ${model.name}...`, "info");
|
|
199
|
-
inflightModel = model;
|
|
200
|
-
|
|
201
|
-
const onSuccess = async () => {
|
|
202
|
-
const piModel = ctx.modelRegistry.find(PROVIDER_ID, model.id);
|
|
203
|
-
if (!piModel) {
|
|
204
|
-
throw new Error(`Cannot find model ${model.name} in pi registry`);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if ((await model.getStatus()) === Status.FAILED) {
|
|
208
|
-
throw new Error(`Failed to load model ${model.name}`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
await pi.setModel(piModel);
|
|
212
|
-
ctx.ui.notify(`Model ${model.name} ready`, "info");
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const onFailure = (err: any) => {
|
|
216
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
ctx.ui.notify(message, "error");
|
|
220
|
-
} catch {
|
|
221
|
-
// ctx went stale between error and notification
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
// Load the model without blocking the UI
|
|
226
|
-
model.load().then(onSuccess).catch(onFailure).finally(resetInflightModel);
|
|
227
|
-
}
|
|
228
|
-
};
|
package/src/events.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { PROVIDER_ID } from "./constants";
|
|
3
|
-
import { ModelSelectEvent } from "./interfaces/events";
|
|
4
|
-
import { listModels } from "./tools/retriever";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Reacts to a new model event triggered by Pi
|
|
8
|
-
* @param event Model selection event
|
|
9
|
-
* @param ctx Pi context
|
|
10
|
-
*/
|
|
11
|
-
export const onModelSelect = async (
|
|
12
|
-
event: ModelSelectEvent,
|
|
13
|
-
ctx: ExtensionContext,
|
|
14
|
-
) => {
|
|
15
|
-
if (event.model.provider !== PROVIDER_ID) return;
|
|
16
|
-
|
|
17
|
-
const models = await listModels();
|
|
18
|
-
const model = models.find((m) => m.id === event.model.id);
|
|
19
|
-
if (!model) return;
|
|
20
|
-
|
|
21
|
-
ctx.ui.notify(`Loading ${model.name}...`, "info");
|
|
22
|
-
await model
|
|
23
|
-
.load()
|
|
24
|
-
.then(() => ctx.ui.notify(`Model ${model.name} ready`, "info"))
|
|
25
|
-
.catch(() => ctx.ui.notify(`Failed to load model ${model.name}`, "error"));
|
|
26
|
-
};
|
package/src/manager.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionCommandContext,
|
|
4
|
-
ProviderModelConfig,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { modelsCommand, notFoundCommand } from "./commands/models";
|
|
7
|
-
import {
|
|
8
|
-
DEFAULT_LLAMA_SERVER_URL,
|
|
9
|
-
PROVIDER_ID,
|
|
10
|
-
PROVIDER_NAME,
|
|
11
|
-
} from "./constants";
|
|
12
|
-
import { BaseModel } from "./models/baseModel";
|
|
13
|
-
import { resolveApiKey, resolveUrl } from "./tools/resolver";
|
|
14
|
-
import { isServerReady, listModels } from "./tools/retriever";
|
|
15
|
-
|
|
16
|
-
export class CommandManager {
|
|
17
|
-
private baseUrl: string = DEFAULT_LLAMA_SERVER_URL;
|
|
18
|
-
private serverModels: BaseModel[] = [];
|
|
19
|
-
|
|
20
|
-
constructor(private readonly pi: ExtensionAPI) {}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Sets up the initial state of the provider
|
|
24
|
-
*/
|
|
25
|
-
async initialize() {
|
|
26
|
-
if (await isServerReady()) {
|
|
27
|
-
await this.update();
|
|
28
|
-
} else {
|
|
29
|
-
await this.register([]);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Ensures the models are up-to-date with the server
|
|
35
|
-
*/
|
|
36
|
-
async update() {
|
|
37
|
-
this.baseUrl = `${await resolveUrl(process.cwd())}`;
|
|
38
|
-
|
|
39
|
-
this.serverModels = await listModels();
|
|
40
|
-
const modelConfigs = await Promise.all(
|
|
41
|
-
this.serverModels.map((m) => m.toProviderConfig()),
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
await this.register(modelConfigs);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Registers the provider in Pi with the given configurations
|
|
49
|
-
* Note: Registrations overload previous provider
|
|
50
|
-
*
|
|
51
|
-
* @param models Provider configurations for the models
|
|
52
|
-
*/
|
|
53
|
-
async register(models: ProviderModelConfig[]) {
|
|
54
|
-
this.pi.registerProvider(PROVIDER_ID, {
|
|
55
|
-
name: PROVIDER_NAME,
|
|
56
|
-
baseUrl: this.baseUrl,
|
|
57
|
-
api: "openai-completions",
|
|
58
|
-
apiKey: await resolveApiKey(),
|
|
59
|
-
models,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Dispatches the /models command
|
|
65
|
-
*
|
|
66
|
-
* @param args Arguments passed to the command
|
|
67
|
-
* @param ctx The context used by Pi
|
|
68
|
-
* @param pi The Pi extension
|
|
69
|
-
*/
|
|
70
|
-
async run(args: string, ctx: ExtensionCommandContext) {
|
|
71
|
-
if (!(await isServerReady())) {
|
|
72
|
-
return await notFoundCommand(ctx);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Refresh the model list from the server
|
|
76
|
-
await this.update();
|
|
77
|
-
|
|
78
|
-
// Command: `/models info`
|
|
79
|
-
if (args === "info") {
|
|
80
|
-
const info = await Promise.all(this.serverModels.map((m) => m.getInfo()));
|
|
81
|
-
const message = ctx.ui.theme.fg("accent", info.join("\n"));
|
|
82
|
-
ctx.ui.notify(message, "info");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Command: `/models unload`
|
|
87
|
-
if (args === "unload") {
|
|
88
|
-
await Promise.all(this.serverModels.map((m) => m.unload()));
|
|
89
|
-
ctx.ui.notify(`Unloaded all ${PROVIDER_NAME} models`, "info");
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Command: `/models` (interactive menu)
|
|
94
|
-
return await modelsCommand(ctx, this.pi, this.serverModels);
|
|
95
|
-
}
|
|
96
|
-
}
|
package/src/tools/resolver.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { access, constants, readFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
API_KEY_PLACEHOLDER,
|
|
6
|
-
DEFAULT_LLAMA_SERVER_URL,
|
|
7
|
-
PROVIDER_ID,
|
|
8
|
-
} from "../constants";
|
|
9
|
-
import { AuthFile } from "../interfaces/auth";
|
|
10
|
-
|
|
11
|
-
// The URL is detected once, to reuse forever
|
|
12
|
-
let resolvedUrl: string | undefined;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Detects if a particular file is present
|
|
16
|
-
* @param filePath The path
|
|
17
|
-
* @returns True if exists
|
|
18
|
-
*/
|
|
19
|
-
const fileExists = async (filePath: string): Promise<boolean> => {
|
|
20
|
-
try {
|
|
21
|
-
await access(filePath, constants.F_OK);
|
|
22
|
-
return true;
|
|
23
|
-
} catch (error) {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Reads and parses the contents of a file as JSON
|
|
30
|
-
* @param filePath The path to the file
|
|
31
|
-
* @returns The parsed content, or null if parsing fails
|
|
32
|
-
*/
|
|
33
|
-
const readContents = async <T>(filePath: string): Promise<T | null> => {
|
|
34
|
-
const raw = await readFile(filePath, "utf-8");
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const contents = JSON.parse(raw);
|
|
38
|
-
return contents;
|
|
39
|
-
} catch (err) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Reads a value from a JSON config file by key
|
|
46
|
-
* @param filePath Path to the JSON config file
|
|
47
|
-
* @param key Key to extract from the parsed JSON
|
|
48
|
-
* @returns The value at the given key, or null if file/key missing or invalid
|
|
49
|
-
*/
|
|
50
|
-
const readConfigValue = async <T>(
|
|
51
|
-
filePath: string,
|
|
52
|
-
key: keyof T,
|
|
53
|
-
): Promise<T[keyof T] | null> => {
|
|
54
|
-
const cfg = await readContents<T>(filePath);
|
|
55
|
-
return cfg?.[key] ?? null;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Reads API key from Pi's auth file
|
|
60
|
-
* @returns The API key, as defined by the auth.json file
|
|
61
|
-
*/
|
|
62
|
-
export const resolveApiKey = async (): Promise<string> => {
|
|
63
|
-
const authPath = join(getAgentDir(), "settings.json");
|
|
64
|
-
if (!(await fileExists(authPath))) return API_KEY_PLACEHOLDER;
|
|
65
|
-
|
|
66
|
-
const cfg = await readConfigValue<AuthFile>(authPath, PROVIDER_ID);
|
|
67
|
-
return cfg?.key ?? API_KEY_PLACEHOLDER;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Resolves the llama-server url by searching for it in the global settings.json file
|
|
72
|
-
* @returns The URL, if found.
|
|
73
|
-
*/
|
|
74
|
-
const resolveGlobalUrl = async (): Promise<string | null> => {
|
|
75
|
-
const globalPath = join(getAgentDir(), "settings.json");
|
|
76
|
-
if (!(await fileExists(globalPath))) return null;
|
|
77
|
-
|
|
78
|
-
return readConfigValue<Record<string, string>>(globalPath, "llamaServerUrl");
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Resolves the llama-server url by searching for it in the project's .pi/llama-server.json file
|
|
83
|
-
* @param cwd The current working directory
|
|
84
|
-
* @returns The URL, if found.
|
|
85
|
-
*/
|
|
86
|
-
const resolveProjectUrl = async (cwd: string): Promise<string | null> => {
|
|
87
|
-
const projectPath = join(cwd, ".pi", "llama-server.json");
|
|
88
|
-
|
|
89
|
-
if (!(await fileExists(projectPath))) return null;
|
|
90
|
-
return readConfigValue<Record<string, string>>(projectPath, "url");
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Resolves the llama-server url by searching for it in the environment
|
|
95
|
-
* @returns The URL, if found.
|
|
96
|
-
*/
|
|
97
|
-
const resolveEnvUrl = async (): Promise<string | null> => {
|
|
98
|
-
return process.env.LLAMA_SERVER_URL ?? null;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Tries all possible ways to retrieve the llama-server URL
|
|
103
|
-
* @param cwd The current working directory
|
|
104
|
-
* @returns The URL, or a default if not found
|
|
105
|
-
*/
|
|
106
|
-
const resolveUrlWithFallbacks = async (cwd: string): Promise<string> => {
|
|
107
|
-
// 1. per-project config
|
|
108
|
-
let response = await resolveProjectUrl(cwd);
|
|
109
|
-
if (response) return response;
|
|
110
|
-
|
|
111
|
-
// 2. env
|
|
112
|
-
response = await resolveEnvUrl();
|
|
113
|
-
if (response) return response;
|
|
114
|
-
|
|
115
|
-
// 3. global settings: ~/.pi/agent/settings.json
|
|
116
|
-
response = await resolveGlobalUrl();
|
|
117
|
-
if (response) return response;
|
|
118
|
-
|
|
119
|
-
// 4. default
|
|
120
|
-
return DEFAULT_LLAMA_SERVER_URL;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Resolves the URL where llama-server is running
|
|
125
|
-
* @param cwd The current working directory
|
|
126
|
-
* @returns The URL, or a default if not found
|
|
127
|
-
*/
|
|
128
|
-
export const resolveUrl = async (cwd: string): Promise<string> => {
|
|
129
|
-
if (resolvedUrl) return resolvedUrl;
|
|
130
|
-
const result = await resolveUrlWithFallbacks(cwd);
|
|
131
|
-
|
|
132
|
-
// Strip trailing slashes
|
|
133
|
-
resolvedUrl = result.replace(/\/+$/, "");
|
|
134
|
-
|
|
135
|
-
return resolvedUrl;
|
|
136
|
-
};
|
package/src/tools/retriever.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { HealthEndpoint } from "../interfaces/endpoints/health";
|
|
2
|
-
import { ModelsEndpoint } from "../interfaces/endpoints/models";
|
|
3
|
-
import { BaseModel } from "../models/baseModel";
|
|
4
|
-
import { RouterModel } from "../models/routerModel";
|
|
5
|
-
import { SingleModel } from "../models/singleModel";
|
|
6
|
-
import { resolveApiKey, resolveUrl } from "./resolver";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Detects if the server is ready
|
|
10
|
-
* @returns True if it's ready to work
|
|
11
|
-
*/
|
|
12
|
-
export const isServerReady = async (): Promise<boolean> => {
|
|
13
|
-
try {
|
|
14
|
-
const { status } = await rpc<HealthEndpoint>("/health");
|
|
15
|
-
return status === "ok";
|
|
16
|
-
} catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Makes an HTTP request to the llama-server and returns the parsed JSON response
|
|
23
|
-
*
|
|
24
|
-
* @param endpoint The endpoint path to fetch (e.g. "/health")
|
|
25
|
-
* @param body The optional request body for POST requests
|
|
26
|
-
* @returns The parsed JSON response from the server
|
|
27
|
-
*/
|
|
28
|
-
export const rpc = async <T>(
|
|
29
|
-
endpoint: string,
|
|
30
|
-
body?: Record<string, unknown>,
|
|
31
|
-
): Promise<T> => {
|
|
32
|
-
const base = await resolveUrl(process.cwd());
|
|
33
|
-
const url = `${base}${endpoint}`;
|
|
34
|
-
|
|
35
|
-
const data = {
|
|
36
|
-
method: body ? "POST" : "GET",
|
|
37
|
-
headers: body ? { "Content-Type": "application/json" } : undefined,
|
|
38
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const apiKey = await resolveApiKey();
|
|
42
|
-
const res = await fetch(url, {
|
|
43
|
-
...data,
|
|
44
|
-
headers: {
|
|
45
|
-
...data.headers,
|
|
46
|
-
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const response: T = await res.json();
|
|
51
|
-
return response;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Retrieves a list of available models from llama-server
|
|
56
|
-
* @param base Base URL to use
|
|
57
|
-
* @returns The list of models
|
|
58
|
-
*/
|
|
59
|
-
export const listModels = async (): Promise<BaseModel[]> => {
|
|
60
|
-
const { models, data } = await rpc<ModelsEndpoint>("/models");
|
|
61
|
-
|
|
62
|
-
if (models) {
|
|
63
|
-
return data.map((m) => new SingleModel(m));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const response = data
|
|
67
|
-
.map((m) => new RouterModel(m))
|
|
68
|
-
.sort((a, b) => (a.id > b.id ? 1 : a.id === b.id ? 0 : -1));
|
|
69
|
-
|
|
70
|
-
return response;
|
|
71
|
-
};
|