# v0.1 Provider Profile 管理规格 本文是 AgentRun `v0.1` provider profile 管理能力的长期规格。实施跟踪见 [pikasTech/agentrun#28](https://github.com/pikasTech/agentrun/issues/28),HWLAB v0.2 WebUI 配置入口见 [pikasTech/HWLAB#917](https://github.com/pikasTech/HWLAB/issues/917)。 ## 设计目标 - 由 `agentrun-mgr` 提供 provider profile 状态查询、API Key 写入、Secret/config 更新和 canary 验证 REST API。 - AgentRun 后端直接信任来自 HWLAB `hwlab-cloud-api` 的服务端委托调用,不实现浏览器用户登录、Web session、HWLAB 用户角色或 OpenFGA 判断。 - 浏览器和 HWLAB Cloud Web 不直接访问 AgentRun,也不把原始 API Key 传入 run/session/command payload。 - AgentRun 只返回脱敏状态:profile、SecretRef、resourceVersion、hash 后缀、validation result、run/command/job identity 和 failureKind。 - provider profile 仍通过现有 `backendProfile`、`executionPolicy.secretScope.providerCredentials[]`、profile-scoped SecretRef 和 writable `CODEX_HOME` 装配到 runner。 ## 职责边界 | 组件 | 职责 | | --- | --- | | `agentrun-mgr` | provider profile 管理 API、Secret/config 写入、状态查询、canary 创建、validation 状态聚合和脱敏审计。 | | `agentrun-runner` | 按既有 `backendProfile`/SecretRef 装配 runtime,执行 canary run,不读取管理 API 的用户上下文。 | | HWLAB `hwlab-cloud-api` | 用户鉴权、业务授权、Web/CLI 同源 API、审计 actor、调用 AgentRun 管理 API。 | | HWLAB `hwlab-cloud-web` | “管理”页面和 API Key 表单;不直接调用 AgentRun。 | AgentRun 不承担 HWLAB 用户鉴权。它只做机器层 contract 校验:调用来源、tenant/project/profile allowlist、SecretRef scope、payload schema、redaction 和幂等性。AgentRun 不判断某个 HWLAB 用户是否能配置 API Key,也不保存 HWLAB Web session、API key 或 OpenFGA decision。 ## Profile 列表 `v0.1` 可管理 profile 分为内建 profile 与动态 profile slug。内建 profile 是无需 Secret 也必须出现在列表中的稳定能力;动态 profile 是管理员通过 HWLAB Cloud API / HWLAB CLI 或 AgentRun CLI 写入受控 Secret 后立即可见、可配置、可验证的 profile,不需要为每个新 slug 修改 AgentRun 或 HWLAB 服务代码。 | profile | backendKind | 默认 SecretRef | 说明 | | --- | --- | --- | --- | | `codex` | `codex-app-server-stdio` | `agentrun-v01/agentrun-v01-provider-codex` | Codex API profile。 | | `deepseek` | `codex-app-server-stdio` | `agentrun-v01/agentrun-v01-provider-deepseek` | DeepSeek profile,经 HWLAB Moon Bridge 到 DeepSeek 官方 upstream。 | | `minimax-m3` | `codex-app-server-stdio` | `agentrun-v01/agentrun-v01-provider-minimax-m3` | MiniMax-M3 profile。 | | `dsflash-go` | `codex-app-server-stdio` | `agentrun-v01/agentrun-v01-provider-dsflash-go` | DeepSeek V4 Flash profile,经 HWLAB Moon Bridge 到 OpenCode Zen Go upstream;Secret 额外要求 `model-catalog.json`,模型目录与 `config.toml` 均声明 1M/900k context。 | 动态 profile slug 必须匹配小写 slug 规则 `^[a-z0-9][a-z0-9-]{0,63}$`,并固定映射到同 namespace 内 `agentrun-v01-provider-` SecretRef,required keys 仍为 `auth.json` 与 `config.toml`。profile 管理 API 不得允许任意 namespace、任意 Secret name、`runtime-default` 或不符合 slug 规则的 profile。新增普通 OpenAI/Codex-compatible provider profile 时,管理员应优先通过 `provider-profiles set-config` / `set-key` / `validate` 创建动态 slug;只有需要新的 backend kind、特殊装配规则、额外 Secret key、租户策略或产品级固定友好名时,才更新本规格、内建 capability、CLI 示例和验证规格。 ## REST API Provider profile 管理 API 属于 `agentrun-mgr` 公共 REST API 的服务端委托面: ```http GET /api/v1/provider-profiles GET /api/v1/provider-profiles/:profile DELETE /api/v1/provider-profiles/:profile GET /api/v1/provider-profiles/:profile/config PUT /api/v1/provider-profiles/:profile/config PUT /api/v1/provider-profiles/:profile/credential POST /api/v1/provider-profiles/:profile/validate GET /api/v1/provider-profiles/:profile/validations/:validationId ``` 所有成功和失败响应都必须是 JSON。失败响应至少包含 `failureKind`、`message` 和 `requestId`。除显式 `GET /api/v1/provider-profiles/:profile/config` 返回 `config.toml` 明文供 HWLAB admin 管理页查看外,其他响应不得包含 API Key 原文、Codex `auth.json` 明文、Codex `config.toml` 明文、base64 Secret data、Authorization header、Kubernetes token 或 provider request header。 ### `GET /api/v1/provider-profiles` 返回所有内建 profile 与已存在动态 profile 的脱敏状态。动态 profile 通过受控 Secret 的 name、label 或 annotation 发现;删除动态 profile 的最后一个 Secret 后,它不再出现在 collection list 中。字段至少包含: - `profile` - `backendKind` - `configured` - `secretRef.namespace/name/keys` - `resourceVersion` - `keyHashSuffix` 或 `credentialHashSuffix` - `configHashSuffix` - `updatedAt` - `lastValidation.status/failureKind/message/runId/commandId/jobName` Secret 缺失时仍要返回 profile capability,并把状态标为 `configured=false` 或 `failureKind=secret-unavailable`;不得因为 Secret 未配置而隐藏 profile。 ### `DELETE /api/v1/provider-profiles/:profile` 删除 profile 对应 Kubernetes Secret。 - 内建 profile(`codex`、`deepseek`、`minimax-m3`、`dsflash-go`)删除后,capability 仍必须保留在 `GET /api/v1/provider-profiles` 列表中,但状态回到 `configured=false` / `failureKind=secret-unavailable`。 - 动态 slug 删除后,若没有剩余 Secret,对应 slug 不再出现在 collection list 中;显式 `GET /api/v1/provider-profiles/:profile` 仍可返回该 slug 的未配置状态。 - 响应必须返回 `removed` 或 `alreadyAbsent`,并保持 Secret/API Key 脱敏。 ### `GET/PUT /api/v1/provider-profiles/:profile/config` `GET` 返回当前 profile 的 `configToml`、SecretRef、resourceVersion 和 hash 后缀,供 HWLAB admin 管理页查看。`PUT` 接收 `configToml`,保存时只替换同一 Secret 的 `config.toml`,保留现有 `auth.json`,并返回 resourceVersion 和 `configHashSuffix`。 ### `PUT /api/v1/provider-profiles/:profile/credential` 请求体由 HWLAB 后端或受控 CLI 发送,最小形态: ```json { "apiKey": "", "config": { "model": "", "baseUrl": "" }, "delegatedBy": { "system": "hwlab-v02", "userId": "", "username": "", "requestId": "" }, "reason": "hwlab-provider-management" } ``` 规则: - `apiKey` 只在本次 request 内用于生成 Secret data,不能落入 Postgres、event、trace、日志或响应。 - Manager 写入 profile 对应 Kubernetes Secret 的 `auth.json` 和 `config.toml`,并返回新的 `resourceVersion` 与不可逆 hash 后缀;`dsflash-go` 同时生成或保留 `model-catalog.json`。 - Manager 可记录 `delegatedBy` 的脱敏审计信息,但不把它作为用户鉴权依据。 - 非 HWLAB 委托调用可以用于 operator CLI,但也必须走同一 schema 和 redaction。 - 非法 profile、非法 baseUrl、SecretRef scope 越界、Kubernetes 写入失败和 config render 失败必须结构化失败。 ### `POST /api/v1/provider-profiles/:profile/validate` 触发一个真实 canary。它必须通过 AgentRun 自身 run/command/runner-job 路径执行,不得只做静态 Secret 读取、mock provider 或直接 curl provider 作为通过证据。响应短返回: ```json { "validationId": "val_...", "profile": "deepseek", "runId": "run_...", "commandId": "cmd_...", "jobName": "agentrun-v01-runner-...", "status": "running", "pollUrl": "/api/v1/provider-profiles/deepseek/validations/val_..." } ``` `GET /validations/:validationId` 聚合 command result、runner job status、events 和 provider failureKind,返回 `running|completed|failed|cancelled`。成功时必须能证明当前 `backendProfile`、SecretRef、CODEX_HOME、provider status 和 assistant reply;失败时必须保留 provider/secret/runner failureKind。 ## DeepSeek 配置规则 `deepseek` profile 的 Codex config 必须指向 HWLAB v0.2 Moon Bridge,而不是 hyue: ```text baseUrl: http://hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local:4000/v1 expected request path: /v1/responses upstream: DeepSeek 官方 API ``` AgentRun 不直接拥有 DeepSeek 官方 upstream URL 的业务路由;它只把 Codex app-server 请求送到 HWLAB Moon Bridge。若 HWLAB bridge 需要独立 upstream Secret 或 rollout,AgentRun 管理 API 必须在响应中返回 `requiresExternalBridgeUpdate=true` 或由 HWLAB 委托请求显式声明 bridge 同步已完成。AgentRun 不得把 `deepseek` 配置改到 `hyueapi.com`,也不得因 DeepSeek 失败 fallback 到 `codex`。 ## dsflash-go 配置规则 `dsflash-go` 是内建 DeepSeek V4 Flash / OpenCode Zen Go profile,不是普通动态 slug。它必须通过 Codex stdio profile 形态运行,并满足以下固定规则: - `model` 必须是 `deepseek-v4-flash`,不得被 `deepseek-chat` 或其他模型覆盖。 - `config.toml` 必须声明 `model_context_window = 1000000` 和 `model_auto_compact_token_limit = 900000`。 - `config.toml` 必须声明 `model_catalog_json = "/home/agentrun/.codex-dsflash-go/model-catalog.json"`;Secret 中必须存在同名 `model-catalog.json`,其中 `deepseek-v4-flash` 的 context window 与 `config.toml` 一致。 - base URL 必须指向 HWLAB Moon Bridge service 或 wrapper-local bridge,禁止指向 `hyueapi.com`;当前 G14 v0.2 默认服务入口是 `http://hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local:4000/v1`。 - `PUT /credential` 与 `PUT /config` 均必须在不打印 Secret value 的前提下生成或保留 `model-catalog.json`;状态查询只显示 key presence/hash 摘要。 - 若上游 compact 路径返回 404、not found、unsupported、no route 或 not implemented,adapter 必须归类为 `provider-compact-unsupported`,避免被泛化成 `backend-failed`。 ## Secret 与 RBAC Manager ServiceAccount 需要最小 Secret 管理权限,只允许 `get`、`list`、`create`、`replace`、`delete`、`patch` 受控 provider profile Secret: - `agentrun-v01-provider-codex` - `agentrun-v01-provider-deepseek` - `agentrun-v01-provider-minimax-m3` - `agentrun-v01-provider-dsflash-go` - `agentrun-v01-provider-` 不得授予 Manager 更新 namespace 内任意 Secret 的宽权限;动态 Secret 必须受 `agentrun-v01-provider-` 前缀、profile label/annotation 和 slug 校验约束。状态查询只读取受控 Secret metadata 和 key presence/hash,不返回 Secret value。 Secret 写入后,runner Job 仍按 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) 通过 SecretRef projection 装配;run/command payload 中不出现 API Key。 Secret 写入不得留下包含 Secret data 的 Kubernetes last-applied 注解。Manager 更新 credential 时应使用不会生成 `kubectl.kubernetes.io/last-applied-configuration` 的 patch/update 路径,或在写入完成后显式删除该注解;状态响应、event、trace 和 CLI 输出仍只能返回 SecretRef、resourceVersion、hash 后缀和 redacted validation identity。 ## CLI AgentRun CLI 提供 operator 和综合联调入口: ```bash ./scripts/agentrun provider-profiles list ./scripts/agentrun provider-profiles show deepseek ./scripts/agentrun provider-profiles config deepseek ./scripts/agentrun provider-profiles remove deepseek ./scripts/agentrun provider-profiles set-key deepseek --key-stdin ./scripts/agentrun provider-profiles set-config deepseek --config-stdin ./scripts/agentrun provider-profiles validate deepseek --wait --timeout-ms 120000 ./scripts/agentrun provider-profiles set-config my-provider --config-stdin ./scripts/agentrun provider-profiles set-key my-provider --key-stdin ``` CLI 必须调用 manager REST API,不直连 Postgres,不读取 Kubernetes Secret value。`set-key --key-stdin` 从 stdin 读入 API Key,默认输出只包含 SecretRef、resourceVersion、hash 后缀、failureKind 和下一步验证命令。 ## 审计与脱敏 Manager 审计事件允许记录:profile、action、delegatedBy.system、delegatedBy.userId、requestId、SecretRef、old/new hash 后缀、resourceVersion、validationId、runId、commandId、jobName、failureKind。禁止记录:API Key 原文、Secret data、Codex auth/config 明文、Authorization header、provider request header/body、Kubernetes token。 ## 验收规格 ### T1 profile 状态 阅读本文和 [spec-v01-secret-distribution.md](spec-v01-secret-distribution.md),调用 `GET /api/v1/provider-profiles` 或 `./scripts/agentrun provider-profiles list`。确认 `codex`、`deepseek`、`minimax-m3`、`dsflash-go` 等内建 profile 全部可见,缺 Secret 时显示 `configured=false` 或 `secret-unavailable`,不隐藏 capability;已创建的动态 slug 也会列出;所有输出都不得包含 Secret value。 ### T2 DeepSeek 写入 用 `./scripts/agentrun provider-profiles set-key deepseek --key-stdin` 写入测试 key。确认输出只有 resourceVersion/hash 后缀和 redacted SecretRef;Postgres、event、trace、日志和 CLI 输出不含完整 key。 ### T3 DeepSeek canary 用 `./scripts/agentrun provider-profiles validate deepseek --wait` 触发真实 runner canary。通过证据必须包含 validationId、runId、commandId、jobName、backendProfile=deepseek、SecretRef=`agentrun-v01-provider-deepseek`、terminal completed 和非空 assistant reply。 ### T4 HWLAB 委托 通过 HWLAB v0.2 Cloud API `/v1/admin/provider-profiles/deepseek/credential` 和 `/validate` 调用本 API,确认 AgentRun 不要求 HWLAB 用户凭据,不读取 Web session,不返回用户权限判断;但会记录 `delegatedBy.system=hwlab-v02` 和 requestId 的脱敏审计。 ### T5 redaction 检查 manager 日志、AgentRun events、CLI 输出和 validation result,确认不包含 API Key 原文、Codex `auth.json`、`config.toml`、Secret data 或 Authorization header。 ### T6 profile 删除 用 `./scripts/agentrun provider-profiles remove ` 删除一个动态 slug,再删除一个内建 profile。确认动态 slug 从 collection list 消失;内建 profile 仍留在 list 中但 `configured=false`;CLI/日志/响应不输出 Secret value。 ### T7 动态 profile 无代码变更 用 HWLAB CLI 或 AgentRun CLI 对一个临时动态 slug 执行 `set-config`、`set-key`、`list`、`remove`。通过证据必须显示 profile SecretRef 为 `agentrun-v01-provider-`、`configured=true` 只在 `auth.json` 和 `config.toml` 同时存在时成立、删除后 collection list 不再包含该 slug,并且整个过程没有 AgentRun/HWLAB service code change、PR、PipelineRun 或 rollout 作为前置条件。 ### T8 dsflash-go model catalog 用 `./scripts/agentrun provider-profiles set-key dsflash-go --key-stdin` 或同源 HWLAB 委托 API 写入测试 key。确认输出只包含脱敏 SecretRef、resourceVersion/hash 后缀和 validation identity;Kubernetes Secret 中存在 `auth.json`、`config.toml`、`model-catalog.json` 三个 key;`config.toml` 使用 `deepseek-v4-flash`、1M/900k context 和固定 `model_catalog_json` 路径;`provider-profiles show dsflash-go` 必须显示三项 key presence,且不输出任何 Secret value。 ## 实现状态 | 能力 | 状态 | 说明 | | --- | --- | --- | | Provider profile 管理规格 | 已定义/已落地 | 本文为 AgentRun `v0.1` profile 管理权威规格。 | | REST 管理 API | 已实现 | `agentrun-mgr` 提供 `/api/v1/provider-profiles*`,覆盖 list/show/remove/set-key/validate/validation。 | | 动态 profile slug | 已实现 | 小写 slug 通过 `agentrun-v01-provider-` SecretRef 动态生效;普通 provider API Key/config 轮换不需要为每个新 slug 修改服务代码或触发专门 CI/CD。 | | CLI 管理入口 | 已实现 | `./scripts/agentrun provider-profiles list/show/remove/set-key/set-config/validate` 调用 manager REST API,不直连 Secret value。 | | DeepSeek Secret 写入 | 已实现/需硬化 | 已按受控 SecretRef 更新 `auth.json`/`config.toml` 并保持 HWLAB Moon Bridge 官方链路;后续必须去除 credential update 产生 `last-applied-configuration` 注解的副作用。 | | `dsflash-go` model catalog | 已实现 | `dsflash-go` 使用 `deepseek-v4-flash`、1M/900k context、固定 `model_catalog_json` 路径和 Secret 内 `model-catalog.json`;compact unsupported 明确归类为 `provider-compact-unsupported`。 | | Provider canary | 已实现 | canary 通过真实 run/command/runner-job 路径执行,并返回 validationId、runId、commandId、jobName 和 terminal status。 | | HWLAB 委托信任边界 | 已验证 | HWLAB v0.2 通过 Cloud API 委托调用本 API;AgentRun 不读取 HWLAB Web session,也不做用户级鉴权。 |