eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
121 lines (120 loc) • 5.63 kB
TypeScript
import type { ModelMessage } from "ai";
import type { SessionAuth } from "#context/keys.js";
import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
/**
* Stream event types allowed for dynamic tool resolvers. Dispatch
* supports any event; this extract restricts the public surface until
* more events are validated.
*/
export type DynamicToolEventName = Extract<UnstampedMessageStreamEvent["type"], "session.started" | "turn.started" | "step.started">;
export declare const ALLOWED_DYNAMIC_TOOL_EVENTS: ReadonlySet<string>;
/**
* Instructions and skills are restricted to session/turn boundaries.
* Keeping their resolved context stable within a turn avoids changing the
* model input between tool-loop steps.
*/
export declare const ALLOWED_DYNAMIC_INSTRUCTION_EVENTS: ReadonlySet<string>;
export declare const ALLOWED_DYNAMIC_SKILL_EVENTS: ReadonlySet<string>;
export declare const ALLOWED_DYNAMIC_CONNECTION_EVENTS: ReadonlySet<string>;
/**
* Context passed to a dynamic resolver's event handler.
*
* Exposes read-only session identity, auth, and channel metadata. State
* is not exposed here; resolvers read it through `defineState` handles or
* the session context inside tool `execute` functions.
*/
export interface DynamicResolveContext {
readonly session: {
readonly id: string;
readonly auth: SessionAuth;
};
/** Channel metadata for the request that triggered this resolve. */
readonly channel: {
/** Channel type that produced the request (e.g. `"slack"`, `"http"`), when known. */
readonly kind?: string;
/** Channel-owned resume handle for the conversation, when the channel supplies one. */
readonly continuationToken?: string;
/** Free-form channel-specific metadata attached to the request. */
readonly metadata?: Readonly<Record<string, unknown>>;
};
/** Conversation history visible at this resolve point, oldest first. */
readonly messages: readonly ModelMessage[];
}
/**
* Base event handler map accepted by `defineDynamic`. Intentionally
* wide so it accepts both tool-returning and skill-returning handlers:
* the slot directory (tools/ vs skills/) determines the required return,
* validated at runtime by the respective resolver.
*/
export type DynamicEvents<TResult = unknown> = {
readonly [K in DynamicToolEventName]?: (event: unknown, ctx: DynamicResolveContext) => TResult | Promise<TResult>;
};
type DynamicEventMapHandler<TEvents extends DynamicEvents> = Extract<NonNullable<TEvents[keyof TEvents]>, (...args: never[]) => unknown>;
type DynamicEventMapResult<TEvents extends DynamicEvents> = Awaited<ReturnType<DynamicEventMapHandler<TEvents>>>;
/**
* Marker discriminator for a `defineDynamic({ events })` export.
*/
export declare const DYNAMIC_SENTINEL_KIND: "eve:dynamic";
/**
* Return value of `defineDynamic`: the runtime shape of a dynamic export,
* stamped with a sentinel kind the compiler/normalizer detects.
*/
export type DynamicSentinel<TResult = unknown> = {
readonly kind: typeof DYNAMIC_SENTINEL_KIND;
readonly events: DynamicEvents<TResult>;
};
/**
* Defines a dynamic resolver evaluated at runtime from stream-event
* handlers. It is shared across tools, skills, connections, and agent definitions;
* the directory it is authored in (not this function) decides what each
* handler must return and which events are honored. The file's path-derived
* slug names the single-entry case; a `Record<string, ...>` return names
* entries `slug__key`. Return `null` to contribute nothing for that event.
*
* Per-slot return shape:
* - `agent/tools/`: return a single `defineTool(...)`, a
* `Record<string, defineTool(...)>`, or `null`.
* - `agent/skills/`: return a single `defineSkill(...)`, a
* `Record<string, defineSkill(...)>`, or `null`.
* - `agent/connections/`: return one connection definition, a
* `Record<string, connection definition>`, or `null`.
* - `agent/subagents/<name>/agent.ts`: return `defineAgent(...)` to configure
* and expose the subagent, or `null` to omit it.
*
* Per-slot events: tools resolvers run at `session.started`,
* `turn.started`, and `step.started`. Skills and connection resolvers run only
* at `session.started` and `turn.started`; the runtime never invokes a
* handler keyed on `step.started` in that slot. Dynamic subagents run at
* `session.started` and `turn.started` only.
*
* ```ts
* import { defineDynamic, defineTool } from "eve/tools";
* import { z } from "zod";
*
* export default defineDynamic({
* events: {
* "session.started": async (event, ctx) => ({
* export: defineTool({
* description: "Export data",
* inputSchema: z.object({ format: z.string() }),
* async execute(input) {
* return doExport(input.format);
* },
* }),
* }),
* },
* });
* ```
*
* A single return is named after the file slug. A map names each entry by its
* bare key — there is no automatic slug prefix, so namespace keys yourself
* (e.g. `team__playbook`) when a bare name might collide. A dynamic tool/skill
* whose name matches an authored one overrides it; two dynamic resolvers
* emitting the same name is an error.
*/
export declare function defineDynamic<const TEvents extends DynamicEvents>(definition: {
readonly events: TEvents;
}): DynamicSentinel<DynamicEventMapResult<TEvents>>;
export declare function assertResolverOnlyDynamicSentinel(sentinel: DynamicSentinel, message: string): void;
export declare function isDynamicSentinel(value: unknown): value is DynamicSentinel;
export {};