diff options
| author | Adam Malczewski <[email protected]> | 2026-06-26 22:03:19 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-26 22:23:39 +0900 |
| commit | 727c98c9dae516a2070eb950410314380a20c974 (patch) | |
| tree | 52aa1022c54f11770be7e4e2a324f0a8b8b8deec /packages/kernel/src/contracts | |
| parent | e59dc11f63b1df51142259bb2c406af8c9c8c2bb (diff) | |
| download | dispatch-727c98c9dae516a2070eb950410314380a20c974.tar.gz dispatch-727c98c9dae516a2070eb950410314380a20c974.zip | |
style: switch from tabs to 2-space indentation
Diffstat (limited to 'packages/kernel/src/contracts')
| -rw-r--r-- | packages/kernel/src/contracts/auth.ts | 28 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/conversation.ts | 38 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/dispatch.ts | 4 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/events.ts | 32 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/extension.ts | 338 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/hooks.ts | 24 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/index.ts | 192 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/logging.ts | 164 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/provider.ts | 140 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/runtime.ts | 282 | ||||
| -rw-r--r-- | packages/kernel/src/contracts/tool.ts | 178 |
11 files changed, 710 insertions, 710 deletions
diff --git a/packages/kernel/src/contracts/auth.ts b/packages/kernel/src/contracts/auth.ts index 6058156..85963ba 100644 --- a/packages/kernel/src/contracts/auth.ts +++ b/packages/kernel/src/contracts/auth.ts @@ -11,9 +11,9 @@ * This is the common case for OpenAI-compatible and most provider extensions. */ export interface ApiKeyCredentials { - readonly type: "api-key"; - readonly apiKey: string; - readonly baseURL?: string; + readonly type: "api-key"; + readonly apiKey: string; + readonly baseURL?: string; } /** @@ -22,9 +22,9 @@ export interface ApiKeyCredentials { * receives a currently-valid token. */ export interface BearerTokenCredentials { - readonly type: "bearer-token"; - readonly token: string; - readonly baseURL?: string; + readonly type: "bearer-token"; + readonly token: string; + readonly baseURL?: string; } /** Union of credential shapes the kernel recognizes. */ @@ -36,13 +36,13 @@ export type Credentials = ApiKeyCredentials | BearerTokenCredentials; * directly (the concrete vault is a core extension). */ export interface AuthContract { - /** Unique identifier for this auth provider (e.g. "apikey", "claude-oauth"). */ - readonly id: string; + /** Unique identifier for this auth provider (e.g. "apikey", "claude-oauth"). */ + readonly id: string; - /** - * Resolve currently-valid credentials. May involve reading from the - * secret vault (injected via Host API) or performing a token refresh. - * Returns `null` if credentials are unavailable (e.g. not yet configured). - */ - readonly resolve: () => Promise<Credentials | null>; + /** + * Resolve currently-valid credentials. May involve reading from the + * secret vault (injected via Host API) or performing a token refresh. + * Returns `null` if credentials are unavailable (e.g. not yet configured). + */ + readonly resolve: () => Promise<Credentials | null>; } diff --git a/packages/kernel/src/contracts/conversation.ts b/packages/kernel/src/contracts/conversation.ts index b459532..f074c52 100644 --- a/packages/kernel/src/contracts/conversation.ts +++ b/packages/kernel/src/contracts/conversation.ts @@ -6,23 +6,23 @@ */ export type { - ChatMessage, - Chunk, - CompactionResult, - ConversationMeta, - ConversationStatus, - ErrorChunk, - Role, - StepId, - StepMetrics, - StoredChunk, - SystemChunk, - TextChunk, - ThinkingChunk, - ToolCallChunk, - ToolResultChunk, - TurnId, - TurnMetrics, - Workspace, - WorkspaceEntry, + ChatMessage, + Chunk, + CompactionResult, + ConversationMeta, + ConversationStatus, + ErrorChunk, + Role, + StepId, + StepMetrics, + StoredChunk, + SystemChunk, + TextChunk, + ThinkingChunk, + ToolCallChunk, + ToolResultChunk, + TurnId, + TurnMetrics, + Workspace, + WorkspaceEntry, } from "@dispatch/wire"; diff --git a/packages/kernel/src/contracts/dispatch.ts b/packages/kernel/src/contracts/dispatch.ts index c2914cf..318f14a 100644 --- a/packages/kernel/src/contracts/dispatch.ts +++ b/packages/kernel/src/contracts/dispatch.ts @@ -26,6 +26,6 @@ * (safe for any tool), yet the first tool starts during generation. */ export interface ToolDispatchPolicy { - readonly maxConcurrent: number; - readonly eager: boolean; + readonly maxConcurrent: number; + readonly eager: boolean; } diff --git a/packages/kernel/src/contracts/events.ts b/packages/kernel/src/contracts/events.ts index dca34c2..dfd7456 100644 --- a/packages/kernel/src/contracts/events.ts +++ b/packages/kernel/src/contracts/events.ts @@ -6,20 +6,20 @@ */ export type { - AgentEvent, - StatusEvent, - TurnDoneEvent, - TurnErrorEvent, - TurnInputEvent, - TurnProviderRetryEvent, - TurnReasoningDeltaEvent, - TurnSealedEvent, - TurnStartEvent, - TurnSteeringEvent, - TurnStepCompleteEvent, - TurnTextDeltaEvent, - TurnToolCallEvent, - TurnToolOutputEvent, - TurnToolResultEvent, - TurnUsageEvent, + AgentEvent, + StatusEvent, + TurnDoneEvent, + TurnErrorEvent, + TurnInputEvent, + TurnProviderRetryEvent, + TurnReasoningDeltaEvent, + TurnSealedEvent, + TurnStartEvent, + TurnSteeringEvent, + TurnStepCompleteEvent, + TurnTextDeltaEvent, + TurnToolCallEvent, + TurnToolOutputEvent, + TurnToolResultEvent, + TurnUsageEvent, } from "@dispatch/wire"; diff --git a/packages/kernel/src/contracts/extension.ts b/packages/kernel/src/contracts/extension.ts index 4d6cf07..fd1594b 100644 --- a/packages/kernel/src/contracts/extension.ts +++ b/packages/kernel/src/contracts/extension.ts @@ -9,11 +9,11 @@ import type { AuthContract } from "./auth.js"; import type { - EventHandler, - EventHookDescriptor, - FilterDescriptor, - FilterHandler, - ServiceHandle, + EventHandler, + EventHookDescriptor, + FilterDescriptor, + FilterHandler, + ServiceHandle, } from "./hooks.js"; import type { Logger } from "./logging.js"; @@ -32,16 +32,16 @@ export type TrustLevel = "bundled" | "local" | "external"; * discovery, dependency resolution, and the capability gate. */ export interface ManifestContributions { - readonly tools?: readonly string[]; - readonly providers?: readonly string[]; - readonly auth?: readonly string[]; - readonly hooks?: readonly string[]; - readonly routes?: readonly string[]; - readonly commands?: readonly string[]; - readonly services?: readonly string[]; - readonly migrations?: readonly string[]; - readonly scheduledJobs?: readonly string[]; - readonly settings?: readonly string[]; + readonly tools?: readonly string[]; + readonly providers?: readonly string[]; + readonly auth?: readonly string[]; + readonly hooks?: readonly string[]; + readonly routes?: readonly string[]; + readonly commands?: readonly string[]; + readonly services?: readonly string[]; + readonly migrations?: readonly string[]; + readonly scheduledJobs?: readonly string[]; + readonly settings?: readonly string[]; } /** @@ -50,12 +50,12 @@ export interface ManifestContributions { * declared capability for. */ export interface ManifestCapabilities { - readonly fs?: boolean; - readonly shell?: boolean; - readonly network?: boolean; - readonly secrets?: boolean; - readonly db?: boolean; - readonly spawn?: boolean; + readonly fs?: boolean; + readonly shell?: boolean; + readonly network?: boolean; + readonly secrets?: boolean; + readonly db?: boolean; + readonly spawn?: boolean; } /** @@ -64,32 +64,32 @@ export interface ManifestCapabilities { * compatibility, and enforce the capability gate. */ export interface Manifest { - /** Unique extension identifier (e.g. "tools-fs", "provider-anthropic"). */ - readonly id: string; + /** Unique extension identifier (e.g. "tools-fs", "provider-anthropic"). */ + readonly id: string; - /** Human-readable display name. */ - readonly name: string; + /** Human-readable display name. */ + readonly name: string; - /** Extension's own version (semver). */ - readonly version: string; + /** Extension's own version (semver). */ + readonly version: string; - /** Semver range of kernel API versions this extension is compatible with. */ - readonly apiVersion: string; + /** Semver range of kernel API versions this extension is compatible with. */ + readonly apiVersion: string; - /** Ids of extensions this one depends on (resolved topologically). */ - readonly dependsOn?: readonly string[]; + /** Ids of extensions this one depends on (resolved topologically). */ + readonly dependsOn?: readonly string[]; - /** Activation strategy: "eager" (on boot) or lazy event triggers. */ - readonly activation?: "eager" | string; + /** Activation strategy: "eager" (on boot) or lazy event triggers. */ + readonly activation?: "eager" | string; - /** What this extension contributes to the system. */ - readonly contributes?: ManifestContributions; + /** What this extension contributes to the system. */ + readonly contributes?: ManifestContributions; - /** Capabilities this extension requires from the host. */ - readonly capabilities?: ManifestCapabilities; + /** Capabilities this extension requires from the host. */ + readonly capabilities?: ManifestCapabilities; - /** Trust level — bundled (first-party), local (project), or external. */ - readonly trust: TrustLevel; + /** Trust level — bundled (first-party), local (project), or external. */ + readonly trust: TrustLevel; } // --- Storage interface --- @@ -100,40 +100,40 @@ export interface Manifest { * only the contract. Supports key-value and simple query operations. */ export interface StorageNamespace { - readonly get: (key: string) => Promise<string | null>; - readonly set: (key: string, value: string) => Promise<void>; - readonly delete: (key: string) => Promise<void>; - readonly has: (key: string) => Promise<boolean>; - readonly keys: (prefix?: string) => Promise<readonly string[]>; + readonly get: (key: string) => Promise<string | null>; + readonly set: (key: string, value: string) => Promise<void>; + readonly delete: (key: string) => Promise<void>; + readonly has: (key: string) => Promise<boolean>; + readonly keys: (prefix?: string) => Promise<readonly string[]>; } // --- Permission --- /** The outcome of a permission check. */ export interface PermissionDecision { - readonly allowed: boolean; - readonly reason?: string; + readonly allowed: boolean; + readonly reason?: string; } /** A request to check whether an action is permitted. */ export interface PermissionRequest { - readonly tool: string; - readonly action: string; - readonly context?: Readonly<Record<string, unknown>>; + readonly tool: string; + readonly action: string; + readonly context?: Readonly<Record<string, unknown>>; } /** Permission gate exposed through the Host API. */ export interface PermissionGate { - readonly check: (request: PermissionRequest) => Promise<PermissionDecision>; + readonly check: (request: PermissionRequest) => Promise<PermissionDecision>; } // --- Scheduler --- /** A scheduled job definition an extension can register with the host. */ export interface ScheduledJob { - readonly id: string; - readonly cron: string; - readonly execute: () => void | Promise<void>; + readonly id: string; + readonly cron: string; + readonly execute: () => void | Promise<void>; } // --- Logger is re-exported from logging.ts (structured, correlated) --- @@ -142,24 +142,24 @@ export interface ScheduledJob { /** Read-only config access for an extension's own settings namespace. */ export interface ConfigAccess { - readonly get: <T = unknown>(key: string) => T | undefined; - readonly getAll: () => Readonly<Record<string, unknown>>; + readonly get: <T = unknown>(key: string) => T | undefined; + readonly getAll: () => Readonly<Record<string, unknown>>; } // --- Secrets --- /** Capability-gated access to the secret/credential vault. */ export interface SecretsAccess { - readonly get: (key: string) => Promise<string | null>; - readonly set: (key: string, value: string) => Promise<void>; - readonly delete: (key: string) => Promise<void>; + readonly get: (key: string) => Promise<string | null>; + readonly set: (key: string, value: string) => Promise<void>; + readonly delete: (key: string) => Promise<void>; } // --- Events emitter --- /** Outward event emitter available to extensions via the Host API. */ export interface EventsEmitter { - readonly emit: (event: { readonly type: string; readonly [key: string]: unknown }) => void; + readonly emit: (event: { readonly type: string; readonly [key: string]: unknown }) => void; } // --- Host API --- @@ -175,102 +175,102 @@ export interface EventsEmitter { * module (not the kernel contracts). */ export interface HostAPI { - /** Register a tool with the kernel's tool registry. */ - readonly defineTool: (tool: ToolContract) => void; - - /** Register a provider with the kernel's provider registry. */ - readonly defineProvider: (provider: ProviderContract) => void; - - /** Register an auth provider with the kernel's auth registry. */ - readonly defineAuth: (auth: AuthContract) => void; - - /** Subscribe to an event hook. Handlers are error-isolated per call. */ - readonly on: <TPayload>( - hook: EventHookDescriptor<TPayload>, - handler: EventHandler<TPayload>, - ) => () => void; - - /** - * Emit an event hook: fire-and-forget dispatch to every `on` subscriber, - * error-isolated per handler (a thrown handler is caught + logged, never - * breaks the caller). The counterpart of `on`. - * - * This lets a core extension that OWNS a lifecycle publish typed events that - * standard extensions react to — e.g. the session-orchestrator emitting - * per-turn start/settle events a cache-warming extension subscribes to. The - * kernel owns the mechanism; the owner declares the typed `EventHookDescriptor`. - */ - readonly emit: <TPayload>(hook: EventHookDescriptor<TPayload>, payload: TPayload) => void; - - /** Add a filter to a filter hook chain. Filters are awaited in-band. */ - readonly addFilter: <TValue>( - hook: FilterDescriptor<TValue>, - fn: FilterHandler<TValue>, - ) => () => void; - - /** - * Run a filter chain: thread `value` through every filter registered for - * `hook` in priority/registration order and return the final value. The - * single-value-in/value-out counterpart to `addFilter`. Awaited in-band. - * - * Fail-open by default (a thrown filter is logged and the value passes - * through unchanged); pass `{ failClosed: true }` to make a thrown filter - * reject. With no registered filters the input value is returned as-is. - * - * This is what lets a core extension expose a contribution point (e.g. the - * session-orchestrator running a per-turn tool/context-assembly chain) that - * standard extensions plug into via `addFilter` — the kernel owns the - * mechanism, the owner declares the typed `FilterDescriptor`. - */ - readonly applyFilters: <TValue>( - hook: FilterDescriptor<TValue>, - value: TValue, - opts?: { readonly failClosed?: boolean }, - ) => Promise<TValue>; - - /** Provide an implementation for a typed service handle. */ - readonly provideService: <T>(handle: ServiceHandle<T>, impl: T) => void; - - /** Retrieve the implementation for a typed service handle. */ - readonly getService: <T>(handle: ServiceHandle<T>) => T; - - /** Get a namespaced storage interface for this extension. */ - readonly storage: (namespace: string) => StorageNamespace; - - /** Read-only access to merged config (global → project → extension). */ - readonly config: ConfigAccess; - - /** Capability-gated access to the secret/credential vault. */ - readonly secrets: SecretsAccess; - - /** Permission gate — check whether an action is allowed. */ - readonly permissions: PermissionGate; - - /** Emit outward events (transport pushes these to clients). */ - readonly events: EventsEmitter; - - /** Logger — always available, even before other extensions activate. */ - readonly logger: Logger; - - /** Read-only view of all registered providers. */ - readonly getProviders: () => ReadonlyMap<string, ProviderContract>; - - /** Read-only view of all registered tools. */ - readonly getTools: () => ReadonlyMap<string, ToolContract>; - - /** Read-only view of all registered auth providers. */ - readonly getAuthProviders: () => ReadonlyMap<string, AuthContract>; - - /** Look up a single auth provider by id. */ - readonly getAuthProvider: (id: string) => AuthContract | undefined; - - /** Read-only view of all activated extensions' manifests (what is loaded). */ - readonly getExtensions: () => readonly Manifest[]; - - /** Register a scheduled job with the host's scheduler. */ - readonly scheduler: { - readonly register: (job: ScheduledJob) => void; - }; + /** Register a tool with the kernel's tool registry. */ + readonly defineTool: (tool: ToolContract) => void; + + /** Register a provider with the kernel's provider registry. */ + readonly defineProvider: (provider: ProviderContract) => void; + + /** Register an auth provider with the kernel's auth registry. */ + readonly defineAuth: (auth: AuthContract) => void; + + /** Subscribe to an event hook. Handlers are error-isolated per call. */ + readonly on: <TPayload>( + hook: EventHookDescriptor<TPayload>, + handler: EventHandler<TPayload>, + ) => () => void; + + /** + * Emit an event hook: fire-and-forget dispatch to every `on` subscriber, + * error-isolated per handler (a thrown handler is caught + logged, never + * breaks the caller). The counterpart of `on`. + * + * This lets a core extension that OWNS a lifecycle publish typed events that + * standard extensions react to — e.g. the session-orchestrator emitting + * per-turn start/settle events a cache-warming extension subscribes to. The + * kernel owns the mechanism; the owner declares the typed `EventHookDescriptor`. + */ + readonly emit: <TPayload>(hook: EventHookDescriptor<TPayload>, payload: TPayload) => void; + + /** Add a filter to a filter hook chain. Filters are awaited in-band. */ + readonly addFilter: <TValue>( + hook: FilterDescriptor<TValue>, + fn: FilterHandler<TValue>, + ) => () => void; + + /** + * Run a filter chain: thread `value` through every filter registered for + * `hook` in priority/registration order and return the final value. The + * single-value-in/value-out counterpart to `addFilter`. Awaited in-band. + * + * Fail-open by default (a thrown filter is logged and the value passes + * through unchanged); pass `{ failClosed: true }` to make a thrown filter + * reject. With no registered filters the input value is returned as-is. + * + * This is what lets a core extension expose a contribution point (e.g. the + * session-orchestrator running a per-turn tool/context-assembly chain) that + * standard extensions plug into via `addFilter` — the kernel owns the + * mechanism, the owner declares the typed `FilterDescriptor`. + */ + readonly applyFilters: <TValue>( + hook: FilterDescriptor<TValue>, + value: TValue, + opts?: { readonly failClosed?: boolean }, + ) => Promise<TValue>; + + /** Provide an implementation for a typed service handle. */ + readonly provideService: <T>(handle: ServiceHandle<T>, impl: T) => void; + + /** Retrieve the implementation for a typed service handle. */ + readonly getService: <T>(handle: ServiceHandle<T>) => T; + + /** Get a namespaced storage interface for this extension. */ + readonly storage: (namespace: string) => StorageNamespace; + + /** Read-only access to merged config (global → project → extension). */ + readonly config: ConfigAccess; + + /** Capability-gated access to the secret/credential vault. */ + readonly secrets: SecretsAccess; + + /** Permission gate — check whether an action is allowed. */ + readonly permissions: PermissionGate; + + /** Emit outward events (transport pushes these to clients). */ + readonly events: EventsEmitter; + + /** Logger — always available, even before other extensions activate. */ + readonly logger: Logger; + + /** Read-only view of all registered providers. */ + readonly getProviders: () => ReadonlyMap<string, ProviderContract>; + + /** Read-only view of all registered tools. */ + readonly getTools: () => ReadonlyMap<string, ToolContract>; + + /** Read-only view of all registered auth providers. */ + readonly getAuthProviders: () => ReadonlyMap<string, AuthContract>; + + /** Look up a single auth provider by id. */ + readonly getAuthProvider: (id: string) => AuthContract | undefined; + + /** Read-only view of all activated extensions' manifests (what is loaded). */ + readonly getExtensions: () => readonly Manifest[]; + + /** Register a scheduled job with the host's scheduler. */ + readonly scheduler: { + readonly register: (job: ScheduledJob) => void; + }; } // --- Extension lifecycle --- @@ -281,18 +281,18 @@ export interface HostAPI { * `deactivate` is optional and called on shutdown or reload. */ export interface Extension { - /** The extension's manifest — its declaration of identity and capabilities. */ - readonly manifest: Manifest; - - /** - * Called by the host to activate the extension. The extension registers - * its contributions (tools, providers, hooks, services) through the Host API. - */ - readonly activate: (host: HostAPI) => void | Promise<void>; - - /** - * Optional cleanup called when the extension is deactivated (shutdown, - * reload, or auto-disable). Should dispose resources the extension owns. - */ - readonly deactivate?: () => void | Promise<void>; + /** The extension's manifest — its declaration of identity and capabilities. */ + readonly manifest: Manifest; + + /** + * Called by the host to activate the extension. The extension registers + * its contributions (tools, providers, hooks, services) through the Host API. + */ + readonly activate: (host: HostAPI) => void | Promise<void>; + + /** + * Optional cleanup called when the extension is deactivated (shutdown, + * reload, or auto-disable). Should dispose resources the extension owns. + */ + readonly deactivate?: () => void | Promise<void>; } diff --git a/packages/kernel/src/contracts/hooks.ts b/packages/kernel/src/contracts/hooks.ts index eb94465..8f2bd1f 100644 --- a/packages/kernel/src/contracts/hooks.ts +++ b/packages/kernel/src/contracts/hooks.ts @@ -21,9 +21,9 @@ * (a thrown handler is caught and logged — it never breaks the turn). */ export interface EventHookDescriptor<TPayload> { - readonly kind: "event"; - readonly id: string; - readonly _payload?: TPayload; + readonly kind: "event"; + readonly id: string; + readonly _payload?: TPayload; } /** @@ -35,9 +35,9 @@ export interface EventHookDescriptor<TPayload> { * the owner may mark a chain fail-closed. */ export interface FilterDescriptor<TValue> { - readonly kind: "filter"; - readonly id: string; - readonly _value?: TValue; + readonly kind: "filter"; + readonly id: string; + readonly _value?: TValue; } /** Union of hook descriptor kinds the kernel mechanism supports. */ @@ -49,9 +49,9 @@ export type HookDescriptor<TPayload> = EventHookDescriptor<TPayload> | FilterDes * "which of N handlers wins?" ambiguity; a service has exactly one provider. */ export interface ServiceHandle<T> { - readonly kind: "service"; - readonly id: string; - readonly _type?: T; + readonly kind: "service"; + readonly id: string; + readonly _type?: T; } /** @@ -61,7 +61,7 @@ export interface ServiceHandle<T> { * @param id - Namespaced hook id in `owner/name` form (e.g. "kernel/turn.sealed"). */ export function defineEventHook<TPayload>(id: string): EventHookDescriptor<TPayload> { - return { kind: "event", id }; + return { kind: "event", id }; } /** @@ -71,7 +71,7 @@ export function defineEventHook<TPayload>(id: string): EventHookDescriptor<TPayl * @param id - Namespaced filter id in `owner/name` form. */ export function defineFilter<TValue>(id: string): FilterDescriptor<TValue> { - return { kind: "filter", id }; + return { kind: "filter", id }; } /** @@ -82,7 +82,7 @@ export function defineFilter<TValue>(id: string): FilterDescriptor<TValue> { * @param id - Namespaced service id in `owner/name` form. */ export function defineService<T>(id: string): ServiceHandle<T> { - return { kind: "service", id }; + return { kind: "service", id }; } /** Handler function for an event hook subscription. */ diff --git a/packages/kernel/src/contracts/index.ts b/packages/kernel/src/contracts/index.ts index f3e5bca..09e0a56 100644 --- a/packages/kernel/src/contracts/index.ts +++ b/packages/kernel/src/contracts/index.ts @@ -7,118 +7,118 @@ */ export type { - ApiKeyCredentials, - AuthContract, - BearerTokenCredentials, - Credentials, + ApiKeyCredentials, + AuthContract, + BearerTokenCredentials, + Credentials, } from "./auth.js"; export type { - ChatMessage, - Chunk, - CompactionResult, - ConversationMeta, - ConversationStatus, - ErrorChunk, - Role, - StepId, - StepMetrics, - StoredChunk, - SystemChunk, - TextChunk, - ThinkingChunk, - ToolCallChunk, - ToolResultChunk, - TurnId, - TurnMetrics, - Workspace, - WorkspaceEntry, + ChatMessage, + Chunk, + CompactionResult, + ConversationMeta, + ConversationStatus, + ErrorChunk, + Role, + StepId, + StepMetrics, + StoredChunk, + SystemChunk, + TextChunk, + ThinkingChunk, + ToolCallChunk, + ToolResultChunk, + TurnId, + TurnMetrics, + Workspace, + WorkspaceEntry, } from "./conversation.js"; export type { ToolDispatchPolicy } from "./dispatch.js"; export type { - AgentEvent, - StatusEvent, - TurnDoneEvent, - TurnErrorEvent, - TurnInputEvent, - TurnProviderRetryEvent, - TurnReasoningDeltaEvent, - TurnSealedEvent, - TurnStartEvent, - TurnSteeringEvent, - TurnStepCompleteEvent, - TurnTextDeltaEvent, - TurnToolCallEvent, - TurnToolOutputEvent, - TurnToolResultEvent, - TurnUsageEvent, + AgentEvent, + StatusEvent, + TurnDoneEvent, + TurnErrorEvent, + TurnInputEvent, + TurnProviderRetryEvent, + TurnReasoningDeltaEvent, + TurnSealedEvent, + TurnStartEvent, + TurnSteeringEvent, + TurnStepCompleteEvent, + TurnTextDeltaEvent, + TurnToolCallEvent, + TurnToolOutputEvent, + TurnToolResultEvent, + TurnUsageEvent, } from "./events.js"; export type { - ConfigAccess, - EventsEmitter, - Extension, - HostAPI, - Manifest, - ManifestCapabilities, - ManifestContributions, - PermissionDecision, - PermissionGate, - PermissionRequest, - ScheduledJob, - SecretsAccess, - StorageNamespace, - TrustLevel, + ConfigAccess, + EventsEmitter, + Extension, + HostAPI, + Manifest, + ManifestCapabilities, + ManifestContributions, + PermissionDecision, + PermissionGate, + PermissionRequest, + ScheduledJob, + SecretsAccess, + StorageNamespace, + TrustLevel, } from "./extension.js"; export type { - EventHandler, - EventHookDescriptor, - FilterDescriptor, - FilterHandler, - HookDescriptor, - ServiceHandle, + EventHandler, + EventHookDescriptor, + FilterDescriptor, + FilterHandler, + HookDescriptor, + ServiceHandle, } from "./hooks.js"; export { defineEventHook, defineFilter, defineService } from "./hooks.js"; export type { - Attributes, - ErrorAttributes, - Level, - LogContext, - LogDeps, - Logger, - LogLineRecord, - LogRecord, - LogSink, - Span, - SpanCloseRecord, - SpanLink, - SpanOpenRecord, - SpanStatus, + Attributes, + ErrorAttributes, + Level, + LogContext, + LogDeps, + Logger, + LogLineRecord, + LogRecord, + LogSink, + Span, + SpanCloseRecord, + SpanLink, + SpanOpenRecord, + SpanStatus, } from "./logging.js"; export type { - FinishEvent, - ModelInfo, - ProviderContract, - ProviderErrorEvent, - ProviderEvent, - ProviderStreamOptions, - ProviderToolCallEvent, - ReasoningDeltaEvent, - ReasoningEffort, - TextDeltaEvent, - Usage, - UsageEvent, + FinishEvent, + ModelInfo, + ProviderContract, + ProviderErrorEvent, + ProviderEvent, + ProviderStreamOptions, + ProviderToolCallEvent, + ReasoningDeltaEvent, + ReasoningEffort, + TextDeltaEvent, + Usage, + UsageEvent, } from "./provider.js"; export type { - EventEmitter, - FinishReason, - RetryStrategy, - RunTurnInput, - RunTurnResult, + EventEmitter, + FinishReason, + RetryStrategy, + RunTurnInput, + RunTurnResult, } from "./runtime.js"; export type { - JsonSchemaProperty, - ToolCall, - ToolContract, - ToolExecuteContext, - ToolParameterSchema, - ToolResult, + JsonSchemaProperty, + ToolCall, + ToolContract, + ToolExecuteContext, + ToolParameterSchema, + ToolResult, } from "./tool.js"; diff --git a/packages/kernel/src/contracts/logging.ts b/packages/kernel/src/contracts/logging.ts index a8bab7c..d121777 100644 --- a/packages/kernel/src/contracts/logging.ts +++ b/packages/kernel/src/contracts/logging.ts @@ -27,12 +27,12 @@ export type Attributes = Readonly<Record<string, string | number | boolean | nul /** Correlation context carried on every log record and span. */ export interface LogContext { - /** Auto-stamped by host from manifest.id (D6) — never caller-supplied. */ - readonly extensionId: string; - readonly conversationId?: string; - readonly turnId?: string; - readonly spanId?: string; - readonly parentSpanId?: string; + /** Auto-stamped by host from manifest.id (D6) — never caller-supplied. */ + readonly extensionId: string; + readonly conversationId?: string; + readonly turnId?: string; + readonly spanId?: string; + readonly parentSpanId?: string; } // --- Span --- @@ -43,27 +43,27 @@ export interface LogContext { * crashed turn is reconstructable from the journal (D3). */ export interface Span { - readonly id: string; - /** Pre-bound Logger scoped to this span's correlation. */ - readonly log: Logger; - /** Add or overwrite attributes on this span. */ - readonly setAttributes: (attrs: Attributes) => void; - /** Record a causal link to another span (D4 cross-feature causality). */ - readonly addLink: ( - target: { readonly spanId: string; readonly turnId?: string }, - reason?: string, - ) => void; - /** Open a child span nested under this one. */ - readonly child: (name: string, attrs?: Attributes, body?: string) => Span; - /** - * Close this span. Records duration + status. Optionally records an - * error, additional attributes, and/or a body payload. - */ - readonly end: (outcome?: { - readonly err?: unknown; - readonly attrs?: Attributes; - readonly body?: string; - }) => void; + readonly id: string; + /** Pre-bound Logger scoped to this span's correlation. */ + readonly log: Logger; + /** Add or overwrite attributes on this span. */ + readonly setAttributes: (attrs: Attributes) => void; + /** Record a causal link to another span (D4 cross-feature causality). */ + readonly addLink: ( + target: { readonly spanId: string; readonly turnId?: string }, + reason?: string, + ) => void; + /** Open a child span nested under this one. */ + readonly child: (name: string, attrs?: Attributes, body?: string) => Span; + /** + * Close this span. Records duration + status. Optionally records an + * error, additional attributes, and/or a body payload. + */ + readonly end: (outcome?: { + readonly err?: unknown; + readonly attrs?: Attributes; + readonly body?: string; + }) => void; } // --- Logger --- @@ -75,17 +75,17 @@ export interface Span { * `info("msg")` must still compile — attrs is optional (backward compat). */ export interface Logger { - readonly debug: (msg: string, attrs?: Attributes) => void; - readonly info: (msg: string, attrs?: Attributes) => void; - readonly warn: (msg: string, attrs?: Attributes) => void; - readonly error: (msg: string, attrs?: ErrorAttributes) => void; - /** - * Create a child logger with additional correlation context. - * Explicit values passed down (P3 — no ambient state). - */ - readonly child: (ctx: Partial<LogContext> & { readonly attrs?: Attributes }) => Logger; - /** Open a new span. Emits a `span-open` record immediately (D3). */ - readonly span: (name: string, attrs?: Attributes, body?: string) => Span; + readonly debug: (msg: string, attrs?: Attributes) => void; + readonly info: (msg: string, attrs?: Attributes) => void; + readonly warn: (msg: string, attrs?: Attributes) => void; + readonly error: (msg: string, attrs?: ErrorAttributes) => void; + /** + * Create a child logger with additional correlation context. + * Explicit values passed down (P3 — no ambient state). + */ + readonly child: (ctx: Partial<LogContext> & { readonly attrs?: Attributes }) => Logger; + /** Open a new span. Emits a `span-open` record immediately (D3). */ + readonly span: (name: string, attrs?: Attributes, body?: string) => Span; } /** @@ -94,8 +94,8 @@ export interface Logger { * pass `error("msg", { err })` directly. */ export interface ErrorAttributes { - readonly err?: unknown; - readonly [key: string]: unknown; + readonly err?: unknown; + readonly [key: string]: unknown; } // --- LogRecord (discriminated union) --- @@ -109,9 +109,9 @@ export type SpanStatus = "ok" | "error"; * A link to another span, recorded at a handoff moment (D4). */ export interface SpanLink { - readonly spanId: string; - readonly turnId?: string; - readonly reason?: string; + readonly spanId: string; + readonly turnId?: string; + readonly reason?: string; } /** @@ -126,50 +126,50 @@ export type LogRecord = LogLineRecord | SpanOpenRecord | SpanCloseRecord; /** A structured log line (debug/info/warn/error). */ export interface LogLineRecord { - readonly kind: "log"; - readonly level: Level; - readonly msg: string; - readonly timestamp: number; - readonly extensionId: string; - readonly conversationId?: string; - readonly turnId?: string; - readonly spanId?: string; - readonly parentSpanId?: string; - readonly attributes?: Attributes; - /** Optional large verbatim payload (store-fat, serve-thin). */ - readonly body?: string; + readonly kind: "log"; + readonly level: Level; + readonly msg: string; + readonly timestamp: number; + readonly extensionId: string; + readonly conversationId?: string; + readonly turnId?: string; + readonly spanId?: string; + readonly parentSpanId?: string; + readonly attributes?: Attributes; + /** Optional large verbatim payload (store-fat, serve-thin). */ + readonly body?: string; } /** Emitted when a span is opened (at `logger.span(name)`). */ export interface SpanOpenRecord { - readonly kind: "span-open"; - readonly spanId: string; - readonly name: string; - readonly timestamp: number; - readonly extensionId: string; - readonly conversationId?: string; - readonly turnId?: string; - readonly parentSpanId?: string; - readonly attributes?: Attributes; - readonly links?: readonly SpanLink[]; - readonly body?: string; + readonly kind: "span-open"; + readonly spanId: string; + readonly name: string; + readonly timestamp: number; + readonly extensionId: string; + readonly conversationId?: string; + readonly turnId?: string; + readonly parentSpanId?: string; + readonly attributes?: Attributes; + readonly links?: readonly SpanLink[]; + readonly body?: string; } /** Emitted when a span is closed (at `span.end()`). Carries duration + status. */ export interface SpanCloseRecord { - readonly kind: "span-close"; - readonly spanId: string; - readonly name: string; - readonly timestamp: number; - readonly durationMs: number; - readonly status: SpanStatus; - readonly extensionId: string; - readonly conversationId?: string; - readonly turnId?: string; - readonly parentSpanId?: string; - readonly attributes?: Attributes; - readonly links?: readonly SpanLink[]; - readonly body?: string; + readonly kind: "span-close"; + readonly spanId: string; + readonly name: string; + readonly timestamp: number; + readonly durationMs: number; + readonly status: SpanStatus; + readonly extensionId: string; + readonly conversationId?: string; + readonly turnId?: string; + readonly parentSpanId?: string; + readonly attributes?: Attributes; + readonly links?: readonly SpanLink[]; + readonly body?: string; } // --- LogSink --- @@ -179,13 +179,13 @@ export interface SpanCloseRecord { * a concrete implementation. Kernel never lets sink errors escape (D7). */ export interface LogSink { - readonly emit: (record: LogRecord) => void; + readonly emit: (record: LogRecord) => void; } // --- Deterministic helpers (injected for testability) --- /** Clock + id generator injected into the logger factory. */ export interface LogDeps { - readonly now: () => number; - readonly newId: () => string; + readonly now: () => number; + readonly newId: () => string; } diff --git a/packages/kernel/src/contracts/provider.ts b/packages/kernel/src/contracts/provider.ts index 52d853b..b6dc8ca 100644 --- a/packages/kernel/src/contracts/provider.ts +++ b/packages/kernel/src/contracts/provider.ts @@ -19,23 +19,23 @@ export type { ReasoningEffort, Usage } from "@dispatch/wire"; * Discriminated by `type`. */ export type ProviderEvent = - | TextDeltaEvent - | ReasoningDeltaEvent - | ProviderToolCallEvent - | UsageEvent - | FinishEvent - | ProviderErrorEvent; + | TextDeltaEvent + | ReasoningDeltaEvent + | ProviderToolCallEvent + | UsageEvent + | FinishEvent + | ProviderErrorEvent; /** Incremental text content from the model. */ export interface TextDeltaEvent { - readonly type: "text-delta"; - readonly delta: string; + readonly type: "text-delta"; + readonly delta: string; } /** Incremental reasoning / thinking content from the model. */ export interface ReasoningDeltaEvent { - readonly type: "reasoning-delta"; - readonly delta: string; + readonly type: "reasoning-delta"; + readonly delta: string; } /** @@ -43,16 +43,16 @@ export interface ReasoningDeltaEvent { * dispatch to the matching `ToolContract`. */ export interface ProviderToolCallEvent { - readonly type: "tool-call"; - readonly toolCallId: string; - readonly toolName: string; - readonly input: unknown; + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly input: unknown; } /** Token usage report, typically emitted at step end. */ export interface UsageEvent { - readonly type: "usage"; - readonly usage: Usage; + readonly type: "usage"; + readonly usage: Usage; } /** @@ -60,16 +60,16 @@ export interface UsageEvent { * generating (e.g. "stop", "tool-calls", "length", "content-filter"). */ export interface FinishEvent { - readonly type: "finish"; - readonly reason: string; + readonly type: "finish"; + readonly reason: string; } /** An error from the provider (network, rate-limit, model error, etc.). */ export interface ProviderErrorEvent { - readonly type: "error"; - readonly message: string; - readonly code?: string; - readonly retryable?: boolean; + readonly type: "error"; + readonly message: string; + readonly code?: string; + readonly retryable?: boolean; } /** @@ -77,30 +77,30 @@ export interface ProviderErrorEvent { * Kept minimal — providers may ignore fields they don't support. */ export interface ProviderStreamOptions { - /** Model identifier to use. */ - readonly model?: string; - /** Sampling temperature override. */ - readonly temperature?: number; - /** Maximum output tokens override. */ - readonly maxTokens?: number; - /** System prompt to prepend. */ - readonly systemPrompt?: string; - /** - * Reasoning-effort level for this request (already RESOLVED by the caller — - * the session-orchestrator applies the request → conversation → `"high"` - * default chain, so a provider receiving `undefined` may treat it as "no - * preference"). The provider maps the level to its native thinking knob in - * its own code; providers without such a knob ignore it. - */ - readonly reasoningEffort?: ReasoningEffort; - /** - * Correlated logger for this turn's step (Phase A logging ABI). When present, - * the provider should open a child `provider.request` span and capture the - * verbatim post-transform request + raw response/error there, self-redacting - * secrets in its own code. Optional so non-instrumented callers/tests still - * compile (the provider falls back to no capture). - */ - readonly logger?: Logger; + /** Model identifier to use. */ + readonly model?: string; + /** Sampling temperature override. */ + readonly temperature?: number; + /** Maximum output tokens override. */ + readonly maxTokens?: number; + /** System prompt to prepend. */ + readonly systemPrompt?: string; + /** + * Reasoning-effort level for this request (already RESOLVED by the caller — + * the session-orchestrator applies the request → conversation → `"high"` + * default chain, so a provider receiving `undefined` may treat it as "no + * preference"). The provider maps the level to its native thinking knob in + * its own code; providers without such a knob ignore it. + */ + readonly reasoningEffort?: ReasoningEffort; + /** + * Correlated logger for this turn's step (Phase A logging ABI). When present, + * the provider should open a child `provider.request` span and capture the + * verbatim post-transform request + raw response/error there, self-redacting + * secrets in its own code. Optional so non-instrumented callers/tests still + * compile (the provider falls back to no capture). + */ + readonly logger?: Logger; } /** @@ -110,10 +110,10 @@ export interface ProviderStreamOptions { * is the wire model identifier; `displayName` is an optional human label. */ export interface ModelInfo { - readonly id: string; - readonly displayName?: string; - /** The model's max context window in tokens (e.g. 200000). Optional — providers that don't report it leave it undefined. */ - readonly contextWindow?: number; + readonly id: string; + readonly displayName?: string; + /** The model's max context window in tokens (e.g. 200000). Optional — providers that don't report it leave it undefined. */ + readonly contextWindow?: number; } /** @@ -122,26 +122,26 @@ export interface ModelInfo { * concrete LLM API is behind it. */ export interface ProviderContract { - /** Unique identifier for this provider (e.g. "anthropic", "openai-compat"). */ - readonly id: string; + /** Unique identifier for this provider (e.g. "anthropic", "openai-compat"). */ + readonly id: string; - /** - * Stream a response for the given messages and available tools. - * The provider yields `ProviderEvent`s incrementally; the kernel drives - * tool dispatch and chunk assembly from them. - */ - readonly stream: ( - messages: readonly ChatMessage[], - tools: readonly ToolContract[], - opts?: ProviderStreamOptions, - ) => AsyncIterable<ProviderEvent>; + /** + * Stream a response for the given messages and available tools. + * The provider yields `ProviderEvent`s incrementally; the kernel drives + * tool dispatch and chunk assembly from them. + */ + readonly stream: ( + messages: readonly ChatMessage[], + tools: readonly ToolContract[], + opts?: ProviderStreamOptions, + ) => AsyncIterable<ProviderEvent>; - /** - * Enumerate the models this provider can serve, each in its own way (e.g. an - * OpenAI-compatible provider GETs `/v1/models`). Optional: a provider that - * cannot (or chooses not to) enumerate omits it, and a catalog simply lists - * none for it. A future multi-credential design may pass per-credential - * credentials in; today the provider uses the key it resolved at activate. - */ - readonly listModels?: () => Promise<readonly ModelInfo[]>; + /** + * Enumerate the models this provider can serve, each in its own way (e.g. an + * OpenAI-compatible provider GETs `/v1/models`). Optional: a provider that + * cannot (or chooses not to) enumerate omits it, and a catalog simply lists + * none for it. A future multi-credential design may pass per-credential + * credentials in; today the provider uses the key it resolved at activate. + */ + readonly listModels?: () => Promise<readonly ModelInfo[]>; } diff --git a/packages/kernel/src/contracts/runtime.ts b/packages/kernel/src/contracts/runtime.ts index dc74c84..71d2211 100644 --- a/packages/kernel/src/contracts/runtime.ts +++ b/packages/kernel/src/contracts/runtime.ts @@ -26,14 +26,14 @@ export type EventEmitter = (event: AgentEvent) => void; * passed through verbatim without losing autocomplete on the known values. */ export type FinishReason = - | "stop" - | "tool-calls" - | "length" - | "content-filter" - | "max-steps" - | "error" - | "aborted" - | (string & {}); + | "stop" + | "tool-calls" + | "length" + | "content-filter" + | "max-steps" + | "error" + | "aborted" + | (string & {}); /** * Input to `runTurn` — everything the kernel needs to execute one turn. @@ -41,121 +41,121 @@ export type FinishReason = * the kernel never reads config or resolves providers/tools itself. */ export interface RunTurnInput { - /** The resolved provider to stream from. */ - readonly provider: ProviderContract; - - /** The conversation history (including system prompt as first message). */ - readonly messages: readonly ChatMessage[]; - - /** The tool set available for this turn (may be empty). */ - readonly tools: readonly ToolContract[]; - - /** How to dispatch tool calls within each step. */ - readonly dispatch: ToolDispatchPolicy; - - /** The emitter the kernel calls for each outward event. */ - readonly emit: EventEmitter; - - /** - * Identifiers used to attribute every emitted `AgentEvent`. The kernel does - * not generate these — the session-orchestrator owns turn/conversation identity - * and passes them in, so events are traceable to their conversation. - */ - readonly conversationId: string; - readonly turnId: string; - - /** - * Optional per-turn provider options (model, temperature, maxTokens, - * systemPrompt). The orchestrator resolves these; the kernel forwards them - * verbatim to `provider.stream` and never interprets them. A provider may - * also be pre-configured at construction and ignore these. - */ - readonly providerOpts?: ProviderStreamOptions; - - /** Cancellation signal for the entire turn. */ - readonly signal?: AbortSignal; - - /** - * Working directory for this turn's tool execution. The kernel does NOT - * interpret it — it forwards the value verbatim to each `ToolExecuteContext.cwd` - * so tools resolve/contain paths against it. It never enters the model prompt, - * so it does not affect prompt caching. When omitted, tools fall back to their - * own configured/default workdir. - */ - readonly cwd?: string; - - /** - * The computer to execute this turn's tools on (SSH support). Omitted/undefined - * = LOCAL (today's behavior). When set, it is an SSH config alias; the kernel - * does NOT interpret it — it forwards the value verbatim to each - * `ToolExecuteContext.computerId`, exactly like `cwd`. It never enters the - * model prompt, so it does not affect prompt caching. Tools resolve their - * execution backend (local vs. remote) from this; see - * `notes/ssh-support-plan.md`. - */ - readonly computerId?: string; - - /** - * Optional logger for structured span instrumentation. The runtime opens - * turn/step/tool-call spans using this logger. If omitted, no spans are - * emitted (backward-compatible with callers that don't yet pass a logger). - */ - readonly logger?: Logger; - - /** - * Optional monotonic-ish clock (milliseconds) for emitting wall-clock timing - * on outward events: per-step `step-complete` (ttft/decode/genTotal), tool - * execution `durationMs` on `tool-result`, and turn `durationMs` on `done`. - * Injected (not ambient) so the runtime stays pure and deterministic in tests. - * If omitted, the runtime emits no such timing (the optional fields stay - * absent) — backward-compatible with callers that don't provide a clock. - */ - readonly now?: () => number; - - /** - * Optional. Called by the runtime at the tool-result boundary — after a - * step whose tool calls have all executed, before the next step begins — - * to drain messages to inject alongside the tool results. Whatever it - * returns is appended as user-role messages to the next step's input, so - * a caller can inject mid-turn guidance the model sees with the tool - * results. When omitted or returning an empty array, no injection happens - * (the runtime is unchanged). - * - * Injected (not ambient) so the kernel stays pure: it owns no queue and - * names no feature — it just calls the callback and appends what it gets. - * Only invoked when a step PRODUCED tool calls (the tool-result boundary); - * a step that ends without tool calls does not drain (the caller decides - * what to do with any pending messages after the turn ends). - */ - readonly drainSteering?: () => readonly ChatMessage[]; - - /** - * Optional. Called by the runtime after each step's messages are finalized - * (the assistant message + tool-result messages are built). The caller can - * use this to persist step messages incrementally — assigning seq numbers - * during generation so consumers can `GET /conversations/:id?sinceSeq=N` - * mid-turn. When omitted, the caller must persist all messages at turn end - * (via `RunTurnResult.messages`). The messages passed to this callback are - * the SAME objects in `RunTurnResult.messages` — the caller must NOT - * double-persist them. - */ - readonly onStepComplete?: (messages: readonly ChatMessage[]) => Promise<void> | void; - - /** - * Optional injected retry strategy for retryable provider errors (e.g. HTTP - * 429 / 5xx "overloaded"). When omitted, a retryable error ends the step - * exactly as before (backward-compatible). When provided, the runtime wraps - * `provider.stream()` consumption in a retry loop: on a retryable error - * (an emitted `error` ProviderEvent with `retryable === true`, OR a thrown - * error) — ONLY when no content was emitted yet this step (the safety - * invariant — never duplicate partial output) — it asks `retry.delayFor` - * for a delay, emits a transient `provider-retry` AgentEvent, sleeps via the - * injected `retry.sleep` (abortable), and re-calls `provider.stream()`. - * - * Injected (not ambient): the kernel imports no timer and owns no schedule. - * Mirrors the `now`/`logger` injection pattern — optional + backward-compatible. - */ - readonly retry?: RetryStrategy; + /** The resolved provider to stream from. */ + readonly provider: ProviderContract; + + /** The conversation history (including system prompt as first message). */ + readonly messages: readonly ChatMessage[]; + + /** The tool set available for this turn (may be empty). */ + readonly tools: readonly ToolContract[]; + + /** How to dispatch tool calls within each step. */ + readonly dispatch: ToolDispatchPolicy; + + /** The emitter the kernel calls for each outward event. */ + readonly emit: EventEmitter; + + /** + * Identifiers used to attribute every emitted `AgentEvent`. The kernel does + * not generate these — the session-orchestrator owns turn/conversation identity + * and passes them in, so events are traceable to their conversation. + */ + readonly conversationId: string; + readonly turnId: string; + + /** + * Optional per-turn provider options (model, temperature, maxTokens, + * systemPrompt). The orchestrator resolves these; the kernel forwards them + * verbatim to `provider.stream` and never interprets them. A provider may + * also be pre-configured at construction and ignore these. + */ + readonly providerOpts?: ProviderStreamOptions; + + /** Cancellation signal for the entire turn. */ + readonly signal?: AbortSignal; + + /** + * Working directory for this turn's tool execution. The kernel does NOT + * interpret it — it forwards the value verbatim to each `ToolExecuteContext.cwd` + * so tools resolve/contain paths against it. It never enters the model prompt, + * so it does not affect prompt caching. When omitted, tools fall back to their + * own configured/default workdir. + */ + readonly cwd?: string; + + /** + * The computer to execute this turn's tools on (SSH support). Omitted/undefined + * = LOCAL (today's behavior). When set, it is an SSH config alias; the kernel + * does NOT interpret it — it forwards the value verbatim to each + * `ToolExecuteContext.computerId`, exactly like `cwd`. It never enters the + * model prompt, so it does not affect prompt caching. Tools resolve their + * execution backend (local vs. remote) from this; see + * `notes/ssh-support-plan.md`. + */ + readonly computerId?: string; + + /** + * Optional logger for structured span instrumentation. The runtime opens + * turn/step/tool-call spans using this logger. If omitted, no spans are + * emitted (backward-compatible with callers that don't yet pass a logger). + */ + readonly logger?: Logger; + + /** + * Optional monotonic-ish clock (milliseconds) for emitting wall-clock timing + * on outward events: per-step `step-complete` (ttft/decode/genTotal), tool + * execution `durationMs` on `tool-result`, and turn `durationMs` on `done`. + * Injected (not ambient) so the runtime stays pure and deterministic in tests. + * If omitted, the runtime emits no such timing (the optional fields stay + * absent) — backward-compatible with callers that don't provide a clock. + */ + readonly now?: () => number; + + /** + * Optional. Called by the runtime at the tool-result boundary — after a + * step whose tool calls have all executed, before the next step begins — + * to drain messages to inject alongside the tool results. Whatever it + * returns is appended as user-role messages to the next step's input, so + * a caller can inject mid-turn guidance the model sees with the tool + * results. When omitted or returning an empty array, no injection happens + * (the runtime is unchanged). + * + * Injected (not ambient) so the kernel stays pure: it owns no queue and + * names no feature — it just calls the callback and appends what it gets. + * Only invoked when a step PRODUCED tool calls (the tool-result boundary); + * a step that ends without tool calls does not drain (the caller decides + * what to do with any pending messages after the turn ends). + */ + readonly drainSteering?: () => readonly ChatMessage[]; + + /** + * Optional. Called by the runtime after each step's messages are finalized + * (the assistant message + tool-result messages are built). The caller can + * use this to persist step messages incrementally — assigning seq numbers + * during generation so consumers can `GET /conversations/:id?sinceSeq=N` + * mid-turn. When omitted, the caller must persist all messages at turn end + * (via `RunTurnResult.messages`). The messages passed to this callback are + * the SAME objects in `RunTurnResult.messages` — the caller must NOT + * double-persist them. + */ + readonly onStepComplete?: (messages: readonly ChatMessage[]) => Promise<void> | void; + + /** + * Optional injected retry strategy for retryable provider errors (e.g. HTTP + * 429 / 5xx "overloaded"). When omitted, a retryable error ends the step + * exactly as before (backward-compatible). When provided, the runtime wraps + * `provider.stream()` consumption in a retry loop: on a retryable error + * (an emitted `error` ProviderEvent with `retryable === true`, OR a thrown + * error) — ONLY when no content was emitted yet this step (the safety + * invariant — never duplicate partial output) — it asks `retry.delayFor` + * for a delay, emits a transient `provider-retry` AgentEvent, sleeps via the + * injected `retry.sleep` (abortable), and re-calls `provider.stream()`. + * + * Injected (not ambient): the kernel imports no timer and owns no schedule. + * Mirrors the `now`/`logger` injection pattern — optional + backward-compatible. + */ + readonly retry?: RetryStrategy; } /** @@ -163,14 +163,14 @@ export interface RunTurnInput { * persist the new messages and report usage. */ export interface RunTurnResult { - /** The assistant messages produced by this turn (appended to history). */ - readonly messages: readonly ChatMessage[]; + /** The assistant messages produced by this turn (appended to history). */ + readonly messages: readonly ChatMessage[]; - /** Aggregated token usage across all steps in the turn. */ - readonly usage: Usage; + /** Aggregated token usage across all steps in the turn. */ + readonly usage: Usage; - /** Why the turn ended. */ - readonly finishReason: FinishReason; + /** Why the turn ended. */ + readonly finishReason: FinishReason; } /** @@ -187,16 +187,16 @@ export interface RunTurnResult { * the step exactly as before). */ export interface RetryStrategy { - /** - * Pure, deterministic decision: given the 0-based attempt index, return the - * delay in ms to sleep before the next retry, or `undefined` to stop (budget - * exhausted). No I/O, no clock — fully testable. - */ - readonly delayFor: (attempt: number) => number | undefined; - /** - * Injected effect: actually sleep for the given ms. Must honor the abort - * signal — reject when aborted so the turn seals `aborted`. The kernel - * imports no timer; the shell provides a `setTimeout`-based implementation. - */ - readonly sleep: (ms: number, signal: AbortSignal) => Promise<void>; + /** + * Pure, deterministic decision: given the 0-based attempt index, return the + * delay in ms to sleep before the next retry, or `undefined` to stop (budget + * exhausted). No I/O, no clock — fully testable. + */ + readonly delayFor: (attempt: number) => number | undefined; + /** + * Injected effect: actually sleep for the given ms. Must honor the abort + * signal — reject when aborted so the turn seals `aborted`. The kernel + * imports no timer; the shell provides a `setTimeout`-based implementation. + */ + readonly sleep: (ms: number, signal: AbortSignal) => Promise<void>; } diff --git a/packages/kernel/src/contracts/tool.ts b/packages/kernel/src/contracts/tool.ts index 589fbd0..897b86e 100644 --- a/packages/kernel/src/contracts/tool.ts +++ b/packages/kernel/src/contracts/tool.ts @@ -16,22 +16,22 @@ import type { Logger } from "./logging.js"; * Using a structural type (not a library) keeps the kernel dependency-free. */ export interface ToolParameterSchema { - readonly type: "object"; - readonly properties?: Readonly<Record<string, JsonSchemaProperty>>; - readonly required?: readonly string[]; - readonly additionalProperties?: boolean; - readonly description?: string; + readonly type: "object"; + readonly properties?: Readonly<Record<string, JsonSchemaProperty>>; + readonly required?: readonly string[]; + readonly additionalProperties?: boolean; + readonly description?: string; } /** A single property within a tool's parameter schema. */ export interface JsonSchemaProperty { - readonly type?: string; - readonly description?: string; - readonly enum?: readonly string[]; - readonly items?: JsonSchemaProperty; - readonly properties?: Readonly<Record<string, JsonSchemaProperty>>; - readonly required?: readonly string[]; - readonly default?: unknown; + readonly type?: string; + readonly description?: string; + readonly enum?: readonly string[]; + readonly items?: JsonSchemaProperty; + readonly properties?: Readonly<Record<string, JsonSchemaProperty>>; + readonly required?: readonly string[]; + readonly default?: unknown; } /** @@ -40,56 +40,56 @@ export interface JsonSchemaProperty { * concurrent tool output is never interleaved ambiguously. */ export interface ToolExecuteContext { - /** Unique id of the tool-call this execution serves. */ - readonly toolCallId: string; - - /** - * Stream output from the tool. The kernel attributes every call to the - * tool-call id, so concurrent shell output from different tools is - * correctly separated. - */ - readonly onOutput: (data: string, stream: "stdout" | "stderr") => void; - - /** - * Cancellation signal. An aborted turn sets this so in-flight tool work - * can clean up rather than leak. - */ - readonly signal: AbortSignal; - - /** - * Pre-bound Logger scoped to this tool-call span. Tools log correlated - * without a global (P3). The kernel stamps extensionId, conversationId, - * turnId, and spanId automatically. - */ - readonly log: Logger; - - /** - * Working directory for this turn, forwarded verbatim from `RunTurnInput.cwd`. - * Tools that touch the filesystem resolve and contain paths against it. - * Optional: when omitted, a tool falls back to its own configured/default - * workdir. The kernel never interprets it. - */ - readonly cwd?: string; - - /** - * The conversation this tool-call belongs to. Tools that maintain - * per-conversation state (e.g. a todo list) key on this. Forwarded - * verbatim from `RunTurnInput.conversationId`. Optional: when omitted, - * a tool has no conversation scope (e.g. a global tool). - */ - readonly conversationId?: string; - - /** - * The computer this tool-call executes on (SSH support). When - * omitted/undefined, execution is LOCAL (today's behavior — the tool uses - * the local node fs/child_process). When set, it is an SSH config alias - * (see `notes/ssh-support-plan.md` §3); a tool resolves a remote - * `ExecBackend` for it via its injected resolver. The kernel never - * interprets it — it forwards the value verbatim from - * `RunTurnInput.computerId`, exactly like `cwd`. It never enters the model - * prompt, so it does not affect prompt caching. - */ - readonly computerId?: string; + /** Unique id of the tool-call this execution serves. */ + readonly toolCallId: string; + + /** + * Stream output from the tool. The kernel attributes every call to the + * tool-call id, so concurrent shell output from different tools is + * correctly separated. + */ + readonly onOutput: (data: string, stream: "stdout" | "stderr") => void; + + /** + * Cancellation signal. An aborted turn sets this so in-flight tool work + * can clean up rather than leak. + */ + readonly signal: AbortSignal; + + /** + * Pre-bound Logger scoped to this tool-call span. Tools log correlated + * without a global (P3). The kernel stamps extensionId, conversationId, + * turnId, and spanId automatically. + */ + readonly log: Logger; + + /** + * Working directory for this turn, forwarded verbatim from `RunTurnInput.cwd`. + * Tools that touch the filesystem resolve and contain paths against it. + * Optional: when omitted, a tool falls back to its own configured/default + * workdir. The kernel never interprets it. + */ + readonly cwd?: string; + + /** + * The conversation this tool-call belongs to. Tools that maintain + * per-conversation state (e.g. a todo list) key on this. Forwarded + * verbatim from `RunTurnInput.conversationId`. Optional: when omitted, + * a tool has no conversation scope (e.g. a global tool). + */ + readonly conversationId?: string; + + /** + * The computer this tool-call executes on (SSH support). When + * omitted/undefined, execution is LOCAL (today's behavior — the tool uses + * the local node fs/child_process). When set, it is an SSH config alias + * (see `notes/ssh-support-plan.md` §3); a tool resolves a remote + * `ExecBackend` for it via its injected resolver. The kernel never + * interprets it — it forwards the value verbatim from + * `RunTurnInput.computerId`, exactly like `cwd`. It never enters the model + * prompt, so it does not affect prompt caching. + */ + readonly computerId?: string; } /** @@ -98,8 +98,8 @@ export interface ToolExecuteContext { * react without the kernel interpreting the content. */ export interface ToolResult { - readonly content: string; - readonly isError?: boolean; + readonly content: string; + readonly isError?: boolean; } /** @@ -108,9 +108,9 @@ export interface ToolResult { * to the matched tool's `execute`. */ export interface ToolCall { - readonly id: string; - readonly name: string; - readonly input: unknown; + readonly id: string; + readonly name: string; + readonly input: unknown; } /** @@ -119,26 +119,26 @@ export interface ToolCall { * concrete tools exist. */ export interface ToolContract { - /** Unique name the model uses to invoke this tool. */ - readonly name: string; - - /** Human-readable description shown to the model. */ - readonly description: string; - - /** JSON-Schema-ish parameter declaration (structural, no library dep). */ - readonly parameters: ToolParameterSchema; - - /** - * Execute the tool with parsed input. The kernel provides a per-call - * context (cancellation, output streaming, attribution). - */ - readonly execute: (args: unknown, ctx: ToolExecuteContext) => Promise<ToolResult>; - - /** - * Whether this tool is safe to run concurrently with other tools. - * When `false`, the kernel serializes this tool's calls even when the - * dispatch policy allows parallelism. Defaults to `true` if omitted. - * This overrides the global setting downward only (never widens parallelism). - */ - readonly concurrencySafe?: boolean; + /** Unique name the model uses to invoke this tool. */ + readonly name: string; + + /** Human-readable description shown to the model. */ + readonly description: string; + + /** JSON-Schema-ish parameter declaration (structural, no library dep). */ + readonly parameters: ToolParameterSchema; + + /** + * Execute the tool with parsed input. The kernel provides a per-call + * context (cancellation, output streaming, attribution). + */ + readonly execute: (args: unknown, ctx: ToolExecuteContext) => Promise<ToolResult>; + + /** + * Whether this tool is safe to run concurrently with other tools. + * When `false`, the kernel serializes this tool's calls even when the + * dispatch policy allows parallelism. Defaults to `true` if omitted. + * This overrides the global setting downward only (never widens parallelism). + */ + readonly concurrencySafe?: boolean; } |
