mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
feat: auto-detect dedicated memory llm providers
This commit is contained in:
@@ -110,6 +110,23 @@ async function withModelFetchSettings(run) {
|
||||
}
|
||||
}
|
||||
|
||||
async function withModelFetchSettingsOverrides(overrides, run) {
|
||||
const previousSettings = JSON.parse(
|
||||
JSON.stringify(extensionsApi.extension_settings.st_bme || {}),
|
||||
);
|
||||
extensionsApi.extension_settings.st_bme = {
|
||||
...previousSettings,
|
||||
...buildModelFetchSettings(),
|
||||
...(overrides || {}),
|
||||
};
|
||||
|
||||
try {
|
||||
await run();
|
||||
} finally {
|
||||
extensionsApi.extension_settings.st_bme = previousSettings;
|
||||
}
|
||||
}
|
||||
|
||||
async function testFetchMemoryModelsUsesCustomStatusFirst() {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const seenBodies = [];
|
||||
@@ -238,8 +255,70 @@ async function testFetchMemoryModelsParsesNestedPayload() {
|
||||
}
|
||||
}
|
||||
|
||||
async function testFetchMemoryModelsUsesGoogleStatusRoute() {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const seenBodies = [];
|
||||
|
||||
globalThis.fetch = async (_url, options = {}) => {
|
||||
seenBodies.push(JSON.parse(String(options.body || "{}")));
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
data: [{ id: "gemini-2.5-pro" }],
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
await withModelFetchSettingsOverrides(
|
||||
{
|
||||
llmApiUrl:
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent",
|
||||
llmApiKey: "gemini-secret",
|
||||
},
|
||||
async () => {
|
||||
const result = await llm.fetchMemoryLLMModels();
|
||||
assert.equal(result.success, true);
|
||||
assert.deepEqual(result.models, [
|
||||
{ id: "gemini-2.5-pro", label: "gemini-2.5-pro" },
|
||||
]);
|
||||
assert.equal(seenBodies.length, 1);
|
||||
assert.equal(seenBodies[0].chat_completion_source, "makersuite");
|
||||
assert.equal(seenBodies[0].reverse_proxy, "https://generativelanguage.googleapis.com");
|
||||
assert.equal(seenBodies[0].proxy_password, "gemini-secret");
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
}
|
||||
|
||||
async function testFetchMemoryModelsReturnsHelpfulMessageForAnthropic() {
|
||||
await withModelFetchSettingsOverrides(
|
||||
{
|
||||
llmApiUrl: "https://api.anthropic.com/v1/messages",
|
||||
llmApiKey: "anthropic-secret",
|
||||
llmModel: "claude-sonnet-4-5",
|
||||
},
|
||||
async () => {
|
||||
const result = await llm.fetchMemoryLLMModels();
|
||||
assert.equal(result.success, false);
|
||||
assert.equal(result.models.length, 0);
|
||||
assert.match(result.error, /Anthropic Claude/);
|
||||
assert.match(result.error, /手动填写模型名/);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await testFetchMemoryModelsUsesCustomStatusFirst();
|
||||
await testFetchMemoryModelsFallsBackToLegacyStatus();
|
||||
await testFetchMemoryModelsParsesNestedPayload();
|
||||
await testFetchMemoryModelsUsesGoogleStatusRoute();
|
||||
await testFetchMemoryModelsReturnsHelpfulMessageForAnthropic();
|
||||
|
||||
console.log("llm-model-fetch tests passed");
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
isSameLlmConfigSnapshot,
|
||||
isUsableLlmConfigSnapshot,
|
||||
normalizeLlmPresetMap,
|
||||
resolveDedicatedLlmProviderConfig,
|
||||
resolveLlmConfigSelection,
|
||||
resolveActiveLlmPresetName,
|
||||
sanitizeLlmPresetSettings,
|
||||
@@ -226,4 +227,31 @@ assert.deepEqual(invalidTaskPresetSelection.config, {
|
||||
llmModel: "model-global",
|
||||
});
|
||||
|
||||
const arkProvider = resolveDedicatedLlmProviderConfig(
|
||||
"https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions",
|
||||
);
|
||||
assert.equal(arkProvider.providerId, "volcengine-ark");
|
||||
assert.equal(arkProvider.transportId, "dedicated-openai-compatible");
|
||||
assert.equal(arkProvider.routeMode, "custom");
|
||||
assert.equal(arkProvider.apiUrl, "https://ark.cn-beijing.volces.com/api/coding/v3");
|
||||
assert.equal(arkProvider.supportsModelFetch, true);
|
||||
|
||||
const anthropicProvider = resolveDedicatedLlmProviderConfig(
|
||||
"https://api.anthropic.com/v1/messages",
|
||||
);
|
||||
assert.equal(anthropicProvider.providerId, "anthropic-claude");
|
||||
assert.equal(anthropicProvider.transportId, "dedicated-anthropic-claude");
|
||||
assert.equal(anthropicProvider.routeMode, "reverse-proxy");
|
||||
assert.equal(anthropicProvider.apiUrl, "https://api.anthropic.com/v1");
|
||||
assert.equal(anthropicProvider.supportsModelFetch, false);
|
||||
|
||||
const geminiProvider = resolveDedicatedLlmProviderConfig(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent",
|
||||
);
|
||||
assert.equal(geminiProvider.providerId, "google-ai-studio");
|
||||
assert.equal(geminiProvider.transportId, "dedicated-google-ai-studio");
|
||||
assert.equal(geminiProvider.routeMode, "reverse-proxy");
|
||||
assert.equal(geminiProvider.apiUrl, "https://generativelanguage.googleapis.com");
|
||||
assert.equal(geminiProvider.supportsModelFetch, true);
|
||||
|
||||
console.log("llm-preset-utils tests passed");
|
||||
|
||||
@@ -83,7 +83,7 @@ if (originalSendOpenAIRequest === undefined) {
|
||||
globalThis.__llmStreamingSendOpenAIRequest = originalSendOpenAIRequest;
|
||||
}
|
||||
|
||||
function buildStreamingSettings(generation = {}) {
|
||||
function buildStreamingSettings(generation = {}, overrides = {}) {
|
||||
const taskProfiles = createDefaultTaskProfiles();
|
||||
taskProfiles.extract.profiles[0].generation = {
|
||||
...taskProfiles.extract.profiles[0].generation,
|
||||
@@ -96,6 +96,7 @@ function buildStreamingSettings(generation = {}) {
|
||||
timeoutMs: 1234,
|
||||
taskProfilesVersion: 3,
|
||||
taskProfiles,
|
||||
...(overrides || {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,13 +126,13 @@ function getSnapshot(taskKey = "extract") {
|
||||
return globalThis.__stBmeRuntimeDebugState?.taskLlmRequests?.[taskKey] || null;
|
||||
}
|
||||
|
||||
async function withStreamingSettings(generation, run) {
|
||||
async function withStreamingSettings(generation, run, overrides = {}) {
|
||||
const previousSettings = JSON.parse(
|
||||
JSON.stringify(extensionsApi.extension_settings.st_bme || {}),
|
||||
);
|
||||
extensionsApi.extension_settings.st_bme = {
|
||||
...previousSettings,
|
||||
...buildStreamingSettings(generation),
|
||||
...buildStreamingSettings(generation, overrides),
|
||||
};
|
||||
delete globalThis.__stBmeRuntimeDebugState;
|
||||
|
||||
@@ -415,9 +416,72 @@ async function testJsonRetryKeepsProfileCompletionTokens() {
|
||||
}
|
||||
}
|
||||
|
||||
async function testAnthropicRouteUsesReverseProxyAndDisablesStreaming() {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let requestBody = null;
|
||||
|
||||
globalThis.fetch = async (_url, options = {}) => {
|
||||
requestBody = JSON.parse(String(options.body || "{}"));
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: '{"ok":true}',
|
||||
},
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
await withStreamingSettings(
|
||||
{ stream: true },
|
||||
async () => {
|
||||
const result = await llm.callLLMForJSON({
|
||||
systemPrompt: "system",
|
||||
userPrompt: "user",
|
||||
maxRetries: 0,
|
||||
taskType: "extract",
|
||||
requestSource: "test:anthropic-route",
|
||||
});
|
||||
|
||||
assert.deepEqual(result, { ok: true });
|
||||
assert.equal(requestBody?.chat_completion_source, "claude");
|
||||
assert.equal(requestBody?.reverse_proxy, "https://api.anthropic.com/v1");
|
||||
assert.equal(requestBody?.proxy_password, "sk-stream-secret");
|
||||
assert.equal(requestBody?.stream, false);
|
||||
assert.ok(requestBody?.json_schema);
|
||||
|
||||
const snapshot = getSnapshot("extract");
|
||||
assert.ok(snapshot);
|
||||
assert.equal(snapshot.route, "dedicated-anthropic-claude");
|
||||
assert.equal(snapshot.llmProviderLabel, "Anthropic Claude");
|
||||
assert.equal(snapshot.streamRequested, false);
|
||||
assert.equal(snapshot.streamForceDisabled, true);
|
||||
},
|
||||
{
|
||||
llmApiUrl: "https://api.anthropic.com/v1/messages",
|
||||
llmModel: "claude-sonnet-4-5",
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
}
|
||||
|
||||
await testDedicatedStreamingSuccess();
|
||||
await testDedicatedStreamingFallsBackToNonStream();
|
||||
await testDedicatedStreamingAbortDoesNotLeaveActiveState();
|
||||
await testJsonRetryKeepsProfileCompletionTokens();
|
||||
await testAnthropicRouteUsesReverseProxyAndDisablesStreaming();
|
||||
|
||||
console.log("llm-streaming tests passed");
|
||||
|
||||
Reference in New Issue
Block a user