Skip to main content
核心概念

SDK 认证

每个 SDK 会话(TypeScript 的 query()、Python 的 query() / QoderSDKClient)都必须配置一种认证方式。Java 在 QoderClient.Builder.auth() 上配置,该客户端创建的所有查询和会话使用同一种认证方式。同一个会话只能选择一种:
认证方式代表的身份适用场景
Personal Access Token(PAT)Qoder 用户需要使用该用户权限和数据的脚本、CI 或宿主应用
Service Account组织工作负载不应依赖个人账号的后端服务、CI 和定时任务
本机 qoderclicn 登录态当前登录用户已登录 Qoder 的开发者工作站

使用 PAT

PAT 代表一个 Qoder 用户,适合必须访问该用户权限和数据的自动化。

获取 PAT

在 Qoder Account Integrations 创建 PAT:
  1. 登录 Qoder。
  2. 打开 Account → Integrations。
  3. 选择有效期和所需权限并创建 PAT。
  4. 立即复制生成的值;页面关闭后无法再次查看。
建议为本地脚本、CI 和生产环境分别创建 PAT,以便单独轮换或吊销。

从环境变量读取 PAT

export QODERCN_PERSONAL_ACCESS_TOKEN="<your-qoder-personal-access-token>"
import { accessTokenFromEnv, query } from '@qodercn-ai/qodercn-agent-sdk';

const q = query({
  prompt: 'Summarize the current workspace.',
  options: {
    auth: accessTokenFromEnv(),
  },
});
该函数默认读取 QODERCN_PERSONAL_ACCESS_TOKEN。自定义变量名时使用:
auth: accessTokenFromEnv('MY_QODERCN_PAT')
如果 options.env 和进程环境中存在同名变量,SDK 优先读取 options.env 中的值。 Java 每次启动 CLI 时从 JVM 进程的环境变量读取,putEnvironment() 传入的同名变量不参与认证。变量未设置或为空时,启动以 AuthenticationException 失败。该变量不会传给 CLI 子进程。

直接传入 PAT

可信服务端已经取得 PAT 时,可以直接传入:
import { accessToken, query } from '@qodercn-ai/qodercn-agent-sdk';

const token = await readTokenFromSecretManager();
const q = query({
  prompt: 'List the most recently modified files.',
  options: {
    auth: accessToken(token),
  },
});
Java 用 SecretValue 包装凭据,toString() 恒为 [REDACTED]。SecretValue.of(char[]) 会复制传入的数组,调用方可以随后自行清零。 SDK 不会自动刷新 PAT。PAT 失效后,应取得新 PAT 并创建新的 SDK 会话。

使用 Service Account

开始前,请让 Qoder 组织管理员创建 Service Account、授予应用所需权限并生成 Key。Key 应保存在密钥管理服务中,并只交给可信的服务端进程或 CI 任务。

直接传入 Service Account Key

当可信服务端已经从密钥管理服务读取 Key 时,可以直接传入:
import { query, serviceAccount } from '@qodercn-ai/qodercn-agent-sdk';

// Get the Service Account key from the host's secret manager adapter.
const serviceAccountKey = await readSecret('qoder-service-account-key');

const q = query({
  prompt: 'Explain the purpose of this project in one sentence.',
  options: {
    auth: serviceAccount({ serviceAccountKey }),
    cwd: process.cwd(),
  },
});
调用方通过 serviceAccount({ serviceAccountKey })(TypeScript)/ service_account(service_account_key=...)(Python)/ Auth.serviceAccountKey(SecretValue)(Java)将 Key 传入本次会话。SDK 和 qoderclicn 会为会话取得并刷新短期 Service Account Token(SAT)。
密钥管理服务
     |
     | Service Account Key
     v
调用方读取 Key
     |
     | serviceAccount({ serviceAccountKey })
     v
