fix: rewrite tool-lab tests as pure functions (remove dead mount/FakeFs; fix import-type-as-value bug)
This commit is contained in:
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm install vitest
|
- run: pnpm install vitest
|
||||||
- run: pnpm -r build
|
- run: pnpm -r build
|
||||||
- run: pnpm -r test
|
- run: pnpm test
|
||||||
- run: pnpm -r pack
|
- run: pnpm -r pack
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
|
|||||||
@@ -10,7 +10,11 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm -r run build",
|
"build": "pnpm -r run build",
|
||||||
"test": "pnpm -r run test",
|
"test": "vitest run",
|
||||||
"pack": "pnpm -r pack"
|
"pack": "pnpm -r pack"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.20.0",
|
||||||
|
"vitest": "^4.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,8 +35,7 @@
|
|||||||
"node": ">=22.19"
|
"node": ">=22.19"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json"
|
||||||
"test": "vitest run"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@deepseek-ai/cordis": "^4.0.1",
|
"@deepseek-ai/cordis": "^4.0.1",
|
||||||
|
|||||||
@@ -1,79 +1,14 @@
|
|||||||
import { describe, expect, it, vi, afterEach } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { Context } from '@deepseek-ai/cordis'
|
|
||||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
|
||||||
import ToolRuntime from '@deepseek-ai/dsh-tools'
|
|
||||||
import { FileSystem } from '@deepseek-ai/dsh-fs'
|
|
||||||
import type { FsTarget, FsDirEntry, FsEditOutcome, FsEditRequest, FsInfo, FsPathInfo, FsWriteIntent, FsWriteOutcome, FsVersion, FsTargetKey } from '@deepseek-ai/dsh-fs'
|
|
||||||
import * as ToolLab from '@deepseek-ai/dsh-tool-lab'
|
|
||||||
import { runComfyImage, resolveImageArgs, buildWorkflow } from '@deepseek-ai/dsh-tool-lab/src/comfy.ts'
|
import { runComfyImage, resolveImageArgs, buildWorkflow } from '@deepseek-ai/dsh-tool-lab/src/comfy.ts'
|
||||||
import { runDoclingOcr } from '@deepseek-ai/dsh-tool-lab/src/docling.ts'
|
import { runDoclingOcr } from '@deepseek-ai/dsh-tool-lab/src/docling.ts'
|
||||||
import { runWhishTranscribe } from '@deepseek-ai/dsh-tool-lab/src/whish.ts'
|
import { runWhishTranscribe } from '@deepseek-ai/dsh-tool-lab/src/whish.ts'
|
||||||
import type { SandboxExecutionPolicy, SandboxMode } from '@deepseek-ai/dsh-sandbox'
|
|
||||||
|
|
||||||
const testToolSignal = new AbortController().signal
|
const pngBytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13])
|
||||||
|
|
||||||
/** In-memory fake ctx.fs backend for upload tools. */
|
|
||||||
class FakeFs extends FileSystem {
|
|
||||||
files = new Map<string, Uint8Array>()
|
|
||||||
override async resolve(path: string): Promise<FsTarget> {
|
|
||||||
return { targetKey: FsTargetKey(`key:${path}`), displayPath: `/abs/${path}` }
|
|
||||||
}
|
|
||||||
override processPath(target: FsTarget): string { return String(target.targetKey) }
|
|
||||||
override fileUrl(target: FsTarget): string { return `file://${target.targetKey}` }
|
|
||||||
override contains(parent: FsTarget, child: FsTarget): boolean {
|
|
||||||
return child.targetKey === parent.targetKey || String(child.targetKey).startsWith(`${parent.targetKey}/`)
|
|
||||||
}
|
|
||||||
override async stat(): Promise<FsInfo | undefined> { return undefined }
|
|
||||||
override async lstat(): Promise<FsPathInfo | undefined> { return undefined }
|
|
||||||
override async readText(target: FsTarget): Promise<string> {
|
|
||||||
return new TextDecoder().decode(this.files.get(target.targetKey) ?? new Uint8Array())
|
|
||||||
}
|
|
||||||
override async streamText(target: FsTarget): Promise<AsyncIterable<string>> {
|
|
||||||
const text = await this.readText(target)
|
|
||||||
return (async function* () { yield text })()
|
|
||||||
}
|
|
||||||
override async readBytes(target: FsTarget, _signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array> {
|
|
||||||
const bytes = this.files.get(target.targetKey) ?? new Uint8Array()
|
|
||||||
if (bytes.length > maxBytes) throw new Error('FS_TOO_LARGE')
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
override async listDir(): Promise<FsDirEntry[]> { return [] }
|
|
||||||
override async writeText(target: FsTarget, content: string): Promise<FsWriteOutcome> {
|
|
||||||
const before = this.files.get(target.targetKey) ?? null
|
|
||||||
this.files.set(target.targetKey, new TextEncoder().encode(content))
|
|
||||||
return { operation: before !== null ? 'update' : 'create', version: FsVersion('v2'), before, after: content }
|
|
||||||
}
|
|
||||||
override async editText(target: FsTarget, edit: FsEditRequest): Promise<FsEditOutcome> {
|
|
||||||
const content = new TextDecoder().decode(this.files.get(target.targetKey) ?? new Uint8Array())
|
|
||||||
const after = content.split(edit.oldString).join(edit.newString)
|
|
||||||
this.files.set(target.targetKey, new TextEncoder().encode(after))
|
|
||||||
return { version: FsVersion('v3'), before: content, after }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mount(opts: { files?: Record<string, Uint8Array> } = {}) {
|
|
||||||
const ctx = new Context()
|
|
||||||
await ctx.plugin(ToolRuntime)
|
|
||||||
const fs = new FakeFs()
|
|
||||||
if (opts.files) for (const [k, v] of Object.entries(opts.files)) fs.files.set(`key:${k}`, v)
|
|
||||||
await ctx.plugin(fs, {})
|
|
||||||
await ctx.plugin(ToolLab, {})
|
|
||||||
let counter = 0
|
|
||||||
const call = (name: string, args: unknown) => ctx.tools.execute({
|
|
||||||
signal: testToolSignal,
|
|
||||||
callId: CallId(`call-${++counter}`),
|
|
||||||
name,
|
|
||||||
arguments: args,
|
|
||||||
})
|
|
||||||
return { ctx, fs, call }
|
|
||||||
}
|
|
||||||
|
|
||||||
function jsonResponse(body: unknown, { status = 200, headers }: { status?: number; headers?: Record<string, string> } = {}): Response {
|
function jsonResponse(body: unknown, { status = 200, headers }: { status?: number; headers?: Record<string, string> } = {}): Response {
|
||||||
return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', ...headers } })
|
return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', ...headers } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const pngBytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13])
|
|
||||||
|
|
||||||
afterEach(() => { vi.unstubAllGlobals() })
|
afterEach(() => { vi.unstubAllGlobals() })
|
||||||
|
|
||||||
describe('lab_generate_image (ComfyUI)', () => {
|
describe('lab_generate_image (ComfyUI)', () => {
|
||||||
@@ -86,8 +21,6 @@ describe('lab_generate_image (ComfyUI)', () => {
|
|||||||
if (url.includes('/history/p-1')) return jsonResponse({ 'p-1': { outputs: { '9': { images: [{ filename: 'dsh_00001_.png' }] } } } })
|
if (url.includes('/history/p-1')) return jsonResponse({ 'p-1': { outputs: { '9': { images: [{ filename: 'dsh_00001_.png' }] } } } })
|
||||||
throw new Error(`unexpected fetch ${url}`)
|
throw new Error(`unexpected fetch ${url}`)
|
||||||
}))
|
}))
|
||||||
const { fs } = await mountTarget({ files: { 'x.png': pngBytes } })
|
|
||||||
void fs
|
|
||||||
|
|
||||||
const out = await runComfyImage('http://192.168.31.240:8188', resolveImageArgs({ prompt: 'a cat' }), undefined, 30_000)
|
const out = await runComfyImage('http://192.168.31.240:8188', resolveImageArgs({ prompt: 'a cat' }), undefined, 30_000)
|
||||||
expect(out).toBe('http://192.168.31.240:8188/view?filename=dsh_00001_.png&subfolder=&type=output')
|
expect(out).toBe('http://192.168.31.240:8188/view?filename=dsh_00001_.png&subfolder=&type=output')
|
||||||
@@ -155,8 +88,3 @@ describe('lab_transcribe_audio (Whishper)', () => {
|
|||||||
.rejects.toThrow(/no id/)
|
.rejects.toThrow(/no id/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// keep imports referenced for the fake implementation; these types come from
|
|
||||||
// @deepseek-ai/dsh-fs and are used by FakeFs above.
|
|
||||||
type _ = SandboxExecutionPolicy | SandboxMode
|
|
||||||
export { FakeFs }
|
|
||||||
@@ -35,8 +35,7 @@
|
|||||||
"node": ">=22.19"
|
"node": ">=22.19"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json"
|
||||||
"test": "vitest run"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@deepseek-ai/cordis": "^4.0.1",
|
"@deepseek-ai/cordis": "^4.0.1",
|
||||||
|
|||||||
21
pnpm-lock.yaml
generated
21
pnpm-lock.yaml
generated
@@ -6,19 +6,14 @@ settings:
|
|||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.: {}
|
.:
|
||||||
|
devDependencies:
|
||||||
.test-dsh/profiles/web:
|
'@types/node':
|
||||||
dependencies:
|
specifier: ^22.20.0
|
||||||
'@deepseek-ai/dsh-telegram-remote':
|
version: 22.20.1
|
||||||
specifier: workspace:*
|
vitest:
|
||||||
version: link:../../../packages/telegram-remote
|
specifier: ^4.1.8
|
||||||
'@deepseek-ai/dsh-tool-lab':
|
version: 4.1.11(@types/node@22.20.1)(vite@8.2.2(@types/node@22.20.1))
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../../../packages/tool-lab
|
|
||||||
'@deepseek-ai/dsh-web-search-searxng':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../../../packages/web-search-searxng
|
|
||||||
|
|
||||||
packages/telegram-remote: {}
|
packages/telegram-remote: {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user