Files
callstack__agent-device/tsdown.config.ts
Michał Pierzchała 801734d433 feat(ai-sdk): add agent-device/ai-sdk tool set and document the MCP zero-code path (#1804)
* feat(ai-sdk): add agent-device/ai-sdk tool set and document the MCP zero-code path

Adds `createAgentDeviceTools()` under a new `agent-device/ai-sdk` subpath,
built from the same command registry the MCP server uses so both stay in
lockstep without a hand-maintained tool list. Introduces a `frameworkTier`
descriptor facet ('core' | 'extended') so the factory can default to a
curated perceive/act loop instead of handing a model dozens of tools.

`ai` is wired as an optional peer dependency, imported lazily inside the
factory rather than at module scope, so importing the subpath itself never
requires `ai` to be installed - only calling it does. The package's own
publishing gate (scripts/lib/shipped-imports.ts) is extended to recognize
peerDependencies as a valid resolution source, since this is the first
optional peer this package has shipped.

Also restructures the AI SDK doc around three tiers (zero-code via
@ai-sdk/mcp, the new typed tool set, hand-written tools) and fixes a stale
`needsApproval` reference in favor of the current `toolApproval` API.

* fix(layering): classify src/ai-sdk as a rank-4 zone

The layering guard requires every src/<folder>/ to be explicitly ranked or
unranked; the new src/ai-sdk/ subpath (added in the prior commit) was left
unclassified, failing CI's Layering Guard job. It sits at the same tier as
client/compat/daemon-server/metro/remote/sdk - a public integration surface
consuming mcp (3) and core (2), imported by nothing else in the tree.

* fix(ci): cover, exempt, and pack the new ai-sdk subpath

Fixes the remaining CI failures on the ai-sdk subpath commit:

- Coverage: src/ai-sdk/index.ts had no dedicated unit test (only manual/
  integration verification), so changed-line coverage sat at 6.9% against
  the 70% gate. Adds src/ai-sdk/__tests__/index.test.ts (core vs 'all' tool
  filtering, session/platform pinning and schema hiding, error
  normalization, toolApproval passthrough) with createCommandToolExecutor
  and createAgentDeviceClient mocked the same way command-tools.test.ts
  does, plus a dedicated missing-peer-dependency.test.ts that mocks `ai`
  itself to throw, isolated to its own file so it doesn't affect the other
  tests' use of the real, installed `ai` package. Changed-line coverage is
  now 29/29 (100%).
- Fallow Code Quality: src/ai-sdk/index.ts and examples/sdk/ai-sdk-tools.ts
  are entry points with no in-repo importer (reached only via package.json
  exports / run directly), and the new subpath's exports are unused
  internally by design - both need the same treatment src/sdk/*.ts and its
  examples already have in .fallowrc.json.
- Integration Tests: test/integration/installed-package-metro.test.ts and
  src/__tests__/package-exports.test.ts each hand-list every published
  subpath and smoke-check it from a real packed install; added ./ai-sdk to
  both so the new subpath is actually exercised, not just silently passing.

* fix(ai-sdk): hide MCP transport/config fields from the model too

createAgentDeviceTools() only removed session and mcpOutputFormat from tool
schemas. stateDir was still model-visible and reached the shared executor
as client configuration, letting a tool call redirect into a different
daemon state directory - defeating the "one pinned session" guarantee the
factory exists to provide. includeCost and responseLevel are MCP
tool-config knobs in the same category, irrelevant to this adapter.

Widens the hidden-field set to session/stateDir/mcpOutputFormat/
includeCost/responseLevel, and now strips them from the runtime input
inside execute() too (not just the schema), so the guarantee holds even if
a caller bypasses schema validation. The schema-properties filter and the
input filter now share one omitHidden() helper instead of two near-
duplicate implementations.

Addresses the P1 review comment on #1804.
2026-08-18 11:57:34 +02:00

122 lines
4.0 KiB
TypeScript

import fs from 'node:fs';
import { defineConfig } from 'tsdown';
const typeScriptPackageJsonUrl = import.meta.resolve('typescript/package.json');
const { default: getTypeScript7ExePath } = await import(
new URL('lib/getExePath.js', typeScriptPackageJsonUrl).href
);
const packageJson = JSON.parse(
fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8'),
) as { version: string };
const publicSdkChunkGroups = [
[
'sdk-contracts',
/packages[\\/]kernel[\\/]src[\\/]contracts\.d\.[cm]?ts$/,
/packages[\\/]kernel[\\/]src[\\/]contracts\.ts$/,
],
[
'sdk-errors',
/packages[\\/]kernel[\\/]src[\\/]errors\.d\.[cm]?ts$/,
/packages[\\/]kernel[\\/]src[\\/]errors\.ts$/,
],
[
'sdk-device',
/packages[\\/]kernel[\\/]src[\\/]device\.d\.[cm]?ts$/,
/packages[\\/]kernel[\\/]src[\\/]device\.ts$/,
],
[
'sdk-snapshot',
/packages[\\/]kernel[\\/]src[\\/]snapshot\.d\.[cm]?ts$/,
/packages[\\/]kernel[\\/]src[\\/]snapshot\.ts$/,
],
['sdk-io', /src[\\/]io\.d\.[cm]?ts$/, /src[\\/]io\.ts$/],
['sdk-batch', /src[\\/]batch-policy\.d\.[cm]?ts$/, /src[\\/]batch-policy\.ts$/],
['sdk-batch-runner', /src[\\/]core[\\/]batch\.d\.[cm]?ts$/, /src[\\/]core[\\/]batch\.ts$/],
['sdk-finders', /src[\\/]finders\.d\.[cm]?ts$/, /src[\\/]finders\.ts$/],
[
'sdk-android-adb',
/src[\\/]platforms[\\/]android[\\/]adb-executor\.d\.[cm]?ts$/,
/src[\\/]platforms[\\/]android[\\/]adb-executor\.ts$/,
],
[
'sdk-app-inventory',
/src[\\/]contracts[\\/]app-inventory\.d\.[cm]?ts$/,
/src[\\/]contracts[\\/]app-inventory\.ts$/,
],
[
'sdk-remote-config',
/src[\\/]remote[\\/]remote-config-schema\.d\.[cm]?ts$/,
/src[\\/]remote[\\/]remote-config-schema\.ts$/,
],
['sdk-selectors', /src[\\/]sdk[\\/]selectors\.d\.[cm]?ts$/, /src[\\/]sdk[\\/]selectors\.ts$/],
] as const;
export default defineConfig({
entry: {
index: 'src/sdk/index.ts',
io: 'src/sdk/io.ts',
artifacts: 'src/sdk/artifacts.ts',
batch: 'src/sdk/batch.ts',
metro: 'src/sdk/metro.ts',
'remote-config': 'src/sdk/remote-config.ts',
'install-source': 'src/sdk/install-source.ts',
'android-adb': 'src/sdk/android-adb.ts',
limrun: 'src/sdk/limrun.ts',
contracts: 'src/sdk/contracts.ts',
selectors: 'src/sdk/selectors.ts',
finders: 'src/sdk/finders.ts',
'ai-sdk': 'src/ai-sdk/index.ts',
'internal/bin': 'src/bin.ts',
'internal/companion-tunnel': 'src/client/companion-tunnel.ts',
'internal/daemon': 'src/daemon.ts',
'internal/png-worker': 'src/utils/png-worker.ts',
'internal/update-check-entry': 'src/utils/update-check-entry.ts',
},
deps: {
alwaysBundle: [/^@agent-device\//, 'pngjs'],
onlyBundle: ['pngjs'],
},
inputOptions: {
// A build with missing workspace links resolves nothing under `alwaysBundle` and emits the
// specifiers as externals instead. That is how 0.20.4 shipped an unresolvable
// `@agent-device/ad-script` import: rolldown warned, exited 0, and `prepack` packed the result.
// An unresolved import in a bundle that is supposed to inline its workspace is never a warning.
onLog(level, log, handler) {
if (log.code === 'UNRESOLVED_IMPORT') {
throw new Error(
`${log.message}\nRun \`pnpm install\` to restore workspace links: unresolved imports would ship as unresolvable externals.`,
);
}
handler(level, log);
},
},
format: 'esm',
platform: 'node',
target: 'es2022',
outDir: 'dist/src',
tsconfig: 'tsconfig.lib.json',
define: {
__AGENT_DEVICE_VERSION__: JSON.stringify(packageJson.version),
__OWNER_FILES__: 'false',
},
shims: true,
hash: false,
outputOptions: {
codeSplitting: {
groups: publicSdkChunkGroups.flatMap(([name, dtsTest, jsTest]) => [
{ test: dtsTest, name: `${name}.d` },
{ test: jsTest, name },
]),
},
},
outExtensions: () => ({ js: '.js', dts: '.d.ts' }),
minify: true,
dts: {
tsgo: {
path: getTypeScript7ExePath(),
},
},
});