feat(tui): add pi-ai Anthropic example

This commit is contained in:
Yichen Jiang
2026-07-26 13:30:01 +08:00
parent eeab643ad9
commit 970f893d6b
6 changed files with 73 additions and 3 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-14-provider-routed-llm-adapters.md: 98205d18d07752e0cdba86d7cba80368d45fd816
2026-07-14-provider-routed-llm-adapters.zh.md: c35225a86baf4c2d09732b5940abbc8046d365fb
2026-07-14-provider-routed-llm-adapters.md: a0f43751a17134f42e3f1a73879c79521fa7921b
2026-07-14-provider-routed-llm-adapters.zh.md: a8712166e74e323e3091251766fd26fefd0690d0

View File

@@ -84,6 +84,7 @@ The on-disk session format remains the pre-release pinned version `0`, with no c
- Unit coverage exercises registry conflicts, request reconstruction, session validation, profile resolution, single-attempt option forwarding, native API selection including OpenAI Responses, conversion, replay validation, error mapping, caller cancellation, idle-timeout transport termination, content rewrites, and same-instance versus different-instance replay dispatch.
- Keyless loop/session tests and ACP snapshots exercise durable provider/model metadata, resume and fork propagation, workflow/subagent overrides, and unchanged user-visible transcripts; the key-gated DeepSeek e2e retains real provider streaming and tool follow-up coverage.
- The TUI's `pi-ai-anthropic.cordis.yml` overlay disables the native provider owner, mounts an explicit Anthropic profile over deployment-supplied credentials and endpoint, and selects a catalog model. Its keyless PTY smoke opens `/model`, observes the profile's default reasoning effort, and selects the next adapter-advertised effort without provider I/O.
- Public JSDoc, package READMEs, architecture and core-data-structure docs, generated catalogs, examples, session fixtures, and Python SDK pairs use provider/model targets consistently and are checked by the repository documentation and type-equivalence gates.
## Risks

View File

@@ -84,6 +84,7 @@ JSON-RPC 运行时显式接收 provider 与 model。仅当 `deepseek` 提供方
- 单元测试覆盖注册表冲突、请求重建、会话验证、配置解析、单次请求的选项转发、包括 OpenAI Responses 在内的原生 API 选择、转换、回放验证、错误映射、调用方取消、空闲超时导致的传输终止、内容重写,以及同一实例与不同实例间的回放分发。
- 无密钥的 agent loop/会话测试和 ACP 快照覆盖持久化 provider/model 元数据、恢复与 fork 传播、工作流/subagent 覆盖,以及不变的用户可见 transcript文本记录密钥门控的 DeepSeek e2e 测试保留真实提供方的流式输出与工具后续调用覆盖率。
- TUI 的 `pi-ai-anthropic.cordis.yml` 覆盖配置会禁用原生提供方所有者,在部署提供的凭据和端点之上挂载显式 Anthropic 配置,并选择一个目录模型。其无密钥 PTY 冒烟测试会打开 `/model`,观察该配置的默认推理级别,并在不执行提供方 I/O 的情况下选择适配器公布的下一个推理级别。
- 公共 JSDoc、package README、架构与核心数据结构文档、生成目录、示例、会话 fixture测试前置数据和 Python SDK 配对文档统一使用 provider/model 目标,并由仓库文档与类型等价门禁校验。
## 风险

View File

