jaguarjack / workbuddy-openapi
workbuddy openapi PHP SDK
Requires
- php: ^8.1
- symfony/http-client: ^6.4
Requires (Dev)
- phpunit/phpunit: ^10.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
WorkBuddy OpenAPI 的 PHP SDK。公开接口采用显式静态 Facade,每个动作由独立 Action 类实现。
当前状态:REST SDK 与 ACP 实时通道已实现。公开 API 与协议风险见 架构文档。
安装
composer install
项目发布后,使用方通过 Composer 引入:
composer require jaguarjack/workbuddy-openapi
面向 AI Agent 的使用约定
AI Agent 在操作本仓库或使用本 SDK 时遵循以下顺序:
- 读取本 README 与
docs/architecture.md。 - 单用户流程使用
WorkBuddy::configure();多用户流程使用WorkBuddy::using()和withTokens()。 - 仅调用 README 接口表中列出的公开方法。
- 使用具名参数,让调用意图保持清晰。
- 捕获
WorkBuddyException;通过ErrorCode、HTTP 状态和requestId决定后续动作。 sendLocalAssistantMessage、权限响应、ACP prompt、createTask、redeemCode、redeemGift会产生外部副作用,执行前取得用户确认。- 凭证只从环境变量或服务端密钥存储读取;日志统一脱敏。
- 创建任务与发送消息遇到网络超时时,先查询资源状态;兑换请求复用同一个
requestId。
目标用法
use Workbuddy\Openapi\Config; use Workbuddy\Openapi\Enums\ErrorCode; use Workbuddy\Openapi\Exceptions\ApiException; use Workbuddy\Openapi\WorkBuddy; WorkBuddy::configure(Config::fromEnvironment()); try { $task = WorkBuddy::createTask( prompt: '整理今天的会议纪要', name: '会议纪要', ); $task = WorkBuddy::getTask(taskId: $task->id); } catch (ApiException $exception) { if ($exception->errorCode === ErrorCode::InsufficientScope) { // 引导用户补充授权范围。 } // requestId 可用于服务端问题排查。 report($exception->requestId); }
实例模式
多用户、队列 Worker、Swoole 和 RoadRunner 使用独立实例:
$app = WorkBuddy::using(new Config( clientId: $clientId, clientSecret: $clientSecret, redirectUri: $redirectUri, )); $alice = $app->withTokens($aliceTokens); $bob = $app->withTokens($bobTokens); $aliceTasks = $alice->listTasks(); $bobTasks = $bob->listTasks();
应用配置只创建一次。每次 withTokens() 返回新的 WorkBuddyClient,复用 Symfony HTTP Client,并独立持有用户 Token、REST Transport 与 ACP Transport。传统单用户调用可继续使用静态 WorkBuddy::configure()。
OAuth 回调可以直接衔接:
$tokens = $app->exchangeAuthorizationCode( code: $code, returnedState: $state, expectedState: $expectedState, ); $userClient = $app->withTokens($tokens);
配置
WORKBUDDY_CLIENT_ID=app_xxx WORKBUDDY_CLIENT_SECRET=sk_xxx WORKBUDDY_REDIRECT_URI=https://example.com/callback WORKBUDDY_ACCESS_TOKEN=xxx WORKBUDDY_REFRESH_TOKEN=xxx
client_secret 适用于服务端应用。授权回调必须使用与开放平台登记值字节级一致的 redirect_uri。
动作索引
客户端生命周期
| 方法 | 用途 | 返回 |
|---|---|---|
WorkBuddy::using(Config, ?HttpClientInterface) |
创建独立应用客户端 | WorkBuddyClient |
WorkBuddy::configure(Config, ?HttpClientInterface) |
配置静态默认客户端 | void |
$client->withTokens(TokenSet) |
派生用户授权客户端 | WorkBuddyClient |
WorkBuddy::withTokens(TokenSet) |
从静态默认客户端派生用户客户端 | WorkBuddyClient |
完整接口表
静态 WorkBuddy 与实例 WorkBuddyClient 提供相同业务方法。
| 方法 | HTTP / 协议 | Scope / 凭证 | 返回 | 副作用 |
|---|---|---|---|---|
getAuthorizationUrl(array $scopes = [], ?string $state = null) |
GET /authorize |
client_id |
AuthorizationRequest |
打开授权流程 |
exchangeAuthorizationCode(string $code, string $returnedState, string $expectedState) |
POST /token form |
应用凭证 | TokenSet |
创建 Token |
refreshAccessToken(?string $refreshToken = null) |
POST /token form |
应用凭证 | TokenSet |
轮换 Token |
getProfile() |
GET /user/profile |
user.profile.readable |
Profile |
只读 |
verifyPhone(string $phoneNumber) |
POST /user/phoneverification JSON |
user.contact.readable |
bool |
只读校验 |
getLocalAssistantStatus() |
GET /localassistant |
user.localassistant.readable |
LocalAssistantStatus |
只读 |
sendLocalAssistantMessage(string $content, MessageType $type) |
POST /localassistant/message |
user.localassistant.invokable |
SentMessage |
触发本地执行 |
respondToLocalAssistantPermission(string $requestId, array $answers) |
POST /localassistant/message |
user.localassistant.invokable |
SentMessage |
回答 AskQuestion |
listLocalAssistantMessages(int $limit = 20, int $offset = 0, ?string $afterMessageId = null) |
GET /localassistant/message |
user.localassistant.readable |
MessagePage |
只读 |
createTask(string $prompt, ?string $name = null) |
POST /tasks |
user.task.invokable |
Task |
创建云任务 |
listTasks(int $page = 1, int $size = 20) |
GET /tasks |
user.task.readable |
TaskPage |
只读 |
getTask(string $taskId) |
GET /tasks/{task_id} |
user.task.readable |
Task |
只读 |
listArtifacts(string $taskId, ?ArtifactType $type = null, ?string $sessionId = null, int $startMs = 0, int $endMs = 0, int $limit = 0, int $offset = 0) |
GET {sandbox}/api/session/artifacts |
任务 Token | ArtifactPage |
只读 |
downloadArtifact(string $taskId, Artifact $artifact) |
Artifact URL | 签名 URL 或任务 Token | string |
下载文件 |
redeemCode(string $code, string $requestId) |
POST /redemptions |
user.credit.exchange |
Redemption |
发放积分 |
redeemGift(string $giftKey, string $giftCode, string $requestId) |
POST /redemptions |
user.credit.exchange |
Redemption |
发放积分 |
openAcpConnection(string $taskId) |
ACP GET SSE |
任务 Token | AcpConnection |
打开长连接 |
initializeAcp(AcpConnection $connection) |
JSON-RPC initialize |
ACP 连接 | array |
协商协议 |
loadAcpSession(AcpConnection $connection, string $cwd = '/workspace', array $mcpServers = []) |
JSON-RPC session/load |
ACP 连接 | array |
加载会话 |
promptAcp(AcpConnection $connection, string|array $prompt) |
JSON-RPC session/prompt + SSE |
ACP 连接 | Generator<AcpEvent> |
执行 Prompt |
respondToAcpPermission(AcpConnection $connection, string|int $requestId, array $result) |
JSON-RPC response | ACP 连接 | void |
回复权限请求 |
respondToAcpRequest(AcpConnection $connection, string|int $requestId, array $result) |
JSON-RPC response | ACP 连接 | void |
回复通用服务端请求 |
closeAcpConnection(AcpConnection $connection) |
本地操作 | ACP 连接 | void |
关闭长连接 |
返回模型
| 类型 | 公开字段 |
|---|---|
TokenSet |
accessToken, tokenType, refreshToken, expiresIn, scope, openId |
Profile |
nickname, avatar |
Message |
id, role, content, type, rawType, createdAt, attachments, metadata |
Task |
id, status, rawStatus, name, link, token, expiresAt, sandboxLink, sandboxDataLink, createdAt, updatedAt |
Artifact |
Entry 元数据、公共字段,以及 plan/tasks/media/overview 分类型字段 |
ArtifactPage |
sessionId, artifacts, total, returned, limit, offset, hasMore, filterType, startMs, endMs |
AcpEvent |
type, id, method, params, result, error, stopReason, rawStopReason, usage, raw |
AcpException |
errorCode, httpStatus, requestId, retryable, context |
未知枚举值映射为 Unknown,原始值保留在对应 DTO 中。响应字段类型异常统一抛出 ApiException(ErrorCode::InvalidResponse)。
Artifact 下载
$page = $userClient->listArtifacts( taskId: $taskId, type: ArtifactType::Media, limit: 50, ); $bytes = $userClient->downloadArtifact($taskId, $page->artifacts[0]);
下载优先使用 Entry 的直接 url。该 URL 按签名地址访问;agent:/// fallback 使用任务 Token 请求 sandbox。
Token 刷新
$userClient = $userClient->withTokens( $userClient->refreshAccessToken(), );
刷新响应包含新 refresh_token 时完成轮换;省略该字段时沿用当前值。Token 的持久化方式由接入应用决定。
官方契约说明
- 手机验证端点表标注 GET,请求示例使用 POST JSON;SDK 采用可承载
phone_number的 POST 示例。 - Token scope 示例出现
task.write;SDK 的任务接口使用端点表定义的user.task.readable与user.task.invokable。 - Task link 示例存在
/sessions/{id}与/acp两种形态;Artifact 优先按/acp规则派生地址,并兼容sandboxLink。 - ACP 按
open → initialized → loaded → prompting → loaded → closed校验调用顺序。
ACP 实时会话
use Workbuddy\Openapi\Enums\AcpEventType; $connection = WorkBuddy::openAcpConnection(taskId: $task->id); try { WorkBuddy::initializeAcp($connection); WorkBuddy::loadAcpSession($connection); foreach (WorkBuddy::promptAcp($connection, '继续整理结果') as $event) { if ($event->type === AcpEventType::PermissionRequest) { WorkBuddy::respondToAcpPermission( connection: $connection, requestId: $event->id, result: ['outcome' => 'approved'], ); } if ($event->type === AcpEventType::FinalResponse) { $stopReason = $event->result['stopReason'] ?? null; } } } finally { WorkBuddy::closeAcpConnection($connection); }
promptAcp() 每条连接只运行一个活跃 prompt。调用方完整消费事件流,或在提前结束时关闭连接。传输中断会抛出 AcpException;prompt 已提交后的中断使用 ErrorCode::AcpOutcomeUnknown 表达结果未知。
错误处理
SDK 使用异常表达失败,使用枚举表达稳定错误语义:
try { $profile = WorkBuddy::getProfile(); } catch (ApiException $exception) { match ($exception->errorCode) { ErrorCode::InvalidToken => refreshTokenAndRetryOnce(), ErrorCode::InsufficientScope => requestRequiredScope(), ErrorCode::RateLimited => retryWithBackoff($exception->retryAfter), default => reportToWorkBuddy($exception->requestId), }; }
HTTP/API 错误尽量保留上游错误码、HTTP 状态、request_id 与脱敏上下文。SDK 本地校验和响应结构错误使用对应错误枚举,服务端元数据字段可能为空。完整层次见 错误模型。
官方文档
- WorkBuddy OpenAPI
- 基础地址:
https://www.workbuddy.cn/openapi/v2
开发验证
composer test
composer validate --strict
HTTP 客户端使用 Symfony HttpClient 6.4,最低支持 PHP 8.1。本地 PHPUnit 运行于 PHP 8.3,CI 覆盖 PHP 8.1–8.4。