import type { OperatorScope } from "../gateway/operator-scopes.js"; import type { AgentEventPayload, AgentEventStream } from "../infra/agent-events.js"; import type { PluginHookAgentContext, PluginHookBeforeToolCallEvent, PluginHookBeforeToolCallResult, PluginHookToolContext, } from "./hook-types.js"; import type { PluginJsonValue } from "./host-hook-json.js"; import type { PluginAgentTurnPrepareResult, PluginNextTurnInjectionPlacement, PluginNextTurnInjectionRecord, } from "./host-hook-turn-types.js"; export { isPluginJsonValue } from "./host-hook-json.js"; export type { PluginJsonPrimitive, PluginJsonValue } from "./host-hook-json.js"; export type { PluginAgentTurnPrepareEvent, PluginAgentTurnPrepareResult, PluginHeartbeatPromptContributionEvent, PluginHeartbeatPromptContributionResult, PluginNextTurnInjection, PluginNextTurnInjectionEnqueueResult, PluginNextTurnInjectionPlacement, PluginNextTurnInjectionRecord, } from "./host-hook-turn-types.js"; export type PluginHostCleanupReason = "disable" | "reset" | "delete" | "restart"; export type PluginSessionExtensionProjectionContext = { sessionKey: string; sessionId?: string; state: PluginJsonValue | undefined; }; export type PluginSessionExtensionRegistration = { namespace: string; description: string; project?: (ctx: PluginSessionExtensionProjectionContext) => PluginJsonValue | undefined; cleanup?: (ctx: { reason: PluginHostCleanupReason; sessionKey?: string }) => void | Promise; /** * When set, after every successful `patchSessionExtension` the projected * value is mirrored to `SessionEntry[]` so non-plugin readers * can consume the typed slot without reaching into * `pluginExtensions[pluginId][namespace]`. * * The slot is a read-only mirror: writes always go through * `patchSessionExtension`; the host overwrites the slot value on every * subsequent patch. */ sessionEntrySlotKey?: string; /** * Optional JSON-compatible schema describing the projected slot value. * Purely informational at this layer; clients may use it to validate the * mirrored slot against a contract. */ sessionEntrySlotSchema?: PluginJsonValue; }; export type PluginSessionExtensionProjection = { pluginId: string; namespace: string; value: PluginJsonValue; }; export type PluginSessionExtensionPatchParams = { key: string; pluginId: string; namespace: string; value?: PluginJsonValue; unset?: boolean; }; export type PluginToolPolicyDecision = | PluginHookBeforeToolCallResult | { allow?: boolean; reason?: string; }; export type PluginTrustedToolPolicyRegistration = { id: string; description: string; evaluate: ( event: PluginHookBeforeToolCallEvent, ctx: PluginHookToolContext, ) => PluginToolPolicyDecision | void | Promise; }; export type PluginToolMetadataRegistration = { toolName: string; displayName?: string; description?: string; risk?: "low" | "medium" | "high"; tags?: string[]; }; export type PluginCommandContinuation = { continueAgent?: boolean; }; export type PluginControlUiDescriptor = { id: string; surface: "session" | "tool" | "run" | "settings"; label: string; description?: string; placement?: string; schema?: PluginJsonValue; requiredScopes?: OperatorScope[]; }; export type PluginRuntimeLifecycleRegistration = { id: string; description?: string; cleanup?: (ctx: { reason: PluginHostCleanupReason; sessionKey?: string; runId?: string; }) => void | Promise; }; export type PluginAgentEventSubscriptionRegistration = { id: string; description?: string; streams?: AgentEventStream[]; handle: ( event: AgentEventPayload, ctx: { // oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Run-context JSON reads are caller-typed by namespace. getRunContext: ( namespace: string, ) => T | undefined; setRunContext: (namespace: string, value: PluginJsonValue) => void; clearRunContext: (namespace?: string) => void; }, ) => void | Promise; }; export type PluginRunContextPatch = { runId: string; namespace: string; value?: PluginJsonValue; unset?: boolean; }; export type PluginRunContextGetParams = { runId: string; namespace: string; }; export type PluginSessionSchedulerJobRegistration = { id: string; sessionKey: string; kind: string; description?: string; cleanup?: (ctx: { reason: PluginHostCleanupReason; sessionKey: string; jobId: string; }) => void | Promise; }; export type PluginSessionSchedulerJobHandle = { id: string; pluginId: string; sessionKey: string; kind: string; }; export function normalizePluginHostHookId(value: string | undefined): string { return (value ?? "").trim(); } function normalizeQueuedInjectionText( entry: PluginNextTurnInjectionRecord, placement: PluginNextTurnInjectionPlacement, ): string | undefined { const candidate = entry as { placement?: unknown; text?: unknown; }; if (candidate.placement !== placement || typeof candidate.text !== "string") { return undefined; } const text = candidate.text.trim(); return text || undefined; } export function buildPluginAgentTurnPrepareContext(params: { queuedInjections: PluginNextTurnInjectionRecord[]; }): PluginAgentTurnPrepareResult { const prepend = params.queuedInjections .map((entry) => normalizeQueuedInjectionText(entry, "prepend_context")) .filter(Boolean); const append = params.queuedInjections .map((entry) => normalizeQueuedInjectionText(entry, "append_context")) .filter(Boolean); return { ...(prepend.length > 0 ? { prependContext: prepend.join("\n\n") } : {}), ...(append.length > 0 ? { appendContext: append.join("\n\n") } : {}), }; } export type PluginHostHookRunContext = PluginHookAgentContext;