Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
dec3e7b
feat(opencode): Add Databricks provider support
dgokeeffe Jan 17, 2026
b1e90e4
feat: Add host field support for authentication
dgokeeffe Jan 17, 2026
d003ae3
feat(opencode): Add TUI authentication flow for Databricks
dgokeeffe Jan 17, 2026
9df2434
chore: Regenerate SDK types for auth host field
dgokeeffe Jan 17, 2026
d1cf86f
fix(opencode): Improve TUI dialog keyboard handling
dgokeeffe Jan 17, 2026
699a764
feat(opencode): Enable caching for Databricks models
dgokeeffe Jan 17, 2026
1a8efb6
feat(opencode): Add GPT-5 and Gemini model variants to Databricks pro…
dgokeeffe Jan 17, 2026
c90b7a6
docs: Add Databricks provider documentation
dgokeeffe Jan 17, 2026
21117b9
refactor(opencode): Improve Databricks provider code quality
dgokeeffe Jan 17, 2026
7c64880
test(opencode): Improve Databricks test cleanup
dgokeeffe Jan 17, 2026
e6ce134
feat: Add Databricks CLI token cache support
dgokeeffe Jan 19, 2026
79385b1
chore: Update Bun version to 1.3.6
dgokeeffe Jan 19, 2026
491d8db
Revert "chore: Update Bun version to 1.3.6"
dgokeeffe Jan 19, 2026
dc4d062
feat: Auto-detect Databricks CLI credentials in dialog
dgokeeffe Jan 19, 2026
2e216e9
feat: Add Databricks CLI profile and token refresh support
dgokeeffe Jan 19, 2026
88dff50
fix: Improve Databricks schema and message handling
dgokeeffe Jan 19, 2026
6116e02
docs: Update Databricks provider documentation
dgokeeffe Jan 19, 2026
8d569f8
chore: Add missing type dependencies for UI package
dgokeeffe Jan 23, 2026
7bedbf0
fix: Handle empty tool outputs for OpenAI-compatible APIs
dgokeeffe Jan 23, 2026
6a629ab
test: Add comprehensive Databricks model tests
dgokeeffe Jan 23, 2026
bcc3f47
feat: Only include tool-calling models for Databricks provider
dgokeeffe Jan 23, 2026
699a90e
test: Add Databricks tool calling tests for all model types
dgokeeffe Jan 23, 2026
9c3b86c
fix: Handle empty content in assistant messages for Databricks API
dgokeeffe Jan 24, 2026
3f2d518
chore: Review feedback improvements for Databricks provider
dgokeeffe Jan 24, 2026
4b01d3f
test: Add Databricks CLI token cache tests
dgokeeffe Jan 24, 2026
cb9ed57
test: Add Databricks prompt caching tests
dgokeeffe Jan 24, 2026
211b238
fix: Add token refresh to getFreshToken for expired Databricks CLI to…
dgokeeffe Jan 24, 2026
baaf764
fix: Address PR #7984 feedback for Databricks Gemini models
dgokeeffe Feb 5, 2026
9dca54a
fix: Handle providers with empty models in provider listing route
dgokeeffe Feb 8, 2026
5b3aec6
fix: Add circular $ref detection to Gemini schema sanitization
dgokeeffe Feb 8, 2026
4a89b7c
test: Add comprehensive tests for PR #7984 review feedback
dgokeeffe Feb 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/opencode/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export namespace Auth {
.object({
type: z.literal("api"),
key: z.string(),
host: z.string().optional(), // For providers like Databricks that need a host URL
})
.meta({ ref: "ApiAuth" })

Expand Down
31 changes: 31 additions & 0 deletions packages/opencode/src/cli/cmd/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ export const AuthLoginCommand = cmd({
google: 4,
openrouter: 5,
vercel: 6,
databricks: 7,
}
let provider = await prompts.autocomplete({
message: "Select provider",
Expand Down Expand Up @@ -344,6 +345,19 @@ export const AuthLoginCommand = cmd({
)
}

if (provider === "databricks") {
prompts.log.info(
"Databricks Foundation Model APIs authentication:\n" +
" Enter your workspace URL and Personal Access Token\n" +
" Create token at: Workspace > Settings > Developer > Access tokens\n\n" +
"Authentication options (in priority order):\n" +
" 1. PAT: Enter your Personal Access Token below, or set DATABRICKS_TOKEN\n" +
" 2. OAuth M2M: Set DATABRICKS_CLIENT_ID + DATABRICKS_CLIENT_SECRET\n" +
" 3. Azure AD Service Principal: Set ARM_CLIENT_ID + ARM_CLIENT_SECRET + ARM_TENANT_ID\n" +
" 4. Azure CLI (auto): For Azure Databricks, will use 'az account get-access-token' if logged in",
)
}

if (provider === "opencode") {
prompts.log.info("Create an api key at https://opencode.ai/auth")
}
Expand All @@ -358,6 +372,22 @@ export const AuthLoginCommand = cmd({
)
}

// For Databricks, prompt for host first
let host: string | undefined
if (provider === "databricks") {
const hostInput = await prompts.text({
message: "Enter your Databricks workspace URL",
placeholder: "https://your-workspace.cloud.databricks.com",
validate: (x) => {
if (!x || x.length === 0) return "Required"
if (!x.startsWith("https://")) return "Must start with https://"
return undefined
},
})
if (prompts.isCancel(hostInput)) throw new UI.CancelledError()
host = hostInput.replace(/\/$/, "") // Remove trailing slash
}

const key = await prompts.password({
message: "Enter your API key",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
Expand All @@ -366,6 +396,7 @@ export const AuthLoginCommand = cmd({
await Auth.set(provider, {
type: "api",
key,
host,
})

prompts.outro("Done")
Expand Down
119 changes: 118 additions & 1 deletion packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMemo, createSignal, onMount, Show } from "solid-js"
import { createMemo, createSignal, onMount, Show, createEffect } from "solid-js"
import { useSync } from "@tui/context/sync"
import { map, pipe, sortBy } from "remeda"
import { DialogSelect } from "@tui/ui/dialog-select"
Expand Down Expand Up @@ -97,6 +97,10 @@ export function createDialogProviderOptions() {
}
}
if (method.type === "api") {
// Databricks requires both host and API key
if (provider.id === "databricks") {
return dialog.replace(() => <DatabricksApiMethod providerID={provider.id} title={method.label} />)
}
return dialog.replace(() => <ApiMethod providerID={provider.id} title={method.label} />)
}
},
Expand Down Expand Up @@ -212,6 +216,119 @@ function CodeMethod(props: CodeMethodProps) {
)
}

interface DatabricksApiMethodProps {
providerID: string
title: string
}
function DatabricksApiMethod(props: DatabricksApiMethodProps) {
const { theme } = useTheme()
const dialog = useDialog()
const sdk = useSDK()
const sync = useSync()
// Get host from environment variable
const envHost = typeof process !== "undefined" ? process.env["DATABRICKS_HOST"] : undefined

// Check if we have a valid token in the CLI cache for this host
onMount(async () => {
if (!envHost) return

const normalizedHost = envHost.replace(/\/$/, "")
const homedir = typeof process !== "undefined" ? (process.env["HOME"] ?? process.env["USERPROFILE"]) : undefined
if (!homedir) return

try {
const tokenCachePath = `${homedir}/.databricks/token-cache.json`
const file = Bun.file(tokenCachePath)
if (!(await file.exists())) return

const cache = (await file.json()) as {
tokens: Record<string, { access_token: string; expiry: string; refresh_token?: string }>
}

const tokenEntry = cache.tokens[normalizedHost]
if (!tokenEntry) return

// Check if token is valid or can be refreshed
const expiry = new Date(tokenEntry.expiry)
const hasValidToken = expiry.getTime() - 5 * 60 * 1000 > Date.now()
const canRefresh = Boolean(tokenEntry.refresh_token)

if (hasValidToken || canRefresh) {
// We have CLI auth available, skip prompts and go straight to model selection
// Dispose and bootstrap to pick up the CLI token
await sdk.client.instance.dispose()
await sync.bootstrap()
dialog.replace(() => <DialogModel providerID={props.providerID} />)
}
} catch {
// Token cache not available or invalid, continue with normal flow
}
})

return (
<DialogPrompt
title="Databricks Host URL"
placeholder="https://your-workspace.cloud.databricks.com"
value={envHost ? envHost.replace(/\/$/, "") : undefined}
description={() => (
<box gap={1}>
<text fg={theme.textMuted}>Enter your Databricks workspace URL</text>
<text fg={theme.textMuted}>Examples:</text>
<text fg={theme.textMuted}> • https://dbc-xxx.cloud.databricks.com (AWS/GCP)</text>
<text fg={theme.textMuted}> • https://adb-xxx.azuredatabricks.net (Azure)</text>
</box>
)}
onConfirm={(value) => {
if (!value) return
// Remove trailing slash if present
const cleanHost = value.replace(/\/$/, "")
dialog.replace(() => (
<DatabricksApiKeyMethod providerID={props.providerID} title={props.title} host={cleanHost} />
))
}}
/>
)
}

interface DatabricksApiKeyMethodProps {
providerID: string
title: string
host: string
}
function DatabricksApiKeyMethod(props: DatabricksApiKeyMethodProps) {
const dialog = useDialog()
const sdk = useSDK()
const sync = useSync()
const { theme } = useTheme()

return (
<DialogPrompt
title={props.title}
placeholder="API key (Personal Access Token)"
description={
<box gap={1}>
<text fg={theme.textMuted}>Enter your Databricks Personal Access Token</text>
<text fg={theme.textMuted}>Create at: Workspace → Settings → Developer → Access tokens</text>
</box>
}
onConfirm={async (value) => {
if (!value) return
sdk.client.auth.set({
providerID: props.providerID,
auth: {
type: "api",
key: value,
host: props.host,
},
})
await sdk.client.instance.dispose()
await sync.bootstrap()
dialog.replace(() => <DialogModel providerID={props.providerID} />)
}}
/>
)
}

interface ApiMethodProps {
providerID: string
title: string
Expand Down
10 changes: 10 additions & 0 deletions packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ export function DialogPrompt(props: DialogPromptProps) {

useKeyboard((evt) => {
if (evt.name === "return") {
evt.preventDefault()
props.onConfirm?.(textarea.plainText)
}
if (evt.name === "escape") {
props.onCancel?.()
}
})

onMount(() => {
Expand All @@ -47,6 +51,12 @@ export function DialogPrompt(props: DialogPromptProps) {
onSubmit={() => {
props.onConfirm?.(textarea.plainText)
}}
onKeyDown={(e) => {
if (e.name === "return") {
e.preventDefault()
props.onConfirm?.(textarea.plainText)
}
}}
height={3}
keyBindings={[{ name: "return", action: "submit" }]}
ref={(val: TextareaRenderable) => (textarea = val)}
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/provider/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,13 @@ export namespace ProviderAuth {
z.object({
providerID: z.string(),
key: z.string(),
host: z.string().optional(),
}),
async (input) => {
await Auth.set(input.providerID, {
type: "api",
key: input.key,
host: input.host,
})
},
)
Expand Down
Loading