mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
refactor(config): remove preferences API
This commit is contained in:
@@ -97,9 +97,9 @@ Review endpoints in document order. For each endpoint, select one disposition an
|
||||
| [x] 018 | `GET` | `/api/command` | `command.list` | Keep | Request and response accepted as-is. |
|
||||
| [x] 019 | `GET` | `/api/skill` | `skill.list` | Keep | Renamed `location` to `path`; removed the skill-specific `slash` flag and slash-command behavior. |
|
||||
| [x] 020 | `GET` | `/api/reference` | `reference.list` | Keep | Removed duplicate `description` and `hidden` fields from nested `source`. |
|
||||
| [ ] 021 | `GET` | `/api/config` | `config.get` | Change | Removed `claude` and `agents` entry variants; one compatibility plugin now normalizes their skills. |
|
||||
| [ ] 022 | `GET` | `/api/config/preferences` | `config.preferences` | | |
|
||||
| [ ] 023 | `PATCH` | `/api/config/preferences` | `config.updatePreferences` | | |
|
||||
| [x] 021 | `GET` | `/api/config` | `config.get` | Keep | Compatibility entries removed; response now contains only documents and OpenCode directories. |
|
||||
| [x] 022 | `GET` | `/api/config/preferences` | `config.preferences` | Remove | Redundant special projection of global config. |
|
||||
| [x] 023 | `PATCH` | `/api/config/preferences` | `config.updatePreferences` | Remove | Redundant field-specific config mutation API. |
|
||||
| [ ] 024 | `GET` | `/api/config/shell` | `config.shells` | | |
|
||||
|
||||
## Group 3: Credentials, integrations, MCP, and web search
|
||||
|
||||
@@ -20,7 +20,6 @@ test("session settings use the remote server context", async ({ page }) => {
|
||||
// one toggle sweeps every connected server, not just the focused one.
|
||||
await mockServers(page, permissionRequests, permissionResponses, {
|
||||
pending: { [serverA]: [pendingPermission("permission-pending-a", sessionA.id)] },
|
||||
preferencesUnavailable: true,
|
||||
})
|
||||
await configureServers(page)
|
||||
|
||||
@@ -326,7 +325,6 @@ type MockServerOptions = {
|
||||
listFailures?: Record<string, number>
|
||||
// Records /api/session/:id GETs so tests can assert session resyncs.
|
||||
sessionGets?: string[]
|
||||
preferencesUnavailable?: boolean
|
||||
}
|
||||
|
||||
async function mockServers(
|
||||
@@ -416,11 +414,8 @@ async function mockServers(
|
||||
directory,
|
||||
project: { id: remote ? sessionB.projectID : "project-server-a", directory, canonical: directory },
|
||||
})
|
||||
if (url.pathname === "/api/config/preferences") return json(route, {}, options.preferencesUnavailable ? 404 : 200)
|
||||
if (url.pathname === "/api/config/shell")
|
||||
return json(route, options.preferencesUnavailable ? {} : [], options.preferencesUnavailable ? 404 : 200)
|
||||
if (url.pathname === "/api/websearch/provider")
|
||||
return json(route, { location: { directory }, data: [] }, options.preferencesUnavailable ? 404 : 200)
|
||||
if (url.pathname === "/api/config/shell") return json(route, [])
|
||||
if (url.pathname === "/api/websearch/provider") return json(route, { location: { directory }, data: [] })
|
||||
if (url.pathname === "/api/worktree") return json(route, [{ directory }])
|
||||
if (url.pathname === "/api/vcs")
|
||||
return json(route, { location: { directory }, data: { branch: "main", defaultBranch: "main" } })
|
||||
|
||||
@@ -24,9 +24,6 @@ test.beforeEach(async ({ page }) => {
|
||||
sandboxes,
|
||||
},
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
preferences: { shell: "zsh", websearch: { provider: "exa" } },
|
||||
shells: [{ path: "/bin/zsh", name: "zsh", acceptable: true }],
|
||||
websearchProviders: [{ id: "exa", name: "Exa" }],
|
||||
sessions: sandboxes.map((directory, index) => ({
|
||||
id: `ses_settings_${index + 1}`,
|
||||
title: `Workspace ${index + 1} session`,
|
||||
@@ -78,38 +75,12 @@ test("single-server settings expose scoped pages without a server picker", async
|
||||
await settings.getByRole("tab", { name: "Server", exact: true }).click()
|
||||
await expect(settings.getByRole("button", { name: "Add server", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("heading", { name: "Connection", exact: true })).toBeVisible()
|
||||
await expect(settings.getByRole("heading", { name: "Preferences", exact: true })).toBeVisible()
|
||||
await expect(settings.getByText("Terminal shell", { exact: true })).toBeVisible()
|
||||
await expect(settings.getByText("Third-party search", { exact: true })).toBeVisible()
|
||||
await expect(settings.getByText("zsh", { exact: true })).toBeVisible()
|
||||
await expect(settings.getByText("Exa", { exact: true })).toBeVisible()
|
||||
|
||||
await settings.getByText("Exa", { exact: true }).click()
|
||||
const updated = page.waitForRequest(
|
||||
(request) => request.method() === "PATCH" && new URL(request.url()).pathname === "/api/config/preferences",
|
||||
)
|
||||
await page.getByRole("option", { name: "Any", exact: true }).click()
|
||||
expect((await updated).postDataJSON()).toEqual({ websearch: { provider: "random" } })
|
||||
})
|
||||
|
||||
test("server details tolerate unavailable preference endpoints", async ({ page }) => {
|
||||
await page.route(
|
||||
(url) =>
|
||||
url.pathname === "/api/config/preferences" ||
|
||||
url.pathname === "/api/config/shell" ||
|
||||
url.pathname === "/api/websearch/provider",
|
||||
(route) => route.fulfill({ status: 404, json: {} }),
|
||||
)
|
||||
const settings = page.getByTestId("settings-screen")
|
||||
await settings.getByRole("tab", { name: "Server", exact: true }).click()
|
||||
|
||||
const connection = settings.locator('[data-component="settings-server-connection"]')
|
||||
await expect(connection.getByRole("heading", { name: "Connection", exact: true })).toBeVisible()
|
||||
await expect(connection.locator('[data-component="settings-list"]')).toHaveCSS("padding-left", "16px")
|
||||
await expect(connection.locator(".settings-servers-row")).toHaveCSS("padding-top", "20px")
|
||||
await expect(connection.locator(".settings-servers-lead")).toHaveCSS("column-gap", "4px")
|
||||
await expect(connection.locator(".settings-servers-copy")).toHaveCSS("row-gap", "6px")
|
||||
await expect(page.getByText("Server request failed", { exact: true })).toHaveCount(0)
|
||||
})
|
||||
|
||||
test("project settings open as a nested autosaving view", async ({ page }) => {
|
||||
|
||||
@@ -77,13 +77,6 @@ const Group = HttpApiGroup.make("mock")
|
||||
success: Json,
|
||||
}),
|
||||
)
|
||||
.add(HttpApiEndpoint.get("configPreferences", "/api/config/preferences", { success: Json }))
|
||||
.add(
|
||||
HttpApiEndpoint.patch("configUpdatePreferences", "/api/config/preferences", {
|
||||
payload: JsonPayload,
|
||||
success: Json,
|
||||
}),
|
||||
)
|
||||
.add(HttpApiEndpoint.get("configShells", "/api/config/shell", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("websearchProviders", "/api/websearch/provider", { success: Json }))
|
||||
.add(
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface MockServerConfig {
|
||||
provider: unknown | (() => unknown)
|
||||
integrationMethods?: Record<string, unknown[]>
|
||||
onConnectKey?: (input: { integrationID: string; body: unknown }) => void
|
||||
preferences?: Record<string, unknown>
|
||||
shells?: unknown[]
|
||||
websearchProviders?: unknown[]
|
||||
directory: string
|
||||
@@ -198,7 +197,6 @@ const corsHeaders = {
|
||||
function mockHandlers(config: MockServerConfig, state: { cursors: Map<string, string>; nextCursor: number }) {
|
||||
const noContent = Effect.succeed(HttpApiSchema.NoContent.make())
|
||||
const delay = config.messageDelay === undefined ? Effect.void : Effect.sleep(Duration.millis(config.messageDelay))
|
||||
const preferences = { current: config.preferences ?? {} }
|
||||
return HttpApiBuilder.group(MockApi, "mock", (handlers) =>
|
||||
handlers
|
||||
.handleRaw("event", () => {
|
||||
@@ -285,12 +283,6 @@ function mockHandlers(config: MockServerConfig, state: { cursors: Map<string, st
|
||||
canonical: project.canonical ?? config.directory,
|
||||
})
|
||||
},
|
||||
configPreferences: () => Effect.succeed(preferences.current),
|
||||
configUpdatePreferences: (ctx) =>
|
||||
Effect.sync(() => {
|
||||
preferences.current = { ...preferences.current, ...ctx.payload }
|
||||
return preferences.current
|
||||
}),
|
||||
configShells: () => Effect.succeed(config.shells ?? []),
|
||||
websearchProviders: () => Effect.succeed({ location: location(config), data: config.websearchProviders ?? [] }),
|
||||
worktreeList: () =>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createMemo, createResource, onMount, type Accessor } from "solid-js"
|
||||
import type { ConfigPreferences, ConfigUpdatePreferencesInput } from "@opencode/client/promise"
|
||||
import { createMemo, onMount, type Accessor } from "solid-js"
|
||||
import type { ColorScheme } from "@opencode/ui/theme/context"
|
||||
import { useTheme } from "@opencode/ui/theme/context"
|
||||
import {
|
||||
@@ -16,103 +15,10 @@ import {
|
||||
} from "@/settings/model"
|
||||
import { playSoundById, SOUND_OPTIONS } from "@/shell/notifications/sound"
|
||||
import { createSoundPreviewController } from "./behavior"
|
||||
import { ServerConnection } from "@/runtime/server/registry"
|
||||
import { useServerCtx } from "@/runtime/server/runtime"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { showToast } from "@/shell/notifications/toast"
|
||||
|
||||
export { createShellOptions, createSoundPreviewController } from "./behavior"
|
||||
export type { ShellOption, ShellSelectOption } from "./behavior"
|
||||
|
||||
export function createServerPreferencesController(server: Accessor<ServerConnection.Any>) {
|
||||
const language = useLanguage()
|
||||
const serverCtx = useServerCtx(server)
|
||||
const source = () => ServerConnection.key(server())
|
||||
const [preferences, preferencesActions] = createResource<ConfigPreferences, ServerConnection.Key>(
|
||||
source,
|
||||
() =>
|
||||
serverCtx()
|
||||
.sdk.api.config.preferences()
|
||||
.catch(() => ({})),
|
||||
{ initialValue: {} },
|
||||
)
|
||||
const [shells] = createResource(
|
||||
source,
|
||||
() =>
|
||||
serverCtx()
|
||||
.sdk.api.config.shells()
|
||||
.catch(() => []),
|
||||
{ initialValue: [] },
|
||||
)
|
||||
const [providers] = createResource(
|
||||
source,
|
||||
() =>
|
||||
serverCtx()
|
||||
.sdk.api.websearch.providers()
|
||||
.then((result) => result.data)
|
||||
.catch(() => []),
|
||||
{ initialValue: [] },
|
||||
)
|
||||
|
||||
const update = async (patch: ConfigUpdatePreferencesInput) => {
|
||||
const context = serverCtx()
|
||||
const previous = preferences.latest
|
||||
preferencesActions.mutate({
|
||||
...previous,
|
||||
...(patch.shell === undefined ? {} : { shell: patch.shell ?? undefined }),
|
||||
...(patch.websearch === undefined ? {} : { websearch: patch.websearch ?? undefined }),
|
||||
})
|
||||
await context.sdk.api.config
|
||||
.updatePreferences(patch)
|
||||
.then(preferencesActions.mutate)
|
||||
.catch((error: unknown) => {
|
||||
preferencesActions.mutate(previous)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: error instanceof Error ? error.message : language.t("common.requestFailed"),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const websearchOptions = createMemo(() => {
|
||||
const options = providers.latest.map((provider) => ({ value: provider.id, label: provider.name }))
|
||||
const selected = preferences.latest.websearch
|
||||
const configured = selected && selected.provider !== "random" ? selected.provider : undefined
|
||||
return [
|
||||
{ value: "random" as const, label: language.t("session.websearch.any") },
|
||||
...options,
|
||||
...(configured && !options.some((option) => option.value === configured)
|
||||
? [{ value: configured, label: configured }]
|
||||
: []),
|
||||
{ value: false as const, label: language.t("session.websearch.disable") },
|
||||
]
|
||||
})
|
||||
const websearchCurrent = createMemo(() => {
|
||||
const selection = preferences.latest.websearch
|
||||
const value = selection === false ? false : (selection?.provider ?? "random")
|
||||
return websearchOptions().find((option) => option.value === value) ?? websearchOptions()[0]
|
||||
})
|
||||
|
||||
return {
|
||||
shell: {
|
||||
shells: () => shells.latest,
|
||||
current: () => preferences.latest.shell ?? "",
|
||||
select: (value: string) => {
|
||||
if (value === (preferences.latest.shell ?? "")) return
|
||||
void update({ shell: value || null })
|
||||
},
|
||||
},
|
||||
websearch: {
|
||||
options: websearchOptions,
|
||||
current: websearchCurrent,
|
||||
select: (value: string | false) => {
|
||||
void update({ websearch: value === false ? false : { provider: value } })
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function createAppearanceSettingsController() {
|
||||
const settings = useSettings()
|
||||
const theme = useTheme()
|
||||
@@ -208,6 +114,5 @@ export function createSoundSettingsController() {
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellSettingsController = ReturnType<typeof createServerPreferencesController>["shell"]
|
||||
export type AppearanceSettingsController = ReturnType<typeof createAppearanceSettingsController>
|
||||
export type SoundSettingsController = ReturnType<typeof createSoundSettingsController>
|
||||
|
||||
@@ -18,12 +18,7 @@ import { formatKeybind } from "@/shell/commands/command"
|
||||
import { ExternalLink } from "@/runtime/platform/external-link"
|
||||
import { SettingsList } from "@/settings/list"
|
||||
import { SettingsRow } from "@/settings/row"
|
||||
import {
|
||||
createAppearanceSettingsController,
|
||||
createShellOptions,
|
||||
type AppearanceSettingsController,
|
||||
type ShellSettingsController,
|
||||
} from "./controllers"
|
||||
import { createAppearanceSettingsController, type AppearanceSettingsController } from "./controllers"
|
||||
import "@/settings/settings.css"
|
||||
|
||||
const schemeOptions: ("system" | "light" | "dark")[] = ["system", "light", "dark"]
|
||||
@@ -96,37 +91,6 @@ const WorkspaceDestinationSetting: Component = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export const ShellSetting: Component<{ controller: ShellSettingsController }> = (props) => {
|
||||
const language = useLanguage()
|
||||
const options = createMemo(() =>
|
||||
createShellOptions({
|
||||
shells: props.controller.shells(),
|
||||
current: props.controller.current(),
|
||||
}),
|
||||
)
|
||||
return (
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.shell.title")}
|
||||
description={language.t("settings.general.row.shell.description")}
|
||||
>
|
||||
<Select
|
||||
data-action="settings-shell"
|
||||
options={options()}
|
||||
current={options().find((option) => option.value === props.controller.current()) ?? options()[0]}
|
||||
placement="bottom-end"
|
||||
gutter={6}
|
||||
value={(option) => option.id}
|
||||
label={(option) => {
|
||||
if (option.id === "auto") return language.t("settings.general.row.shell.autoDefault")
|
||||
if (!option.terminalOnly) return option.name
|
||||
return `${option.name} (${language.t("settings.general.row.shell.terminalOnly")})`
|
||||
}}
|
||||
onSelect={(option) => option && props.controller.select(option.value)}
|
||||
/>
|
||||
</SettingsRow>
|
||||
)
|
||||
}
|
||||
|
||||
const TerminalPlacementSetting: Component = () => {
|
||||
const language = useLanguage()
|
||||
const settings = useSettings()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Badge } from "@opencode/ui/badge"
|
||||
import { Select } from "@opencode/ui/select"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { createMemo, Show, type Component } from "solid-js"
|
||||
import { ServerRowMenu } from "@/servers/registry/row-menu"
|
||||
@@ -11,35 +10,9 @@ import { DialogServer } from "@/servers/connect/dialog"
|
||||
import { AddServerMenu, WslServerSettings } from "@/servers/wsl/settings"
|
||||
import { SshServerSettings } from "@/servers/ssh/settings"
|
||||
import { SettingsList } from "@/settings/list"
|
||||
import { SettingsRow } from "@/settings/row"
|
||||
import { ShellSetting } from "@/settings/general/general"
|
||||
import { createServerPreferencesController } from "@/settings/general/controllers"
|
||||
import type { SettingsServer } from "./inventory"
|
||||
import "@/settings/settings.css"
|
||||
|
||||
const WebSearchSetting: Component<{
|
||||
controller: ReturnType<typeof createServerPreferencesController>["websearch"]
|
||||
}> = (props) => {
|
||||
const language = useLanguage()
|
||||
return (
|
||||
<SettingsRow
|
||||
title={language.t("settings.server.preferences.websearch.title")}
|
||||
description={language.t("settings.server.preferences.websearch.description")}
|
||||
>
|
||||
<Select
|
||||
data-action="settings-websearch"
|
||||
options={props.controller.options()}
|
||||
current={props.controller.current()}
|
||||
value={(option) => String(option.value)}
|
||||
label={(option) => option.label}
|
||||
placement="bottom-end"
|
||||
gutter={6}
|
||||
onSelect={(option) => option && props.controller.select(option.value)}
|
||||
/>
|
||||
</SettingsRow>
|
||||
)
|
||||
}
|
||||
|
||||
export const SettingsServerGeneral: Component<{
|
||||
entry: SettingsServer
|
||||
nested?: boolean
|
||||
@@ -116,25 +89,7 @@ export const SettingsServerGeneral: Component<{
|
||||
</Show>
|
||||
</SettingsList>
|
||||
</section>
|
||||
|
||||
<Show when={props.entry.connection} keyed>
|
||||
{(server) => <ServerPreferences server={server} />}
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ServerPreferences(props: { server: ServerConnection.Any }) {
|
||||
const language = useLanguage()
|
||||
const preferences = createServerPreferencesController(() => props.server)
|
||||
return (
|
||||
<section class="settings-section">
|
||||
<h3 class="settings-section-title">{language.t("settings.tab.preferences")}</h3>
|
||||
<SettingsList>
|
||||
<ShellSetting controller={preferences.shell} />
|
||||
<WebSearchSetting controller={preferences.websearch} />
|
||||
</SettingsList>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2307,18 +2307,6 @@ export type ConfigGetInput = { readonly location?: { readonly directory?: string
|
||||
export type ConfigGetOutput = ReadonlyArray<Config.Entry>
|
||||
export type ConfigGetOperation<E = never> = (input?: ConfigGetInput) => Effect.Effect<ConfigGetOutput, E>
|
||||
|
||||
export type ConfigPreferencesOutput = Config.Preferences
|
||||
export type ConfigPreferencesOperation<E = never> = () => Effect.Effect<ConfigPreferencesOutput, E>
|
||||
|
||||
export type ConfigUpdatePreferencesInput = {
|
||||
readonly shell?: string | null | undefined
|
||||
readonly websearch?: false | { readonly provider: "random" | WebSearch.ID } | null | undefined
|
||||
}
|
||||
export type ConfigUpdatePreferencesOutput = Config.Preferences
|
||||
export type ConfigUpdatePreferencesOperation<E = never> = (
|
||||
input?: ConfigUpdatePreferencesInput,
|
||||
) => Effect.Effect<ConfigUpdatePreferencesOutput, E>
|
||||
|
||||
export type ConfigShellsOutput = ReadonlyArray<{
|
||||
readonly path: string
|
||||
readonly name: string
|
||||
@@ -2328,8 +2316,6 @@ export type ConfigShellsOperation<E = never> = () => Effect.Effect<ConfigShellsO
|
||||
|
||||
export interface ConfigApi<E = never> {
|
||||
readonly get: ConfigGetOperation<E>
|
||||
readonly preferences: ConfigPreferencesOperation<E>
|
||||
readonly updatePreferences: ConfigUpdatePreferencesOperation<E>
|
||||
readonly shells: ConfigShellsOperation<E>
|
||||
}
|
||||
|
||||
|
||||
@@ -263,9 +263,6 @@ import type {
|
||||
WebsearchQueryOutput,
|
||||
ConfigGetInput,
|
||||
ConfigGetOutput,
|
||||
ConfigPreferencesOutput,
|
||||
ConfigUpdatePreferencesInput,
|
||||
ConfigUpdatePreferencesOutput,
|
||||
ConfigShellsOutput,
|
||||
} from "../api/api.js"
|
||||
import { ClientError } from "./client-error.js"
|
||||
@@ -1556,23 +1553,11 @@ const EndpointConfigGet = (raw: RawClient["server.config"]) => (input?: ConfigGe
|
||||
raw["config.get"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const EndpointConfigPreferences = (raw: RawClient["server.config"]) => () =>
|
||||
preserveEffect<ConfigPreferencesOutput>()(raw["config.preferences"]({}).pipe(Effect.mapError(mapClientError)))
|
||||
|
||||
const EndpointConfigUpdatePreferences = (raw: RawClient["server.config"]) => (input?: ConfigUpdatePreferencesInput) =>
|
||||
preserveEffect<ConfigUpdatePreferencesOutput>()(
|
||||
raw["config.updatePreferences"]({ payload: { shell: input?.["shell"], websearch: input?.["websearch"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const EndpointConfigShells = (raw: RawClient["server.config"]) => () =>
|
||||
preserveEffect<ConfigShellsOutput>()(raw["config.shells"]({}).pipe(Effect.mapError(mapClientError)))
|
||||
|
||||
const adaptGroupConfig = (raw: RawClient["server.config"]) => ({
|
||||
get: EndpointConfigGet(raw),
|
||||
preferences: EndpointConfigPreferences(raw),
|
||||
updatePreferences: EndpointConfigUpdatePreferences(raw),
|
||||
shells: EndpointConfigShells(raw),
|
||||
})
|
||||
|
||||
|
||||
@@ -259,9 +259,6 @@ import type {
|
||||
WebsearchQueryOutput,
|
||||
ConfigGetInput,
|
||||
ConfigGetOutput,
|
||||
ConfigPreferencesOutput,
|
||||
ConfigUpdatePreferencesInput,
|
||||
ConfigUpdatePreferencesOutput,
|
||||
ConfigShellsOutput,
|
||||
} from "./types.js"
|
||||
import { ClientError } from "./client-error.js"
|
||||
@@ -2155,29 +2152,6 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
preferences: (requestOptions?: RequestOptions) =>
|
||||
request<ConfigPreferencesOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/config/preferences`,
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
updatePreferences: (input?: ConfigUpdatePreferencesInput, requestOptions?: RequestOptions) =>
|
||||
request<ConfigUpdatePreferencesOutput>(
|
||||
{
|
||||
method: "PATCH",
|
||||
path: `/api/config/preferences`,
|
||||
body: { shell: input?.["shell"], websearch: input?.["websearch"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
shells: (requestOptions?: RequestOptions) =>
|
||||
request<ConfigShellsOutput>(
|
||||
{ method: "GET", path: `/api/config/shell`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
|
||||
|
||||
@@ -424,8 +424,6 @@ export type WebSearchResult = { url: string; title?: string; content?: string; t
|
||||
|
||||
export type ConfigWorktree = { directory: string }
|
||||
|
||||
export type ConfigPreferences = { shell?: string; websearch?: false | { provider: "random" | (string & {}) } }
|
||||
|
||||
export type ConfigShellOption = { path: string; name: string; acceptable: boolean }
|
||||
|
||||
export type SessionMessageLocationSwitched = {
|
||||
@@ -6211,19 +6209,4 @@ export type ConfigGetInput = {
|
||||
|
||||
export type ConfigGetOutput = Array<ConfigEntry>
|
||||
|
||||
export type ConfigPreferencesOutput = ConfigPreferences
|
||||
|
||||
export type ConfigUpdatePreferencesInput = {
|
||||
readonly shell?: {
|
||||
readonly shell?: string | null
|
||||
readonly websearch?: false | { readonly provider: "random" | (string & {}) } | null
|
||||
}["shell"]
|
||||
readonly websearch?: {
|
||||
readonly shell?: string | null
|
||||
readonly websearch?: false | { readonly provider: "random" | (string & {}) } | null
|
||||
}["websearch"]
|
||||
}
|
||||
|
||||
export type ConfigUpdatePreferencesOutput = ConfigPreferences
|
||||
|
||||
export type ConfigShellsOutput = Array<ConfigShellOption>
|
||||
|
||||
@@ -3,9 +3,9 @@ export * as Config from "./config.js"
|
||||
import { makeLocationNode } from "@opencode/util/effect/app-node"
|
||||
import path from "path"
|
||||
import { isDeepStrictEqual } from "node:util"
|
||||
import { applyEdits, modify, type ParseError, parse } from "jsonc-parser"
|
||||
import { type ParseError, parse } from "jsonc-parser"
|
||||
import { Context, Effect, FiberMap, Layer, Option, PubSub, Ref, Schema, Semaphore, Stream } from "effect"
|
||||
import { Directory, Document, Info, type Preferences, type PreferencesPatch, type Entry, Event } from "@opencode/schema/config"
|
||||
import { Directory, Document, Info, type Entry, Event } from "@opencode/schema/config"
|
||||
import { Credential } from "./credential.js"
|
||||
import { Bus } from "./bus.js"
|
||||
import { Watcher } from "./filesystem/watcher.js"
|
||||
@@ -38,10 +38,6 @@ export interface Interface {
|
||||
* source files they parse and rebuild their own state.
|
||||
*/
|
||||
readonly changes: () => Stream.Stream<Watcher.Update>
|
||||
/** Returns preferences from the highest-precedence global config document. */
|
||||
readonly preferences?: () => Effect.Effect<Preferences, FSUtil.Error>
|
||||
/** Patches preferences in the highest-precedence global config document. */
|
||||
readonly updatePreferences?: (patch: PreferencesPatch) => Effect.Effect<Preferences, FSUtil.Error>
|
||||
}
|
||||
|
||||
export const Options = Schema.Struct({
|
||||
@@ -88,19 +84,6 @@ export const testLayer = (
|
||||
}),
|
||||
)
|
||||
|
||||
function decodePreferences(text: string): Preferences {
|
||||
const errors: ParseError[] = []
|
||||
const input: unknown = parse(text, errors, { allowTrailingComma: true })
|
||||
if (errors.length) return {}
|
||||
const normalized = ConfigNormalize.normalize(input)
|
||||
if (normalized.type === "rejected") return {}
|
||||
const info = Option.getOrUndefined(Schema.decodeUnknownOption(Info)(normalized.encoded))
|
||||
return {
|
||||
...(info?.shell === undefined ? {} : { shell: info.shell }),
|
||||
...(info?.websearch === undefined ? {} : { websearch: info.websearch }),
|
||||
}
|
||||
}
|
||||
|
||||
export const layer = (options?: Options) =>
|
||||
Layer.effect(
|
||||
Service,
|
||||
@@ -113,7 +96,6 @@ export const layer = (options?: Options) =>
|
||||
const globalService = yield* Global.Service
|
||||
const wellknown = yield* WellKnown.Service
|
||||
const reloadLock = Semaphore.makeUnsafe(1)
|
||||
const updateLock = Semaphore.makeUnsafe(1)
|
||||
const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as const
|
||||
const decodeInfo = Schema.decodeUnknownOption(Info, decodeOptions)
|
||||
const parseInfo = Effect.fn("Config.parseInfo")(function* (text: string, source: string) {
|
||||
@@ -341,43 +323,6 @@ export const layer = (options?: Options) =>
|
||||
)
|
||||
yield* reloadLock.withPermit(reconcile(initial))
|
||||
|
||||
const globalConfigPath = Effect.fn("Config.globalConfigPath")(function* () {
|
||||
const directory = initial.global ?? AbsolutePath.make(globalService.config)
|
||||
const candidates = ConfigDiscovery.names.map((name) => path.join(directory, name))
|
||||
const existing = yield* Effect.filter(candidates, fs.isFile)
|
||||
return existing.at(-1) ?? path.join(directory, "opencode.jsonc")
|
||||
})
|
||||
|
||||
const preferences = Effect.fn("Config.preferences")(function* () {
|
||||
const filepath = yield* globalConfigPath()
|
||||
const text = yield* fs.readFileStringSafe(filepath)
|
||||
return text === undefined ? {} : decodePreferences(text)
|
||||
})
|
||||
|
||||
const updatePreferences = Effect.fn("Config.updatePreferences")(
|
||||
function* (patch: PreferencesPatch) {
|
||||
const filepath = yield* globalConfigPath()
|
||||
const text = (yield* fs.readFileStringSafe(filepath)) ?? "{}\n"
|
||||
const updated = yield* Effect.try({
|
||||
try: () =>
|
||||
(["shell", "websearch"] as const).reduce((content, key) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(patch, key)) return content
|
||||
return applyEdits(
|
||||
content,
|
||||
modify(content, [key], patch[key] === null ? undefined : patch[key], {
|
||||
formattingOptions: { tabSize: 2, insertSpaces: true },
|
||||
}),
|
||||
)
|
||||
}, text),
|
||||
catch: (cause) => new FSUtil.FileSystemError({ method: "config.updatePreferences", cause }),
|
||||
})
|
||||
yield* fs.writeWithDirs(filepath, updated.endsWith("\n") ? updated : `${updated}\n`)
|
||||
yield* requestReload
|
||||
return decodePreferences(updated)
|
||||
},
|
||||
(effect) => updateLock.withPermit(effect),
|
||||
)
|
||||
|
||||
return Service.of({
|
||||
entries: Effect.fnUntraced(function* () {
|
||||
return configs
|
||||
@@ -388,8 +333,6 @@ export const layer = (options?: Options) =>
|
||||
agents: Effect.filter(sources.agents, fs.isDir),
|
||||
}),
|
||||
changes: () => Stream.fromPubSub(updates),
|
||||
preferences,
|
||||
updatePreferences,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -12724,98 +12724,6 @@
|
||||
"summary": "Get configuration"
|
||||
}
|
||||
},
|
||||
"/api/config/preferences": {
|
||||
"get": {
|
||||
"tags": ["config"],
|
||||
"operationId": "config.preferences",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Config.Preferences",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.Preferences"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Return preferences from the highest-precedence global configuration document.",
|
||||
"summary": "Get global preferences"
|
||||
},
|
||||
"patch": {
|
||||
"tags": ["config"],
|
||||
"operationId": "config.updatePreferences",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Config.Preferences",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.Preferences"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Patch preferences in the highest-precedence global configuration document.",
|
||||
"summary": "Update global preferences",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.PreferencesPatch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/config/shell": {
|
||||
"get": {
|
||||
"tags": ["config"],
|
||||
@@ -13686,60 +13594,6 @@
|
||||
"required": ["package"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Preferences": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"websearch": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.PreferencesPatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shell": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"websearch": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.ProviderEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -20,29 +20,6 @@ export const ConfigGroup = HttpApiGroup.make("server.config")
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("config.preferences", "/api/config/preferences", {
|
||||
success: Config.Preferences,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "config.preferences",
|
||||
summary: "Get global preferences",
|
||||
description: "Return preferences from the highest-precedence global configuration document.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.patch("config.updatePreferences", "/api/config/preferences", {
|
||||
payload: Config.PreferencesPatch,
|
||||
success: Config.Preferences,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "config.updatePreferences",
|
||||
summary: "Update global preferences",
|
||||
description: "Patch preferences in the highest-precedence global configuration document.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("config.shells", "/api/config/shell", {
|
||||
success: Schema.Array(ConfigShell.Option),
|
||||
|
||||
@@ -109,18 +109,6 @@ export class Info extends Schema.Class<Info>("Config.Info")({
|
||||
experimental: ConfigExperimental.Info.pipe(optional),
|
||||
}) {}
|
||||
|
||||
export const Preferences = Schema.Struct({
|
||||
shell: Schema.String.pipe(optional),
|
||||
websearch: ConfigWebSearch.Selection.pipe(optional),
|
||||
}).annotate({ identifier: "Config.Preferences" })
|
||||
export interface Preferences extends Schema.Schema.Type<typeof Preferences> {}
|
||||
|
||||
export const PreferencesPatch = Schema.Struct({
|
||||
shell: Schema.NullOr(Schema.String).pipe(optional),
|
||||
websearch: Schema.NullOr(ConfigWebSearch.Selection).pipe(optional),
|
||||
}).annotate({ identifier: "Config.PreferencesPatch" })
|
||||
export interface PreferencesPatch extends Schema.Schema.Type<typeof PreferencesPatch> {}
|
||||
|
||||
export class Document extends Schema.Class<Document>("Config.Document")({
|
||||
type: Schema.Literal("document"),
|
||||
path: AbsolutePath.pipe(optional),
|
||||
|
||||
@@ -7,22 +7,6 @@ import { Api } from "../api"
|
||||
export const ConfigHandler = HttpApiBuilder.group(Api, "server.config", (handlers) =>
|
||||
handlers
|
||||
.handle("config.get", () => Config.Service.use((config) => config.entries()))
|
||||
.handle(
|
||||
"config.preferences",
|
||||
Effect.fn(function* () {
|
||||
const config = yield* Config.Service
|
||||
if (!config.preferences) return yield* Effect.die(new Error("Config preferences are unavailable"))
|
||||
return yield* config.preferences().pipe(Effect.orDie)
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"config.updatePreferences",
|
||||
Effect.fn(function* (ctx) {
|
||||
const config = yield* Config.Service
|
||||
if (!config.updatePreferences) return yield* Effect.die(new Error("Config preference updates are unavailable"))
|
||||
return yield* config.updatePreferences(ctx.payload).pipe(Effect.orDie)
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"config.shells",
|
||||
Effect.fn(function* () {
|
||||
|
||||
@@ -59,46 +59,12 @@ it.live("returns ordered config entries for the requested directory", () =>
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("updates global preferences without replacing unrelated JSONC", () =>
|
||||
it.live("lists available shells", () =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-config-preferences-")))
|
||||
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-config-shells-")))
|
||||
const global = path.join(tmp.path, "global")
|
||||
const config = path.join(global, "opencode.jsonc")
|
||||
yield* Effect.promise(() => fs.mkdir(global, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
fs.writeFile(
|
||||
config,
|
||||
`{
|
||||
// keep this comment
|
||||
"model": "provider/model",
|
||||
"shell": "bash"
|
||||
}
|
||||
`,
|
||||
),
|
||||
)
|
||||
const server = yield* startServer(global)
|
||||
const response = yield* Effect.promise(() =>
|
||||
fetch(new URL("/api/config/preferences", server.base), {
|
||||
method: "PATCH",
|
||||
headers: { ...server.headers, "content-type": "application/json" },
|
||||
body: JSON.stringify({ shell: null, websearch: { provider: "random" } }),
|
||||
}),
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(yield* Effect.promise(() => response.json())).toEqual({ websearch: { provider: "random" } })
|
||||
const text = yield* Effect.promise(() => fs.readFile(config, "utf8"))
|
||||
expect(text).toContain("// keep this comment")
|
||||
expect(text).toContain('"model": "provider/model"')
|
||||
expect(text).not.toContain('"shell"')
|
||||
expect(text).toContain('"websearch"')
|
||||
|
||||
const preferences = yield* Effect.promise(() =>
|
||||
fetch(new URL("/api/config/preferences", server.base), { headers: server.headers }),
|
||||
)
|
||||
expect(preferences.status).toBe(200)
|
||||
expect(yield* Effect.promise(() => preferences.json())).toEqual({ websearch: { provider: "random" } })
|
||||
|
||||
const shells = yield* Effect.promise(() =>
|
||||
fetch(new URL("/api/config/shell", server.base), { headers: server.headers }),
|
||||
)
|
||||
|
||||
@@ -12724,98 +12724,6 @@
|
||||
"summary": "Get configuration"
|
||||
}
|
||||
},
|
||||
"/api/config/preferences": {
|
||||
"get": {
|
||||
"tags": ["config"],
|
||||
"operationId": "config.preferences",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Config.Preferences",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.Preferences"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Return preferences from the highest-precedence global configuration document.",
|
||||
"summary": "Get global preferences"
|
||||
},
|
||||
"patch": {
|
||||
"tags": ["config"],
|
||||
"operationId": "config.updatePreferences",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Config.Preferences",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.Preferences"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Patch preferences in the highest-precedence global configuration document.",
|
||||
"summary": "Update global preferences",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.PreferencesPatch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/config/shell": {
|
||||
"get": {
|
||||
"tags": ["config"],
|
||||
@@ -13686,60 +13594,6 @@
|
||||
"required": ["package"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Preferences": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"websearch": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.PreferencesPatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shell": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"websearch": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.ProviderEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -12724,98 +12724,6 @@
|
||||
"summary": "Get configuration"
|
||||
}
|
||||
},
|
||||
"/api/config/preferences": {
|
||||
"get": {
|
||||
"tags": ["config"],
|
||||
"operationId": "config.preferences",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Config.Preferences",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.Preferences"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Return preferences from the highest-precedence global configuration document.",
|
||||
"summary": "Get global preferences"
|
||||
},
|
||||
"patch": {
|
||||
"tags": ["config"],
|
||||
"operationId": "config.updatePreferences",
|
||||
"parameters": [],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Config.Preferences",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.Preferences"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Patch preferences in the highest-precedence global configuration document.",
|
||||
"summary": "Update global preferences",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Config.PreferencesPatch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/config/shell": {
|
||||
"get": {
|
||||
"tags": ["config"],
|
||||
@@ -13686,60 +13594,6 @@
|
||||
"required": ["package"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Preferences": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"websearch": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.PreferencesPatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"shell": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"websearch": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [false]
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.ProviderEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user