import { type AgentSession, type LoadExtensionsResult, ModelRuntime, SettingsManager } from "@earendil-works/pi-coding-agent"; import type { Provider } from "@earendil-works/pi-ai"; /** Oversized diagnostics are omitted, not truncated: a truncated prefix can contain a partial key. */ export declare function redactDiagnostic(value: string, redact: (value: string) => string): string; export declare function isValidApiKey(value: unknown): value is string; /** Compile key representations once per session, never per log or response. */ export declare function createApiKeyRedactor(apiKey?: string): (value: string) => string; export declare function redactApiKey(value: string, apiKey?: string): string; /** Resolve extension: env override → npm package → monorepo relative path. */ export declare function resolveExt(envVar: string, packageName: string, entryFile: string, monorepoRelative: string): string; /** * Both the internal registrar runtime and every user session disable compaction * (sessions are short-lived/one-shot over HTTP — no need for the SDK to manage * context window pressure) and use an in-memory settings store (no on-disk * config to load/persist for either). Shared factory so the two call sites * (ensureInternalRuntime()'s createRuntime, create()) don't drift. * * The seed is the STARTUP SNAPSHOT of the deployment agent dir's settings.json * (read once by the SessionStore constructor via readSettingsSeed — see * SessionStore.settingsSeed). It is never re-read and never taken from a * per-request agent_dir: an explicit per-request agent_dir affects * DefaultResourceLoader resource loading only, so sessions cannot diverge from * each other after settings.json edits or partial writes. Deployments that want * to supply pi settings (e.g. defaultProvider) next to their custom * models.json/auth.json restart the sidecar to apply changes. Compaction stays * disabled regardless of what the seed says: sidecar sessions are one-shot over * HTTP. * * Exported for tests. */ export declare function createSessionSettingsManager(seed?: Record): SettingsManager; /** Read `/settings.json` as seed settings; {} when absent/unparseable. * Called once per process (SessionStore constructor) — the result is the * startup snapshot handed to createSessionSettingsManager. Exported for tests. */ export declare function readSettingsSeed(agentDir?: string): Record; /** Manifest-configured model snapshot for a `trustKeyScopedListing`-flagged * provider: the fields forwarded (mapped) into key-scoped listing responses. */ export interface ManifestModelSnapshot { id: string; name: string; contextWindow?: number; maxTokens?: number; } /** Startup snapshot of a provider flagged `trustKeyScopedListing: true`: * the pinned baseUrl that granted trust (issue 913 security fix — the runtime * provider is loaded lazily and its baseUrl may differ from the manifest read * at SessionStore construction, so trust must compare against THIS value, not * the live provider) plus the provider's manifest-configured models. */ export interface TrustedProviderSnapshot { /** Normalized href of the manifest `baseUrl`, or undefined when the manifest * entry has no parseable URL — in which case the flag can never grant trust. */ baseUrl?: string; models: ManifestModelSnapshot[]; } /** Read provider IDs flagged `trustKeyScopedListing: true` from `/models.json` * (empty map when absent/unparseable), each mapped to a snapshot of that * provider's pinned `baseUrl` and manifest-configured `models[]`. Called once * per process (SessionStore constructor) — the same startup snapshot discipline * as readSettingsSeed. This admin-controlled manifest is the ONLY source the * key-scoped listing trust gate accepts; request- or session-level input never * contributes. Exported for tests. */ export declare function readTrustedKeyScopedProviders(agentDir?: string): Map; /** Default agent dir for the internal registrar runtime; independent of per-request agent_dir overrides. */ export declare const INTERNAL_AGENT_DIR = "/tmp/pi-sidecar-agent"; /** * Resolve the agent dir the internal registrar runtime reads models.json / * auth.json / settings.json from (and the per-session default agent dir). * Precedence: explicit option → PI_SIDECAR_AGENT_DIR env var (trimmed, * non-empty) → INTERNAL_AGENT_DIR default. The default is an ephemeral, * undocumented-as-config scratch dir — deployments that want to supply custom * providers/settings set the option or the env var to a persistent dir. * Exported for tests. */ export declare function resolveInternalAgentDir(explicit?: string): string; /** * Builds the CreateAgentSessionRuntimeFactory for the internal registrar * runtime (see SessionStore.ensureInternalRuntime()). Extracted to a * standalone module-level function — rather than an inline closure nested * inside the class method's async IIFE — so the factory's own logic (wiring * settings, extensions, and diagnostics) reads independently of the * lazy-init/idempotency plumbing around it. */ export declare function snapshotLegacyAmbientProviders(registrations: LoadExtensionsResult["runtime"]["pendingNativeProviderRegistrations"], legacyAmbientProviders: WeakSet): void; export declare const DEFAULT_TOOLS: readonly ["read", "grep", "find", "ls", "bash"]; /** Methods SessionStore calls on a stored session (create() or test fixture). */ type StoredSession = Pick; /** * Whether a model's prices are known, as opposed to merely numeric. * * Pi requires numeric prices, so a model whose source published none is * registered with zeros — and those zeros mean "unknown pricing", not "free". * Three sources supply such zeros, and all three must be withheld: * * - Models we fabricate (key-scoped listings, CLI/ACPX discovery without a * catalog price, openai-compatible /v1/models records), marked PRICING_UNKNOWN. * - models.json definitions that omit `cost`: Pi's provider composer fills them * with zeros (see pi's provider-composer `cost: definition.cost ?? {0,0,0,0}`) * and hands us a model with no marker. * - Any other unmarked all-zero cost with no catalog entry behind it. * * An all-zero cost is authoritative in exactly two cases, and both require proof: * * 1. A source published it (PRICING_KNOWN) — models.dev or an OpenAI-compatible * `/v1/models` record stating a zero price for a model pi-ai never shipped. * 2. The cost object *is* pi-ai's generated catalog entry for this model, which * is how a genuinely free model (an OpenRouter `:free` variant) is * recognized. Identity, not a provider+id match: an unpriced models.json * override of a free catalog model gets its own zero-filled cost object from * the provider composer, so sharing identifiers is not evidence of anything. */ export declare function hasKnownPricing(model: { id?: string; provider?: string; api?: string; baseUrl?: string; cost?: Record; } | undefined): boolean; /** * Wire-format shape for custom tool configs received via the HTTP API. * Non-HTTP tools are passed through to the Pi SDK as-is and must already * conform to the SDK's ToolDefinition interface (including the SDK-style * `execute(toolCallId, params, signal, onUpdate, ctx)` signature). * HTTP-backed tools are automatically wrapped with the correct SDK signature. */ export interface CustomToolConfig { name: string; description?: string; parameters?: Record; /** When present, the tool's execute function is backed by an HTTP request. */ http?: Record; /** Additional properties passed through to the Pi SDK ToolDefinition. */ [key: string]: any; } export interface CreateSessionOptions { provider: string; model: string; systemPrompt: string; cwd: string; agentDir?: string; apiKey?: string; tools?: string[]; customTools?: CustomToolConfig[]; } export interface ProviderStatus { provider: string; registered: boolean; modelCount: number; /** Whether POST /sessions accepts api_key for this provider, regardless of server credentials. */ supportsSessionApiKey: boolean; authStatus: ReturnType | null; /** Always null or AuthCheck — never undefined (checkAuth's undefined is normalized via ?? null). */ authCheck: NonNullable>> | null; } export declare class SessionStore { private sessions; /** * Runtime recording override (#935) — INSTANCE state, not module state: * two sidecar servers in one process must not share the toggle, and a * closed server must not leave its override behind for a later instance. * `undefined` means no override; the boot-time env value governs. Set via * POST /transcripts/recording, cleared via /transcripts/recording/reset. * In-memory only: a restart restores the env default by design. */ private recordingOverride; /** Set the runtime override; takes effect for subsequent turns. */ setRecordingOverride(enabled: boolean): void; /** Drop the runtime override; the env default governs again. */ clearRecordingOverride(): void; /** Effective recording state, with whether it comes from env or override. */ recordingState(): { enabled: boolean; source: "env" | "override"; }; /** Env default OR this store's runtime override. */ isRecordingEnabled(): boolean; /** Redact only the credential owned by this session. Never expose it to the registrar or other sessions. */ redactSessionValue(id: string, value: string): string; /** Shared ModelRuntime (0.81+) — sourced from internalRuntime.services once created. */ private modelRuntime; private modelRegistry; /** Legacy providers identified by the registrar's registration queue, not mutable configuration. */ private legacyAmbientProviders; /** * Internal AgentSessionRuntime that owns the shared ModelRuntime and keeps * unified Providers (ACPX + CLI), Vertex, and Subagent extensions loaded for the sidecar process's * lifetime. Created once, lazily, and disposed only in disposeAll(). * * Why this exists: the unified Providers extension (ACPX + CLI) keeps * module-level state (acpx runtime sessions, CLI --resume markers) that is * only correct if exactly one instance of that extension is ever loaded. * `AgentSessionRuntime.dispose()` emits a `session_shutdown` event, which * those providers use as their own teardown signal — they clear that * module-level state and their `filterModels()` (driven by `isConfigured()`) * starts returning an empty list for every acpx-* / cli-* model. So this * runtime must never be disposed except at process shutdown, and it must * never be recreated to "refresh" discovery (see refreshModels()). * * User sessions created via create() never load the unified Providers * extension (only Subagent and Vertex, which have no such shared state) and * resolve acpx-* / cli-* models directly via `modelRuntime.getModel()`. This * intentionally bypasses `ModelRuntime.getAvailable()`/`ModelRegistry.getAvailable()`, * whose `filterModels()` call would read `isConfigured()` from the *internal* * runtime's extension module instance — something a user session has no * reason to depend on and would only add a footgun if it silently changed. */ private internalRuntime; private runtimeInit; private _ready; private _discoveryError; /** * Cached acpx-* / cli-* model snapshots, refreshed by snapshotExtensionModels(). * This is the source of truth for create()'s acpx/cli validation — not a * live provider lookup — so a session request always sees a consistent * list matching the most recent GET /models response. */ private acpxModels; private cliModels; /** * Set at the very start of disposeAll(), before any session or the internal * runtime is torn down. Once true, ensureInternalRuntime()/create()/getModels()/ * refreshModels()/getProviderStatus() all reject immediately instead of doing * work against (or recreating) a runtime that is mid-teardown or gone — the * internal runtime must never be recreated after process shutdown begins. */ private _disposed; /** * Agent dir the internal registrar runtime reads models.json/auth.json/ * settings.json from, and the per-session default agent dir. Resolved once * at construction (see resolveInternalAgentDir); per-request agent_dir still * overrides the session ResourceLoader only. */ private readonly agentDir; /** * Startup snapshot of `/settings.json` ({} when absent or * unparseable). Read exactly once, here in the constructor, and used for * every settings manager this store builds (internal registrar runtime and * every user session). Never re-read: sessions must not diverge after * settings.json edits or partial writes — deployments restart the sidecar to * apply settings changes. Per-request agent_dir never contributes settings. */ private readonly settingsSeed; /** * Startup snapshot of provider IDs flagged `trustKeyScopedListing: true` in * `/models.json` (empty when absent or unparseable), each mapped * to that provider's manifest-configured `models[]` snapshots. Read exactly * once, here in the constructor — same snapshot discipline as settingsSeed. * This admin-controlled manifest is the only source the key-scoped listing * trust gate (getModelsForApiKey) accepts; request/session input never * contributes. Deployments restart the sidecar to apply flag changes. */ private readonly trustedKeyScopedProviders; constructor(options?: { agentDir?: string; }); get disposed(): boolean; private assertNotDisposed; get ready(): boolean; get discoveryError(): string | null; /** * In-flight refreshModels() so concurrent getModels()/create()/startup share * one discovery pass instead of racing two snapshots. */ private refreshInFlight; /** * Ensure the first discovery pass has finished (or failed with _ready set). * Startup kicks refreshModels() async after listen; GET /models and create() * must not serve empty acpx/cli caches during that window. * * If init failed before modelRuntime was assigned, throws a controlled 503 * instead of letting callers crash on `this.modelRuntime!`. */ private ensureDiscoveryComplete; count(): number; /** * Test fixture: register a session without create() (no real SDK). * Not part of the HTTP/Python client contract. Requires abort() so * SessionStore.abort() cannot TypeError. */ putSessionFixture(id: string, session: StoredSession, cwd: string, pricingKnown?: boolean): void; /** * Lazily create the internal AgentSessionRuntime (see field docstring above). * Idempotent — concurrent callers await the same in-flight creation. */ private ensureInternalRuntime; /** List registered runtime provider IDs without model discovery or auth checks. */ getProviders(): Promise>; /** * List models from all sources. Ensures the internal runtime is initialized * so builtins are never silently empty during startup races. * ACPX models take precedence over builtin placeholders with the same base ID; * cli-* stays a separate source (no acpx↔cli merge). */ getModels(): Promise>; /** * Snapshot one extension model source (acpx or cli) for each configured agent. * Prefers the shared ModelRuntime's provider catalog (already populated by * the internal runtime's extension load / refresh()); falls back to a * jiti-loaded discovery call only when that provider is registered but empty. */ private snapshotAgentSource; private snapshotExtensionModels; /** * Discover models from all configured providers. Blocks until complete. * Called on startup — /health returns ok only after this finishes. */ refreshModels(): Promise>; private runRefreshModels; /** Shared by status and create; model snapshots do not indicate auth capability. */ private supportsSessionApiKey; /** * Auth/registration/model-count snapshot for one provider, for diagnostics * (GET /models/:provider/status). Model counts for acpx-* / cli-* come from * the cache populated by snapshotExtensionModels() — the same source create() * validates against — rather than the provider's live (auth-gated) catalog. */ getProviderStatus(provider: string): Promise; /** Query the provider with this request's key, never the shared model registry. */ getModelsForApiKey(providerId: string, apiKey: string): Promise<{ models: Array<{ provider: string; id: string; name: string; source: "listing" | "manifest"; capabilities?: Record; }>; modelListingSupported: boolean; }>; create(options: CreateSessionOptions): Promise; prompt(id: string, message: string): Promise<{ text: string; usage: any; error?: string; }>; abort(id: string): Promise; /** Disposes only the user AgentSession — never the internal registrar runtime. */ delete(id: string): boolean; /** * Dispose all user sessions, then the internal runtime — in that order. * User sessions' `AgentSession.dispose()` does not emit `session_shutdown` * and is safe at any time. The internal runtime's `AgentSessionRuntime.dispose()` * does emit it (see the internalRuntime field docstring), so it must be last: * disposing it earlier would clear the unified Providers extension's * module-level state while user sessions might still be relying on models * resolved from it. * * Sets `_disposed = true` as the very first step, before any teardown work * runs, so that concurrent/in-flight callers of ensureInternalRuntime()/create()/ * getModels()/refreshModels()/getProviderStatus() start rejecting immediately * instead of racing the teardown or (worse) recreating the internal runtime * after it's gone. Idempotent — safe to call more than once. */ disposeAll(): Promise; cleanupStale(maxAge: number): number; } export {};