|
@@ -3,11 +3,12 @@ import {
|
|
|
mkdirSync,
|
|
mkdirSync,
|
|
|
mkdtempSync,
|
|
mkdtempSync,
|
|
|
readFileSync,
|
|
readFileSync,
|
|
|
|
|
+ symlinkSync,
|
|
|
writeFileSync,
|
|
writeFileSync,
|
|
|
} from 'node:fs'
|
|
} from 'node:fs'
|
|
|
import { rm } from 'node:fs/promises'
|
|
import { rm } from 'node:fs/promises'
|
|
|
import { tmpdir } from 'node:os'
|
|
import { tmpdir } from 'node:os'
|
|
|
-import { dirname, join, resolve } from 'node:path'
|
|
|
|
|
|
|
+import { delimiter, dirname, join, resolve } from 'node:path'
|
|
|
import { fileURLToPath } from 'node:url'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
import { promisify } from 'node:util'
|
|
import { promisify } from 'node:util'
|
|
|
import type {
|
|
import type {
|
|
@@ -19,7 +20,7 @@ import { Context } from '@deepseek-ai/cordis'
|
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
|
import SubagentService from '@deepseek-ai/dsh-subagent'
|
|
import SubagentService from '@deepseek-ai/dsh-subagent'
|
|
|
-import type { SubprocessHandle } from '@deepseek-ai/dsh-subprocess'
|
|
|
|
|
|
|
+import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
|
|
|
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
|
|
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
|
|
|
import * as claudeCode from '../src/index.ts'
|
|
import * as claudeCode from '../src/index.ts'
|
|
|
import {
|
|
import {
|
|
@@ -98,9 +99,11 @@ afterEach(async () => {
|
|
|
interface RealHarness {
|
|
interface RealHarness {
|
|
|
readonly ctx: Context
|
|
readonly ctx: Context
|
|
|
readonly handles: SubprocessHandle[]
|
|
readonly handles: SubprocessHandle[]
|
|
|
|
|
+ readonly spawnSpecs: SubprocessSpawnSpec[]
|
|
|
readonly parent: Agent
|
|
readonly parent: Agent
|
|
|
readonly workspace: string
|
|
readonly workspace: string
|
|
|
readonly env: Record<string, string>
|
|
readonly env: Record<string, string>
|
|
|
|
|
+ readonly executable: string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function realHarness(behavior: MessagesBehavior): Promise<{
|
|
async function realHarness(behavior: MessagesBehavior): Promise<{
|
|
@@ -112,9 +115,17 @@ async function realHarness(behavior: MessagesBehavior): Promise<{
|
|
|
const workspace = join(root, 'workspace')
|
|
const workspace = join(root, 'workspace')
|
|
|
const claudeConfig = join(root, 'claude-config')
|
|
const claudeConfig = join(root, 'claude-config')
|
|
|
const xdgConfig = join(root, 'xdg')
|
|
const xdgConfig = join(root, 'xdg')
|
|
|
|
|
+ const nativeBin = join(root, 'native&%literal%!bang!bin')
|
|
|
mkdirSync(workspace)
|
|
mkdirSync(workspace)
|
|
|
mkdirSync(claudeConfig)
|
|
mkdirSync(claudeConfig)
|
|
|
mkdirSync(xdgConfig)
|
|
mkdirSync(xdgConfig)
|
|
|
|
|
+ mkdirSync(nativeBin)
|
|
|
|
|
+ const executable = join(nativeBin, process.platform === 'win32' ? 'claude.cmd' : 'claude')
|
|
|
|
|
+ if (process.platform === 'win32') {
|
|
|
|
|
+ writeFileSync(executable, `@echo off\r\n"${claudeBin}" %*\r\n`)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ symlinkSync(claudeBin, executable)
|
|
|
|
|
+ }
|
|
|
writeFileSync(
|
|
writeFileSync(
|
|
|
join(claudeConfig, 'settings.json'),
|
|
join(claudeConfig, 'settings.json'),
|
|
|
`${JSON.stringify({ model: settingsModel }, null, 2)}\n`,
|
|
`${JSON.stringify({ model: settingsModel }, null, 2)}\n`,
|
|
@@ -122,6 +133,7 @@ async function realHarness(behavior: MessagesBehavior): Promise<{
|
|
|
const fixture = await startMessagesFixture(behavior)
|
|
const fixture = await startMessagesFixture(behavior)
|
|
|
fixtures.push(fixture)
|
|
fixtures.push(fixture)
|
|
|
const env = {
|
|
const env = {
|
|
|
|
|
+ PATH: `${nativeBin}${delimiter}${process.env.PATH ?? ''}`,
|
|
|
ANTHROPIC_API_KEY: fakeKey,
|
|
ANTHROPIC_API_KEY: fakeKey,
|
|
|
ANTHROPIC_BASE_URL: fixture.baseUrl,
|
|
ANTHROPIC_BASE_URL: fixture.baseUrl,
|
|
|
CLAUDE_CONFIG_DIR: claudeConfig,
|
|
CLAUDE_CONFIG_DIR: claudeConfig,
|
|
@@ -141,8 +153,10 @@ async function realHarness(behavior: MessagesBehavior): Promise<{
|
|
|
await ctx.plugin(SubagentService)
|
|
await ctx.plugin(SubagentService)
|
|
|
await ctx.plugin(LocalSubprocessService)
|
|
await ctx.plugin(LocalSubprocessService)
|
|
|
const handles: SubprocessHandle[] = []
|
|
const handles: SubprocessHandle[] = []
|
|
|
|
|
+ const spawnSpecs: SubprocessSpawnSpec[] = []
|
|
|
const spawn = ctx.subprocess.spawn.bind(ctx.subprocess)
|
|
const spawn = ctx.subprocess.spawn.bind(ctx.subprocess)
|
|
|
vi.spyOn(ctx.subprocess, 'spawn').mockImplementation((spec) => {
|
|
vi.spyOn(ctx.subprocess, 'spawn').mockImplementation((spec) => {
|
|
|
|
|
+ spawnSpecs.push(spec)
|
|
|
const handle = spawn(spec)
|
|
const handle = spawn(spec)
|
|
|
handles.push(handle)
|
|
handles.push(handle)
|
|
|
return handle
|
|
return handle
|
|
@@ -153,7 +167,7 @@ async function realHarness(behavior: MessagesBehavior): Promise<{
|
|
|
session: { header: { cwd: workspace } },
|
|
session: { header: { cwd: workspace } },
|
|
|
} as unknown as Agent
|
|
} as unknown as Agent
|
|
|
return {
|
|
return {
|
|
|
- harness: { ctx, handles, parent, workspace, env },
|
|
|
|
|
|
|
+ harness: { ctx, handles, spawnSpecs, parent, workspace, env, executable },
|
|
|
fixture,
|
|
fixture,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -182,7 +196,7 @@ function startRequest(
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
|
|
|
|
|
|
|
+describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220 fixture', {
|
|
|
timeout: 60_000,
|
|
timeout: 60_000,
|
|
|
}, () => {
|
|
}, () => {
|
|
|
it('inherits host settings and sends the exact task and fake key to local Messages', async () => {
|
|
it('inherits host settings and sends the exact task and fake key to local Messages', async () => {
|
|
@@ -195,7 +209,7 @@ describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
|
|
|
expect(sdkPackage.version).toBe('0.3.220')
|
|
expect(sdkPackage.version).toBe('0.3.220')
|
|
|
expect(sdkPackage.claudeCodeVersion).toBe('2.1.220')
|
|
expect(sdkPackage.claudeCodeVersion).toBe('2.1.220')
|
|
|
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.220')
|
|
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.220')
|
|
|
- const version = await execFileAsync(claudeBin, ['--version'], {
|
|
|
|
|
|
|
+ const version = await execFileAsync(process.platform === 'win32' ? claudeBin : harness.executable, ['--version'], {
|
|
|
env: { ...process.env, ...harness.env },
|
|
env: { ...process.env, ...harness.env },
|
|
|
})
|
|
})
|
|
|
expect(version.stdout.trim()).toBe('2.1.220 (Claude Code)')
|
|
expect(version.stdout.trim()).toBe('2.1.220 (Claude Code)')
|
|
@@ -212,6 +226,18 @@ describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
|
|
|
message.type === 'system' && message.subtype === 'init',
|
|
message.type === 'system' && message.subtype === 'init',
|
|
|
)
|
|
)
|
|
|
expect(initMessage?.claude_code_version).toBe('2.1.220')
|
|
expect(initMessage?.claude_code_version).toBe('2.1.220')
|
|
|
|
|
+ if (process.platform === 'win32') {
|
|
|
|
|
+ expect(harness.spawnSpecs[0]?.argv.slice(0, 6)).toEqual([
|
|
|
|
|
+ 'cmd.exe', '/d', '/v:off', '/s', '/c', '%DSH_CLAUDE_CODE_EXECUTABLE%',
|
|
|
|
|
+ ])
|
|
|
|
|
+ const batchExecutable = harness.spawnSpecs[0]?.env?.DSH_CLAUDE_CODE_EXECUTABLE
|
|
|
|
|
+ expect(batchExecutable?.startsWith('"')).toBe(true)
|
|
|
|
|
+ expect(batchExecutable?.endsWith('"')).toBe(true)
|
|
|
|
|
+ expect(batchExecutable?.slice(1, -1).toLowerCase())
|
|
|
|
|
+ .toBe(harness.executable.toLowerCase())
|
|
|
|
|
+ } else {
|
|
|
|
|
+ expect(harness.spawnSpecs[0]?.argv[0]).toBe(harness.executable)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
expect(fixture.requests).toHaveLength(1)
|
|
expect(fixture.requests).toHaveLength(1)
|
|
|
const recorded = fixture.requests[0]!
|
|
const recorded = fixture.requests[0]!
|