Merge remote-tracking branch 'origin/master' into worktree/pi-ai-manual-e2e

This commit is contained in:
Yichen Jiang
2026-07-19 13:39:27 +08:00
438 changed files with 15534 additions and 5146 deletions
+3 -1
View File
@@ -2,6 +2,8 @@
Generic multi-provider adapter for the harness LLM seam backed by [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai). One plugin instance owns an explicit list of provider profiles; every request selects a profile with `GenerateOptions.provider` and resolves `GenerateOptions.model` dynamically from pi-ai's installed catalog.
The package root exposes the Cordis plugin contract and `PiAiAdapter`; profile resolution, model construction, replay conversion, and stream conversion remain package-internal.
## Config
Configure credentials and deployment-specific transport settings per provider. Omitting `apiKey` delegates authentication to pi-ai's provider-native ambient discovery. `baseURL` overrides only the endpoint of the selected catalog model, preserving its API family and compatibility metadata, so private proxies such as `https://proxy.example.com:8443` remain supported.
@@ -76,4 +78,4 @@ Unit tests use pi-ai catalog models redirected to local mock servers and cover p
- **Catalog membership is required** — custom model ids that are absent from the installed pi-ai catalog fail with `UNKNOWN_MODEL`, even when a provider profile supplies a custom endpoint.
- **`GenerateOptions.stop` is unsupported** — pi-ai's common stream options cannot guarantee stop-sequence behavior across providers, so the adapter rejects the field.
- **In-history `system` messages use pi-ai's common context conversion** — provider-specific placement follows pi-ai rather than a harness-owned wire override.
- **`LlmError.status` is unavailable for in-stream failures** — pi-ai error events do not expose a stable HTTP status across providers.
- **Provider HTTP status is unavailable** — pi-ai error events do not expose a stable HTTP status across providers; failures expose only stable harness error codes.
+1 -5
View File
@@ -27,12 +27,8 @@ import { Config, resolveProfiles } from './config.ts'
export { PiAiAdapter } from './adapter.ts'
export type { PiAiAdapterOptions } from './adapter.ts'
export { Config, resolveProfiles } from './config.ts'
export { Config } from './config.ts'
export type { PiAiProviderProfile } from './config.ts'
export { toPiContext } from './context.ts'
export { toPiReplayState } from './replay.ts'
export type { PiAiReplayState } from './replay.ts'
export { mapStopReason, mapUsage, toStreamChunks } from './stream.ts'
export const name = 'llm-pi-ai'
export const inject = ['llm']
+14 -1
View File
@@ -4,7 +4,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import LlmService, { LlmError, userAgent } from '@deepseek-ai/dsh-llm'
import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai'
import { PiAiAdapter, resolveProfiles } from '@deepseek-ai/dsh-llm-pi-ai'
import { PiAiAdapter } from '@deepseek-ai/dsh-llm-pi-ai'
import { resolveProfiles } from '../src/config.ts'
import { assemble } from './assemble.ts'
interface MockServer {
@@ -180,6 +181,18 @@ describe('PiAiAdapter provider routing', () => {
})
describe('provider profile lifecycle', () => {
it('keeps adapter helpers off the package root', () => {
for (const helper of [
'resolveProfiles',
'toPiContext',
'toPiReplayState',
'toPiAssistant',
'mapStopReason',
'mapUsage',
'toStreamChunks',
]) expect(LlmPiAi).not.toHaveProperty(helper)
})
it('registers every profile atomically and unregisters on dispose', async () => {
const ctx = new Context()
await ctx.plugin(LlmService)
+3 -1
View File
@@ -2,7 +2,9 @@ import { describe, expect, it } from 'vitest'
import { CallId, LlmError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm'
import type { AssistantMessage, AssistantMessageEvent, Usage } from '@earendil-works/pi-ai'
import { mapStopReason, mapUsage, toPiContext, toPiReplayState, toStreamChunks } from '@deepseek-ai/dsh-llm-pi-ai'
import { toPiContext } from '../src/context.ts'
import { toPiReplayState } from '../src/replay.ts'
import { mapStopReason, mapUsage, toStreamChunks } from '../src/stream.ts'
function usage(input = 0, output = 0, cacheRead = 0, cacheWrite = 0): Usage {
return {