Merge pull request #5369 from deepseek-harness/fix/python-wheel-size-5250

fix: download optional Python runtime resources explicitly
This commit is contained in:
Yudong Han authored and GitHub committed 2026-10-09 16:32:27 +08:00
commit 18643292ae
40 files changed
+1442 -355

No files matched your search

+66 -59
View File
@@ -1,8 +1,12 @@
import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { join } from 'node:path'
import { afterEach, expect, it } from 'vitest'
import { copyOfficeSidecar } from './build-exe-for-python-sdk-office.ts'
import { createHash } from 'node:crypto'
import { c as tar } from 'tar'
import { downloadOfficeSidecar, runtimeNpmArchive } from './build-exe-for-python-sdk-office.ts'
import { officePackageDirectories } from './libreoffice-packages.mjs'
const temporaryDirectories: string[] = []
@@ -24,33 +28,8 @@ async function fixture() {
return { root, staging, destination, packageAt }
}
it('copies package-owned data, licenses, helper permissions, and nested dependencies', async () => {
const { staging, destination, packageAt } = await fixture()
const entry = await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: { '@deepseek-ai/libreoffice-kit-wasm': '0.0.1' }, dependencies: { decoder: '1' } })
const engine = await packageAt('@deepseek-ai/libreoffice-kit-wasm')
const decoder = await packageAt('decoder', { dependencies: { codec: '1' } }, entry)
await packageAt('codec', {}, decoder)
await packageAt('codec', { version: '2.0.0' })
const assets = ['assets/soffice.data', 'prebuilds.json', 'licenses/LICENSE', 'bin/helper']
for (const name of assets) {
const path = join(engine, name)
await mkdir(dirname(path), { recursive: true })
await writeFile(path, name)
}
await chmod(join(engine, 'bin/helper'), 0o755)
const packages = await copyOfficeSidecar(staging, destination, { platform: 'linux', arch: 'x64' })
expect(packages).toHaveLength(4)
expect(await readFile(join(destination, 'node_modules/@deepseek-ai/libreoffice-kit-wasm/assets/soffice.data'), 'utf8')).toBe('assets/soffice.data')
expect(await readFile(join(destination, 'node_modules/@deepseek-ai/libreoffice-kit-wasm/licenses/LICENSE'), 'utf8')).toBe('licenses/LICENSE')
expect(await readFile(join(destination, 'node_modules/@deepseek-ai/libreoffice-kit/node_modules/decoder/node_modules/codec/package.json'), 'utf8')).toContain('codec')
await expect(stat(join(destination, 'node_modules/codec'))).rejects.toMatchObject({ code: 'ENOENT' })
if (process.platform !== 'win32') expect((await stat(join(destination, 'node_modules/@deepseek-ai/libreoffice-kit-wasm/bin/helper'))).mode & 0o111).toBe(0o111)
})
it('copies installed target optionals and leaves other platforms and absent optionals out', async () => {
const { staging, destination, packageAt } = await fixture()
it('locates installed target optionals and leaves other platforms and absent optionals out', async () => {
const { staging, packageAt } = await fixture()
await packageAt('@deepseek-ai/libreoffice-kit', {
optionalDependencies: { native: '1', foreign: '1', absent: '1' },
})
@@ -58,84 +37,112 @@ it('copies installed target optionals and leaves other platforms and absent opti
await packageAt('native', { os: ['linux'], cpu: ['x64'] })
await packageAt('foreign', { os: ['darwin'], cpu: ['arm64'] })
const packages = await copyOfficeSidecar(staging, destination, { platform: 'linux', arch: 'x64' })
const packages = await officePackageDirectories(staging, { platform: 'linux', arch: 'x64' })
expect(packages.map(path => path.replaceAll('\\', '/'))).toEqual([
expect(packages.map(path => path.slice(staging.length + 1).replaceAll('\\', '/'))).toEqual([
'node_modules/@deepseek-ai/libreoffice-kit',
'node_modules/@deepseek-ai/libreoffice-kit-wasm',
'node_modules/native',
])
})
it('rejects a missing required dependency before producing a sidecar', async () => {
const { staging, destination, packageAt } = await fixture()
it('rejects a missing required dependency in the deployed closure', async () => {
const { staging, packageAt } = await fixture()
await packageAt('@deepseek-ai/libreoffice-kit', { dependencies: { 'dsh-missing-office-fixture': '1' } })
await expect(copyOfficeSidecar(staging, destination, { platform: 'linux', arch: 'x64' }))
await expect(officePackageDirectories(staging, { platform: 'linux', arch: 'x64' }))
.rejects.toThrow('dsh-missing-office-fixture required by @deepseek-ai/libreoffice-kit is missing')
await expect(stat(destination)).rejects.toMatchObject({ code: 'ENOENT' })
})
it('rejects an incomplete installed optional instead of omitting it', async () => {
const { staging, destination, packageAt } = await fixture()
const { staging, packageAt } = await fixture()
await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: { broken: '1' } })
await mkdir(join(staging, 'node_modules/broken'), { recursive: true })
await expect(copyOfficeSidecar(staging, destination, { platform: 'linux', arch: 'x64' }))
await expect(officePackageDirectories(staging, { platform: 'linux', arch: 'x64' }))
.rejects.toMatchObject({ code: 'ENOENT' })
})
it('rejects an ancestor dependency outside the deployed closure', async () => {
const { root, staging, destination, packageAt } = await fixture()
const { root, staging, packageAt } = await fixture()
await packageAt('@deepseek-ai/libreoffice-kit', { dependencies: { 'ancestor-office-fixture': '1' } })
await packageAt('ancestor-office-fixture', {}, root)
await expect(copyOfficeSidecar(staging, destination, { platform: 'linux', arch: 'x64' }))
await expect(officePackageDirectories(staging, { platform: 'linux', arch: 'x64' }))
.rejects.toThrow('outside the deployed closure')
})
it('requires the declared WASM engine inside the deployed closure', async () => {
const { root, staging, destination, packageAt } = await fixture()
await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: { '@deepseek-ai/libreoffice-kit-wasm': '0.0.1' } })
it.each([true, false])('requires a staged WASM engine when declared=%s even when an ancestor has one', async (declared) => {
const { root, staging, packageAt } = await fixture()
await packageAt('@deepseek-ai/libreoffice-kit', declared ? { optionalDependencies: { '@deepseek-ai/libreoffice-kit-wasm': '0.0.1' } } : {})
await packageAt('@deepseek-ai/libreoffice-kit-wasm', {}, root)
await expect(copyOfficeSidecar(staging, destination, { platform: 'linux', arch: 'x64' }))
await expect(officePackageDirectories(staging, { platform: 'linux', arch: 'x64' }))
.rejects.toThrow('Office engine @deepseek-ai/libreoffice-kit-wasm required for linux/x64 is missing.')
await expect(stat(destination)).rejects.toMatchObject({ code: 'ENOENT' })
})
it.each([
['darwin', 'arm64', 'darwin-arm64'], ['darwin', 'x64', 'darwin-x64'],
['win32', 'arm64', 'win32-arm64'], ['win32', 'x64', 'win32-x64'],
['linux', 'x64', 'linux-x64'], ['linux', 'arm64', 'wasm'], ['freebsd', 'x64', 'wasm'],
])('copies only the %s/%s engine even when other engines are staged', async (platform, arch, selected) => {
const { staging, destination, packageAt } = await fixture()
])('locates only the %s/%s engine even when other engines are staged', async (platform, arch, selected) => {
const { staging, packageAt } = await fixture()
const targets = ['darwin-arm64', 'darwin-x64', 'win32-arm64', 'win32-x64', 'linux-x64', 'wasm']
const names = targets.map(target => `@deepseek-ai/libreoffice-kit-${target}`)
await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: Object.fromEntries(names.map(name => [name, '0.0.1'])) })
for (const name of names) await packageAt(name)
const expected = `@deepseek-ai/libreoffice-kit-${selected}`
const packages = await copyOfficeSidecar(staging, destination, { platform, arch })
expect(packages.map(path => path.replaceAll('\\', '/'))).toEqual([
const packages = await officePackageDirectories(staging, { platform, arch })
expect(packages.map(path => path.slice(staging.length + 1).replaceAll('\\', '/'))).toEqual([
'node_modules/@deepseek-ai/libreoffice-kit', `node_modules/${expected}`,
])
})
it.each(['darwin', 'win32', 'linux'])('%s requires its native package even when WASM is staged', async (platform) => {
const { staging, destination, packageAt } = await fixture()
const { staging, packageAt } = await fixture()
await packageAt('@deepseek-ai/libreoffice-kit', { optionalDependencies: {
'@deepseek-ai/libreoffice-kit-wasm': '0.0.1', [`@deepseek-ai/libreoffice-kit-${platform}-arm64`]: '0.0.1',
} })
await packageAt('@deepseek-ai/libreoffice-kit-wasm')
await expect(copyOfficeSidecar(staging, destination, { platform, arch: 'arm64' }))
await expect(officePackageDirectories(staging, { platform, arch: 'arm64' }))
.rejects.toThrow(`Office engine @deepseek-ai/libreoffice-kit-${platform}-arm64 required for ${platform}/arm64 is missing.`)
await expect(stat(destination)).rejects.toMatchObject({ code: 'ENOENT' })
})
it('requires a staged Linux WASM engine even when an ancestor has one', async () => {
const { root, staging, destination, packageAt } = await fixture()
await packageAt('@deepseek-ai/libreoffice-kit')
await packageAt('@deepseek-ai/libreoffice-kit-wasm', {}, root)
await expect(copyOfficeSidecar(staging, destination, { platform: 'linux', arch: 'x64' }))
.rejects.toThrow('Office engine @deepseek-ai/libreoffice-kit-wasm required for linux/x64 is missing.')
await expect(stat(destination)).rejects.toMatchObject({ code: 'ENOENT' })
it('downloads pinned npm packages into the sidecar without resolving or executing package scripts', async () => {
const { root, destination } = await fixture()
const source = join(root, 'package')
await mkdir(source)
await writeFile(join(source, 'package.json'), JSON.stringify({ name: 'fixture', version: '1.0.0', scripts: { install: 'must-not-run' } }))
await writeFile(join(source, 'worker.js'), 'worker')
const archive = join(root, 'package.tgz')
await tar({ file: archive, cwd: root, gzip: true }, ['package'])
const bytes = await readFile(archive)
const checksum = createHash('sha512').update(bytes).digest()
const cache = join(root, 'archives')
await mkdir(cache)
await writeFile(join(cache, checksum.toString('hex')), bytes)
const artifact = { name: 'fixture', version: '1.0.0', directory: 'node_modules/fixture',
url: 'https://unused.invalid/fixture.tgz', integrity: `sha512-${checksum.toString('base64')}` }
await downloadOfficeSidecar([artifact], destination, cache)
expect(await readFile(join(destination, 'node_modules/fixture/worker.js'), 'utf8')).toBe('worker')
await expect(downloadOfficeSidecar([{ ...artifact, directory: 'node_modules/../../outside' }], destination, cache))
.rejects.toThrow('invalid package directory')
await expect(downloadOfficeSidecar([{ ...artifact, version: '2.0.0' }], destination, cache))
.rejects.toThrow('package identity mismatch')
await writeFile(join(cache, checksum.toString('hex')), 'corrupted archive')
await expect(downloadOfficeSidecar([artifact], destination, cache)).rejects.toThrow('checksum mismatch')
})
it('takes npm integrity from the workspace lock without querying registry metadata', async () => {
const { root } = await fixture()
const lockfile = join(root, 'pnpm-lock.yaml')
const integrity = `sha512-${Buffer.alloc(64, 7).toString('base64')}`
await writeFile(lockfile, JSON.stringify({ packages: { '@scope/[email protected]': { resolution: { integrity } } } }))
expect(await runtimeNpmArchive('@scope/fixture', '1.2.3', lockfile)).toEqual({
name: '@scope/fixture', version: '1.2.3', integrity,
url: 'https://registry.npmjs.org/@scope/fixture/-/fixture-1.2.3.tgz',
})
await expect(runtimeNpmArchive('@scope/fixture', '1.2.4', lockfile)).rejects.toThrow('missing SHA-512 integrity')
await writeFile(lockfile, JSON.stringify({ packages: { '@scope/[email protected]': { resolution: { integrity: 'sha256-invalid' } } } }))
await expect(runtimeNpmArchive('@scope/fixture', '1.2.3', lockfile)).rejects.toThrow('missing SHA-512 integrity')
})
+49 -23
View File
@@ -1,6 +1,8 @@
/** Keep LibreOffice workers, prebuilt engines, and their dependencies on the real filesystem. */
import { cp, mkdir, rm } from 'node:fs/promises'
import { join, relative, sep } from 'node:path'
import { readFile } from 'node:fs/promises'
import { isAbsolute, join, relative, sep } from 'node:path'
import { downloadRuntimeNpmPackage, type RuntimeNpmArchive } from './primary-runtime/prepare.ts'
import { load as parseYaml } from 'js-yaml'
import { officePackageDirectories } from './libreoffice-packages.mjs'
/** pkg applies these exclusions to dependency `files` as well as root asset globs. */
@@ -9,29 +11,53 @@ export const OFFICE_ASSET_IGNORES = [
'**/node_modules/@deepseek-ai/libreoffice-kit-*/**',
]
/** One npm package at its existing sidecar dependency location. */
export interface OfficePackageArchive extends RuntimeNpmArchive {
/** Relative package directory inside the sidecar. */
readonly directory: string
}
/**
* Copy the installed Office dependency tree without changing package contents or executable modes.
* Harness sidecars require the kit's declared target native engine, or WASM for other targets.
* Missing target engines fail with their package name; missing required dependencies or paths outside the deployed closure also fail.
* @param staging - Symlink-free deployed Node closure.
* @param destination - Target-specific Office directory beside the executable; replaced when present.
* @param target - Node platform and CPU of the executable.
* @returns Relative package directories included in the sidecar.
* Resolve one installed npm version against its reviewed workspace lock integrity.
* @param name - Published package name.
* @param version - Exact installed version.
* @param lockfile - Workspace lock recording the exact published archive.
* @returns Locked npm tarball identity; rejects versions without SHA-512 integrity.
*/
export async function copyOfficeSidecar(
staging: string,
destination: string,
target: { platform: string; arch: string },
): Promise<string[]> {
export async function runtimeNpmArchive(name: string, version: string, lockfile = join(import.meta.dirname, '../pnpm-lock.yaml')): Promise<RuntimeNpmArchive> {
const lock = parseYaml(await readFile(lockfile, 'utf8')) as { packages?: Record<string, { resolution?: { integrity?: string } }> }
const integrity = lock.packages?.[`${name}@${version}`]?.resolution?.integrity
if (typeof integrity !== 'string' || !/^sha512-[A-Za-z0-9+/]+={0,2}$/u.test(integrity)) {
throw new Error(`runtime npm lock: missing SHA-512 integrity for ${name}@${version}`)
}
return { name, version, url: `https://registry.npmjs.org/${name}/-/${name.split('/').at(-1)}-${version}.tgz`, integrity }
}
/**
* Lock the installed Office closure to published npm tarballs, preserving nested dependency locations.
* @param staging - Complete installed Node closure.
* @param target - Sidecar platform and CPU.
* @returns Download records for the selected engine and all dependencies.
*/
export async function officeSidecarArchives(staging: string, target: { platform: string; arch: string }): Promise<OfficePackageArchive[]> {
const directories = await officePackageDirectories(staging, target)
await rm(destination, { recursive: true, force: true })
await mkdir(destination, { recursive: true })
for (const source of directories) {
const nestedModules = join(source, 'node_modules')
await cp(source, join(destination, relative(staging, source)), {
recursive: true,
filter: path => path !== nestedModules && !path.startsWith(nestedModules + sep),
})
return Promise.all(directories.map(async (directory) => {
const manifest = JSON.parse(await readFile(join(directory, 'package.json'), 'utf8')) as { name: string; version: string }
return { ...await runtimeNpmArchive(manifest.name, manifest.version), directory: relative(staging, directory).split(sep).join('/') }
}))
}
/**
* Download the complete pinned Office sidecar without npm or package lifecycle scripts.
* @param packages - Build-locked npm packages and dependency locations.
* @param destination - Empty sidecar directory.
* @param cache - Verified archive cache.
* @returns Resolves after all packages are unpacked and checked.
*/
export async function downloadOfficeSidecar(packages: readonly OfficePackageArchive[], destination: string, cache: string): Promise<void> {
for (const artifact of packages) {
if (isAbsolute(artifact.directory) || artifact.directory.split(/[\\/]/u).some(part => part === '..')
|| !artifact.directory.startsWith('node_modules/')) throw new Error(`Office download: invalid package directory ${artifact.directory}`)
await downloadRuntimeNpmPackage(artifact, join(destination, artifact.directory), cache)
}
return directories.map(directory => relative(staging, directory))
}
+2 -1
View File
@@ -175,7 +175,8 @@ describe('Python runtime executable builder CLI', () => {
expect(result.status).toBe(0)
expect(result.stdout).toContain('exec pkg')
expect(result.stdout).toContain('--sea --targets node24-macos-x64')
expect(result.stdout).toContain('prepare Python and Office skills for mac-x64')
expect(result.stdout).toContain('keep only node-pty prebuilds darwin-x64')
expect(result.stdout).toContain('lock optional resource downloads and copy Office skills')
expect(result.stdout).toContain(join(root, 'dist-exe', 'macos-x64'))
})
+63 -21
View File
@@ -6,17 +6,20 @@
* runtime imports that pkg cannot discover statically.
*/
import { createHash } from 'node:crypto'
import { build as bundle } from 'tsdown'
import { spawn } from 'node:child_process'
import { pnpmInvocation, restoreLegacyHoists } from './executable-packaging.ts'
import { existsSync, statSync } from 'node:fs'
import { chmod, copyFile, cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { chmod, copyFile, cp, glob, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'
import { basename, dirname, join, resolve, sep } from 'node:path'
import { parseArgs } from 'node:util'
import { pathToFileURL } from 'node:url'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './executable-native-pty.ts'
import { copyOfficeSidecar, OFFICE_ASSET_IGNORES } from './build-exe-for-python-sdk-office.ts'
import { officeSidecarArchives, runtimeNpmArchive, OFFICE_ASSET_IGNORES } from './build-exe-for-python-sdk-office.ts'
import { deduplicateStagedWorkspacePackages, materializeStagedLinks } from './build-exe-for-python-sdk-staging.ts'
import { preparePrimaryRuntime, smokePrimaryRuntime, type PrimaryRuntimeTarget } from './primary-runtime/prepare.ts'
import { prepareOfficeSkillAssets } from './primary-runtime/prepare.ts'
import primaryLock from './primary-runtime/lock.json' with { type: 'json' }
const root = resolve(import.meta.dirname, '..')
@@ -45,6 +48,7 @@ const DEPLOY_ONLY_DOCS = ['README.md', 'README.zh.md', 'README.i18n.yaml']
*/
const ASSET_GLOBS = [
'package.json',
'*.mjs',
'node_modules/**/*.js',
'node_modules/**/*.cjs',
'node_modules/**/*.mjs',
@@ -301,6 +305,9 @@ class SingleExeBuild {
for (const name of DEPLOY_ONLY_DOCS) console.log(`build-exe-for-python-sdk: [dry-run] rm -f ${join(this.staging, name)}`)
} else {
await Promise.all(DEPLOY_ONLY_DOCS.map(name => rm(join(this.staging, name), { force: true })))
for await (const file of glob('node_modules/**/{README*.md,CHANGELOG*.md,HISTORY*.md,*.map,*.d.ts,*.d.mts,*.d.cts}', {
cwd: this.staging, exclude: ['node_modules/**/assets/**'],
})) await rm(join(this.staging, file), { force: true })
}
}
@@ -331,7 +338,14 @@ class SingleExeBuild {
/** Add the executable entry and pkg assets to the staged manifest. */
async injectPkgConfig(): Promise<void> {
const patch = { bin: ENTRY_BIN, pkg: { assets: ASSET_GLOBS, ignore: OFFICE_ASSET_IGNORES } }
const sourceModules = join(root, 'node_modules').replaceAll('\\', '/')
const patch = { bin: ENTRY_BIN, pkg: { assets: ASSET_GLOBS, ignore: [
...OFFICE_ASSET_IGNORES,
`${sourceModules}/**`, `${sourceModules}/.pnpm/**`,
...['packages', 'apps', 'vendor', 'native', 'scripts'].map(directory => `${join(root, directory).replaceAll('\\', '/')}/**`),
`${root.replaceAll('\\', '/')}/tsconfig*.json`,
'**/*.map', '**/*.d.ts', '**/*.d.mts', '**/*.d.cts',
] } }
const manifestPath = join(this.staging, 'package.json')
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] patch ${manifestPath} with ${JSON.stringify(patch)}`)
@@ -343,8 +357,28 @@ class SingleExeBuild {
if (!existsSync(join(this.staging, ENTRY_BIN))) {
throw new Error(`build-exe-for-python-sdk: staged bootstrap ${join(this.staging, ENTRY_BIN)} is missing.`)
}
const helpers = [
{ name: 'primary-runtime', source: 'scripts/primary-runtime/prepare.ts', exports: 'downloadNodeRuntime, preparePrimaryRuntime' },
{ name: 'office-sidecar', source: 'scripts/build-exe-for-python-sdk-office.ts', exports: 'downloadOfficeSidecar' },
]
const entries: Record<string, string> = {}
for (const helper of helpers) {
const entry = join(this.staging, `.${helper.name}-entry.ts`)
entries[helper.name] = entry
await writeFile(entry, `export { ${helper.exports} } from ${JSON.stringify(join(root, helper.source).replaceAll('\\', '/'))}\n`)
}
try {
await bundle({ config: false, tsconfig: false, inputOptions: { tsconfig: false }, target: 'es2024', entry: entries,
outDir: this.staging, clean: false, format: 'esm', platform: 'node', dts: false,
deps: { neverBundle: [/^@deepseek-ai\//u], alwaysBundle: [/.*/u] }, shims: true, outExtensions: () => ({ js: '.mjs' }),
define: { 'import.meta.main': 'false' } })
} finally {
for (const entry of Object.values(entries)) await rm(entry, { force: true })
}
const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as Record<string, unknown>
await writeFile(manifestPath, `${JSON.stringify({ ...manifest, ...patch }, null, 2)}\n`)
await this.run('resource helper imports', process.execPath, ['--input-type=module', '--eval',
['primary-runtime.mjs', 'office-sidecar.mjs'].map(file => `await import(${JSON.stringify(pathToFileURL(join(this.staging, file)).href)})`).join('\n')])
console.log(`build-exe-for-python-sdk: injected pkg config into ${manifestPath}`)
}
@@ -357,6 +391,7 @@ class SingleExeBuild {
const productBase = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
const product = target.platform === 'win' ? `${productBase}.exe` : productBase
await this.prepareNativePty(target)
const platform = target.platform === 'macos' ? 'darwin' : target.platform === 'win' ? 'win32' : target.platform
if (!this.cli.dryRun) await mkdir(this.outDir, { recursive: true })
await this.runPnpm(`pkg ${target.spec}`, [
'exec',
@@ -371,26 +406,22 @@ class SingleExeBuild {
if (!this.cli.dryRun && !existsSync(product)) {
throw new Error(`build-exe-for-python-sdk: product ${product} is missing after the pkg run; inspect ${this.outDir}.`)
}
const office = `${productBase}-office`
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] copy Office dependency closure from ${this.staging} to ${office}`)
} else {
const platform = target.platform === 'macos' ? 'darwin' : target.platform === 'win' ? 'win32' : target.platform
const packages = await copyOfficeSidecar(this.staging, office, { platform, arch: target.arch })
console.log(`build-exe-for-python-sdk: copied ${packages.length} Office packages to ${office}`)
}
const ripgrep = await this.copyRipgrepSidecar(target, product)
const resources = join(this.outDir, `${target.platform}-${target.arch}`)
const runtimeTarget = `${target.platform === 'macos' ? 'mac' : target.platform}-${target.arch}` as PrimaryRuntimeTarget
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] prepare Python and Office skills for ${runtimeTarget} in ${resources}`)
console.log(`build-exe-for-python-sdk: [dry-run] lock optional resource downloads and copy Office skills to ${resources}`)
} else {
const { version } = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')) as { version: string }
await preparePrimaryRuntime({ target: runtimeTarget, output: resources,
cache: join(tmpdir(), 'dsh-primary-runtime-downloads'), version })
smokePrimaryRuntime(join(resources, 'primary-runtime'))
await rm(resources, { recursive: true, force: true })
await mkdir(resources, { recursive: true })
const { version, packageManager } = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')) as { version: string; packageManager: string }
const office = await officeSidecarArchives(this.staging, { platform, arch: target.arch })
const pnpm = await runtimeNpmArchive('pnpm', packageManager.replace('pnpm@', ''))
const inputs = { version, target: `${target.platform === 'macos' ? 'mac' : target.platform}-${target.arch}`, office, pnpm }
const identity = createHash('sha256').update(JSON.stringify({ ...inputs, primaryLock })).digest('hex')
await writeFile(join(resources, 'downloads.json'), JSON.stringify({ ...inputs, identity }, undefined, 2) + '\n')
await prepareOfficeSkillAssets(join(this.staging, 'node_modules/@deepseek-ai/dsh-skill-office/assets'), join(resources, 'office-skills'))
}
if (target.platform !== 'macos') return [product, ripgrep, office, resources]
if (target.platform !== 'macos') return [product, ripgrep, resources]
const spawnHelper = `${product}-spawn-helper`
const source = join(this.staging, 'node_modules', 'node-pty', 'prebuilds', `darwin-${target.arch}`, 'spawn-helper')
if (this.cli.dryRun) {
@@ -399,7 +430,7 @@ class SingleExeBuild {
await copyFile(source, spawnHelper)
await chmod(spawnHelper, 0o755)
}
return [product, ripgrep, spawnHelper, office, resources]
return [product, ripgrep, spawnHelper, resources]
}
/** Copy the target ripgrep binary beside the executable so Node can spawn it outside pkg's virtual filesystem. */
@@ -446,6 +477,17 @@ class SingleExeBuild {
'node_modules',
'node-pty',
)
const platform = target.platform === 'macos' ? 'darwin' : target.platform === 'win' ? 'win32' : target.platform
const selected = `${platform}-${target.arch}`
const prebuilds = join(this.staging, 'node_modules/node-pty/prebuilds')
if (this.cli.dryRun) console.log(`build-exe-for-python-sdk: [dry-run] keep only node-pty prebuilds ${selected}`)
else {
const source = join(packageDirectory, 'prebuilds', selected)
if (existsSync(source)) await cp(source, join(prebuilds, selected), { recursive: true })
for (const entry of await readdir(prebuilds, { withFileTypes: true })) {
if (entry.isDirectory() && entry.name !== selected) await rm(join(prebuilds, entry.name), { recursive: true, force: true })
}
}
if (target.platform === 'win') {
if (target.arch !== 'x64') {
throw new Error('build-exe-for-python-sdk: Windows supports x64 only.')
+4 -48
View File
@@ -56,11 +56,6 @@ def runtime_filenames(executable_name: str) -> tuple[str, ...]:
return (*names, f"{executable_name}-spawn-helper") if "-macos-" in executable_name else names
def office_sidecar_name(executable_name: str) -> str:
"""Return the complete Office dependency directory for one executable."""
return f"{executable_name.removesuffix('.exe')}-office"
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--package", choices=("sdk", "runtime"), required=True)
@@ -229,54 +224,18 @@ def stage_runtime(destination: Path, version: str, executable: Path, executable_
source_directory = executable.parent
for filename in runtime_filenames(executable_name):
shutil.copy2(source_directory / filename, runtime_dir / filename)
office = office_sidecar_name(executable_name)
shutil.copytree(source_directory / office, runtime_dir / office)
resources = executable_name.removeprefix("deepseek-harness-sdk-runtime-").removesuffix(".exe")
shutil.copytree(source_directory / resources, runtime_dir / resources)
def verify_office_payload(archive: zipfile.ZipFile, office_modules: str, platform_tag: str) -> None:
"""Check packaged engine assets and native helper executable permissions."""
names = set(archive.namelist())
adapter = f"{office_modules}/@deepseek-ai/libreoffice-kit/package.json"
if adapter not in names:
raise RuntimeError("Office dependency is missing: libreoffice-kit")
engines = f"{office_modules}/@deepseek-ai"
target = next(name for name, value in PLATFORMS.items() if value[0] == platform_tag)
native_target = target.replace("win-", "win32-").replace("macos-", "darwin-")
declared = json.loads(archive.read(adapter)).get("optionalDependencies", {})
selected = native_target if f"@deepseek-ai/libreoffice-kit-{native_target}" in declared else "wasm"
for required in (f"libreoffice-kit-{selected}/prebuilds.json",):
if f"{engines}/{required}" not in names:
raise RuntimeError(f"Office dependency is missing: {required}")
manifests = (
name for name in names
if name.startswith(f"{engines}/libreoffice-kit-")
and name.endswith("/prebuilds.json")
and name.count("/") == engines.count("/") + 2
)
for manifest_path in manifests:
if manifest_path != f"{engines}/libreoffice-kit-{selected}/prebuilds.json":
raise RuntimeError(f"Unexpected Office engine for {platform_tag}: {manifest_path}")
manifest = json.loads(archive.read(manifest_path))
engine = manifest["engine"]
native = engine["kind"] == "native"
fields = ("executable",) if native else ("loader", "wasm", "data", "metadata")
for field in fields:
asset = f"{manifest_path.rsplit('/', 1)[0]}/{engine[field]}"
if asset not in names:
raise RuntimeError(f"Office engine asset is missing: {asset}")
mode = archive.getinfo(asset).external_attr >> 16
if native and platform_tag != "win_amd64" and mode & stat.S_IXUSR == 0:
raise RuntimeError(f"Office helper lost its executable bit: {asset}")
def verify_wheel(
wheel: Path,
package: str,
version: str,
platform: tuple[str, str] | None,
) -> None:
if wheel.stat().st_size > 100_000_000:
raise RuntimeError(f"{wheel} is {wheel.stat().st_size} bytes and exceeds the PyPI single-file limit of 100000000 bytes")
expected_tag = "py3-none-any" if platform is None else f"py3-none-{platform[0]}"
expected_distribution = SDK_DISTRIBUTION if package == "sdk" else RUNTIME_DISTRIBUTION
dist_info = f"{expected_distribution.replace('-', '_')}-{version}.dist-info"
@@ -308,15 +267,12 @@ def verify_wheel(
]
if package == "runtime":
assert platform is not None
office = office_sidecar_name(platform[1])
resources = platform[1].removeprefix("deepseek-harness-sdk-runtime-").removesuffix(".exe")
expected_files = sorted((*runtime_filenames(platform[1]), office, resources))
expected_files = sorted((*runtime_filenames(platform[1]), resources))
found_files = sorted({name.split("/runtime/", 1)[1].split("/", 1)[0] for name in runtime_payload})
if found_files != expected_files:
raise RuntimeError(f"{wheel} runtime payload must be {expected_files}, found {found_files}")
office_modules = f"deepseek_harness_runtime/runtime/{office}/node_modules"
verify_office_payload(archive, office_modules, platform[0])
validate = runpy.run_path(str(ROOT / "python/sdk-runtime/src/deepseek_harness_runtime/_resources.py"))["validate_resources"]
validate = runpy.run_path(str(ROOT / "python/sdk-runtime/src/deepseek_harness_runtime/_resources.py"))["validate_downloads"]
target = next(name for name, value in PLATFORMS.items() if value == platform)
validate(zipfile.Path(archive, f"deepseek_harness_runtime/runtime/{resources}/"), target)
for runtime_file in runtime_payload:
+1 -1
View File
@@ -15,7 +15,7 @@ export async function apply(ctx, config) {
if (json === undefined) throw new Error('Office skill did not supply CLI paths')
const { libreofficeKit: { node, cli } } = JSON.parse(json)
const options = { env: { ...process.env, PATH: '' }, timeout: 120_000 }
const capabilities = await promisify(execFile)(node, [cli, 'capabilities'], options)
const capabilities = await promisify(execFile)(node, [cli, 'capabilities', '--json'], options)
await promisify(execFile)(node, [cli, 'convert', '--input', config.input, '--output', config.output + '.cli.pdf'], options)
const pdf = await readFile(config.output + '.cli.pdf')
if (pdf.subarray(0, 5).toString() !== '%PDF-') throw new Error('Skill CLI did not create a PDF')
+5 -1
View File
@@ -1,6 +1,6 @@
import { readFileSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { basename, join } from 'node:path'
import { zipSync } from 'fflate'
@@ -61,7 +61,11 @@ it('extracts a hash-verified cached library without a Python installer or networ
const hash = createHash('sha256').update(libraryWheel).digest('hex')
const archive = join(root, hash)
await writeFile(archive, libraryWheel)
const original = await stat(archive)
expect(await downloadPrimaryRuntimeAsset('https://unused.invalid/library.whl', hash, root)).toBe(archive)
const reused = await stat(archive)
expect(reused.ino).toBe(original.ino)
expect(reused.mtimeMs).toBe(original.mtimeMs)
await unpackPrimaryRuntimeWheel(archive, join(root, 'site-packages'))
expect(await readFile(join(root, 'site-packages/sample.py'), 'utf8')).toBe('sample = 42\n')
await writeFile(archive, 'corrupt archive')
+98 -30
View File
@@ -1,8 +1,8 @@
/** Prepare pinned, relocatable script interpreters without installing into the build host. */
import { execFileSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { createHash, randomUUID } from 'node:crypto'
import { cpSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
import { cp } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
@@ -10,30 +10,100 @@ import { dirname, join, resolve } from 'node:path'
import { parseArgs } from 'node:util'
import extractZip from 'extract-zip'
import { x as extractTar } from 'tar'
import { parsePrimaryRuntime, workspaceDependencyPaths, type PrimaryRuntimeManifest } from '../../packages/skill/tool-workspace-dependencies/src/index.ts'
import { parsePrimaryRuntime, workspaceDependencyPaths, type PrimaryRuntimeManifest } from '@deepseek-ai/dsh-tool-workspace-dependencies'
import lock from './lock.json' with { type: 'json' }
/**
* Download or reuse an archive only when its bytes match the release lock.
* @param url - Locked archive URL.
* @param sha256 - Expected SHA-256 digest.
* @param sha256 - Expected hexadecimal digest for the selected algorithm.
* @param cache - Download cache directory.
* @param algorithm - Digest algorithm; npm archives use SHA-512.
* @returns Verified local archive path.
*/
export async function downloadPrimaryRuntimeAsset(url: string, sha256: string, cache: string): Promise<string> {
export async function downloadPrimaryRuntimeAsset(url: string, sha256: string, cache: string, algorithm: 'sha256' | 'sha512' = 'sha256'): Promise<string> {
mkdirSync(cache, { recursive: true })
const destination = join(cache, sha256)
let bytes: Buffer
let cached = true
try { bytes = readFileSync(destination) } catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
const response = await fetch(url)
cached = false
const timeout = Number(process.env.DSH_RESOURCE_DOWNLOAD_TIMEOUT_MS ?? '300000')
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 2_147_483_647) throw new Error('DSH_RESOURCE_DOWNLOAD_TIMEOUT_MS must be an integer from 1 to 2147483647')
const response = await fetch(url, { signal: AbortSignal.timeout(timeout) })
if (!response.ok) throw new Error(`primary runtime download: ${String(response.status)} ${url}`)
bytes = Buffer.from(await response.arrayBuffer())
}
if (createHash('sha256').update(bytes).digest('hex') !== sha256) throw new Error(`primary runtime download: checksum mismatch for ${url}`)
writeFileSync(destination, bytes)
if (createHash(algorithm).update(bytes).digest('hex') !== sha256) {
rmSync(destination, { force: true })
throw new Error(`primary runtime download: checksum mismatch for ${url}`)
}
if (cached) return destination
const temporary = join(cache, `.${randomUUID()}`)
try {
writeFileSync(temporary, bytes)
renameSync(temporary, destination)
} finally {
rmSync(temporary, { force: true })
}
return destination
}
/** Pinned npm tarball, without dependency resolution or lifecycle scripts. */
export interface RuntimeNpmArchive {
/** Exact published package name. */
readonly name: string
/** Exact published version. */
readonly version: string
/** Registry tarball URL. */
readonly url: string
/** npm SHA-512 integrity. */
readonly integrity: string
}
/**
* Download and unpack one locked npm package without running its scripts.
* @param artifact - Published tarball identity and integrity.
* @param destination - Empty package directory.
* @param cache - Verified archive cache.
* @returns Resolves after package identity validation.
*/
export async function downloadRuntimeNpmPackage(artifact: RuntimeNpmArchive, destination: string, cache: string): Promise<void> {
if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/u.test(artifact.integrity)) throw new Error(`runtime download: invalid integrity for ${artifact.name}`)
const checksum = Buffer.from(artifact.integrity.slice(7), 'base64').toString('hex')
const archive = await downloadPrimaryRuntimeAsset(artifact.url, checksum, cache, 'sha512')
mkdirSync(destination, { recursive: true })
await extractTar({ file: archive, cwd: destination, strip: 1 })
const installed = JSON.parse(readFileSync(join(destination, 'package.json'), 'utf8')) as { name?: string; version?: string }
if (installed.name !== artifact.name || installed.version !== artifact.version) throw new Error(`runtime download: package identity mismatch for ${artifact.name}`)
}
/**
* Prepare the locked standalone Node independently of Python and pnpm.
* @param target - Target interpreter platform.
* @param destination - Directory receiving bin/node and LICENSE.
* @param cache - Verified archive cache.
* @returns Resolves after extracting the interpreter.
*/
export async function downloadNodeRuntime(target: PrimaryRuntimeTarget, destination: string, cache: string): Promise<void> {
const artifact = lock.targets[target]
const filename = `node-v${lock.nodeVersion}-${artifact.nodeArchive}`
const archive = await downloadPrimaryRuntimeAsset(`https://nodejs.org/dist/v${lock.nodeVersion}/${filename}`, artifact.nodeSha256, cache)
const staging = mkdtempSync(join(tmpdir(), 'dsh-node-'))
try {
if (target === 'win-x64') await extractZip(archive, { dir: staging })
else await extractTar({ file: archive, cwd: staging })
const source = join(staging, filename.replace(/\.(?:zip|tar\.gz)$/u, ''))
mkdirSync(join(destination, 'bin'), { recursive: true })
cpSync(join(source, ...(target === 'win-x64' ? ['node.exe'] : ['bin', 'node'])),
join(destination, 'bin', target === 'win-x64' ? 'node.exe' : 'node'))
cpSync(join(source, 'LICENSE'), join(destination, 'LICENSE'))
} finally {
rmSync(staging, { recursive: true, force: true })
}
}
async function pythonArchive(target: keyof typeof lock.targets, cache: string): Promise<string> {
const artifact = lock.targets[target]
const filename = `cpython-${lock.pythonVersion}+${lock.pythonRelease}-${artifact.pythonTarget}-install_only_stripped.tar.gz`
@@ -91,7 +161,7 @@ export async function prepareOfficeSkillAssets(source: string, destination: stri
/** A locked interpreter and wheel target. */
export type PrimaryRuntimeTarget = keyof typeof lock.targets
/** Build-only inputs shared by Desktop and SDK carriers. */
/** Resource preparation inputs shared by builds and explicit SDK downloads. */
export interface PreparePrimaryRuntimeOptions {
/** Target whose archives and wheels are downloaded. */
readonly target: PrimaryRuntimeTarget
@@ -101,6 +171,10 @@ export interface PreparePrimaryRuntimeOptions {
readonly cache: string
/** Carrier release recorded in the legacy desktopVersion manifest field. */
readonly version: string
/** Locked npm pnpm tarball for installed carriers; build hosts otherwise copy their installed pnpm. */
readonly pnpmArchive?: RuntimeNpmArchive
/** Ordinary filesystem Office assets; build hosts otherwise resolve the workspace package. */
readonly skillSource?: string
/** Omit Node.js and pnpm for carriers providing only Python. */
readonly pythonOnly?: boolean
}
@@ -113,7 +187,6 @@ export interface PreparePrimaryRuntimeOptions {
export async function preparePrimaryRuntime(options: PreparePrimaryRuntimeOptions): Promise<void> {
const { target } = options
const paths = { runtime: resolve(options.output), downloads: resolve(options.cache) }
const artifact = lock.targets[target]
mkdirSync(paths.runtime, { recursive: true })
mkdirSync(paths.downloads, { recursive: true })
const staging = mkdtempSync(join(tmpdir(), 'dsh-primary-'))
@@ -123,23 +196,18 @@ export async function preparePrimaryRuntime(options: PreparePrimaryRuntimeOption
mkdirSync(dependencies, { recursive: true })
let pnpmVersion: string | undefined
if (!options.pythonOnly) {
const nodeFilename = `node-v${lock.nodeVersion}-${artifact.nodeArchive}`
const nodeArchive = await downloadPrimaryRuntimeAsset(`https://nodejs.org/dist/v${lock.nodeVersion}/${nodeFilename}`, artifact.nodeSha256, paths.downloads)
const unpackedNode = join(staging, 'node')
mkdirSync(unpackedNode)
if (target === 'win-x64') await extractZip(nodeArchive, { dir: unpackedNode })
else await extractTar({ file: nodeArchive, cwd: unpackedNode })
const nodeSource = join(unpackedNode, nodeFilename.replace(/\.(?:zip|tar\.gz)$/u, ''))
mkdirSync(join(dependencies, 'node', 'bin'), { recursive: true })
mkdirSync(join(dependencies, 'node', 'node_modules'))
writeFileSync(join(dependencies, 'node', 'node_modules', 'README.txt'), 'Reserved for bundled Node packages. pnpm uses its default installation directories.\n')
cpSync(join(nodeSource, ...(target === 'win-x64' ? ['node.exe'] : ['bin', 'node'])),
join(dependencies, 'node', 'bin', target === 'win-x64' ? 'node.exe' : 'node'))
cpSync(join(nodeSource, 'LICENSE'), join(dependencies, 'node', 'LICENSE'))
const require = createRequire(import.meta.url)
const pnpmManifest = require.resolve('pnpm')
pnpmVersion = (JSON.parse(readFileSync(pnpmManifest, 'utf8')) as { version: string }).version
await cp(dirname(pnpmManifest), join(dependencies, 'pnpm'), { recursive: true, dereference: true })
const node = join(dependencies, 'node')
await downloadNodeRuntime(target, node, paths.downloads)
mkdirSync(join(node, 'node_modules'))
writeFileSync(join(node, 'node_modules', 'README.txt'), 'Reserved for bundled Node packages. pnpm uses its default installation directories.\n')
if (options.pnpmArchive !== undefined) {
pnpmVersion = options.pnpmArchive.version
await downloadRuntimeNpmPackage(options.pnpmArchive, join(dependencies, 'pnpm'), paths.downloads)
} else {
const pnpmManifest = createRequire(import.meta.url).resolve('pnpm')
pnpmVersion = (JSON.parse(readFileSync(pnpmManifest, 'utf8')) as { version: string }).version
await cp(dirname(pnpmManifest), join(dependencies, 'pnpm'), { recursive: true, dereference: true })
}
}
await extractTar({ file: await pythonArchive(target, paths.downloads), cwd: dependencies })
const manifest: PrimaryRuntimeManifest = {
@@ -152,7 +220,7 @@ export async function preparePrimaryRuntime(options: PreparePrimaryRuntimeOption
pythonPackages: lock.pythonPackages,
}
const entries = workspaceDependencyPaths(output, manifest)
for (const wheel of [...artifact.wheels, ...lock.wheels]) {
for (const wheel of [...lock.targets[target].wheels, ...lock.wheels]) {
await unpackPrimaryRuntimeWheel(await downloadPrimaryRuntimeAsset(wheel.url, wheel.sha256, paths.downloads), entries.pythonPackages)
}
writeFileSync(join(output, 'runtime.json'), `${JSON.stringify(manifest, undefined, 2)}\n`)
@@ -162,8 +230,8 @@ export async function preparePrimaryRuntime(options: PreparePrimaryRuntimeOption
} finally {
rmSync(staging, { recursive: true, force: true })
}
const require = createRequire(import.meta.url)
await prepareOfficeSkillAssets(join(dirname(require.resolve('@deepseek-ai/dsh-skill-office/package.json')), 'assets'),
const assets = options.skillSource ?? join(dirname(createRequire(import.meta.url).resolve('@deepseek-ai/dsh-skill-office/package.json')), 'assets')
await prepareOfficeSkillAssets(assets,
join(paths.runtime, 'office-skills'))
}
+135
View File
@@ -0,0 +1,135 @@
/** Explicit resource acquisition through the Python carrier's private dispatcher. */
import { spawn } from 'node:child_process'
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile, copyFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { expect, it } from 'vitest'
async function fixture() {
const root = await mkdtemp(join(tmpdir(), 'dsh-resource-download-'))
const bootstrap = join(root, 'node', 'runtime-bootstrap.mjs')
const platform = process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'win' : process.platform
const resources = join(root, `${platform}-${process.arch}`)
const cache = join(root, 'cache')
async function file(path: string, content: string) {
await mkdir(dirname(path), { recursive: true })
await writeFile(path, content)
}
await file(join(resources, 'downloads.json'), JSON.stringify({ identity: 'a'.repeat(64), office: [], pnpm: {} }))
await mkdir(dirname(bootstrap), { recursive: true })
await copyFile(new URL('../python/sdk-runtime/runtime-bootstrap.mjs', import.meta.url), bootstrap)
await file(join(root, 'node/node_modules/@deepseek-ai/dsh-http-proxy/package.json'), '{"type":"module","exports":"./index.mjs"}')
await file(join(root, 'node/node_modules/@deepseek-ai/dsh-http-proxy/index.mjs'), 'export async function installProxyFromEnvironment(env) { if (typeof env.get !== "function") throw new Error("missing environment lookup"); return async () => {} }')
await file(join(root, 'node/node_modules/@deepseek-ai/dsh-sandbox-windows-acl/package.json'), '{"type":"module","exports":{"./runner":"./runner.mjs"}}')
await file(join(root, 'node/node_modules/@deepseek-ai/dsh-sandbox-windows-acl/runner.mjs'), '')
await file(join(root, 'node/node_modules/@deepseek-ai/dsh/package.json'), '{"type":"module"}')
await file(join(root, 'node/node_modules/@deepseek-ai/dsh/lib/bin.js'), 'export async function runCli() { if (process.argv[2]) { console.log(JSON.stringify({ argv: process.argv.slice(2) })); return } console.log(JSON.stringify({ primary: process.env.DSH_BUNDLED_PRIMARY_RUNTIME, office: process.env.DSH_BUNDLED_OFFICE_CLI })) }')
await file(join(root, 'node/primary-runtime.mjs'), `
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
export async function downloadNodeRuntime(target, destination) {
if (process.env.DSH_RUNTIME_DOWNLOAD !== undefined) throw new Error("private selector leaked");
await mkdir(join(destination, 'bin'), { recursive: true });
await writeFile(join(destination, 'bin', process.platform === 'win32' ? 'node.exe' : 'node'), 'node');
}
export async function preparePrimaryRuntime({ output }) {
if (process.env.DSH_RUNTIME_DOWNLOAD !== undefined) throw new Error("private selector leaked");
await mkdir(join(output, 'primary-runtime'), { recursive: true });
await writeFile(join(output, 'primary-runtime/runtime.json'), '{}');
}
`)
await file(join(root, 'node/office-sidecar.mjs'), `
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
export async function downloadOfficeSidecar(packages, destination) {
const directory = join(destination, 'node_modules/@deepseek-ai/libreoffice-kit/lib');
await mkdir(directory, { recursive: true });
await writeFile(join(directory, 'cli.js'), 'cli');
if (process.env.DSH_FIXTURE_FAIL) throw new Error('fixture download failed');
if (process.env.DSH_FIXTURE_BARRIER) {
process.stderr.write('ready\\n');
await new Promise(resolve => process.stdin.once('data', resolve));
process.stdin.pause();
}
}
`)
function run(resource?: string, extra: NodeJS.ProcessEnv = {}, args: string[] = []) {
const child = spawn(process.execPath, [bootstrap, ...args], {
env: { ...process.env, DSH_RESOURCE_CACHE: cache, DSH_RUNTIME_DOWNLOAD: resource, DSH_BUNDLED_PRIMARY_RUNTIME: undefined,
DSH_OFFICE_SIDECAR: undefined, DSH_BUNDLED_OFFICE_CLI: undefined, ...extra },
stdio: ['pipe', 'pipe', 'pipe'],
})
let stdout = '', stderr = ''
child.stdout.on('data', (data) => { stdout += String(data) })
let reportReady: (() => void) | undefined
let failReady: ((error: Error) => void) | undefined
const ready = extra.DSH_FIXTURE_BARRIER === undefined ? Promise.resolve()
: new Promise<void>((resolve, reject) => { reportReady = resolve; failReady = reject })
child.stderr.on('data', (data) => { stderr += String(data); if (stderr.includes('ready\n')) reportReady?.() })
const done = new Promise<{ code: number | null; stdout: string; stderr: string }>((resolve, reject) => {
child.once('error', reject)
child.once('close', (code) => { failReady?.(new Error(`download exited before readiness: ${stderr}`)); resolve({ code, stdout, stderr }) })
})
return { child, ready, done }
}
return { root, cache, run }
}
it('starts without downloading, independently downloads either resource, and reuses completed caches', async () => {
const { root, cache, run } = await fixture()
try {
expect(await run().done).toMatchObject({ code: 0, stdout: '{}\n' })
await expect(readdir(cache)).rejects.toMatchObject({ code: 'ENOENT' })
const primary = await run('primary').done
expect(primary.code, primary.stderr).toBe(0)
expect(JSON.parse(primary.stdout)).toBe(join(cache, 'a'.repeat(64), 'primary', 'primary-runtime'))
await expect(readdir(join(cache, 'a'.repeat(64), 'office'))).rejects.toMatchObject({ code: 'ENOENT' })
const office = await run('office').done
expect(office.code, office.stderr).toBe(0)
const location = JSON.parse(office.stdout) as string
expect(await readFile(join(location, 'node_modules/@deepseek-ai/libreoffice-kit/lib/cli.js'), 'utf8')).toBe('cli')
expect(await run('office', { DSH_FIXTURE_FAIL: '1' }).done).toMatchObject({ code: 0, stdout: office.stdout })
} finally { await rm(root, { recursive: true, force: true }) }
})
it('cleans failed staging and allows an explicit retry', async () => {
const { root, cache, run } = await fixture()
try {
const failed = await run('office', { DSH_FIXTURE_FAIL: '1' }).done
expect(failed.code).toBe(1)
expect(failed.stderr).toContain('fixture download failed')
expect(await readdir(join(cache, 'a'.repeat(64)))).toEqual([])
expect((await run('office').done).code).toBe(0)
} finally { await rm(root, { recursive: true, force: true }) }
})
it('publishes one complete cache when two processes download concurrently', async () => {
const { root, cache, run } = await fixture()
const jobs = [run('office', { DSH_FIXTURE_BARRIER: '1' }), run('office', { DSH_FIXTURE_BARRIER: '1' })]
try {
await Promise.all(jobs.map(job => job.ready))
for (const job of jobs) job.child.stdin.end('continue')
const results = await Promise.all(jobs.map(job => job.done))
for (const result of results) expect(result.code, result.stderr).toBe(0)
expect(results[0]!.stdout).toBe(results[1]!.stdout)
expect(await readdir(join(cache, 'a'.repeat(64)))).toEqual(['office'])
} finally {
for (const job of jobs) if (job.child.exitCode === null && job.child.signalCode === null) job.child.kill()
await Promise.allSettled(jobs.map(job => job.done))
await rm(root, { recursive: true, force: true })
}
})
it('leaves ordinary CLI arguments to the CLI and rejects an invalid private download selector', async () => {
const { root, cache, run } = await fixture()
try {
expect(await run(undefined, {}, ['--download-office']).done).toMatchObject({
code: 0, stdout: JSON.stringify({ argv: ['--download-office'] }) + '\n',
})
const rejected = await run('invalid').done
expect(rejected.code).toBe(1)
expect(rejected.stderr).toContain('DSH_RUNTIME_DOWNLOAD must select office or primary')
await expect(readdir(cache)).rejects.toMatchObject({ code: 'ENOENT' })
} finally { await rm(root, { recursive: true, force: true }) }
})
+47 -7
View File
@@ -884,10 +884,18 @@ def main() -> None:
def smoke_sdk_authoring(base_url: str, executable: Path, update_snapshots: bool) -> None:
"""Query the bundled Python and switch skills without replacing that environment."""
with tempfile.TemporaryDirectory(prefix="dsh-sdk-authoring-cache-") as cache_directory:
_smoke_sdk_authoring_cached(base_url, executable, update_snapshots, Path(cache_directory))
def _smoke_sdk_authoring_cached(base_url: str, executable: Path, update_snapshots: bool, cache: Path) -> None:
from deepseek_harness import DeepSeekHarness
resources = executable.with_name(executable.name.removeprefix("deepseek-harness-sdk-runtime-").removesuffix(".exe"))
manifest = json.loads((resources / "primary-runtime/runtime.json").read_text())
cache_environment = {**os.environ, "DSH_RESOURCE_CACHE": str(cache), "DSH_RUNTIME_DOWNLOAD": "primary"}
primary = Path(json.loads(subprocess.check_output([str(executable)], encoding="utf-8", env=cache_environment)))
resources = primary.parent
manifest = json.loads((primary / "runtime.json").read_text())
for mode in ("default", "replacement", "disabled"):
with tempfile.TemporaryDirectory(prefix="dsh-sdk-authoring-") as temporary:
root = Path(temporary).resolve()
@@ -903,7 +911,7 @@ def smoke_sdk_authoring(base_url: str, executable: Path, update_snapshots: bool)
provider="deepseek-official", model="smoke-model", cwd=str(root),
dsh_bin=str(executable), dsh_home=str(home), patches=(str(patch),),
api_key="sk-keyless-smoke", base_url=base_url,
env={"DSH_PERMISSION_MODE": "danger-full-access", "DSH_TELEMETRY_DISABLED": "1"},
env={"DSH_PERMISSION_MODE": "danger-full-access", "DSH_TELEMETRY_DISABLED": "1", "DSH_RESOURCE_CACHE": str(cache)},
request_timeout_seconds=60,
) as harness:
result = harness.run(AUTHORING_PROMPT, session_id="authoring")
@@ -958,7 +966,9 @@ def smoke_sdk_office(executable: Path) -> None:
shutil.copytree(source, destination)
else:
shutil.copy2(source, destination)
office = root / f"{stem}-office"
cache_environment = {**os.environ, "DSH_RESOURCE_CACHE": str(root / "cache")}
office = Path(json.loads(subprocess.check_output([str(relocated)], encoding="utf-8", env={**cache_environment, "DSH_RUNTIME_DOWNLOAD": "office"})))
assert not (office.parent / "primary").exists(), "Office download unexpectedly prepared the authoring environment"
adapter = office / "node_modules/@deepseek-ai/libreoffice-kit/package.json"
native = stem.removeprefix("deepseek-harness-sdk-runtime-").replace("win-", "win32-").replace("macos-", "darwin-")
declared = json.loads(adapter.read_text(encoding="utf-8")).get("optionalDependencies", {})
@@ -997,7 +1007,7 @@ def smoke_sdk_office(executable: Path) -> None:
patches=(str(patch),),
api_key="sk-keyless-smoke",
base_url="http://127.0.0.1:9",
env={"DSH_PERMISSION_MODE": "danger-full-access", "DSH_TELEMETRY_DISABLED": "1"},
env={"DSH_PERMISSION_MODE": "danger-full-access", "DSH_TELEMETRY_DISABLED": "1", "DSH_RESOURCE_CACHE": str(root / "cache")},
# The startup plugin awaits a converter with a 120-second deadline before JSON-RPC is ready.
initialize_timeout_seconds=180,
request_timeout_seconds=180,
@@ -1007,10 +1017,32 @@ def smoke_sdk_office(executable: Path) -> None:
if result["backend"] != expected_backend:
raise AssertionError(f"Office conversion did not use {expected_backend}: {result}")
if not result["moduleUrl"].startswith(office.as_uri() + "/"):
raise AssertionError(f"Office module was not loaded from the relocated wheel: {result}")
raise AssertionError(f"Office module was not loaded from the downloaded sidecar: {result}")
pdf = output.read_bytes()
if len(pdf) < 100 or not pdf.startswith(b"%PDF-"):
raise AssertionError(f"Office conversion produced an invalid PDF at {output}")
workbook = root / "input.xlsx"
with zipfile.ZipFile(workbook, "w", compression=zipfile.ZIP_DEFLATED) as archive:
archive.writestr("[Content_Types].xml", '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>')
archive.writestr("_rels/.rels", '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>')
archive.writestr("xl/workbook.xml", '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="Sheet1" sheetId="1" r:id="rId1"/></sheets></workbook>')
archive.writestr("xl/_rels/workbook.xml.rels", '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>')
archive.writestr("xl/worksheets/sheet1.xml", '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData><row r="1"><c r="A1"><f>1+2</f><v>0</v></c></row></sheetData></worksheet>')
subprocess.run([sys.executable, "-c", """
import sys, zipfile
from pathlib import Path
from xml.etree import ElementTree
from deepseek_harness.office import convert, render_images, recalculate
root = Path(sys.argv[1])
assert convert(root / 'document.docx', root / 'python.pdf').backend == sys.argv[2]
rendered = render_images(root / 'python.pdf', root / 'images', pages=[1])
assert rendered.images and rendered.images[0].path.read_bytes().startswith(b'\\x89PNG')
recalculate(root / 'input.xlsx', root / 'calculated.xlsx')
with zipfile.ZipFile(root / 'calculated.xlsx') as archive:
sheet = ElementTree.fromstring(archive.read('xl/worksheets/sheet1.xml'))
assert sheet.find('.//{*}c[@r="A1"]/{*}v').text == '3'
""", str(root), expected_backend], env={name: value for name, value in cache_environment.items() if name != "DSH_OFFICE_SIDECAR"}, check=True, timeout=180)
assert not (office.parent / "primary").exists(), "Python Office API unexpectedly downloaded authoring resources"
print(f"smoke-python-runtime: relocated Office {result['backend']} DOCX produced {len(pdf)} PDF bytes")
@@ -1336,6 +1368,8 @@ def smoke_sdk_dynamic_tools(base_url: str, executable: Path, update_snapshots: b
"DSH_PERMISSION_MODE": "danger-full-access", "DSH_TELEMETRY_DISABLED": "1",
"HOME": str(user_home), "USERPROFILE": str(user_home),
"DSH_AGENTS_HOME": str(user_home / ".agents"),
# Pin the tool baseline independently of optional resources in the host cache.
"DSH_PRIMARY_RUNTIME": "",
},
api_key="sk-keyless-smoke", base_url=base_url, request_timeout_seconds=60,
) as harness:
@@ -2307,11 +2341,17 @@ def build_dynamic_tools_snapshot_files(
assert changes == [[], [{"type": "tool_addition", "tool": {"type": "tool_reference", "name": "snapshot_ping"}}], []], changes
assert requests[1]["messages"][:len(requests[0]["messages"])] == requests[0]["messages"], "tool addition changed earlier messages"
replacements = [(str(cwd), "{{cwd}}"), (result.session_id, "{{session}}")]
header_references = {header["seq"]: f"{{{{header:{index + 1}}}}}" for index, header in enumerate(headers)}
snapshot_events = [
{**event, "data": {**event["data"], "headerSeq": header_references[event["data"]["headerSeq"]]}}
if event["type"] == "developer/message" and "headerSeq" in event["data"] else event
for event in events if event["type"] in {"request/header", "request/context", "developer/message"}
]
evidence = {
"finalResponse": result.final_response,
"declarations": declarations,
"requestToolChanges": changes,
"events": [event for event in events if event["type"] in {"request/header", "request/context", "developer/message"}],
"events": snapshot_events,
"toolCalls": calls,
"pingResult": ping,
}
@@ -56,7 +56,6 @@
"job_list",
"job_output",
"list_agents",
"load_workspace_dependencies",
"read",
"read_image",
"send_message",
@@ -87,7 +86,7 @@
},
{
"type": "request/header",
"seq": 20,
"seq": 19,
"time": 0,
"data": {
"header": {
@@ -113,7 +112,6 @@
"job_list",
"job_output",
"list_agents",
"load_workspace_dependencies",
"read",
"read_image",
"send_message",
@@ -135,7 +133,7 @@
},
{
"type": "developer/message",
"seq": 21,
"seq": 20,
"time": 0,
"data": {
"turn": 1,
@@ -153,13 +151,13 @@
"role": "developer",
"id": "{{messageId}}"
},
"headerSeq": 20
"headerSeq": "{{header:2}}"
},
"surfaceOp": "append"
},
{
"type": "request/header",
"seq": 28,
"seq": 26,
"time": 0,
"data": {
"header": {
@@ -185,7 +183,6 @@
"job_list",
"job_output",
"list_agents",
"load_workspace_dependencies",
"read",
"read_image",
"send_message",
@@ -206,7 +203,7 @@
},
{
"type": "developer/message",
"seq": 29,
"seq": 27,
"time": 0,
"data": {
"turn": 1,