Files
WeKnora/pluginsdk/pluginapi/connector.go
wizardchen 39045543d1 feat(pluginsdk): extension protocol v1, Go SDK and conformance suite
A new module, github.com/Tencent/WeKnora/pluginsdk, so plugin authors import
the protocol without WeKnora's dependency tree:

- pluginapi: the protocol (envelope with context and the system / tenant /
  instance config scopes, error codes, NDJSON stream events, web search and
  connector messages, the host handshake line, HMAC request signing) plus
  openapi.yaml describing it for other languages.
- pluginsdk: build a plugin in Go. Register web search providers and
  connectors, then Serve: started by the plugin host it listens on the
  socket it is given, prints the handshake and accepts only the host token;
  run on its own it verifies signed requests. Envelope deadlines reach the
  handler's context, panics become internal errors, and fetches stream
  items and checkpoints.
- client: calls a plugin over a handshake socket or HTTPS, with typed
  transport errors; WeKnora's adapters and the conformance suite share it.
- conformance + cmd/weknora-plugin-conformance: checks a running plugin
  answers discovery, rejects unauthenticated and malformed calls, and
  answers every endpoint it claims with an output or a protocol error.

A test keeps openapi.yaml and the SDK routes in step. A new workflow builds
and tests the module on Linux, macOS and Windows; go-lint lints it.
2026-09-25 22:26:29 +08:00

97 lines
3.8 KiB
Go

package pluginapi
import "time"
// ConnectorValidatePath is the validate endpoint of connector id.
func ConnectorValidatePath(id string) string { return "/v1/connectors/" + id + "/validate" }
// ConnectorListResourcesPath is the list-resources endpoint of connector id.
func ConnectorListResourcesPath(id string) string { return "/v1/connectors/" + id + "/list-resources" }
// ConnectorResolveAncestorsPath is the resolve-ancestors endpoint of connector id.
func ConnectorResolveAncestorsPath(id string) string {
return "/v1/connectors/" + id + "/resolve-ancestors"
}
// ConnectorFetchPath is the streaming fetch endpoint of connector id.
func ConnectorFetchPath(id string) string { return "/v1/connectors/" + id + "/fetch" }
// The connector's instance configuration arrives in Envelope.Config.Instance
// as {"credentials": {...}, "settings": {...}, "resourceIds": [...]}.
// ListResourcesInput asks for the resources under ParentID ("" = top level).
type ListResourcesInput struct {
ParentID string `json:"parentId,omitempty"`
}
// ListResourcesOutput lists resources a user can pick for syncing.
type ListResourcesOutput struct {
Resources []Resource `json:"resources"`
}
// Resource is something a data source can sync (a space, folder, feed).
type Resource struct {
ExternalID string `json:"externalId"`
Name string `json:"name"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
URL string `json:"url,omitempty"`
ModifiedAt *time.Time `json:"modifiedAt,omitempty"`
ParentID string `json:"parentId,omitempty"`
HasChildren bool `json:"hasChildren,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
// ResolveAncestorsInput asks which ancestors of the given resources a
// lazily loaded picker must expand.
type ResolveAncestorsInput struct {
ResourceIDs []string `json:"resourceIds"`
}
// ResolveAncestorsOutput is the deduplicated set of ancestor IDs.
type ResolveAncestorsOutput struct {
Ancestors []string `json:"ancestors"`
}
// Fetch modes.
const (
FetchFull = "full"
FetchIncremental = "incremental"
)
// FetchInput starts or resumes a sync. The answer is a stream of "item"
// events (FetchedItem), "checkpoint" events (Cursor) at page boundaries and
// an "end" event carrying the final Cursor.
type FetchInput struct {
Mode string `json:"mode"`
// Cursor is the last checkpoint or final cursor; nil starts from scratch.
Cursor *Cursor `json:"cursor,omitempty"`
// ResourceIDs are the selected resources (also in config.instance).
ResourceIDs []string `json:"resourceIds,omitempty"`
}
// Cursor is the connector's resumable state. WeKnora stores it and hands it
// back; its content is the plugin's business. A checkpoint must be a
// complete snapshot: resuming from it reproduces all progress so far.
type Cursor struct {
LastSyncTime *time.Time `json:"lastSyncTime,omitempty"`
State map[string]any `json:"state,omitempty"`
}
// FetchedItem is one document fetched from the source.
type FetchedItem struct {
ExternalID string `json:"externalId"`
Title string `json:"title"`
// Content is the document body; Markdown preferred. JSON carries it as
// base64.
Content []byte `json:"content,omitempty"`
ContentType string `json:"contentType,omitempty"`
FileName string `json:"fileName,omitempty"`
URL string `json:"url,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
CreatedAt *time.Time `json:"createdAt,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
IsDeleted bool `json:"isDeleted,omitempty"`
SourceResourceID string `json:"sourceResourceId,omitempty"`
}