Files
WeKnora/pluginsdk/pluginapi/openapi.yaml
T
lyingbug ff49f9491d feat(plugin): Host API with a key-value store, and document parsers from plugins (#3721)
* feat(plugin): Host API with plugin tokens and a key-value store
* feat(plugin): document parsers from code plugins
* docs(pluginsdk): parsers and the Host API
2026-09-26 14:45:39 +08:00

375 lines
13 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" }
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 }
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 } }
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" }