mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-13 00:44:45 +08:00
520 lines
18 KiB
YAML
520 lines
18 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: WeKnora extension protocol
|
|
version: "1"
|
|
description: |
|
|
What WeKnora calls on a code plugin. Every POST takes an Envelope and
|
|
answers {"output": ...}, or streams NDJSON Events. Failures answer a
|
|
non-2xx status with an ErrorBody. Within a major version the protocol
|
|
only grows; both sides ignore fields they do not know.
|
|
|
|
Host plugins (started by the WeKnora plugin host) are called over the
|
|
socket from their handshake with `Authorization: Bearer <token>`. Remote
|
|
plugins are called over HTTPS and verify `X-WeKnora-Signature`:
|
|
hex HMAC-SHA256 of "<X-WeKnora-Timestamp>.<body>" with the shared secret,
|
|
rejecting timestamps more than 5 minutes off.
|
|
servers:
|
|
- url: http://plugin
|
|
security:
|
|
- hostToken: []
|
|
- signature: []
|
|
paths:
|
|
/v1/manifest:
|
|
get:
|
|
summary: What this process is and serves
|
|
responses:
|
|
"200":
|
|
description: The manifest
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/Manifest" }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/health:
|
|
get:
|
|
summary: Liveness
|
|
responses:
|
|
"200":
|
|
description: Healthy
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [status]
|
|
properties: { status: { const: ok } }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/config/validate:
|
|
post:
|
|
summary: Check the plugin's system and tenant configuration
|
|
requestBody: { $ref: "#/components/requestBodies/Envelope" }
|
|
responses:
|
|
"200": { $ref: "#/components/responses/EmptyOutput" }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/websearch/{id}/search:
|
|
post:
|
|
summary: Web search (WebSearchProvider.Search)
|
|
parameters: [{ $ref: "#/components/parameters/ContributionID" }]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Envelope"
|
|
- properties: { input: { $ref: "#/components/schemas/SearchInput" } }
|
|
responses:
|
|
"200":
|
|
description: Results
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [output]
|
|
properties: { output: { $ref: "#/components/schemas/SearchOutput" } }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/connectors/{id}/validate:
|
|
post:
|
|
summary: Check a data source configuration (Connector.Validate)
|
|
parameters: [{ $ref: "#/components/parameters/ContributionID" }]
|
|
requestBody: { $ref: "#/components/requestBodies/Envelope" }
|
|
responses:
|
|
"200": { $ref: "#/components/responses/EmptyOutput" }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/connectors/{id}/list-resources:
|
|
post:
|
|
summary: List syncable resources under a parent (Connector.ListResources)
|
|
parameters: [{ $ref: "#/components/parameters/ContributionID" }]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Envelope"
|
|
- properties:
|
|
input:
|
|
type: object
|
|
properties: { parentId: { type: string } }
|
|
responses:
|
|
"200":
|
|
description: Resources
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [output]
|
|
properties:
|
|
output:
|
|
type: object
|
|
required: [resources]
|
|
properties:
|
|
resources: { type: array, items: { $ref: "#/components/schemas/Resource" } }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/connectors/{id}/resolve-ancestors:
|
|
post:
|
|
summary: Ancestors a lazy picker must expand to show a selection
|
|
parameters: [{ $ref: "#/components/parameters/ContributionID" }]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Envelope"
|
|
- properties:
|
|
input:
|
|
type: object
|
|
properties: { resourceIds: { type: array, items: { type: string } } }
|
|
responses:
|
|
"200":
|
|
description: Ancestor IDs
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [output]
|
|
properties:
|
|
output:
|
|
type: object
|
|
required: [ancestors]
|
|
properties: { ancestors: { type: array, items: { type: string } } }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/connectors/{id}/fetch:
|
|
post:
|
|
summary: Stream a full or incremental sync
|
|
description: |
|
|
Answers NDJSON: "item" events carry a FetchedItem, "checkpoint"
|
|
events a Cursor (a complete, resumable snapshot), and the stream
|
|
ends with "end" (data = final Cursor) or "error". A stream that
|
|
stops without either is treated as interrupted.
|
|
parameters: [{ $ref: "#/components/parameters/ContributionID" }]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Envelope"
|
|
- properties: { input: { $ref: "#/components/schemas/FetchInput" } }
|
|
responses:
|
|
"200":
|
|
description: Event stream
|
|
content:
|
|
application/x-ndjson:
|
|
schema: { $ref: "#/components/schemas/Event" }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/parsers/{id}/parse:
|
|
post:
|
|
summary: Parse a document into Markdown (DocReader.Read)
|
|
description: |
|
|
The file arrives as base64 content (or a URL). Answer Markdown and
|
|
the images it references; WeKnora stores the images and chunks the
|
|
text. A non-retryable error fails the document for good; a
|
|
retryable one (unavailable, rate_limited) is retried later.
|
|
parameters: [{ $ref: "#/components/parameters/ContributionID" }]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Envelope"
|
|
- properties: { input: { $ref: "#/components/schemas/ParseInput" } }
|
|
responses:
|
|
"200":
|
|
description: The parsed document
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [output]
|
|
properties: { output: { $ref: "#/components/schemas/ParseOutput" } }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/ui/request:
|
|
post:
|
|
summary: Answer a request from one of the plugin's pages
|
|
description: |
|
|
Plugin pages (pages, settingsSections, kbTabs) run in a sandboxed
|
|
iframe and reach the plugin only through the WeKnora bridge.
|
|
WeKnora checks the caller may open the page (its minRole) and
|
|
forwards the request with the usual envelope. Method and path are
|
|
the page's own routing. Plugins with pages that make no requests
|
|
answer not_found. The manifest lists "ui": ["request"] under
|
|
contributes when the plugin answers.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Envelope"
|
|
- properties: { input: { $ref: "#/components/schemas/UIRequest" } }
|
|
responses:
|
|
"200":
|
|
description: What the page receives
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [output]
|
|
properties: { output: { $ref: "#/components/schemas/UIResponse" } }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/events:
|
|
post:
|
|
summary: Receive an event the plugin subscribed to (permissions.events)
|
|
description: |
|
|
At least once: a retryable error or a timeout brings the same event
|
|
back later with the same id and a higher attempt, so handle it
|
|
idempotently. A non-retryable error drops it. Ignore unknown types.
|
|
The manifest lists "events": ["handler"] under contributes when the
|
|
plugin handles events.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Envelope"
|
|
- properties: { input: { $ref: "#/components/schemas/EventDelivery" } }
|
|
responses:
|
|
"200":
|
|
description: Handled
|
|
content:
|
|
application/json:
|
|
schema: { type: object, required: [output], properties: { output: { type: object } } }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
/v1/webhooks/{id}:
|
|
post:
|
|
summary: Answer an inbound call to webhook id (contributes.webhooks)
|
|
description: |
|
|
Third parties call a secret per-workspace URL of WeKnora; WeKnora
|
|
relays the call here with that workspace's envelope. Verify the
|
|
sender (e.g. a signature header) with the workspace configuration.
|
|
parameters: [{ $ref: "#/components/parameters/ContributionID" }]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Envelope"
|
|
- properties: { input: { $ref: "#/components/schemas/WebhookRequest" } }
|
|
responses:
|
|
"200":
|
|
description: The answer to the caller
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [output]
|
|
properties: { output: { $ref: "#/components/schemas/WebhookResponse" } }
|
|
default: { $ref: "#/components/responses/Error" }
|
|
components:
|
|
securitySchemes:
|
|
hostToken: { type: http, scheme: bearer }
|
|
signature: { type: apiKey, in: header, name: X-WeKnora-Signature }
|
|
parameters:
|
|
ContributionID:
|
|
name: id
|
|
in: path
|
|
required: true
|
|
description: The contribution's local ID from plugin.yaml
|
|
schema: { type: string }
|
|
requestBodies:
|
|
Envelope:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/Envelope" }
|
|
responses:
|
|
EmptyOutput:
|
|
description: Accepted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [output]
|
|
properties: { output: { type: object } }
|
|
Error:
|
|
description: A failed call
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/ErrorBody" }
|
|
schemas:
|
|
Envelope:
|
|
type: object
|
|
required: [context, config]
|
|
properties:
|
|
context:
|
|
type: object
|
|
required: [tenantId]
|
|
properties:
|
|
tenantId: { type: integer }
|
|
userId: { type: string }
|
|
locale: { type: string }
|
|
requestId: { type: string }
|
|
deadline: { type: string, format: date-time }
|
|
host:
|
|
type: object
|
|
description: Host API access; absent without granted scopes
|
|
properties:
|
|
url: { type: string }
|
|
token: { type: string }
|
|
webhooks:
|
|
type: object
|
|
description: This workspace's webhook URLs by webhook ID, when WeKnora knows its public address
|
|
additionalProperties: { type: string }
|
|
config:
|
|
type: object
|
|
description: Configuration scopes, secrets decrypted, never merged
|
|
properties:
|
|
system: { type: object }
|
|
tenant: { type: object }
|
|
instance: { type: object }
|
|
input: {}
|
|
Manifest:
|
|
type: object
|
|
required: [id, version, apiVersion, contributes]
|
|
properties:
|
|
id: { type: string }
|
|
version: { type: string }
|
|
apiVersion: { const: weknora.plugin/v1 }
|
|
contributes:
|
|
type: object
|
|
additionalProperties: { type: array, items: { type: string } }
|
|
SearchInput:
|
|
type: object
|
|
required: [query]
|
|
properties:
|
|
query: { type: string }
|
|
maxResults: { type: integer }
|
|
includeDate: { type: boolean }
|
|
region: { type: string }
|
|
freshness: { type: string }
|
|
SearchOutput:
|
|
type: object
|
|
required: [results]
|
|
properties:
|
|
results:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [title, url]
|
|
properties:
|
|
title: { type: string }
|
|
url: { type: string }
|
|
snippet: { type: string }
|
|
content: { type: string }
|
|
age: { type: string }
|
|
publishedAt: { type: string, format: date-time }
|
|
Resource:
|
|
type: object
|
|
required: [externalId, name]
|
|
properties:
|
|
externalId: { type: string }
|
|
name: { type: string }
|
|
type: { type: string }
|
|
description: { type: string }
|
|
url: { type: string }
|
|
modifiedAt: { type: string, format: date-time }
|
|
parentId: { type: string }
|
|
hasChildren: { type: boolean }
|
|
metadata: { type: object }
|
|
FetchInput:
|
|
type: object
|
|
required: [mode]
|
|
properties:
|
|
mode: { enum: [full, incremental] }
|
|
cursor: { $ref: "#/components/schemas/Cursor" }
|
|
resourceIds: { type: array, items: { type: string } }
|
|
Cursor:
|
|
type: object
|
|
properties:
|
|
lastSyncTime: { type: string, format: date-time }
|
|
state: { type: object }
|
|
FetchedItem:
|
|
type: object
|
|
required: [externalId, title]
|
|
properties:
|
|
externalId: { type: string }
|
|
title: { type: string }
|
|
content: { type: string, contentEncoding: base64 }
|
|
contentType: { type: string }
|
|
fileName: { type: string }
|
|
url: { type: string }
|
|
updatedAt: { type: string, format: date-time }
|
|
createdAt: { type: string, format: date-time }
|
|
metadata: { type: object, additionalProperties: { type: string } }
|
|
isDeleted: { type: boolean }
|
|
sourceResourceId: { type: string }
|
|
ParseInput:
|
|
type: object
|
|
properties:
|
|
fileName: { type: string }
|
|
fileType: { type: string, description: "Lower-case extension without the dot" }
|
|
content: { type: string, contentEncoding: base64 }
|
|
url: { type: string }
|
|
title: { type: string }
|
|
ParseOutput:
|
|
type: object
|
|
required: [markdown]
|
|
properties:
|
|
markdown: { type: string }
|
|
images:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [originalRef, data]
|
|
properties:
|
|
originalRef: { type: string }
|
|
mimeType: { type: string }
|
|
data: { type: string, contentEncoding: base64 }
|
|
metadata: { type: object, additionalProperties: { type: string } }
|
|
EventDelivery:
|
|
type: object
|
|
required: [id, type, occurredAt, attempt]
|
|
properties:
|
|
id: { type: string, description: Idempotency key; kept across retries }
|
|
type: { enum: [knowledge.ingested, knowledge.failed, knowledge.deleted, chat.answered] }
|
|
occurredAt: { type: string, format: date-time }
|
|
attempt: { type: integer, minimum: 1 }
|
|
data:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/KnowledgeEventData"
|
|
- $ref: "#/components/schemas/ChatEventData"
|
|
KnowledgeEventData:
|
|
type: object
|
|
required: [knowledgeBaseId, knowledgeId]
|
|
properties:
|
|
knowledgeBaseId: { type: string }
|
|
knowledgeId: { type: string }
|
|
title: { type: string }
|
|
fileName: { type: string }
|
|
fileType: { type: string }
|
|
source: { type: string }
|
|
error: { type: string }
|
|
ChatEventData:
|
|
type: object
|
|
required: [sessionId, messageId, answer]
|
|
properties:
|
|
sessionId: { type: string }
|
|
messageId: { type: string }
|
|
agentId: { type: string }
|
|
userId: { type: string }
|
|
question: { type: string }
|
|
answer: { type: string }
|
|
WebhookRequest:
|
|
type: object
|
|
required: [method, path]
|
|
properties:
|
|
method: { type: string }
|
|
path: { type: string }
|
|
query: { type: string }
|
|
headers: { type: object, additionalProperties: { type: string } }
|
|
body: { type: string, contentEncoding: base64 }
|
|
WebhookResponse:
|
|
type: object
|
|
properties:
|
|
status: { type: integer }
|
|
contentType: { type: string }
|
|
body: { type: string, contentEncoding: base64 }
|
|
UIRequest:
|
|
type: object
|
|
required: [mount, method, path]
|
|
properties:
|
|
mount: { type: string, description: '"<point>/<id>" of the page, e.g. pages/links' }
|
|
method: { type: string }
|
|
path: { type: string }
|
|
body: { description: JSON the page sent }
|
|
role: { enum: [viewer, contributor, admin, owner] }
|
|
UIResponse:
|
|
type: object
|
|
properties:
|
|
status: { type: integer, description: HTTP-style status for the page; 200 when absent }
|
|
body: { description: JSON returned to the page }
|
|
Event:
|
|
type: object
|
|
required: [type]
|
|
properties:
|
|
type: { enum: [item, checkpoint, progress, log, error, end] }
|
|
data: {}
|
|
error: { $ref: "#/components/schemas/Error" }
|
|
message: { type: string }
|
|
level: { enum: [debug, info, warn, error] }
|
|
Error:
|
|
type: object
|
|
required: [code, message]
|
|
properties:
|
|
code: { enum: [invalid_config, unauthorized, not_found, rate_limited, unavailable, bad_request, internal] }
|
|
message: { type: string }
|
|
retryable: { type: boolean }
|
|
details:
|
|
type: object
|
|
properties:
|
|
fields: { type: object, additionalProperties: { type: string } }
|
|
retryAfter: { type: integer }
|
|
ErrorBody:
|
|
type: object
|
|
required: [error]
|
|
properties:
|
|
error: { $ref: "#/components/schemas/Error" }
|