import type { EntityMetrics } from "@lobu/connector-sdk";
import type { AgentSettings } from "@lobu/core";
import { ApiError } from "../../memory/_lib/errors.js";
import type { DesiredAgentMetadata, DesiredEntityType, DesiredRelationshipType } from "./desired-state.js";
import { type EntityBacking, type RelationshipRule, type WatcherSource } from "./shared.js";
export interface RemoteAgent {
    agentId: string;
    name: string;
    description?: string;
}
export interface RemotePlatform {
    id: string;
    platform: string;
    agentId?: string;
    config?: Record<string, unknown>;
    status?: string;
}
export interface RemoteEntityType {
    slug: string;
    name?: string;
    description?: string;
    required?: string[];
    properties?: Record<string, unknown>;
    /** Event kinds keyed by semantic_type (mirrors {@link DesiredEntityType.eventKinds}); hoisted from the row's `event_kinds`. */
    eventKinds?: Record<string, unknown>;
    /**
     * Current default view template (mirrors {@link DesiredEntityType.viewTemplate}).
     * NOT returned by the entity-type list (kept off that hot path); apply-cmd
     * fetches it per relevant type via {@link ApplyClient.getEntityTypeViewTemplate}
     * and attaches it before diffing.
     */
    viewTemplate?: Record<string, unknown>;
    /** Present only for derived types (mirrors {@link DesiredEntityType.backing}). */
    backing?: EntityBacking;
    /** Declared metrics (mirrors {@link DesiredEntityType.metrics}); hoisted from the row's `metrics_config`. */
    metrics?: EntityMetrics;
    /**
     * Owning org id. The list endpoint also returns *public* types from OTHER
     * orgs (`o.visibility = 'public'`), so prune must compare this against the
     * target org and never delete a type this org doesn't own.
     */
    organization_id?: string;
}
export interface RemoteRelationshipType {
    slug: string;
    name?: string;
    description?: string;
    rules?: RelationshipRule[];
    /** Owning org id — see RemoteEntityType.organization_id (public-type guard). */
    organization_id?: string;
}
interface RemoteOrg {
    id: string;
    slug: string;
    name?: string;
}
export interface RemoteWatcher {
    slug: string;
    name?: string;
    watcher_id?: string;
    agent_id?: string | null;
    schedule?: string | null;
    device_worker_id?: string | null;
    goal_id?: number | null;
    scheduler_client_id?: string | null;
    agent_kind?: string | null;
    notification_channel?: string | null;
    notification_priority?: string | null;
    min_cooldown_seconds?: number | null;
    tags?: string[] | null;
    sources?: WatcherSource[] | null;
    description?: string | null;
    prompt?: string | null;
    classifiers?: unknown[] | null;
    keying_config?: Record<string, unknown> | null;
    reactions_guidance?: string | null;
}
interface UpsertPlatformResult {
    /** Server reports `noop: true` when the desired config matches what's stored. */
    noop?: boolean;
    /** When the config materially changed, the live worker is restarted. */
    willRestart?: boolean;
    updated?: boolean;
    created?: boolean;
    platform?: RemotePlatform;
}
interface UpsertEntityTypeResult {
    created?: boolean;
    updated?: boolean;
    noop?: boolean;
}
export interface RemoteConnectorDefinition {
    key: string;
    name?: string;
    version?: string;
    options_schema?: Record<string, unknown> | null;
    feeds_schema?: Record<string, unknown> | null;
    auth_schema?: Record<string, unknown> | null;
    installed?: boolean;
    installable?: boolean;
    catalog_origin?: string;
    /** `file://` URI of the bundled source on the server host (catalog entries). */
    source_uri?: string | null;
}
export interface RemoteAuthProfile {
    id?: number;
    slug: string;
    display_name?: string;
    connector_key: string;
    profile_kind: string;
    status: string;
}
export interface RemoteConnection {
    id: number;
    slug: string;
    connector_key: string;
    display_name?: string;
    status: string;
    auth_profile_slug?: string | null;
    app_auth_profile_slug?: string | null;
    config?: Record<string, unknown> | null;
    device_worker_id?: string | null;
}
export interface RemoteFeed {
    id: number;
    connection_id: number;
    feed_key: string;
    display_name?: string;
    status: string;
    schedule?: string | null;
    config?: Record<string, unknown> | null;
}
interface InstallConnectorResult {
    connectorKey: string;
    updated: boolean;
    version?: string;
}
/**
 * Result of ensuring an auth profile exists. For interactive kinds
 * (`oauth_account` / `browser_session`) `connectUrl` carries the URL the
 * operator must open to complete auth; `status` is the state the server
 * reports (`pending_auth` until auth completes).
 */
