mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-08 00:08:23 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4327a67fa | ||
|
|
6b9f03b13f | ||
|
|
5183ea45c3 | ||
|
|
053e534e5a | ||
|
|
8bd8960154 | ||
|
|
d5154fb74e | ||
|
|
70c6c8cbc5 | ||
|
|
e2979039f1 | ||
|
|
119cb15d1d |
No files matched your search
@@ -33,7 +33,7 @@ const Options = Schema.Struct({
|
||||
store: lenient(Schema.Boolean),
|
||||
thinkingLevel: lenient(ThinkingLevel),
|
||||
thinkingSummaries: lenient(knownString<"auto" | "none">()),
|
||||
serviceTier: lenient(knownString<"standard" | "flex" | "priority">()),
|
||||
serviceTier: lenient(knownString<"standard" | "flex" | "priority" | "deferred">()),
|
||||
})
|
||||
export type OptionsInput = typeof Options.Encoded
|
||||
export type ProviderOptionsInput = OptionsInput
|
||||
|
||||
@@ -9,7 +9,8 @@ export type OpenAITextVerbosity = OpenResponsesOptions.TextVerbosity
|
||||
// in lockstep with `openai-node/src/resources/responses/responses.ts`.
|
||||
export const OpenAIResponseIncludables = OpenResponsesOptions.ResponseIncludables
|
||||
export type OpenAIResponseIncludable = OpenResponsesOptions.ResponseIncludable
|
||||
export const OpenAIServiceTiers = [...OpenResponsesOptions.ServiceTiers, "scale"] as const
|
||||
// Mirrors OpenAI's `ServiceTier` union from the official SDK.
|
||||
export const OpenAIServiceTiers = [...OpenResponsesOptions.ServiceTiers, "scale", "fast", "ultrafast"] as const
|
||||
export type OpenAIServiceTier = (typeof OpenAIServiceTiers)[number] | (string & {})
|
||||
|
||||
export const OpenAIReasoningEffort = OpenResponsesOptions.ReasoningEffort
|
||||
|
||||
@@ -9,6 +9,7 @@ LLM.request({ model: selected, prompt: "Hello", providerOptions: { reasoningEffo
|
||||
LLM.request({ model: selected, prompt: "Hello", providerOptions: { textVerbosity: "low" } })
|
||||
LLM.request({ model: selected, prompt: "Hello", providerOptions: { textVerbosity: "verbose" } })
|
||||
LLM.request({ model: selected, prompt: "Hello", providerOptions: { serviceTier: "scale" } })
|
||||
LLM.request({ model: selected, prompt: "Hello", providerOptions: { serviceTier: "ultrafast" } })
|
||||
LLM.request({ model: selected, prompt: "Hello", providerOptions: { serviceTier: "future-tier" } })
|
||||
LLM.request({ model: chat, prompt: "Hello", providerOptions: { reasoningEffort: "max" } })
|
||||
LLM.request({ model: chat, prompt: "Hello", providerOptions: { reasoningEffort: "experimental" } })
|
||||
|
||||
@@ -325,11 +325,13 @@ describe("OpenAI Responses route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("passes through provider-defined service tiers", () =>
|
||||
it.effect("passes through provider-defined and future service tiers", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(LLMRequest.update(request, { providerOptions: { serviceTier: "scale" } }))
|
||||
for (const serviceTier of ["scale", "ultrafast", "future-tier"]) {
|
||||
const prepared = yield* compileRequest(LLMRequest.update(request, { providerOptions: { serviceTier } }))
|
||||
|
||||
expect(prepared.body.service_tier).toBe("scale")
|
||||
expect(prepared.body.service_tier).toBe(serviceTier)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ test("open file tab browses, searches, and tracks missing files", async ({ page
|
||||
const panel = page.locator("#review-panel")
|
||||
const sidebar = panel.locator('[data-slot="session-review-v2-sidebar"]')
|
||||
const sidebarToggle = panel.getByRole("button", { name: "Toggle file tree" })
|
||||
const contextButton = page.getByRole("button", { name: "View context usage" })
|
||||
const contextButton = page.getByRole("button", { name: "Toggle session context" })
|
||||
const openFile = panel.getByRole("button", { name: "Open file" })
|
||||
const tab = (name: string) => panel.getByRole("tab", { name, exact: true })
|
||||
await contextButton.click()
|
||||
@@ -309,7 +309,7 @@ test("context closes the side region only when its button opened it", async ({ p
|
||||
})
|
||||
const panel = page.locator("#review-panel")
|
||||
const toggle = page.getByRole("button", { name: "Toggle review", exact: true })
|
||||
const contextButton = page.getByRole("button", { name: "View context usage" })
|
||||
const contextButton = page.getByRole("button", { name: "Toggle session context" })
|
||||
const context = panel.getByRole("tab", { name: "Context", exact: true })
|
||||
|
||||
await contextButton.click()
|
||||
@@ -630,7 +630,7 @@ test("restores review state and the side-panel tab per session", async ({ page }
|
||||
await page.getByRole("option", { name: "Branch changes" }).click()
|
||||
await page.getByRole("button", { name: "beta.ts" }).click()
|
||||
await selectedFile("beta.ts")
|
||||
await page.getByRole("button", { name: "View context usage" }).click()
|
||||
await page.getByRole("button", { name: "Toggle session context" }).click()
|
||||
await selectedTab("Context")
|
||||
|
||||
await switchSession("Gamma review state")
|
||||
|
||||
@@ -56,7 +56,6 @@ export function mount(input: { server: string; route: string; direction: Directi
|
||||
return (
|
||||
<AppInterface
|
||||
servers={[server]}
|
||||
defaultServer={ServerConnection.key(server)}
|
||||
canonicalLocalServer={ServerConnection.key(server)}
|
||||
router={(props) => <MemoryRouter {...props} history={history} />}
|
||||
>
|
||||
|
||||
@@ -241,11 +241,7 @@ export function mount(input: {
|
||||
|
||||
return (
|
||||
<Show when={extensions.ready()}>
|
||||
<AppInterface
|
||||
servers={servers()}
|
||||
defaultServer={ServerConnection.Key.make("sidecar")}
|
||||
router={(props) => <MemoryRouter {...props} history={history} />}
|
||||
/>
|
||||
<AppInterface servers={servers()} router={(props) => <MemoryRouter {...props} history={history} />} />
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { render } from "solid-js/web"
|
||||
import type { Bridge } from "@opencode/gui-extensions/sdk/bridge"
|
||||
import { AppBaseProviders, AppInterface } from "../../src/app"
|
||||
import { PlatformProvider, type Platform } from "../../src/runtime/platform/platform"
|
||||
import { ServerConnection } from "../../src/runtime/server/registry"
|
||||
|
||||
// The app on a Windows desktop platform, so the titlebar shows the app menu. Menu actions are listed in an output.
|
||||
export function mount(input: { server: string }) {
|
||||
@@ -59,7 +58,6 @@ export function mount(input: { server: string }) {
|
||||
<output aria-label="Desktop menu actions">{store.actions.join(",")}</output>
|
||||
<AppInterface
|
||||
servers={[{ type: "sidecar", variant: "base", displayName: "Local Server", http: { url: input.server } }]}
|
||||
defaultServer={ServerConnection.Key.make("sidecar")}
|
||||
router={(props) => <MemoryRouter {...props} history={history} />}
|
||||
/>
|
||||
</AppBaseProviders>
|
||||
|
||||
@@ -86,7 +86,6 @@ export function AppBaseProviders(
|
||||
|
||||
export function AppInterface(props: {
|
||||
children?: JSX.Element
|
||||
defaultServer?: ServerConnection.Key
|
||||
canonicalLocalServer?: ServerConnection.Key
|
||||
servers?: Array<ServerConnection.Any>
|
||||
router?: Component<BaseRouterProps>
|
||||
@@ -108,11 +107,7 @@ export function AppInterface(props: {
|
||||
)
|
||||
|
||||
return (
|
||||
<ServersProvider
|
||||
defaultServer={props.defaultServer}
|
||||
canonicalLocalServer={props.canonicalLocalServer}
|
||||
servers={props.servers}
|
||||
>
|
||||
<ServersProvider canonicalLocalServer={props.canonicalLocalServer} servers={props.servers}>
|
||||
<SettingsProvider>
|
||||
<Dynamic component={props.router ?? Router} root={Root}>
|
||||
<AppRoutes />
|
||||
|
||||
@@ -16,7 +16,7 @@ import pkg from "../package.json"
|
||||
import { ServerConnection } from "@/runtime/server/registry"
|
||||
|
||||
const getLocale = () => {
|
||||
if (typeof navigator !== "object") return "en" as const
|
||||
if (typeof navigator === "undefined") return "en" as const
|
||||
const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
|
||||
|
||||
for (const language of languages) {
|
||||
@@ -101,7 +101,6 @@ if (root instanceof HTMLElement && root.dataset.opencodeMounted === undefined) {
|
||||
<PlatformProvider value={web.platform}>
|
||||
<AppBaseProviders locale={locale}>
|
||||
<AppInterface
|
||||
defaultServer={web.defaultServerUrl ? ServerConnection.Key.make(web.defaultServerUrl) : undefined}
|
||||
canonicalLocalServer={server ? ServerConnection.key(server) : undefined}
|
||||
servers={server ? [server] : []}
|
||||
>
|
||||
|
||||
@@ -86,10 +86,6 @@ export function createHomeProjectsController(home: HomeController) {
|
||||
const key = ServerConnection.key(conn)
|
||||
setState("collapsed", key, !state().collapsed[key])
|
||||
},
|
||||
canDefault: serverManagement.defaults.available,
|
||||
defaultKey: serverManagement.defaults.key,
|
||||
setDefault: (conn: ServerConnection.Any | undefined) =>
|
||||
serverManagement.defaults.set(conn ? ServerConnection.key(conn) : null),
|
||||
canRemove: (conn: ServerConnection.Any) => serverManagement.connection.canRemove(ServerConnection.key(conn)),
|
||||
remove: (conn: ServerConnection.Any) => serverManagement.connection.remove(ServerConnection.key(conn)),
|
||||
canHide: (conn: ServerConnection.Any) => serverManagement.connection.canHide(ServerConnection.key(conn)),
|
||||
|
||||
@@ -19,8 +19,6 @@ export function HomeProjects(props: {
|
||||
serverHealth={props.projects.server.health}
|
||||
projectsForServer={props.projects.server.projects}
|
||||
collapsed={props.projects.server.collapsed}
|
||||
canDefaultServer={props.projects.server.canDefault()}
|
||||
defaultServerKey={props.projects.server.defaultKey()}
|
||||
canRevealProject={props.projects.project.canReveal}
|
||||
unseenCount={props.projects.project.unseenCount}
|
||||
onWheel={props.scroll.viewport.containWheel}
|
||||
@@ -29,7 +27,6 @@ export function HomeProjects(props: {
|
||||
onAuthenticateServer={props.projects.server.authenticate}
|
||||
onToggleCollapsed={props.projects.server.toggleCollapsed}
|
||||
onEditServer={props.projects.server.edit}
|
||||
onSetDefaultServer={props.projects.server.setDefault}
|
||||
canRemoveServer={props.projects.server.canRemove}
|
||||
onRemoveServer={props.projects.server.remove}
|
||||
canHideServer={props.projects.server.canHide}
|
||||
|
||||
@@ -52,8 +52,6 @@ export type HomeProjectsViewProps = {
|
||||
serverHealth: (server: ServerConnection.Any) => ServerHealth | undefined
|
||||
projectsForServer: (server: ServerConnection.Any) => LocalProject[]
|
||||
collapsed: (server: ServerConnection.Any) => boolean
|
||||
canDefaultServer: boolean
|
||||
defaultServerKey: ServerConnection.Key | null | undefined
|
||||
canRevealProject: (server: ServerConnection.Any) => boolean
|
||||
unseenCount: (server: ServerConnection.Any, project: LocalProject) => number
|
||||
onWheel: (event: WheelEvent) => void
|
||||
@@ -62,7 +60,6 @@ export type HomeProjectsViewProps = {
|
||||
onAuthenticateServer?: (server: ServerConnection.Any) => void
|
||||
onToggleCollapsed: (server: ServerConnection.Any) => void
|
||||
onEditServer: (server: ServerConnection.Http) => void
|
||||
onSetDefaultServer: (server: ServerConnection.Any | undefined) => void
|
||||
canRemoveServer: (server: ServerConnection.Any) => boolean
|
||||
onRemoveServer: (server: ServerConnection.Any) => void
|
||||
canHideServer: (server: ServerConnection.Any) => boolean
|
||||
@@ -347,12 +344,9 @@ function HomeServerRow(props: {
|
||||
language: HomeProjectsViewProps["language"]
|
||||
projectsForServer: HomeProjectsViewProps["projectsForServer"]
|
||||
contextMenuOpen: HomeProjectsContextMenuProps["contextMenuOpen"]
|
||||
canDefaultServer: HomeProjectsViewProps["canDefaultServer"]
|
||||
defaultServerKey: HomeProjectsViewProps["defaultServerKey"]
|
||||
onFocusServer: HomeProjectsViewProps["onFocusServer"]
|
||||
onToggleCollapsed: HomeProjectsViewProps["onToggleCollapsed"]
|
||||
onEditServer: HomeProjectsViewProps["onEditServer"]
|
||||
onSetDefaultServer: HomeProjectsViewProps["onSetDefaultServer"]
|
||||
canRemoveServer: HomeProjectsViewProps["canRemoveServer"]
|
||||
onRemoveServer: HomeProjectsViewProps["onRemoveServer"]
|
||||
canHideServer: HomeProjectsViewProps["canHideServer"]
|
||||
@@ -470,13 +464,9 @@ function HomeServerRow(props: {
|
||||
<ServerRowMenuView
|
||||
server={props.server}
|
||||
labels={serverMenuLabels(props.language)}
|
||||
canDefault={props.canDefaultServer}
|
||||
isDefault={props.defaultServerKey === ServerConnection.key(props.server)}
|
||||
canRemove={props.canRemoveServer(props.server)}
|
||||
canHide={props.canHideServer(props.server)}
|
||||
onEdit={props.onEditServer}
|
||||
onSetDefault={() => props.onSetDefaultServer(props.server)}
|
||||
onRemoveDefault={() => props.onSetDefaultServer(undefined)}
|
||||
onRemove={() => props.onRemoveServer(props.server)}
|
||||
onHide={() => props.onHideServer(props.server)}
|
||||
open={props.contextMenuOpen(contextMenuID())}
|
||||
|
||||
@@ -7,14 +7,23 @@ import type {
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
import { useServerSDK } from "@/runtime/server/client"
|
||||
import { formatServerError } from "@/runtime/server/errors"
|
||||
import { useData } from "@/runtime/server/current"
|
||||
import { createEffect, createMemo, on, onCleanup } from "solid-js"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
|
||||
export type ProviderConnectMethod = Extract<IntegrationMethod, { type: "key" | "oauth" }>
|
||||
export type ProviderConnectMethod = Extract<IntegrationMethod, { type: "key" | "oauth" | "external" }>
|
||||
|
||||
type Authorization = IntegrationOauthConnectOutput["data"]
|
||||
|
||||
type Polling = {
|
||||
generation: number
|
||||
timer?: ReturnType<typeof setTimeout>
|
||||
disposed: boolean
|
||||
// An attempt the server still considers open; cancelled when the dialog goes away.
|
||||
attempt?: Authorization
|
||||
}
|
||||
|
||||
// OpenCode Go and OpenCode Zen both bill through the OpenCode Console, so the
|
||||
// Console sign-in is the connection method for both providers.
|
||||
export const CONSOLE_INTEGRATION = "opencode"
|
||||
@@ -35,7 +44,7 @@ export function providerFormDefaults(fields: ProviderConnectMethod["form"]) {
|
||||
if (actual === undefined) return false
|
||||
|
||||
const equal = Array.isArray(actual)
|
||||
? typeof condition.value === "string" && actual.includes(condition.value)
|
||||
? actual.some((item) => item === condition.value)
|
||||
: actual === condition.value
|
||||
|
||||
return condition.op === "eq" ? equal : !equal
|
||||
@@ -43,7 +52,7 @@ export function providerFormDefaults(fields: ProviderConnectMethod["form"]) {
|
||||
|
||||
if (!active) return answer
|
||||
|
||||
return { ...answer, [field.key]: field.default }
|
||||
return Object.assign(answer, { [field.key]: field.default })
|
||||
}, {})
|
||||
}
|
||||
|
||||
@@ -74,9 +83,8 @@ export function createProviderConnectionController(options: {
|
||||
|
||||
// Not createResource: the dialog is owned by whichever page opened it, so reading a pending
|
||||
// resource here would suspend that page's <Suspense> and blank the screen behind the dialog.
|
||||
const [integration, setIntegration] = createStore({
|
||||
const [integration, setIntegration] = createStore<{ loading: boolean; latest?: IntegrationInfo }>({
|
||||
loading: true,
|
||||
latest: undefined as IntegrationInfo | undefined,
|
||||
})
|
||||
|
||||
createEffect(
|
||||
@@ -100,7 +108,8 @@ export function createProviderConnectionController(options: {
|
||||
|
||||
const methods = createMemo<ProviderConnectMethod[]>(() => {
|
||||
const values = integration.latest?.methods.filter(
|
||||
(method): method is ProviderConnectMethod => method.type === "key" || method.type === "oauth",
|
||||
(method): method is ProviderConnectMethod =>
|
||||
method.type === "key" || method.type === "oauth" || method.type === "external",
|
||||
)
|
||||
|
||||
if (values?.length) return [...values]
|
||||
@@ -108,14 +117,19 @@ export function createProviderConnectionController(options: {
|
||||
return [{ type: "key", label: language.t("provider.connect.method.apiKey") }]
|
||||
})
|
||||
|
||||
const [store, setStore] = createStore({
|
||||
methodIndex: undefined as number | undefined,
|
||||
authorization: undefined as Authorization | undefined,
|
||||
formAnswer: undefined as FormAnswer | undefined,
|
||||
const [store, setStore] = createStore<{
|
||||
methodIndex?: number
|
||||
authorization?: Authorization
|
||||
formAnswer?: FormAnswer
|
||||
// Nothing is in flight until a method is selected; `busy()` reads this, so a truthy initial
|
||||
// value would keep multi-method providers on the spinner instead of the method list.
|
||||
state: undefined as "pending" | "waiting" | "refreshing" | "ready" | "error" | "form" | undefined,
|
||||
error: undefined as string | undefined,
|
||||
state?: "pending" | "waiting" | "refreshing" | "ready" | "error" | "form"
|
||||
error?: string
|
||||
auto: boolean
|
||||
connected: boolean
|
||||
browserFailed: boolean
|
||||
statusFailed: boolean
|
||||
}>({
|
||||
auto: false,
|
||||
// The credential is stored; a retry only needs to reload the catalogs.
|
||||
connected: false,
|
||||
@@ -124,12 +138,9 @@ export function createProviderConnectionController(options: {
|
||||
statusFailed: false,
|
||||
})
|
||||
|
||||
const polling = {
|
||||
const polling: Polling = {
|
||||
generation: 0,
|
||||
timer: undefined as ReturnType<typeof setTimeout> | undefined,
|
||||
disposed: false,
|
||||
// An attempt the server still considers open; cancelled when the dialog goes away.
|
||||
attempt: undefined as Authorization | undefined,
|
||||
}
|
||||
|
||||
const currentMethod = createMemo(() =>
|
||||
@@ -206,8 +217,6 @@ export function createProviderConnectionController(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = (error: unknown) => (error instanceof Error ? error.message : String(error))
|
||||
|
||||
const cancelAttempt = () => {
|
||||
const attempt = polling.attempt
|
||||
polling.attempt = undefined
|
||||
@@ -276,7 +285,9 @@ export function createProviderConnectionController(options: {
|
||||
setStore("statusFailed", true)
|
||||
dispatch({
|
||||
type: "auth.error",
|
||||
error: isConsole() ? language.t("provider.connect.console.statusFailed") : errorMessage(result.error),
|
||||
error: isConsole()
|
||||
? language.t("provider.connect.console.statusFailed")
|
||||
: formatServerError(result.error, language.t),
|
||||
})
|
||||
|
||||
return
|
||||
@@ -358,8 +369,6 @@ export function createProviderConnectionController(options: {
|
||||
return
|
||||
}
|
||||
|
||||
if (selected.type !== "oauth") return
|
||||
|
||||
if (selected.form?.some((field) => field.type !== "string")) {
|
||||
dispatch({ type: "auth.error", error: language.t("provider.connect.error.unsupportedFields") })
|
||||
|
||||
@@ -368,11 +377,35 @@ export function createProviderConnectionController(options: {
|
||||
|
||||
dispatch({ type: "auth.pending" })
|
||||
|
||||
if (selected.type === "external") {
|
||||
const saved = await serverSDK.api.integration.connect
|
||||
.external({
|
||||
integrationID: options.provider(),
|
||||
methodID: selected.id,
|
||||
answer: Object.keys(merged).length ? merged : undefined,
|
||||
location: location(),
|
||||
})
|
||||
.then(() => ({ ok: true as const }))
|
||||
.catch((error) => ({ ok: false as const, error }))
|
||||
|
||||
if (polling.disposed || generation !== polling.generation) return
|
||||
|
||||
if (!saved.ok) {
|
||||
dispatch({ type: "auth.error", error: formatServerError(saved.error, language.t) })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await finish()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const result = await serverSDK.api.integration.oauth
|
||||
.connect({
|
||||
integrationID: options.provider(),
|
||||
methodID: selected.id,
|
||||
...(Object.keys(merged).length ? { answer: merged } : {}),
|
||||
answer: Object.keys(merged).length ? merged : undefined,
|
||||
location: location(),
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -404,7 +437,9 @@ export function createProviderConnectionController(options: {
|
||||
if (!result.ok) {
|
||||
dispatch({
|
||||
type: "auth.error",
|
||||
error: isConsole() ? language.t("provider.connect.console.startFailed") : errorMessage(result.error),
|
||||
error: isConsole()
|
||||
? language.t("provider.connect.console.startFailed")
|
||||
: formatServerError(result.error, language.t),
|
||||
})
|
||||
|
||||
return
|
||||
@@ -448,7 +483,7 @@ export function createProviderConnectionController(options: {
|
||||
integrationID: options.keyProvider?.() ?? options.provider(),
|
||||
location: location(),
|
||||
key,
|
||||
...(store.formAnswer ? { answer: store.formAnswer } : {}),
|
||||
answer: store.formAnswer,
|
||||
})
|
||||
await finish()
|
||||
}
|
||||
@@ -468,7 +503,8 @@ export function createProviderConnectionController(options: {
|
||||
.then(() => ({ ok: true as const }))
|
||||
.catch((error) => ({ ok: false as const, error }))
|
||||
|
||||
if (!result.ok) return errorMessage(result.error) || language.t("provider.connect.oauth.code.invalid")
|
||||
if (!result.ok)
|
||||
return formatServerError(result.error, language.t, language.t("provider.connect.oauth.code.invalid"))
|
||||
await finish()
|
||||
|
||||
return undefined
|
||||
@@ -496,11 +532,13 @@ export function createProviderConnectionController(options: {
|
||||
authorization: () => store.authorization,
|
||||
browserFailed: () => store.browserFailed,
|
||||
// True while nothing useful can be shown yet: the integration is loading, a method is
|
||||
// about to be picked automatically, or the authorization request is in flight.
|
||||
// about to be picked automatically, the authorization request is in flight, or an external
|
||||
// method, which has no view of its own, is refreshing the catalogs after saving.
|
||||
busy: () =>
|
||||
integration.loading ||
|
||||
(store.methodIndex === undefined && !store.auto && autoIndex() !== undefined) ||
|
||||
store.state === "pending",
|
||||
store.state === "pending" ||
|
||||
(store.state === "refreshing" && currentMethod()?.type === "external"),
|
||||
auth: {
|
||||
state: () => store.state,
|
||||
error: () => store.error,
|
||||
|
||||
@@ -285,9 +285,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "የይለፍ ቃል",
|
||||
"dialog.server.edit.title": "አገልጋይ አርትዕ",
|
||||
"dialog.server.menu.edit": "አርትዕ",
|
||||
"dialog.server.menu.default": "ነባሪ አዘጋጅ",
|
||||
"dialog.server.menu.defaultRemove": "ነባሪውን አስወግድ",
|
||||
"dialog.server.status.default": "ነባሪ",
|
||||
"dialog.project.edit.title": "ፕሮጀክት አርትዕ",
|
||||
"dialog.project.edit.icon": "አዶ",
|
||||
"dialog.project.edit.icon.alt": "የፕሮጀክት አዶ",
|
||||
|
||||
@@ -297,9 +297,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "كلمة المرور (اختياري)",
|
||||
"dialog.server.edit.title": "تحرير الخادم",
|
||||
"dialog.server.menu.edit": "تعديل",
|
||||
"dialog.server.menu.default": "تعيين كافتراضي",
|
||||
"dialog.server.menu.defaultRemove": "إزالة الافتراضي",
|
||||
"dialog.server.status.default": "افتراضي",
|
||||
"dialog.project.edit.title": "تحرير المشروع",
|
||||
"dialog.project.edit.icon": "أيقونة",
|
||||
"dialog.project.edit.icon.alt": "أيقونة المشروع",
|
||||
|
||||
@@ -288,9 +288,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parol",
|
||||
"dialog.server.edit.title": "Serveri redaktə et",
|
||||
"dialog.server.menu.edit": "Redaktə et",
|
||||
"dialog.server.menu.default": "Standart olaraq təyin et",
|
||||
"dialog.server.menu.defaultRemove": "Standartı sil",
|
||||
"dialog.server.status.default": "Standart",
|
||||
"dialog.project.edit.title": "Layihəni redaktə et",
|
||||
"dialog.project.edit.icon": "İkon",
|
||||
"dialog.project.edit.icon.alt": "Layihə ikonu",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "парола",
|
||||
"dialog.server.edit.title": "Редактиране на сървъра",
|
||||
"dialog.server.menu.edit": "Редактиране",
|
||||
"dialog.server.menu.default": "Задайте по подразбиране",
|
||||
"dialog.server.menu.defaultRemove": "Премахване на подразбиране",
|
||||
"dialog.server.status.default": "По подразбиране",
|
||||
"dialog.project.edit.title": "Редактиране на проекта",
|
||||
"dialog.project.edit.icon": "Икона",
|
||||
"dialog.project.edit.icon.alt": "Икона на проекта",
|
||||
|
||||
@@ -290,9 +290,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "পাসওয়ার্ড",
|
||||
"dialog.server.edit.title": "সার্ভার সম্পাদনা করুন",
|
||||
"dialog.server.menu.edit": "সম্পাদনা করুন",
|
||||
"dialog.server.menu.default": "ডিফল্ট হিসেবে সেট করুন",
|
||||
"dialog.server.menu.defaultRemove": "ডিফল্ট সরান",
|
||||
"dialog.server.status.default": "ডিফল্ট",
|
||||
"dialog.project.edit.title": "প্রকল্প সম্পাদনা করুন",
|
||||
"dialog.project.edit.icon": "আইকন",
|
||||
"dialog.project.edit.icon.alt": "প্রকল্প আইকন",
|
||||
|
||||
@@ -508,9 +508,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "Senha (opcional)",
|
||||
"dialog.server.edit.title": "Editar servidor",
|
||||
"dialog.server.menu.edit": "Editar",
|
||||
"dialog.server.menu.default": "Definir como padrão",
|
||||
"dialog.server.menu.defaultRemove": "Remover padrão",
|
||||
"dialog.server.status.default": "Padrão",
|
||||
"dialog.project.edit.title": "Editar projeto",
|
||||
"dialog.project.edit.icon": "Ícone",
|
||||
"dialog.project.edit.icon.alt": "Ícone do projeto",
|
||||
|
||||
@@ -312,9 +312,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Uredi server",
|
||||
|
||||
"dialog.server.menu.edit": "Uredi",
|
||||
"dialog.server.menu.default": "Postavi kao podrazumijevano",
|
||||
"dialog.server.menu.defaultRemove": "Ukloni podrazumijevano",
|
||||
"dialog.server.status.default": "Podrazumijevano",
|
||||
|
||||
"dialog.project.edit.title": "Uredi projekat",
|
||||
"dialog.project.edit.icon": "Ikonica",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "contrasenya",
|
||||
"dialog.server.edit.title": "Edita el servidor",
|
||||
"dialog.server.menu.edit": "Edita",
|
||||
"dialog.server.menu.default": "Estableix com a predeterminat",
|
||||
"dialog.server.menu.defaultRemove": "Elimina el valor predeterminat",
|
||||
"dialog.server.status.default": "Per defecte",
|
||||
"dialog.project.edit.title": "Edita el projecte",
|
||||
"dialog.project.edit.icon": "Icona",
|
||||
"dialog.project.edit.icon.alt": "Icona del projecte",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "heslo",
|
||||
"dialog.server.edit.title": "Upravit server",
|
||||
"dialog.server.menu.edit": "Upravit",
|
||||
"dialog.server.menu.default": "Nastavit jako výchozí",
|
||||
"dialog.server.menu.defaultRemove": "Odebrat výchozí",
|
||||
"dialog.server.status.default": "Výchozí",
|
||||
"dialog.project.edit.title": "Upravit projekt",
|
||||
"dialog.project.edit.icon": "ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projektu",
|
||||
|
||||
@@ -446,9 +446,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Rediger server",
|
||||
|
||||
"dialog.server.menu.edit": "Rediger",
|
||||
"dialog.server.menu.default": "Sæt som standard",
|
||||
"dialog.server.menu.defaultRemove": "Fjern som standard",
|
||||
"dialog.server.status.default": "Standard",
|
||||
|
||||
"dialog.project.edit.title": "Rediger projekt",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
|
||||
@@ -442,9 +442,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "Passwort (optional)",
|
||||
"dialog.server.edit.title": "Server bearbeiten",
|
||||
"dialog.server.menu.edit": "Bearbeiten",
|
||||
"dialog.server.menu.default": "Als Standard festlegen",
|
||||
"dialog.server.menu.defaultRemove": "Standard entfernen",
|
||||
"dialog.server.status.default": "Standard",
|
||||
"dialog.project.edit.title": "Projekt bearbeiten",
|
||||
"dialog.project.edit.icon": "Symbol",
|
||||
"dialog.project.edit.icon.alt": "Projektsymbol",
|
||||
|
||||
@@ -292,9 +292,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "ޕާސްވަރޑް",
|
||||
"dialog.server.edit.title": "އެޑިޓް ސަރވަރ",
|
||||
"dialog.server.menu.edit": "ބަދަލު ގެނައުން",
|
||||
"dialog.server.menu.default": "ޑިފޯލްޓް ގޮތުގައި ސެޓްކުރުން",
|
||||
"dialog.server.menu.defaultRemove": "ޑިފޯލްޓް ނައްތާލާށެވެ",
|
||||
"dialog.server.status.default": "ޑީފޯލްޓް",
|
||||
"dialog.project.edit.title": "އެޑިޓް ޕްރޮޖެކްޓް",
|
||||
"dialog.project.edit.icon": "އައިކޮން",
|
||||
"dialog.project.edit.icon.alt": "ޕްރޮޖެކްޓް އައިކޮން",
|
||||
|
||||
@@ -298,9 +298,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "གསང༌ཨང",
|
||||
"dialog.server.edit.title": "སར་བར་ཞུན་དག་འབད།",
|
||||
"dialog.server.menu.edit": "ཞུན༌དག",
|
||||
"dialog.server.menu.default": "སྔོན་སྒྲིག་སྦེ་གཞི་སྒྲིག་འབད།",
|
||||
"dialog.server.menu.defaultRemove": "སྔོན་སྒྲིག་རྩ་བསྐྲད་གཏང་།",
|
||||
"dialog.server.status.default": "འཐུས་ཤོར",
|
||||
"dialog.project.edit.title": "ཞུན་དག་ལས་གཞི།",
|
||||
"dialog.project.edit.icon": "ངོས་དཔར།",
|
||||
"dialog.project.edit.icon.alt": "ལས་འགུལ་ངོས་དཔར།",
|
||||
|
||||
@@ -288,9 +288,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "κωδικός πρόσβασης",
|
||||
"dialog.server.edit.title": "Επεξεργασία διακομιστή",
|
||||
"dialog.server.menu.edit": "Επεξεργασία",
|
||||
"dialog.server.menu.default": "Ορισμός ως προεπιλογή",
|
||||
"dialog.server.menu.defaultRemove": "Κατάργηση προεπιλογής",
|
||||
"dialog.server.status.default": "Προεπιλογή",
|
||||
"dialog.project.edit.title": "Επεξεργασία έργου",
|
||||
"dialog.project.edit.icon": "Εικονίδιο",
|
||||
"dialog.project.edit.icon.alt": "Εικονίδιο έργου",
|
||||
|
||||
@@ -378,12 +378,9 @@ export const dict = {
|
||||
"Your sign-in for this server is no longer valid. Paste a new pairing link, or enter the server password.",
|
||||
|
||||
"dialog.server.menu.edit": "Edit",
|
||||
"dialog.server.menu.default": "Set as default",
|
||||
"dialog.server.menu.defaultRemove": "Remove default",
|
||||
"dialog.server.menu.remove": "Remove",
|
||||
"dialog.server.menu.hide": "Hide from project list",
|
||||
"dialog.server.menu.show": "Show in project list",
|
||||
"dialog.server.status.default": "Default",
|
||||
|
||||
"server.row.incompatible":
|
||||
"This server is running OpenCode {{version}}, which isn't compatible with this app. Upgrade it to OpenCode V2 to continue.",
|
||||
|
||||
@@ -535,9 +535,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Editar servidor",
|
||||
|
||||
"dialog.server.menu.edit": "Editar",
|
||||
"dialog.server.menu.default": "Establecer como predeterminado",
|
||||
"dialog.server.menu.defaultRemove": "Quitar como predeterminado",
|
||||
"dialog.server.status.default": "Predeterminado",
|
||||
|
||||
"dialog.project.edit.title": "Editar proyecto",
|
||||
"dialog.project.edit.icon": "Icono",
|
||||
|
||||
@@ -285,9 +285,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parool",
|
||||
"dialog.server.edit.title": "Muuda serverit",
|
||||
"dialog.server.menu.edit": "Muuda",
|
||||
"dialog.server.menu.default": "Määra vaikeväärtuseks",
|
||||
"dialog.server.menu.defaultRemove": "Eemalda vaikeseade",
|
||||
"dialog.server.status.default": "Vaikimisi",
|
||||
"dialog.project.edit.title": "Redigeeri projekti",
|
||||
"dialog.project.edit.icon": "Ikoon",
|
||||
"dialog.project.edit.icon.alt": "Projekti ikoon",
|
||||
|
||||
@@ -295,9 +295,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "رمز عبور",
|
||||
"dialog.server.edit.title": "ویرایش سرور",
|
||||
"dialog.server.menu.edit": "ویرایش کنید",
|
||||
"dialog.server.menu.default": "به عنوان پیش فرض تنظیم کنید",
|
||||
"dialog.server.menu.defaultRemove": "حذف پیش فرض",
|
||||
"dialog.server.status.default": "پیش فرض",
|
||||
"dialog.project.edit.title": "ویرایش پروژه",
|
||||
"dialog.project.edit.icon": "نماد",
|
||||
"dialog.project.edit.icon.alt": "نماد پروژه",
|
||||
|
||||
@@ -424,9 +424,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "salasana",
|
||||
"dialog.server.edit.title": "Muokkaa palvelinta",
|
||||
"dialog.server.menu.edit": "Muokkaa",
|
||||
"dialog.server.menu.default": "Aseta oletukseksi",
|
||||
"dialog.server.menu.defaultRemove": "Poista oletuspalvelin",
|
||||
"dialog.server.status.default": "Oletus",
|
||||
"dialog.project.edit.title": "Muokkaa projektia",
|
||||
"dialog.project.edit.icon": "Kuvake",
|
||||
"dialog.project.edit.icon.alt": "Projektikuvake",
|
||||
|
||||
@@ -285,9 +285,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "loyniorð",
|
||||
"dialog.server.edit.title": "Rætta ambætara",
|
||||
"dialog.server.menu.edit": "Rætta",
|
||||
"dialog.server.menu.default": "Set sum forsett",
|
||||
"dialog.server.menu.defaultRemove": "Strika forsett",
|
||||
"dialog.server.status.default": "Forsett",
|
||||
"dialog.project.edit.title": "Rætta verkætlan",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
"dialog.project.edit.icon.alt": "Verkætlanarmerki",
|
||||
|
||||
@@ -516,9 +516,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "Mot de passe (optionnel)",
|
||||
"dialog.server.edit.title": "Modifier le serveur",
|
||||
"dialog.server.menu.edit": "Modifier",
|
||||
"dialog.server.menu.default": "Définir par défaut",
|
||||
"dialog.server.menu.defaultRemove": "Ne plus utiliser par défaut",
|
||||
"dialog.server.status.default": "Défaut",
|
||||
"dialog.project.edit.title": "Modifier le projet",
|
||||
"dialog.project.edit.icon": "Icône",
|
||||
"dialog.project.edit.icon.alt": "Icône du projet",
|
||||
|
||||
@@ -294,9 +294,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "סיסמה",
|
||||
"dialog.server.edit.title": "ערוך שרת",
|
||||
"dialog.server.menu.edit": "ערוך",
|
||||
"dialog.server.menu.default": "הגדר כברירת מחדל",
|
||||
"dialog.server.menu.defaultRemove": "הסר את ברירת המחדל",
|
||||
"dialog.server.status.default": "ברירת מחדל",
|
||||
"dialog.project.edit.title": "ערוך פרויקט",
|
||||
"dialog.project.edit.icon": "סמל",
|
||||
"dialog.project.edit.icon.alt": "סמל פרויקט",
|
||||
|
||||
@@ -292,9 +292,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "पासवर्ड",
|
||||
"dialog.server.edit.title": "सर्वर संपादित करें",
|
||||
"dialog.server.menu.edit": "संपादित करें",
|
||||
"dialog.server.menu.default": "डिफाल्ट के रूप में सेट",
|
||||
"dialog.server.menu.defaultRemove": "डिफ़ॉल्ट हटाएँ",
|
||||
"dialog.server.status.default": "डिफ़ॉल्ट",
|
||||
"dialog.project.edit.title": "प्रोजेक्ट संपादित करें",
|
||||
"dialog.project.edit.icon": "आइकन",
|
||||
"dialog.project.edit.icon.alt": "प्रोजेक्ट आइकन",
|
||||
|
||||
@@ -298,9 +298,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "lozinka",
|
||||
"dialog.server.edit.title": "Uredi poslužitelj",
|
||||
"dialog.server.menu.edit": "Uredi",
|
||||
"dialog.server.menu.default": "Postavi kao zadano",
|
||||
"dialog.server.menu.defaultRemove": "Ukloni zadano",
|
||||
"dialog.server.status.default": "Zadano",
|
||||
"dialog.project.edit.title": "Uredi projekt",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projekta",
|
||||
|
||||
@@ -298,9 +298,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "jelszó",
|
||||
"dialog.server.edit.title": "Szerver szerkesztése",
|
||||
"dialog.server.menu.edit": "Szerkesztés",
|
||||
"dialog.server.menu.default": "Beállítás alapértelmezettként",
|
||||
"dialog.server.menu.defaultRemove": "Alapértelmezés eltávolítása",
|
||||
"dialog.server.status.default": "Alapértelmezett",
|
||||
"dialog.project.edit.title": "Projekt szerkesztése",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
"dialog.project.edit.icon.alt": "Projekt ikonra",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "գաղտնաբառ",
|
||||
"dialog.server.edit.title": "Խմբագրել սերվերը",
|
||||
"dialog.server.menu.edit": "Խմբագրել",
|
||||
"dialog.server.menu.default": "Սահմանել որպես լռելյայն",
|
||||
"dialog.server.menu.defaultRemove": "Հեռացնել լռելյայն",
|
||||
"dialog.server.status.default": "Լռելյայն",
|
||||
"dialog.project.edit.title": "Խմբագրել նախագիծը",
|
||||
"dialog.project.edit.icon": "Պատկերակ",
|
||||
"dialog.project.edit.icon.alt": "Նախագծի պատկերակ",
|
||||
|
||||
@@ -323,9 +323,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Sunting server",
|
||||
|
||||
"dialog.server.menu.edit": "Sunting",
|
||||
"dialog.server.menu.default": "Tetapkan sebagai bawaan",
|
||||
"dialog.server.menu.defaultRemove": "Hapus bawaan",
|
||||
"dialog.server.status.default": "Bawaan",
|
||||
|
||||
"dialog.project.edit.title": "Sunting proyek",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
|
||||
@@ -298,9 +298,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "lykilorð",
|
||||
"dialog.server.edit.title": "Breyta miðlara",
|
||||
"dialog.server.menu.edit": "Breyta",
|
||||
"dialog.server.menu.default": "Stillt sem sjálfgefið",
|
||||
"dialog.server.menu.defaultRemove": "Fjarlægja sjálfgefið",
|
||||
"dialog.server.status.default": "Sjálfgefið",
|
||||
"dialog.project.edit.title": "Breyta verkefni",
|
||||
"dialog.project.edit.icon": "Táknmynd",
|
||||
"dialog.project.edit.icon.alt": "Verkefnistákn",
|
||||
|
||||
@@ -446,9 +446,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "password",
|
||||
"dialog.server.edit.title": "Modifica server",
|
||||
"dialog.server.menu.edit": "Modifica",
|
||||
"dialog.server.menu.default": "Imposta come predefinito",
|
||||
"dialog.server.menu.defaultRemove": "Rimuovi predefinito",
|
||||
"dialog.server.status.default": "Predefinito",
|
||||
"dialog.project.edit.title": "Modifica progetto",
|
||||
"dialog.project.edit.icon": "Icona",
|
||||
"dialog.project.edit.icon.alt": "Icona del progetto",
|
||||
|
||||
@@ -296,9 +296,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "パスワード (オプション)",
|
||||
"dialog.server.edit.title": "サーバーを編集",
|
||||
"dialog.server.menu.edit": "編集",
|
||||
"dialog.server.menu.default": "デフォルトに設定",
|
||||
"dialog.server.menu.defaultRemove": "デフォルト設定を解除",
|
||||
"dialog.server.status.default": "デフォルト",
|
||||
|
||||
"dialog.project.edit.title": "プロジェクトを編集",
|
||||
"dialog.project.edit.icon": "アイコン",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "პაროლი",
|
||||
"dialog.server.edit.title": "სერვერის რედაქტირება",
|
||||
"dialog.server.menu.edit": "რედაქტირება",
|
||||
"dialog.server.menu.default": "დაყენება ნაგულისხმევად",
|
||||
"dialog.server.menu.defaultRemove": "ნაგულისხმევის ამოღება",
|
||||
"dialog.server.status.default": "ნაგულისხმევი",
|
||||
"dialog.project.edit.title": "პროექტის რედაქტირება",
|
||||
"dialog.project.edit.icon": "ხატულა",
|
||||
"dialog.project.edit.icon.alt": "პროექტის ხატულა",
|
||||
|
||||
@@ -293,9 +293,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "ពាក្យសម្ងាត់",
|
||||
"dialog.server.edit.title": "កែសម្រួលម៉ាស៊ីនមេ",
|
||||
"dialog.server.menu.edit": "កែសម្រួល",
|
||||
"dialog.server.menu.default": "កំណត់ជាលំនាំដើម",
|
||||
"dialog.server.menu.defaultRemove": "លុបលំនាំដើមចេញ",
|
||||
"dialog.server.status.default": "លំនាំដើម",
|
||||
"dialog.project.edit.title": "កែសម្រួលគម្រោង",
|
||||
"dialog.project.edit.icon": "រូបតំណាង",
|
||||
"dialog.project.edit.icon.alt": "រូបតំណាងគម្រោង",
|
||||
|
||||
@@ -218,9 +218,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "비밀번호 (선택 사항)",
|
||||
"dialog.server.edit.title": "서버 편집",
|
||||
"dialog.server.menu.edit": "편집",
|
||||
"dialog.server.menu.default": "기본값으로 설정",
|
||||
"dialog.server.menu.defaultRemove": "기본값 제거",
|
||||
"dialog.server.status.default": "기본값",
|
||||
"dialog.project.edit.title": "프로젝트 편집",
|
||||
"dialog.project.edit.icon": "아이콘",
|
||||
"dialog.project.edit.icon.alt": "프로젝트 아이콘",
|
||||
|
||||
@@ -293,9 +293,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "ລະຫັດຜ່ານ",
|
||||
"dialog.server.edit.title": "ແກ້ໄຂເຊີບເວີ",
|
||||
"dialog.server.menu.edit": "ແກ້ໄຂ",
|
||||
"dialog.server.menu.default": "ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ",
|
||||
"dialog.server.menu.defaultRemove": "ເອົາຄ່າເລີ່ມຕົ້ນ",
|
||||
"dialog.server.status.default": "ຄ່າເລີ່ມຕົ້ນ",
|
||||
"dialog.project.edit.title": "ແກ້ໄຂໂຄງການ",
|
||||
"dialog.project.edit.icon": "ໄອຄອນ",
|
||||
"dialog.project.edit.icon.alt": "ໄອຄອນໂຄງການ",
|
||||
|
||||
@@ -299,9 +299,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "slaptažodis",
|
||||
"dialog.server.edit.title": "Redaguoti serverį",
|
||||
"dialog.server.menu.edit": "Redaguoti",
|
||||
"dialog.server.menu.default": "Nustatyti kaip numatytąjį",
|
||||
"dialog.server.menu.defaultRemove": "Pašalinti numatytąjį",
|
||||
"dialog.server.status.default": "Numatytoji",
|
||||
"dialog.project.edit.title": "Redaguoti projektą",
|
||||
"dialog.project.edit.icon": "Piktograma",
|
||||
"dialog.project.edit.icon.alt": "Projekto piktograma",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parole",
|
||||
"dialog.server.edit.title": "Rediģēt serveri",
|
||||
"dialog.server.menu.edit": "Rediģēt",
|
||||
"dialog.server.menu.default": "Iestatīt kā noklusējumu",
|
||||
"dialog.server.menu.defaultRemove": "Noņemt noklusējumu",
|
||||
"dialog.server.status.default": "Noklusējuma",
|
||||
"dialog.project.edit.title": "Rediģēt projektu",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Projekta ikona",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "лозинка",
|
||||
"dialog.server.edit.title": "Уреди сервер",
|
||||
"dialog.server.menu.edit": "Уреди",
|
||||
"dialog.server.menu.default": "Поставете како стандардно",
|
||||
"dialog.server.menu.defaultRemove": "Отстрани стандардно",
|
||||
"dialog.server.status.default": "Стандардно",
|
||||
"dialog.project.edit.title": "Уреди проект",
|
||||
"dialog.project.edit.icon": "Икона",
|
||||
"dialog.project.edit.icon.alt": "Икона на проектот",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "нууц үг",
|
||||
"dialog.server.edit.title": "Сервер засах",
|
||||
"dialog.server.menu.edit": "Засварлах",
|
||||
"dialog.server.menu.default": "Өгөгдмөл болгож тохируулах",
|
||||
"dialog.server.menu.defaultRemove": "Өгөгдмөлийг арилгах",
|
||||
"dialog.server.status.default": "Өгөгдмөл",
|
||||
"dialog.project.edit.title": "Төслийг засварлах",
|
||||
"dialog.project.edit.icon": "Дүрс",
|
||||
"dialog.project.edit.icon.alt": "Төслийн дүрс тэмдэг",
|
||||
|
||||
@@ -295,9 +295,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "kata laluan",
|
||||
"dialog.server.edit.title": "Edit pelayan",
|
||||
"dialog.server.menu.edit": "Sunting",
|
||||
"dialog.server.menu.default": "Tetapkan sebagai lalai",
|
||||
"dialog.server.menu.defaultRemove": "Buang lalai",
|
||||
"dialog.server.status.default": "Lalai",
|
||||
"dialog.project.edit.title": "Edit projek",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
"dialog.project.edit.icon.alt": "Ikon projek",
|
||||
|
||||
@@ -296,9 +296,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "စကားဝှက်",
|
||||
"dialog.server.edit.title": "ဆာဗာကို တည်းဖြတ်ပါ။",
|
||||
"dialog.server.menu.edit": "တည်းဖြတ်ရန်",
|
||||
"dialog.server.menu.default": "မူရင်းအဖြစ် သတ်မှတ်ပါ။",
|
||||
"dialog.server.menu.defaultRemove": "မူရင်းကို ဖယ်ရှားပါ။",
|
||||
"dialog.server.status.default": "မူရင်း",
|
||||
"dialog.project.edit.title": "ပရောဂျက်ကို တည်းဖြတ်ပါ။",
|
||||
"dialog.project.edit.icon": "သင်္ကေတ",
|
||||
"dialog.project.edit.icon.alt": "ပရောဂျက် သင်္ကေတ",
|
||||
|
||||
@@ -291,9 +291,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "पासवर्ड",
|
||||
"dialog.server.edit.title": "सर्भर सम्पादन गर्नुहोस्",
|
||||
"dialog.server.menu.edit": "सम्पादन गर्नुहोस्",
|
||||
"dialog.server.menu.default": "पूर्वनिर्धारित रूपमा सेट गर्नुहोस्",
|
||||
"dialog.server.menu.defaultRemove": "पूर्वनिर्धारित हटाउनुहोस्",
|
||||
"dialog.server.status.default": "पूर्वनिर्धारित",
|
||||
"dialog.project.edit.title": "परियोजना सम्पादन गर्नुहोस्",
|
||||
"dialog.project.edit.icon": "आइकन",
|
||||
"dialog.project.edit.icon.alt": "परियोजना आइकन",
|
||||
|
||||
@@ -498,9 +498,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "wachtwoord",
|
||||
"dialog.server.edit.title": "Server bewerken",
|
||||
"dialog.server.menu.edit": "Bewerken",
|
||||
"dialog.server.menu.default": "Als standaard instellen",
|
||||
"dialog.server.menu.defaultRemove": "Standaard verwijderen",
|
||||
"dialog.server.status.default": "Standaard",
|
||||
"dialog.project.edit.title": "Project bewerken",
|
||||
"dialog.project.edit.icon": "Pictogram",
|
||||
"dialog.project.edit.icon.alt": "Projectpictogram",
|
||||
|
||||
@@ -507,9 +507,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Rediger server",
|
||||
|
||||
"dialog.server.menu.edit": "Rediger",
|
||||
"dialog.server.menu.default": "Sett som standard",
|
||||
"dialog.server.menu.defaultRemove": "Fjern standard",
|
||||
"dialog.server.status.default": "Standard",
|
||||
|
||||
"dialog.project.edit.title": "Rediger prosjekt",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
|
||||
@@ -293,9 +293,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "پاس ورڈ",
|
||||
"dialog.server.edit.title": "سرور وچ ترمیم کرو",
|
||||
"dialog.server.menu.edit": "ترمیم کرو",
|
||||
"dialog.server.menu.default": "ڈیفالٹ دے طور تے سیٹ کرو",
|
||||
"dialog.server.menu.defaultRemove": "ڈیفالٹ ہٹاؤ",
|
||||
"dialog.server.status.default": "طے شدہ",
|
||||
"dialog.project.edit.title": "پروجیکٹ وچ ترمیم کرو",
|
||||
"dialog.project.edit.icon": "آئکن",
|
||||
"dialog.project.edit.icon.alt": "پروجیکٹ آئیکن",
|
||||
|
||||
@@ -289,9 +289,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "Hasło (opcjonalnie)",
|
||||
"dialog.server.edit.title": "Edytuj serwer",
|
||||
"dialog.server.menu.edit": "Edytuj",
|
||||
"dialog.server.menu.default": "Ustaw jako domyślny",
|
||||
"dialog.server.menu.defaultRemove": "Usuń domyślny",
|
||||
"dialog.server.status.default": "Domyślny",
|
||||
"dialog.project.edit.title": "Edytuj projekt",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projektu",
|
||||
|
||||
@@ -285,9 +285,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parolă",
|
||||
"dialog.server.edit.title": "Editează server",
|
||||
"dialog.server.menu.edit": "Editează",
|
||||
"dialog.server.menu.default": "Setează ca implicit",
|
||||
"dialog.server.menu.defaultRemove": "Elimină implicitul",
|
||||
"dialog.server.status.default": "Implicit",
|
||||
"dialog.project.edit.title": "Editează proiectul",
|
||||
"dialog.project.edit.icon": "Pictogramă",
|
||||
"dialog.project.edit.icon.alt": "Pictograma proiectului",
|
||||
|
||||
@@ -311,9 +311,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Редактировать сервер",
|
||||
|
||||
"dialog.server.menu.edit": "Редактировать",
|
||||
"dialog.server.menu.default": "Сделать по умолчанию",
|
||||
"dialog.server.menu.defaultRemove": "Отменить выбор по умолчанию",
|
||||
"dialog.server.status.default": "По умолч.",
|
||||
|
||||
"dialog.project.edit.title": "Редактировать проект",
|
||||
"dialog.project.edit.icon": "Значок",
|
||||
|
||||
@@ -289,9 +289,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "මුරපදය",
|
||||
"dialog.server.edit.title": "සේවාදායකය සංස්කරණය කරන්න",
|
||||
"dialog.server.menu.edit": "සංස්කරණය කරන්න",
|
||||
"dialog.server.menu.default": "පෙරනිමිය ලෙස සකසන්න",
|
||||
"dialog.server.menu.defaultRemove": "පෙරනිමිය ඉවත් කරන්න",
|
||||
"dialog.server.status.default": "පෙරනිමිය",
|
||||
"dialog.project.edit.title": "ව්යාපෘතිය සංස්කරණය කරන්න",
|
||||
"dialog.project.edit.icon": "නිරූපකය",
|
||||
"dialog.project.edit.icon.alt": "ව්යාපෘති නිරූපකය",
|
||||
|
||||
@@ -311,9 +311,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "heslo",
|
||||
"dialog.server.edit.title": "Upraviť server",
|
||||
"dialog.server.menu.edit": "Upraviť",
|
||||
"dialog.server.menu.default": "Nastaviť ako predvolený",
|
||||
"dialog.server.menu.defaultRemove": "Odstrániť predvolený",
|
||||
"dialog.server.status.default": "Predvolený",
|
||||
"dialog.project.edit.title": "Upraviť projekt",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projektu",
|
||||
|
||||
@@ -311,9 +311,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "geslo",
|
||||
"dialog.server.edit.title": "Uredi strežnik",
|
||||
"dialog.server.menu.edit": "Uredi",
|
||||
"dialog.server.menu.default": "Nastavi kot privzeto",
|
||||
"dialog.server.menu.defaultRemove": "Odstrani privzeto",
|
||||
"dialog.server.status.default": "Privzeto",
|
||||
"dialog.project.edit.title": "Uredi projekt",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona projekta",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "fjalëkalimin",
|
||||
"dialog.server.edit.title": "Redakto serverin",
|
||||
"dialog.server.menu.edit": "Redakto",
|
||||
"dialog.server.menu.default": "Cakto si parazgjedhje",
|
||||
"dialog.server.menu.defaultRemove": "Hiq parazgjedhjen",
|
||||
"dialog.server.status.default": "E paracaktuar",
|
||||
"dialog.project.edit.title": "Redakto projektin",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
"dialog.project.edit.icon.alt": "Ikona e projektit",
|
||||
|
||||
@@ -299,9 +299,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "лозинка",
|
||||
"dialog.server.edit.title": "Уреди сервер",
|
||||
"dialog.server.menu.edit": "Уреди",
|
||||
"dialog.server.menu.default": "Подесите као подразумевано",
|
||||
"dialog.server.menu.defaultRemove": "Уклони подразумевано",
|
||||
"dialog.server.status.default": "подразумевано",
|
||||
"dialog.project.edit.title": "Уреди пројекат",
|
||||
"dialog.project.edit.icon": "Икона",
|
||||
"dialog.project.edit.icon.alt": "икона пројекта",
|
||||
|
||||
@@ -491,9 +491,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "lösenord",
|
||||
"dialog.server.edit.title": "Redigera server",
|
||||
"dialog.server.menu.edit": "Redigera",
|
||||
"dialog.server.menu.default": "Ställ in som standard",
|
||||
"dialog.server.menu.defaultRemove": "Ta bort standard",
|
||||
"dialog.server.status.default": "Standard",
|
||||
"dialog.project.edit.title": "Redigera projekt",
|
||||
"dialog.project.edit.icon": "Ikon",
|
||||
"dialog.project.edit.icon.alt": "Projektikonen",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "парол",
|
||||
"dialog.server.edit.title": "Серверро таҳрир кунед",
|
||||
"dialog.server.menu.edit": "Таҳрир",
|
||||
"dialog.server.menu.default": "Ҳамчун пешфарз таъин кунед",
|
||||
"dialog.server.menu.defaultRemove": "Пешфарзро хориҷ кунед",
|
||||
"dialog.server.status.default": "Пешфарз",
|
||||
"dialog.project.edit.title": "Таҳрири лоиҳа",
|
||||
"dialog.project.edit.icon": "Нишона",
|
||||
"dialog.project.edit.icon.alt": "Нишонаи лоиҳа",
|
||||
|
||||
@@ -319,9 +319,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "แก้ไขเซิร์ฟเวอร์",
|
||||
|
||||
"dialog.server.menu.edit": "แก้ไข",
|
||||
"dialog.server.menu.default": "ตั้งเป็นค่าเริ่มต้น",
|
||||
"dialog.server.menu.defaultRemove": "เอาค่าเริ่มต้นออก",
|
||||
"dialog.server.status.default": "ค่าเริ่มต้น",
|
||||
|
||||
"dialog.project.edit.title": "แก้ไขโปรเจกต์",
|
||||
"dialog.project.edit.icon": "ไอคอน",
|
||||
|
||||
@@ -286,9 +286,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parol",
|
||||
"dialog.server.edit.title": "Serweri redaktirläň",
|
||||
"dialog.server.menu.edit": "Redaktirläň",
|
||||
"dialog.server.menu.default": "Dymmaklyk hökmünde düzüň",
|
||||
"dialog.server.menu.defaultRemove": "Dymmaklygy aýyryň",
|
||||
"dialog.server.status.default": "Bellenen",
|
||||
"dialog.project.edit.title": "Taslamany redaktirläň",
|
||||
"dialog.project.edit.icon": "Nyşan",
|
||||
"dialog.project.edit.icon.alt": "Taslamanyň nyşany",
|
||||
|
||||
@@ -324,9 +324,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Sunucuyu düzenle",
|
||||
|
||||
"dialog.server.menu.edit": "Düzenle",
|
||||
"dialog.server.menu.default": "Varsayılan olarak ayarla",
|
||||
"dialog.server.menu.defaultRemove": "Varsayılanı kaldır",
|
||||
"dialog.server.status.default": "Varsayılan",
|
||||
|
||||
"dialog.project.edit.title": "Projeyi düzenle",
|
||||
"dialog.project.edit.icon": "Simge",
|
||||
|
||||
@@ -341,9 +341,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "Редагувати сервер",
|
||||
|
||||
"dialog.server.menu.edit": "Редагувати",
|
||||
"dialog.server.menu.default": "Встановити за замовчуванням",
|
||||
"dialog.server.menu.defaultRemove": "Скасувати використання за замовчуванням",
|
||||
"dialog.server.status.default": "За замовчуванням",
|
||||
|
||||
"dialog.project.edit.title": "Редагувати проєкт",
|
||||
"dialog.project.edit.icon": "Іконка",
|
||||
|
||||
@@ -295,9 +295,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "پاس ورڈ",
|
||||
"dialog.server.edit.title": "سرور میں ترمیم کریں۔",
|
||||
"dialog.server.menu.edit": "ترمیم کریں۔",
|
||||
"dialog.server.menu.default": "بطور ڈیفالٹ سیٹ کریں۔",
|
||||
"dialog.server.menu.defaultRemove": "ڈیفالٹ کو ہٹا دیں۔",
|
||||
"dialog.server.status.default": "طے شدہ",
|
||||
"dialog.project.edit.title": "پروجیکٹ میں ترمیم کریں۔",
|
||||
"dialog.project.edit.icon": "آئیکن",
|
||||
"dialog.project.edit.icon.alt": "پروجیکٹ کا آئیکن",
|
||||
|
||||
@@ -287,9 +287,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "parol",
|
||||
"dialog.server.edit.title": "Serverni tahrirlash",
|
||||
"dialog.server.menu.edit": "Tahrirlash",
|
||||
"dialog.server.menu.default": "Standart sifatida o'rnating",
|
||||
"dialog.server.menu.defaultRemove": "Standartni olib tashlang",
|
||||
"dialog.server.status.default": "Standart",
|
||||
"dialog.project.edit.title": "Loyihani tahrirlash",
|
||||
"dialog.project.edit.icon": "Belgi",
|
||||
"dialog.project.edit.icon.alt": "Loyiha belgisi",
|
||||
|
||||
@@ -299,9 +299,6 @@ export const dict = {
|
||||
"dialog.server.add.passwordPlaceholder": "mật khẩu",
|
||||
"dialog.server.edit.title": "Chỉnh sửa máy chủ",
|
||||
"dialog.server.menu.edit": "Chỉnh sửa",
|
||||
"dialog.server.menu.default": "Đặt làm mặc định",
|
||||
"dialog.server.menu.defaultRemove": "Xóa mặc định",
|
||||
"dialog.server.status.default": "Mặc định",
|
||||
"dialog.project.edit.title": "Chỉnh sửa dự án",
|
||||
"dialog.project.edit.icon": "Biểu tượng",
|
||||
"dialog.project.edit.icon.alt": "Biểu tượng dự án",
|
||||
|
||||
@@ -336,9 +336,6 @@ export const dict = {
|
||||
"dialog.server.add.password": "密码(可选)",
|
||||
"dialog.server.edit.title": "编辑服务器",
|
||||
"dialog.server.menu.edit": "编辑",
|
||||
"dialog.server.menu.default": "设为默认",
|
||||
"dialog.server.menu.defaultRemove": "取消默认",
|
||||
"dialog.server.status.default": "默认",
|
||||
|
||||
"dialog.project.edit.title": "编辑项目",
|
||||
"dialog.project.edit.icon": "图标",
|
||||
|
||||
@@ -321,9 +321,6 @@ export const dict = {
|
||||
"dialog.server.edit.title": "編輯伺服器",
|
||||
|
||||
"dialog.server.menu.edit": "編輯",
|
||||
"dialog.server.menu.default": "設為預設",
|
||||
"dialog.server.menu.defaultRemove": "取消預設",
|
||||
"dialog.server.status.default": "預設",
|
||||
|
||||
"dialog.project.edit.title": "編輯專案",
|
||||
"dialog.project.edit.icon": "圖示",
|
||||
|
||||
@@ -2,7 +2,6 @@ import { createSimpleContext } from "@opencode/ui/context"
|
||||
import type { AsyncStorage, SyncStorage } from "@solid-primitives/storage"
|
||||
import type { Accessor } from "solid-js"
|
||||
import type { DesktopMenuAction } from "@/shell/commands/desktop-menu"
|
||||
import { ServerConnection } from "@/runtime/server/registry"
|
||||
import type { DraftStore } from "@/runtime/persistence/drafts"
|
||||
import type { Bridge } from "@opencode/gui-extensions/sdk/bridge"
|
||||
|
||||
@@ -81,12 +80,6 @@ type PlatformBase = {
|
||||
/** Fetch override */
|
||||
fetch?: typeof fetch
|
||||
|
||||
/** Get the configured default server URL (platform-specific) */
|
||||
getDefaultServer?(): Promise<ServerConnection.Key | null>
|
||||
|
||||
/** Set the default server URL to use on app startup (platform-specific) */
|
||||
setDefaultServer?(url: ServerConnection.Key | null): Promise<void> | void
|
||||
|
||||
/** Webview zoom level (desktop only) */
|
||||
webviewZoom?: Accessor<number>
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { createBrowserDraftStore } from "@/runtime/persistence/drafts"
|
||||
import { ServerConnection } from "@/runtime/server/registry"
|
||||
import type { Platform } from "./platform"
|
||||
|
||||
const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl"
|
||||
|
||||
export function createWebPlatform(version: string) {
|
||||
const currentServerUrl = getCurrentServerUrl()
|
||||
const storedServerUrl = readDefaultServerUrl()
|
||||
|
||||
const platform: Platform = {
|
||||
platform: "web",
|
||||
draftStore: createBrowserDraftStore(),
|
||||
@@ -43,18 +37,11 @@ export function createWebPlatform(version: string) {
|
||||
notification.close()
|
||||
}
|
||||
},
|
||||
getDefaultServer: async () => {
|
||||
const stored = readDefaultServerUrl()
|
||||
|
||||
return stored ? ServerConnection.Key.make(stored) : null
|
||||
},
|
||||
setDefaultServer: writeDefaultServerUrl,
|
||||
}
|
||||
|
||||
return {
|
||||
platform,
|
||||
currentServerUrl,
|
||||
defaultServerUrl: storedServerUrl ?? currentServerUrl,
|
||||
currentServerUrl: getCurrentServerUrl(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,29 +59,3 @@ function getCurrentServerUrl() {
|
||||
|
||||
return location.origin
|
||||
}
|
||||
|
||||
function readDefaultServerUrl() {
|
||||
if (typeof localStorage === "undefined") return null
|
||||
|
||||
try {
|
||||
return localStorage.getItem(DEFAULT_SERVER_URL_KEY)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function writeDefaultServerUrl(value: string | null) {
|
||||
if (typeof localStorage === "undefined") return
|
||||
|
||||
try {
|
||||
if (value !== null) {
|
||||
localStorage.setItem(DEFAULT_SERVER_URL_KEY, value)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
localStorage.removeItem(DEFAULT_SERVER_URL_KEY)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -214,11 +214,7 @@ export namespace ServerConnection {
|
||||
export const { use: useServers, provider: ServersProvider } = createSimpleContext({
|
||||
name: "Server",
|
||||
gate: true,
|
||||
init: (props: {
|
||||
defaultServer?: ServerConnection.Key
|
||||
canonicalLocalServer?: ServerConnection.Key
|
||||
servers?: Array<ServerConnection.Any>
|
||||
}) => {
|
||||
init: (props: { canonicalLocalServer?: ServerConnection.Key; servers?: Array<ServerConnection.Any> }) => {
|
||||
const [store, setStore, _, hydrated] = persisted(
|
||||
{
|
||||
...Persist.global("server"),
|
||||
|
||||
@@ -5,21 +5,19 @@ import { sortServerConnections } from "./controller"
|
||||
const server = (url: string): ServerConnection.Http => ({ type: "http", http: { url } })
|
||||
|
||||
describe("sortServerConnections", () => {
|
||||
test("places the default first and preserves health and insertion ordering", () => {
|
||||
test("orders by health and preserves insertion ordering", () => {
|
||||
const first = server("http://first")
|
||||
const offline = server("http://offline")
|
||||
const preferred = server("http://preferred")
|
||||
const unknown = server("http://unknown")
|
||||
|
||||
const result = sortServerConnections({
|
||||
servers: [first, offline, preferred, unknown],
|
||||
servers: [first, offline, unknown],
|
||||
health: {
|
||||
[ServerConnection.key(first)]: { healthy: true },
|
||||
[ServerConnection.key(offline)]: { healthy: false },
|
||||
},
|
||||
defaultKey: ServerConnection.key(preferred),
|
||||
})
|
||||
|
||||
expect(result).toEqual([preferred, first, unknown, offline])
|
||||
expect(result).toEqual([first, unknown, offline])
|
||||
})
|
||||
})
|
||||
@@ -1,58 +1,15 @@
|
||||
import { createMemo, createResource } from "solid-js"
|
||||
import { createMemo } from "solid-js"
|
||||
import { useGlobal } from "@/runtime/server/runtime"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
import { ServerConnection, useServers } from "@/runtime/server/registry"
|
||||
import { useTabs } from "@/shell/tabs/tabs"
|
||||
import { type ServerHealth } from "@/runtime/server/health"
|
||||
import { showToast } from "@/shell/notifications/toast"
|
||||
import { useExtensionServers } from "@/runtime/extension/servers"
|
||||
|
||||
function showRequestError(language: ReturnType<typeof useLanguage>, err: unknown) {
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
|
||||
function useDefaultServer() {
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
|
||||
const [defaultKey, defaultKeyActions] = createResource(
|
||||
async () => {
|
||||
try {
|
||||
return (await platform.getDefaultServer?.()) ?? null
|
||||
} catch (err) {
|
||||
showRequestError(language, err)
|
||||
|
||||
return null
|
||||
}
|
||||
},
|
||||
{ initialValue: null },
|
||||
)
|
||||
|
||||
const set = async (key: ServerConnection.Key | null) => {
|
||||
try {
|
||||
await platform.setDefaultServer?.(key)
|
||||
defaultKeyActions.mutate(key)
|
||||
} catch (err) {
|
||||
showRequestError(language, err)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key: () => defaultKey.latest,
|
||||
available: createMemo(() => !!platform.getDefaultServer && !!platform.setDefaultServer),
|
||||
set,
|
||||
}
|
||||
}
|
||||
|
||||
export function sortServerConnections(input: {
|
||||
servers: ServerConnection.Any[]
|
||||
health: Record<string, ServerHealth | undefined>
|
||||
defaultKey: ServerConnection.Key | null
|
||||
}) {
|
||||
const order = new Map(input.servers.map((item, index) => [item, index] as const))
|
||||
|
||||
@@ -65,10 +22,6 @@ export function sortServerConnections(input: {
|
||||
}
|
||||
|
||||
return input.servers.slice().sort((a, b) => {
|
||||
const preferred =
|
||||
Number(ServerConnection.key(b) === input.defaultKey) - Number(ServerConnection.key(a) === input.defaultKey)
|
||||
|
||||
if (preferred !== 0) return preferred
|
||||
const health = rank(input.health[ServerConnection.key(a)]) - rank(input.health[ServerConnection.key(b)])
|
||||
|
||||
if (health !== 0) return health
|
||||
@@ -81,24 +34,23 @@ export function useServerActionsController() {
|
||||
const server = useServers()
|
||||
const extensions = useExtensionServers()
|
||||
const tabs = useTabs()
|
||||
const platform = usePlatform()
|
||||
const language = useLanguage()
|
||||
const defaults = useDefaultServer()
|
||||
|
||||
const remove = async (key: ServerConnection.Key) => {
|
||||
try {
|
||||
await extensions.entry(key)?.entry.remove?.()
|
||||
tabs.removeServer(key)
|
||||
server.remove(key)
|
||||
|
||||
if ((await platform.getDefaultServer?.()) === key) await defaults.set(null)
|
||||
} catch (err) {
|
||||
showRequestError(language, err)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
defaults,
|
||||
connection: {
|
||||
canRemove: server.canRemove,
|
||||
remove,
|
||||
@@ -126,7 +78,6 @@ export function useServerCollectionController() {
|
||||
sortServerConnections({
|
||||
servers: items(),
|
||||
health: global.servers.health,
|
||||
defaultKey: actions.defaults.key(),
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -23,11 +23,6 @@ export function ExtensionServerRow(props: { server: ServerConnection.Key; contro
|
||||
authenticationRequired={indicator.auth}
|
||||
/>
|
||||
),
|
||||
default: {
|
||||
available: () => props.controller.defaults.available(),
|
||||
current: () => props.controller.defaults.key() === props.server,
|
||||
set: (value) => void props.controller.defaults.set(value ? props.server : null),
|
||||
},
|
||||
remove: () => props.controller.connection.remove(props.server),
|
||||
Items: () => <ServerRowItems server={props.server} />,
|
||||
}
|
||||
|
||||
@@ -20,14 +20,10 @@ export const ServerRowMenu: Component<{
|
||||
<ServerRowMenuView
|
||||
server={props.server}
|
||||
labels={serverMenuLabels(language)}
|
||||
canDefault={props.domain.defaults.available()}
|
||||
isDefault={props.domain.defaults.key() === key}
|
||||
canRemove={props.domain.connection.canRemove(key)}
|
||||
canHide={props.domain.connection.canHide(key)}
|
||||
hidden={props.domain.connection.isHidden(key)}
|
||||
onEdit={props.onEdit}
|
||||
onSetDefault={() => props.domain.defaults.set(key)}
|
||||
onRemoveDefault={() => props.domain.defaults.set(null)}
|
||||
onRemove={() => props.domain.connection.remove(key)}
|
||||
onHide={() => props.domain.connection.setHidden(key, true)}
|
||||
onShow={() => props.domain.connection.setHidden(key, false)}
|
||||
@@ -42,8 +38,6 @@ export function serverMenuLabels(language: ReturnType<typeof useLanguage>) {
|
||||
more: language.t("common.moreOptions"),
|
||||
server: language.t("settings.section.server"),
|
||||
edit: language.t("dialog.server.menu.edit"),
|
||||
default: language.t("dialog.server.menu.default"),
|
||||
defaultRemove: language.t("dialog.server.menu.defaultRemove"),
|
||||
remove: language.t("dialog.server.menu.remove"),
|
||||
hide: language.t("dialog.server.menu.hide"),
|
||||
show: language.t("dialog.server.menu.show"),
|
||||
@@ -53,14 +47,10 @@ export function serverMenuLabels(language: ReturnType<typeof useLanguage>) {
|
||||
export const ServerRowMenuView: Component<{
|
||||
server: ServerConnection.Any
|
||||
labels: ReturnType<typeof serverMenuLabels>
|
||||
canDefault: boolean
|
||||
isDefault: boolean
|
||||
canRemove: boolean
|
||||
canHide?: boolean
|
||||
hidden?: boolean
|
||||
onEdit: (server: ServerConnection.Http) => void
|
||||
onSetDefault: () => void
|
||||
onRemoveDefault: () => void
|
||||
onRemove: () => void
|
||||
onHide?: () => void
|
||||
onShow?: () => void
|
||||
@@ -93,12 +83,6 @@ export const ServerRowMenuView: Component<{
|
||||
>
|
||||
{props.labels.edit}
|
||||
</Menu.Item>
|
||||
<Show when={props.canDefault && !props.isDefault}>
|
||||
<Menu.Item onSelect={props.onSetDefault}>{props.labels.default}</Menu.Item>
|
||||
</Show>
|
||||
<Show when={props.canDefault && props.isDefault}>
|
||||
<Menu.Item onSelect={props.onRemoveDefault}>{props.labels.defaultRemove}</Menu.Item>
|
||||
</Show>
|
||||
<Show when={props.hidden}>
|
||||
<Menu.Item onSelect={props.onShow}>{props.labels.show}</Menu.Item>
|
||||
</Show>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Badge } from "@opencode/ui/badge"
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { createMemo, Show, type Component } from "solid-js"
|
||||
@@ -82,9 +81,6 @@ export const SettingsServerGeneral: Component<{
|
||||
</Button>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={controller.defaults.available() && controller.defaults.key() === props.entry.key}>
|
||||
<Badge>{language.t("dialog.server.status.default")}</Badge>
|
||||
</Show>
|
||||
<ServerRowMenu server={server()} domain={controller} onEdit={edit} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -248,13 +248,7 @@ function RootSettings() {
|
||||
const ordered = createMemo(() => {
|
||||
const order = new Map(list().map((server, index) => [ServerConnection.key(server), index]))
|
||||
|
||||
return inventory().toSorted((a, b) => {
|
||||
const preferred = Number(b.key === servers.defaults.key()) - Number(a.key === servers.defaults.key())
|
||||
|
||||
if (preferred) return preferred
|
||||
|
||||
return (order.get(a.key) ?? list().length) - (order.get(b.key) ?? list().length)
|
||||
})
|
||||
return inventory().toSorted((a, b) => (order.get(a.key) ?? list().length) - (order.get(b.key) ?? list().length))
|
||||
})
|
||||
|
||||
const sourceServer = createMemo(() => {
|
||||
|
||||
@@ -127,9 +127,29 @@ const authenticate = Effect.fn("cli.auth.login.authenticate")(function* (
|
||||
) {
|
||||
if (method.type === "key") return yield* keyLogin(client, integration, method, answer)
|
||||
if (method.type === "command") return yield* commandLogin(client, integration, method)
|
||||
if (method.type === "external") return yield* externalLogin(client, integration, method, answer)
|
||||
return yield* oauthLogin(client, integration, method, answer)
|
||||
})
|
||||
|
||||
const externalLogin = Effect.fn("cli.auth.login.external")(function* (
|
||||
client: OpenCodeClient,
|
||||
integration: IntegrationInfo,
|
||||
method: Extract<ConnectMethod, { type: "external" }>,
|
||||
answer?: FormAnswer,
|
||||
) {
|
||||
const progress = spinner()
|
||||
progress.start("Saving credential...")
|
||||
yield* request((signal) =>
|
||||
client.integration.connect.external(
|
||||
{ integrationID: integration.id, methodID: method.id, answer, location },
|
||||
{ signal },
|
||||
),
|
||||
).pipe(
|
||||
Effect.tap(() => Effect.sync(() => progress.stop(`Connected to ${integration.name}`))),
|
||||
Effect.tapCause(() => Effect.sync(() => progress.stop("Authentication failed", 1))),
|
||||
)
|
||||
})
|
||||
|
||||
const keyLogin = Effect.fn("cli.auth.login.key")(function* (
|
||||
client: OpenCodeClient,
|
||||
integration: IntegrationInfo,
|
||||
|
||||
@@ -1597,6 +1597,18 @@ export type IntegrationConnectKeyOperation<E = never> = (
|
||||
input: IntegrationConnectKeyInput,
|
||||
) => Effect.Effect<IntegrationConnectKeyOutput, E>
|
||||
|
||||
export type IntegrationConnectExternalInput = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
readonly methodID: Integration.MethodID
|
||||
readonly answer?: Form.Answer | undefined
|
||||
readonly label?: string | undefined
|
||||
}
|
||||
export type IntegrationConnectExternalOutput = void
|
||||
export type IntegrationConnectExternalOperation<E = never> = (
|
||||
input: IntegrationConnectExternalInput,
|
||||
) => Effect.Effect<IntegrationConnectExternalOutput, E>
|
||||
|
||||
export type IntegrationOauthConnectInput = {
|
||||
readonly integrationID: Integration.ID
|
||||
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||
@@ -1687,7 +1699,10 @@ export interface IntegrationApi<E = never> {
|
||||
readonly list: IntegrationListOperation<E>
|
||||
readonly get: IntegrationGetOperation<E>
|
||||
readonly wellknown: { readonly add: IntegrationWellknownAddOperation<E> }
|
||||
readonly connect: { readonly key: IntegrationConnectKeyOperation<E> }
|
||||
readonly connect: {
|
||||
readonly key: IntegrationConnectKeyOperation<E>
|
||||
readonly external: IntegrationConnectExternalOperation<E>
|
||||
}
|
||||
readonly oauth: {
|
||||
readonly connect: IntegrationOauthConnectOperation<E>
|
||||
readonly status: IntegrationOauthStatusOperation<E>
|
||||
|
||||
@@ -127,6 +127,8 @@ import type {
|
||||
IntegrationWellknownAddOutput,
|
||||
IntegrationConnectKeyInput,
|
||||
IntegrationConnectKeyOutput,
|
||||
IntegrationConnectExternalInput,
|
||||
IntegrationConnectExternalOutput,
|
||||
IntegrationOauthConnectInput,
|
||||
IntegrationOauthConnectOutput,
|
||||
IntegrationOauthStatusInput,
|
||||
@@ -897,6 +899,16 @@ const EndpointIntegrationConnectKey = (raw: RawClient["server.integration"]) =>
|
||||
}).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const EndpointIntegrationConnectExternal =
|
||||
(raw: RawClient["server.integration"]) => (input: IntegrationConnectExternalInput) =>
|
||||
preserveEffect<IntegrationConnectExternalOutput>()(
|
||||
raw["integration.connect.external"]({
|
||||
params: { integrationID: input["integrationID"] },
|
||||
query: { location: input["location"] },
|
||||
payload: { methodID: input["methodID"], answer: input["answer"], label: input["label"] },
|
||||
}).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const EndpointIntegrationOauthConnect =
|
||||
(raw: RawClient["server.integration"]) => (input: IntegrationOauthConnectInput) =>
|
||||
preserveEffect<IntegrationOauthConnectOutput>()(
|
||||
@@ -965,7 +977,7 @@ const adaptGroupIntegration = (raw: RawClient["server.integration"]) => ({
|
||||
list: EndpointIntegrationList(raw),
|
||||
get: EndpointIntegrationGet(raw),
|
||||
wellknown: { add: EndpointIntegrationWellknownAdd(raw) },
|
||||
connect: { key: EndpointIntegrationConnectKey(raw) },
|
||||
connect: { key: EndpointIntegrationConnectKey(raw), external: EndpointIntegrationConnectExternal(raw) },
|
||||
oauth: {
|
||||
connect: EndpointIntegrationOauthConnect(raw),
|
||||
status: EndpointIntegrationOauthStatus(raw),
|
||||
|
||||
@@ -121,6 +121,8 @@ import type {
|
||||
IntegrationWellknownAddOutput,
|
||||
IntegrationConnectKeyInput,
|
||||
IntegrationConnectKeyOutput,
|
||||
IntegrationConnectExternalInput,
|
||||
IntegrationConnectExternalOutput,
|
||||
IntegrationOauthConnectInput,
|
||||
IntegrationOauthConnectOutput,
|
||||
IntegrationOauthStatusInput,
|
||||
@@ -1228,6 +1230,19 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
external: (input: IntegrationConnectExternalInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationConnectExternalOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/external`,
|
||||
query: { location: input["location"] },
|
||||
body: { methodID: input["methodID"], answer: input["answer"], label: input["label"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
oauth: {
|
||||
connect: (input: IntegrationOauthConnectInput, requestOptions?: RequestOptions) =>
|
||||
|
||||
@@ -2314,6 +2314,8 @@ export type IntegrationOAuthMethod = { id: string; type: "oauth"; label: string;
|
||||
|
||||
export type IntegrationKeyMethod = { type: "key"; label?: string; form?: FormFields }
|
||||
|
||||
export type IntegrationExternalMethod = { id: string; type: "external"; label: string; form?: FormFields }
|
||||
|
||||
export type CredentialEntry = {
|
||||
id: string
|
||||
integrationID: string
|
||||
@@ -2351,6 +2353,7 @@ export type IntegrationMethod =
|
||||
| IntegrationOAuthMethod
|
||||
| IntegrationCommandMethod
|
||||
| IntegrationKeyMethod
|
||||
| IntegrationExternalMethod
|
||||
| IntegrationEnvMethod
|
||||
|
||||
export type FormCreated = {
|
||||
@@ -5688,6 +5691,28 @@ export type IntegrationConnectKeyInput = {
|
||||
|
||||
export type IntegrationConnectKeyOutput = void
|
||||
|
||||
export type IntegrationConnectExternalInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
readonly methodID: {
|
||||
readonly methodID: string
|
||||
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined
|
||||
readonly label?: string | undefined
|
||||
}["methodID"]
|
||||
readonly answer?: {
|
||||
readonly methodID: string
|
||||
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined
|
||||
readonly label?: string | undefined
|
||||
}["answer"]
|
||||
readonly label?: {
|
||||
readonly methodID: string
|
||||
readonly answer?: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> } | undefined
|
||||
readonly label?: string | undefined
|
||||
}["label"]
|
||||
}
|
||||
|
||||
export type IntegrationConnectExternalOutput = void
|
||||
|
||||
export type IntegrationOauthConnectInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||
|
||||
@@ -40,31 +40,31 @@
|
||||
"workerd": "./src/pty/pty.workerd.ts",
|
||||
"bun": "./src/pty/pty.bun.ts",
|
||||
"node": "./src/pty/pty.node.ts",
|
||||
"default": "./src/pty/pty.node.ts"
|
||||
"default": "./src/pty/pty.bun.ts"
|
||||
},
|
||||
"#persistent-pty-binary": {
|
||||
"workerd": "./src/persistent-pty/binary.workerd.ts",
|
||||
"bun": "./src/persistent-pty/binary.bun.ts",
|
||||
"node": "./src/persistent-pty/binary.node.ts",
|
||||
"default": "./src/persistent-pty/binary.node.ts"
|
||||
"default": "./src/persistent-pty/binary.bun.ts"
|
||||
},
|
||||
"#fff": {
|
||||
"workerd": "./src/filesystem/fff.workerd.ts",
|
||||
"bun": "./src/filesystem/fff.bun.ts",
|
||||
"node": "./src/filesystem/fff.node.ts",
|
||||
"default": "./src/filesystem/fff.node.ts"
|
||||
"default": "./src/filesystem/fff.bun.ts"
|
||||
},
|
||||
"#photon-wasm": {
|
||||
"workerd": "./src/image/photon-wasm.workerd.ts",
|
||||
"bun": "./src/image/photon-wasm.bun.ts",
|
||||
"node": "./src/image/photon-wasm.node.ts",
|
||||
"default": "./src/image/photon-wasm.node.ts"
|
||||
"default": "./src/image/photon-wasm.bun.ts"
|
||||
},
|
||||
"#shell-parser-wasm": {
|
||||
"workerd": "./src/shell/parser-wasm.workerd.ts",
|
||||
"bun": "./src/shell/parser-wasm.bun.ts",
|
||||
"node": "./src/shell/parser-wasm.node.ts",
|
||||
"default": "./src/shell/parser-wasm.node.ts"
|
||||
"default": "./src/shell/parser-wasm.bun.ts"
|
||||
},
|
||||
"#v1-migration": {
|
||||
"types": "./src/database/v1-migration.bun.ts",
|
||||
|
||||
@@ -2,10 +2,14 @@ export * as ConfigMarkdown from "./markdown.js"
|
||||
|
||||
import matter from "gray-matter"
|
||||
export function parse(content: string) {
|
||||
// Passing options bypasses gray-matter's module-global content cache, which
|
||||
// it populates before parsing: a failed YAML parse poisons the entry and
|
||||
// every later parse of the same content replays it without throwing, so the
|
||||
// sanitize fallback below never runs. Upstream: jonschlinkert/gray-matter#166.
|
||||
try {
|
||||
return matter(content)
|
||||
return matter(content, {})
|
||||
} catch {
|
||||
return matter(sanitize(content))
|
||||
return matter(sanitize(content), {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ export type CommandMethod = Integration.CommandMethod
|
||||
export const KeyMethod = Integration.KeyMethod
|
||||
export type KeyMethod = Integration.KeyMethod
|
||||
|
||||
export const ExternalMethod = Integration.ExternalMethod
|
||||
export type ExternalMethod = Integration.ExternalMethod
|
||||
|
||||
export const EnvMethod = Integration.EnvMethod
|
||||
export type EnvMethod = Integration.EnvMethod
|
||||
|
||||
@@ -82,6 +85,11 @@ export interface KeyImplementation {
|
||||
readonly method: KeyMethod
|
||||
}
|
||||
|
||||
export interface ExternalImplementation {
|
||||
readonly integrationID: ID
|
||||
readonly method: ExternalMethod
|
||||
}
|
||||
|
||||
export interface CommandImplementation {
|
||||
readonly integrationID: ID
|
||||
readonly method: CommandMethod
|
||||
@@ -92,7 +100,12 @@ export interface EnvImplementation {
|
||||
readonly method: EnvMethod
|
||||
}
|
||||
|
||||
export type Implementation = OAuthImplementation | CommandImplementation | KeyImplementation | EnvImplementation
|
||||
export type Implementation =
|
||||
| OAuthImplementation
|
||||
| CommandImplementation
|
||||
| KeyImplementation
|
||||
| ExternalImplementation
|
||||
| EnvImplementation
|
||||
|
||||
export const Attempt = Integration.Attempt
|
||||
export type Attempt = Integration.Attempt
|
||||
@@ -180,6 +193,17 @@ export interface Interface extends State.Transformable<Editor> {
|
||||
/** User-facing label for the stored credential. */
|
||||
readonly label?: string
|
||||
}) => Effect.Effect<void, AuthorizationError>
|
||||
/** Runs an external method and stores a reference configured by its form answers. */
|
||||
readonly external: (input: {
|
||||
/** Integration receiving the credential. */
|
||||
readonly integrationID: ID
|
||||
/** External method that defines the form and credential source. */
|
||||
readonly methodID: MethodID
|
||||
/** Values collected from the method's form fields. */
|
||||
readonly answer?: Form.Answer
|
||||
/** User-facing label for the stored credential. */
|
||||
readonly label?: string
|
||||
}) => Effect.Effect<void, AuthorizationError>
|
||||
/** Selects a stored credential as the active integration connection. */
|
||||
readonly activate: (credentialID: Credential.ID) => Effect.Effect<void>
|
||||
/** Updates a stored credential exposed as a connection. */
|
||||
@@ -327,6 +351,8 @@ const layer = Layer.effect(
|
||||
return method.id === implementation.method.id
|
||||
if (method.type === "command" && implementation.method.type === "command")
|
||||
return method.id === implementation.method.id
|
||||
if (method.type === "external" && implementation.method.type === "external")
|
||||
return method.id === implementation.method.id
|
||||
return true
|
||||
})
|
||||
if (index === -1) current.methods.push(implementation.method as Types.DeepMutable<Method>)
|
||||
@@ -345,6 +371,7 @@ const layer = Layer.effect(
|
||||
if (candidate.type !== method.type) return false
|
||||
if (candidate.type === "oauth" && method.type === "oauth") return candidate.id === method.id
|
||||
if (candidate.type === "command" && method.type === "command") return candidate.id === method.id
|
||||
if (candidate.type === "external" && method.type === "external") return candidate.id === method.id
|
||||
return true
|
||||
})
|
||||
if (index !== -1) current.methods.splice(index, 1)
|
||||
@@ -736,6 +763,31 @@ const layer = Layer.effect(
|
||||
}),
|
||||
})
|
||||
}),
|
||||
external: Effect.fn("Integration.connection.external")(function* (input) {
|
||||
const method = state
|
||||
.get()
|
||||
.integrations.get(input.integrationID)
|
||||
?.methods.find((method) => method.type === "external" && method.id === input.methodID)
|
||||
if (method?.type !== "external")
|
||||
return yield* new AuthorizationError({ cause: new Error(`External method not found: ${input.methodID}`) })
|
||||
const answer = input.answer ?? {}
|
||||
if (method.form) {
|
||||
const invalid = Form.validateFields(method.form) ?? Form.validateAnswer(method.form, answer)
|
||||
if (invalid) return yield* new AuthorizationError({ cause: new Error(invalid) })
|
||||
}
|
||||
if (!method.form && Object.keys(answer).length > 0) {
|
||||
return yield* new AuthorizationError({ cause: new Error("External method does not accept a form answer") })
|
||||
}
|
||||
yield* createCredential({
|
||||
integrationID: input.integrationID,
|
||||
label: input.label,
|
||||
value: Credential.External.make({
|
||||
type: "external",
|
||||
methodID: method.id,
|
||||
...(Object.keys(answer).length > 0 ? { metadata: answer } : {}),
|
||||
}),
|
||||
})
|
||||
}),
|
||||
activate: Effect.fn("Integration.connection.activate")((credentialID) => credentials.activate(credentialID)),
|
||||
update: Effect.fn("Integration.connection.update")((credentialID, updates) =>
|
||||
credentials.update(credentialID, updates),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// opencode-pty does not ship Windows binaries.
|
||||
export const available = process.platform !== "win32"
|
||||
|
||||
export async function resolveBinary(_bin: string) {
|
||||
export async function resolveBinary() {
|
||||
return process.env.OPENCODE_PTY_BIN || "opencode-pty"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export const available = false
|
||||
|
||||
export async function resolveBinary(_bin: string): Promise<string> {
|
||||
export async function resolveBinary(): Promise<string> {
|
||||
throw new Error("Persistent PTYs are unavailable in this runtime")
|
||||
}
|
||||
@@ -286,6 +286,13 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
answer: input.answer,
|
||||
label: input.label,
|
||||
}),
|
||||
external: (input) =>
|
||||
integration.connection.external({
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
methodID: Integration.MethodID.make(input.methodID),
|
||||
answer: input.answer,
|
||||
label: input.label,
|
||||
}),
|
||||
},
|
||||
oauth: {
|
||||
connect: (input) =>
|
||||
@@ -665,6 +672,12 @@ function methodImplementation(input: IntegrationMethodRegistration): Integration
|
||||
method: { ...input.method, id: Integration.MethodID.make(input.method.id) },
|
||||
}
|
||||
}
|
||||
if (input.method.type === "external") {
|
||||
return {
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
method: { ...input.method, id: Integration.MethodID.make(input.method.id) },
|
||||
}
|
||||
}
|
||||
return {
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
method: input.method,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Integration } from "../../integration.js"
|
||||
import { Model } from "../../model.js"
|
||||
import { OauthCallbackPage } from "../../oauth/page.js"
|
||||
import { Provider } from "../../provider.js"
|
||||
import { SessionAffinity } from "../../session/affinity.js"
|
||||
import type { PluginInternal } from "../internal.js"
|
||||
|
||||
// First-time sign-in registers a user-owned client; OpenAI returns its issued client ID on the callback.
|
||||
@@ -264,6 +265,21 @@ export const ChatGPTPlugin = define({
|
||||
}),
|
||||
{ providerID },
|
||||
)
|
||||
yield* ctx.session.hook(
|
||||
"model.request",
|
||||
(evt) =>
|
||||
Effect.gen(function* () {
|
||||
if (!chatgpt) return
|
||||
const session = yield* ctx.session
|
||||
.get({ sessionID: evt.sessionID })
|
||||
.pipe(Effect.orElseSucceed(() => undefined))
|
||||
// Mirror the Codex client's session headers: ChatGPT derives prompt-cache affinity from session-id.
|
||||
evt.headers["session-id"] = session ? SessionAffinity.get(session) : evt.sessionID
|
||||
evt.headers["thread-id"] = evt.sessionID
|
||||
evt.headers["x-client-request-id"] = evt.sessionID
|
||||
}),
|
||||
{ providerID },
|
||||
)
|
||||
yield* ctx.provider.transform((providers) => {
|
||||
const item = providers.get(providerID)
|
||||
if (!item) return
|
||||
|
||||
@@ -55,7 +55,7 @@ const forkTitle = (value?: string) => {
|
||||
return `${value} (fork #1)`
|
||||
}
|
||||
|
||||
function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usage) {
|
||||
function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usage, timeUpdated?: number) {
|
||||
return db
|
||||
.update(SessionTable)
|
||||
.set({
|
||||
@@ -65,13 +65,22 @@ function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usa
|
||||
tokens_reasoning: sql`${SessionTable.tokens_reasoning} + ${value.tokens.reasoning}`,
|
||||
tokens_cache_read: sql`${SessionTable.tokens_cache_read} + ${value.tokens.cache.read}`,
|
||||
tokens_cache_write: sql`${SessionTable.tokens_cache_write} + ${value.tokens.cache.write}`,
|
||||
time_updated: sql`${SessionTable.time_updated}`,
|
||||
time_updated: timeUpdated ?? sql`${SessionTable.time_updated}`,
|
||||
})
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
function touch(db: DatabaseService, event: MessageEvent) {
|
||||
return db
|
||||
.update(SessionTable)
|
||||
.set({ time_updated: event.created })
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
const publishSessionUsage = Effect.fn("SessionProjector.publishUsage")(function* (
|
||||
db: DatabaseService,
|
||||
bus: Bus.Interface,
|
||||
@@ -681,19 +690,30 @@ const layer = Layer.effectDiscard(
|
||||
yield* bus.project(SessionEvent.Skill.Activated, (event) => run(db, event))
|
||||
yield* bus.project(SessionEvent.Shell.Started, (event) => run(db, event))
|
||||
yield* bus.project(SessionEvent.Shell.Ended, (event) => run(db, event))
|
||||
yield* bus.project(SessionEvent.Step.Started, (event) => run(db, event))
|
||||
yield* bus.project(SessionEvent.Step.Started, (event) =>
|
||||
Effect.gen(function* () {
|
||||
yield* run(db, event)
|
||||
yield* touch(db, event)
|
||||
}),
|
||||
)
|
||||
yield* bus.project(SessionEvent.Step.Streamed, (event) => run(db, event))
|
||||
yield* bus.project(SessionEvent.Step.Ended, (event) =>
|
||||
Effect.gen(function* () {
|
||||
yield* run(db, event)
|
||||
yield* applyUsage(db, event.data.sessionID, event.data)
|
||||
yield* applyUsage(db, event.data.sessionID, event.data, event.created)
|
||||
}),
|
||||
)
|
||||
yield* bus.project(SessionEvent.Step.Failed, (event) =>
|
||||
Effect.gen(function* () {
|
||||
yield* run(db, event)
|
||||
if (event.data.cost !== undefined && event.data.tokens !== undefined)
|
||||
yield* applyUsage(db, event.data.sessionID, { cost: event.data.cost, tokens: event.data.tokens })
|
||||
yield* applyUsage(
|
||||
db,
|
||||
event.data.sessionID,
|
||||
{ cost: event.data.cost, tokens: event.data.tokens },
|
||||
event.created,
|
||||
)
|
||||
else yield* touch(db, event)
|
||||
}),
|
||||
)
|
||||
yield* bus.project(SessionEvent.Text.Started, (event) => run(db, event))
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { ConfigMarkdown } from "@opencode/core/config/markdown"
|
||||
|
||||
const invalidYaml = `---
|
||||
description: Use when the user needs to crawl pages. Keywords: crawl, scrape
|
||||
---
|
||||
body`
|
||||
|
||||
describe("ConfigMarkdown.parse", () => {
|
||||
test("recovers unquoted-colon frontmatter via the sanitize fallback", () => {
|
||||
const parsed = ConfigMarkdown.parse(invalidYaml)
|
||||
expect(parsed.data.description).toBe("Use when the user needs to crawl pages. Keywords: crawl, scrape")
|
||||
expect(parsed.content.trim()).toBe("body")
|
||||
})
|
||||
|
||||
test("recovers the same content again after a previous failed parse", () => {
|
||||
// gray-matter caches by content before parsing; a poisoned entry used to
|
||||
// make every later parse of the same text return silently without data.
|
||||
expect(() => ConfigMarkdown.parse("---\ndescription: [unclosed\n---\nbody")).toThrow()
|
||||
const parsed = ConfigMarkdown.parse(invalidYaml)
|
||||
expect(parsed.data.description).toBe("Use when the user needs to crawl pages. Keywords: crawl, scrape")
|
||||
})
|
||||
|
||||
test("keeps throwing for the same unparseable content on every call", () => {
|
||||
const input = "---\ndescription: [unclosed\n---\nbody"
|
||||
expect(() => ConfigMarkdown.parse(input)).toThrow()
|
||||
expect(() => ConfigMarkdown.parse(input)).toThrow()
|
||||
})
|
||||
|
||||
test("parses plain content without frontmatter", () => {
|
||||
const parsed = ConfigMarkdown.parse("just body")
|
||||
expect(parsed.content).toBe("just body")
|
||||
expect(parsed.data).toEqual({})
|
||||
})
|
||||
})
|
||||
Loaded 100 of 265 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user