Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 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" +
" Set DATABRICKS_HOST to your workspace URL (e.g., https://dbc-xxx.cloud.databricks.com)\n\n" +
"Authentication options (in priority order):\n" +
" 1. PAT: Enter your Personal Access Token below, or set DATABRICKS_TOKEN\n" +
" Create at: Workspace > Settings > Developer > Access tokens\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 Down
351 changes: 351 additions & 0 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,344 @@ export namespace Provider {
},
}
},
databricks: async (input) => {
const config = await Config.get()
const providerConfig = config.provider?.["databricks"]
const auth = await Auth.get("databricks")

// Host resolution: 1) config file, 2) env var
const configHost = providerConfig?.options?.baseURL ?? providerConfig?.options?.host
const envHost = Env.get("DATABRICKS_HOST")
const host = configHost ?? envHost

if (!host) return { autoload: false }

// Authentication precedence:
// 1. PAT token (DATABRICKS_TOKEN or stored auth)
// 2. OAuth M2M (DATABRICKS_CLIENT_ID + DATABRICKS_CLIENT_SECRET) for Azure
// 3. Azure AD Service Principal (azure_client_id + azure_client_secret + azure_tenant_id)
const token = Env.get("DATABRICKS_TOKEN") ?? (auth?.type === "api" ? auth.key : undefined)

// OAuth M2M credentials for Azure Databricks
const clientId =
Env.get("DATABRICKS_CLIENT_ID") ??
providerConfig?.options?.clientId ??
(auth?.type === "oauth" ? (auth as any).clientId : undefined)
const clientSecret =
Env.get("DATABRICKS_CLIENT_SECRET") ??
providerConfig?.options?.clientSecret ??
(auth?.type === "oauth" ? (auth as any).clientSecret : undefined)

// Azure AD Service Principal credentials
const azureClientId = Env.get("ARM_CLIENT_ID") ?? providerConfig?.options?.azureClientId
const azureClientSecret = Env.get("ARM_CLIENT_SECRET") ?? providerConfig?.options?.azureClientSecret
const azureTenantId = Env.get("ARM_TENANT_ID") ?? providerConfig?.options?.azureTenantId

// Determine which auth method to use
const hasOAuthM2M = clientId && clientSecret
const hasAzureAD = azureClientId && azureClientSecret && azureTenantId
const hasPAT = Boolean(token)
// Check if Azure CLI is available for Azure Databricks workspaces
const isAzureDatabricks = host.includes("azuredatabricks.net")

if (!hasPAT && !hasOAuthM2M && !hasAzureAD && !isAzureDatabricks) return { autoload: false }

// Databricks Foundation Model APIs use OpenAI-compatible endpoints
// The base URL format is: https://<workspace-url>/serving-endpoints
// If baseURL is already a full path (includes /serving-endpoints), use it as-is
const baseURL = host.includes("/serving-endpoints")
? host.replace(/\/$/, "")
: host.replace(/\/$/, "") + "/serving-endpoints"

// For OAuth M2M, we need to fetch an access token
let accessToken: string | undefined = token
if (!accessToken && hasOAuthM2M) {
// Fetch OAuth token from Databricks OIDC endpoint
const tokenEndpoint = `${host.replace(/\/$/, "")}/oidc/v1/token`
try {
const response = await fetch(tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`,
},
body: "grant_type=client_credentials&scope=all-apis",
})
if (response.ok) {
const data = (await response.json()) as { access_token: string }
accessToken = data.access_token
}
} catch (e) {
log.error("Failed to fetch Databricks OAuth token", { error: e })
}
}

// For Azure AD Service Principal, we need to fetch an Azure AD token first
if (!accessToken && hasAzureAD) {
try {
// Get Azure AD token for Databricks resource
const aadTokenEndpoint = `https://login.microsoftonline.com/${azureTenantId}/oauth2/v2.0/token`
const response = await fetch(aadTokenEndpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: azureClientId,
client_secret: azureClientSecret,
scope: "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d/.default", // Azure Databricks resource ID
}).toString(),
})
if (response.ok) {
const data = (await response.json()) as { access_token: string }
accessToken = data.access_token
}
} catch (e) {
log.error("Failed to fetch Azure AD token for Databricks", { error: e })
}
}

