/** * `createToolCatalog` — THE tool catalog holder (agent core B5). * * Framework-free like `createRunTierStore`: `getState` / `subscribe`, no Redux, reads only. The * active list (`tool.definition`, not deleted, active) feeds every picker; `loadById` resolves one * historical reference (inactive tools included) for audit and version surfaces without ever * leaking it into the picker list. The catalog is a READ of every tool the person can pick — * never narrowed by the active organization. */ interface ToolCatalogPgError { message: string; code?: string; } interface ToolCatalogResult { data: T | null; error: ToolCatalogPgError | null; } /** The structural slice of supabase-js the catalog uses. */ interface ToolCatalogQuery extends PromiseLike> { select(columns: string): ToolCatalogQuery; is(column: string, value: null): ToolCatalogQuery; eq(column: string, value: unknown): ToolCatalogQuery; neq(column: string, value: unknown): ToolCatalogQuery; order(column: string, options?: { ascending?: boolean; }): ToolCatalogQuery; maybeSingle(): PromiseLike>; } interface ToolCatalogClient { schema(name: string): { from(table: string): ToolCatalogQuery; }; } /** The minimum every tool row carries. Hosts bind their generated row type. */ interface ToolRowBase { id: string; name: string; } type ToolCatalogStatus = "idle" | "loading" | "succeeded" | "failed"; interface ToolCatalogState { tools: Row[]; status: ToolCatalogStatus; error: string | null; /** Historical/inactive records resolved by id — never in the picker list. */ identityById: Record; lookupStatusById: Record; } interface ToolCatalogConfig { client: ToolCatalogClient | (() => ToolCatalogClient); /** OPTIONAL — DEFAULT: true. False (no database, a signed-out visitor) answers an empty catalog. */ isAvailable?: () => boolean; /** OPTIONAL — DEFAULT: a tagged `console.error`. */ errorSink?: (event: { code: string; message: string; context?: object; }) => void; } interface ToolCatalog { getState(): ToolCatalogState; subscribe(listener: () => void): () => void; /** The active catalog; one read per session (concurrent calls share it). `force` re-reads. */ load(options?: { force?: boolean; }): Promise; /** One reference by id, inactive included. Resolves undefined when it does not exist. */ loadById(toolId: string): Promise; } declare const EMPTY_TOOL_CATALOG_STATE: ToolCatalogState; declare function createToolCatalog(config: ToolCatalogConfig): ToolCatalog; declare const selectAllTools: (s: ToolCatalogState) => Row[]; declare const selectToolsStatus: (s: ToolCatalogState) => ToolCatalogStatus; declare const selectToolsError: (s: ToolCatalogState) => string | null; declare const selectToolsReady: (s: ToolCatalogState) => boolean; declare const selectToolIdentityMap: (s: ToolCatalogState) => Record; declare const selectToolById: (s: ToolCatalogState, toolId: string | null | undefined) => Row | undefined; declare const selectToolLookupStatus: (s: ToolCatalogState, toolId: string) => ToolCatalogStatus; /** * A "bundle:list_*" tool is a bundle's lister, not a tool a person picks: the * builder's Agent Tools dialog and the chat Tools panel both leave them out * (they live under the Bundles category). ONE rule so the two counts agree — * they said 649 and 507 while only one of them applied it. */ declare function isBundleListerName(name?: string | null): boolean; /** * `createToolCatalogSearch` — the paged, server-side search over the Tool catalog (third-party * services lane L7, CONTRACT §13). A thousand-Tool catalog is never loaded whole: every page is * one `fetchPage` call, the holder keeps only the pages the person has scrolled to, and a newer * query always wins over an older one still in flight. * * Framework-free like `createToolCatalog`: `getState` / `subscribe`, no React, no Redux. Hosts bind * `fetchPage` (chat: `agents/identity/tool-search`; the Tools home: its own server client). * * The wire shapes below are the §13 search response, typed here until `@ai-matrx/api/agents` * generates them (CONTRACT §15); nothing in them names a vendor. */ type CatalogVerdict = "strong" | "closest" | "none"; interface CatalogReliability { success_rate: number | null; samples: number; p50_ms: number | null; last_success_at: string | null; } type CatalogToolState = "live" | "paused" | "off" | "draft" | "retired"; interface CatalogToolHit { tool_id: string; job_key: string; display_name: string; display_noun: string; summary: string; job_family: string; price_hint_points: number | null; reliability: CatalogReliability; state: CatalogToolState; /** A `compare` Tool's pinnable sources: each leader by name, then `{endpoint_id: null, display_source: "Matrx"}` * (pinning null = the router's Matrx sources). Empty for every other Tool. */ compare_sources?: CatalogCompareSource[]; } interface CatalogCompareSource { endpoint_id: string | null; display_source: string; } interface CatalogSearchResponse { verdict: CatalogVerdict; tools: CatalogToolHit[]; next_cursor: string | null; } interface CatalogSearchRequest { q: string; job_family?: string; cursor?: string | null; limit?: number; } declare const CATALOG_MAX_PAGE = 50; /** The server route (agents domain, beside `/tools/*`); one place so a rename is one edit. */ declare const CATALOG_SEARCH_PATH = "/tools/catalog/search"; /** Query string for one search page. Empty values are left out. */ declare function catalogSearchQuery(request: CatalogSearchRequest): string; type CatalogSearchStatus = "idle" | "loading" | "loading_more" | "succeeded" | "failed" | "unavailable"; /** * Thrown by a host's `fetchPage` when the server has no Tool catalog route yet (HTTP 404). The * holder goes `unavailable` — a picker shows nothing for it rather than an error in every chat — * and does not ask again for `CATALOG_UNAVAILABLE_RETRY_MS`. */ declare class CatalogUnavailableError extends Error { constructor(message?: string); } declare const CATALOG_UNAVAILABLE_RETRY_MS = 60000; interface CatalogSearchState { query: string; jobFamily: string | null; tools: CatalogToolHit[]; /** The verdict of the FIRST page of this query (later pages carry the same one). */ verdict: CatalogVerdict | null; status: CatalogSearchStatus; error: string | null; nextCursor: string | null; /** Every Tool seen this session by id, so an attached Tool keeps its name after the query changes. */ known: Record; } interface CatalogSearchConfig { fetchPage: (request: CatalogSearchRequest, signal: AbortSignal) => Promise; /** Page size, at most 50 (the contract's ceiling). DEFAULT 25. */ pageSize?: number; errorSink?: (event: { code: string; message: string; }) => void; } interface CatalogSearch { getState(): CatalogSearchState; subscribe(listener: () => void): () => void; /** Start (or restart) a search. Same query and family as the last one is a no-op. */ search(query: string, jobFamily?: string | null): Promise; /** The next page of the current query; no-op while loading or at the end. */ loadMore(): Promise; /** Re-run the current query from its first page. */ retry(): Promise; } declare const EMPTY_CATALOG_SEARCH_STATE: CatalogSearchState; declare function createToolCatalogSearch(config: CatalogSearchConfig): CatalogSearch; declare const selectCatalogHits: (s: CatalogSearchState) => CatalogToolHit[]; declare const selectCatalogStatus: (s: CatalogSearchState) => CatalogSearchStatus; declare const selectCatalogHasMore: (s: CatalogSearchState) => boolean; declare const selectCatalogKnown: (s: CatalogSearchState, toolId: string) => CatalogToolHit | undefined; /** * The Tool call envelope as a client sees it (third-party services lane L7, CONTRACT §5) and the * one runner every screen uses to run a Tool and survive a long call. * * `POST /tools/calls?mode=json` answers once, holding up to `wait_s`, then returns the pending * record; `GET /tools/calls/{call_id}` reads it (and resumes a stalled call). The record is written * with the inputs BEFORE anything runs, so a closed tab never loses a paid answer: `runToolCall` * keeps reading the same record until it settles. Types are the §5a/§5b member columns, typed here * until `@ai-matrx/api/agents` generates them (CONTRACT §15). Members never receive vendor names; * the admin-only fields are optional and absent for a member. */ type ToolCallStatus = "queued" | "running" | "waiting" | "needs_approval" | "succeeded" | "empty" | "failed" | "refused" | "cancelled"; type ToolCallCache = "none" | "own" | "shared"; type ToolCachePolicy = "prefer" | "refresh" | "bypass"; interface ToolCallRequest { tool_id: string; inputs: Record; max_points?: number | null; idempotency_key?: string | null; cache_policy?: ToolCachePolicy; spend_approval_id?: string | null; wait_s?: number | null; } interface ToolCallError { code: string; message: string; } /** * The spend ask a paid call returns (`needs_approval`): the screen-run approval model unchanged * plus the points estimate. `render` is the answer form the chat package's approval dialog shows. */ interface ToolCallApproval { action_request_id: string; organization_id: string | null; /** Admins only (members read points). */ estimate_usd: number | null; ceiling_cap_usd: number | null; what_it_buys: string | null; expires_at: string | null; render: unknown | null; estimate_points: number | null; } interface ToolCallView { call_id: string; tool_id: string | null; tool_name: string | null; display_name: string | null; status: ToolCallStatus; stage: string | null; stage_label: string | null; /** Scrubbed, projected, carries `__kind`; null unless `succeeded`. */ output: unknown; display_source: string | null; ignored_filters: { input: string; label: string; }[]; points_held: number; points_charged: number; cache: ToolCallCache; /** The normalized inputs the call ran with (detection applied, defaults filled), so a stored call can refill its form. */ inputs?: Record | null; /** How many sources were tried; the list itself is admin-only. */ attempts_count?: number; error: ToolCallError | null; approval: ToolCallApproval | null; /** The builders' raw view: a member's is the projected output, an admin's the scrubbed body. */ raw: unknown; created_at: string | null; finished_at: string | null; latency_ms: number | null; /** Admin only. */ cost_usd?: number | null; } declare const TOOL_CALLS_PATH = "/tools/calls"; declare const TOOL_CALL_POLL_MS = 1500; declare const TERMINAL_CALL_STATUSES: readonly ToolCallStatus[]; /** Settled: nothing more will happen without the person (a finished call, or one waiting on their approval). */ declare function isCallSettled(status: ToolCallStatus): boolean; /** True when the call is still doing work on the server. */ declare function isCallRunning(status: ToolCallStatus): boolean; /** A client-made idempotency key: one per submit, kept across a reload with the person's inputs. */ declare function newToolCallKey(): string; interface ToolCallRunner { /** `POST /tools/calls?mode=json`. */ send: (request: ToolCallRequest, signal: AbortSignal) => Promise; /** `GET /tools/calls/{call_id}`. */ read: (callId: string, signal: AbortSignal) => Promise; } /** * Thrown when the record could not be read for several beats in a row. The call is NOT failed: it is * durable on the server, and `resumeToolCall(call_id)` picks it up when the connection is back. */ declare class ToolCallLostError extends Error { readonly callId: string; constructor(callId: string); } interface RunToolCallOptions { request: ToolCallRequest; /** Consecutive failed reads before the wait gives up with `ToolCallLostError`. DEFAULT 8. */ maxReadFailures?: number; onUpdate?: (view: ToolCallView) => void; signal?: AbortSignal; pollMs?: number; /** Give up waiting (the call itself keeps running on the server). DEFAULT 10 minutes. */ maxWaitMs?: number; sleep?: (ms: number, signal: AbortSignal) => Promise; } /** * Send the call, then keep reading the same record until it settles. Every update goes to * `onUpdate`. A dropped connection while reading is retried (the record is durable); only an abort * or `maxWaitMs` ends the wait early — the last view is returned and the call is still on the * server for `resumeToolCall`. */ declare function runToolCall(runner: ToolCallRunner, options: RunToolCallOptions): Promise; /** Pick a call up again by id (a reload, `?call=`): same loop as `runToolCall`, no new spend. */ declare function resumeToolCall(runner: ToolCallRunner, callId: string, options?: Omit): Promise; export { CATALOG_MAX_PAGE, CATALOG_SEARCH_PATH, CATALOG_UNAVAILABLE_RETRY_MS, CatalogUnavailableError, EMPTY_CATALOG_SEARCH_STATE, EMPTY_TOOL_CATALOG_STATE, TERMINAL_CALL_STATUSES, TOOL_CALLS_PATH, TOOL_CALL_POLL_MS, ToolCallLostError, catalogSearchQuery, createToolCatalog, createToolCatalogSearch, isBundleListerName, isCallRunning, isCallSettled, newToolCallKey, resumeToolCall, runToolCall, selectAllTools, selectCatalogHasMore, selectCatalogHits, selectCatalogKnown, selectCatalogStatus, selectToolById, selectToolIdentityMap, selectToolLookupStatus, selectToolsError, selectToolsReady, selectToolsStatus }; export type { CatalogCompareSource, CatalogReliability, CatalogSearch, CatalogSearchConfig, CatalogSearchRequest, CatalogSearchResponse, CatalogSearchState, CatalogSearchStatus, CatalogToolHit, CatalogToolState, CatalogVerdict, RunToolCallOptions, ToolCachePolicy, ToolCallApproval, ToolCallCache, ToolCallError, ToolCallRequest, ToolCallRunner, ToolCallStatus, ToolCallView, ToolCatalog, ToolCatalogClient, ToolCatalogConfig, ToolCatalogPgError, ToolCatalogQuery, ToolCatalogResult, ToolCatalogState, ToolCatalogStatus, ToolRowBase };