Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@
"packages/appkit/src/plugin/index.ts",
"packages/appkit/src/plugin/to-plugin.ts",
"packages/appkit/src/plugins/agents/**",
"template/**",
"tools/**",
"docs/**",
".github/scripts/**",
"packages/appkit/src/core/agent/index.ts",
"packages/appkit/src/core/agent/tools/index.ts",
"packages/appkit/src/core/agent/from-plugin.ts",
"packages/appkit/src/core/agent/load-agents.ts",
"packages/appkit/src/connectors/mcp/index.ts",
"packages/appkit/src/plugin/to-plugin.ts"
"template/**",
"tools/**",
"docs/**",
".github/scripts/**"
],
"ignoreDependencies": ["json-schema-to-typescript"],
"ignoreBinaries": ["tarball"]
Expand Down
4 changes: 4 additions & 0 deletions packages/appkit/src/beta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ export type {
AgentDefinition,
AgentsPluginConfig,
AgentTool,
AgentTools,
AgentToolsFn,
AutoInheritToolsConfig,
BaseSystemPromptOption,
Plugins,
PluginToolkitProvider,
PromptContext,
RegisteredAgent,
ResolvedToolEntry,
Expand Down
2 changes: 1 addition & 1 deletion packages/appkit/src/connectors/mcp/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Input shape consumed by {@link AppKitMcpClient.connect}. Produced by the
* agents plugin from user-facing `HostedTool` declarations (see
* `plugins/agents/tools/hosted-tools.ts`) and accepted directly by the
* `core/agent/tools/hosted-tools.ts`) and accepted directly by the
* connector to keep its surface free of agent-layer concepts.
*/
export interface McpEndpointConfig {
Expand Down
13 changes: 3 additions & 10 deletions packages/appkit/src/core/agent/build-toolkit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AgentToolDefinition } from "shared";
import { applyToolkitOptions } from "./toolkit-options";
import type { ToolRegistry } from "./tools/define-tool";
import { toToolJSONSchema } from "./tools/json-schema";
import type { ToolkitEntry, ToolkitOptions } from "./types";
Expand All @@ -22,19 +23,11 @@ export function buildToolkitEntries(
registry: ToolRegistry,
opts: ToolkitOptions = {},
): Record<string, ToolkitEntry> {
const prefix = opts.prefix ?? `${pluginName}.`;
const only = opts.only ? new Set(opts.only) : null;
const except = opts.except ? new Set(opts.except) : null;
const rename = opts.rename ?? {};

const out: Record<string, ToolkitEntry> = {};

for (const [localName, entry] of Object.entries(registry)) {
if (only && !only.has(localName)) continue;
if (except?.has(localName)) continue;

const keyAfterPrefix = `${prefix}${localName}`;
const key = rename[localName] ?? keyAfterPrefix;
const key = applyToolkitOptions(localName, pluginName, opts);
if (key === null) continue;

const parameters = toToolJSONSchema(
entry.schema,
Expand Down
59 changes: 59 additions & 0 deletions packages/appkit/src/core/agent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Agent runtime primitives. All framework-level agent types, tool helpers,
* and the standalone runner live here. The HTTP-facing `agents()` plugin in
* `plugins/agents/` consumes these but does not own them — peer plugins
* (analytics, files, genie, lakebase) can depend on this module without
* reaching across the sibling boundary.
*/
export { buildToolkitEntries } from "./build-toolkit";
export { consumeAdapterStream } from "./consume-adapter-stream";
export { createAgent } from "./create-agent";
export {
agentIdFromMarkdownPath,
type LoadContext,
type LoadResult,
loadAgentFromFile,
loadAgentsFromDir,
parseFrontmatter,
} from "./load-agents";
export { normalizeToolResult } from "./normalize-result";
export {
type RunAgentInput,
type RunAgentResult,
runAgent,
} from "./run-agent";
export { buildBaseSystemPrompt, composeSystemPrompt } from "./system-prompt";
export { resolveToolkitFromProvider } from "./toolkit-resolver";
export {
defineTool,
executeFromRegistry,
type FunctionTool,
functionToolToDefinition,
type HostedTool,
isFunctionTool,
isHostedTool,
mcpServer,
resolveHostedTools,
type ToolConfig,
type ToolEntry,
type ToolRegistry,
tool,
toolsFromRegistry,
} from "./tools";
export {
type AgentDefinition,
type AgentsPluginConfig,
type AgentTool,
type AgentTools,
type AgentToolsFn,
type AutoInheritToolsConfig,
type BaseSystemPromptOption,
isToolkitEntry,
type Plugins,
type PluginToolkitProvider,
type PromptContext,
type RegisteredAgent,
type ResolvedToolEntry,
type ToolkitEntry,
type ToolkitOptions,
} from "./types";
67 changes: 67 additions & 0 deletions packages/appkit/src/core/agent/plugins-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Plugins, PluginToolkitProvider } from "./types";

/**
* Wrap a plain `Record<string, PluginToolkitProvider>` so that accessing an
* unknown plugin name throws with a named, actionable error instead of the
* default `TypeError: Cannot read properties of undefined (reading 'toolkit')`
* that surfaces from chained access on a missing key.
*
* The {@link Plugins} type is a `Record<string, PluginToolkitProvider>`
* without `noUncheckedIndexedAccess` workspace-wide, so unknown keys type as
* present at compile time but resolve to `undefined` at runtime. The proxy
* closes that gap with a runtime error that names the missing plugin and
* lists what's available — same shape as the agents plugin's pre-existing
* "plugin is not registered" errors.
*
* @param entries - the resolved plugin map; the proxy serves these directly
* for known keys and throws for any other string key.
* @param contextLabel - prefix included in the error message; differentiates
* the runtime context (e.g. `"runAgent: tools(plugins)"` vs
* `"AgentsPlugin: tools(plugins)"`) so users know which path to debug.
*/
export function createPluginsProxy(
entries: Record<string, PluginToolkitProvider>,
contextLabel: string,
): Plugins {
return new Proxy(entries, {
get(target, prop, receiver) {
// Symbols and well-known string accessors (e.g. `Symbol.toPrimitive`,
// `then` checked by Promise.resolve, `toJSON`) must pass through so
// host code that probes the object doesn't hit the missing-plugin
// error. Only named string-key access that misses is treated as a
// user error.
if (typeof prop !== "string") {
return Reflect.get(target, prop, receiver);
}
if (prop in target) {
return Reflect.get(target, prop, receiver);
}
// Probes from runtime tooling (e.g. utility inspection, JSON
// serialization, async-iterator detection) hit common property names
// that are clearly not plugin lookups. Pass those through silently.
if (
prop === "then" ||
prop === "toJSON" ||
prop === "constructor" ||
prop === "toString" ||
prop === "valueOf"
) {
return undefined;
}
const available = Object.keys(target).join(", ") || "(none)";
throw new Error(
`${contextLabel} referenced plugin '${prop}', but it is not registered. ` +
`Available: ${available}.`,
);
},
has(target, prop) {
return Reflect.has(target, prop);
},
ownKeys(target) {
return Reflect.ownKeys(target);
},
getOwnPropertyDescriptor(target, prop) {
return Reflect.getOwnPropertyDescriptor(target, prop);
},
}) as Plugins;
}
Loading
Loading