Explorar el Código

refactor: hide llm adapter helpers

Tianyi Cui hace 2 meses
padre
commit
91075e010a

+ 2 - 2
docs/config-catalog.md

@@ -389,7 +389,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/llm/llm-deepseek/src/index.ts:43`](../packages/llm/llm-deepseek/src/index.ts)
+Source: [`packages/llm/llm-deepseek/src/index.ts:40`](../packages/llm/llm-deepseek/src/index.ts)
 
 ## `@deepseek-ai/dsh-llm-pi-ai`
 
@@ -420,7 +420,7 @@ export interface Config {
 export type PiAiReasoning = 'off' | 'high' | 'xhigh'
 ```
 
-Source: [`packages/llm/llm-pi-ai/src/index.ts:37`](../packages/llm/llm-pi-ai/src/index.ts)
+Source: [`packages/llm/llm-pi-ai/src/index.ts:36`](../packages/llm/llm-pi-ai/src/index.ts)
 
 ## `@deepseek-ai/dsh-llm-replay`
 

+ 2 - 0
packages/llm/llm-deepseek/README.md

@@ -4,6 +4,8 @@ DeepSeek chat-completions adapter for the harness LLM seam: hand-rolled `fetch`
 
 A second, independent implementation of the same seam exists in `@deepseek-ai/dsh-llm-pi-ai` (library-backed). Same Config shape — pick one per context (registering both for the same model names throws by design).
 
