From ffe42bdc8ae5d6acd3fcbaab100001ea020ebb97 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 20 Nov 2025 23:50:01 -0500 Subject: [PATCH] sync --- .../cli/cmd/tui/component/dialog-provider.tsx | 134 +++++++++++------- .../opencode/src/cli/cmd/tui/context/sync.tsx | 3 +- packages/opencode/src/provider/auth.ts | 30 ++-- packages/opencode/src/server/server.ts | 4 +- packages/sdk/js/src/gen/types.gen.ts | 22 +-- 5 files changed, 115 insertions(+), 78 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index c4236d884..45003f219 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -1,12 +1,13 @@ -import { createMemo } from "solid-js" +import { createMemo, onMount } from "solid-js" import { useSync } from "@tui/context/sync" import { map, pipe, sortBy } from "remeda" import { DialogSelect } from "@tui/ui/dialog-select" -import { Dialog, useDialog } from "@tui/ui/dialog" +import { useDialog } from "@tui/ui/dialog" import { useSDK } from "../context/sdk" import { DialogPrompt } from "../ui/dialog-prompt" import { useTheme } from "../context/theme" import { TextAttributes } from "@opentui/core" +import type { ProviderAuthAuthorization, ProviderAuthMethod } from "@opencode-ai/sdk" const PROVIDER_PRIORITY: Record = { opencode: 0, @@ -73,59 +74,15 @@ export function createDialogProviderOptions() { }, }) if (result.data?.method === "code") { - while (true) { - const url = result.data.url - const code = await DialogPrompt.show(dialog, "Login with " + method.label, { - placeholder: "Authorization code", - description: () => ( - - Visit the url to collect your authorization code - {url} - - ), - }) - if (!code) break - const { error } = await sdk.client.provider.oauth.callback({ - path: { - id: provider.id, - }, - body: { - method: index, - code, - }, - }) - if (!error) { - await sdk.client.instance.dispose() - await sync.bootstrap() - return - } - } + dialog.replace(() => ( + + )) } if (result.data?.method === "auto") { - const { instructions, url } = result.data dialog.replace(() => ( - ( - - {url} - {instructions} - - )} - /> + )) - await sdk.client.provider.oauth.callback({ - path: { - id: provider.id, - }, - body: { - method: index, - }, - }) - dialog.clear() - await sdk.client.instance.dispose() - await sync.bootstrap() } } }, @@ -138,24 +95,91 @@ export function createDialogProviderOptions() { export function DialogProvider() { const options = createDialogProviderOptions() - return } -interface PendingDialogProps { +interface AutoMethodProps { + index: number + providerID: string title: string - description: () => JSX.Element + authorization: ProviderAuthAuthorization } -function PendingDialog(props: PendingDialogProps) { +function AutoMethod(props: AutoMethodProps) { const { theme } = useTheme() + const sdk = useSDK() + const dialog = useDialog() + const sync = useSync() + + onMount(async () => { + const result = await sdk.client.provider.oauth.callback({ + path: { + id: props.providerID, + }, + body: { + method: props.index, + }, + }) + if (result.error) { + dialog.clear() + return + } + await sdk.client.instance.dispose() + await sync.bootstrap() + dialog.clear() + }) + return ( {props.title} esc - {props.description} + + {props.authorization.url} + {props.authorization.instructions} + Waiting for authorization... ) } + +interface CodeMethodProps { + index: number + title: string + providerID: string + authorization: ProviderAuthAuthorization +} +function CodeMethod(props: CodeMethodProps) { + const { theme } = useTheme() + const sdk = useSDK() + const sync = useSync() + + return ( + { + const { error } = await sdk.client.provider.oauth.callback({ + path: { + id: props.providerID, + }, + body: { + method: props.index, + code: value, + }, + }) + if (!error) { + await sdk.client.instance.dispose() + await sync.bootstrap() + return + } + }} + description={() => ( + + {props.authorization.instructions} + {props.authorization.url} + + )} + /> + ) +} diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index d5bdadbfc..a5e13adb4 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -13,6 +13,7 @@ import type { FormatterStatus, SessionStatus, ProviderListResponse, + ProviderAuthMethod, } from "@opencode-ai/sdk" import { createStore, produce, reconcile } from "solid-js/store" import { useSDK } from "@tui/context/sdk" @@ -30,7 +31,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ provider: Provider[] provider_default: Record provider_next: ProviderListResponse - provider_auth: Record + provider_auth: Record agent: Agent[] command: Command[] permission: { diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 6467285dd..fb0016039 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -18,10 +18,14 @@ export namespace ProviderAuth { return { methods, pending: {} as Record } }) - export const Method = z.object({ - type: z.union([z.literal("oauth"), z.literal("api")]), - label: z.string(), - }) + export const Method = z + .object({ + type: z.union([z.literal("oauth"), z.literal("api")]), + label: z.string(), + }) + .meta({ + ref: "ProviderAuthMethod", + }) export type Method = z.infer export async function methods() { @@ -36,19 +40,23 @@ export namespace ProviderAuth { ) } - export const AuthorizeResult = z.object({ - url: z.string(), - method: z.union([z.literal("auto"), z.literal("code")]), - instructions: z.string(), - }) - export type AuthorizeResult = z.infer + export const Authorization = z + .object({ + url: z.string(), + method: z.union([z.literal("auto"), z.literal("code")]), + instructions: z.string(), + }) + .meta({ + ref: "ProviderAuthAuthorization", + }) + export type Authorization = z.infer export const authorize = fn( z.object({ providerID: z.string(), method: z.number(), }), - async (input): Promise => { + async (input): Promise => { const auth = await state().then((s) => s.methods[input.providerID]) const method = auth.methods[input.method] if (method.type === "oauth") { diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index c6701c239..1ab4185c2 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -8,7 +8,7 @@ import { proxy } from "hono/proxy" import { Session } from "../session" import z from "zod" import { Provider } from "../provider/provider" -import { map, mapValues, pipe, values } from "remeda" +import { mapValues } from "remeda" import { NamedError } from "../util/error" import { ModelsDev } from "../provider/models" import { Ripgrep } from "../file/ripgrep" @@ -1247,7 +1247,7 @@ export namespace Server { description: "Authorization URL and method", content: { "application/json": { - schema: resolver(ProviderAuth.AuthorizeResult.optional()), + schema: resolver(ProviderAuth.Authorization.optional()), }, }, }, diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 59e5a5d0c..3a69106ae 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1331,6 +1331,17 @@ export type Provider = { } } +export type ProviderAuthMethod = { + type: "oauth" | "api" + label: string +} + +export type ProviderAuthAuthorization = { + url: string + method: "auto" | "code" + instructions: string +} + export type Symbol = { name: string kind: number @@ -2538,10 +2549,7 @@ export type ProviderAuthResponses = { * Provider auth methods */ 200: { - [key: string]: Array<{ - type: "oauth" | "api" - label: string - }> + [key: string]: Array } } @@ -2579,11 +2587,7 @@ export type ProviderOauthAuthorizeResponses = { /** * Authorization URL and method */ - 200: { - url: string - method: "auto" | "code" - instructions: string - } + 200: ProviderAuthAuthorization } export type ProviderOauthAuthorizeResponse = ProviderOauthAuthorizeResponses[keyof ProviderOauthAuthorizeResponses]