SDK 启动 qoderclicn 并获取短期 SAT
     |
     `----> 使用 SAT 认证会话请求
SDK 将 Key 和 SAT 用于当前会话。每次创建新会话时,调用方重新传入 Key。Java 的 Key 保存在 QoderClient 的认证配置中,该客户端之后创建的会话都使用它。请从密钥管理服务读取 Key,并避免把 Key 字面量写入源码、浏览器包、移动应用、日志或测试快照。

宿主提供并刷新 SAT

如果由集成 SDK 的宿主应用负责换取 SAT,请使用 fetch 回调形式(TypeScript 的 serviceAccount({ fetchServiceAccountToken })、Python 的 service_account(fetch_service_account_token=...)、Java 的 Auth.serviceAccountTokenProvider(...))。Service Account Key 保留在宿主进程中,qoderclicn 从宿主回调接收 SAT。 该回调由宿主实现。qoderclicn 需要 SAT 时调用该回调;宿主每次收到请求,都调用 Token exchange 接口并把响应中的新 SAT 返回给 qoderclicn。
qoderclicn
     |
     | 请求 SAT
     v
SDK 宿主中的 fetchServiceAccountToken 回调
     |
     | 使用宿主保管的 Service Account Key 调用 Token exchange
     v
Qoder Token exchange
     |
     `----> 回调把短期 SAT 返回给 qoderclicn
下面的示例展示了完整的宿主刷新逻辑:
import {
  query,
  serviceAccount,
  type ServiceAccountTokenResult,
} from '@qodercn-ai/qodercn-agent-sdk';

// Get the Service Account key from the host's secret manager adapter.
const serviceAccountKey = await readSecret('qoder-service-account-key');
// Example: select the scopes used for model listing and inference.
const serviceAccountScopes = ['models.read', 'chat.completions'];

async function fetchServiceAccountToken(): Promise<ServiceAccountTokenResult> {
  const response = await fetch(
    'https://openapi.qoder.com.cn/api/v1/serviceToken/exchange',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${serviceAccountKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        grant_type: 'client_credentials',
        audience: 'qoder',
        scope: serviceAccountScopes.join(' '),
        ttl_seconds: 3600,
      }),
    },
  );

  if (!response.ok) {
    throw new Error(`Unable to obtain a Qoder SAT: HTTP ${response.status}`);
  }

  const result = (await response.json()) as {
    access_token: string;
    expires_in?: number;
  };

  return {
    token: result.access_token,
    expiresAt:
      result.expires_in === undefined
        ? undefined
        : Date.now() + result.expires_in * 1000,
  };
}

const q = query({
  prompt: 'Summarize the current deployment configuration.',
  options: {
    auth: serviceAccount({ fetchServiceAccountToken }),
  },
});
scope 和回调返回值的含义如下:
  • 换取 SAT 时,填写希望 SAT 包含的 scope。例如,需要获取模型列表并调用推理接口时,可以填写 models.read chat.completions。
  • 将 Token exchange 返回的 SAT 放入回调结果;也可以同时提供 SAT 的过期时间。
  • 无法取得有效 SAT 时返回 null(Python 中为 None)或抛出异常,让当前会话明确失败。
  • Java 的回调返回 CompletionStage<SecretValue>,只有 token,没有过期时间字段;取不到 token 时返回异常完成的 stage。SDK 向 CLI 报告固定的错误信息,不转发回调的失败详情。
  • Java 的回调在客户端的 callbackExecutor 上调用。HTTP 请求等耗时工作放到应用自己的执行器上,像上例一样通过返回的 stage 报告结果,不要阻塞回调线程。
  • HTTP 请求要设置连接和读取超时,并在结束时释放连接。HttpURLConnection 默认无限等待。SDK 对回调的超时(controlRequestTimeoutMs)只结束这次认证请求,不会中断仍在进行的 HTTP 调用;对端不响应时,任务会一直占用执行器线程,后续认证请求排在它后面,JVM 也无法正常退出。

复用本机登录态

本机已经通过 qoderclicn 登录时,可以让 SDK 使用同一登录态。该方式适合开发者工作站,不适合无状态 CI 或生产服务。
import { qodercliAuth, query } from '@qodercn-ai/qodercn-agent-sdk';