+The package root exposes the Cordis plugin contract and `DeepSeekAdapter`; wire serialization, SSE parsing, and chunk translation helpers are not part of that root contract.
+
 ## Config
 
 ```yaml

+ 1 - 4
packages/llm/llm-deepseek/src/index.ts

@@ -23,12 +23,9 @@ import z from 'schemastery'
 import type {} from '@deepseek-ai/dsh-llm'
 import { DeepSeekAdapter } from './adapter.ts'
 
-export { DeepSeekAdapter, httpErrorCode } from './adapter.ts'
+export { DeepSeekAdapter } from './adapter.ts'
 export type { DeepSeekAdapterOptions } from './adapter.ts'
-export { serializeMessages, serializeRequest } from './serialize.ts'
 export type { RequestDefaults } from './serialize.ts'
-export { DONE, parseSse } from './sse.ts'
-export { mapFinishReason, mapUsage, translate } from './translate.ts'
 export type * from './types.ts'
 
 export const name = 'llm-deepseek'

+ 15 - 1
packages/llm/llm-deepseek/tests/adapter.spec.ts

@@ -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 LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
-import { DeepSeekAdapter, httpErrorCode } from '@deepseek-ai/dsh-llm-deepseek'
+import { DeepSeekAdapter } from '@deepseek-ai/dsh-llm-deepseek'
+import { httpErrorCode } from '../src/adapter.ts'
 import { assemble } from './assemble.ts'
 
 /** One scripted behavior for the next request the mock server receives. */
@@ -234,6 +235,19 @@ describe('DeepSeekAdapter against a mock server', () => {
 })
 
 describe('plugin registration and config', () => {
+  it('keeps wire helpers off the package root', () => {
+    for (const helper of [
+      'httpErrorCode',
+      'serializeMessages',
+      'serializeRequest',
+      'DONE',
+      'parseSse',
+      'mapFinishReason',
+      'mapUsage',
+      'translate',
+    ]) expect(LlmDeepSeek).not.toHaveProperty(helper)
+  })
+
   it('registers the configured models and unregisters on dispose (HMR safety)', async () => {
     const server = await mockServer([])
     const ctx = new Context()

+ 1 - 1
packages/llm/llm-deepseek/tests/serialize.spec.ts

@@ -1,7 +1,7 @@
 import { describe, expect, it } from 'vitest'
 import { CallId } from '@deepseek-ai/dsh-llm'
 import type { ContentBlock, GenerateOptions, Message } from '@deepseek-ai/dsh-llm'
-import { serializeMessages, serializeRequest } from '@deepseek-ai/dsh-llm-deepseek'
+import { serializeMessages, serializeRequest } from '../src/serialize.ts'
 
 function request(overrides: Partial<GenerateOptions> = {}): GenerateOptions {
   return { model: 'deepseek-v4-flash', messages: [], ...overrides }

+ 1 - 1
packages/llm/llm-deepseek/tests/sse.spec.ts

@@ -1,6 +1,6 @@
 import { describe, expect, it } from 'vitest'
 import { LlmError } from '@deepseek-ai/dsh-llm'
-import { DONE, parseSse } from '@deepseek-ai/dsh-llm-deepseek'
+import { DONE, parseSse } from '../src/sse.ts'
 
 /** Build a byte stream from string fragments (fragments = network reads). */
 async function* bytes(...fragments: (string | Uint8Array)[]): AsyncGenerator<Uint8Array> {

+ 2 - 1
packages/llm/llm-deepseek/tests/translate.spec.ts

@@ -1,7 +1,8 @@
 import { describe, expect, it } from 'vitest'
 import { BlockAssembler, LlmError } from '@deepseek-ai/dsh-llm'
 import type { StreamChunk } from '@deepseek-ai/dsh-llm'
-import { DONE, mapFinishReason, mapUsage, translate } from '@deepseek-ai/dsh-llm-deepseek'
+import { DONE } from '../src/sse.ts'
+import { mapFinishReason, mapUsage, translate } from '../src/translate.ts'
 
 async function* feed(...payloads: (string | object)[]): AsyncGenerator<string> {
   for (const payload of payloads) {

+ 2 - 0
packages/llm/llm-pi-ai/README.md

@@ -2,6 +2,8 @@
 
 DeepSeek adapter for the harness LLM seam backed by [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai) (the LLM library behind the pi agent).
 
+The package root exposes the Cordis plugin contract and `PiAiAdapter`; model construction and event-conversion helpers are not part of that root contract.
+
 ## Why a second adapter exists
 
 `@deepseek-ai/dsh-llm-deepseek` already talks to the same endpoint. This package is its **design-verification twin**: same models, same wire protocol, completely different internals — a unified LLM library with its own event vocabulary versus hand-rolled fetch/SSE. Anything the harness `StreamChunk` protocol cannot express for BOTH implementations is a core-vocabulary bug. The differences it exercised on purpose:

+ 1 - 2
packages/llm/llm-pi-ai/src/index.ts

@@ -22,9 +22,8 @@ import type {} from '@deepseek-ai/dsh-llm'
 import { PiAiAdapter } from './adapter.ts'
 import type { PiAiReasoning } from './adapter.ts'
 
-export { buildModel, PiAiAdapter } from './adapter.ts'
+export { PiAiAdapter } from './adapter.ts'
 export type { PiAiAdapterOptions, PiAiReasoning } from './adapter.ts'
-export { mapStopReason, mapUsage, toPiContext, toStreamChunks } from './convert.ts'
 
 export const name = 'llm-pi-ai'
 export const inject = ['llm']

+ 8 - 1
packages/llm/llm-pi-ai/tests/adapter.spec.ts

@@ -4,7 +4,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
 import { Context } from 'cordis'
 import LlmService, { CallId, userAgent } from '@deepseek-ai/dsh-llm'
 import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai'
-import { buildModel, PiAiAdapter } from '@deepseek-ai/dsh-llm-pi-ai'
+import { PiAiAdapter } from '@deepseek-ai/dsh-llm-pi-ai'
+import { buildModel } from '../src/adapter.ts'
 import { assemble } from './assemble.ts'
 
 /** Scripted SSE responses, one per request (OpenAI chat-completions shape). */
@@ -245,6 +246,12 @@ describe('PiAiAdapter against a mock server', () => {
 })
 
 describe('option spreads and env fallbacks', () => {
+  it('keeps adapter conversion helpers off the package root', () => {
+    for (const helper of ['buildModel', 'mapStopReason', 'mapUsage', 'toPiContext', 'toStreamChunks']) {
+      expect(LlmPiAi).not.toHaveProperty(helper)
+    }
+  })
+
   it('forwards temperature, maxTokens, and signal', async () => {
     const server = await mockServer([{ events: textEvents }])
     const ctx = await harness(server.url)

+ 1 - 1
packages/llm/llm-pi-ai/tests/convert.spec.ts

@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
 import { CallId } 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, toStreamChunks } from '@deepseek-ai/dsh-llm-pi-ai'
+import { mapStopReason, mapUsage, toPiContext, toStreamChunks } from '../src/convert.ts'
 
 function usage(input = 0, output = 0, cacheRead = 0, cacheWrite = 0): Usage {
   return {