summaryrefslogtreecommitdiffhomepage
path: root/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/app')
-rw-r--r--src/app/store.svelte.ts68
-rw-r--r--src/app/views/ExtensionsPane.svelte2
-rw-r--r--src/app/views/SettingsPane.svelte5
3 files changed, 75 insertions, 0 deletions
diff --git a/src/app/store.svelte.ts b/src/app/store.svelte.ts
index a858927..73b19bc 100644
--- a/src/app/store.svelte.ts
+++ b/src/app/store.svelte.ts
@@ -102,6 +102,10 @@ import type {
import { normalizeNamedAgentRecord, normalizeNamedAgents } from "../features/named-agents";
import type { PluginsStore } from "../features/plugins";
import { createPluginsHttp, createPluginsStore } from "../features/plugins";
+import {
+ normalizeSearchBackendConfig,
+ type SearchBackendConfigResult,
+} from "../features/search-settings";
import type { Tab, TabsState } from "../features/tabs";
import { createTabsStore, deriveTitle, type TabsStore } from "../features/tabs";
import {
@@ -531,6 +535,20 @@ export interface AppStore {
*/
concurrencyStatus(): Promise<ConcurrencyStatusResult>;
/**
+ * Load the GLOBAL search-backend selection
+ * (`GET /search/config`): which engine backs the `web_search` tool's search
+ * mode. Not workspace-scoped — the selection is global. `unavailable: true`
+ * on the failure variant marks the 503 degrade (the backend's
+ * search-config service not loaded) so the UI can disable the field.
+ */
+ searchBackendConfig(): Promise<SearchBackendConfigResult>;
+ /**
+ * Persist a new search-backend selection
+ * (`PUT /search/config`, body `{ backend }`). `400` (unknown/unconfigured
+ * backend) and `503` (service not loaded) both surface as `ok: false`.
+ */
+ setSearchBackend(backend: string): Promise<SearchBackendConfigResult>;
+ /**
* Fetch the per-slot release cooldown (ms) for one provider
* (`GET /concurrency/cooldown/:providerId`). `404` (no concurrency config at
* all) and `503` (extension not loaded) both surface as `ok: false`.
@@ -2557,6 +2575,56 @@ export function createAppStore(opts?: CreateAppStoreOptions): AppStore {
}
},
+ async searchBackendConfig(): Promise<SearchBackendConfigResult> {
+ try {
+ const res = await fetchImpl(`${httpBase}/search/config`);
+ if (!res.ok) {
+ const errBody = (await res.json().catch(() => null)) as { error?: string } | null;
+ return {
+ ok: false,
+ error: errBody?.error ?? `Search config failed (HTTP ${res.status})`,
+ // The 503 degrade = the backend's search-config service not loaded
+ // (the tool-web-search extension absent) — a distinct UI state.
+ ...(res.status === 503 ? { unavailable: true } : {}),
+ };
+ }
+ // Normalize the untyped JSON at this network seam (pure helper) so a
+ // malformed/partial response can never crash the renderer.
+ const config = normalizeSearchBackendConfig(await res.json());
+ return { ok: true, config };
+ } catch (err) {
+ return {
+ ok: false,
+ error: err instanceof Error ? err.message : "Search config request failed",
+ };
+ }
+ },
+
+ async setSearchBackend(backend: string): Promise<SearchBackendConfigResult> {
+ try {
+ const res = await fetchImpl(`${httpBase}/search/config`, {
+ method: "PUT",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ backend }),
+ });
+ if (!res.ok) {
+ const errBody = (await res.json().catch(() => null)) as { error?: string } | null;
+ return {
+ ok: false,
+ error: errBody?.error ?? `Set search backend failed (HTTP ${res.status})`,
+ ...(res.status === 503 ? { unavailable: true } : {}),
+ };
+ }
+ const config = normalizeSearchBackendConfig(await res.json());
+ return { ok: true, config };
+ } catch (err) {
+ return {
+ ok: false,
+ error: err instanceof Error ? err.message : "Set search backend request failed",
+ };
+ }
+ },
+
async getConcurrencyCooldown(providerId: string): Promise<ConcurrencyCooldownResult> {
try {
const res = await fetchImpl(
diff --git a/src/app/views/ExtensionsPane.svelte b/src/app/views/ExtensionsPane.svelte
index 7a2343e..6afc2bf 100644
--- a/src/app/views/ExtensionsPane.svelte
+++ b/src/app/views/ExtensionsPane.svelte
@@ -17,6 +17,7 @@
import { manifest as markdownManifest } from "../../features/markdown";
import { manifest as mcpManifest } from "../../features/mcp";
import { manifest as namedAgentsManifest } from "../../features/named-agents";
+ import { manifest as searchSettingsManifest } from "../../features/search-settings";
import { manifest as settingsManifest } from "../../features/settings";
import { manifest as smartScrollManifest } from "../../features/smart-scroll";
import { SurfaceView } from "../../features/surface-host";
@@ -60,6 +61,7 @@
concurrencyManifest,
visionManifest,
namedAgentsManifest,
+ searchSettingsManifest,
].map((m) => [m.name, m.description] as const);
</script>
diff --git a/src/app/views/SettingsPane.svelte b/src/app/views/SettingsPane.svelte
index 67a88ec..f119103 100644
--- a/src/app/views/SettingsPane.svelte
+++ b/src/app/views/SettingsPane.svelte
@@ -8,6 +8,7 @@
*/
import { tick } from "svelte";
import { ChatLimitField, type ChatLimitSaveResult } from "../../features/settings";
+ import { SearchBackendField } from "../../features/search-settings";
import type { AppViewPorts } from "../view-ports";
let { ports }: { ports: AppViewPorts } = $props();
@@ -31,4 +32,8 @@
<div class="flex flex-col gap-3">
<ChatLimitField chatLimit={store.chatLimit} save={saveChatLimit} />
+ <SearchBackendField
+ load={() => store.searchBackendConfig()}
+ save={(backend) => store.setSearchBackend(backend)}
+ />
</div>