interface EnsureAuthProfileResult {
    created: boolean;
    updated: boolean;
    status?: string;
    connectUrl?: string;
}
interface ApplyClientConfig {
    apiBaseUrl: string;
    orgSlug: string;
    token: string;
}
/**
 * Typed wrappers for the existing server endpoints `lobu apply` calls.
 *
 * The class is open over an injectable `fetchImpl` so tests can stub the
 * network without monkey-patching globals. Real callers leave `fetchImpl`
 * unset and pick up `globalThis.fetch`.
 */
export declare class ApplyClient {
    private readonly orgSlug;
    private readonly http;
    constructor(cfg: ApplyClientConfig, fetchImpl?: typeof fetch);
    /**
     * Delegates the fetch/parse/non-ok-error pipeline to the shared
     * {@link ApiClient}, then layers on the apply-specific shape:
     *   - the body is coerced to a record (`undefined`→`{}`, non-record→`{value}`)
     *   - a body-level `error` string is treated as a failure even on a 2xx
     *
     * Apply endpoints only ever return the listed `okStatuses` (200/201/204) on
     * success or a 4xx/5xx on failure, so `ApiClient`'s status gate produces the
     * same outcome the local pipeline did. `Content-Type: application/json` is
     * sent on every request (no `Accept`) to mirror the previous wire shape.
     */
    private request;
    /**
     * Orgs the authenticated user belongs to, read from the OAuth userinfo
     * endpoint — the same source `lobu org list` uses. Used to check whether the
     * `[memory].org` slug already resolves to one of the operator's orgs. Does
     * not depend on `this.orgSlug`. (`lobu apply` can't create an org headlessly
     * — that needs a logged-in browser session — so there is no `createOrg`.)
     */
    listOrgs(): Promise<RemoteOrg[]>;
    listAgents(): Promise<RemoteAgent[]>;
    /**
     * Idempotent create: PR-2 makes `POST /` return 200 with the existing
     * payload when an agent of the same ID already exists in the same org.
     * Cross-org collision still surfaces as 409 with a clear `error.code` —
     * we re-throw verbatim so `lobu apply` can show the operator the link
     * to the org-scoped IDs issue.
     */
    upsertAgent(agent: DesiredAgentMetadata): Promise<RemoteAgent>;
    patchAgentMetadata(agentId: string, agent: {
        name?: string;
        description?: string;
    }): Promise<void>;
    getAgentSettings(agentId: string): Promise<AgentSettings | null>;
    patchAgentSettings(agentId: string, settings: Partial<AgentSettings>): Promise<void>;
    /**
     * Set (or rotate) the org-shared API key for a provider. Idempotent.
     * Lands in `agent_secrets` under `provider:<id>:apiKey`, scoped to the org.
     */
    setProviderApiKey(agentId: string, providerId: string, value: string): Promise<void>;
    listPlatforms(agentId: string): Promise<RemotePlatform[]>;
    /**
     * Stable-ID upsert.
     *
     * Server contract:
     *   PUT /:agentId/platforms/by-stable-id/:stableId
     *   body: { platform, name?, config }
     *   response when unchanged: { noop: true, platform }
     *   response when changed:   { updated: true, willRestart: true, platform }
     *   response on first write: { created: true, platform }
     */
    upsertPlatform(agentId: string, stableId: string, payload: {
        platform: string;
        name?: string;
        config: Record<string, string>;
    }): Promise<UpsertPlatformResult>;
    /**
     * Reconcile a platform's declarative channel bindings.
     *
     * Server contract:
     *   POST /:agentId/platforms/:platformId/sync-channels
     *   body: { channels: string[] }   // each "<teamId>/<channelId>"
     *   response: { bound: string[], removed: string[] }
     */
    syncPlatformChannels(agentId: string, platformId: string, channels: string[]): Promise<{
        bound: string[];
        removed: string[];
    }>;
    listEntityTypes(): Promise<RemoteEntityType[]>;
    /**
     * The `manage_entity_schema` admin tool exposes separate `create` / `update`
     * actions and surfaces duplicates as a coded 409 (`[entity_type_exists]` /
     * `[relationship_type_exists]`). Probe with `create`; on that explicit
     * duplicate signal retry with `update`. Any other error (e.g. a 422
     * `[invalid_schema]` validation failure) propagates verbatim — retrying it
     * as an update used to mask the real message behind "Entity type not
     * found" (issue #1177).
     */
    private upsertSchemaResource;
    upsertEntityType(entity: Omit<DesiredEntityType, "metadata">): Promise<UpsertEntityTypeResult>;
    /**
     * Fetch an entity type's current default view template (`null` if none).
     * Apply uses this to diff ONLY the types it needs (declared templates, plus
     * every config type under prune) — the template is deliberately NOT returned
     * by the entity-type list, which the UI/bootstrap also calls.
     */
    getEntityTypeViewTemplate(slug: string): Promise<Record<string, unknown> | null>;
    /**
     * Set the entity type's default view template. A separate, version-appending
     * tool from the schema upsert, so apply calls this ONLY on create or a changed
     * template (see apply-cmd) — never every run, which would churn the history.
     */
    setEntityTypeViewTemplate(slug: string, jsonTemplate: Record<string, unknown>): Promise<void>;
    /**
     * Clear the entity type's default view template (prune-gated removal). Nulls
     * the current-version pointer server-side; history rows stay for rollback.
     */
    clearEntityTypeViewTemplate(slug: string): Promise<void>;
    listRelationshipTypes(): Promise<RemoteRelationshipType[]>;
    /**
     * Fetch a relationship type's rules (the `list` action omits them, so the
     * apply diff can't otherwise see remote rules and would churn a perpetual
     * "rules changed" update). Maps the server's `*_entity_type_slug` columns to
     * `{ source, target }`; `id` is carried for reconcile (remove_rule by id).
     */
    listRelationshipTypeRules(slug: string): Promise<Array<RelationshipRule & {
        id: number;
    }>>;
    upsertRelationshipType(rel: Omit<DesiredRelationshipType, "metadata">): Promise<UpsertEntityTypeResult>;
    /**
     * Delete an entity type (code-managed prune). The server soft-deletes and
     * REFUSES if instances of the type still exist — the data is exempt from
     * prune, so that surfaces as a clear error rather than cascading.
     */
    deleteEntityType(slug: string): Promise<void>;
    /** Delete a relationship type (code-managed prune). */
    deleteRelationshipType(slug: string): Promise<void>;
    /**
     * Fetch a single watcher's full payload — `getWatcher` server-side, which
     * returns reaction_script (not in the list response). Used by
     * `lobu init --from-org` to round-trip reaction scripts back to sibling
     * `.ts` files.
     */
    getWatcherDetail(watcherId: string): Promise<{
        reaction_script?: string | null;
        description?: string | null;
    } | null>;
    listWatchers(): Promise<RemoteWatcher[]>;
    /**
     * Create a watcher owned by `agentId`. Duplicate-slug surfaces as a
     * structured error the caller swallows for idempotency.
     */
    createWatcher(payload: {
        slug: string;
        agentId: string;
        name?: string;
        description?: string;
        prompt: string;
        schedule?: string;
        sources?: WatcherSource[];
        reactions_guidance?: string;
        device_worker_id?: string;
        scheduler_client_id?: string;
        notification_channel?: "canvas" | "notification" | "both";
        notification_priority?: "low" | "normal" | "high";
        min_cooldown_seconds?: number;
        tags?: string[];
        agent_kind?: string;
        keying_config?: Record<string, unknown>;
        classifiers?: unknown[];
    }): Promise<{
        watcher_id?: string;
    }>;
    /**
     * Update the **scalar** fields on the `watchers` row — these don't require
     * a new version. Version-bound fields (prompt / sources / reactions_guidance /
     * keying_config / classifiers) require `createWatcherVersion`
     * instead.
     *
     * `null` clears nullable fields (device_worker_id, scheduler_client_id,
     * agent_kind) per the server contract.
     */
    updateWatcher(payload: {
        watcher_id: string;
        schedule?: string | null;
        agent_id?: string;
        device_worker_id?: string | null;
        scheduler_client_id?: string | null;
        notification_channel?: "canvas" | "notification" | "both";
        notification_priority?: "low" | "normal" | "high";
        min_cooldown_seconds?: number;
        tags?: string[];
        agent_kind?: string | null;
    }): Promise<void>;
    /**
     * Create a new watcher_versions row carrying the version-bound fields, then
     * upgrade the watcher's `current_version_id` to that new version. Server
     * inherits unset fields from the previous version row.
     */
    createWatcherVersion(payload: {
        watcher_id: string;
        prompt?: string;
        sources?: WatcherSource[];
        keying_config?: Record<string, unknown>;
        classifiers?: unknown[];
        reactions_guidance?: string;
        change_notes?: string;
    }): Promise<{
        version?: number;
    }>;
    /**
     * Attach (or clear) a reaction script. Pass an empty string to remove it —
     * matches the admin tool contract.
     */
    setReactionScript(watcherId: string, reactionScript: string): Promise<void>;
    /**
     * Delete a watcher by its numeric `watcher_id` (code-managed prune). The
     * admin tool takes an array; we delete one slug's watcher at a time so a
     * failure is attributable.
     */
    deleteWatcher(watcherId: string): Promise<void>;
    private connectionsTool;
    private feedsTool;
    private authProfilesTool;
    private catalogTool;
    /** Installed org connectors + (with `includeInstallable`) the bundled catalog. */
    listConnectors(includeInstallable?: boolean): Promise<RemoteConnectorDefinition[]>;
    /**
     * Idempotent connector install. The CLI passes raw TypeScript source
     * (`compiled: false`) or a `source_url`; the server compiles + extracts
     * metadata and returns the resolved `connectorKey` plus `updated` (false
     * when the installed code is byte-identical).
     */
    installConnector(payload: {
        sourceCode?: string;
        sourceUrl?: string;
        /** `file://` URI of a bundled connector source on the server host. */
        sourceUri?: string;
        /** `sourceCode` is already a compiled bundle (CLI-side compile) — skip server compile. */
        compiled?: boolean;
    }): Promise<InstallConnectorResult>;
    uninstallConnector(connectorKey: string): Promise<void>;
    listAuthProfiles(): Promise<RemoteAuthProfile[]>;
    getAuthProfileBySlug(slug: string): Promise<RemoteAuthProfile | null>;
    createAuthProfile(payload: {
        slug: string;
        connector: string;
        kind: string;
        name?: string;
        credentials?: Record<string, string>;
    }): Promise<EnsureAuthProfileResult>;
    updateAuthProfile(payload: {
        slug: string;
        name?: string;
        credentials?: Record<string, string>;
    }): Promise<EnsureAuthProfileResult>;
    /** Re-issue a connect token for an existing interactive-auth profile. */
    reconnectAuthProfile(slug: string): Promise<string | undefined>;
    listConnections(): Promise<RemoteConnection[]>;
    createConnection(payload: {
        slug: string;
        connector: string;
        name?: string;
        authProfileSlug?: string;
        appAuthProfileSlug?: string;
        config?: Record<string, unknown>;
        deviceWorkerId?: string;
    }): Promise<RemoteConnection>;
    updateConnection(connectionId: number, payload: {
        name?: string;
        authProfileSlug?: string | null;
        appAuthProfileSlug?: string | null;
        config?: Record<string, unknown>;
        deviceWorkerId?: string | null;
    }): Promise<RemoteConnection>;
    listFeeds(connectionId: number): Promise<RemoteFeed[]>;
    createFeed(payload: {
        connectionId: number;
        feedKey: string;
        name?: string;
        schedule?: string;
        config?: Record<string, unknown>;
    }): Promise<RemoteFeed>;
    updateFeed(feedId: number, payload: {
        name?: string;
        schedule?: string;
        config?: Record<string, unknown>;
    }): Promise<RemoteFeed>;
}
/**
 * Recognise duplicate-name errors from the admin tools without substring
 * matching the user-facing message. The server stamps a bracketed code into
 * the error message of every duplicate path `apply` upserts through
 * (`manage_entity_schema` create / add_rule → `[entity_type_exists]`,
 * `[relationship_type_exists]`, `[already_exists]`, all httpStatus 409).
 *
 * Anything else is NOT a duplicate. In particular a 422 schema-validation
 * error (`[invalid_schema]`, e.g. ">4 x-table-column fields") must surface
 * verbatim — the old status-only fallback treated any 4xx as "already
 * exists", retried with `action: update`, and buried the real message under
 * a misleading "Entity type not found" (issue #1177). The 409 fallback stays
 * as a belt-and-braces signal for duplicate paths that predate the codes.
 */
export declare function isDuplicateError(err: ApiError): boolean;
interface ResolvedClient {
    client: ApplyClient;
    apiBaseUrl: string;
    orgSlug: string;
}
export declare function resolveApplyClient(opts: {
    url?: string;
    org?: string;
    fetchImpl?: typeof fetch;
}): Promise<ResolvedClient>;
export {};
//# sourceMappingURL=client.d.ts.map