Merge master into app attribution RFC

This commit is contained in:
Tianyi Cui
2026-07-05 00:45:39 +08:00
197 changed files with 2876 additions and 2047 deletions
+10 -24
View File
@@ -13,9 +13,9 @@
import { stream as piStream } from '@earendil-works/pi-ai'
import type { Model } from '@earendil-works/pi-ai'
import { attributionHeaders, LlmAdapter, LlmError } from '@deepseek-ai/dsh-llm'
import { attributionHeaders, 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
+1 -1
View File
@@ -93,7 +93,7 @@ export function toPiContext(options: GenerateOptions): PiContext {
})
break
default:
// image / plugin-added block types: not representable here.
// plugin-added block types: not representable here.
break
}
}