mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge commit 'a9cc0fddb40be295c43cb2badb4cbcb2b032556c' into codex/product-providers-pr2-claude-code
# Conflicts: # .agents/notes/proposed/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml # .agents/notes/proposed/feature/2026-08-04-claude-code-and-codex-subagent-backends.md # .agents/notes/proposed/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md # docs/core-data-structures/subprocess.i18n.yaml # packages/subprocess/subprocess/README.i18n.yaml
This commit is contained in:
@@ -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 packages/subagent/subagent-claude-code/README.md
|
||||
README.md: 59d7dcd8406cb244854bbb704f644af1a6aa8589
|
||||
README.zh.md: 90ea0a316b91da3249b934984a3f512b71b8b3b2
|
||||
README.md: 5bfc8ff3ba539b6caf891577cee5820d983106cd
|
||||
README.zh.md: 6db9148228ba30c0f84beb14caa7a84aa0dcd9f5
|
||||
|
||||
@@ -27,7 +27,7 @@ The provider advertises no optional start-time capabilities and reports `inherit
|
||||
| Key | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `env` | `{}` | Explicit SDK/CLI environment layered over the shared credential-scrubbed parent environment. |
|
||||
| `disposeGraceMs` | `3000` | Positive finite grace in milliseconds between the shared process-tree owner's termination tiers; disposal then waits for whole-tree exit. |
|
||||
| `disposeGraceMs` | `3000` | Positive finite grace in milliseconds, no greater than [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md), between the shared process-tree owner's termination tiers; disposal then waits for whole-tree exit. |
|
||||
|
||||
Production uses the Claude Code CLI supplied by `@anthropic-ai/claude-agent-sdk` and the host's native settings and authentication. The plugin does not install another CLI, select a model, create a product home, log in, or probe an account. Credential-shaped ambient variables are removed before the explicit `env` overlay is applied, so an API key or endpoint intended for the child must be supplied there; ordinary ambient values such as `PATH` and `HOME` remain available unless overridden.
|
||||
|
||||
@@ -51,7 +51,7 @@ Install this package and add the following rows to your own `cordis.yml`. Shippe
|
||||
|
||||
## Product compatibility and evidence
|
||||
|
||||
The runtime dependency is pinned to `@anthropic-ai/claude-agent-sdk@0.3.220`, whose platform optional dependency supplies Claude Code 2.1.220. The keyless real-product spec drives that official SDK and CLI against a loopback Anthropic Messages SSE service with a non-empty fake key. It proves the exact task, authentication, byte-exact answer, inherited host-setting marker, process failure, cancellation, and process-tree exit. A separate Loader composition e2e boots the README-shaped user configuration alongside the Codex provider with neither product command available, verifies both fixed foreground-only tools, and records zero child starts. A credentialed e2e maps a runtime-only DeepSeek key to the official Claude Code environment contract, starts the production provider and real SDK/CLI against the fixed official `/anthropic` endpoint, requires a unique nonce, and proves process-tree exit without persisting the key or calling the Messages API directly from the test.
|
||||
The runtime dependency is pinned to `@anthropic-ai/claude-agent-sdk@0.3.220`, whose platform optional dependency supplies Claude Code 2.1.220. Required evidence exercises that official distribution through a keyless loopback product path and a credentialed DeepSeek path, while Loader composition proves that both opt-in product packages coexist without starting either product.
|
||||
|
||||
The project owner's identity-scoped distribution authorization covers the official SDK and the official CLI/platform payloads declared by each SDK version. [`THIRD_PARTY_NOTICES.md`](../../../THIRD_PARTY_NOTICES.md) discloses the current optional payload closure without classifying its declared terms as permissive; unrelated non-permissive runtime dependencies continue to fail the notices gate.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK
|
||||
| 配置键 | 默认值 | 含义 |
|
||||
|---|---|---|
|
||||
| `env` | `{}` | 显式指定的 SDK/CLI 环境,叠加在由共享机制清除凭证后的父环境之上。 |
|
||||
| `disposeGraceMs` | `3000` | 共享进程树责任方各终止层级之间的宽限期,单位为毫秒且须为正有限值;随后资源释放会等待整棵进程树退出。 |
|
||||
| `disposeGraceMs` | `3000` | 共享进程树责任方各终止层级之间的宽限期,单位为毫秒且须为正有限值,并不得大于仓库共享的 [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md);随后资源释放会等待整棵进程树退出。 |
|
||||
|
||||
生产环境使用 `@anthropic-ai/claude-agent-sdk` 提供的 Claude Code CLI,以及宿主机原生设置与身份验证。本插件不安装另一份 CLI、不选择模型、不创建产品主目录、不执行登录,也不探测账户。具有凭证特征的环境变量会在显式 `env` 覆盖生效前被清除,因此供子进程使用的 API 密钥或端点必须在该配置中显式提供;除非被覆盖,`PATH` 和 `HOME` 等普通环境变量仍然可用。
|
||||
|
||||
@@ -51,7 +51,7 @@ SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK
|
||||
|
||||
## 产品兼容性与证据
|
||||
|
||||
运行时依赖精确锁定为 `@anthropic-ai/claude-agent-sdk@0.3.220`,其平台可选依赖提供 Claude Code 2.1.220。无密钥真实产品测试使用非空伪密钥,驱动该官方 SDK 与 CLI 连接回环 Anthropic Messages SSE 服务,并证明原始任务、身份验证、逐字节一致的答案、继承的宿主设置标记、进程失败、取消与进程树退出。独立的 Loader 装配 e2e 会在两个产品命令均不可用时,与 Codex 提供方一起启动和 README 所示形态相同的用户配置,验证两个固定且只支持前台执行的工具,并记录零次子级启动。带密钥 e2e 会将仅在运行时提供的 DeepSeek 密钥映射到 Claude Code 官方环境契约,启动生产提供方与真实 SDK 和 CLI 并连接固定的官方 `/anthropic` 端点,要求获得一个唯一随机数,并在不持久化密钥且不由测试直接调用 Messages API 的情况下证明进程树退出。
|
||||
运行时依赖精确锁定为 `@anthropic-ai/claude-agent-sdk@0.3.220`,其平台可选依赖提供 Claude Code 2.1.220。强制证据会通过无密钥回环产品路径与带密钥 DeepSeek 路径运行该官方发行版,而 Loader 组合则证明两个选择启用的产品包能够共存,且不会启动任一产品。
|
||||
|
||||
项目所有者按身份范围授权分发官方 SDK 及每个 SDK 版本声明的官方 CLI/平台载荷。[`THIRD_PARTY_NOTICES.md`](../../../THIRD_PARTY_NOTICES.md) 会披露当前可选载荷闭包,但不会把其声明条款归类为宽松许可证;其他无关的非宽松运行时依赖仍会使第三方声明门禁失败。
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@deepseek-ai/dsh-session": "^0.0.1",
|
||||
"@deepseek-ai/dsh-subagent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-subprocess": "^0.0.1",
|
||||
"@deepseek-ai/dsh-timeout": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -48,6 +49,7 @@
|
||||
"@deepseek-ai/dsh-subagent": "workspace:^",
|
||||
"@deepseek-ai/dsh-subprocess": "workspace:^",
|
||||
"@deepseek-ai/dsh-subprocess-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-timeout": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
|
||||
import {
|
||||
assertPositiveFinite,
|
||||
NO_START_CAPABILITIES,
|
||||
@@ -90,6 +91,11 @@ export function apply(ctx: Context, config: Config): void {
|
||||
'disposeGraceMs',
|
||||
resolved.disposeGraceMs,
|
||||
)
|
||||
if (resolved.disposeGraceMs > MAX_TIMER_DELAY_MS) {
|
||||
throw new Error(
|
||||
`subagent-claude-code: disposeGraceMs must be no greater than ${MAX_TIMER_DELAY_MS}`,
|
||||
)
|
||||
}
|
||||
ctx.subagents.registerProvider(new ClaudeCodeProvider(ctx, resolved))
|
||||
}
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
@@ -83,18 +83,17 @@ function complete(
|
||||
|
||||
/**
|
||||
* Start a loopback-only Anthropic Messages SSE fixture.
|
||||
* @param script - one behavior per Messages request.
|
||||
* @param behavior - the single response behavior for this fixture.
|
||||
* @returns the bound server and its recorded requests.
|
||||
*/
|
||||
export async function startMessagesFixture(
|
||||
script: readonly MessagesBehavior[],
|
||||
behavior: MessagesBehavior,
|
||||
): Promise<MessagesFixture> {
|
||||
const requests: RecordedMessagesRequest[] = []
|
||||
let requestStartedResolve!: () => void
|
||||
const requestStarted = new Promise<void>((resolve) => {
|
||||
requestStartedResolve = resolve
|
||||
})
|
||||
let behaviorIndex = 0
|
||||
const server = createServer((request, response) => {
|
||||
const chunks: Buffer[] = []
|
||||
request.on('data', (chunk: Buffer) => { chunks.push(chunk) })
|
||||
@@ -117,18 +116,6 @@ export async function startMessagesFixture(
|
||||
body,
|
||||
})
|
||||
requestStartedResolve()
|
||||
const behavior = script[behaviorIndex++]
|
||||
if (behavior === undefined) {
|
||||
response.writeHead(500, { 'content-type': 'application/json' })
|
||||
response.end(JSON.stringify({
|
||||
type: 'error',
|
||||
error: {
|
||||
type: 'api_error',
|
||||
message: 'Messages fixture script was exhausted',
|
||||
},
|
||||
}))
|
||||
return
|
||||
}
|
||||
if (behavior.kind === 'complete') {
|
||||
complete(response, body, behavior.text)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ interface RealHarness {
|
||||
readonly env: Record<string, string>
|
||||
}
|
||||
|
||||
async function realHarness(script: readonly MessagesBehavior[]): Promise<{
|
||||
async function realHarness(behavior: MessagesBehavior): Promise<{
|
||||
readonly harness: RealHarness
|
||||
readonly fixture: MessagesFixture
|
||||
}> {
|
||||
@@ -80,7 +80,7 @@ async function realHarness(script: readonly MessagesBehavior[]): Promise<{
|
||||
join(claudeConfig, 'settings.json'),
|
||||
`${JSON.stringify({ model: settingsModel }, null, 2)}\n`,
|
||||
)
|
||||
const fixture = await startMessagesFixture(script)
|
||||
const fixture = await startMessagesFixture(behavior)
|
||||
fixtures.push(fixture)
|
||||
const env = {
|
||||
ANTHROPIC_API_KEY: fakeKey,
|
||||
@@ -149,9 +149,10 @@ describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
|
||||
it('inherits host settings and sends the exact task and fake key to local Messages', async () => {
|
||||
const sentinel = 'REAL_CLAUDE_CODE_SENTINEL_2_1_220'
|
||||
const task = 'Return the fixture sentinel exactly.'
|
||||
const { harness, fixture } = await realHarness([
|
||||
{ kind: 'complete', text: sentinel },
|
||||
])
|
||||
const { harness, fixture } = await realHarness({
|
||||
kind: 'complete',
|
||||
text: sentinel,
|
||||
})
|
||||
expect(sdkPackage.version).toBe('0.3.220')
|
||||
expect(sdkPackage.claudeCodeVersion).toBe('2.1.220')
|
||||
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.220')
|
||||
@@ -191,7 +192,7 @@ describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
|
||||
})
|
||||
|
||||
it('maps a real CLI process failure to error', async () => {
|
||||
const { harness, fixture } = await realHarness([{ kind: 'hold' }])
|
||||
const { harness, fixture } = await realHarness({ kind: 'hold' })
|
||||
const run = await startRequest(harness, 'Exercise the failure path.')
|
||||
await fixture.requestStarted
|
||||
expect(harness.handles).toHaveLength(1)
|
||||
@@ -207,7 +208,7 @@ describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
|
||||
})
|
||||
|
||||
it('settles cancellation and leaves the real SDK-spawned CLI tree quiescent', async () => {
|
||||
const { harness, fixture } = await realHarness([{ kind: 'hold' }])
|
||||
const { harness, fixture } = await realHarness({ kind: 'hold' })
|
||||
const controller = new AbortController()
|
||||
const run = await startRequest(
|
||||
harness,
|
||||
|
||||
@@ -27,6 +27,7 @@ import type {
|
||||
SubprocessSpawnSpec,
|
||||
} from '@deepseek-ai/dsh-subprocess'
|
||||
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
|
||||
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
|
||||
import * as claudeCode from '../src/index.ts'
|
||||
import * as invariant from '../src/invariant.ts'
|
||||
import {
|
||||
@@ -74,8 +75,6 @@ async function nextTask(): Promise<void> {
|
||||
|
||||
interface FakeChildOptions {
|
||||
readonly pid?: number
|
||||
readonly stdin?: PassThrough | undefined
|
||||
readonly stdout?: PassThrough | undefined
|
||||
readonly exitOnTerminate?: boolean
|
||||
readonly waitForExitError?: Error
|
||||
readonly doneError?: Error
|
||||
@@ -145,8 +144,8 @@ function fakeChild(options: FakeChildOptions = {}): FakeChild {
|
||||
})
|
||||
const handle: SubprocessHandle = {
|
||||
pid: options.pid ?? 1234,
|
||||
stdin: options.stdin === undefined ? stdin : options.stdin,
|
||||
stdout: options.stdout === undefined ? stdout : options.stdout,
|
||||
stdin,
|
||||
stdout,
|
||||
stderr: undefined,
|
||||
collected: {},
|
||||
done,
|
||||
@@ -232,7 +231,6 @@ function sdkSpawnOptions(
|
||||
|
||||
interface FakeRun {
|
||||
readonly child: FakeChild
|
||||
readonly query: Query
|
||||
readonly close: ReturnType<typeof vi.fn>
|
||||
readonly spawnSpecs: SubprocessSpawnSpec[]
|
||||
readonly options: Options[]
|
||||
@@ -262,7 +260,7 @@ function fakeRun(
|
||||
params.options.spawnClaudeCodeProcess!(sdkSpawnOptions())
|
||||
return query
|
||||
})
|
||||
return { child, query, close, spawnSpecs, options, spec }
|
||||
return { child, close, spawnSpecs, options, spec }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -318,6 +316,11 @@ describe('task admission and package contracts', () => {
|
||||
await expect(ctx.plugin(claudeCode, { disposeGraceMs }))
|
||||
.rejects.toThrow('disposeGraceMs must be a positive finite number')
|
||||
}
|
||||
await expect(ctx.plugin(claudeCode, {
|
||||
disposeGraceMs: MAX_TIMER_DELAY_MS + 1,
|
||||
})).rejects.toThrow(
|
||||
`disposeGraceMs must be no greater than ${MAX_TIMER_DELAY_MS}`,
|
||||
)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
{
|
||||
"path": "../../subprocess/subprocess"
|
||||
},
|
||||
{
|
||||
"path": "../../util/timeout"
|
||||
},
|
||||
{
|
||||
"path": "../../support/invariants"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user