Merge remote-tracking branch 'origin/master' into nih-imp-sse

# Conflicts:
#	.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml
#	packages/llm/llm/README.i18n.yaml
This commit is contained in:
Tianyi Cui
2026-07-27 06:16:30 +08:00
665 changed files with 11054 additions and 7377 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
README.md: e00cfedc4561b70a0f08e6825617fa5c5217f7ad
README.zh.md: ef6582ea2a71128ea2ab17012852b1125b4fe692
README.md: 1389a19327889d3955c96481c175c0c229c2243f
README.zh.md: fb38054ef7d13b0a7f99b07d66d9a3efeda24154

View File

@@ -73,8 +73,8 @@ Pass-through; the registry preserves the assembled request prefix, while the sel
## Known Limitations and Deferred Work
- **No default retry/caching/rate-limit policy ships in this service** — `llm/stream` remains a single-attempt call-wrapper seam; the agent loop separately offers proven model-request failures to `agent/request-error`, whose default preserves the original failure. `@deepseek-ai/dsh-llm-retry` is an optional policy plugin loaded by the shared example spine.
- **`GenerateOptions` sampling is `temperature`/`maxTokens`/`stop` only** — no `tool_choice`, `top_p`, or penalty fields; the vocabulary grows when a producer lands ([dropped inert knobs](../../../.agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.md)).
- **Producer-gated variants stay out until produced** — `prefill`, per-tool `strict`, block `cache` hints, and the `agent` message-source variant were pruned as producerless ([Agent Note](../../../.agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.md)).
- **`GenerateOptions` sampling is `temperature`/`maxTokens`/`stop` only** — no `tool_choice`, `top_p`, or penalty fields; the vocabulary grows when a producer lands ([dropped inert knobs](../../../.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md)).
- **Producer-gated variants stay out until produced** — `prefill`, per-tool `strict`, block `cache` hints, and the `agent` message-source variant were pruned as producerless ([Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md)).
- **`BlockAssembler` handles core block kinds only** — a plugin-added block type whose stream is never closed by `block-end` makes `blocks()` throw.
- **`APP_IDENTITY.url` names a repository that does not exist yet** — `FIXME`: creating the public `deepseek-ai/deepseek-harness-sdk` repo gates the first release.
- **`GenerateOptions.sessionId` is a locally-declared brand** — importing dsh-session's `SessionId` would cycle; a future ids-owning package would dissolve the workaround.

View File

@@ -73,8 +73,8 @@
## 已知限制与暂缓事项
- **本服务不内置默认重试/缓存/速率限制策略**`llm/stream` 仍是单次尝试调用包装 seamagent loop 会将已验证模型请求失败单独提供给 `agent/request-error`,其默认行为是保留原始失败。`@deepseek-ai/dsh-llm-retry` 是共享示例 spine 加载的可选策略插件。
- **`GenerateOptions` 采样只包含 `temperature``maxTokens``stop`**:没有 `tool_choice``top_p` 或 penalty 字段;有产生方落地时词汇才会增长(见 [已删除惰性旋钮](../../../.agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.md))。
- **由产生方调节的变体在实际产生前保持在外**`prefill`、每工具 `strict`、块 `cache` 提示与 `agent` 消息源变体因没有产生方而被剪除(见 [Agent Note](../../../.agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.md))。
- **`GenerateOptions` 采样只包含 `temperature``maxTokens``stop`**:没有 `tool_choice``top_p` 或 penalty 字段;有产生方落地时词汇才会增长(见 [已删除惰性旋钮](../../../.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md))。
- **由产生方调节的变体在实际产生前保持在外**`prefill`、每工具 `strict`、块 `cache` 提示与 `agent` 消息源变体因没有产生方而被剪除(见 [Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md))。
- **`BlockAssembler` 只处理核心块 kind**:如果插件添加块类型的流从未由 `block-end` 关闭,`blocks()` 会抛出异常。
- **`APP_IDENTITY.url` 指向一个尚不存在的仓库**`FIXME`:创建公开 `deepseek-ai/deepseek-harness-sdk` 仓库是首次发布的前置条件。
- **`GenerateOptions.sessionId` 是本地声明的品牌类型**:导入 dsh-session 的 `SessionId` 会产生循环;未来拥有 id 的包可以消除该权宜之计。

View File

@@ -47,8 +47,10 @@ export function markLlmAdapterFailure(
const error = value instanceof Error
? value as Error & { code?: string }
: new HarnessError(String(value), 'UNKNOWN', { cause: value })
const carried = error instanceof HarnessError ? ownFailureSnapshot(error) : undefined
const failure = carried !== undefined && carried.code === error.code ? carried : Object.freeze({
// Cross-package copies preserve own data but not class identity. Trust the
// carried facts only when both own properties agree after validation.
const carried = ownFailureSnapshot(error)
const failure = carried !== undefined && carried.code === ownErrorCode(error) ? carried : Object.freeze({
message: errorMessage(error),
code: harnessErrorCode(error),
})
@@ -56,6 +58,16 @@ export function markLlmAdapterFailure(
return error
}
/** Read a foreign error's own data-backed `code` without invoking accessors. */
function ownErrorCode(error: Error): unknown {
try {
const descriptor = Object.getOwnPropertyDescriptor(error, 'code')
return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined
} catch (_sdkPropertyTrap) {
return undefined
}
}
/** Snapshot an own data property without invoking an SDK-defined accessor. */
function ownFailureSnapshot(error: Error): LlmFailure | undefined {
try {

View File

@@ -291,6 +291,34 @@ describe('LlmService', () => {
expect(facts).not.toBe(carried)
})
it('keeps validated failure facts across package copies with matching own codes', async () => {
const original = Object.assign(new Error('provider busy'), {
code: 'RATE_LIMIT',
failure: {
message: 'provider busy',
code: 'RATE_LIMIT',
status: 429,
providerRetryAfterMs: 1_500,
requestId: 'req-cross-copy',
},
})
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['test-provider'], new ThrowingAdapter(original))
const stream = ctx.llm.stream({ provider: 'test-provider', model: 'test-model', messages: [] })
await expect((async () => {
for await (const _chunk of stream) { /* drain */ }
})()).rejects.toBe(original)
expect(llmFailureOf(stream, original)).toEqual({
message: 'provider busy',
code: 'RATE_LIMIT',
status: 429,
providerRetryAfterMs: 1_500,
requestId: 'req-cross-copy',
})
})
it('keeps an unknown SDK Error exact without trusting its private code or accessors', async () => {
const original = Object.assign(new Error('socket closed'), { code: 'ECONNRESET' })
Object.defineProperty(original, 'failure', {
@@ -324,6 +352,64 @@ describe('LlmService', () => {
expect(llmFailureOf(stream, original)).toEqual({ message: 'LLM adapter failed', code: 'UNKNOWN' })
})
it('keeps an SDK Error exact without trusting accessor-backed carried facts', async () => {
const original = Object.assign(new Error('busy'), {
failure: { message: 'busy', code: 'SERVER', status: 503 },
})
Object.defineProperty(original, 'code', {
get() { throw new Error('SDK code accessor must not escape') },
})
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['test-provider'], new ThrowingAdapter(original))
const stream = ctx.llm.stream({ provider: 'test-provider', model: 'test-model', messages: [] })
await expect((async () => {
for await (const _chunk of stream) { /* drain */ }
})()).rejects.toBe(original)
expect(llmFailureOf(stream, original)).toEqual({ message: 'busy', code: 'UNKNOWN' })
})
it('does not trust carried facts matched only by an inherited code', async () => {
class InheritedCodeError extends Error {
get code(): string { return 'SERVER' }
}
const original = Object.assign(new InheritedCodeError('busy'), {
failure: { message: 'busy', code: 'SERVER', status: 503 },
})
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['test-provider'], new ThrowingAdapter(original))
const stream = ctx.llm.stream({ provider: 'test-provider', model: 'test-model', messages: [] })
await expect((async () => {
for await (const _chunk of stream) { /* drain */ }
})()).rejects.toBe(original)
expect(llmFailureOf(stream, original)).toEqual({ message: 'busy', code: 'UNKNOWN' })
})
it('keeps an SDK Error exact when code descriptor inspection is trapped', async () => {
const target = Object.assign(new Error('busy'), {
code: 'SERVER',
failure: { message: 'busy', code: 'SERVER', status: 503 },
})
const original = new Proxy(target, {
getOwnPropertyDescriptor(value, property) {
if (property === 'code') throw new Error('SDK code descriptor trap')
return Reflect.getOwnPropertyDescriptor(value, property)
},
})
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['test-provider'], new ThrowingAdapter(original))
const stream = ctx.llm.stream({ provider: 'test-provider', model: 'test-model', messages: [] })
await expect((async () => {
for await (const _chunk of stream) { /* drain */ }
})()).rejects.toBe(original)
expect(llmFailureOf(stream, original)).toEqual({ message: 'busy', code: 'UNKNOWN' })
})
it('falls back safely when SDK objects trap failure inspection or expose malformed facts', async () => {
const propertyTrap = new Proxy(new HarnessError('descriptor trapped', 'SERVER'), {
getOwnPropertyDescriptor(target, property) {