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
21 changes: 21 additions & 0 deletions packages/opencode/src/auth/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AsyncLocalStorage } from "node:async_hooks"

type Store = {
oauthRecordByProvider: Map<string, string>
}

const storage = new AsyncLocalStorage<Store>()

export function getOAuthRecordID(providerID: string): string | undefined {
return storage.getStore()?.oauthRecordByProvider.get(providerID)
}

export function withOAuthRecord<T>(providerID: string, recordID: string, fn: () => T): T {
const current = storage.getStore()
const next: Store = {
oauthRecordByProvider: new Map(current?.oauthRecordByProvider ?? []),
}
next.oauthRecordByProvider.set(providerID, recordID)

return storage.run(next, fn)
}
61 changes: 61 additions & 0 deletions packages/opencode/src/auth/credential-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import z from "zod"
import { Bus } from "../bus"
import { BusEvent } from "../bus/bus-event"
import { Log } from "../util/log"
import { TuiEvent } from "../cli/cmd/tui/event"

const log = Log.create({ service: "credential-manager" })
const DEFAULT_FAILOVER_TOAST_MS = 8000

export namespace CredentialManager {
export const Event = {
Failover: BusEvent.define(
"credential.failover",
z.object({
providerID: z.string(),
fromRecordID: z.string(),
toRecordID: z.string().optional(),
statusCode: z.number(),
message: z.string(),
}),
),
}

export async function notifyFailover(input: {
providerID: string
fromRecordID: string
toRecordID?: string
statusCode: number
toastDurationMs?: number
}): Promise<void> {
const isRateLimit = input.statusCode === 429
const message = isRateLimit
? `Rate limited on "${input.providerID}". Switching OAuth credential...`
: input.statusCode === 0
? `Request failed on "${input.providerID}". Switching OAuth credential...`
: `Auth error on "${input.providerID}". Switching OAuth credential...`
const duration = Math.max(0, input.toastDurationMs ?? DEFAULT_FAILOVER_TOAST_MS)

log.info("oauth credential failover", {
providerID: input.providerID,
fromRecordID: input.fromRecordID,
toRecordID: input.toRecordID,
statusCode: input.statusCode,
})

await Bus.publish(Event.Failover, {
providerID: input.providerID,
fromRecordID: input.fromRecordID,
toRecordID: input.toRecordID,
statusCode: input.statusCode,
message,
}).catch((error) => log.debug("failed to publish credential failover event", { error }))

await Bus.publish(TuiEvent.ToastShow, {
title: "OAuth Credential Failover",
message,
variant: "warning",
duration,
}).catch((error) => log.debug("failed to show failover toast", { error }))
}
}
Loading