@@ -13,6 +13,14 @@ pnpm run demo:tui
Both the demo script and the installable `dsh` CLI ([`apps/cli`](../../apps/cli/README.md)) boot this example's `cordis.yml` as the shipped default config; `dsh` additionally applies the personal overlay from `~/.dsh` and uses the invoking directory as the workspace.
### Anthropic-compatible pi-ai route
The checked-in `pi-ai-anthropic.cordis.yml` overlay disables the native DeepSeek adapter, mounts one explicit `anthropic` profile through `dsh-llm-pi-ai`, and selects `claude-sonnet-4-6` with `high` reasoning by default. It deliberately reuses `DEEPSEEK_API_KEY` and `DEEPSEEK_BASE_URL` for a deployment whose private endpoint accepts the Anthropic Messages protocol; a DeepSeek Chat Completions endpoint is not interchangeable. Run it from the repository root:
```sh
pnpm run demo:tui --config examples/tui-agent/pi-ai-anthropic.cordis.yml
```
Type a coding task. The agent works through the `read`/`write`/`edit` filesystem tools for ordinary file operations and `bash` (+ the generic `task_output` / `task_list` / `task_kill` for background tasks) for shell commands, searches, and test runs, each in a fresh `bash -c` (the system prompt tells the model to pass `workdir` instead of `cd`). Both the fs tools and bash resolve relative paths against the session workspace. It can also delegate with `subagent`/`subagent_fork`.
The `todo_write` task tracker is opt-in and not in the shipped config: add `@deepseek-ai/dsh-tool-todo` to `cordis.yml` (or a personal-config overlay under `~/.dsh`) to expose it. Once loaded, the model records a whole-list plan to the session log and the TUI renders it.
@@ -51,7 +59,7 @@ This example is a thin leaf `cordis.yml`: it picks the swappable backends, loads
| Entry | Demonstrates |
|---|---|
| `hmr` (`@cordisjs/plugin-hmr`) | the dev/demo edit-reload loop — a **leaf** entry (not baked into the app) because it depends on the Loader's internal module access |
| `llm-deepseek` | real `LlmAdapter` via config (`!!js process.env.…` secrets); swap one line to `@deepseek-ai/dsh-llm-pi-ai` for the library-backed twin |
| `llm-deepseek` | the default native adapter; `pi-ai-anthropic.cordis.yml` disables that owner and mounts an explicit library-backed Anthropic profile |
| `bash` (`dsh-bash-local`) | the executor implementation — the swappable half of the bash seam. The model-facing `bash` schema (`tool-bash`) and generic `task_*` controls (`tool-tasks`) come from `dsh-agent-spine-demo`, so only the executor is a leaf choice |
| `tui-agent` (`@deepseek-ai/dsh-tui-demo`) | the app bundle: the agent-spine demo + JSONL persistence + the pi-tui channel + a pre-created `main` agent |
| `subagent`, `subagent-spawn`, `subagent-fork` | the subagent provider registry plus the two in-process backends: a fresh child and a child seeded with the parent's completed-turn prefix |

View File

@@ -0,0 +1,39 @@
# Replace the native DeepSeek adapter with pi-ai's Anthropic route. The
# configured endpoint must implement the Anthropic Messages protocol even
# though this deployment reuses the repository's DeepSeek-named environment
# variables.
- id: base
name: '@cordisjs/plugin-include'
config:
path: ./cordis.yml
patches:
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
disabled: true
- id: tui-agent
name: '@deepseek-ai/dsh-tui-demo'
config:
provider: anthropic
model: claude-sonnet-4-6
resumeSessionId: !!js "typeof resumeSessionId === 'string' ? resumeSessionId : undefined"
persistenceRoot: './.sessions'
resumeCommand: 'dsh --config examples/tui-agent/pi-ai-anthropic.cordis.yml --resume {session}'
workspaceContext:
maxBytes: 65536
ui:
showReasoning: true
maxToolOutputLines: 6
persona: |
You are a coding agent powered by the {{model}} model.
Verify your work by running the code or tests. Keep answers brief and
factual.
- insert:
- id: llm-pi-ai
name: '@deepseek-ai/dsh-llm-pi-ai'
config:
providers:
- provider: anthropic
apiKey: !!js process.env.DEEPSEEK_API_KEY
baseURL: !!js process.env.DEEPSEEK_BASE_URL
reasoning: high

View File

@@ -11,6 +11,7 @@ import { runTuiPtySmoke, type TuiPtySmokeOptions } from './pty-harness.ts'
const dshBinScript = fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url))
const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
const codeModeConfigPath = fileURLToPath(new URL('../code-mode.cordis.yml', import.meta.url))
const piAiAnthropicConfigPath = fileURLToPath(new URL('../pi-ai-anthropic.cordis.yml', import.meta.url))
const scriptedConfigPath = fileURLToPath(new URL('./fixtures/tui-scripted.cordis.yml', import.meta.url))
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
@@ -246,6 +247,26 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('boots the pi-ai Anthropic overlay and selects an advertised reasoning effort', async () => {
const output = await smoke({
label: 'tui-agent pi-ai anthropic',
tempDirPrefix: 'tui-agent-pi-ai-anthropic-',
configPath: piAiAnthropicConfigPath,
env: {
DEEPSEEK_API_KEY: 'keyless-tui-no-call',
DEEPSEEK_BASE_URL: 'http://127.0.0.1:1',
},
actions: [
{ waitFor: 'main-session-', send: '/model\r' },
{ waitFor: 'Claude Sonnet 4.6 — High — current', send: '\x1b[Z\r' },
{ waitFor: 'Reasoning effort: Max.', send: '/exit\r' },
],
})
expect(output).toContain('anthropic/claude-sonnet-4-6')
expect(output).toContain('Reasoning effort: Max.')
expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
})
describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {