description: "面向用户与维护者的授权 flow 注册表:获取配置无法提供的凭据,因为拿到它需要与人对话。"
English | 中文
dsh-authorization 让配置 UI 或其他调用方通过人引导的登录、输入码或回答问题来获取凭据。每次尝试只把 notice 与 prompt 发送到发起它的界面。只有新凭据已存储时,它才报告 authorized;拒绝或撤销会报告 cancelled,而故障仍作为错误。当凭据无法通过配置提供时选择它。它需要凭据存储和一个定义可用授权方法的集成;本包自身不提供特定提供方的授权方法。
本包是产品中负责获取「必须由人交出来」的凭据的部分:插件注册一个知道如何取得自己那份凭据的 flow,任何界面都能发起尝试并向人展示该做什么。常用路径是显式的——为你插件持有的每个凭据注册一个 flow,然后从人正看着的那个界面发起尝试。
只要凭据只能通过与人对话获得——OAuth 式登录、一次性码、选一个账号——且无法存入配置,就使用它。如果凭据是部署方可以提供的一个固定密钥,请改用凭据 seam 存储它。无头或 ACP(Agent Client Protocol)组合也可以安全挂载本包:它本身不提供任何 flow,因此除非插件注册了 flow,否则不会要求人登录。
你的插件为它持有的每个凭据声明一个 flow,以该 flow 写入的 <scope>/<id> 凭据记录为键——scope 点名你的插件,id 点名它拥有的一条凭据:
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
flow 声明它写入的凭据记录、面向用户的标签以及它提供的登录方法,最优先者在前。run() 通过会话与人对话——单向 notice 与 flow 无法自行回答的问题——并且必须在返回前通过 ctx.credentials 提交记录:seam 会拒绝未提交就返回的 flow。list() 与 describe() 让界面展示可授权的内容以及是否有尝试在运行;dispose() 注销该 flow 并撤销仍在运行中的尝试。
每个凭据同时只允许一次尝试。交互随请求传入而非存放在注册表中,因此提问恰好抵达发问的那个页面;无头调用方传入一个直接拒绝的交互实现。当记录在尝试期间被提交并被观察到时,begin() 报告 { status: 'authorized' };当人拒绝或调用方撤销时,报告 { status: 'cancelled' }。cancel(key) 从第二次调用撤销正在运行的尝试,服务于那种用第二次调用来响应「取消」按钮、却不持有第一次调用 signal 的请求/响应式传输。
begin() 会抛出 NO_FLOW;被卸载插件遗留的记录可以删除,但无法重新授权。begin() 会抛出 ALREADY_IN_FLIGHT;entry 上的 inFlight 让界面预先禁用按钮。NOT_COMMITTED,因此 authorized 永远意味着记录真的已存储。UNKNOWN_METHOD——不点名则运行 flow 的第一个方法。cancelled 结算,与撤销的 signal 完全一致;其余任何失败都以抛出的错误抵达调用方。当包级约定不够用时阅读以下页面。它们从共享凭据词汇逐步进入 flow 写入的记录存储,以及本 seam 背后的决策证据。
无,因为授权是配置期与人的对话,flow、notice 与 prompt 都不会抵达模型请求。
不失效;任何授权状态都不会进入请求前缀。
这些限制说明本包何时不合适或需要特别注意。它们是当前包约束,不是任务积压。
ctx.credentials.deleteRecord(key),它只遗忘本地记录而不通知签发方;需要服务端吊销的提供方没有可声明之处。listRecords() 的情况相同。