This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
// The ports the knowledge module's use cases are written against, and the tokens the composition
|
||||
// roots bind. Core imports no SDK and no adapter (spec §2): the search service and the enablement
|
||||
// platform are shapes, and adapters/ is the only place an SDK exists.
|
||||
|
||||
import { type Container, type TenantScope, token } from "@conusai-kit/core"
|
||||
import type { DataSource } from "@conusai-kit/data"
|
||||
import type { FilesDeps } from "@conusai-kit/files"
|
||||
import type { EnablementReason, ServiceState } from "../contract/index.js"
|
||||
import { KNOWLEDGE_FLAG_DEFAULTS } from "../contract/index.js"
|
||||
|
||||
/** A project scope: collections and documents live at this level. */
|
||||
export type KnowledgeScope = TenantScope & { workspaceId: string; projectId: string }
|
||||
|
||||
/** An org scope: the enablement configuration is the organization's own. */
|
||||
export type KnowledgeOrgScope = TenantScope & { orgId: string }
|
||||
|
||||
export interface KnowledgeDeps {
|
||||
readonly data: DataSource
|
||||
readonly files: FilesDeps
|
||||
readonly flags: KnowledgeFlags
|
||||
/**
|
||||
* The search service. Present for the effect procedure (search) and the status read; a write
|
||||
* procedure's deps never carry it, because resolving an effect port from a write is a kind
|
||||
* violation — the write only records the row and publishes the event.
|
||||
*/
|
||||
readonly search?: KnowledgeSearchService | undefined
|
||||
}
|
||||
|
||||
/** What a chunk knows about where it came from: the citation a search result carries. */
|
||||
export interface KnowledgeChunk {
|
||||
/** 1-based position of the chunk in the document. */
|
||||
position: number
|
||||
text: string
|
||||
}
|
||||
|
||||
/** One cited search answer. */
|
||||
export interface KnowledgeHit {
|
||||
fileId: string
|
||||
filename: string
|
||||
collectionName: string
|
||||
/** 0..1; higher is closer. */
|
||||
score: number
|
||||
chunk: number
|
||||
snippet: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The search service: index a document's chunks, search a collection with citations, remove a
|
||||
* document, and report readiness. Two adapters ship with the module: the local vector store
|
||||
* (embeddings through the AI gateway, chunks in the bound vector adapter) and the platform's
|
||||
* managed documents-index service. Embedding leaves the process, so this port is an effect.
|
||||
*/
|
||||
export interface KnowledgeSearchService {
|
||||
readiness(scope: KnowledgeScope): Promise<{ state: ServiceState; reasons: string[] }>
|
||||
index(
|
||||
scope: KnowledgeScope,
|
||||
input: { collectionName: string; fileId: string; filename: string; bytes: Uint8Array },
|
||||
): Promise<void>
|
||||
search(
|
||||
scope: KnowledgeScope,
|
||||
input: { collectionName: string; query: string; limit: number; actorId: string },
|
||||
): Promise<readonly KnowledgeHit[]>
|
||||
remove(scope: KnowledgeScope, input: { collectionName: string; fileId: string }): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* The enablement platform: the checks and the provisioning the enablement flow needs, over the
|
||||
* platform's own APIs. `checkEligibility` is where the plan capability and the rollout flags
|
||||
* surface as typed reasons; `indexingQuota` answers today's remaining indexing volume;
|
||||
* `linkManagedQdrant` provisions (or returns the existing) managed Qdrant backing service;
|
||||
* `probeQdrant` validates an operator-configured endpoint before its configuration is persisted.
|
||||
*/
|
||||
export interface DocumentAiPlatform {
|
||||
checkEligibility(
|
||||
scope: KnowledgeOrgScope,
|
||||
): Promise<{ ok: true } | { ok: false; reason: EnablementReason; detail?: string }>
|
||||
indexingQuota(scope: KnowledgeOrgScope): Promise<number>
|
||||
linkManagedQdrant(scope: KnowledgeOrgScope): Promise<{ serviceId: string; url: string | null }>
|
||||
probeQdrant(url: string, apiKey: string): Promise<{ ok: true } | { ok: false; detail: string }>
|
||||
/** The operator-configured endpoint+key the deployment carries, when it carries one. */
|
||||
operatorQdrant(): { url: string; apiKey: string } | null
|
||||
}
|
||||
|
||||
/** What a flag service answers for the project a call runs in. */
|
||||
export interface KnowledgeFlags {
|
||||
enabled(flag: keyof typeof KNOWLEDGE_FLAG_DEFAULTS, scope: KnowledgeScope): boolean | Promise<boolean>
|
||||
}
|
||||
|
||||
/** Bound by an app that evaluates flags (the platform's flag service). Unbound: the manifest default. */
|
||||
export const KNOWLEDGE_FLAGS = token<KnowledgeFlags>("knowledge.flags")
|
||||
|
||||
export const defaultFlags: KnowledgeFlags = { enabled: (flag) => KNOWLEDGE_FLAG_DEFAULTS[flag] }
|
||||
|
||||
export const flagsOf = (container: Container): KnowledgeFlags =>
|
||||
container.has(KNOWLEDGE_FLAGS) ? container.resolve(KNOWLEDGE_FLAGS) : defaultFlags
|
||||
|
||||
/** Bound by the worker and the web shell with the module's search backend adapter. */
|
||||
export const KNOWLEDGE_SEARCH = token<KnowledgeSearchService>("knowledge.search", { access: "effect" })
|
||||
|
||||
/**
|
||||
* The embeddings the local vector backend chunks and queries with: the platform's embeddings
|
||||
* endpoint through the AI gateway. Declared here (not reached through another module's token) so
|
||||
* the module's boundary stays its own; the composition root binds it to the gateway client.
|
||||
*/
|
||||
export interface KnowledgeEmbeddings {
|
||||
embed(texts: readonly string[], options?: { inputType?: "passage" | "query" }): Promise<readonly number[][]>
|
||||
}
|
||||
|
||||
export const KNOWLEDGE_EMBEDDINGS = token<KnowledgeEmbeddings>("knowledge.embeddings", { access: "effect" })
|
||||
|
||||
/** Bound by the web shell (the enablement flow runs in a request). */
|
||||
export const KNOWLEDGE_ENABLEMENT = token<DocumentAiPlatform>("knowledge.enablement", { access: "effect" })
|
||||
|
||||
/** The per-run scope, under the module's own name (decision AT-F6). */
|
||||
export const KNOWLEDGE_SCOPE = token<TenantScope>("knowledge.scope")
|
||||
|
||||
export const knowledgeEnabled = async (deps: KnowledgeDeps, scope: KnowledgeScope): Promise<boolean> =>
|
||||
deps.flags.enabled("knowledge.enabled", scope)
|
||||
Reference in New Issue
Block a user