mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-02 21:15:14 +00:00
* feat(node-sdk): add agent-core-v2 backed SDKRpcClientV2 harness - add SDKRpcClientV2 wiring the v2 engine (DI x Scope) in-process via the klient memory transport, with getExperimentalFeatures migrated to klient.global.flags.list() and unmigrated methods failing fast - export createKimiHarnessV2 / SDKRpcClientV2 from the SDK index - wire the experimental v2 gate into the CLI interactive shell (run-shell) - extend build-dts to bundle agent-core-v2 and klient declarations * feat(node-sdk): migrate listWorkspaceSkills to agent-core-v2 - add engineAccessor escape hatch exposing the in-process engine's app-scope service accessor for SDK methods the klient facade does not cover yet - implement listWorkspaceSkills via ISkillDiscovery plus the v2 user/project root helpers and BUILTIN_SKILLS (plugin skills and skillDirs still gaps) - add a v1-v2 parity test pinning identical return values per migrated method, with understood gaps listed explicitly in KNOWN_DIFFS * feat(node-sdk): migrate the SDK method surface to agent-core-v2 - implement the remaining SDKRpcClientBase methods on SDKRpcClientV2, routed through the klient facade where covered, the engineAccessor escape hatch where the engine has a service, or SDK-side rebuilds on v2 primitives where only primitives exist (config shape mapping, global mcp.json store, MCP OAuth flows, importContext, session warnings, print background policy) - translate the v2 event stream into the v1 Event union and bridge approval/question/user_tool interactions per live session - rebuild resume replay by folding the v2 wire.jsonl through the v1 agent restore pipeline, so resumed sessions render history again - keep deleteSession as not_implemented; the v2 engine has no delete capability - extend the v1-v2 parity suite to every migrated method, pinning understood engine differences in KNOWN_DIFFS - add the dev:cli:v2 root script to launch the TUI on the v2 engine * fix(node-sdk): await the v2 undo and compaction-cancel agent calls * test(cli): spread the real oauth module in the telemetry test mock * feat(node-sdk): forward v2 engine telemetry to the host telemetry client * feat(cli): gate the v2 TUI route behind a dedicated KIMI_CODE_TUI_V2 switch * fix(node-sdk): honor skillDirs on the agent-core-v2 SDK route The v2 SDK client accepted KimiHarnessOptions.skillDirs (the CLI's --skills-dir) but never seeded it into the engine, so explicit skill dirs were silently dropped on the v2 TUI route and the Skill tool could not find skills from them. Seed skillCatalogRuntimeOptions at bootstrap and let listWorkspaceSkills resolve the explicit dirs as the user source, matching the engine's session skill catalog. * refactor(cli): gate the v2 TUI route behind the master experimental flag again Drop the dedicated KIMI_CODE_TUI_V2 switch: the TUI v2 harness route is gated by KIMI_CODE_EXPERIMENTAL_FLAG, the same master switch as the kimi -p v2 route. The gate tests are kept with updated assertions, and dev:cli:v2 sets the master flag again.
219 lines
6.8 KiB
JavaScript
219 lines
6.8 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
import { existsSync } from 'node:fs';
|
|
import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
import { createRequire } from 'node:module';
|
|
import path from 'node:path';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const packageRoot = path.resolve(import.meta.dirname, '..');
|
|
const tempDir = path.join(packageRoot, '.tmp-api-extractor');
|
|
const dtsRoot = path.join(tempDir, 'dts');
|
|
const providerClientShimPath = path.join(dtsRoot, 'provider-clients.d.ts');
|
|
const tscBinPath = packageBinPath('typescript', 'bin/tsc');
|
|
const apiExtractorBinPath = packageBinPath('@microsoft/api-extractor', 'bin/api-extractor');
|
|
|
|
const packageDirs = new Set(['agent-core', 'agent-core-v2', 'kaos', 'klient', 'kosong', 'node-sdk', 'oauth']);
|
|
const workspacePackages = new Map([
|
|
['@moonshot-ai/agent-core-v2', 'agent-core-v2'],
|
|
['@moonshot-ai/agent-core', 'agent-core'],
|
|
['@moonshot-ai/kaos', 'kaos'],
|
|
['@moonshot-ai/kimi-code-oauth', 'oauth'],
|
|
['@moonshot-ai/klient', 'klient'],
|
|
['@moonshot-ai/kosong', 'kosong'],
|
|
]);
|
|
|
|
try {
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
await run('tsc', tscBinPath, ['-p', 'tsconfig.dts.json']);
|
|
await writeProviderClientShim();
|
|
await rewriteWorkspaceSpecifiers();
|
|
await run('api-extractor', apiExtractorBinPath, ['run', '--local']);
|
|
} finally {
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
|
|
function packageBinPath(packageName, binPath) {
|
|
return path.join(path.dirname(require.resolve(`${packageName}/package.json`)), binPath);
|
|
}
|
|
|
|
function run(command, binPath, args) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(process.execPath, [binPath, ...args], {
|
|
cwd: packageRoot,
|
|
stdio: 'inherit',
|
|
});
|
|
|
|
child.once('error', reject);
|
|
child.once('exit', (code, signal) => {
|
|
if (code === 0) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
const detail = signal === null ? `exit code ${String(code)}` : `signal ${signal}`;
|
|
reject(new Error(`${command} failed with ${detail}`));
|
|
});
|
|
});
|
|
}
|
|
|
|
async function writeProviderClientShim() {
|
|
await mkdir(dtsRoot, { recursive: true });
|
|
await writeFile(
|
|
providerClientShimPath,
|
|
[
|
|
'export interface Anthropic {}',
|
|
'export interface GoogleGenAI {}',
|
|
'export interface OpenAI {}',
|
|
'export namespace OpenAI {',
|
|
' export namespace Chat {',
|
|
' export type ChatCompletion = unknown;',
|
|
' export type ChatCompletionChunk = unknown;',
|
|
' export type ChatCompletionCreateParamsNonStreaming = unknown;',
|
|
' }',
|
|
'}',
|
|
'',
|
|
].join('\n'),
|
|
);
|
|
}
|
|
|
|
async function rewriteWorkspaceSpecifiers() {
|
|
const files = await findDtsFiles(dtsRoot);
|
|
const emittedFiles = new Set(files.map((file) => path.resolve(file)));
|
|
|
|
await Promise.all(
|
|
files.map(async (file) => {
|
|
const packageDir = packageDirForFile(file);
|
|
if (packageDir === undefined) {
|
|
return;
|
|
}
|
|
|
|
const text = await readFile(file, 'utf8');
|
|
const providerClientSpecifier = relativeSpecifier(file, providerClientShimPath);
|
|
const providerClientText = text
|
|
.replaceAll(
|
|
"import Anthropic from '@anthropic-ai/sdk';",
|
|
`import { Anthropic } from '${providerClientSpecifier}';`,
|
|
)
|
|
.replaceAll(
|
|
"import OpenAI from 'openai';",
|
|
`import { OpenAI } from '${providerClientSpecifier}';`,
|
|
)
|
|
.replaceAll(
|
|
"import type OpenAI from 'openai';",
|
|
`import type { OpenAI } from '${providerClientSpecifier}';`,
|
|
)
|
|
.replaceAll(
|
|
"import { GoogleGenAI as GenAIClient } from '@google/genai';",
|
|
`import { GoogleGenAI as GenAIClient } from '${providerClientSpecifier}';`,
|
|
);
|
|
const updated = providerClientText.replaceAll(
|
|
/(["'])(#\/[^"']+|@moonshot-ai\/(?:agent-core-v2|agent-core|kaos|kimi-code-oauth|klient|kosong)(?:\/[^"']+)?)\1/g,
|
|
(_match, quote, specifier) => {
|
|
const resolved = resolveSpecifier({
|
|
currentFile: file,
|
|
emittedFiles,
|
|
packageDir,
|
|
specifier,
|
|
});
|
|
|
|
return `${quote}${relativeSpecifier(file, resolved)}${quote}`;
|
|
},
|
|
);
|
|
|
|
if (updated !== text) {
|
|
await writeFile(file, updated);
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function findDtsFiles(dir) {
|
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
const files = await Promise.all(
|
|
entries.map(async (entry) => {
|
|
const entryPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
return findDtsFiles(entryPath);
|
|
}
|
|
return entry.name.endsWith('.d.ts') ? [entryPath] : [];
|
|
}),
|
|
);
|
|
|
|
return files.flat();
|
|
}
|
|
|
|
function packageDirForFile(file) {
|
|
const parts = path.relative(dtsRoot, file).split(path.sep);
|
|
const [packageDir, firstDir] = parts;
|
|
|
|
if (packageDir === undefined || firstDir !== 'src' || !packageDirs.has(packageDir)) {
|
|
return undefined;
|
|
}
|
|
|
|
return packageDir;
|
|
}
|
|
|
|
function resolveSpecifier({ currentFile, emittedFiles, packageDir, specifier }) {
|
|
if (specifier.startsWith('#/')) {
|
|
return resolvePackageSubpath({
|
|
emittedFiles,
|
|
packageDir,
|
|
subpath: specifier.slice(2),
|
|
originalSpecifier: specifier,
|
|
});
|
|
}
|
|
|
|
const workspacePackage = workspacePackageForSpecifier(specifier);
|
|
if (workspacePackage === undefined) {
|
|
throw new Error(`Unexpected workspace specifier in ${currentFile}: ${specifier}`);
|
|
}
|
|
|
|
return resolvePackageSubpath({
|
|
emittedFiles,
|
|
packageDir: workspacePackage.packageDir,
|
|
subpath: workspacePackage.subpath,
|
|
originalSpecifier: specifier,
|
|
});
|
|
}
|
|
|
|
function workspacePackageForSpecifier(specifier) {
|
|
for (const [packageName, packageDir] of workspacePackages) {
|
|
if (specifier === packageName) {
|
|
return { packageDir, subpath: 'index' };
|
|
}
|
|
|
|
const prefix = `${packageName}/`;
|
|
if (specifier.startsWith(prefix)) {
|
|
return { packageDir, subpath: specifier.slice(prefix.length) };
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function resolvePackageSubpath({ emittedFiles, packageDir, subpath, originalSpecifier }) {
|
|
const srcRoot = path.join(dtsRoot, packageDir, 'src');
|
|
const directFile = path.resolve(srcRoot, `${subpath}.d.ts`);
|
|
if (emittedFiles.has(directFile) || existsSync(directFile)) {
|
|
return directFile;
|
|
}
|
|
|
|
const indexFile = path.resolve(srcRoot, subpath, 'index.d.ts');
|
|
if (emittedFiles.has(indexFile) || existsSync(indexFile)) {
|
|
return indexFile;
|
|
}
|
|
|
|
throw new Error(`Unable to resolve ${originalSpecifier} in emitted declarations`);
|
|
}
|
|
|
|
function relativeSpecifier(fromFile, toFile) {
|
|
const fromDir = path.dirname(fromFile);
|
|
const withoutExtension = toFile.slice(0, -'.d.ts'.length);
|
|
let relative = path.relative(fromDir, withoutExtension).replaceAll(path.sep, '/');
|
|
|
|
if (!relative.startsWith('.')) {
|
|
relative = `./${relative}`;
|
|
}
|
|
|
|
return relative;
|
|
}
|