@databricks/appkit 0.74.0 → 0.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -0
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +4 -4
- package/dist/beta.js +3 -3
- package/dist/cli/commands/agent/eval.js +84 -9
- package/dist/cli/commands/agent/eval.js.map +1 -1
- package/dist/cli/commands/registry/add.js +78 -7
- package/dist/cli/commands/registry/add.js.map +1 -1
- package/dist/cli/commands/registry/config-writer.js +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/evals/discover.d.ts +8 -1
- package/dist/evals/discover.d.ts.map +1 -1
- package/dist/evals/discover.js +33 -17
- package/dist/evals/discover.js.map +1 -1
- package/dist/evals/index.d.ts +3 -3
- package/dist/evals/index.js +2 -2
- package/dist/evals/run-evals.d.ts +9 -2
- package/dist/evals/run-evals.d.ts.map +1 -1
- package/dist/evals/run-evals.js +13 -2
- package/dist/evals/run-evals.js.map +1 -1
- package/dist/evals/types.d.ts +40 -2
- package/dist/evals/types.d.ts.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +87 -87
- package/docs/api/appkit/Function.findRootEvalConfig.md +18 -0
- package/docs/api/appkit/Function.loadRootEvalConfig.md +18 -0
- package/docs/api/appkit/Interface.EvalWebServer.md +47 -0
- package/docs/api/appkit.md +3 -0
- package/docs/plugins/agents.md +255 -4
- package/llms.txt +3 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.js","names":[],"sources":["../../../../src/cli/commands/registry/add.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\n\nimport {\n fetchRegistryItem,\n fetchVerifiedNames,\n isValidItemName,\n type RegistryItem,\n type RegistryItemFile,\n stripNamespace,\n} from \"./client\";\nimport {\n buildConfigPlan,\n collectBindingValueNeeds,\n planHasContent,\n} from \"./config-plan\";\nimport {\n reportConfigWrite,\n validateBundle,\n writeConfig,\n} from \"./config-writer\";\nimport {\n JS_IDENTIFIER,\n REGISTRY_REPO,\n type RegistryToken,\n resolveToken,\n} from \"./constants\";\nimport { parseEnv } from \"./env-reconcile\";\nimport {\n extractRequirements,\n type ResourceRequirementRow,\n renderRequirements,\n} from \"./requirements\";\n\n/** Subdirectories that commonly hold the frontend / server in an AppKit app. */\nconst FRONTEND_SUBDIRS = [\"client\", \"frontend\", \"web\", \"app\"];\nconst SERVER_SUBDIRS = [\"server\", \"api\", \"backend\"];\n\nfunction isDir(p: string): boolean {\n return fs.existsSync(p) && fs.statSync(p).isDirectory();\n}\n\n/** A registry item is a server plugin if it ships a manifest.json. */\nfunction isPluginItem(item: RegistryItem): boolean {\n return (item.files ?? []).some(\n (f) => path.basename(f.target ?? f.path) === \"manifest.json\",\n );\n}\n\n/**\n * Locates the frontend root for UI components. AppKit apps put the client in a\n * client/ subdir (with its own components.json + src/); the CLI is typically\n * run from the repo root. Prefer the dir with components.json, then a src/.\n */\nfunction findFrontendRoot(cwd: string): string {\n if (fs.existsSync(path.join(cwd, \"components.json\"))) return cwd;\n for (const sub of FRONTEND_SUBDIRS) {\n if (fs.existsSync(path.join(cwd, sub, \"components.json\"))) {\n return path.join(cwd, sub);\n }\n }\n if (isDir(path.join(cwd, \"src\"))) return cwd;\n for (const sub of FRONTEND_SUBDIRS) {\n if (isDir(path.join(cwd, sub, \"src\"))) return path.join(cwd, sub);\n }\n return cwd;\n}\n\n/** Locates the server root for plugins (the server/ subdir, else cwd). */\nfunction findServerRoot(cwd: string): string {\n for (const sub of SERVER_SUBDIRS) {\n if (isDir(path.join(cwd, sub))) return path.join(cwd, sub);\n }\n return cwd;\n}\n\n/** Nearest dir with a package.json, walking up from start (for dep install). */\nfunction findNearestPackageJson(start: string): string {\n let dir = start;\n for (;;) {\n if (fs.existsSync(path.join(dir, \"package.json\"))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return start;\n dir = parent;\n }\n}\n\n/**\n * The Databricks profile the app is configured with, read from\n * `DATABRICKS_CONFIG_PROFILE` in `cwd/.env` via the same dotenv parser the app\n * loads its env with. The CLI's top-level `dotenv/config` only loads the launch\n * dir's `.env` into `process.env`; when `--cwd` points at a different app dir,\n * that file isn't loaded, so the workspace picker and bundle validate would\n * miss the app's profile — this reads it directly. Undefined if the file or key\n * is absent, so the SDK's own default resolution still applies.\n */\nexport function profileFromEnv(cwd: string): string | undefined {\n try {\n const content = fs.readFileSync(path.join(cwd, \".env\"), \"utf-8\");\n return parseEnv(content).DATABRICKS_CONFIG_PROFILE || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolves a registry item's `target` under `base`, enforcing that the result\n * stays inside `base`. Registry items are untrusted remote data; a `target`\n * like `../../../.zshrc` or an absolute path could otherwise write files\n * anywhere on disk (arbitrary-write → RCE). Throws on any escape.\n */\nexport function resolveWithinBase(base: string, target: string): string {\n if (path.isAbsolute(target)) {\n throw new Error(`Refusing absolute file target from registry: ${target}`);\n }\n const baseResolved = path.resolve(base);\n const resolved = path.resolve(baseResolved, target);\n if (\n resolved !== baseResolved &&\n !resolved.startsWith(baseResolved + path.sep)\n ) {\n throw new Error(\n `Refusing file target that escapes the destination directory: ${target}`,\n );\n }\n return resolved;\n}\n\n/** UI file destination (relative to the frontend root): placed in src/ if present. */\nfunction uiTargetPath(base: string, file: RegistryItemFile): string {\n let target = file.target ?? path.join(\"components\", path.basename(file.path));\n if (!target.startsWith(\"src/\") && isDir(path.join(base, \"src\"))) {\n target = path.join(\"src\", target);\n }\n return target;\n}\n\n/** Best-effort: the `toPlugin` export name from the item's index.ts. Returns\n * null (caller falls back to printed instructions) unless the name is a plain\n * JS identifier — the item is untrusted remote content and the value is\n * interpolated into the user's server.ts. */\nexport function pluginExportName(item: RegistryItem): string | null {\n const index = (item.files ?? []).find(\n (f) => path.basename(f.target ?? f.path) === \"index.ts\",\n );\n const match = index?.content.match(/export\\s*\\{([^}]*)\\}/);\n if (!match) return null;\n const names = match[1].split(\",\").map((s) => s.trim());\n // Prefer the camelCase toPlugin instance over the PascalCase class.\n const chosen = names.find((n) => /^[a-z]/.test(n)) ?? names[0];\n return chosen && JS_IDENTIFIER.test(chosen) ? chosen : null;\n}\n\nfunction detectPackageManager(cwd: string): \"pnpm\" | \"yarn\" | \"bun\" | \"npm\" {\n if (fs.existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (fs.existsSync(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (fs.existsSync(path.join(cwd, \"bun.lockb\"))) return \"bun\";\n return \"npm\";\n}\n\n/**\n * A safe npm dependency spec: `[@scope/]name` with an optional `@version`\n * range. Registry `dependencies` are untrusted remote data passed to the\n * package manager, so we reject anything that isn't a plain name+range —\n * blocks tarball/git URL specs (install-script RCE) and `-`-prefixed entries\n * that the PM would parse as flags (argument injection).\n */\nconst SAFE_DEP_SPEC =\n /^(@[a-z0-9][\\w.-]*\\/)?[a-z0-9][\\w.-]*(@[\\w.\\-+~^><=|* ]+)?$/i;\n\n/** Splits deps into safe (installable) and rejected (surfaced to the user). */\nexport function partitionDeps(deps: string[]): {\n safe: string[];\n rejected: string[];\n} {\n const safe: string[] = [];\n const rejected: string[] = [];\n for (const dep of deps) {\n if (dep.startsWith(\"-\") || !SAFE_DEP_SPEC.test(dep)) rejected.push(dep);\n else safe.push(dep);\n }\n return { safe, rejected };\n}\n\nfunction installDependencies(deps: string[], cwd: string): void {\n if (deps.length === 0) return;\n\n const { safe, rejected } = partitionDeps(deps);\n if (rejected.length > 0) {\n console.warn(\n pc.yellow(\n `Skipping suspicious dependenc${rejected.length === 1 ? \"y\" : \"ies\"} from the registry (not a plain name@version): ${rejected.join(\", \")}. Install manually if you trust them.`,\n ),\n );\n }\n if (safe.length === 0) return;\n\n if (!fs.existsSync(path.join(cwd, \"package.json\"))) {\n console.warn(\n pc.yellow(\n `No package.json found — install these manually: ${safe.join(\" \")}`,\n ),\n );\n return;\n }\n const pm = detectPackageManager(cwd);\n const subcommand = pm === \"npm\" ? \"install\" : \"add\";\n console.log(`\\nInstalling dependencies with ${pm}: ${safe.join(\" \")}`);\n // `--` stops the PM from parsing any dep as a flag (defense in depth).\n const result = spawnSync(pm, [subcommand, \"--\", ...safe], {\n stdio: \"inherit\",\n cwd,\n });\n if (result.status !== 0) {\n console.warn(\n pc.yellow(\n `Dependency install exited with code ${result.status ?? \"unknown\"} — install manually if needed: ${safe.join(\" \")}`,\n ),\n );\n }\n}\n\n/** Runs `appkit plugin sync --write` via this same CLI binary. */\nfunction runPluginSync(cwd: string): void {\n const result = spawnSync(\n process.execPath,\n [process.argv[1], \"plugin\", \"sync\", \"--write\"],\n { stdio: \"inherit\", cwd },\n );\n if (result.status !== 0) {\n console.warn(\n pc.yellow(\n \" Plugin sync did not complete cleanly — run `appkit plugin sync --write` manually.\",\n ),\n );\n }\n}\n\nfunction writeItemFile(\n base: string,\n target: string,\n content: string,\n force: boolean,\n cwd: string,\n): void {\n const dest = resolveWithinBase(base, target);\n const existed = fs.existsSync(dest);\n if (existed && !force) {\n console.error(\n pc.red(\n `Refusing to overwrite ${path.relative(cwd, dest)} — pass --force to replace it.`,\n ),\n );\n process.exit(1);\n }\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, content);\n const label = existed ? pc.yellow(\"Updated\") : pc.green(\"Created\");\n console.log(`${label} ${path.relative(cwd, dest)}`);\n}\n\ninterface PluginSummary {\n importPath: string;\n exportName: string | null;\n}\n\n/**\n * Fetches the requested items plus their transitive registryDependencies.\n * Dependencies are resolved breadth-first and de-duplicated by name, so a\n * plugin that depends on another registry item pulls the whole graph in one\n * `add`. Explicitly-requested items keep their request order and come first.\n */\nexport async function resolveItems(\n names: string[],\n token: RegistryToken | null,\n fetchItem: (\n name: string,\n token: RegistryToken | null,\n ) => Promise<RegistryItem> = fetchRegistryItem,\n): Promise<RegistryItem[]> {\n const seen = new Set<string>();\n const ordered: RegistryItem[] = [];\n // A name is both the fetch path and the on-disk `plugins/<name>` dir, so\n // reject non-slug refs (`/`, `..`, control chars) before they reach either.\n const enqueue = (ref: string): string => {\n const name = stripNamespace(ref);\n if (!isValidItemName(name)) {\n throw new Error(`Invalid registry item name: ${JSON.stringify(ref)}`);\n }\n return name;\n };\n // Breadth-first over the dependency graph, one level per iteration. Items in\n // a level are fetched concurrently (fetch latency is additive otherwise), but\n // levels stay ordered and dedup/cycle handling is unchanged: a name is marked\n // seen before its level is fetched, so it's never fetched or queued twice.\n let level = names.map(enqueue).filter((name) => {\n if (seen.has(name)) return false;\n seen.add(name);\n return true;\n });\n\n while (level.length > 0) {\n const items = await Promise.all(\n level.map(async (key) => {\n const item = await fetchItem(key, token);\n // Pin to the fetch key: the body's self-reported `name` is untrusted\n // and could claim a verified name to slip past the gate. The key is the\n // trustworthy identity the index keys `verified` on.\n item.name = key;\n return item;\n }),\n );\n ordered.push(...items);\n const next: string[] = [];\n for (const item of items) {\n for (const dep of item.registryDependencies ?? []) {\n const depName = enqueue(dep);\n if (seen.has(depName)) continue;\n seen.add(depName);\n next.push(depName);\n }\n }\n level = next;\n }\n\n return ordered;\n}\n\ninterface AddOptions {\n force?: boolean;\n cwd?: string;\n register?: boolean;\n /** false = don't reconcile resource env vars into .env. */\n resources?: boolean;\n /** true = never prompt; use --env flags or leave unset (agent/CI). */\n yes?: boolean;\n /** Pre-supplied env values from repeated --env KEY=VALUE flags. */\n env?: Record<string, string>;\n /** Databricks profile passed to `bundle validate` after writing config. */\n profile?: string;\n /** true = install items the registry index doesn't mark verified. */\n allowUnverified?: boolean;\n}\n\n/**\n * Splits requested names into verified and unverified against the index's\n * verified set. When `verified` is null the index couldn't be read — we can't\n * prove anything is verified, so every name is treated as unverified (the gate\n * then decides whether to warn-and-continue or block). Names are compared with\n * the namespace stripped, matching how items are fetched.\n */\nexport function partitionVerified(\n refs: string[],\n verified: Set<string> | null,\n): { verified: string[]; unverified: string[] } {\n const ok: string[] = [];\n const bad: string[] = [];\n for (const ref of refs) {\n const name = stripNamespace(ref);\n if (verified?.has(name)) ok.push(name);\n else bad.push(name);\n }\n return { verified: ok, unverified: bad };\n}\n\nasync function runAdd(refs: string[], opts: AddOptions): Promise<void> {\n const cwd = opts.cwd ? path.resolve(opts.cwd) : process.cwd();\n const token = resolveToken();\n if (token) {\n console.log(\n `Using ${token.envName} to fetch from ${REGISTRY_REPO} (private).`,\n );\n }\n\n // Resolve the full graph and fetch the verified index concurrently (two\n // independent round-trips). Item resolution is read-only — nothing is written\n // or installed until after the gate below.\n const verifiedP = opts.allowUnverified\n ? Promise.resolve(null)\n : fetchVerifiedNames(token);\n const items = await resolveItems(refs, token);\n\n // Integrity gate over the *entire resolved set* (not just requested names, so\n // an unverified transitive dep can't ride in on a verified item). Fails closed:\n // a null verified set (unreadable index) makes every item count as unverified.\n if (!opts.allowUnverified) {\n const verified = await verifiedP;\n const { unverified } = partitionVerified(\n items.map((i) => i.name),\n verified,\n );\n if (unverified.length > 0) {\n const reason =\n verified === null\n ? \"could not read the registry index to verify these items\"\n : `not marked verified in ${REGISTRY_REPO}`;\n console.error(\n pc.red(\n `Refusing to add unverified item(s) (${reason}): ${unverified.join(\", \")}.`,\n ),\n );\n console.error(\n pc.dim(\n \" Re-run with --allow-unverified if you trust the source; unverified items run code in your app.\",\n ),\n );\n process.exit(1);\n }\n }\n\n const hasUi = items.some((i) => !isPluginItem(i));\n const hasPlugin = items.some(isPluginItem);\n const frontendRoot = hasUi ? findFrontendRoot(cwd) : cwd;\n const serverRoot = hasPlugin ? findServerRoot(cwd) : cwd;\n if (hasUi && frontendRoot !== cwd) {\n console.log(pc.dim(`UI components → ${path.relative(cwd, frontendRoot)}/`));\n }\n if (hasPlugin && serverRoot !== cwd) {\n console.log(pc.dim(`Plugins → ${path.relative(cwd, serverRoot)}/`));\n }\n\n const deps = new Set<string>();\n let wroteUi = false;\n const pluginSummaries: PluginSummary[] = [];\n const allRequirements: ResourceRequirementRow[] = [];\n\n for (const item of items) {\n for (const dep of item.dependencies ?? []) deps.add(dep);\n\n if (isPluginItem(item)) {\n let pluginRel = path.join(\"plugins\", item.name);\n for (const file of item.files ?? []) {\n const target =\n file.target ??\n path.join(\"plugins\", item.name, path.basename(file.path));\n writeItemFile(\n serverRoot,\n target,\n file.content,\n Boolean(opts.force),\n cwd,\n );\n if (path.basename(target) === \"manifest.json\") {\n pluginRel = path.dirname(target);\n }\n }\n const requirements = extractRequirements(item);\n if (requirements.length > 0) {\n console.log(`\\n${renderRequirements(item, requirements)}`);\n allRequirements.push(...requirements);\n }\n pluginSummaries.push({\n importPath: `./${pluginRel}`,\n exportName: pluginExportName(item),\n });\n } else {\n for (const file of item.files ?? []) {\n writeItemFile(\n frontendRoot,\n uiTargetPath(frontendRoot, file),\n file.content,\n Boolean(opts.force),\n cwd,\n );\n wroteUi = true;\n }\n }\n }\n\n installDependencies([...deps], findNearestPackageJson(cwd));\n\n if (hasPlugin) {\n console.log(pc.dim(\"\\nRegistering plugins (appkit plugin sync)...\"));\n runPluginSync(cwd);\n }\n\n if (wroteUi) {\n console.log(\n pc.dim(\n '\\nReminder: import \"@databricks/appkit-ui/styles.css\" once at your app root so components are themed.',\n ),\n );\n }\n // Lazy import: server-register pulls in @ast-grep/napi (a native addon), and\n // this CLI is imported eagerly by index.ts, so a static import would make\n // every unrelated command pay that cost.\n const registerPluginInServer =\n opts.register !== false && pluginSummaries.some((s) => s.exportName)\n ? (await import(\"./server-register.js\")).registerPluginInServer\n : null;\n for (const s of pluginSummaries) {\n // Try to wire the plugin into the server's createApp call automatically;\n // fall back to printing the snippet when the shape isn't the standard one.\n let wired = false;\n // Why auto-registration was skipped, so the fallback can say so (an empty\n // reason means the user opted out via --no-register — no nag then).\n let skipReason: string | undefined;\n if (opts.register === false) {\n // opted out — print the snippet without a \"couldn't\" message\n } else if (!s.exportName) {\n skipReason =\n \"couldn't read a plugin export name from the item's index.ts\";\n } else if (registerPluginInServer) {\n const result = registerPluginInServer(\n serverRoot,\n s.importPath,\n s.exportName,\n );\n // result.file is relative to serverRoot; show it from cwd for the user.\n const shown =\n result.file && path.relative(cwd, path.join(serverRoot, result.file));\n if (result.status === \"wired\") {\n console.log(`\\n${pc.green(\"Registered\")} ${s.exportName} in ${shown}`);\n wired = true;\n } else if (result.status === \"already\") {\n console.log(\n pc.dim(`\\n${s.exportName} is already registered in ${shown}`),\n );\n wired = true;\n } else {\n skipReason = result.reason;\n }\n }\n if (!wired) {\n const imp = s.exportName ?? \"<plugin>\";\n if (skipReason) {\n console.log(\n pc.yellow(`\\nCouldn't auto-register ${imp} — ${skipReason}.`),\n );\n }\n console.log(\n `${pc.bold(\"Add this to your server's createApp call:\")}\\n` +\n pc.dim(\n ` import { ${imp} } from \"${s.importPath}\";\\n` +\n ` const app = await createApp({ plugins: [${imp}(), /* ... */] });`,\n ),\n );\n }\n }\n\n if (opts.resources !== false && allRequirements.length > 0) {\n // Lazy import (same reason as server-register above): env-writer pulls in\n // the workspace picker and, through it, the Databricks SDK.\n const { collectBindingValues, reportEnvResolutions, syncEnv } =\n await import(\"./env-writer.js\");\n // Without --profile, fall back to the app's configured profile so the\n // picker and bundle validate reach the workspace the app runs against.\n const profile = opts.profile ?? profileFromEnv(cwd);\n console.log(pc.dim(\"\\nReconciling resource env vars into .env...\"));\n const resolutions = await syncEnv(allRequirements, {\n cwd,\n nonInteractive: Boolean(opts.yes),\n values: opts.env,\n profile,\n });\n reportEnvResolutions(resolutions);\n\n // Deploy config (app.yaml + databricks.yml). Values come from what the\n // user supplied for env fields (flags or prompts); other fields fall back\n // to their manifest defaults inside buildConfigPlan.\n const values: Record<string, string> = { ...(opts.env ?? {}) };\n for (const r of resolutions) {\n if (r.value !== undefined) values[r.env] = r.value;\n }\n // Binding fields with no env name (e.g. postgres project/branch/database)\n // never flow through .env, so collect them separately — else their\n // databricks.yml bundle variables stay unassigned and bundle validate fails.\n const bindingNeeds = collectBindingValueNeeds(allRequirements);\n if (bindingNeeds.length > 0) {\n const bindingValues = await collectBindingValues(bindingNeeds, {\n cwd,\n nonInteractive: Boolean(opts.yes),\n values: opts.env,\n profile,\n });\n Object.assign(values, bindingValues);\n }\n const plan = buildConfigPlan(allRequirements, values);\n if (planHasContent(plan)) {\n const result = writeConfig(cwd, plan);\n reportConfigWrite(result);\n if (result.databricksYmlChanged) validateBundle(cwd, profile);\n }\n warnScopeNeeding(allRequirements);\n }\n}\n\n/**\n * v1 does not write `user_api_scopes` (deferred to the manifest scope\n * extension). Warn when an added plugin's resource type is known to need one,\n * so the user adds it before deploy.\n */\n/** Resource types known to require a user_api_scope, and the scope each needs. */\nexport const SCOPE_BY_RESOURCE_TYPE: Record<string, string> = {\n genie_space: \"dashboards.genie\",\n serving_endpoint: \"serving.serving-endpoints\",\n // volumes/files-backed access uses files.files\n volume: \"files.files\",\n};\n\n/** Returns the user_api_scopes implied by a set of resource rows (deduped). */\nexport function scopesForResources(\n rows: ResourceRequirementRow[],\n): Map<string, string> {\n const needed = new Map<string, string>();\n for (const row of rows) {\n const scope = SCOPE_BY_RESOURCE_TYPE[row.type];\n if (scope) needed.set(row.type, scope);\n }\n return needed;\n}\n\nfunction warnScopeNeeding(rows: ResourceRequirementRow[]): void {\n const needed = scopesForResources(rows);\n if (needed.size === 0) return;\n const list = [...needed.entries()]\n .map(([type, scope]) => `${type} → ${scope}`)\n .join(\", \");\n console.warn(\n pc.yellow(\n `\\n Note: these resources may need a user_api_scope before deploy: ${list}.\\n` +\n \" Add it under resources.apps.app.user_api_scopes in databricks.yml.\",\n ),\n );\n}\n\n/** Commander reducer for repeatable `--env KEY=VALUE` flags. */\nfunction collectEnvFlag(\n raw: string,\n acc: Record<string, string>,\n): Record<string, string> {\n const eq = raw.indexOf(\"=\");\n if (eq === -1) {\n console.error(`Ignoring --env \"${raw}\" (expected KEY=VALUE).`);\n return acc;\n }\n const key = raw.slice(0, eq).trim();\n const value = raw.slice(eq + 1);\n if (key) acc[key] = value;\n return acc;\n}\n\nexport const addCommand = new Command(\"add\")\n .description(\"Add a UI component or server plugin from the AppKit registry\")\n .argument(\"<item...>\", \"Registry item name(s), e.g. metric-card or hello\")\n .option(\"-f, --force\", \"Overwrite existing files\")\n .option(\"-C, --cwd <dir>\", \"Run as if started in <dir>\")\n .option(\"--no-register\", \"Don't edit the server entry to register plugins\")\n .option(\"--no-resources\", \"Don't reconcile resource env vars into .env\")\n .option(\"-y, --yes\", \"Don't prompt; use --env values or leave vars unset\")\n .option(\n \"--env <KEY=VALUE>\",\n \"Pre-set a resource env var (repeatable)\",\n collectEnvFlag,\n {},\n )\n .option(\n \"-p, --profile <name>\",\n \"Databricks profile for the resource picker and bundle validate (defaults to the app's DATABRICKS_CONFIG_PROFILE)\",\n )\n .option(\n \"--allow-unverified\",\n \"Add items the registry doesn't mark verified (runs untrusted code)\",\n )\n .addHelpText(\n \"after\",\n `\nNo components.json is required. Item type is detected automatically:\n • UI components → <frontend>/src/components/appkit/ (client/ detected)\n • Server plugins → <server>/plugins/<name>/, runs plugin sync, and registers\n them in your createApp call (use --no-register to skip the server edit)\n\nServer plugins declare Databricks resources. On add, their env vars are\nreconciled into .env (and names into .env.example), and the deploy config\n(app.yaml + databricks.yml resource bindings) is patched to match — existing\nentries are never clobbered. Interactive by default; pass --yes for agents/CI\n(uses --env values, leaves the rest unset) and --env KEY=VALUE to supply\nvalues non-interactively. Pass --profile to validate the bundle after writing.\n\nThe frontend/server roots are detected from common layouts, so you can run\nthis from the repo root. While the registry repo is private, a read token is\nresolved from \\`gh auth token\\` or APPKIT_REGISTRY_TOKEN / GITHUB_TOKEN / GH_TOKEN.\n\nExamples:\n $ appkit add metric-card # UI component\n $ appkit add hello # server plugin\n $ appkit add metric-card hello # mix in one call\n $ appkit add analytics --yes --env DATABRICKS_WAREHOUSE_ID=abc123`,\n )\n .action((items: string[], opts: AddOptions) =>\n runAdd(items, opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"add.js","names":[],"sources":["../../../../src/cli/commands/registry/add.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\n\nimport {\n fetchRegistryItem,\n fetchVerifiedNames,\n isValidItemName,\n type RegistryItem,\n type RegistryItemFile,\n stripNamespace,\n} from \"./client\";\nimport {\n buildConfigPlan,\n collectBindingValueNeeds,\n planHasContent,\n} from \"./config-plan\";\nimport {\n reportConfigWrite,\n validateBundle,\n writeConfig,\n} from \"./config-writer\";\nimport {\n JS_IDENTIFIER,\n REGISTRY_REPO,\n type RegistryToken,\n resolveToken,\n} from \"./constants\";\nimport { parseEnv } from \"./env-reconcile\";\nimport {\n extractRequirements,\n type ResourceRequirementRow,\n renderRequirements,\n} from \"./requirements\";\n\n/** Subdirectories that commonly hold the frontend / server in an AppKit app. */\nconst FRONTEND_SUBDIRS = [\"client\", \"frontend\", \"web\", \"app\"];\nconst SERVER_SUBDIRS = [\"server\", \"api\", \"backend\"];\n\nfunction isDir(p: string): boolean {\n return fs.existsSync(p) && fs.statSync(p).isDirectory();\n}\n\n/** A registry item is a server plugin if it ships a manifest.json. */\nfunction isPluginItem(item: RegistryItem): boolean {\n return (item.files ?? []).some(\n (f) => path.basename(f.target ?? f.path) === \"manifest.json\",\n );\n}\n\n/**\n * Locates the frontend root for UI components. AppKit apps put the client in a\n * client/ subdir (with its own components.json + src/); the CLI is typically\n * run from the repo root. Prefer the dir with components.json, then a src/.\n */\nfunction findFrontendRoot(cwd: string): string {\n if (fs.existsSync(path.join(cwd, \"components.json\"))) return cwd;\n for (const sub of FRONTEND_SUBDIRS) {\n if (fs.existsSync(path.join(cwd, sub, \"components.json\"))) {\n return path.join(cwd, sub);\n }\n }\n if (isDir(path.join(cwd, \"src\"))) return cwd;\n for (const sub of FRONTEND_SUBDIRS) {\n if (isDir(path.join(cwd, sub, \"src\"))) return path.join(cwd, sub);\n }\n return cwd;\n}\n\n/** Locates the server root for plugins (the server/ subdir, else cwd). */\nfunction findServerRoot(cwd: string): string {\n for (const sub of SERVER_SUBDIRS) {\n if (isDir(path.join(cwd, sub))) return path.join(cwd, sub);\n }\n return cwd;\n}\n\n/** Nearest dir with a package.json, walking up from start (for dep install). */\nfunction findNearestPackageJson(start: string): string {\n let dir = start;\n for (;;) {\n if (fs.existsSync(path.join(dir, \"package.json\"))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return start;\n dir = parent;\n }\n}\n\n/**\n * The Databricks profile the app is configured with, read from\n * `DATABRICKS_CONFIG_PROFILE` in `cwd/.env` via the same dotenv parser the app\n * loads its env with. The CLI's top-level `dotenv/config` only loads the launch\n * dir's `.env` into `process.env`; when `--cwd` points at a different app dir,\n * that file isn't loaded, so the workspace picker and bundle validate would\n * miss the app's profile — this reads it directly. Undefined if the file or key\n * is absent, so the SDK's own default resolution still applies.\n */\nexport function profileFromEnv(cwd: string): string | undefined {\n try {\n const content = fs.readFileSync(path.join(cwd, \".env\"), \"utf-8\");\n return parseEnv(content).DATABRICKS_CONFIG_PROFILE || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolves a registry item's `target` under `base`, enforcing that the result\n * stays inside `base`. Registry items are untrusted remote data; a `target`\n * like `../../../.zshrc` or an absolute path could otherwise write files\n * anywhere on disk (arbitrary-write → RCE). Throws on any escape.\n */\nexport function resolveWithinBase(base: string, target: string): string {\n if (path.isAbsolute(target)) {\n throw new Error(`Refusing absolute file target from registry: ${target}`);\n }\n const baseResolved = path.resolve(base);\n const resolved = path.resolve(baseResolved, target);\n if (\n resolved !== baseResolved &&\n !resolved.startsWith(baseResolved + path.sep)\n ) {\n throw new Error(\n `Refusing file target that escapes the destination directory: ${target}`,\n );\n }\n return resolved;\n}\n\n/** UI file destination (relative to the frontend root): placed in src/ if present. */\nfunction uiTargetPath(base: string, file: RegistryItemFile): string {\n let target = file.target ?? path.join(\"components\", path.basename(file.path));\n if (!target.startsWith(\"src/\") && isDir(path.join(base, \"src\"))) {\n target = path.join(\"src\", target);\n }\n return target;\n}\n\n/** Best-effort: the `toPlugin` export name from the item's index.ts. Returns\n * null (caller falls back to printed instructions) unless the name is a plain\n * JS identifier — the item is untrusted remote content and the value is\n * interpolated into the user's server.ts. */\nexport function pluginExportName(item: RegistryItem): string | null {\n const index = (item.files ?? []).find(\n (f) => path.basename(f.target ?? f.path) === \"index.ts\",\n );\n const match = index?.content.match(/export\\s*\\{([^}]*)\\}/);\n if (!match) return null;\n const names = match[1].split(\",\").map((s) => s.trim());\n // Prefer the camelCase toPlugin instance over the PascalCase class.\n const chosen = names.find((n) => /^[a-z]/.test(n)) ?? names[0];\n return chosen && JS_IDENTIFIER.test(chosen) ? chosen : null;\n}\n\nfunction detectPackageManager(cwd: string): \"pnpm\" | \"yarn\" | \"bun\" | \"npm\" {\n if (fs.existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (fs.existsSync(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (fs.existsSync(path.join(cwd, \"bun.lockb\"))) return \"bun\";\n return \"npm\";\n}\n\n/**\n * A safe npm dependency spec: `[@scope/]name` with an optional `@version`\n * range. Registry `dependencies` are untrusted remote data passed to the\n * package manager, so we reject anything that isn't a plain name+range —\n * blocks tarball/git URL specs (install-script RCE) and `-`-prefixed entries\n * that the PM would parse as flags (argument injection).\n */\nconst SAFE_DEP_SPEC =\n /^(@[a-z0-9][\\w.-]*\\/)?[a-z0-9][\\w.-]*(@[\\w.\\-+~^><=|* ]+)?$/i;\n\n/** Splits deps into safe (installable) and rejected (surfaced to the user). */\nexport function partitionDeps(deps: string[]): {\n safe: string[];\n rejected: string[];\n} {\n const safe: string[] = [];\n const rejected: string[] = [];\n for (const dep of deps) {\n if (dep.startsWith(\"-\") || !SAFE_DEP_SPEC.test(dep)) rejected.push(dep);\n else safe.push(dep);\n }\n return { safe, rejected };\n}\n\n/** Splits an npm spec into `name` and optional version `range`, handling the\n * leading `@` of scoped names (`@databricks/appkit-ui@^0.41.0` → name\n * `@databricks/appkit-ui`, range `^0.41.0`; `lodash` → no range). */\nexport function parseDepSpec(spec: string): { name: string; range?: string } {\n const at = spec.lastIndexOf(\"@\");\n // at <= 0 means either no `@` (bare name) or the scope's `@` at index 0.\n if (at <= 0) return { name: spec };\n return { name: spec.slice(0, at), range: spec.slice(at + 1) };\n}\n\n/**\n * Registry `dependencies` are floors (\"needs at least this version\"), not pins.\n * Against the versions already in the target `package.json`, returns which specs\n * to install: missing packages (as declared) and genuine upgrades (installed\n * floor below the requested floor). A package already at a same-or-newer version\n * is skipped so the package manager never downgrades it; ranges that can't be\n * compared (workspace:*, catalog:, git) are skipped for the same reason.\n *\n * `semver` is injected so the caller can lazy-load it (see `installDependencies`).\n */\nexport function reconcileDeps(\n specs: string[],\n installed: Record<string, string>,\n { gte, minVersion }: Pick<typeof import(\"semver\"), \"gte\" | \"minVersion\">,\n): { install: string[]; skipped: string[] } {\n // minVersion() throws on ranges it can't parse (workspace:*, catalog:, git);\n // treat those as uncomparable and skip rather than risk a downgrade.\n const floor = (range: string): string | null => {\n try {\n return minVersion(range)?.version ?? null;\n } catch {\n return null;\n }\n };\n const install: string[] = [];\n const skipped: string[] = [];\n for (const spec of specs) {\n const { name, range } = parseDepSpec(spec);\n const have = installed[name];\n if (!have) {\n install.push(spec); // missing → install as declared\n continue;\n }\n if (!range) {\n skipped.push(spec); // present, no version asked → keep what's there\n continue;\n }\n const reqFloor = floor(range);\n const haveFloor = floor(have);\n if (!reqFloor || !haveFloor) {\n skipped.push(spec); // can't compare safely → don't touch\n } else if (gte(haveFloor, reqFloor)) {\n skipped.push(spec); // installed is same-or-newer → keep (no downgrade)\n } else {\n install.push(spec); // installed is older than required → upgrade\n }\n }\n return { install, skipped };\n}\n\n/** Merged dependencies + devDependencies (name → declared range) of a\n * package.json, tolerating a missing/unreadable file (returns {}). */\nfunction readInstalledRanges(pkgPath: string): Record<string, string> {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n return { ...pkg.dependencies, ...pkg.devDependencies };\n } catch {\n return {};\n }\n}\n\nasync function installDependencies(deps: string[], cwd: string): Promise<void> {\n if (deps.length === 0) return;\n\n const { safe, rejected } = partitionDeps(deps);\n if (rejected.length > 0) {\n console.warn(\n pc.yellow(\n `Skipping suspicious dependenc${rejected.length === 1 ? \"y\" : \"ies\"} from the registry (not a plain name@version): ${rejected.join(\", \")}. Install manually if you trust them.`,\n ),\n );\n }\n if (safe.length === 0) return;\n\n const pkgPath = path.join(cwd, \"package.json\");\n if (!fs.existsSync(pkgPath)) {\n console.warn(\n pc.yellow(\n `No package.json found — install these manually: ${safe.join(\" \")}`,\n ),\n );\n return;\n }\n\n // Lazy import: this file is loaded eagerly by the CLI entry, so keep semver\n // (only needed here) off the startup path — as with server-register/env-writer.\n const semver = await import(\"semver\");\n const { install, skipped } = reconcileDeps(\n safe,\n readInstalledRanges(pkgPath),\n semver,\n );\n if (skipped.length > 0) {\n console.log(\n pc.dim(\n `Keeping already-installed (registry asked for an older/equal range): ${skipped.join(\", \")}`,\n ),\n );\n }\n if (install.length === 0) return;\n\n const pm = detectPackageManager(cwd);\n const subcommand = pm === \"npm\" ? \"install\" : \"add\";\n console.log(`\\nInstalling dependencies with ${pm}: ${install.join(\" \")}`);\n // `--` stops the PM from parsing any dep as a flag (defense in depth).\n const result = spawnSync(pm, [subcommand, \"--\", ...install], {\n stdio: \"inherit\",\n cwd,\n });\n if (result.status !== 0) {\n console.warn(\n pc.yellow(\n `Dependency install exited with code ${result.status ?? \"unknown\"} — install manually if needed: ${install.join(\" \")}`,\n ),\n );\n }\n}\n\n/** Runs `appkit plugin sync --write` via this same CLI binary. */\nfunction runPluginSync(cwd: string): void {\n const result = spawnSync(\n process.execPath,\n [process.argv[1], \"plugin\", \"sync\", \"--write\"],\n { stdio: \"inherit\", cwd },\n );\n if (result.status !== 0) {\n console.warn(\n pc.yellow(\n \" Plugin sync did not complete cleanly — run `appkit plugin sync --write` manually.\",\n ),\n );\n }\n}\n\nfunction writeItemFile(\n base: string,\n target: string,\n content: string,\n force: boolean,\n cwd: string,\n): void {\n const dest = resolveWithinBase(base, target);\n const existed = fs.existsSync(dest);\n if (existed && !force) {\n console.error(\n pc.red(\n `Refusing to overwrite ${path.relative(cwd, dest)} — pass --force to replace it.`,\n ),\n );\n process.exit(1);\n }\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, content);\n const label = existed ? pc.yellow(\"Updated\") : pc.green(\"Created\");\n console.log(`${label} ${path.relative(cwd, dest)}`);\n}\n\ninterface PluginSummary {\n importPath: string;\n exportName: string | null;\n}\n\n/**\n * Fetches the requested items plus their transitive registryDependencies.\n * Dependencies are resolved breadth-first and de-duplicated by name, so a\n * plugin that depends on another registry item pulls the whole graph in one\n * `add`. Explicitly-requested items keep their request order and come first.\n */\nexport async function resolveItems(\n names: string[],\n token: RegistryToken | null,\n fetchItem: (\n name: string,\n token: RegistryToken | null,\n ) => Promise<RegistryItem> = fetchRegistryItem,\n): Promise<RegistryItem[]> {\n const seen = new Set<string>();\n const ordered: RegistryItem[] = [];\n // A name is both the fetch path and the on-disk `plugins/<name>` dir, so\n // reject non-slug refs (`/`, `..`, control chars) before they reach either.\n const enqueue = (ref: string): string => {\n const name = stripNamespace(ref);\n if (!isValidItemName(name)) {\n throw new Error(`Invalid registry item name: ${JSON.stringify(ref)}`);\n }\n return name;\n };\n // Breadth-first over the dependency graph, one level per iteration. Items in\n // a level are fetched concurrently (fetch latency is additive otherwise), but\n // levels stay ordered and dedup/cycle handling is unchanged: a name is marked\n // seen before its level is fetched, so it's never fetched or queued twice.\n let level = names.map(enqueue).filter((name) => {\n if (seen.has(name)) return false;\n seen.add(name);\n return true;\n });\n\n while (level.length > 0) {\n const items = await Promise.all(\n level.map(async (key) => {\n const item = await fetchItem(key, token);\n // Pin to the fetch key: the body's self-reported `name` is untrusted\n // and could claim a verified name to slip past the gate. The key is the\n // trustworthy identity the index keys `verified` on.\n item.name = key;\n return item;\n }),\n );\n ordered.push(...items);\n const next: string[] = [];\n for (const item of items) {\n for (const dep of item.registryDependencies ?? []) {\n const depName = enqueue(dep);\n if (seen.has(depName)) continue;\n seen.add(depName);\n next.push(depName);\n }\n }\n level = next;\n }\n\n return ordered;\n}\n\ninterface AddOptions {\n force?: boolean;\n cwd?: string;\n register?: boolean;\n /** false = don't reconcile resource env vars into .env. */\n resources?: boolean;\n /** true = never prompt; use --env flags or leave unset (agent/CI). */\n yes?: boolean;\n /** Pre-supplied env values from repeated --env KEY=VALUE flags. */\n env?: Record<string, string>;\n /** Databricks profile passed to `bundle validate` after writing config. */\n profile?: string;\n /** true = install items the registry index doesn't mark verified. */\n allowUnverified?: boolean;\n}\n\n/**\n * Splits requested names into verified and unverified against the index's\n * verified set. When `verified` is null the index couldn't be read — we can't\n * prove anything is verified, so every name is treated as unverified (the gate\n * then decides whether to warn-and-continue or block). Names are compared with\n * the namespace stripped, matching how items are fetched.\n */\nexport function partitionVerified(\n refs: string[],\n verified: Set<string> | null,\n): { verified: string[]; unverified: string[] } {\n const ok: string[] = [];\n const bad: string[] = [];\n for (const ref of refs) {\n const name = stripNamespace(ref);\n if (verified?.has(name)) ok.push(name);\n else bad.push(name);\n }\n return { verified: ok, unverified: bad };\n}\n\nasync function runAdd(refs: string[], opts: AddOptions): Promise<void> {\n const cwd = opts.cwd ? path.resolve(opts.cwd) : process.cwd();\n const token = resolveToken();\n if (token) {\n console.log(\n `Using ${token.envName} to fetch from ${REGISTRY_REPO} (private).`,\n );\n }\n\n // Resolve the full graph and fetch the verified index concurrently (two\n // independent round-trips). Item resolution is read-only — nothing is written\n // or installed until after the gate below.\n const verifiedP = opts.allowUnverified\n ? Promise.resolve(null)\n : fetchVerifiedNames(token);\n const items = await resolveItems(refs, token);\n\n // Integrity gate over the *entire resolved set* (not just requested names, so\n // an unverified transitive dep can't ride in on a verified item). Fails closed:\n // a null verified set (unreadable index) makes every item count as unverified.\n if (!opts.allowUnverified) {\n const verified = await verifiedP;\n const { unverified } = partitionVerified(\n items.map((i) => i.name),\n verified,\n );\n if (unverified.length > 0) {\n const reason =\n verified === null\n ? \"could not read the registry index to verify these items\"\n : `not marked verified in ${REGISTRY_REPO}`;\n console.error(\n pc.red(\n `Refusing to add unverified item(s) (${reason}): ${unverified.join(\", \")}.`,\n ),\n );\n console.error(\n pc.dim(\n \" Re-run with --allow-unverified if you trust the source; unverified items run code in your app.\",\n ),\n );\n process.exit(1);\n }\n }\n\n const hasUi = items.some((i) => !isPluginItem(i));\n const hasPlugin = items.some(isPluginItem);\n const frontendRoot = hasUi ? findFrontendRoot(cwd) : cwd;\n const serverRoot = hasPlugin ? findServerRoot(cwd) : cwd;\n if (hasUi && frontendRoot !== cwd) {\n console.log(pc.dim(`UI components → ${path.relative(cwd, frontendRoot)}/`));\n }\n if (hasPlugin && serverRoot !== cwd) {\n console.log(pc.dim(`Plugins → ${path.relative(cwd, serverRoot)}/`));\n }\n\n const deps = new Set<string>();\n let wroteUi = false;\n const pluginSummaries: PluginSummary[] = [];\n const allRequirements: ResourceRequirementRow[] = [];\n\n for (const item of items) {\n for (const dep of item.dependencies ?? []) deps.add(dep);\n\n if (isPluginItem(item)) {\n let pluginRel = path.join(\"plugins\", item.name);\n for (const file of item.files ?? []) {\n const target =\n file.target ??\n path.join(\"plugins\", item.name, path.basename(file.path));\n writeItemFile(\n serverRoot,\n target,\n file.content,\n Boolean(opts.force),\n cwd,\n );\n if (path.basename(target) === \"manifest.json\") {\n pluginRel = path.dirname(target);\n }\n }\n const requirements = extractRequirements(item);\n if (requirements.length > 0) {\n console.log(`\\n${renderRequirements(item, requirements)}`);\n allRequirements.push(...requirements);\n }\n pluginSummaries.push({\n importPath: `./${pluginRel}`,\n exportName: pluginExportName(item),\n });\n } else {\n for (const file of item.files ?? []) {\n writeItemFile(\n frontendRoot,\n uiTargetPath(frontendRoot, file),\n file.content,\n Boolean(opts.force),\n cwd,\n );\n wroteUi = true;\n }\n }\n }\n\n await installDependencies([...deps], findNearestPackageJson(cwd));\n\n if (hasPlugin) {\n console.log(pc.dim(\"\\nRegistering plugins (appkit plugin sync)...\"));\n runPluginSync(cwd);\n }\n\n if (wroteUi) {\n console.log(\n pc.dim(\n '\\nReminder: import \"@databricks/appkit-ui/styles.css\" once at your app root so components are themed.',\n ),\n );\n }\n // Lazy import: server-register pulls in @ast-grep/napi (a native addon), and\n // this CLI is imported eagerly by index.ts, so a static import would make\n // every unrelated command pay that cost.\n const registerPluginInServer =\n opts.register !== false && pluginSummaries.some((s) => s.exportName)\n ? (await import(\"./server-register.js\")).registerPluginInServer\n : null;\n for (const s of pluginSummaries) {\n // Try to wire the plugin into the server's createApp call automatically;\n // fall back to printing the snippet when the shape isn't the standard one.\n let wired = false;\n // Why auto-registration was skipped, so the fallback can say so (an empty\n // reason means the user opted out via --no-register — no nag then).\n let skipReason: string | undefined;\n if (opts.register === false) {\n // opted out — print the snippet without a \"couldn't\" message\n } else if (!s.exportName) {\n skipReason =\n \"couldn't read a plugin export name from the item's index.ts\";\n } else if (registerPluginInServer) {\n const result = registerPluginInServer(\n serverRoot,\n s.importPath,\n s.exportName,\n );\n // result.file is relative to serverRoot; show it from cwd for the user.\n const shown =\n result.file && path.relative(cwd, path.join(serverRoot, result.file));\n if (result.status === \"wired\") {\n console.log(`\\n${pc.green(\"Registered\")} ${s.exportName} in ${shown}`);\n wired = true;\n } else if (result.status === \"already\") {\n console.log(\n pc.dim(`\\n${s.exportName} is already registered in ${shown}`),\n );\n wired = true;\n } else {\n skipReason = result.reason;\n }\n }\n if (!wired) {\n const imp = s.exportName ?? \"<plugin>\";\n if (skipReason) {\n console.log(\n pc.yellow(`\\nCouldn't auto-register ${imp} — ${skipReason}.`),\n );\n }\n console.log(\n `${pc.bold(\"Add this to your server's createApp call:\")}\\n` +\n pc.dim(\n ` import { ${imp} } from \"${s.importPath}\";\\n` +\n ` const app = await createApp({ plugins: [${imp}(), /* ... */] });`,\n ),\n );\n }\n }\n\n if (opts.resources !== false && allRequirements.length > 0) {\n // Lazy import (same reason as server-register above): env-writer pulls in\n // the workspace picker and, through it, the Databricks SDK.\n const { collectBindingValues, reportEnvResolutions, syncEnv } =\n await import(\"./env-writer.js\");\n // Without --profile, fall back to the app's configured profile so the\n // picker and bundle validate reach the workspace the app runs against.\n const profile = opts.profile ?? profileFromEnv(cwd);\n console.log(pc.dim(\"\\nReconciling resource env vars into .env...\"));\n const resolutions = await syncEnv(allRequirements, {\n cwd,\n nonInteractive: Boolean(opts.yes),\n values: opts.env,\n profile,\n });\n reportEnvResolutions(resolutions);\n\n // Deploy config (app.yaml + databricks.yml). Values come from what the\n // user supplied for env fields (flags or prompts); other fields fall back\n // to their manifest defaults inside buildConfigPlan.\n const values: Record<string, string> = { ...(opts.env ?? {}) };\n for (const r of resolutions) {\n if (r.value !== undefined) values[r.env] = r.value;\n }\n // Binding fields with no env name (e.g. postgres project/branch/database)\n // never flow through .env, so collect them separately — else their\n // databricks.yml bundle variables stay unassigned and bundle validate fails.\n const bindingNeeds = collectBindingValueNeeds(allRequirements);\n if (bindingNeeds.length > 0) {\n const bindingValues = await collectBindingValues(bindingNeeds, {\n cwd,\n nonInteractive: Boolean(opts.yes),\n values: opts.env,\n profile,\n });\n Object.assign(values, bindingValues);\n }\n const plan = buildConfigPlan(allRequirements, values);\n if (planHasContent(plan)) {\n const result = writeConfig(cwd, plan);\n reportConfigWrite(result);\n if (result.databricksYmlChanged) validateBundle(cwd, profile);\n }\n warnScopeNeeding(allRequirements);\n }\n}\n\n/**\n * v1 does not write `user_api_scopes` (deferred to the manifest scope\n * extension). Warn when an added plugin's resource type is known to need one,\n * so the user adds it before deploy.\n */\n/** Resource types known to require a user_api_scope, and the scope each needs. */\nexport const SCOPE_BY_RESOURCE_TYPE: Record<string, string> = {\n genie_space: \"dashboards.genie\",\n serving_endpoint: \"serving.serving-endpoints\",\n // volumes/files-backed access uses files.files\n volume: \"files.files\",\n};\n\n/** Returns the user_api_scopes implied by a set of resource rows (deduped). */\nexport function scopesForResources(\n rows: ResourceRequirementRow[],\n): Map<string, string> {\n const needed = new Map<string, string>();\n for (const row of rows) {\n const scope = SCOPE_BY_RESOURCE_TYPE[row.type];\n if (scope) needed.set(row.type, scope);\n }\n return needed;\n}\n\nfunction warnScopeNeeding(rows: ResourceRequirementRow[]): void {\n const needed = scopesForResources(rows);\n if (needed.size === 0) return;\n const list = [...needed.entries()]\n .map(([type, scope]) => `${type} → ${scope}`)\n .join(\", \");\n console.warn(\n pc.yellow(\n `\\n Note: these resources may need a user_api_scope before deploy: ${list}.\\n` +\n \" Add it under resources.apps.app.user_api_scopes in databricks.yml.\",\n ),\n );\n}\n\n/** Commander reducer for repeatable `--env KEY=VALUE` flags. */\nfunction collectEnvFlag(\n raw: string,\n acc: Record<string, string>,\n): Record<string, string> {\n const eq = raw.indexOf(\"=\");\n if (eq === -1) {\n console.error(`Ignoring --env \"${raw}\" (expected KEY=VALUE).`);\n return acc;\n }\n const key = raw.slice(0, eq).trim();\n const value = raw.slice(eq + 1);\n if (key) acc[key] = value;\n return acc;\n}\n\nexport const addCommand = new Command(\"add\")\n .description(\"Add a UI component or server plugin from the AppKit registry\")\n .argument(\"<item...>\", \"Registry item name(s), e.g. metric-card or hello\")\n .option(\"-f, --force\", \"Overwrite existing files\")\n .option(\"-C, --cwd <dir>\", \"Run as if started in <dir>\")\n .option(\"--no-register\", \"Don't edit the server entry to register plugins\")\n .option(\"--no-resources\", \"Don't reconcile resource env vars into .env\")\n .option(\"-y, --yes\", \"Don't prompt; use --env values or leave vars unset\")\n .option(\n \"--env <KEY=VALUE>\",\n \"Pre-set a resource env var (repeatable)\",\n collectEnvFlag,\n {},\n )\n .option(\n \"-p, --profile <name>\",\n \"Databricks profile for the resource picker and bundle validate (defaults to the app's DATABRICKS_CONFIG_PROFILE)\",\n )\n .option(\n \"--allow-unverified\",\n \"Add items the registry doesn't mark verified (runs untrusted code)\",\n )\n .addHelpText(\n \"after\",\n `\nNo components.json is required. Item type is detected automatically:\n • UI components → <frontend>/src/components/appkit/ (client/ detected)\n • Server plugins → <server>/plugins/<name>/, runs plugin sync, and registers\n them in your createApp call (use --no-register to skip the server edit)\n\nServer plugins declare Databricks resources. On add, their env vars are\nreconciled into .env (and names into .env.example), and the deploy config\n(app.yaml + databricks.yml resource bindings) is patched to match — existing\nentries are never clobbered. Interactive by default; pass --yes for agents/CI\n(uses --env values, leaves the rest unset) and --env KEY=VALUE to supply\nvalues non-interactively. Pass --profile to validate the bundle after writing.\n\nThe frontend/server roots are detected from common layouts, so you can run\nthis from the repo root. While the registry repo is private, a read token is\nresolved from \\`gh auth token\\` or APPKIT_REGISTRY_TOKEN / GITHUB_TOKEN / GH_TOKEN.\n\nExamples:\n $ appkit add metric-card # UI component\n $ appkit add hello # server plugin\n $ appkit add metric-card hello # mix in one call\n $ appkit add analytics --yes --env DATABRICKS_WAREHOUSE_ID=abc123`,\n )\n .action((items: string[], opts: AddOptions) =>\n runAdd(items, opts).catch((err) => {\n console.error(err);\n process.exit(1);\n }),\n );\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import { APP_YAML_FILE, DATABRICKS_YML_FILE, bindingToNode } from "../../deploy-
|
|
|
2
2
|
import { planHasContent } from "./config-plan.js";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import pc from "picocolors";
|
|
6
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
+
import pc from "picocolors";
|
|
7
7
|
import { parseDocument } from "yaml";
|
|
8
8
|
|
|
9
9
|
//#region src/cli/commands/registry/config-writer.ts
|
package/dist/cli/index.js
CHANGED
|
@@ -30,7 +30,10 @@ cmd.addCommand(doctorCommand);
|
|
|
30
30
|
cmd.addCommand(registryCommand, { hidden: true });
|
|
31
31
|
cmd.addCommand(addCommand, { hidden: true });
|
|
32
32
|
cmd.addCommand(agentCommand);
|
|
33
|
-
await cmd.parseAsync()
|
|
33
|
+
await cmd.parseAsync().catch((err) => {
|
|
34
|
+
console.error(`\n${err instanceof Error ? err.message : String(err)}`);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
});
|
|
34
37
|
|
|
35
38
|
//#endregion
|
|
36
39
|
export { };
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { Command } from \"commander\";\n\nimport { agentCommand } from \"./commands/agent/index.js\";\nimport { codemodCommand } from \"./commands/codemod/index.js\";\nimport { docsCommand } from \"./commands/docs.js\";\nimport { doctorCommand } from \"./commands/doctor/index.js\";\nimport { generateTypesCommand } from \"./commands/generate-types.js\";\nimport { lintCommand } from \"./commands/lint.js\";\nimport { pluginCommand } from \"./commands/plugin/index.js\";\nimport { addCommand } from \"./commands/registry/add.js\";\nimport { registryCommand } from \"./commands/registry/index.js\";\nimport { setupCommand } from \"./commands/setup.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgPath = join(__dirname, \"../../package.json\");\nconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\nconst cmd = new Command();\n\ncmd\n .name(\"appkit\")\n .description(\"CLI tools for Databricks AppKit\")\n .version(pkg.version);\n\ncmd.addCommand(setupCommand);\ncmd.addCommand(generateTypesCommand);\ncmd.addCommand(lintCommand);\ncmd.addCommand(docsCommand);\ncmd.addCommand(pluginCommand);\ncmd.addCommand(codemodCommand);\ncmd.addCommand(doctorCommand);\n// Registry commands are executable but hidden from --help while the feature\n// is still in development (registry + add work end-to-end but aren't announced).\ncmd.addCommand(registryCommand, { hidden: true });\ncmd.addCommand(addCommand, { hidden: true });\ncmd.addCommand(agentCommand);\n\nawait cmd.parseAsync();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoBA,MAAM,UAAU,KADE,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACzB,qBAAqB;AACrD,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;AAEtD,MAAM,MAAM,IAAI,SAAS;AAEzB,IACG,KAAK,SAAS,CACd,YAAY,kCAAkC,CAC9C,QAAQ,IAAI,QAAQ;AAEvB,IAAI,WAAW,aAAa;AAC5B,IAAI,WAAW,qBAAqB;AACpC,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,cAAc;AAC7B,IAAI,WAAW,eAAe;AAC9B,IAAI,WAAW,cAAc;AAG7B,IAAI,WAAW,iBAAiB,EAAE,QAAQ,MAAM,CAAC;AACjD,IAAI,WAAW,YAAY,EAAE,QAAQ,MAAM,CAAC;AAC5C,IAAI,WAAW,aAAa;
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { Command } from \"commander\";\n\nimport { agentCommand } from \"./commands/agent/index.js\";\nimport { codemodCommand } from \"./commands/codemod/index.js\";\nimport { docsCommand } from \"./commands/docs.js\";\nimport { doctorCommand } from \"./commands/doctor/index.js\";\nimport { generateTypesCommand } from \"./commands/generate-types.js\";\nimport { lintCommand } from \"./commands/lint.js\";\nimport { pluginCommand } from \"./commands/plugin/index.js\";\nimport { addCommand } from \"./commands/registry/add.js\";\nimport { registryCommand } from \"./commands/registry/index.js\";\nimport { setupCommand } from \"./commands/setup.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgPath = join(__dirname, \"../../package.json\");\nconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\nconst cmd = new Command();\n\ncmd\n .name(\"appkit\")\n .description(\"CLI tools for Databricks AppKit\")\n .version(pkg.version);\n\ncmd.addCommand(setupCommand);\ncmd.addCommand(generateTypesCommand);\ncmd.addCommand(lintCommand);\ncmd.addCommand(docsCommand);\ncmd.addCommand(pluginCommand);\ncmd.addCommand(codemodCommand);\ncmd.addCommand(doctorCommand);\n// Registry commands are executable but hidden from --help while the feature\n// is still in development (registry + add work end-to-end but aren't announced).\ncmd.addCommand(registryCommand, { hidden: true });\ncmd.addCommand(addCommand, { hidden: true });\ncmd.addCommand(agentCommand);\n\n// Surface a clean message for any uncaught setup failure (e.g. a root\n// evals.config.ts that throws on import) instead of a raw unhandled-rejection\n// stack; a non-zero exit still gates CI.\nawait cmd.parseAsync().catch((err: unknown) => {\n console.error(`\\n${err instanceof Error ? err.message : String(err)}`);\n process.exitCode = 1;\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoBA,MAAM,UAAU,KADE,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACzB,qBAAqB;AACrD,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;AAEtD,MAAM,MAAM,IAAI,SAAS;AAEzB,IACG,KAAK,SAAS,CACd,YAAY,kCAAkC,CAC9C,QAAQ,IAAI,QAAQ;AAEvB,IAAI,WAAW,aAAa;AAC5B,IAAI,WAAW,qBAAqB;AACpC,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,cAAc;AAC7B,IAAI,WAAW,eAAe;AAC9B,IAAI,WAAW,cAAc;AAG7B,IAAI,WAAW,iBAAiB,EAAE,QAAQ,MAAM,CAAC;AACjD,IAAI,WAAW,YAAY,EAAE,QAAQ,MAAM,CAAC;AAC5C,IAAI,WAAW,aAAa;AAK5B,MAAM,IAAI,YAAY,CAAC,OAAO,QAAiB;AAC7C,SAAQ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACtE,SAAQ,WAAW;EACnB"}
|
package/dist/evals/discover.d.ts
CHANGED
|
@@ -22,6 +22,13 @@ interface DiscoveredEvalConfig {
|
|
|
22
22
|
* path relative to that evals dir with `.eval.ts` stripped. Sorted + stable.
|
|
23
23
|
*/
|
|
24
24
|
declare function discoverEvalFiles(rootDir: string): DiscoveredEval[];
|
|
25
|
+
/**
|
|
26
|
+
* Path to the root `evals.config.ts` (from {@link defineEvalConfig}) at
|
|
27
|
+
* `<rootDir>/evals.config.ts`, or `undefined` when absent. The root config
|
|
28
|
+
* holds run-wide settings (`baseUrl`, `webServer`); it's distinct from the
|
|
29
|
+
* per-agent configs found by {@link discoverEvalConfigs}.
|
|
30
|
+
*/
|
|
31
|
+
declare function findRootEvalConfig(rootDir: string): string | undefined;
|
|
25
32
|
/**
|
|
26
33
|
* Discover the per-agent `evals.config.ts` (from {@link defineEvalConfig}) at
|
|
27
34
|
* `<rootDir>/server/agents/<agent>/evals/evals.config.ts`. Config is per-agent:
|
|
@@ -30,5 +37,5 @@ declare function discoverEvalFiles(rootDir: string): DiscoveredEval[];
|
|
|
30
37
|
*/
|
|
31
38
|
declare function discoverEvalConfigs(rootDir: string): DiscoveredEvalConfig[];
|
|
32
39
|
//#endregion
|
|
33
|
-
export { DiscoveredEval, DiscoveredEvalConfig, discoverEvalConfigs, discoverEvalFiles };
|
|
40
|
+
export { DiscoveredEval, DiscoveredEvalConfig, discoverEvalConfigs, discoverEvalFiles, findRootEvalConfig };
|
|
34
41
|
//# sourceMappingURL=discover.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","names":[],"sources":["../../src/evals/discover.ts"],"mappings":";;UAOiB,cAAA;EAAc;EAE7B,IAAA;EAF6B;EAI7B,EAAA;EAAA;EAEA,KAAA;AAAA;;UAIe,oBAAA;EAAoB;EAEnC,IAAA;EAAA;EAEA,KAAA;AAAA;;;;;
|
|
1
|
+
{"version":3,"file":"discover.d.ts","names":[],"sources":["../../src/evals/discover.ts"],"mappings":";;UAOiB,cAAA;EAAc;EAE7B,IAAA;EAF6B;EAI7B,EAAA;EAAA;EAEA,KAAA;AAAA;;UAIe,oBAAA;EAAoB;EAEnC,IAAA;EAAA;EAEA,KAAA;AAAA;;;;;AA+DF;;iBA3BgB,iBAAA,CAAkB,OAAA,WAAkB,cAAA;;;AAsCpD;;;;iBAXgB,kBAAA,CAAmB,OAAA;;;;;;;iBAWnB,mBAAA,CAAoB,OAAA,WAAkB,oBAAA"}
|
package/dist/evals/discover.js
CHANGED
|
@@ -16,21 +16,33 @@ function evalFilesIn(dir) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
+
* List the agent directory names under `<rootDir>/server/agents/` (empty if the
|
|
20
|
+
* dir is absent). Shared by the eval-file and eval-config discovery below.
|
|
21
|
+
*/
|
|
22
|
+
function listAgents(rootDir) {
|
|
23
|
+
const agentsDir = path.join(rootDir, CODE_AGENTS_SOURCE_DIR);
|
|
24
|
+
try {
|
|
25
|
+
return {
|
|
26
|
+
agentsDir,
|
|
27
|
+
agents: agentDirNames(readdirSync(agentsDir, { withFileTypes: true }))
|
|
28
|
+
};
|
|
29
|
+
} catch {
|
|
30
|
+
return {
|
|
31
|
+
agentsDir,
|
|
32
|
+
agents: []
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
19
37
|
* Discover evals under `<rootDir>/server/agents/<agent>/evals/` — co-located
|
|
20
38
|
* with each agent's `agent.{md,ts}` (same folder-per-agent layout the agents
|
|
21
39
|
* plugin discovers). The agent id is the folder name; the eval id is the file
|
|
22
40
|
* path relative to that evals dir with `.eval.ts` stripped. Sorted + stable.
|
|
23
41
|
*/
|
|
24
42
|
function discoverEvalFiles(rootDir) {
|
|
25
|
-
const agentsDir =
|
|
43
|
+
const { agentsDir, agents } = listAgents(rootDir);
|
|
26
44
|
const out = [];
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
entries = readdirSync(agentsDir, { withFileTypes: true });
|
|
30
|
-
} catch {
|
|
31
|
-
return out;
|
|
32
|
-
}
|
|
33
|
-
for (const agent of agentDirNames(entries)) {
|
|
45
|
+
for (const agent of agents) {
|
|
34
46
|
const evalsDir = path.join(agentsDir, agent, "evals");
|
|
35
47
|
for (const file of evalFilesIn(evalsDir)) {
|
|
36
48
|
const id = path.relative(evalsDir, file).replace(/\.eval\.ts$/, "").split(path.sep).join("/");
|
|
@@ -44,21 +56,25 @@ function discoverEvalFiles(rootDir) {
|
|
|
44
56
|
return out.sort((a, b) => a.agent.localeCompare(b.agent) || a.id.localeCompare(b.id));
|
|
45
57
|
}
|
|
46
58
|
/**
|
|
59
|
+
* Path to the root `evals.config.ts` (from {@link defineEvalConfig}) at
|
|
60
|
+
* `<rootDir>/evals.config.ts`, or `undefined` when absent. The root config
|
|
61
|
+
* holds run-wide settings (`baseUrl`, `webServer`); it's distinct from the
|
|
62
|
+
* per-agent configs found by {@link discoverEvalConfigs}.
|
|
63
|
+
*/
|
|
64
|
+
function findRootEvalConfig(rootDir) {
|
|
65
|
+
const file = path.join(rootDir, "evals.config.ts");
|
|
66
|
+
return existsSync(file) ? file : void 0;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
47
69
|
* Discover the per-agent `evals.config.ts` (from {@link defineEvalConfig}) at
|
|
48
70
|
* `<rootDir>/server/agents/<agent>/evals/evals.config.ts`. Config is per-agent:
|
|
49
71
|
* each agent's config applies only to that agent's evals. Agents without a
|
|
50
72
|
* config file are omitted. Returns a stable, sorted list.
|
|
51
73
|
*/
|
|
52
74
|
function discoverEvalConfigs(rootDir) {
|
|
53
|
-
const agentsDir =
|
|
75
|
+
const { agentsDir, agents } = listAgents(rootDir);
|
|
54
76
|
const out = [];
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
entries = readdirSync(agentsDir, { withFileTypes: true });
|
|
58
|
-
} catch {
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
|
-
for (const agent of agentDirNames(entries)) {
|
|
77
|
+
for (const agent of agents) {
|
|
62
78
|
const file = path.join(agentsDir, agent, "evals", "evals.config.ts");
|
|
63
79
|
if (existsSync(file)) out.push({
|
|
64
80
|
file,
|
|
@@ -69,5 +85,5 @@ function discoverEvalConfigs(rootDir) {
|
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
//#endregion
|
|
72
|
-
export { discoverEvalConfigs, discoverEvalFiles };
|
|
88
|
+
export { discoverEvalConfigs, discoverEvalFiles, findRootEvalConfig };
|
|
73
89
|
//# sourceMappingURL=discover.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.js","names":[],"sources":["../../src/evals/discover.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"discover.js","names":[],"sources":["../../src/evals/discover.ts"],"sourcesContent":["import { existsSync, readdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { agentDirNames } from \"../core/agent/agent-dirs\";\nimport { CODE_AGENTS_SOURCE_DIR } from \"../core/agent/load-code-agents\";\n\n/** An eval file found under `server/agents/<agent>/evals/`. */\nexport interface DiscoveredEval {\n /** Absolute path to the `*.eval.ts` file. */\n file: string;\n /** Id relative to the agent's evals dir, without `.eval.ts` (e.g. `weather/basic`). */\n id: string;\n /** The agent id (the `server/agents/<agent>` directory name). */\n agent: string;\n}\n\n/** A per-agent `evals.config.ts` found under `server/agents/<agent>/evals/`. */\nexport interface DiscoveredEvalConfig {\n /** Absolute path to the `evals.config.ts` file. */\n file: string;\n /** The agent id whose evals this config applies to. */\n agent: string;\n}\n\n/** Recursively collect `*.eval.ts` files under `dir`. Empty when `dir` is absent. */\nfunction evalFilesIn(dir: string): string[] {\n try {\n return readdirSync(dir, { recursive: true, withFileTypes: true })\n .filter((e) => e.isFile() && e.name.endsWith(\".eval.ts\"))\n .map((e) => path.join(e.parentPath, e.name));\n } catch {\n return [];\n }\n}\n\n/**\n * List the agent directory names under `<rootDir>/server/agents/` (empty if the\n * dir is absent). Shared by the eval-file and eval-config discovery below.\n */\nfunction listAgents(rootDir: string): { agentsDir: string; agents: string[] } {\n const agentsDir = path.join(rootDir, CODE_AGENTS_SOURCE_DIR);\n try {\n return {\n agentsDir,\n agents: agentDirNames(readdirSync(agentsDir, { withFileTypes: true })),\n };\n } catch {\n return { agentsDir, agents: [] };\n }\n}\n\n/**\n * Discover evals under `<rootDir>/server/agents/<agent>/evals/` — co-located\n * with each agent's `agent.{md,ts}` (same folder-per-agent layout the agents\n * plugin discovers). The agent id is the folder name; the eval id is the file\n * path relative to that evals dir with `.eval.ts` stripped. Sorted + stable.\n */\nexport function discoverEvalFiles(rootDir: string): DiscoveredEval[] {\n const { agentsDir, agents } = listAgents(rootDir);\n const out: DiscoveredEval[] = [];\n\n for (const agent of agents) {\n const evalsDir = path.join(agentsDir, agent, \"evals\");\n for (const file of evalFilesIn(evalsDir)) {\n const id = path\n .relative(evalsDir, file)\n .replace(/\\.eval\\.ts$/, \"\")\n .split(path.sep)\n .join(\"/\");\n out.push({ file, id, agent });\n }\n }\n\n return out.sort(\n (a, b) => a.agent.localeCompare(b.agent) || a.id.localeCompare(b.id),\n );\n}\n\n/**\n * Path to the root `evals.config.ts` (from {@link defineEvalConfig}) at\n * `<rootDir>/evals.config.ts`, or `undefined` when absent. The root config\n * holds run-wide settings (`baseUrl`, `webServer`); it's distinct from the\n * per-agent configs found by {@link discoverEvalConfigs}.\n */\nexport function findRootEvalConfig(rootDir: string): string | undefined {\n const file = path.join(rootDir, \"evals.config.ts\");\n return existsSync(file) ? file : undefined;\n}\n\n/**\n * Discover the per-agent `evals.config.ts` (from {@link defineEvalConfig}) at\n * `<rootDir>/server/agents/<agent>/evals/evals.config.ts`. Config is per-agent:\n * each agent's config applies only to that agent's evals. Agents without a\n * config file are omitted. Returns a stable, sorted list.\n */\nexport function discoverEvalConfigs(rootDir: string): DiscoveredEvalConfig[] {\n const { agentsDir, agents } = listAgents(rootDir);\n const out: DiscoveredEvalConfig[] = [];\n\n for (const agent of agents) {\n const file = path.join(agentsDir, agent, \"evals\", \"evals.config.ts\");\n if (existsSync(file)) out.push({ file, agent });\n }\n\n return out.sort((a, b) => a.agent.localeCompare(b.agent));\n}\n"],"mappings":";;;;;;;AAyBA,SAAS,YAAY,KAAuB;AAC1C,KAAI;AACF,SAAO,YAAY,KAAK;GAAE,WAAW;GAAM,eAAe;GAAM,CAAC,CAC9D,QAAQ,MAAM,EAAE,QAAQ,IAAI,EAAE,KAAK,SAAS,WAAW,CAAC,CACxD,KAAK,MAAM,KAAK,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC;SACxC;AACN,SAAO,EAAE;;;;;;;AAQb,SAAS,WAAW,SAA0D;CAC5E,MAAM,YAAY,KAAK,KAAK,SAAS,uBAAuB;AAC5D,KAAI;AACF,SAAO;GACL;GACA,QAAQ,cAAc,YAAY,WAAW,EAAE,eAAe,MAAM,CAAC,CAAC;GACvE;SACK;AACN,SAAO;GAAE;GAAW,QAAQ,EAAE;GAAE;;;;;;;;;AAUpC,SAAgB,kBAAkB,SAAmC;CACnE,MAAM,EAAE,WAAW,WAAW,WAAW,QAAQ;CACjD,MAAM,MAAwB,EAAE;AAEhC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,WAAW,KAAK,KAAK,WAAW,OAAO,QAAQ;AACrD,OAAK,MAAM,QAAQ,YAAY,SAAS,EAAE;GACxC,MAAM,KAAK,KACR,SAAS,UAAU,KAAK,CACxB,QAAQ,eAAe,GAAG,CAC1B,MAAM,KAAK,IAAI,CACf,KAAK,IAAI;AACZ,OAAI,KAAK;IAAE;IAAM;IAAI;IAAO,CAAC;;;AAIjC,QAAO,IAAI,MACR,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,MAAM,IAAI,EAAE,GAAG,cAAc,EAAE,GAAG,CACrE;;;;;;;;AASH,SAAgB,mBAAmB,SAAqC;CACtE,MAAM,OAAO,KAAK,KAAK,SAAS,kBAAkB;AAClD,QAAO,WAAW,KAAK,GAAG,OAAO;;;;;;;;AASnC,SAAgB,oBAAoB,SAAyC;CAC3E,MAAM,EAAE,WAAW,WAAW,WAAW,QAAQ;CACjD,MAAM,MAA8B,EAAE;AAEtC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,KAAK,KAAK,WAAW,OAAO,SAAS,kBAAkB;AACpE,MAAI,WAAW,KAAK,CAAE,KAAI,KAAK;GAAE;GAAM;GAAO,CAAC;;AAGjD,QAAO,IAAI,MAAM,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,MAAM,CAAC"}
|
package/dist/evals/index.d.ts
CHANGED
|
@@ -2,13 +2,13 @@ import { DatabricksAuth, ResolveDatabricksAuthOptions, resolveDatabricksAuth, re
|
|
|
2
2
|
import { MlflowClient, PostResult, normalizeHost } from "../connectors/mlflow/client.js";
|
|
3
3
|
import "../connectors/mlflow/index.js";
|
|
4
4
|
import { DatasetRow, ReadEvalDatasetOptions, readEvalDataset, userTurns } from "./dataset.js";
|
|
5
|
-
import { AssertionHandle, AssertionResult, CustomJudgeSpec, DriveResult, EvalDefinition, EvalDriver, EvalResult, MatchResult, Matcher, Severity, TestContext } from "./types.js";
|
|
5
|
+
import { AssertionHandle, AssertionResult, CustomJudgeSpec, DriveResult, EvalDefinition, EvalDriver, EvalResult, EvalWebServer, MatchResult, Matcher, Severity, TestContext } from "./types.js";
|
|
6
6
|
import { defineEval, defineEvalConfig } from "./define-eval.js";
|
|
7
|
-
import { DiscoveredEval, DiscoveredEvalConfig, discoverEvalConfigs, discoverEvalFiles } from "./discover.js";
|
|
7
|
+
import { DiscoveredEval, DiscoveredEvalConfig, discoverEvalConfigs, discoverEvalFiles, findRootEvalConfig } from "./discover.js";
|
|
8
8
|
import { HttpDriverOptions, createHttpDriver } from "./http-driver.js";
|
|
9
9
|
import { JudgeConfig, JudgeScore, configureJudge, isJudgeConfigured } from "./judge.js";
|
|
10
10
|
import { equals, includes, matches } from "./matchers.js";
|
|
11
11
|
import { Assessment, ReportOutcome, buildAssessments, reportToMlflow } from "./mlflow-report.js";
|
|
12
12
|
import { EvalSummary, evalGlyph, formatEvalDetail, formatEvalHeadline, formatEvalResults, formatResultsJUnit, formatResultsJson, formatSummaryLine, summarize } from "./report.js";
|
|
13
13
|
import { RunEvalOptions, runEval } from "./run-eval.js";
|
|
14
|
-
import { EvalProgress, EvalRunSummary, RunEvalsOptions, runEvalsInDir, runWithRetries } from "./run-evals.js";
|
|
14
|
+
import { EvalProgress, EvalRunSummary, RunEvalsOptions, loadRootEvalConfig, runEvalsInDir, runWithRetries } from "./run-evals.js";
|
package/dist/evals/index.js
CHANGED
|
@@ -2,13 +2,13 @@ import { resolveDatabricksAuth, resolveWorkspaceClient } from "../connectors/mlf
|
|
|
2
2
|
import { MlflowClient, normalizeHost } from "../connectors/mlflow/client.js";
|
|
3
3
|
import { readEvalDataset, userTurns } from "./dataset.js";
|
|
4
4
|
import { defineEval, defineEvalConfig } from "./define-eval.js";
|
|
5
|
-
import { discoverEvalConfigs, discoverEvalFiles } from "./discover.js";
|
|
5
|
+
import { discoverEvalConfigs, discoverEvalFiles, findRootEvalConfig } from "./discover.js";
|
|
6
6
|
import { createHttpDriver } from "./http-driver.js";
|
|
7
7
|
import { configureJudge, isJudgeConfigured } from "./judge.js";
|
|
8
8
|
import { equals, includes, matches } from "./matchers.js";
|
|
9
9
|
import { buildAssessments, reportToMlflow } from "./mlflow-report.js";
|
|
10
10
|
import { evalGlyph, formatEvalDetail, formatEvalHeadline, formatEvalResults, formatResultsJUnit, formatResultsJson, formatSummaryLine, summarize } from "./report.js";
|
|
11
11
|
import { runEval } from "./run-eval.js";
|
|
12
|
-
import { runEvalsInDir, runWithRetries } from "./run-evals.js";
|
|
12
|
+
import { loadRootEvalConfig, runEvalsInDir, runWithRetries } from "./run-evals.js";
|
|
13
13
|
|
|
14
14
|
export { };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WorkspaceClient } from "../workspace-client/index.js";
|
|
2
2
|
import "./dataset.js";
|
|
3
|
-
import { EvalResult } from "./types.js";
|
|
3
|
+
import { EvalConfig, EvalResult } from "./types.js";
|
|
4
4
|
import { ReportOutcome } from "./mlflow-report.js";
|
|
5
5
|
import { FinishOutcome } from "./mlflow-run.js";
|
|
6
6
|
|
|
@@ -98,6 +98,13 @@ interface EvalRunSummary {
|
|
|
98
98
|
finish: FinishOutcome;
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Load the root `evals.config.ts` under `rootDir` (the project root), or return
|
|
103
|
+
* `undefined` when there is none. This is the run-wide config carrying
|
|
104
|
+
* `baseUrl`/`webServer`; the CLI reads it to resolve options and manage the
|
|
105
|
+
* app-under-test lifecycle before calling {@link runEvalsInDir}.
|
|
106
|
+
*/
|
|
107
|
+
declare function loadRootEvalConfig(rootDir: string): Promise<EvalConfig | undefined>;
|
|
101
108
|
/**
|
|
102
109
|
* Run `attempt` up to `1 + retries` times, stopping as soon as it returns a
|
|
103
110
|
* result that is neither a thrown error / per-eval timeout (`error`) nor a
|
|
@@ -119,5 +126,5 @@ declare function runWithRetries(retries: number, attempt: (attemptNumber: number
|
|
|
119
126
|
*/
|
|
120
127
|
declare function runEvalsInDir(options: RunEvalsOptions): Promise<EvalRunSummary>;
|
|
121
128
|
//#endregion
|
|
122
|
-
export { EvalProgress, EvalRunSummary, RunEvalsOptions, runEvalsInDir, runWithRetries };
|
|
129
|
+
export { EvalProgress, EvalRunSummary, RunEvalsOptions, loadRootEvalConfig, runEvalsInDir, runWithRetries };
|
|
123
130
|
//# sourceMappingURL=run-evals.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-evals.d.ts","names":[],"sources":["../../src/evals/run-evals.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"run-evals.d.ts","names":[],"sources":["../../src/evals/run-evals.ts"],"mappings":";;;;;;;UAoBiB,eAAA;;EAEf,OAAA;EAF8B;EAI9B,OAAA;EAWU;EATV,MAAA;EAwDkB;;;;EAnDlB,IAAA;EALA;EAOA,MAAA;EAAA;EAEA,OAAA,GAAU,MAAA;EAAA;;;;;;EAOV,WAAA;EAiBA;;;;;EAXA,MAAA;IACE,IAAA;IACA,KAAA;IACA,YAAA,UA6BF;IA3BE,cAAA;EAAA;EA6BS;;;AAGb;EA1BE,KAAA;IAAU,IAAA;IAAc,KAAA;IAAe,KAAA;EAAA;EA4BnC;;;;EAvBJ,eAAA,GAAkB,eAAA;EAwB4B;EAtB9C,WAAA;EAuBoB;EArBpB,GAAA;EAqBwC;;;;AAE1C;EAjBE,SAAA;;;;;;EAMA,OAAA;EAYA;EAVA,OAAA,IAAW,KAAA,EAAO,YAAA;AAAA;AAAA,KAGR,YAAA;EACN,IAAA;EAAoB,KAAA;AAAA;EACpB,IAAA;EAAqB,KAAA;AAAA;EACrB,IAAA;EAAe,EAAA;EAAY,KAAA;EAAe,KAAA;AAAA;EAC1C,IAAA;EAAgB,MAAA,EAAQ,UAAA;EAAY,KAAA;EAAe,KAAA;AAAA;AAAA,UAExC,cAAA;EACf,OAAA,EAAS,UAAA;EAyPmC;EAvP5C,MAAA;IAAW,KAAA;IAAe,MAAA,EAAQ,aAAA;IAAe,MAAA,EAAQ,aAAA;EAAA;AAAA;;;;;;;iBAmDrC,kBAAA,CACpB,OAAA,WACC,OAAA,CAAQ,UAAA;;;;;;;;;;;;iBAgMW,cAAA,CACpB,OAAA,UACA,OAAA,GAAU,aAAA,aAA0B,OAAA,CAAQ,UAAA,GAC5C,OAAA;EAAW,WAAA;AAAA,IACV,OAAA,CAAQ,UAAA;;;;;;iBA8GW,aAAA,CACpB,OAAA,EAAS,eAAA,GACR,OAAA,CAAQ,cAAA"}
|
package/dist/evals/run-evals.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MlflowClient } from "../connectors/mlflow/client.js";
|
|
2
2
|
import { readEvalDataset } from "./dataset.js";
|
|
3
|
-
import { discoverEvalConfigs, discoverEvalFiles } from "./discover.js";
|
|
3
|
+
import { discoverEvalConfigs, discoverEvalFiles, findRootEvalConfig } from "./discover.js";
|
|
4
4
|
import { createHttpDriver } from "./http-driver.js";
|
|
5
5
|
import { configureJudge, teardownJudge } from "./judge.js";
|
|
6
6
|
import { mapPool } from "./pool.js";
|
|
@@ -43,6 +43,17 @@ async function loadEvalConfig(file) {
|
|
|
43
43
|
return resolveConfigDefault(await tsImportFile(file));
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
+
* Load the root `evals.config.ts` under `rootDir` (the project root), or return
|
|
47
|
+
* `undefined` when there is none. This is the run-wide config carrying
|
|
48
|
+
* `baseUrl`/`webServer`; the CLI reads it to resolve options and manage the
|
|
49
|
+
* app-under-test lifecycle before calling {@link runEvalsInDir}.
|
|
50
|
+
*/
|
|
51
|
+
async function loadRootEvalConfig(rootDir) {
|
|
52
|
+
const file = findRootEvalConfig(rootDir);
|
|
53
|
+
if (!file) return void 0;
|
|
54
|
+
return loadEvalConfig(file);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
46
57
|
* Unwrap the config default export across module-interop shapes (see
|
|
47
58
|
* {@link resolveEvalDefault}). A config has no `.test`, so the first plain
|
|
48
59
|
* object reached through the `default` chain is taken as the config.
|
|
@@ -355,5 +366,5 @@ async function runEvalsInDir(options) {
|
|
|
355
366
|
}
|
|
356
367
|
|
|
357
368
|
//#endregion
|
|
358
|
-
export { runEvalsInDir, runWithRetries };
|
|
369
|
+
export { loadRootEvalConfig, runEvalsInDir, runWithRetries };
|
|
359
370
|
//# sourceMappingURL=run-evals.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-evals.js","names":["sleep"],"sources":["../../src/evals/run-evals.ts"],"sourcesContent":["import { setTimeout as sleep } from \"node:timers/promises\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { MlflowClient } from \"../connectors/mlflow\";\nimport type { WorkspaceClient } from \"../workspace-client\";\nimport { type DatasetRow, readEvalDataset } from \"./dataset\";\nimport {\n type DiscoveredEval,\n discoverEvalConfigs,\n discoverEvalFiles,\n} from \"./discover\";\nimport { createHttpDriver } from \"./http-driver\";\nimport { configureJudge, teardownJudge } from \"./judge\";\nimport { type ReportOutcome, reportToMlflow } from \"./mlflow-report\";\nimport { createEvalRun, type FinishOutcome, finishEvalRun } from \"./mlflow-run\";\nimport { mapPool } from \"./pool\";\nimport { runEval } from \"./run-eval\";\nimport type { EvalConfig, EvalDefinition, EvalResult } from \"./types\";\n\nexport interface RunEvalsOptions {\n /** Project root containing `server/agents/`. Defaults to `process.cwd()`. */\n rootDir?: string;\n /** Base URL of the running app to drive, e.g. `http://localhost:3000`. */\n baseUrl: string;\n /** Substring filter on `<agent>/<id>` (or an exact agent id). */\n filter?: string;\n /**\n * Only run evals whose `tags` intersect this list. Empty/undefined runs all.\n * Tags live on the eval def, so filtering happens after each file is loaded.\n */\n tags?: string[];\n /** Soft assertion failures also fail the eval. */\n strict?: boolean;\n /** Extra request headers for the driver (e.g. auth for a deployed app). */\n headers?: Record<string, string>;\n /**\n * Max evals to drive concurrently. Each eval opens one stream to the app as\n * the same user, so keep this at or below the app's\n * `maxConcurrentStreamsPerUser` (default 5) or the surplus streams hit the\n * 429 guard. Defaults to 4; clamped to `[1, total]`.\n */\n concurrency?: number;\n /**\n * When set, create a native MLflow \"Evaluation run\": each eval's trace is\n * linked to the run, pass/fail is written as feedback, and aggregate metrics\n * are logged. Requires Databricks creds + the target experiment.\n */\n mlflow?: {\n host: string;\n token: string;\n experimentId: string;\n /** SQL warehouse id for writing assessments to UC-backed (V4) traces. */\n sqlWarehouseId?: string;\n };\n /**\n * When set, enable `t.judge.*` LLM-as-judge scoring via autoevals against a\n * Databricks serving endpoint (`model`).\n */\n judge?: { host: string; token: string; model: string };\n /**\n * Workspace client used to read managed evaluation datasets (for evals that\n * declare `dataset`). Required alongside {@link warehouseId} for those evals.\n */\n workspaceClient?: WorkspaceClient;\n /** SQL warehouse id used to read managed evaluation datasets. */\n warehouseId?: string;\n /** Wall-clock timestamp (ms) for run create/finish — pass `Date.now()`. */\n now?: number;\n /**\n * Default per-eval timeout (ms): `runEval` races the whole test against it and\n * it also caps each driver turn. A per-eval `def.timeoutMs` overrides it, and\n * it wins over an agent's `evals.config.ts` `timeoutMs`. Unbounded when unset.\n */\n timeoutMs?: number;\n /**\n * Re-run an eval up to this many extra times when it fails on infrastructure —\n * a thrown error/timeout (`result.error`) or a transport/agent turn failure\n * (`result.infraFailure`). Assertion failures are never retried. Defaults to `0`.\n */\n retries?: number;\n /** Progress callback, invoked as evals are discovered, started, and finished. */\n onEvent?: (event: EvalProgress) => void;\n}\n\nexport type EvalProgress =\n | { type: \"discovered\"; total: number }\n | { type: \"run-created\"; runId: string }\n | { type: \"start\"; id: string; index: number; total: number }\n | { type: \"result\"; result: EvalResult; index: number; total: number };\n\nexport interface EvalRunSummary {\n results: EvalResult[];\n /** Present when an MLflow evaluation run was created. */\n mlflow?: { runId: string; report: ReportOutcome; finish: FinishOutcome };\n}\n\n/**\n * Import a TypeScript file with tsx's programmatic loader so eval files run\n * without a build step. The specifier is indirected so the type checker doesn't\n * try to resolve tsx's internal entry.\n */\nasync function tsImportFile(file: string): Promise<unknown> {\n const tsxApi = \"tsx/esm/api\";\n let tsImport: (specifier: string, parentURL: string) => Promise<unknown>;\n try {\n ({ tsImport } = (await import(tsxApi)) as {\n tsImport: (specifier: string, parentURL: string) => Promise<unknown>;\n });\n } catch {\n throw new Error(\n \"Running .eval.ts files requires `tsx`. Install it as a dev dependency (`pnpm add -D tsx`).\",\n );\n }\n return tsImport(pathToFileURL(file).href, import.meta.url);\n}\n\n/**\n * Load a `*.eval.ts` file and return its default-exported {@link EvalDefinition}.\n */\nasync function loadEval(file: string): Promise<EvalDefinition> {\n const mod = await tsImportFile(file);\n const def = resolveEvalDefault(mod);\n if (!def) {\n throw new Error(`${file}: must default-export defineEval({ test })`);\n }\n return def;\n}\n\n/**\n * Load an `evals.config.ts` file and return its default-exported\n * {@link EvalConfig}. A malformed/missing default surfaces as `undefined` so a\n * bad config never aborts a whole run.\n */\nasync function loadEvalConfig(file: string): Promise<EvalConfig | undefined> {\n const mod = await tsImportFile(file);\n return resolveConfigDefault(mod);\n}\n\n/**\n * Unwrap the config default export across module-interop shapes (see\n * {@link resolveEvalDefault}). A config has no `.test`, so the first plain\n * object reached through the `default` chain is taken as the config.\n */\nexport function resolveConfigDefault(mod: unknown): EvalConfig | undefined {\n let candidate: unknown = mod;\n // `i < 4` bounds the chain; no visited-set needed (cf. resolveEvalDefault).\n for (let i = 0; i < 4 && candidate; i++) {\n const next = (candidate as { default?: unknown }).default;\n if (next === undefined) {\n return typeof candidate === \"object\"\n ? (candidate as EvalConfig)\n : undefined;\n }\n candidate = next;\n }\n return undefined;\n}\n\n/**\n * Unwrap the eval default export across module-interop shapes. Depending on\n * whether the eval file is treated as ESM or CJS, the value lands at\n * `mod.default` (ESM), `mod.default.default` (CJS `__esModule` double-wrap), or\n * `mod` itself. Returns the first candidate that looks like an eval.\n */\nexport function resolveEvalDefault(mod: unknown): EvalDefinition | undefined {\n let candidate: unknown = mod;\n for (let i = 0; i < 4 && candidate; i++) {\n if (typeof (candidate as EvalDefinition).test === \"function\") {\n return candidate as EvalDefinition;\n }\n candidate = (candidate as { default?: unknown }).default;\n }\n return undefined;\n}\n\n/**\n * Run one eval turn against a fresh driver. Never throws — a run failure becomes\n * a non-passing {@link EvalResult} so one bad eval can't abort the run. `row`\n * binds the current managed-dataset row (see {@link resolveDatasetRows}), or is\n * `undefined` for a plain single-run eval.\n */\nasync function runOne(\n d: DiscoveredEval,\n id: string,\n def: EvalDefinition,\n row: DatasetRow | undefined,\n runId: string | undefined,\n options: RunEvalsOptions,\n): Promise<EvalResult> {\n try {\n // Each attempt builds a fresh driver, so a retry never inherits the failed\n // attempt's thread. (runWithRetries defines what counts as retryable.)\n return await runWithRetries(options.retries ?? 0, () =>\n runEval(def, {\n id,\n driver: createHttpDriver({\n baseUrl: options.baseUrl,\n agent: def.agent ?? d.agent,\n headers: options.headers,\n mlflowRunId: runId,\n // Cap the driver turn at the eval's effective timeout (runEval's signal also aborts it).\n timeoutMs: def.timeoutMs ?? options.timeoutMs,\n }),\n strict: options.strict,\n row,\n timeoutMs: options.timeoutMs,\n }),\n );\n } catch (err) {\n return {\n id,\n assertions: [],\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Resolve the rows a (possibly dataset-driven) eval runs over. A plain eval\n * yields a single `undefined` row; a dataset eval reads its Unity Catalog table\n * via {@link readEvalDataset}. On misconfiguration or read failure, returns a\n * single `undefined` row plus an `error`, so the eval still surfaces one result.\n */\nexport async function resolveDatasetRows(\n def: EvalDefinition,\n options: RunEvalsOptions,\n): Promise<{ rows: Array<DatasetRow | undefined>; error?: string }> {\n if (!def.dataset) return { rows: [undefined] };\n if (!options.workspaceClient || !options.warehouseId) {\n return {\n rows: [undefined],\n error:\n \"dataset eval requires a workspace client and warehouse (pass --warehouse-id)\",\n };\n }\n try {\n const rows = await readEvalDataset(options.workspaceClient, {\n table: def.dataset.table,\n warehouseId: options.warehouseId,\n limit: def.dataset.limit,\n });\n if (rows.length === 0) {\n return {\n rows: [undefined],\n error: `dataset \"${def.dataset.table}\" returned no rows`,\n };\n }\n return { rows };\n } catch (err) {\n return {\n rows: [undefined],\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Run one already-loaded eval (from the `loaded` pre-pass), expanding a\n * dataset-driven eval into one run per row. Appends one result per row to\n * `results`, emitting `start`/`result` around each. Never throws: a load error\n * (carried in `loadError`) or a dataset-read failure surfaces as a non-passing\n * result. `total` counts eval files, not rows — per-row detail is carried in the\n * result id (`[row i/n]`).\n */\nasync function runDiscovered(\n d: DiscoveredEval,\n def: EvalDefinition,\n loadError: string | undefined,\n index: number,\n total: number,\n runId: string | undefined,\n options: RunEvalsOptions,\n emit: (event: EvalProgress) => void,\n results: EvalResult[],\n): Promise<void> {\n const id = `${d.agent}/${d.id}`;\n\n // Load failed in the pre-pass (def is a placeholder) → one non-passing result.\n if (loadError) {\n emit({ type: \"start\", id, index, total });\n const result: EvalResult = {\n id,\n assertions: [],\n passed: false,\n error: loadError,\n };\n results.push(result);\n emit({ type: \"result\", result, index, total });\n return;\n }\n\n const { rows, error: datasetError } = await resolveDatasetRows(def, options);\n\n for (let r = 0; r < rows.length; r++) {\n const rowId =\n def.dataset && rows.length > 1\n ? `${id} [row ${r + 1}/${rows.length}]`\n : id;\n emit({ type: \"start\", id: rowId, index, total });\n const result: EvalResult = datasetError\n ? { id: rowId, assertions: [], passed: false, error: datasetError }\n : await runOne(d, rowId, def, rows[r], runId, options);\n results.push(result);\n emit({ type: \"result\", result, index, total });\n }\n}\n\n/** Base delay (ms) before the first retry; doubled per attempt, full-jittered, capped. */\nconst DEFAULT_RETRY_BASE_DELAY_MS = 250;\n/** Ceiling for a single retry backoff wait (ms). */\nconst MAX_RETRY_DELAY_MS = 5_000;\n\n/**\n * Run `attempt` up to `1 + retries` times, stopping as soon as it returns a\n * result that is neither a thrown error / per-eval timeout (`error`) nor a\n * transport/agent turn failure (`infraFailure`). Assertion failures set\n * neither, so a failed-but-completed eval is returned on the first try and\n * never retried. Returns the last result when every attempt failed on infra.\n *\n * Between attempts it waits a full-jittered exponential backoff (infra flakes\n * are overload-correlated). `retries` is coerced to a finite non-negative\n * integer; `baseDelayMs: 0` disables the wait (tests).\n */\nexport async function runWithRetries(\n retries: number,\n attempt: (attemptNumber: number) => Promise<EvalResult>,\n options: { baseDelayMs?: number } = {},\n): Promise<EvalResult> {\n const baseDelayMs = options.baseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n const maxRetries = Number.isFinite(retries)\n ? Math.max(0, Math.floor(retries))\n : 0;\n const maxAttempts = 1 + maxRetries;\n let result: EvalResult;\n for (let n = 1; ; n++) {\n result = await attempt(n);\n const infraFailed = result.error !== undefined || result.infraFailure;\n if (!infraFailed || n >= maxAttempts) return result;\n if (baseDelayMs > 0) {\n // Full jitter: a random wait in [0, min(cap, base * 2^(n-1))].\n const ceiling = Math.min(baseDelayMs * 2 ** (n - 1), MAX_RETRY_DELAY_MS);\n await sleep(Math.random() * ceiling);\n }\n }\n}\n\n/**\n * Whether an eval's `tags` satisfy a `--tag` filter: `true` when the filter is\n * empty/undefined (no filtering), otherwise only when the eval shares at least\n * one tag with it. An eval with no tags never matches a non-empty filter.\n */\nexport function matchesTags(\n defTags: string[] | undefined,\n filterTags: string[] | undefined,\n): boolean {\n if (!filterTags || filterTags.length === 0) return true;\n return defTags?.some((t) => filterTags.includes(t)) ?? false;\n}\n\n/** Configure the LLM judge when judge creds were supplied; otherwise a no-op. */\nasync function maybeConfigureJudge(options: RunEvalsOptions): Promise<void> {\n if (!options.judge) return;\n await configureJudge({\n client: new MlflowClient(options.judge.host, options.judge.token),\n token: options.judge.token,\n model: options.judge.model,\n });\n}\n\n/**\n * Report per-eval assessments and finish the MLflow run, when one was created.\n * Returns the run summary, or `undefined` when there was no run to finalize.\n */\nasync function finalizeMlflow(\n client: MlflowClient | undefined,\n runId: string | undefined,\n results: EvalResult[],\n options: RunEvalsOptions,\n): Promise<EvalRunSummary[\"mlflow\"]> {\n if (!client || !runId) return undefined;\n // reportToMlflow is not supposed to throw, but if it ever does the run must\n // still be finished — otherwise it hangs in RUNNING forever.\n let report: ReportOutcome = { written: 0, skipped: 0, failures: [] };\n try {\n report = await reportToMlflow(\n client,\n results,\n options.mlflow?.sqlWarehouseId,\n );\n } catch (err) {\n report.failures.push({\n traceId: \"(report)\",\n error: err instanceof Error ? err.message : String(err),\n });\n }\n const finish = await finishEvalRun(client, {\n runId,\n results,\n endTime: options.now ?? Date.now(),\n });\n return { runId, report, finish };\n}\n\n/**\n * Default max evals in flight. Each eval opens one stream to the app as the\n * same user; the server caps concurrent streams per user at 5 by default\n * (`maxConcurrentStreamsPerUser`), so 4 leaves headroom under that limit.\n */\nconst DEFAULT_CONCURRENCY = 4;\n\n/**\n * Resolve the work-pool width: `--concurrency` wins; else the lowest\n * `maxConcurrency` any *participating* agent's `evals.config.ts` requests (all\n * evals share one per-user stream budget, so the most conservative ceiling\n * governs); else {@link DEFAULT_CONCURRENCY}.\n */\nexport function deriveConcurrency(\n activeAgents: Set<string>,\n configs: Map<string, EvalConfig>,\n cliConcurrency: number | undefined,\n): number {\n const configMin = [...configs.entries()]\n .filter(([agent]) => activeAgents.has(agent))\n .map(([, c]) => c.maxConcurrency)\n .filter((n): n is number => typeof n === \"number\")\n .reduce<number | undefined>(\n (min, n) => (min === undefined ? n : Math.min(min, n)),\n undefined,\n );\n return cliConcurrency ?? configMin ?? DEFAULT_CONCURRENCY;\n}\n\n/**\n * Discover, load, and run every eval under each agent's `evals/` dir, driving\n * the agents on a running app. Never throws for an individual eval — load/run\n * failures become non-passing {@link EvalResult}s.\n */\nexport async function runEvalsInDir(\n options: RunEvalsOptions,\n): Promise<EvalRunSummary> {\n const root = options.rootDir ?? process.cwd();\n const now = options.now ?? Date.now();\n let discovered = discoverEvalFiles(root);\n\n if (options.filter) {\n const f = options.filter;\n discovered = discovered.filter(\n (d) => d.agent === f || `${d.agent}/${d.id}`.includes(f),\n );\n }\n\n const emit = options.onEvent ?? (() => {});\n\n // Load each agent's `evals.config.ts` (best-effort, per-agent): its settings\n // apply only to that agent's evals. A malformed/missing config never aborts\n // the run — the agent just falls back to CLI options and built-in defaults.\n const configs = new Map<string, EvalConfig>();\n for (const c of discoverEvalConfigs(root)) {\n try {\n const cfg = await loadEvalConfig(c.file);\n if (cfg) configs.set(c.agent, cfg);\n } catch {\n // Ignore: fall back to CLI options / defaults for this agent.\n }\n }\n\n // Load each eval def and apply the `--tag` filter up front. Tags live on the\n // def, so a tag miss removes the eval entirely (like the substring filter\n // excludes files) rather than surfacing as a result. Load failures are kept\n // so a broken file still reports as a non-passing result.\n const loaded: Array<{\n d: DiscoveredEval;\n def: EvalDefinition;\n loadError?: string;\n }> = [];\n for (const d of discovered) {\n let def: EvalDefinition;\n try {\n def = await loadEval(d.file);\n } catch (err) {\n loaded.push({\n d,\n // No def loaded; placeholder def is never run (error short-circuits).\n def: { test: () => {} },\n loadError: err instanceof Error ? err.message : String(err),\n });\n continue;\n }\n if (!matchesTags(def.tags, options.tags)) continue;\n loaded.push({ d, def });\n }\n\n // Pool width from participating agents' configs (see {@link deriveConcurrency}).\n const activeAgents = new Set(loaded.map((l) => l.d.agent));\n const concurrency = deriveConcurrency(\n activeAgents,\n configs,\n options.concurrency,\n );\n\n const total = loaded.length;\n emit({ type: \"discovered\", total });\n\n // The judge sets OPENAI_* env vars globally (autoevals reads them per call),\n // so tear them down in `finally` once the run is over — pass or throw — so\n // the bearer doesn't linger in process.env.\n await maybeConfigureJudge(options);\n try {\n // Create the MLflow evaluation run up front so each eval's trace can be\n // linked to it as it runs. One client is shared by run create/finish and\n // the per-trace assessment writes.\n let runId: string | undefined;\n let mlflowClient: MlflowClient | undefined;\n if (options.mlflow) {\n mlflowClient = new MlflowClient(\n options.mlflow.host,\n options.mlflow.token,\n );\n runId = await createEvalRun(mlflowClient, {\n experimentId: options.mlflow.experimentId,\n runName: `appkit-eval ${new Date(now).toISOString()}`,\n startTime: now,\n });\n emit({ type: \"run-created\", runId });\n }\n\n // Run each loaded (tag-filtered) eval through the bounded pool — one in-flight\n // stream per eval, so the pool respects the server's per-user stream cap (see\n // mapPool/concurrency). A dataset eval expands into per-row runs that execute\n // serially within its slot; results preserve discovery order (mapPool writes\n // by index) and row order within each file. Per-agent timeout is folded into\n // the file's options (CLI wins over `evals.config.ts`; `def.timeoutMs` still\n // overrides, applied inside runEval). `total` counts eval files, not dataset\n // rows — per-row detail is carried in the result id (`[row i/n]`).\n const perFile = await mapPool(\n loaded,\n concurrency,\n async ({ d, def, loadError }, index) => {\n const fileResults: EvalResult[] = [];\n const fileOptions: RunEvalsOptions = {\n ...options,\n timeoutMs: options.timeoutMs ?? configs.get(d.agent)?.timeoutMs,\n };\n await runDiscovered(\n d,\n def,\n loadError,\n index,\n total,\n runId,\n fileOptions,\n emit,\n fileResults,\n );\n return fileResults;\n },\n );\n const results = perFile.flat();\n\n const summary: EvalRunSummary = { results };\n const mlflow = await finalizeMlflow(mlflowClient, runId, results, options);\n if (mlflow) summary.mlflow = mlflow;\n return summary;\n } finally {\n teardownJudge();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqGA,eAAe,aAAa,MAAgC;CAC1D,MAAM,SAAS;CACf,IAAI;AACJ,KAAI;AACF,GAAC,CAAE,YAAc,MAAM,OAAO;SAGxB;AACN,QAAM,IAAI,MACR,6FACD;;AAEH,QAAO,SAAS,cAAc,KAAK,CAAC,MAAM,OAAO,KAAK,IAAI;;;;;AAM5D,eAAe,SAAS,MAAuC;CAE7D,MAAM,MAAM,mBADA,MAAM,aAAa,KAAK,CACD;AACnC,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,GAAG,KAAK,4CAA4C;AAEtE,QAAO;;;;;;;AAQT,eAAe,eAAe,MAA+C;AAE3E,QAAO,qBADK,MAAM,aAAa,KAAK,CACJ;;;;;;;AAQlC,SAAgB,qBAAqB,KAAsC;CACzE,IAAI,YAAqB;AAEzB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,WAAW,KAAK;EACvC,MAAM,OAAQ,UAAoC;AAClD,MAAI,SAAS,OACX,QAAO,OAAO,cAAc,WACvB,YACD;AAEN,cAAY;;;;;;;;;AAWhB,SAAgB,mBAAmB,KAA0C;CAC3E,IAAI,YAAqB;AACzB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,WAAW,KAAK;AACvC,MAAI,OAAQ,UAA6B,SAAS,WAChD,QAAO;AAET,cAAa,UAAoC;;;;;;;;;AAWrD,eAAe,OACb,GACA,IACA,KACA,KACA,OACA,SACqB;AACrB,KAAI;AAGF,SAAO,MAAM,eAAe,QAAQ,WAAW,SAC7C,QAAQ,KAAK;GACX;GACA,QAAQ,iBAAiB;IACvB,SAAS,QAAQ;IACjB,OAAO,IAAI,SAAS,EAAE;IACtB,SAAS,QAAQ;IACjB,aAAa;IAEb,WAAW,IAAI,aAAa,QAAQ;IACrC,CAAC;GACF,QAAQ,QAAQ;GAChB;GACA,WAAW,QAAQ;GACpB,CAAC,CACH;UACM,KAAK;AACZ,SAAO;GACL;GACA,YAAY,EAAE;GACd,QAAQ;GACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACxD;;;;;;;;;AAUL,eAAsB,mBACpB,KACA,SACkE;AAClE,KAAI,CAAC,IAAI,QAAS,QAAO,EAAE,MAAM,CAAC,OAAU,EAAE;AAC9C,KAAI,CAAC,QAAQ,mBAAmB,CAAC,QAAQ,YACvC,QAAO;EACL,MAAM,CAAC,OAAU;EACjB,OACE;EACH;AAEH,KAAI;EACF,MAAM,OAAO,MAAM,gBAAgB,QAAQ,iBAAiB;GAC1D,OAAO,IAAI,QAAQ;GACnB,aAAa,QAAQ;GACrB,OAAO,IAAI,QAAQ;GACpB,CAAC;AACF,MAAI,KAAK,WAAW,EAClB,QAAO;GACL,MAAM,CAAC,OAAU;GACjB,OAAO,YAAY,IAAI,QAAQ,MAAM;GACtC;AAEH,SAAO,EAAE,MAAM;UACR,KAAK;AACZ,SAAO;GACL,MAAM,CAAC,OAAU;GACjB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACxD;;;;;;;;;;;AAYL,eAAe,cACb,GACA,KACA,WACA,OACA,OACA,OACA,SACA,MACA,SACe;CACf,MAAM,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE;AAG3B,KAAI,WAAW;AACb,OAAK;GAAE,MAAM;GAAS;GAAI;GAAO;GAAO,CAAC;EACzC,MAAM,SAAqB;GACzB;GACA,YAAY,EAAE;GACd,QAAQ;GACR,OAAO;GACR;AACD,UAAQ,KAAK,OAAO;AACpB,OAAK;GAAE,MAAM;GAAU;GAAQ;GAAO;GAAO,CAAC;AAC9C;;CAGF,MAAM,EAAE,MAAM,OAAO,iBAAiB,MAAM,mBAAmB,KAAK,QAAQ;AAE5E,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,QACJ,IAAI,WAAW,KAAK,SAAS,IACzB,GAAG,GAAG,QAAQ,IAAI,EAAE,GAAG,KAAK,OAAO,KACnC;AACN,OAAK;GAAE,MAAM;GAAS,IAAI;GAAO;GAAO;GAAO,CAAC;EAChD,MAAM,SAAqB,eACvB;GAAE,IAAI;GAAO,YAAY,EAAE;GAAE,QAAQ;GAAO,OAAO;GAAc,GACjE,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,IAAI,OAAO,QAAQ;AACxD,UAAQ,KAAK,OAAO;AACpB,OAAK;GAAE,MAAM;GAAU;GAAQ;GAAO;GAAO,CAAC;;;;AAKlD,MAAM,8BAA8B;;AAEpC,MAAM,qBAAqB;;;;;;;;;;;;AAa3B,eAAsB,eACpB,SACA,SACA,UAAoC,EAAE,EACjB;CACrB,MAAM,cAAc,QAAQ,eAAe;CAI3C,MAAM,cAAc,KAHD,OAAO,SAAS,QAAQ,GACvC,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC,GAChC;CAEJ,IAAI;AACJ,MAAK,IAAI,IAAI,IAAK,KAAK;AACrB,WAAS,MAAM,QAAQ,EAAE;AAEzB,MAAI,EADgB,OAAO,UAAU,UAAa,OAAO,iBACrC,KAAK,YAAa,QAAO;AAC7C,MAAI,cAAc,GAAG;GAEnB,MAAM,UAAU,KAAK,IAAI,cAAc,MAAM,IAAI,IAAI,mBAAmB;AACxE,SAAMA,WAAM,KAAK,QAAQ,GAAG,QAAQ;;;;;;;;;AAU1C,SAAgB,YACd,SACA,YACS;AACT,KAAI,CAAC,cAAc,WAAW,WAAW,EAAG,QAAO;AACnD,QAAO,SAAS,MAAM,MAAM,WAAW,SAAS,EAAE,CAAC,IAAI;;;AAIzD,eAAe,oBAAoB,SAAyC;AAC1E,KAAI,CAAC,QAAQ,MAAO;AACpB,OAAM,eAAe;EACnB,QAAQ,IAAI,aAAa,QAAQ,MAAM,MAAM,QAAQ,MAAM,MAAM;EACjE,OAAO,QAAQ,MAAM;EACrB,OAAO,QAAQ,MAAM;EACtB,CAAC;;;;;;AAOJ,eAAe,eACb,QACA,OACA,SACA,SACmC;AACnC,KAAI,CAAC,UAAU,CAAC,MAAO,QAAO;CAG9B,IAAI,SAAwB;EAAE,SAAS;EAAG,SAAS;EAAG,UAAU,EAAE;EAAE;AACpE,KAAI;AACF,WAAS,MAAM,eACb,QACA,SACA,QAAQ,QAAQ,eACjB;UACM,KAAK;AACZ,SAAO,SAAS,KAAK;GACnB,SAAS;GACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACxD,CAAC;;CAEJ,MAAM,SAAS,MAAM,cAAc,QAAQ;EACzC;EACA;EACA,SAAS,QAAQ,OAAO,KAAK,KAAK;EACnC,CAAC;AACF,QAAO;EAAE;EAAO;EAAQ;EAAQ;;;;;;;AAQlC,MAAM,sBAAsB;;;;;;;AAQ5B,SAAgB,kBACd,cACA,SACA,gBACQ;CACR,MAAM,YAAY,CAAC,GAAG,QAAQ,SAAS,CAAC,CACrC,QAAQ,CAAC,WAAW,aAAa,IAAI,MAAM,CAAC,CAC5C,KAAK,GAAG,OAAO,EAAE,eAAe,CAChC,QAAQ,MAAmB,OAAO,MAAM,SAAS,CACjD,QACE,KAAK,MAAO,QAAQ,SAAY,IAAI,KAAK,IAAI,KAAK,EAAE,EACrD,OACD;AACH,QAAO,kBAAkB,aAAa;;;;;;;AAQxC,eAAsB,cACpB,SACyB;CACzB,MAAM,OAAO,QAAQ,WAAW,QAAQ,KAAK;CAC7C,MAAM,MAAM,QAAQ,OAAO,KAAK,KAAK;CACrC,IAAI,aAAa,kBAAkB,KAAK;AAExC,KAAI,QAAQ,QAAQ;EAClB,MAAM,IAAI,QAAQ;AAClB,eAAa,WAAW,QACrB,MAAM,EAAE,UAAU,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE,KAAK,SAAS,EAAE,CACzD;;CAGH,MAAM,OAAO,QAAQ,kBAAkB;CAKvC,MAAM,0BAAU,IAAI,KAAyB;AAC7C,MAAK,MAAM,KAAK,oBAAoB,KAAK,CACvC,KAAI;EACF,MAAM,MAAM,MAAM,eAAe,EAAE,KAAK;AACxC,MAAI,IAAK,SAAQ,IAAI,EAAE,OAAO,IAAI;SAC5B;CASV,MAAM,SAID,EAAE;AACP,MAAK,MAAM,KAAK,YAAY;EAC1B,IAAI;AACJ,MAAI;AACF,SAAM,MAAM,SAAS,EAAE,KAAK;WACrB,KAAK;AACZ,UAAO,KAAK;IACV;IAEA,KAAK,EAAE,YAAY,IAAI;IACvB,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IAC5D,CAAC;AACF;;AAEF,MAAI,CAAC,YAAY,IAAI,MAAM,QAAQ,KAAK,CAAE;AAC1C,SAAO,KAAK;GAAE;GAAG;GAAK,CAAC;;CAKzB,MAAM,cAAc,kBADC,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,EAAE,MAAM,CAAC,EAGxD,SACA,QAAQ,YACT;CAED,MAAM,QAAQ,OAAO;AACrB,MAAK;EAAE,MAAM;EAAc;EAAO,CAAC;AAKnC,OAAM,oBAAoB,QAAQ;AAClC,KAAI;EAIF,IAAI;EACJ,IAAI;AACJ,MAAI,QAAQ,QAAQ;AAClB,kBAAe,IAAI,aACjB,QAAQ,OAAO,MACf,QAAQ,OAAO,MAChB;AACD,WAAQ,MAAM,cAAc,cAAc;IACxC,cAAc,QAAQ,OAAO;IAC7B,SAAS,eAAe,IAAI,KAAK,IAAI,CAAC,aAAa;IACnD,WAAW;IACZ,CAAC;AACF,QAAK;IAAE,MAAM;IAAe;IAAO,CAAC;;EAkCtC,MAAM,WAvBU,MAAM,QACpB,QACA,aACA,OAAO,EAAE,GAAG,KAAK,aAAa,UAAU;GACtC,MAAM,cAA4B,EAAE;GACpC,MAAM,cAA+B;IACnC,GAAG;IACH,WAAW,QAAQ,aAAa,QAAQ,IAAI,EAAE,MAAM,EAAE;IACvD;AACD,SAAM,cACJ,GACA,KACA,WACA,OACA,OACA,OACA,aACA,MACA,YACD;AACD,UAAO;IAEV,EACuB,MAAM;EAE9B,MAAM,UAA0B,EAAE,SAAS;EAC3C,MAAM,SAAS,MAAM,eAAe,cAAc,OAAO,SAAS,QAAQ;AAC1E,MAAI,OAAQ,SAAQ,SAAS;AAC7B,SAAO;WACC;AACR,iBAAe"}
|
|
1
|
+
{"version":3,"file":"run-evals.js","names":["sleep"],"sources":["../../src/evals/run-evals.ts"],"sourcesContent":["import { setTimeout as sleep } from \"node:timers/promises\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { MlflowClient } from \"../connectors/mlflow\";\nimport type { WorkspaceClient } from \"../workspace-client\";\nimport { type DatasetRow, readEvalDataset } from \"./dataset\";\nimport {\n type DiscoveredEval,\n discoverEvalConfigs,\n discoverEvalFiles,\n findRootEvalConfig,\n} from \"./discover\";\nimport { createHttpDriver } from \"./http-driver\";\nimport { configureJudge, teardownJudge } from \"./judge\";\nimport { type ReportOutcome, reportToMlflow } from \"./mlflow-report\";\nimport { createEvalRun, type FinishOutcome, finishEvalRun } from \"./mlflow-run\";\nimport { mapPool } from \"./pool\";\nimport { runEval } from \"./run-eval\";\nimport type { EvalConfig, EvalDefinition, EvalResult } from \"./types\";\n\nexport interface RunEvalsOptions {\n /** Project root containing `server/agents/`. Defaults to `process.cwd()`. */\n rootDir?: string;\n /** Base URL of the running app to drive, e.g. `http://localhost:3000`. */\n baseUrl: string;\n /** Substring filter on `<agent>/<id>` (or an exact agent id). */\n filter?: string;\n /**\n * Only run evals whose `tags` intersect this list. Empty/undefined runs all.\n * Tags live on the eval def, so filtering happens after each file is loaded.\n */\n tags?: string[];\n /** Soft assertion failures also fail the eval. */\n strict?: boolean;\n /** Extra request headers for the driver (e.g. auth for a deployed app). */\n headers?: Record<string, string>;\n /**\n * Max evals to drive concurrently. Each eval opens one stream to the app as\n * the same user, so keep this at or below the app's\n * `maxConcurrentStreamsPerUser` (default 5) or the surplus streams hit the\n * 429 guard. Defaults to 4; clamped to `[1, total]`.\n */\n concurrency?: number;\n /**\n * When set, create a native MLflow \"Evaluation run\": each eval's trace is\n * linked to the run, pass/fail is written as feedback, and aggregate metrics\n * are logged. Requires Databricks creds + the target experiment.\n */\n mlflow?: {\n host: string;\n token: string;\n experimentId: string;\n /** SQL warehouse id for writing assessments to UC-backed (V4) traces. */\n sqlWarehouseId?: string;\n };\n /**\n * When set, enable `t.judge.*` LLM-as-judge scoring via autoevals against a\n * Databricks serving endpoint (`model`).\n */\n judge?: { host: string; token: string; model: string };\n /**\n * Workspace client used to read managed evaluation datasets (for evals that\n * declare `dataset`). Required alongside {@link warehouseId} for those evals.\n */\n workspaceClient?: WorkspaceClient;\n /** SQL warehouse id used to read managed evaluation datasets. */\n warehouseId?: string;\n /** Wall-clock timestamp (ms) for run create/finish — pass `Date.now()`. */\n now?: number;\n /**\n * Default per-eval timeout (ms): `runEval` races the whole test against it and\n * it also caps each driver turn. A per-eval `def.timeoutMs` overrides it, and\n * it wins over an agent's `evals.config.ts` `timeoutMs`. Unbounded when unset.\n */\n timeoutMs?: number;\n /**\n * Re-run an eval up to this many extra times when it fails on infrastructure —\n * a thrown error/timeout (`result.error`) or a transport/agent turn failure\n * (`result.infraFailure`). Assertion failures are never retried. Defaults to `0`.\n */\n retries?: number;\n /** Progress callback, invoked as evals are discovered, started, and finished. */\n onEvent?: (event: EvalProgress) => void;\n}\n\nexport type EvalProgress =\n | { type: \"discovered\"; total: number }\n | { type: \"run-created\"; runId: string }\n | { type: \"start\"; id: string; index: number; total: number }\n | { type: \"result\"; result: EvalResult; index: number; total: number };\n\nexport interface EvalRunSummary {\n results: EvalResult[];\n /** Present when an MLflow evaluation run was created. */\n mlflow?: { runId: string; report: ReportOutcome; finish: FinishOutcome };\n}\n\n/**\n * Import a TypeScript file with tsx's programmatic loader so eval files run\n * without a build step. The specifier is indirected so the type checker doesn't\n * try to resolve tsx's internal entry.\n */\nasync function tsImportFile(file: string): Promise<unknown> {\n const tsxApi = \"tsx/esm/api\";\n let tsImport: (specifier: string, parentURL: string) => Promise<unknown>;\n try {\n ({ tsImport } = (await import(tsxApi)) as {\n tsImport: (specifier: string, parentURL: string) => Promise<unknown>;\n });\n } catch {\n throw new Error(\n \"Running .eval.ts files requires `tsx`. Install it as a dev dependency (`pnpm add -D tsx`).\",\n );\n }\n return tsImport(pathToFileURL(file).href, import.meta.url);\n}\n\n/**\n * Load a `*.eval.ts` file and return its default-exported {@link EvalDefinition}.\n */\nasync function loadEval(file: string): Promise<EvalDefinition> {\n const mod = await tsImportFile(file);\n const def = resolveEvalDefault(mod);\n if (!def) {\n throw new Error(`${file}: must default-export defineEval({ test })`);\n }\n return def;\n}\n\n/**\n * Load an `evals.config.ts` file and return its default-exported\n * {@link EvalConfig}. A malformed/missing default surfaces as `undefined` so a\n * bad config never aborts a whole run.\n */\nasync function loadEvalConfig(file: string): Promise<EvalConfig | undefined> {\n const mod = await tsImportFile(file);\n return resolveConfigDefault(mod);\n}\n\n/**\n * Load the root `evals.config.ts` under `rootDir` (the project root), or return\n * `undefined` when there is none. This is the run-wide config carrying\n * `baseUrl`/`webServer`; the CLI reads it to resolve options and manage the\n * app-under-test lifecycle before calling {@link runEvalsInDir}.\n */\nexport async function loadRootEvalConfig(\n rootDir: string,\n): Promise<EvalConfig | undefined> {\n const file = findRootEvalConfig(rootDir);\n if (!file) return undefined;\n return loadEvalConfig(file);\n}\n\n/**\n * Unwrap the config default export across module-interop shapes (see\n * {@link resolveEvalDefault}). A config has no `.test`, so the first plain\n * object reached through the `default` chain is taken as the config.\n */\nexport function resolveConfigDefault(mod: unknown): EvalConfig | undefined {\n let candidate: unknown = mod;\n // `i < 4` bounds the chain; no visited-set needed (cf. resolveEvalDefault).\n for (let i = 0; i < 4 && candidate; i++) {\n const next = (candidate as { default?: unknown }).default;\n if (next === undefined) {\n return typeof candidate === \"object\"\n ? (candidate as EvalConfig)\n : undefined;\n }\n candidate = next;\n }\n return undefined;\n}\n\n/**\n * Unwrap the eval default export across module-interop shapes. Depending on\n * whether the eval file is treated as ESM or CJS, the value lands at\n * `mod.default` (ESM), `mod.default.default` (CJS `__esModule` double-wrap), or\n * `mod` itself. Returns the first candidate that looks like an eval.\n */\nexport function resolveEvalDefault(mod: unknown): EvalDefinition | undefined {\n let candidate: unknown = mod;\n for (let i = 0; i < 4 && candidate; i++) {\n if (typeof (candidate as EvalDefinition).test === \"function\") {\n return candidate as EvalDefinition;\n }\n candidate = (candidate as { default?: unknown }).default;\n }\n return undefined;\n}\n\n/**\n * Run one eval turn against a fresh driver. Never throws — a run failure becomes\n * a non-passing {@link EvalResult} so one bad eval can't abort the run. `row`\n * binds the current managed-dataset row (see {@link resolveDatasetRows}), or is\n * `undefined` for a plain single-run eval.\n */\nasync function runOne(\n d: DiscoveredEval,\n id: string,\n def: EvalDefinition,\n row: DatasetRow | undefined,\n runId: string | undefined,\n options: RunEvalsOptions,\n): Promise<EvalResult> {\n try {\n // Each attempt builds a fresh driver, so a retry never inherits the failed\n // attempt's thread. (runWithRetries defines what counts as retryable.)\n return await runWithRetries(options.retries ?? 0, () =>\n runEval(def, {\n id,\n driver: createHttpDriver({\n baseUrl: options.baseUrl,\n agent: def.agent ?? d.agent,\n headers: options.headers,\n mlflowRunId: runId,\n // Cap the driver turn at the eval's effective timeout (runEval's signal also aborts it).\n timeoutMs: def.timeoutMs ?? options.timeoutMs,\n }),\n strict: options.strict,\n row,\n timeoutMs: options.timeoutMs,\n }),\n );\n } catch (err) {\n return {\n id,\n assertions: [],\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Resolve the rows a (possibly dataset-driven) eval runs over. A plain eval\n * yields a single `undefined` row; a dataset eval reads its Unity Catalog table\n * via {@link readEvalDataset}. On misconfiguration or read failure, returns a\n * single `undefined` row plus an `error`, so the eval still surfaces one result.\n */\nexport async function resolveDatasetRows(\n def: EvalDefinition,\n options: RunEvalsOptions,\n): Promise<{ rows: Array<DatasetRow | undefined>; error?: string }> {\n if (!def.dataset) return { rows: [undefined] };\n if (!options.workspaceClient || !options.warehouseId) {\n return {\n rows: [undefined],\n error:\n \"dataset eval requires a workspace client and warehouse (pass --warehouse-id)\",\n };\n }\n try {\n const rows = await readEvalDataset(options.workspaceClient, {\n table: def.dataset.table,\n warehouseId: options.warehouseId,\n limit: def.dataset.limit,\n });\n if (rows.length === 0) {\n return {\n rows: [undefined],\n error: `dataset \"${def.dataset.table}\" returned no rows`,\n };\n }\n return { rows };\n } catch (err) {\n return {\n rows: [undefined],\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Run one already-loaded eval (from the `loaded` pre-pass), expanding a\n * dataset-driven eval into one run per row. Appends one result per row to\n * `results`, emitting `start`/`result` around each. Never throws: a load error\n * (carried in `loadError`) or a dataset-read failure surfaces as a non-passing\n * result. `total` counts eval files, not rows — per-row detail is carried in the\n * result id (`[row i/n]`).\n */\nasync function runDiscovered(\n d: DiscoveredEval,\n def: EvalDefinition,\n loadError: string | undefined,\n index: number,\n total: number,\n runId: string | undefined,\n options: RunEvalsOptions,\n emit: (event: EvalProgress) => void,\n results: EvalResult[],\n): Promise<void> {\n const id = `${d.agent}/${d.id}`;\n\n // Load failed in the pre-pass (def is a placeholder) → one non-passing result.\n if (loadError) {\n emit({ type: \"start\", id, index, total });\n const result: EvalResult = {\n id,\n assertions: [],\n passed: false,\n error: loadError,\n };\n results.push(result);\n emit({ type: \"result\", result, index, total });\n return;\n }\n\n const { rows, error: datasetError } = await resolveDatasetRows(def, options);\n\n for (let r = 0; r < rows.length; r++) {\n const rowId =\n def.dataset && rows.length > 1\n ? `${id} [row ${r + 1}/${rows.length}]`\n : id;\n emit({ type: \"start\", id: rowId, index, total });\n const result: EvalResult = datasetError\n ? { id: rowId, assertions: [], passed: false, error: datasetError }\n : await runOne(d, rowId, def, rows[r], runId, options);\n results.push(result);\n emit({ type: \"result\", result, index, total });\n }\n}\n\n/** Base delay (ms) before the first retry; doubled per attempt, full-jittered, capped. */\nconst DEFAULT_RETRY_BASE_DELAY_MS = 250;\n/** Ceiling for a single retry backoff wait (ms). */\nconst MAX_RETRY_DELAY_MS = 5_000;\n\n/**\n * Run `attempt` up to `1 + retries` times, stopping as soon as it returns a\n * result that is neither a thrown error / per-eval timeout (`error`) nor a\n * transport/agent turn failure (`infraFailure`). Assertion failures set\n * neither, so a failed-but-completed eval is returned on the first try and\n * never retried. Returns the last result when every attempt failed on infra.\n *\n * Between attempts it waits a full-jittered exponential backoff (infra flakes\n * are overload-correlated). `retries` is coerced to a finite non-negative\n * integer; `baseDelayMs: 0` disables the wait (tests).\n */\nexport async function runWithRetries(\n retries: number,\n attempt: (attemptNumber: number) => Promise<EvalResult>,\n options: { baseDelayMs?: number } = {},\n): Promise<EvalResult> {\n const baseDelayMs = options.baseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n const maxRetries = Number.isFinite(retries)\n ? Math.max(0, Math.floor(retries))\n : 0;\n const maxAttempts = 1 + maxRetries;\n let result: EvalResult;\n for (let n = 1; ; n++) {\n result = await attempt(n);\n const infraFailed = result.error !== undefined || result.infraFailure;\n if (!infraFailed || n >= maxAttempts) return result;\n if (baseDelayMs > 0) {\n // Full jitter: a random wait in [0, min(cap, base * 2^(n-1))].\n const ceiling = Math.min(baseDelayMs * 2 ** (n - 1), MAX_RETRY_DELAY_MS);\n await sleep(Math.random() * ceiling);\n }\n }\n}\n\n/**\n * Whether an eval's `tags` satisfy a `--tag` filter: `true` when the filter is\n * empty/undefined (no filtering), otherwise only when the eval shares at least\n * one tag with it. An eval with no tags never matches a non-empty filter.\n */\nexport function matchesTags(\n defTags: string[] | undefined,\n filterTags: string[] | undefined,\n): boolean {\n if (!filterTags || filterTags.length === 0) return true;\n return defTags?.some((t) => filterTags.includes(t)) ?? false;\n}\n\n/** Configure the LLM judge when judge creds were supplied; otherwise a no-op. */\nasync function maybeConfigureJudge(options: RunEvalsOptions): Promise<void> {\n if (!options.judge) return;\n await configureJudge({\n client: new MlflowClient(options.judge.host, options.judge.token),\n token: options.judge.token,\n model: options.judge.model,\n });\n}\n\n/**\n * Report per-eval assessments and finish the MLflow run, when one was created.\n * Returns the run summary, or `undefined` when there was no run to finalize.\n */\nasync function finalizeMlflow(\n client: MlflowClient | undefined,\n runId: string | undefined,\n results: EvalResult[],\n options: RunEvalsOptions,\n): Promise<EvalRunSummary[\"mlflow\"]> {\n if (!client || !runId) return undefined;\n // reportToMlflow is not supposed to throw, but if it ever does the run must\n // still be finished — otherwise it hangs in RUNNING forever.\n let report: ReportOutcome = { written: 0, skipped: 0, failures: [] };\n try {\n report = await reportToMlflow(\n client,\n results,\n options.mlflow?.sqlWarehouseId,\n );\n } catch (err) {\n report.failures.push({\n traceId: \"(report)\",\n error: err instanceof Error ? err.message : String(err),\n });\n }\n const finish = await finishEvalRun(client, {\n runId,\n results,\n endTime: options.now ?? Date.now(),\n });\n return { runId, report, finish };\n}\n\n/**\n * Default max evals in flight. Each eval opens one stream to the app as the\n * same user; the server caps concurrent streams per user at 5 by default\n * (`maxConcurrentStreamsPerUser`), so 4 leaves headroom under that limit.\n */\nconst DEFAULT_CONCURRENCY = 4;\n\n/**\n * Resolve the work-pool width: `--concurrency` wins; else the lowest\n * `maxConcurrency` any *participating* agent's `evals.config.ts` requests (all\n * evals share one per-user stream budget, so the most conservative ceiling\n * governs); else {@link DEFAULT_CONCURRENCY}.\n */\nexport function deriveConcurrency(\n activeAgents: Set<string>,\n configs: Map<string, EvalConfig>,\n cliConcurrency: number | undefined,\n): number {\n const configMin = [...configs.entries()]\n .filter(([agent]) => activeAgents.has(agent))\n .map(([, c]) => c.maxConcurrency)\n .filter((n): n is number => typeof n === \"number\")\n .reduce<number | undefined>(\n (min, n) => (min === undefined ? n : Math.min(min, n)),\n undefined,\n );\n return cliConcurrency ?? configMin ?? DEFAULT_CONCURRENCY;\n}\n\n/**\n * Discover, load, and run every eval under each agent's `evals/` dir, driving\n * the agents on a running app. Never throws for an individual eval — load/run\n * failures become non-passing {@link EvalResult}s.\n */\nexport async function runEvalsInDir(\n options: RunEvalsOptions,\n): Promise<EvalRunSummary> {\n const root = options.rootDir ?? process.cwd();\n const now = options.now ?? Date.now();\n let discovered = discoverEvalFiles(root);\n\n if (options.filter) {\n const f = options.filter;\n discovered = discovered.filter(\n (d) => d.agent === f || `${d.agent}/${d.id}`.includes(f),\n );\n }\n\n const emit = options.onEvent ?? (() => {});\n\n // Load each agent's `evals.config.ts` (best-effort, per-agent): its settings\n // apply only to that agent's evals. A malformed/missing config never aborts\n // the run — the agent just falls back to CLI options and built-in defaults.\n const configs = new Map<string, EvalConfig>();\n for (const c of discoverEvalConfigs(root)) {\n try {\n const cfg = await loadEvalConfig(c.file);\n if (cfg) configs.set(c.agent, cfg);\n } catch {\n // Ignore: fall back to CLI options / defaults for this agent.\n }\n }\n\n // Load each eval def and apply the `--tag` filter up front. Tags live on the\n // def, so a tag miss removes the eval entirely (like the substring filter\n // excludes files) rather than surfacing as a result. Load failures are kept\n // so a broken file still reports as a non-passing result.\n const loaded: Array<{\n d: DiscoveredEval;\n def: EvalDefinition;\n loadError?: string;\n }> = [];\n for (const d of discovered) {\n let def: EvalDefinition;\n try {\n def = await loadEval(d.file);\n } catch (err) {\n loaded.push({\n d,\n // No def loaded; placeholder def is never run (error short-circuits).\n def: { test: () => {} },\n loadError: err instanceof Error ? err.message : String(err),\n });\n continue;\n }\n if (!matchesTags(def.tags, options.tags)) continue;\n loaded.push({ d, def });\n }\n\n // Pool width from participating agents' configs (see {@link deriveConcurrency}).\n const activeAgents = new Set(loaded.map((l) => l.d.agent));\n const concurrency = deriveConcurrency(\n activeAgents,\n configs,\n options.concurrency,\n );\n\n const total = loaded.length;\n emit({ type: \"discovered\", total });\n\n // The judge sets OPENAI_* env vars globally (autoevals reads them per call),\n // so tear them down in `finally` once the run is over — pass or throw — so\n // the bearer doesn't linger in process.env.\n await maybeConfigureJudge(options);\n try {\n // Create the MLflow evaluation run up front so each eval's trace can be\n // linked to it as it runs. One client is shared by run create/finish and\n // the per-trace assessment writes.\n let runId: string | undefined;\n let mlflowClient: MlflowClient | undefined;\n if (options.mlflow) {\n mlflowClient = new MlflowClient(\n options.mlflow.host,\n options.mlflow.token,\n );\n runId = await createEvalRun(mlflowClient, {\n experimentId: options.mlflow.experimentId,\n runName: `appkit-eval ${new Date(now).toISOString()}`,\n startTime: now,\n });\n emit({ type: \"run-created\", runId });\n }\n\n // Run each loaded (tag-filtered) eval through the bounded pool — one in-flight\n // stream per eval, so the pool respects the server's per-user stream cap (see\n // mapPool/concurrency). A dataset eval expands into per-row runs that execute\n // serially within its slot; results preserve discovery order (mapPool writes\n // by index) and row order within each file. Per-agent timeout is folded into\n // the file's options (CLI wins over `evals.config.ts`; `def.timeoutMs` still\n // overrides, applied inside runEval). `total` counts eval files, not dataset\n // rows — per-row detail is carried in the result id (`[row i/n]`).\n const perFile = await mapPool(\n loaded,\n concurrency,\n async ({ d, def, loadError }, index) => {\n const fileResults: EvalResult[] = [];\n const fileOptions: RunEvalsOptions = {\n ...options,\n timeoutMs: options.timeoutMs ?? configs.get(d.agent)?.timeoutMs,\n };\n await runDiscovered(\n d,\n def,\n loadError,\n index,\n total,\n runId,\n fileOptions,\n emit,\n fileResults,\n );\n return fileResults;\n },\n );\n const results = perFile.flat();\n\n const summary: EvalRunSummary = { results };\n const mlflow = await finalizeMlflow(mlflowClient, runId, results, options);\n if (mlflow) summary.mlflow = mlflow;\n return summary;\n } finally {\n teardownJudge();\n }\n}\n"],"mappings":"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"}
|