mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-09 03:22:40 +00:00
fix(lmstudio): preserve wizard prompter binding
Bind LM Studio wizard prompter callbacks before storing them so class-backed gateway setup sessions keep their receiver and no longer crash when selecting LM Studio. Thanks @christineyan4. Co-authored-by: Christine Yan <christine.yan4@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -182,6 +182,54 @@ function createQueuedWizardPrompterHarness(textValues: string[]): {
|
||||
return { prompter, note, text };
|
||||
}
|
||||
|
||||
function createMethodBoundWizardPrompterHarness(textValues: string[]): {
|
||||
prompter: WizardPrompter;
|
||||
note: ReturnType<typeof vi.fn>;
|
||||
text: ReturnType<typeof vi.fn>;
|
||||
} {
|
||||
const queue = [...textValues];
|
||||
const note = vi.fn(async (_message: string, _title?: string) => {});
|
||||
const text = vi.fn(async () => queue.shift() ?? "");
|
||||
|
||||
class MethodBoundWizardPrompter implements WizardPrompter {
|
||||
async intro() {}
|
||||
async outro() {}
|
||||
async note(message: string, title?: string) {
|
||||
await this.recordNote(message, title);
|
||||
}
|
||||
async select<T>(params: { options: Array<{ value: T }> }) {
|
||||
const firstOption = params.options[0];
|
||||
if (!firstOption) {
|
||||
throw new Error("select called without options");
|
||||
}
|
||||
return firstOption.value;
|
||||
}
|
||||
async multiselect() {
|
||||
return [];
|
||||
}
|
||||
async text() {
|
||||
return await this.readText();
|
||||
}
|
||||
async confirm() {
|
||||
return false;
|
||||
}
|
||||
progress() {
|
||||
return {
|
||||
update: () => {},
|
||||
stop: () => {},
|
||||
};
|
||||
}
|
||||
private async readText() {
|
||||
return await text();
|
||||
}
|
||||
private async recordNote(message: string, title?: string) {
|
||||
await note(message, title);
|
||||
}
|
||||
}
|
||||
|
||||
return { prompter: new MethodBoundWizardPrompter(), note, text };
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
@@ -798,6 +846,43 @@ describe("lmstudio setup", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("interactive setup preserves gateway wizard prompter method binding", async () => {
|
||||
const { prompter, text } = createMethodBoundWizardPrompterHarness([
|
||||
"http://localhost:1234/api/v1/",
|
||||
"lmstudio-test-key",
|
||||
"4096",
|
||||
]);
|
||||
|
||||
const result = await promptAndConfigureLmstudioInteractive({
|
||||
config: buildConfig(),
|
||||
prompter,
|
||||
});
|
||||
|
||||
expect(text).toHaveBeenCalledTimes(3);
|
||||
expect(result.defaultModel).toBe("lmstudio/qwen3-8b-instruct");
|
||||
});
|
||||
|
||||
it("interactive setup preserves gateway wizard note binding on discovery failure", async () => {
|
||||
fetchLmstudioModelsMock.mockResolvedValueOnce({ reachable: false, models: [] });
|
||||
const { prompter, note, text } = createMethodBoundWizardPrompterHarness([
|
||||
"http://localhost:1234/api/v1/",
|
||||
"lmstudio-test-key",
|
||||
]);
|
||||
|
||||
await expect(
|
||||
promptAndConfigureLmstudioInteractive({
|
||||
config: buildConfig(),
|
||||
prompter,
|
||||
}),
|
||||
).rejects.toThrow("LM Studio not reachable");
|
||||
|
||||
expect(text).toHaveBeenCalledTimes(2);
|
||||
expect(note).toHaveBeenCalledWith(
|
||||
"LM Studio could not be reached at http://localhost:1234/v1.\nStart LM Studio (or run lms server start) and re-run setup.",
|
||||
"LM Studio",
|
||||
);
|
||||
});
|
||||
|
||||
it("interactive setup accepts a blank API key for unauthenticated local LM Studio", async () => {
|
||||
const { prompter, text } = createQueuedWizardPrompterHarness([
|
||||
"http://localhost:1234/api/v1/",
|
||||
|
||||
@@ -390,11 +390,13 @@ export async function promptAndConfigureLmstudioInteractive(params: {
|
||||
promptText?: ProviderPromptText;
|
||||
note?: ProviderPromptNote;
|
||||
}): Promise<ProviderAuthResult> {
|
||||
const promptText = params.prompter?.text ?? params.promptText;
|
||||
const promptText = params.prompter
|
||||
? params.prompter.text.bind(params.prompter)
|
||||
: params.promptText;
|
||||
if (!promptText) {
|
||||
throw new Error("LM Studio interactive setup requires a text prompter.");
|
||||
}
|
||||
const note = params.prompter?.note ?? params.note;
|
||||
const note = params.prompter ? params.prompter.note.bind(params.prompter) : params.note;
|
||||
const defaultBaseUrl = resolveLmstudioSetupDefaultBaseUrl();
|
||||
const baseUrlRaw = await promptText({
|
||||
message: `${LMSTUDIO_PROVIDER_LABEL} base URL`,
|
||||
|
||||
Reference in New Issue
Block a user