// For Azure Databricks, try Azure CLI as a fallback
if (!accessToken && isAzureDatabricks) {
try {
// Try to get token from Azure CLI
const proc = Bun.spawn(
["az", "account", "get-access-token", "--resource", "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d", "-o", "json"],
{ stdout: "pipe", stderr: "pipe" },
)
const output = await new Response(proc.stdout).text()
const exitCode = await proc.exited
if (exitCode === 0) {
const data = JSON.parse(output) as { accessToken: string }
accessToken = data.accessToken
log.info("Using Azure CLI token for Databricks authentication")
}
} catch (e) {
log.debug("Azure CLI not available for Databricks auth", { error: e })
}
}

if (!accessToken) return { autoload: false }

// Define default Databricks Foundation Model API endpoints
// These are the pay-per-token endpoints available in most workspaces
// Users can override or add more models in their opencode.json config
const defaultModels: Record<string, ModelsDev.Model> = {
"databricks-claude-sonnet-4": {
id: "databricks-claude-sonnet-4",
name: "Claude Sonnet 4 (Databricks)",
family: "claude-sonnet",
attachment: true,
reasoning: false,
tool_call: true,
temperature: true,
release_date: "2025-05-22",
modalities: { input: ["text", "image"], output: ["text"] },
cost: { input: 3, output: 15, cache_read: 0.3 },
limit: { context: 200000, output: 64000 },
options: {},
},
"databricks-claude-sonnet-4-5": {
id: "databricks-claude-sonnet-4-5",
name: "Claude Sonnet 4.5 (Databricks)",
family: "claude-sonnet",
attachment: true,
reasoning: true,
tool_call: true,
temperature: true,
release_date: "2025-10-22",
modalities: { input: ["text", "image"], output: ["text"] },
cost: { input: 3, output: 15, cache_read: 0.3 },
limit: { context: 200000, output: 64000 },
options: {},
},
"databricks-claude-haiku-4-5": {
id: "databricks-claude-haiku-4-5",
name: "Claude Haiku 4.5 (Databricks)",
family: "claude-haiku",
attachment: true,
reasoning: false,
tool_call: true,
temperature: true,
release_date: "2025-10-22",
modalities: { input: ["text", "image"], output: ["text"] },
cost: { input: 0.8, output: 4, cache_read: 0.08 },
limit: { context: 200000, output: 8192 },
options: {},
},
"databricks-claude-opus-4-5": {
id: "databricks-claude-opus-4-5",
name: "Claude Opus 4.5 (Databricks)",
family: "claude-opus",
attachment: true,
reasoning: true,
tool_call: true,
temperature: true,
release_date: "2025-10-22",
modalities: { input: ["text", "image"], output: ["text"] },
cost: { input: 15, output: 75, cache_read: 1.5 },
limit: { context: 200000, output: 32000 },
options: {},
},
"databricks-meta-llama-3-3-70b-instruct": {
id: "databricks-meta-llama-3-3-70b-instruct",
name: "Meta Llama 3.3 70B Instruct (Databricks)",
family: "llama-3.3",
attachment: false,
reasoning: false,
tool_call: true,
temperature: true,
release_date: "2024-12-06",
modalities: { input: ["text"], output: ["text"] },
cost: { input: 0.65, output: 2.56 },
limit: { context: 128000, output: 4096 },
options: {},
},
"databricks-claude-3-7-sonnet": {
id: "databricks-claude-3-7-sonnet",
name: "Claude 3.7 Sonnet (Databricks)",
family: "claude-sonnet",
attachment: true,
reasoning: true,
tool_call: true,
temperature: true,
release_date: "2025-02-24",
modalities: { input: ["text", "image"], output: ["text"] },
cost: { input: 3, output: 15, cache_read: 0.3 },
limit: { context: 200000, output: 64000 },
options: {},
},
"databricks-claude-opus-4-1": {
id: "databricks-claude-opus-4-1",
name: "Claude Opus 4.1 (Databricks)",
family: "claude-opus",
attachment: true,
reasoning: true,
tool_call: true,
temperature: true,
release_date: "2025-04-16",
modalities: { input: ["text", "image"], output: ["text"] },
cost: { input: 15, output: 75, cache_read: 1.5 },
limit: { context: 200000, output: 32000 },
options: {},
},
"databricks-llama-4-maverick": {
id: "databricks-llama-4-maverick",
name: "Llama 4 Maverick (Databricks)",
family: "llama-4",
attachment: false,
reasoning: false,
tool_call: true,
temperature: true,
release_date: "2025-04-05",
modalities: { input: ["text"], output: ["text"] },
cost: { input: 0.2, output: 0.6 },
limit: { context: 1048576, output: 65536 },
options: {},
},
"databricks-meta-llama-3-1-405b-instruct": {
id: "databricks-meta-llama-3-1-405b-instruct",
name: "Meta Llama 3.1 405B Instruct (Databricks)",
family: "llama-3.1",
attachment: false,
reasoning: false,
tool_call: true,
temperature: true,
release_date: "2024-07-23",
modalities: { input: ["text"], output: ["text"] },
cost: { input: 3, output: 3 },
limit: { context: 128000, output: 4096 },
options: {},
},
"databricks-meta-llama-3-1-8b-instruct": {
id: "databricks-meta-llama-3-1-8b-instruct",
name: "Meta Llama 3.1 8B Instruct (Databricks)",
family: "llama-3.1",
attachment: false,
reasoning: false,
tool_call: true,
temperature: true,
release_date: "2024-07-23",
modalities: { input: ["text"], output: ["text"] },
cost: { input: 0.1, output: 0.1 },
limit: { context: 128000, output: 4096 },
options: {},
},
}

