我把claude code cli变成了一个Prism provider
事情是这样的。我在一个 Laravel 应用上工作,几乎所有功能都会触碰到大语言模型(LLM):从 PDF 提取结构化数据、重写段落、摘要、拼写检查、生成内容。所有这些都通过 Prism 来完成——这很方便,因为只需写Prism::text()->using('gemini', 'gemini-2.5-flash'),就不用操心 HTTP 客户端了。但 API 账单依然是账单。而且我已经每月付费订阅了 Claude Code,它在我睡觉的时候就闲置着。然后我意识到:Claude Code CLI 本质上是一个二进制文件,它接收标准输入,向标准输出打印 JSON。这基本就是一个 LLM API,只不过多了几步。那么,为什么不让 Prism 和它对话呢?这篇文章就是我的实现过程。整套方案只有一个类。
Prism 到底需要 provider 做什么
Prism 并不神秘。一个 provider 就是一个继承 Prism\Prism\Providers\Provider 的类,并实现你关心的那些方法。你不需要实现所有方法。我只需要两个:
text(TextRequest $request): TextResponsestructured(StructuredRequest $request): StructuredResponse
不需要嵌入、图片、流式传输。如果有代码在我的 provider 上调用 ->asStream(),它会报错——但那无所谓,因为我的应用里没有人这样调用。
CLI 契约
先看看这个二进制文件返回什么:
echo "say hi" | claude -p --output-format json --model claude-haiku-4-5
你会得到一个这样的信封:
{
"is_error" : false,
"result" : "hi!",
"session_id" : "abc-123",
"model" : "claude-haiku-4-5",
"usage" : {
"input_tokens" : 12,
"output_tokens" : 3,
"cache_read_input_tokens" : 0
}
}
这就是 Prism 需要的一切。result 是文本,usage 直接映射到 Prism 的 Usage,session_id 和 model 映射到 Meta。完美。
我把 Claude Code CLI 变成了一个 Prism Provider
provider app/Prism/ClaudeCli/ClaudeCliProvider.php:
final class ClaudeCliProvider extends Provider
{
public function __construct(
private readonly string $binary,
private readonly ?string $oauthToken,
private readonly string $defaultModel,
private readonly int $timeout,
) {}
public function text(TextRequest $request): TextResponse
{
$envelope = $this->invoke(
$this->resolveModel($request->model()),
$this->composeSystemPrompt($request->systemPrompts()),
$this->composeUserPrompt($request->prompt(), $request->messages()),
$this->timeoutFor($request->clientOptions()),
);
return new TextResponse(
steps: new Collection,
text: $this->resultText($envelope),
finishReason: FinishReason::Stop,
toolCalls: [],
toolResults: [],
usage: $this->usage($envelope),
meta: $this->meta($envelope),
messages: new Collection,
);
}
}
整体结构就是:拿到 Prism 请求,压平为字符串,调外部命令,再拼回 Prism 响应。整个过程不涉及 AI,纯粹是管道胶水代码。