// The enablement platform over the platform's own APIs, server-side. Eligibility reads the // rollout flags (agent_document_tools, agent_knowledge_tools) from the flag snapshot and probes // the AI tools registry for the document tool family — discovery is the cheap plan/binding/flag // filter, and "a working OCR endpoint does not by itself make OCR a discoverable tool". The // managed Qdrant link goes through the providers API (the workspace services surface): an existing // link is reused, so re-running the enablement flow never provisions a second service. The quota // pre-check has no platform API yet (the metering slice owns it); the indexing calls themselves // carry the typed quota_exhausted refusal at run time. import { createRegistryClient, ToolIndexingError } from "@conusai-sdk/ai" import { ConusAIClient, type CreateServiceRequest, type ServiceInfo } from "@conusai-sdk/node-sdk" import type { EnablementReason } from "../contract/index.js" import type { DocumentAiPlatform } from "../core/ports.js" export const AGENT_DOCUMENT_TOOLS_FLAG = "agent_document_tools" export const AGENT_KNOWLEDGE_TOOLS_FLAG = "agent_knowledge_tools" const FLAG_KEYS = [AGENT_DOCUMENT_TOOLS_FLAG, AGENT_KNOWLEDGE_TOOLS_FLAG] as const export interface SdkDocumentAiPlatformOptions { /** The deployment contract's API URL and token. */ apiUrl: string token: string /** The organization's slug and the workspace the app links services into. */ orgSlug: string workspaceSlug: string /** The deployment environment, for the operator-configured Qdrant variables. */ env?: Record fetch?: typeof fetch } export function sdkDocumentAiPlatform(options: SdkDocumentAiPlatformOptions): DocumentAiPlatform { const http = options.fetch ?? fetch const rolloutFlagsOff = async (): Promise => { try { const response = await http(new URL(`${options.apiUrl.replace(/\/+$/, "")}/flags/snapshot`), { headers: { authorization: `Bearer ${options.token}`, accept: "application/json" }, signal: AbortSignal.timeout(5_000), }) if (!response.ok) return null // the flags service is not the eligibility authority const body = (await response.json()) as { flags?: Array<{ key?: string; enabled?: boolean }> } for (const flag of body.flags ?? []) { if ( typeof flag.key === "string" && (FLAG_KEYS as readonly string[]).includes(flag.key) && flag.enabled === false ) { return flag.key } } return null } catch { return null } } return { async checkEligibility(): Promise<{ ok: true } | { ok: false; reason: EnablementReason; detail?: string }> { // The rollout flags first: a kill switch or a disabled feature refuses before any probe. const off = await rolloutFlagsOff() if (off !== null) return { ok: false, reason: "feature_disabled", detail: `the ${off} flag is off` } // The plan probe: can this deployment discover the document tool family at all? try { const registry = createRegistryClient({ env: { CONUSAI_API_URL: options.apiUrl, CONUSAI_API_TOKEN: options.token }, fetch: http, }) const found = await registry.tools.search("read and index documents") if (found.length === 0) { return { ok: false, reason: "plan_required", detail: "the ai_tools plan capability lists no document tools" } } return { ok: true } } catch (error) { if (error instanceof ToolIndexingError) { return { ok: false, reason: "feature_disabled", detail: "the tool registry is still indexing" } } return { ok: false, reason: "plan_required", detail: "the AI tools registry did not answer the eligibility probe", } } }, async indexingQuota() { // No pre-check API exists on the platform yet; the call-time quota refusal is the hard // gate. Until the limit endpoint lands, the admission check answers "within quota". return Number.MAX_SAFE_INTEGER }, async linkManagedQdrant() { const client = new ConusAIClient({ baseUrl: options.apiUrl, apiKey: options.token }) const path = { org_slug: options.orgSlug, workspace_slug: options.workspaceSlug } const existing = await client.listServices({ path }) const services = (existing.data ?? []) as ServiceInfo[] const reused = services.find((service) => service.service_type === "qdrant" && service.status !== "deleted") if (reused) return { serviceId: String(reused.id), url: null } const body: CreateServiceRequest = { name: "knowledge-vectors", service_type: "qdrant", parameters: {} } const created = await client.createService({ path, body }) const status = created.response?.status ?? 0 if (status === 403) { throw Object.assign(new Error("the plan does not allow a managed Qdrant service"), { reason: "plan_required" as const, }) } if (status !== 201 || !created.data) { throw new Error(`the providers API answered ${status} for the Qdrant link`) } return { serviceId: String(created.data.id), url: null } }, async probeQdrant(url, apiKey) { try { const response = await http(new URL("/collections", url), { headers: { "api-key": apiKey }, signal: AbortSignal.timeout(5_000), }) if (response.ok) return { ok: true } return { ok: false, detail: `the endpoint answered ${response.status}` } } catch (error) { return { ok: false, detail: error instanceof Error ? (error.message.split("\n")[0] ?? error.message) : "the endpoint did not answer", } } }, operatorQdrant() { // The operator-configured endpoint: exactly the variables a linked Qdrant service injects, // set by hand in the deployment instead. The key is validated by probing; it is never // persisted — only the endpoint is recorded. const env = options.env ?? process.env const url = env.QDRANT_URL?.trim() const apiKey = env.QDRANT_API_KEY?.trim() if (!url || !apiKey) return null return { url, apiKey } }, } }