const q = query({
  prompt: 'Summarize the current workspace.',
  options: {
    auth: qodercliAuth(),
  },
});
Java 的 CLI 子进程默认只继承 PATH。CLI 读取登录态依赖的 HOME 等变量需要显式传入,见子进程环境(Java)。

认证失败回调(TypeScript、Python)

远端拒绝 token、token 过期或 CLI 以认证错误退出时,可以使用 onAuthExpired(TypeScript)/ on_auth_expired(Python)触发重新登录或换 token 流程。每个 SDK 会话最多触发一次。
from qodercn_agent_sdk import QoderAgentOptions, access_token_from_env

def show_sign_in_required() -> None:
    print("Authentication has expired. Please sign in again.")

options = QoderAgentOptions(
    auth=access_token_from_env(),
    on_auth_expired=show_sign_in_required,
)
SDK 不会自动刷新 PAT。拿到新 token 后应创建新的会话并传入新的 auth 配置。

错误

Python SDK 认证配置错误会抛出带 code 的异常:
  • 缺少认证配置:AuthNotConfiguredError,code == "auth_not_configured"。
  • PAT 环境变量未设置:AuthAccessTokenEnvVarError,code == "auth_access_token_env_var_not_configured"。
  • Service Account Key 环境变量未设置:AuthServiceAccountEnvVarError,code == "auth_service_account_env_var_not_configured"。
Java SDK 的认证错误都继承 QoderException,用 getCode() 区分:
  • 没有调用 auth():QoderClient.Builder.build() 同步抛出 InvalidOptionsException,getCode() 为 INVALID_OPTIONS。
  • Auth.accessTokenEnv() 读取的变量未设置或为空:启动时以 AuthenticationException 失败,getCode() 为 AUTHENTICATION_FAILED。
  • CLI 以认证失败退出(退出码 41):AuthenticationException,getCode() 为 CLI_AUTH_FATAL。Java 没有认证失败回调,应用捕获该异常后提示重新登录或更换凭据,再创建新的查询或会话。
启动和运行中的失败从消息游标、consume() 返回的 stage 或 openSession() 返回的 stage 交付。完整列表见 Java 异常一览。

子进程环境(Java)

Java SDK 启动 CLI 子进程时默认只继承父进程的 PATH。Windows 上另外继承 SystemRoot、TEMP、TMP、USERPROFILE、APPDATA、LOCALAPPDATA 等系统目录变量。CLI 需要的其他变量,例如 HOME、语言环境和代理地址,用 SessionOptions.Builder 显式传入:
Java
SessionOptions.Builder options = SessionOptions.builder();
String home = System.getenv("HOME");
if (home != null) {
    options.putEnvironment("HOME", home);
}
String proxy = System.getenv("HTTPS_PROXY");
if (proxy != null) {
    options.putProxyEnvironment("HTTPS_PROXY", SecretValue.of(proxy));
}
SessionOptions sessionOptions = options.build();
  • putEnvironment(String, String) 添加一个变量,environment(Map) 批量添加,不会清掉之前设置的变量。
  • 代理地址可能带用户名和密码,用 putProxyEnvironment(String, SecretValue) 传入。SDK 会在诊断和 stderr 中对其中的凭据脱敏;用 putEnvironment 传入 HTTPS_PROXY 等代理变量时也按同样方式处理。
  • QODERCN_PERSONAL_ACCESS_TOKEN 等由 SDK 管理的凭据变量不会传给 CLI,显式传入也会被剥除。认证只能通过 Auth 配置。

最佳实践

  • 生产和 CI 中通过密钥管理服务提供凭证,不要在源码中写入凭证。
  • 不要把 PAT、Service Account Key 或 SAT 写入日志、错误对象或调试输出。
  • 自动化环境显式配置 PAT 或 Service Account,不要依赖本机 qoderclicn 登录态。
  • 对用户可见应用注册认证失败回调,把认证失败转成明确的登录提示。
  • 更新或轮换凭证后创建新的 SDK 会话,不要复用已经认证失败的会话。