Appearance
Demo:最小 Faux Provider
这个 Demo 不依赖 pi 的任何安装,用工程化的目录结构复现 @pi/ai 的核心抽象:消息模型 + 事件流 + StreamFunction 契约 + Faux Provider。
工程化结构
Demo 不再是一个单文件,而是仿照 pi 的包组织,分模块放在 pi-principles/:
text
pi-principles/
├── package.json # scripts:cli / chat / agent / app / repl / ai ...
├── tsconfig.json
├── src/ # 各模块源码(复现 pi 各包)
│ ├── config.ts # .env 配置加载(OPENAI_BASE_URL/KEY/MODEL)
│ ├── ai/ # ← 本 Demo 相关
│ │ ├── types.ts # 消息模型(user/assistant/toolResult、Context、Tool)
│ │ ├── event-stream.ts # EventStream + AssistantMessageEventStream
│ │ ├── stream-function.ts # StreamFn 契约
│ │ ├── stream-resolver.ts # 真实 OpenAI / 离线假模型自动选择
│ │ ├── faux-provider.ts # 假模型:切片流式输出
│ │ ├── api/ # 协议实现(适配器)
│ │ │ ├── openai-responses.ts # createOpenAIStreamFn(真实模型)
│ │ │ └── openai-messages.ts # pi ⇄ OpenAI 双向转换
│ │ └── utils/sse.ts # SSE 解析
│ ├── agent/ ✓
│ ├── tools/ ✓ # calculator / bash / read
│ ├── protocol/ ✓
│ ├── transport/ ✓
│ ├── tui/ ✓
│ └── cli/ ✓ # CLI:args/main/chat/agent/app/repl
└── play/ # 模块化离线入口
├── ai.ts # ← 本 Demo 的入口
├── event-stream.ts / protocol.ts / transport.ts / tui.ts / mini-agent.ts关键实现
消息模型(src/ai/types.ts)
ts
export type Message =
| { role: "user"; content: string | TextContent[]; timestamp: number }
| { role: "assistant"; content: ContentBlock[]; stopReason: StopReason; timestamp: number }
| { role: "toolResult"; toolCallId: string; toolName: string; content: TextContent[]; isError; timestamp };事件流(src/ai/event-stream.ts)
ts
export class EventStream<T, R> implements AsyncIterable<T> {
// ...
push(event: T): void; // 生产者推送
end(result?: R): void; // 生产者结束
result(): Promise<R>; // 消费者等最终结果
async *[Symbol.asyncIterator]() { ... } // 消费者逐个消费
}Faux Provider(src/ai/faux-provider.ts)
ts
export function createFauxProvider(): FauxCore;
// FauxCore.stream(context, options) → AssistantMessageEventStream
// 按 2 个字符切成 text_delta 增量,模拟打字机;错误也编码进流,绝不 throw。入口与运行
play/ai.ts 演示:消费事件流看到增量 → result() 拿最终消息 → 验证"中止也编码进流而非抛错"。
bash
cd pi-principles
bun play/ai.ts # 或 npm run ai输出:
当前累积: 假模
当前累积: 假模型:
...(逐字累积)
最终消息: 假模型:收到 1 条消息,系统提示「你是教学用假模型」
中止时通过 result() 拿到(而非抛错),stopReason = aborted对应到真实源码
| 工程化 Demo | 真实 pi 源码 |
|---|---|
src/ai/event-stream.ts | packages/ai/src/utils/event-stream.ts |
src/ai/stream-function.ts | packages/agent/src/types.ts:28 |
src/ai/faux-provider.ts | packages/ai/src/providers/faux.ts |
理解要点
- 事件流"可迭代 + 可等待":
for await消费增量,result()拿最终结果。 - 契约"不 throw":中止也通过
error事件结束,保证事件序列完整。 - 工程化分文件后,每个类一个职责,和 pi 真实的包组织一致。
下一步:2.1 核心 Agent 循环。