kimi-code/build/hash-imports-loader.mjs
haozhe.yang 4e7209394c feat(server-v2): add minimal server wired to agent-core-v2 DI
Add `packages/server-v2`, a minimal Fastify server bootstrapped through the
`agent-core-v2` DI engine (`bootstrap()` -> Core `Scope`), speaking the same
`/api/v1` interface as the v1 server. Route handlers resolve Core services
via `core.accessor.get(IXxx)`.

- Serve healthz, meta, auth, oauth (login poll/start/cancel/logout) and
  shutdown with the standard `{code, msg, data, request_id}` envelope.
- Re-point root `pnpm dev:server` to server-v2 and add a `src/main.ts`
  foreground entry.
- Add `build/hash-imports-loader.mjs` so tsx resolves `agent-core-v2`'s `#/`
  array-fallback subpath imports.
- `agent-core-v2` Phase 0 fixes so it compiles and boots: add the `event`
  domain (`IEventService`), fix `gateway` `event.subscribe` -> `event.on`, fix
  the `wireRecord` fallback `BlobStoreService` construction, export the
  `provider` barrel, and fix a `tooldedup` test import casing.
2026-06-28 17:26:39 +08:00

79 lines
2.7 KiB
JavaScript

import { existsSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
/**
* Node ESM `resolve` hook: correctly resolve `#/` subpath imports against the
* importing package's own `package.json` `imports` field — including array
* fallbacks such as `"#/*": ["./src/*.ts", "./src/<name>/index.ts"]`.
*
* `tsx` (its resolver) only honors the first array element and therefore breaks
* on directory-style `#/` imports (for example `#/_base/errors` →
* `_base/errors/index.ts`). This loader short-circuits `#/` resolution before
* tsx sees it, mirroring the Vite `hashImportsPlugin` used by the v2 tests.
*/
const pkgCache = new Map();
function findPackageJson(fromFileUrl) {
let dir = dirname(fileURLToPath(fromFileUrl));
for (;;) {
const candidate = join(dir, 'package.json');
if (existsSync(candidate)) return candidate;
const parent = dirname(dir);
if (parent === dir) return undefined;
dir = parent;
}
}
function readPackage(pkgPath) {
let pkg = pkgCache.get(pkgPath);
if (pkg === undefined) {
pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
pkgCache.set(pkgPath, pkg);
}
return pkg;
}
function resolveTarget(pkgDir, target, rest) {
const resolved = rest === undefined ? target : target.replace('*', rest);
const full = join(pkgDir, resolved);
return existsSync(full) ? pathToFileURL(full).href : undefined;
}
function resolveHashImport(specifier, parentURL) {
if (parentURL === undefined) return undefined;
const pkgPath = findPackageJson(parentURL);
if (pkgPath === undefined) return undefined;
const imports = readPackage(pkgPath).imports;
if (imports === undefined) return undefined;
const pkgDir = dirname(pkgPath);
for (const [key, raw] of Object.entries(imports)) {
if (!key.startsWith('#')) continue;
const targets = Array.isArray(raw) ? raw : [raw];
if (key.endsWith('*')) {
const prefix = key.slice(0, -1);
if (!specifier.startsWith(prefix)) continue;
const rest = specifier.slice(prefix.length);
for (const target of targets) {
const url = resolveTarget(pkgDir, target, rest);
if (url !== undefined) return url;
}
} else if (specifier === key) {
for (const target of targets) {
const url = resolveTarget(pkgDir, target, undefined);
if (url !== undefined) return url;
}
}
}
return undefined;
}
export async function resolve(specifier, context, nextResolve) {
if (specifier.startsWith('#/')) {
const url = resolveHashImport(specifier, context.parentURL);
if (url !== undefined) return { url, shortCircuit: true };
}
return nextResolve(specifier, context);
}