|
|
@@ -13,9 +13,9 @@
|
|
|
|
|
|
import { stream as piStream } from '@earendil-works/pi-ai'
|
|
|
import type { Model } from '@earendil-works/pi-ai'
|
|
|
-import { LlmAdapter, LlmError } from '@deepseek-ai/dsh-llm'
|
|
|
+import { LlmAdapter } from '@deepseek-ai/dsh-llm'
|
|
|
import { CallId } from '@deepseek-ai/dsh-llm'
|
|
|
-import type { GenerateOptions, StreamChunk, ToolSchema } from '@deepseek-ai/dsh-llm'
|
|
|
+import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
|
|
import { toPiContext, toStreamChunks } from './convert.ts'
|
|
|
|
|
|
/** Reasoning levels surfaced by this adapter (DeepSeek wire: high|max). */
|
|
|
@@ -61,7 +61,7 @@ export function buildModel(modelId: string, options: PiAiAdapterOptions): Model<
|
|
|
}
|
|
|
|
|
|
type Payload = {
|
|
|
- tools?: { function?: { name?: unknown; strict?: unknown } }[]
|
|
|
+ tools?: { function?: { strict?: unknown } }[]
|
|
|
messages?: {
|
|
|
role?: unknown
|
|
|
tool_calls?: { id?: unknown; function?: { arguments?: unknown } }[]
|
|
|
@@ -81,10 +81,6 @@ function rawToolArguments(options: GenerateOptions): Map<CallId, string> {
|
|
|
return raw
|
|
|
}
|
|
|
|
|
|
-function strictByToolName(tools: ToolSchema[] | undefined): Map<string, boolean | undefined> {
|
|
|
- return new Map((tools ?? []).map(tool => [tool.name, tool.strict]))
|
|
|
-}
|
|
|
-
|
|
|
function patchPayload(payload: unknown, options: GenerateOptions, reasoning: PiAiReasoning | undefined): unknown {
|
|
|
/* v8 ignore next -- pi-ai onPayload always receives an object; tolerate unusual future hooks defensively */
|
|
|
if (typeof payload !== 'object' || payload === null) return payload
|
|
|
@@ -97,16 +93,13 @@ function patchPayload(payload: unknown, options: GenerateOptions, reasoning: PiA
|
|
|
body.stop = options.stop
|
|
|
}
|
|
|
|
|
|
- const strictByName = strictByToolName(options.tools)
|
|
|
+ // pi-ai stamps its own `strict` default on every serialized tool; the
|
|
|
+ // harness tool contract has no strict field and the hand-rolled twin sends
|
|
|
+ // none, so scrub it for wire parity.
|
|
|
for (const tool of body.tools ?? []) {
|
|
|
/* v8 ignore next -- malformed pi-ai payload guard: real tool entries always carry function */
|
|
|
if (tool.function === undefined) continue
|
|
|
- const name = tool.function.name
|
|
|
- /* v8 ignore next -- malformed pi-ai payload guard: real function entries always carry a string name */
|
|
|
- if (typeof name !== 'string') continue
|
|
|
- const strict = strictByName.get(name)
|
|
|
- if (strict === undefined) delete tool.function.strict
|
|
|
- else tool.function.strict = strict
|
|
|
+ delete tool.function.strict
|
|
|
}
|
|
|
|
|
|
const rawById = rawToolArguments(options)
|
|
|
@@ -131,9 +124,9 @@ function patchPayload(payload: unknown, options: GenerateOptions, reasoning: PiA
|
|
|
*
|
|
|
* Implementation notes:
|
|
|
* - `onPayload` patches provider payload details pi-ai cannot express directly:
|
|
|
- * stop sequences, per-tool strict, omitted reasoning effort, and raw replayed
|
|
|
- * tool-call arguments.
|
|
|
- * - `prefill` throws UNSUPPORTED (same contract as dsh-llm-deepseek).
|
|
|
+ * stop sequences, scrubbing pi-ai's own per-tool `strict` default (the
|
|
|
+ * hand-rolled twin sends no such field), omitted reasoning effort, and raw
|
|
|
+ * replayed tool-call arguments.
|
|
|
* - pi-ai reports request failures as in-stream error events; convert.ts
|
|
|
* maps them to `finish {kind:'error'|'aborted'}` chunks rather than
|
|
|
* throwing — both are sanctioned StreamChunk error paths.
|
|
|
@@ -144,13 +137,6 @@ export class PiAiAdapter extends LlmAdapter {
|
|
|
}
|
|
|
|
|
|
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
|
|
- if (options.prefill !== undefined) {
|
|
|
- throw new LlmError(
|
|
|
- 'prefill is not supported by the pi-ai adapter',
|
|
|
- 'UNSUPPORTED',
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
const model = buildModel(options.model, this.options)
|
|
|
// Undefined config means "provider default" (DeepSeek: thinking ENABLED),
|
|
|
// matching llm-deepseek's omission semantics. pi-ai derives the wire
|