description: "The authorization flow registry for users and maintainers who obtain credentials that configuration cannot supply, because getting one means a conversation with a human."
English | 中文
dsh-authorization obtains credentials that configuration cannot supply by asking a human: a plugin registers one flow per credential, and a configuration UI or another surface runs an attempt whose notices and questions reach exactly the page that asked. A human signs in with one of the flow's methods, pastes a code, or answers a question; when the flow resolves, its credential record is committed to the dsh-credentials store, and an attempt only reports authorized when that commit was observed. A refusal or a withdrawn attempt settles as cancelled rather than an error, so a surface can tell "the human said no" from "the flow broke". Choose it when a credential must be obtained interactively: it builds on the credential-record half of the credential seam, needs that store mounted, and ships no flows of its own — your plugin registers them.
This package is the part of the product that obtains credentials a human must hand over: a plugin registers the flow that knows how to get its own credential, and any surface can run an attempt and show the human what to do. The common path is explicit — register a flow for each credential your plugin holds, then start attempts from the surface the human is looking at.
Use it whenever a credential can only be obtained by talking to a human — an OAuth-style sign-in, a one-time code, an account pick — and cannot be stored in configuration. If a credential is a fixed key a deployment can supply, store it with the credential seam instead. A headless or ACP composition can mount this package safely: it offers no flows of its own, so nothing asks a human to sign in unless a plugin registered a flow.
Your plugin declares one flow per credential it holds, keyed by the <scope>/<id> credential record the flow writes — the scope names your plugin, the id names one credential it owns:
import type { Context } from '@deepseek-ai/cordis'
import type { AuthorizationSession } from '@deepseek-ai/dsh-authorization'
import { credentialKey } from '@deepseek-ai/dsh-credentials'
declare const ctx: Context
declare const exchangeCode: (code: string, signal: AbortSignal) => Promise<{ token: string }>
const key = credentialKey('llm-pi-ai', 'openai-codex') // <scope>/<id> — your plugin / this credential
const dispose = ctx.authorization.registerFlow({
key,
label: 'ChatGPT (Codex)',
methods: [{ id: 'oauth', label: 'Sign in with ChatGPT' }, { id: 'api-key', label: 'Paste a key' }],
async run(session: AuthorizationSession) {
session.notify({ message: 'Continue in your browser', url: 'https://auth.example/start' })
const code = await session.prompt({ kind: 'text', message: 'Paste the code' })
const { token } = await exchangeCode(code, session.signal)
await ctx.credentials.modifyRecord(key, () => Promise.resolve({ kind: 'grant', payload: { token } }))
},
})
ctx.authorization.list() // every registered flow, with inFlight
ctx.authorization.describe(key) // the entry above, or undefined
dispose() // unregister; withdraws any running attempt
A flow declares the credential record it writes, a user-facing label, and the sign-in methods it offers, most preferred first. run() talks to the human through the session — one-way notices and questions the flow cannot answer for itself — and must commit the record through ctx.credentials before resolving: the seam refuses a flow that resolved without committing. list() and describe() let a surface show what can be authorized and whether an attempt is running; dispose() unregisters the flow and withdraws any attempt still running.
A surface runs one attempt per credential at a time. The interaction travels with the request rather than living in a registry, so prompts reach exactly the page that asked; a headless caller supplies an interaction that declines. begin() reports { status: 'authorized' } when the record was committed and observed during the attempt, and { status: 'cancelled' } when the human declined or the caller withdrew. cancel(key) withdraws the running attempt from a second call, for the request/response transport that answers a Cancel button without holding the first call's signal.
begin() on a key no flow claims throws NO_FLOW; a record left by an uninstalled plugin can be deleted but not re-authorized.begin() while one is running throws ALREADY_IN_FLIGHT; inFlight on the entry lets a UI disable the button up front.NOT_COMMITTED, so authorized always means the record is really stored.UNKNOWN_METHOD — naming none runs the flow's first method.cancelled, the same as a withdrawn signal; any other failure reaches the caller as a thrown error.Read these pages when the package-level contract is not enough. They move from the shared credential vocabulary to the record store the flows write through and the decision evidence behind the seam.
None, as authorization is a configuration-time conversation with a human and no flow, notice, or prompt reaches a model request.
No invalidation; no authorization state enters a request prefix.
These limits define when this package is a poor fit or needs special care. They are current package constraints, not a task backlog.
ctx.credentials.deleteRecord(key), which forgets the local record without telling the issuer; a provider that needs a server-side revoke has no place to declare it.listRecords().