// Transform ModelsDev.Model to Provider.Model format
function toProviderModel(model: ModelsDev.Model): Model {
return {
id: model.id,
providerID: "databricks",
name: model.name,
family: model.family,
api: {
id: model.id,
url: baseURL,
npm: "@ai-sdk/openai-compatible",
},
status: "active",
headers: {},
options: model.options ?? {},
cost: {
input: model.cost?.input ?? 0,
output: model.cost?.output ?? 0,
cache: {
read: model.cost?.cache_read ?? 0,
write: model.cost?.cache_write ?? 0,
},
},
limit: {
context: model.limit.context,
output: model.limit.output,
},
capabilities: {
temperature: model.temperature,
reasoning: model.reasoning,
attachment: model.attachment,
toolcall: model.tool_call,
input: {
text: model.modalities?.input?.includes("text") ?? false,
audio: model.modalities?.input?.includes("audio") ?? false,
image: model.modalities?.input?.includes("image") ?? false,
video: model.modalities?.input?.includes("video") ?? false,
pdf: model.modalities?.input?.includes("pdf") ?? false,
},
output: {
text: model.modalities?.output?.includes("text") ?? false,
audio: model.modalities?.output?.includes("audio") ?? false,
image: model.modalities?.output?.includes("image") ?? false,
video: model.modalities?.output?.includes("video") ?? false,
pdf: model.modalities?.output?.includes("pdf") ?? false,
},
interleaved: false,
},
release_date: model.release_date,
variants: {},
}
}

// Add default models to the input provider if not already defined
for (const [modelID, model] of Object.entries(defaultModels)) {
if (!input.models[modelID]) {
input.models[modelID] = toProviderModel(model)
}
}

return {
autoload: true,
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
return sdk.chat(modelID)
},
options: {
baseURL,
apiKey: accessToken,
headers: {
"User-Agent": "opencode",
},
},
}
},
}

export const Model = z
Expand Down Expand Up @@ -642,6 +980,19 @@ export namespace Provider {
}
}

// Add Databricks provider for Foundation Model APIs
// This provider is not in models.dev so we create it programmatically
if (!database["databricks"]) {
database["databricks"] = {
id: "databricks",
name: "Databricks",
source: "custom",
env: ["DATABRICKS_TOKEN"],
options: {},
models: {},
}
}

function mergeProvider(providerID: string, provider: Partial<Info>) {
const existing = providers[providerID]
if (existing) {
Expand Down
Loading