mirror of
https://github.com/Narcooo/inkos.git
synced 2026-09-01 15:08:51 +08:00
fix(studio): make RightCode custom chat work end-to-end
This commit is contained in:
@@ -144,11 +144,13 @@ export function createLLMClient(config: LLMConfig): LLMClient {
|
||||
const baseUrl = config.baseUrl || preset?.baseUrl || "";
|
||||
const extraHeaders = config.headers ?? parseEnvHeaders();
|
||||
|
||||
const provider = config.provider === "anthropic" ? "anthropic" : "openai";
|
||||
|
||||
const piModel: PiModel<PiApi> = {
|
||||
id: config.model,
|
||||
name: config.model,
|
||||
api: piApi,
|
||||
provider: serviceName,
|
||||
provider,
|
||||
baseUrl,
|
||||
reasoning: (config.thinkingBudget ?? 0) > 0,
|
||||
input: ["text"] as ("text" | "image")[],
|
||||
@@ -158,7 +160,6 @@ export function createLLMClient(config: LLMConfig): LLMClient {
|
||||
...(extraHeaders ? { headers: extraHeaders } : {}),
|
||||
};
|
||||
|
||||
const provider = config.provider === "anthropic" ? "anthropic" : "openai";
|
||||
return {
|
||||
provider,
|
||||
apiFormat,
|
||||
|
||||
@@ -122,6 +122,7 @@ vi.mock("@actalk/inkos-core", () => {
|
||||
loadProjectSession: loadProjectSessionMock,
|
||||
resolveSessionActiveBook: resolveSessionActiveBookMock,
|
||||
runAgentSession: runAgentSessionMock,
|
||||
buildAgentSystemPrompt: vi.fn(() => "You are helpful."),
|
||||
findOrCreateBookSession: findOrCreateBookSessionMock,
|
||||
loadBookSession: loadBookSessionMock,
|
||||
persistBookSession: persistBookSessionMock,
|
||||
@@ -989,7 +990,7 @@ describe("createStudioServer daemon lifecycle", () => {
|
||||
responseText: "",
|
||||
messages: [{ role: "user", content: "nihao" }],
|
||||
});
|
||||
chatCompletionMock.mockRejectedValueOnce(new Error("quota exhausted"));
|
||||
chatCompletionMock.mockRejectedValue(new Error("quota exhausted"));
|
||||
|
||||
const { createStudioServer } = await import("./server.js");
|
||||
const app = createStudioServer(cloneProjectConfig() as never, root);
|
||||
@@ -1010,6 +1011,32 @@ describe("createStudioServer daemon lifecycle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to plain chat when the tool-agent returns empty text", async () => {
|
||||
runAgentSessionMock.mockResolvedValueOnce({
|
||||
responseText: "",
|
||||
messages: [{ role: "user", content: "nihao" }],
|
||||
});
|
||||
chatCompletionMock.mockResolvedValueOnce({
|
||||
content: "你好!",
|
||||
usage: { promptTokens: 1, completionTokens: 1, totalTokens: 2 },
|
||||
});
|
||||
|
||||
const { createStudioServer } = await import("./server.js");
|
||||
const app = createStudioServer(cloneProjectConfig() as never, root);
|
||||
|
||||
const response = await app.request("http://localhost/api/v1/agent", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ instruction: "nihao", activeBookId: "demo-book" }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
await expect(response.json()).resolves.toEqual({
|
||||
response: "你好!",
|
||||
session: { sessionId: "agent-session-1" },
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the shared interaction session state", async () => {
|
||||
loadProjectSessionMock.mockResolvedValue({
|
||||
sessionId: "session-2",
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
persistBookSession,
|
||||
appendBookSessionMessage,
|
||||
runAgentSession,
|
||||
buildAgentSystemPrompt,
|
||||
resolveServicePreset,
|
||||
resolveServiceModel,
|
||||
loadSecrets,
|
||||
@@ -812,7 +813,13 @@ export function createStudioServer(initialConfig: ProjectConfig, root: string) {
|
||||
return c.json({ ok: false, error: "连接成功但未返回可用模型" }, 400);
|
||||
}
|
||||
|
||||
const sampleModel = models[0]?.id;
|
||||
const rawConfig = await loadRawConfig(root).catch(() => ({} as Record<string, unknown>));
|
||||
const preferredModel = typeof (rawConfig.llm as Record<string, unknown> | undefined)?.defaultModel === "string"
|
||||
? String((rawConfig.llm as Record<string, unknown>).defaultModel)
|
||||
: undefined;
|
||||
const sampleModel = models.find((m) => m.id === preferredModel)?.id
|
||||
?? models.find((m) => m.id === "gpt-5.4")?.id
|
||||
?? models[0]?.id;
|
||||
if (sampleModel) {
|
||||
const client = createLLMClient({
|
||||
provider: service === "anthropic" ? "anthropic" : "openai",
|
||||
@@ -1310,6 +1317,41 @@ export function createStudioServer(initialConfig: ProjectConfig, root: string) {
|
||||
});
|
||||
}
|
||||
if (!result.responseText) {
|
||||
try {
|
||||
const fallbackClient = createLLMClient({
|
||||
...config.llm,
|
||||
service: configuredEntry?.service ?? reqService ?? config.llm.service,
|
||||
model: reqModel ?? config.llm.model,
|
||||
apiKey: agentApiKey ?? config.llm.apiKey,
|
||||
baseUrl: configuredEntry?.baseUrl ?? config.llm.baseUrl,
|
||||
...(configuredEntry?.apiFormat ? { apiFormat: configuredEntry.apiFormat } : {}),
|
||||
...(configuredEntry?.stream !== undefined ? { stream: configuredEntry.stream } : {}),
|
||||
} as ProjectConfig["llm"]);
|
||||
const fallback = await chatCompletion(
|
||||
fallbackClient,
|
||||
reqModel ?? config.llm.model,
|
||||
[
|
||||
{ role: "system", content: buildAgentSystemPrompt(activeBookId ?? null, config.language ?? "zh") },
|
||||
{ role: "user", content: instruction },
|
||||
],
|
||||
{ maxTokens: 256 },
|
||||
);
|
||||
if (fallback.content?.trim()) {
|
||||
bookSession = appendBookSessionMessage(bookSession, {
|
||||
role: "assistant",
|
||||
content: fallback.content,
|
||||
timestamp: Date.now() + 1,
|
||||
});
|
||||
await persistBookSession(root, bookSession);
|
||||
return c.json({
|
||||
response: fallback.content,
|
||||
session: { sessionId: bookSession.sessionId },
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// fall through to probe-based diagnosis below
|
||||
}
|
||||
|
||||
try {
|
||||
const probeClient = createLLMClient({
|
||||
...config.llm,
|
||||
|
||||
Reference in New Issue
Block a user