ext

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 6 Imported by: 0

README

alist-ext

AList v4 插件 SDK(Go)。插件以独立进程运行,经 hashicorp/go-plugin 的 gRPC 通道与 AList 宿主通信;本仓库提供插件侧需要的全部契约与运行时:

  • ext — 扩展契约:ManifestExtension、各 Kind 接口(StorageDriverAuthnMethodSearchIndexer 等)与可选能力接口(CopierPutterRapidPutter …)
  • ext/grpcplugin — 插件侧运行时:实现接口后一行 grpcplugin.Serve(impl) 即可作为插件进程启动
  • ext/grpcproto — gRPC wire 契约(buf 生成)与编解码

使用

go get github.com/AlistGo/alist-ext@latest

最小存储驱动插件:

package main

import (
	ext "github.com/AlistGo/alist-ext"
	"github.com/AlistGo/alist-ext/grpcplugin"
)

func main() {
	grpcplugin.Serve(myDriver{}) // myDriver 实现 ext.StorageDriver
}

脚手架、打包与签名由 AList 主程序的隐藏子命令提供:

alist plugin new my-driver   # 生成插件工程骨架
alist plugin sign / pack     # 签名与打包

版本与来源

本仓库是 AlistGo/alist v4 主仓 ext/ 目录的发布快照;开发在主仓进行,按版本打 tag 同步到这里。插件工程请依赖本仓库的 tag,而不是主仓路径。

License

MIT(注意:AList 主程序本身为 AGPL-3.0;SDK 采用 MIT 以便闭源/商业插件合法引用)。

Documentation

Overview

Package ext is the frozen public SDK boundary between the AList core (the "host") and its extensions — storage drivers today, and (reserved) the other extension Kinds tomorrow.

STABILITY: v0.x — UNSTABLE. Per docs/v4-plugin-sdk-9.0-resolution.md §0.3 the contract is intentionally NOT frozen until v4 GA: it must first be exercised by the Step 10 install-wizard and the gRPC host (internal/extension/grpc) consumers and validated against representative driver parity. Until then, breaking changes may land on minor version bumps. Do not treat any type here as stable.

BOUNDARY RULE: this module MUST NOT import anything from the core's internal/ tree. It is a standalone Go module (its own go.mod; the root module pulls it in through a replace directive) precisely so that out-of-tree extensions can depend on the SDK without dragging in the whole core. Keep the dependency set to the Go standard library.

Index

Constants

View Source
const (
	ServiceStateRunning = "running"
	ServiceStateStopped = "stopped"
	ServiceStateError   = "error"
)

Service lifecycle state constants for ServiceStatus.State.

View Source
const (
	WizardInfo              = "info"
	WizardPermissionConsent = "permission-consent"
	WizardForm              = "form"
	WizardOAuth             = "oauth"
	WizardChoice            = "choice"
	WizardTest              = "test"
	WizardRisk              = "risk"
	WizardDone              = "done"
)

WizardStep kinds. The host's engine understands each kind; oauth is a seam (embedded auth broker, Step 2b) and risk is reserved for Step 11's risk interstitial — it renders whatever the manifest declares but carries no enforcement yet.

Variables

View Source
var ErrNeedAppPassword = NewError(ErrCodePermissionDenied, "account requires an app password for protocol login")

ErrNeedAppPassword is returned by Auth.Login for an account with a second factor enabled: a protocol client cannot enter a TOTP code, so it must use an app password instead of the raw account password (design §2.1).

View Source
var ErrPermissionDenied = NewError(ErrCodePermissionDenied, "permission denied")

ErrPermissionDenied is the sentinel a Host capability returns when the plugin's Grant does not authorize the call (design §6.3): an ungranted capability, a network host outside Grant.Network, or an FS/Sign target the plugin's principal cannot reach. It carries ErrCodePermissionDenied so it maps back to errs.PermissionDenied across the boundary.

Functions

func RefKey

func RefKey(ref ObjRef) string

RefKey is the canonical serialization of an ObjRef used as the map key in a FileDecorator result and in the fs/fields backfill response. It is the shared contract both the host and a decorator key by. A borrowed-fs ref (empty StorageID, virtual path in Anchor) keys by its path directly; a storage-anchored ref prefixes the storage id so the two ref worlds never collide.

Types

type APIRoute

type APIRoute interface {
	Extension

	// Routes declares the sub-routes this plugin serves. The host validates and
	// mounts each; an empty/invalid prefix or one that escapes the plugin
	// namespace is rejected.
	Routes() []RouteDecl

	// Handle serves one proxied request. req.URL carries the sub-path BELOW the
	// plugin's mount (the host strips the /api/v4/ext/{id} prefix). The response
	// is written back to the client verbatim. Handle must not assume it can reach
	// the host beyond its granted capabilities.
	Handle(ctx context.Context, req *HTTPRequest) (*HTTPResponse, error)
}

APIRoute lets a plugin mount HTTP sub-routes the host proxies to it: a webhook receiver, a share-management API, a public share landing page (design §2.5 FeatureBundle = APIRoute + PublicPage + …). The plugin declares its routes and one Handle entry point; the host owns the actual mounting, the reserved-prefix isolation, and the authentication boundary.

Two boundaries the host enforces around this interface:

  • Namespacing / reserved words: a plugin's routes mount under the plugin's own namespace (/api/v4/ext/{plugin-id}/…), so they can never collide with a core route. A route that instead asks for a top-level short prefix (a share short-link) is checked against the host's reserved-prefix table (CheckReservedPrefix) and refused on conflict — at install/registration.
  • Authentication: a RouteDecl with Public=false mounts behind the auth middleware (only logged-in callers reach Handle). Public=true mounts under the unauthenticated public group AND is gated on the "public_routes" grant; its Handle runs with a delegated-only Host (design §2.5), so a public route can borrow the fs only through an owner-delegated principal, never the plugin's own grant.

type AccessProtocol

type AccessProtocol interface {
	Extension
	// ProtocolInfo describes how the host binds this protocol: its scheme, its
	// transport, the default port, and — for TransportHTTP — where the host mounts
	// the handler.
	ProtocolInfo() ProtocolInfo

	// ServeHTTP handles ONE inbound HTTP request for a TransportHTTP protocol
	// (WebDAV/S3/MCP). The host proxies the client's request here after mounting
	// the protocol at ProtocolInfo.MountPattern; the plugin authenticates it via
	// host.Auth() and performs every fs operation via host.FS(). The request and
	// response are the serializable HTTPRequest/HTTPResponse (the frozen wire
	// contract, same as APIRoute) so the same handler works in-process or over
	// gRPC. Only meaningful for TransportHTTP; a TransportConn plugin returns
	// ErrCodeNotImplement.
	ServeHTTP(ctx context.Context, req *HTTPRequest) (*HTTPResponse, error)

	// HandleConn services ONE accepted raw connection for a TransportConn protocol
	// (FTP/SFTP). The host owns the listener (port/TLS from settings) and hands the
	// plugin a live net.Conn; the plugin drives the wire protocol and, per command,
	// authenticates via host.Auth() and acts via host.FS(). HandleConn must return
	// when the connection closes or ctx is cancelled (host shutdown).
	//
	// NATIVE ONLY: a raw net.Conn is not serializable, so a TransportConn plugin
	// runs in-process (or as a trusted official binary), never behind the gRPC
	// bridge. The trust requirement is the point — a plugin holding a bare socket
	// is outside the capability sandbox for that connection's bytes, so only signed
	// official / built-in code provides one (design §5, §2.1 decision 4). Only
	// meaningful for TransportConn; a TransportHTTP plugin returns
	// ErrCodeNotImplement.
	HandleConn(ctx context.Context, conn net.Conn) error
}

AccessProtocol exposes the AList filesystem OVER an inbound network protocol (design v4-plugin-extensions-design.md §2.1). It is the common floor beneath WebDAV / S3 / MCP (served over HTTP) and FTP / SFTP (raw byte streams). It is orthogonal to StorageDriver: a StorageDriver connects OUT to some cloud, an AccessProtocol serves the AList tree IN to a client.

The one iron security rule (design §0, §2.1): a protocol plugin NEVER makes an authorization decision. It only turns an inbound credential into a host-issued Principal via host.Auth().Login / VerifyToken / VerifyAppPassword, then does every filesystem operation through host.FS() with that Principal — and host.FS runs the core Authorize on every single call. So the v3 defects this fixes (WebDAV's per-operation re-check, S3 server auth) are satisfied structurally: the decision lives in the host, not in the plugin. A protocol plugin that tried to bypass Auth would only ever hold the empty Principal, which is its own narrow bound grant — it cannot mint a session or impersonate a user.

Because it owns sockets and speaks wire protocols it is native/official tier only (design §5): it is never sandboxed to WASM, and the raw-socket transport (FTP/SFTP) does not cross the gRPC boundary at all (see Transport).

type Action

type Action interface {
	Extension

	// Invoke runs the action named actionID for principal, with opaque args from
	// the client. principal is the host-issued identity handle of the acting user
	// (design §3) — the empty Principal means the plugin's own bound identity.
	// The returned value is serialized back to the client.
	Invoke(ctx context.Context, principal Principal, actionID string, args map[string]any) (any, error)
}

Action is a plugin operation the user triggers from the UI: a file/dir context-menu action or a global action (design §2, contributions MenuAction). The plugin declares the actions it offers as Contributions.MenuActions (id + label + target); this interface is how the host invokes the one the user picked.

The host executes an action only AFTER authorizing the caller (the /api/v4/extensions/{id}/actions/{aid} endpoint is admin-gated; per-action permission scopes are a later refinement). The action does its filesystem work through the Grant-gated Host.FS keyed off principal, so an action can never reach outside what the plugin was granted, and the core Authorize still runs on every borrowed operation.

type AddArgs

type AddArgs struct {
	URL     string `json:"url"`
	TempDir string `json:"temp_dir"`
	UID     string `json:"uid"`
}

AddArgs parameterizes one AddURL call: the source URL, the host-provided temp directory the daemon should download into, and the id of the user the download runs on behalf of.

type ArchiveDecompress

type ArchiveDecompress interface {
	ArchiveDecompress(ctx context.Context, src, dstDir ObjRef, innerPath, password string, putIntoNewDir bool) ([]Obj, error)
}

type ArchiveGetter

type ArchiveGetter interface {
	ArchiveGet(ctx context.Context, obj ObjRef, innerPath, password string) (Obj, error)
}

type ArchiveMeta

type ArchiveMeta struct {
	Comment   string `json:"comment,omitempty"`
	Encrypted bool   `json:"encrypted"`
}

ArchiveMeta is the serializable meta-info of an archive (9.0 §6). The tree structure, when present, is carried out-of-band by the host.

type ArchiveReader

type ArchiveReader interface {
	GetArchiveMeta(ctx context.Context, obj ObjRef, password string) (ArchiveMeta, error)
	ListArchive(ctx context.Context, obj ObjRef, innerPath, password string) ([]Obj, error)
	Extract(ctx context.Context, obj ObjRef, innerPath, password string) (*Link, error)
}

ArchiveReader is a driver's native archive handling (115/云盘). Distinct from the ArchiveTool Kind (9.0 §6). Extract returns a Link (§1 contract).

type AssistForm

type AssistForm struct {
	ID        string            `json:"id"`
	Title     map[string]string `json:"title"`
	Schema    *JSONSchema       `json:"schema"`
	TestProbe string            `json:"test_probe,omitempty"`
}

AssistForm is an in-place completion sub-form referenced by a field's x-assist (SDK §4.1). Submitting it makes the frontend POST /api/v4/extensions/{id}/assist/{form-id}; the returned value refills the originating field.

type AssistRef

type AssistRef struct {
	Form       string            `json:"form"`
	Permission string            `json:"permission,omitempty"`
	Label      map[string]string `json:"label,omitempty"`
}

AssistRef points a field at an AssistForm for in-place completion (SDK §4.1).

type Auth

type Auth interface {
	// Login resolves a username/password to a Principal. The host applies
	// login rate-limiting here. For a 2FA-enabled account it returns
	// ErrNeedAppPassword, forcing protocol clients onto an app password rather
	// than letting a protocol login bypass the second factor.
	Login(ctx context.Context, username, password string) (Principal, error)
	// VerifyToken resolves a bearer/session token to a Principal.
	VerifyToken(ctx context.Context, token string) (Principal, error)
	// VerifyAppPassword resolves a per-client app password to a Principal.
	VerifyAppPassword(ctx context.Context, username, appPassword string) (Principal, error)
}

Auth lets a protocol plugin turn an inbound credential into a Principal (the host runs rate-limiting / brute-force protection at this choke point) and verify tokens. Token ISSUANCE always stays in the core (design §7 decision 3): a plugin only proves "this credential is valid", the host decides what to mint.

type AuthKind

type AuthKind uint8

AuthKind classifies where in the login flow a method participates.

const (
	// AuthPrimary replaces the password as the initial authenticator (LDAP, SSO
	// acting as the sole factor). The plugin is the trusted authenticator; per
	// design §5/§6.4 a primary method is native + signed + audited only, and its
	// consent screen is marked "can assert login identity (account-takeover
	// capability)".
	AuthPrimary AuthKind = iota
	// AuthSecondFactor is a step-up factor run AFTER a password succeeds (TOTP,
	// WebAuthn). Its Begin receives the already-identified Principal; it proves
	// possession, it does not resolve identity.
	AuthSecondFactor
	// AuthFederated is an external IdP (OIDC) that may auto-provision a new user
	// — but only behind the host's default-off policy gate, and never as an
	// admin (design §2.2, §6.1).
	AuthFederated
)

type AuthMethodInfo

type AuthMethodInfo struct {
	// ID identifies the method within its extension. A single extension may
	// expose more than one method (e.g. an SSO plugin offering two IdPs), so the
	// host addresses a method by (extensionID, ID).
	ID   string   `json:"id"`
	Kind AuthKind `json:"kind"`
	// Display is the i18n label the login button / step-up prompt renders.
	Display map[string]string `json:"display,omitempty"`
}

AuthMethodInfo is the method's self-description.

type AuthNChallenge

type AuthNChallenge struct {
	// ChallengeID correlates this Begin with the eventual Finish. The plugin may
	// set it to carry its own handshake state; if left empty the host mints one.
	ChallengeID string `json:"challenge_id,omitempty"`
	// RedirectURL, when set, is where the host sends the browser (federated /
	// OIDC authorization URL).
	RedirectURL string `json:"redirect_url,omitempty"`
	// Data is an opaque, method-specific payload (WebAuthn creation options, a
	// TOTP provisioning URI) the client-side component consumes, carried as JSON
	// bytes.
	Data []byte `json:"data,omitempty"`
}

AuthNChallenge is what the host relays back to the client to continue (or complete) the handshake.

type AuthNMethod

type AuthNMethod interface {
	Extension

	// MethodInfo reports how the host should classify and present this method.
	// It is the authoritative source for AuthKind: the login-flow orchestrator
	// consults it to route (primary/federated on the login page; second_factor
	// only after a password succeeds) and to refuse using a second_factor method
	// as a standalone login.
	MethodInfo() AuthMethodInfo

	// Begin starts a challenge: a WebAuthn options blob, an OIDC redirect URL, an
	// LDAP bind prompt, a TOTP setup URI. principal is empty for a fresh login
	// (primary/federated) and non-empty for step-up (a second factor on an
	// already-identified user); it is an opaque host-issued handle the plugin
	// cannot forge or introspect (design §3).
	Begin(ctx context.Context, principal Principal, req AuthNRequest) (AuthNChallenge, error)

	// Finish verifies the client's response and returns the resolved identity.
	// Returning Verified=false (with a nil error) is the normal "credentials
	// rejected" outcome; a non-nil error is reserved for operational failures
	// (IdP unreachable, malformed response). Finish must never be treated by the
	// host as authorization — only as an identity assertion the host then
	// independently gates.
	Finish(ctx context.Context, req AuthNResponse) (AuthNResult, error)
}

AuthNMethod adds a way to authenticate a user beyond the core's built-in username/password path (design §2.2). It is the extension point behind SSO / OIDC, LDAP, TOTP and WebAuthn.

The security contract is the whole point of this interface: the plugin only PROVES an identity — it returns "this identity was verified, here is the username/provision hint" — and the HOST alone decides whether to mint a session token, for whom, and with what privileges (design §0, §2.2). A plugin can never sign a token itself, and the host never unconditionally trusts a returned username: federated auto-provisioning is gated by an explicit, default-off policy, and no AuthNMethod path may mint an admin session.

AuthNMethod is deliberately DISTINCT from AuthProvider (see manifest.go's Kind comments): AuthProvider brokers a storage vendor's OAuth token exchange (it produces a storage *credential*); AuthNMethod authenticates an AList *user* (it produces, at most, a host-issued session). They share no code path.

type AuthNRequest

type AuthNRequest struct {
	// RedirectURI is the host's own callback URL (points back at AList), for
	// federated flows whose IdP redirects the browser back. Mirrors
	// AuthURLRequest.RedirectURI.
	RedirectURI string `json:"redirect_uri,omitempty"`
	// State is an opaque, host-generated anti-CSRF token the plugin round-trips
	// unmodified through the external handshake (RFC 6749 §10.12); the plugin
	// never needs to interpret it.
	State string `json:"state,omitempty"`
	// Params carries method-specific client inputs the user supplied on the
	// login page (e.g. a typed LDAP username) as an opaque string map. Bytes and
	// credentials that should not be logged belong here, not in dedicated fields.
	Params map[string]string `json:"params,omitempty"`
}

AuthNRequest carries the host-supplied inputs to Begin.

type AuthNResponse

type AuthNResponse struct {
	// ChallengeID echoes the challenge this response answers.
	ChallengeID string `json:"challenge_id,omitempty"`
	// Params carries the client's response fields (OIDC code+state, a TOTP code,
	// an LDAP password, a WebAuthn assertion) as an opaque map.
	Params map[string]string `json:"params,omitempty"`
	// Data is an opaque, method-specific response payload as JSON bytes.
	Data []byte `json:"data,omitempty"`
}

AuthNResponse carries the client's reply to a challenge back into Finish.

type AuthNResult

type AuthNResult struct {
	// Username is the resolved AList user the plugin asserts. For a primary
	// method the host requires this user to already exist; for a federated
	// method a non-existent user triggers the (default-off) auto-provision path.
	Username string `json:"username,omitempty"`
	// Verified is the plugin's assertion that the credential checked out. A
	// false value (with nil error) is a clean rejection; the host mints nothing.
	Verified bool `json:"verified"`
	// AutoProvision, when non-nil on a federated result, carries attributes for
	// creating/updating the user — honored ONLY when the host's auto-provision
	// policy is enabled, and never to grant admin.
	AutoProvision *ProvisionHint `json:"auto_provision,omitempty"`
}

AuthNResult is the identity assertion Finish returns. It is an assertion, not an authorization: the host independently gates on it (existing/disabled user, admin cap, federated auto-provision policy) before any token is minted.

type AuthProvider

type AuthProvider interface {
	Extension

	// AuthURL builds the vendor's authorization-page URL that the admin's
	// browser should be sent to. req.State is an opaque, host-generated
	// anti-CSRF token; the provider's only obligation is to make the vendor
	// echo it back unmodified as a query parameter on redirect (RFC 6749 §10.12)
	// — it never needs to interpret it.
	AuthURL(ctx context.Context, req AuthURLRequest) (string, error)

	// Exchange trades an authorization code for a token by calling the vendor's
	// token endpoint directly from inside the plugin process (using whatever
	// client_id/secret/etc the plugin was configured with). It never proxies
	// through a third-party broker — the exchange is local to this call.
	Exchange(ctx context.Context, req ExchangeRequest) (Token, error)

	// Refresh trades a refresh token for a fresh access token. Same locality
	// guarantee as Exchange: the vendor's token endpoint is called directly.
	Refresh(ctx context.Context, refreshToken string) (Token, error)
}

AuthProvider is a Kind=AuthProvider extension (SDK doc §1, WS-8): it turns an OAuth authorization-code dance into a durable token, with the code<->token exchange happening entirely INSIDE the plugin process — the plugin holds the vendor's client_id/secret and talks to the vendor's token endpoint itself. No external broker sits between the admin's AList instance and the storage vendor; that locality is WS-8's core requirement.

AuthProvider is deliberately NOT embedded in StorageDriver: a plugin that only brokers OAuth for a network drive (or for the install wizard of a different plugin) declares Kind=AuthProvider on its own manifest and Extension value; a plugin that is both a storage driver and its own OAuth broker declares both Kinds and implements both interfaces on the same value.

type AuthURLRequest

type AuthURLRequest struct {
	// State is the anti-CSRF token the provider must round-trip unmodified.
	State string `json:"state"`
	// RedirectURI is the host's own OAuth callback URL (points back at AList,
	// not at the provider or any broker). Exchange's RedirectURI must match it
	// exactly — most vendors require that.
	RedirectURI string `json:"redirect_uri"`
	// Scopes are the OAuth scopes requested, as declared by the install
	// wizard's oauth step (ext.WizardStep.Scopes).
	Scopes []string `json:"scopes,omitempty"`
}

AuthURLRequest carries the inputs AuthURL needs to build the vendor's authorization-page URL.

type BackgroundService

type BackgroundService interface {
	Extension

	// Run performs the long-running work. It MUST block until ctx is cancelled
	// (host shutdown or a Stop) and return promptly when it fires. Returning an
	// error (or panicking) is isolated by the host supervisor, which logs it and
	// restarts the service after a backoff — one service crashing never affects
	// the others or the host.
	Run(ctx context.Context) error

	// Status reports the service's current state for the frontend status card. It
	// must be cheap and non-blocking (it is polled): read cached counters, do not
	// perform a live probe. State is "running" | "stopped" | "error"; Detail and
	// Metrics are free-form (e.g. an frp tunnel's proxy/traffic counts).
	Status(ctx context.Context) (ServiceStatus, error)
}

BackgroundService is a long-running side task the host starts after boot and supervises for the process lifetime (design §2.4): an frp tunnel, a sync engine. Unlike ScheduledJob (which the host ticks on a cadence), a BackgroundService runs continuously — Run blocks until its context is cancelled, and the host restarts it with backoff if it returns early or panics. Its Status() feeds the frontend status card (a StatusProvider contribution polls it via the host).

Everything the service does inside Run — outbound network, fs access — is still borrowed through the injected Host and stays Grant-gated + Authorize- enforced by the F0 layer. A background service with no user context that touches storage does so under the fs:system scope (design §6.1). frp is the canonical case: a native-tier service granted the "network" capability.

type BundleSpec

type BundleSpec struct {
	// NeedsStore requests the plugin's namespaced Store (design §2.5: share/labels
	// persist their tables). The actual access is still gated by the "store"
	// grant at the host boundary.
	NeedsStore bool `json:"needs_store,omitempty"`
	// NeedsSign requests the Signer (design §2.5: share signs ID-anchored,
	// expiring download links; the plugin never sees the key). Gated by the "sign"
	// grant.
	NeedsSign bool `json:"needs_sign,omitempty"`
}

BundleSpec declares the host resources a FeatureBundle needs provisioned.

type ByteSource

type ByteSource string

ByteSource is the wire-serializable classification of where a Link's bytes come from (9.0 §1). v3 had three forms (URL / RangeReadCloser / MFile), two of them json:"-". They collapse to two: URL, and Stream — where Stream absorbs both RangeReadCloser and MFile (an MFile is just "can read a range from an open file", a special case of Stream).

const (
	ByteSourceURL    ByteSource = "url"    // host pulls the bytes from URL+Header itself
	ByteSourceStream ByteSource = "stream" // bytes come out of the driver: host calls back RangeOpener.OpenRange
)

type Cache

type Cache interface {
	Get(key string) (value []byte, ok bool)
	Set(key string, value []byte, ttl time.Duration)
	Delete(key string)
}

Cache is a per-plugin in-memory cache, namespaced by plugin id.

type Capability

type Capability string

Capability names an optional interface a StorageDriver may implement. It is orthogonal to Kind (9.0 §4). The host validates that a driver's declared Capabilities are a subset of what it actually implements, and — with gRPC (9.2) — dispatches from the declaration because a single proxy struct cannot express "has Move but not Copy" through Go type assertions.

const (
	CapMkdir  Capability = "Mkdir"
	CapMove   Capability = "Move"
	CapRename Capability = "Rename"
	CapCopy   Capability = "Copy"
	CapRemove Capability = "Remove"
	CapPut    Capability = "Put"
	CapPutURL Capability = "PutURL"
	// CapPutRapid marks driver.PutRapid (SP-H 秒传: hash-based instant upload,
	// no bytes transferred on hit).
	CapPutRapid          Capability = "PutRapid"
	CapGetter            Capability = "Getter"
	CapGetRooter         Capability = "GetRooter"
	CapOther             Capability = "Other"
	CapRangeOpener       Capability = "RangeOpener"
	CapArchiveReader     Capability = "ArchiveReader"
	CapArchiveGetter     Capability = "ArchiveGetter"
	CapArchiveDecompress Capability = "ArchiveDecompress"
	CapQuota             Capability = "Quota"
	CapRemoteDownload    Capability = "RemoteDownload"
)

type ConfigSection

type ConfigSection struct {
	Parent      string            `json:"parent"`
	Title       map[string]string `json:"title"`
	SettingKeys []string          `json:"setting_keys"`
	Order       int               `json:"order,omitempty"`
}

ConfigSection is a titled group of settings rendered on the Parent feature's config page (design C). An extension whose config extends a base feature declares one with Parent=<feature id>; a base feature declares its own "general" section with Parent=<self id>. Membership in the merged document equals "enabled" — disabling the owning extension removes its section, so the feature page shows only the sections of currently-enabled contributors.

type Contributions

type Contributions struct {
	ConfigForm     *JSONSchema     `json:"config_form,omitempty"`
	MenuActions    []MenuAction    `json:"menu_actions,omitempty"`
	Previewers     []PreviewerReg  `json:"previewers,omitempty"`
	UIPanels       []UIPanel       `json:"ui_panels,omitempty"`
	DashboardCards []DashboardCard `json:"dashboard_cards,omitempty"`
	SettingsPages  []SettingsPage  `json:"settings_pages,omitempty"`
	Themes         []Theme         `json:"themes,omitempty"`
	I18n           []I18nBundle    `json:"i18n,omitempty"`
	AssistForms    []AssistForm    `json:"assist_forms,omitempty"`

	// StatusProviders render a runtime status card the host polls (design §4):
	// AccessProtocol / BackgroundService plugins (running state, port, metrics,
	// start/stop toggle).
	StatusProviders []StatusProvider `json:"status_providers,omitempty"`
	// LoginMethods render login affordances for an AuthNMethod (design §4):
	// primary/federated login-page buttons, or a second-factor prompt component.
	LoginMethods []LoginMethod `json:"login_methods,omitempty"`
	// PublicPages render an extension's unauthenticated landing page (design §4):
	// a share bundle's public page (iframe, guest context).
	PublicPages []PublicPage `json:"public_pages,omitempty"`
	// FileColumns add a badge/column to the file listing (design §4): the labels
	// badge. Its data source is the extension's FileDecorator field group.
	FileColumns []FileColumn `json:"file_columns,omitempty"`
	// ConfigSections render titled setting groups on a feature's config page
	// (design C). A base feature declares its own "general" section with
	// Parent=self; an extension whose config extends a base feature declares one
	// with Parent=<base feature id>. The frontend's FeatureConfigPage(id) collects
	// every section (across enabled extensions) whose Parent==id.
	ConfigSections []ConfigSection `json:"config_sections,omitempty"`
}

Contributions is the declarative front-end surface an extension adds to the host UI (SDK doc §4). The host aggregates the Contributions of every enabled extension into one versioned document (GET /api/v4/extensions/contributions); the frontend renders them natively. Everything here is data only — no behavior crosses this boundary.

type Copier

type Copier interface {
	Copy(ctx context.Context, src, dstDir ObjRef) (Obj, error)
}

type DashboardCard

type DashboardCard struct {
	ID    string            `json:"id"`
	Title map[string]string `json:"title"`
	Route string            `json:"route,omitempty"`
}

DashboardCard is a card shown on the admin dashboard.

type DependsOn

type DependsOn struct {
	Field string `json:"field"`
	Value any    `json:"value,omitempty"`
}

DependsOn drives conditional show/hide/required of a field on another field's value (SDK §4.1).

type DriverConfig

type DriverConfig struct {
	Name              string `json:"name"`
	LocalSort         bool   `json:"local_sort"`
	OnlyLocal         bool   `json:"only_local"`
	OnlyProxy         bool   `json:"only_proxy"`
	NoCache           bool   `json:"no_cache"`
	NoUpload          bool   `json:"no_upload"`
	DefaultRoot       string `json:"default_root"`
	CheckStatus       bool   `json:"check_status"`
	Alert             string `json:"alert,omitempty"`
	NoOverwriteUpload bool   `json:"no_overwrite_upload"`
	ProxyRangeOption  bool   `json:"proxy_range_option"`
}

DriverConfig is a storage driver's static configuration, returned by StorageDriver.DriverConfig. It mirrors the fields v3 carried on internal/driver.Config that affect host behavior.

9.1 note: built-in drivers still expose their config through the legacy internal/driver.Config reflection path; DriverConfig is the frozen SDK shape future native (out-of-tree) drivers will use. Seam.

type ErrCode

type ErrCode string

ErrCode is the cross-boundary error code (9.0 §5). v3 drove control flow with sentinel errors (errs.NotImplement/NotSupport trigger op-layer fallbacks). Once gRPC flattens an error to a status string, errors.Is stops working — so the code travels explicitly and the host boundary maps it back to the matching sentinel, keeping op-layer control flow (errors.Is(err, errs.NotImplement)) intact.

const (
	ErrCodeNotImplement         ErrCode = "NOT_IMPLEMENT"
	ErrCodeNotSupport           ErrCode = "NOT_SUPPORT"
	ErrCodeObjectNotFound       ErrCode = "OBJECT_NOT_FOUND"
	ErrCodeWrongArchivePassword ErrCode = "WRONG_ARCHIVE_PASSWORD"
	ErrCodePermissionDenied     ErrCode = "PERMISSION_DENIED"
	ErrCodeStorageNotInit       ErrCode = "STORAGE_NOT_INIT"
	ErrCodeStorageNotFound      ErrCode = "STORAGE_NOT_FOUND"
)

func CodeOf

func CodeOf(err error) (ErrCode, bool)

CodeOf returns the ErrCode of err if it is (or wraps) an *Error, and whether one was found.

type Error

type Error struct {
	Code    ErrCode `json:"code"`
	Message string  `json:"message,omitempty"`
}

Error is an error carrying an ErrCode across the boundary. The host boundary layer maps Code back to the corresponding internal sentinel error.

func NewError

func NewError(code ErrCode, msg string) *Error

NewError builds an *Error with a code and message.

func (*Error) Error

func (e *Error) Error() string

type Event

type Event struct {
	// Type is the event verb (fs.move, fs.remove, ...).
	Type EventType `json:"type"`
	// UnixMilli is the host wall-clock time the event fired.
	UnixMilli int64 `json:"unix_milli"`
	// Path is the primary virtual path the operation acted on.
	Path string `json:"path"`
	// DstPath is the destination for two-path operations (move/rename); empty
	// otherwise.
	DstPath string `json:"dst_path,omitempty"`
	// Actor is the username of the acting principal, when the host knows it
	// (empty for background/system-initiated operations).
	Actor string `json:"actor,omitempty"`
}

Event is the serializable payload delivered to EventHook.OnEvent. It carries only the operation's shape: never object bytes (a hook that needs content re-reads it through the Grant-gated Host.FS) and never credentials.

type EventHook

type EventHook interface {
	Extension

	// OnEvent is called once per host event the plugin is subscribed to. Its
	// error/panic is isolated by the host. It should return promptly; heavy work
	// belongs on a queue the plugin drains, not inline in the host's dispatch.
	OnEvent(ctx context.Context, e Event) error
}

EventHook observes host events — primarily filesystem mutations (move, rename, remove, mkdir, upload) — after they succeed (design §2.5: FeatureBundle composes EventHook; F1 "事件/定时"). It is a NOTIFICATION sink, not a veto: the host fires OnEvent AFTER an operation has already committed, so a hook cannot block or undo a user action. This keeps the fs mutation path free of plugin latency/failure in its critical section — a misbehaving hook can only fail itself.

Every OnEvent runs in failure isolation (the host recovers panics and swallows errors, logging them): one hook erroring never affects the originating operation nor the other hooks. A hook that needs to borrow host capabilities (read the moved file, notify an external service) does so through the injected Host, which remains Grant-gated and Authorize-enforced (design §0, §6.3) — the Event payload itself only carries the operation's shape (verb + virtual paths + actor), never object bytes or credentials.

type EventType

type EventType string

EventType names a host event. The fs.* family fires after the matching filesystem mutation commits.

const (
	// EventFSMkdir fires after a directory is created (Path = the new dir).
	EventFSMkdir EventType = "fs.mkdir"
	// EventFSMove fires after an object is moved (Path = source, DstPath =
	// destination directory).
	EventFSMove EventType = "fs.move"
	// EventFSRename fires after an object is renamed (Path = source, DstPath =
	// the new name).
	EventFSRename EventType = "fs.rename"
	// EventFSRemove fires after an object is removed (Path = the removed object).
	EventFSRemove EventType = "fs.remove"
	// EventFSPut fires after an object is uploaded (Path = destination
	// directory).
	EventFSPut EventType = "fs.put"
)

type ExchangeRequest

type ExchangeRequest struct {
	Code        string `json:"code"`
	RedirectURI string `json:"redirect_uri"`
}

ExchangeRequest carries the inputs Exchange needs to trade an authorization code for a token.

type Extension

type Extension interface {
	Manifest() Manifest
	// Init is called once with the Host callback set the core injects.
	Init(ctx context.Context, host Host) error
	// Shutdown is called once when the extension is torn down.
	Shutdown(ctx context.Context) error
}

Extension is the base interface of every extension. Init/Shutdown are process-level and run once (9.0 §3); per-instance lifecycle for storage drivers is added in StorageDriver.

type FS

type FS interface {
	// Resolve maps a virtual path to an ObjRef. It is authorized (read) so a
	// plugin cannot probe existence outside its grant. This is the per-request
	// hot path for protocol plugins that live in the path world (WebDAV/S3/FTP).
	Resolve(ctx context.Context, p Principal, path string) (ObjRef, error)
	List(ctx context.Context, p Principal, dir ObjRef, a ListArgs) ([]Obj, error)
	Get(ctx context.Context, p Principal, ref ObjRef) (Obj, error)
	Link(ctx context.Context, p Principal, ref ObjRef, a LinkArgs) (*Link, error)
	Mkdir(ctx context.Context, p Principal, parent ObjRef, name string) (Obj, error)
	Move(ctx context.Context, p Principal, src, dstDir ObjRef) error
	Rename(ctx context.Context, p Principal, src ObjRef, name string) error
	Copy(ctx context.Context, p Principal, src, dstDir ObjRef) error
	Remove(ctx context.Context, p Principal, ref ObjRef) error
	Put(ctx context.Context, p Principal, dstDir ObjRef, up UploadStream) (Obj, error)
}

FS is the host filesystem borrowed by a plugin. EVERY method runs the core Authorize against the acting principal before touching storage (design §0, §3): borrowing FS never bypasses authorization. The plugin only ever holds opaque ObjRefs (pillar A) — never a mutable virtual-path key it could fabricate to reach outside its grant. Even a fabricated ref is backstopped by the per-call Authorize.

type FeatureBundle

type FeatureBundle interface {
	Extension

	// Bundle declares the host resources this composite needs. The composed
	// extension-point implementations are discovered by interface assertion on the
	// bundle value itself (the host checks whether it also implements APIRoute /
	// EventHook / ScheduledJob), so this spec carries only the resource requests.
	Bundle() BundleSpec
}

FeatureBundle is a COMPOSITE feature (design §2.5): share and labels are not a single extension point but a bundle of several — an APIRoute to manage them, a public landing page (an APIRoute with Public=true), an EventHook to react to fs changes, a ScheduledJob for expiry cleanup — plus host Store/Sign to persist and to sign ID-anchored links. Rather than invent a new mega-interface, a FeatureBundle is a value that ALSO implements the existing extension-point interfaces it contributes; the host EXPANDS it onto the F1 wiring points (RegisterAPIRouteInstance / RegisterEventHookInstance / …) by asserting which interfaces it satisfies, reusing that plumbing rather than duplicating it.

So a share bundle is one Go value implementing Extension + FeatureBundle + APIRoute (+ EventHook + ScheduledJob as needed); Bundle() only declares the host RESOURCES it needs (Store/Sign), which the host provisions and gates through the store / sign grants.

type FileColumn

type FileColumn struct {
	ID         string            `json:"id"`
	Header     map[string]string `json:"header"`
	FieldGroup string            `json:"field_group"` // e.g. "_labels"; data source = FileDecorator
}

FileColumn adds a badge/column to the file listing (design §4). Its data is NOT inlined in the base listing; it is registered as an fs/list extension field group (FieldGroup, "_"-prefixed), fetched on demand from the extension's FileDecorator only when the column is actually shown — so a column nobody opens costs nothing, and a slow decorator only slows its own column (design §2.6).

type FileDecorator

type FileDecorator interface {
	Extension

	// Groups names the field groups this decorator provides (each "_"-prefixed,
	// e.g. "_labels"), matching a FileColumn contribution's FieldGroup. The host
	// routes a requested group to the decorator(s) that declare it.
	Groups() []string

	// Decorate returns the field values for one group over a batch of refs. The
	// result maps a ref's serialized key (RefKey) to the arbitrary field value the
	// column renders (a badge list, a boolean, …). A ref absent from the map simply
	// has no value for this group. Decorate must honor ctx: the host cancels it at
	// the per-group budget and marks the group pending.
	Decorate(ctx context.Context, group string, refs []ObjRef) (map[string]any, error)
}

FileDecorator supplies EXTENSION FIELD GROUPS for the file listing (design §2.6): the data behind a FileColumn contribution — label badges, share markers. It is the backend of the "field groups, fetched on demand" protocol: the base listing (name/size/time/type) always returns, but a decorator's group is computed only when a client actually asks for that column (fs/list?fields=_labels), and each group runs under a per-group time budget so a slow decorator is marked pending and backfilled rather than blocking the listing.

A decorator must be pure-read and fast (typically a single indexed query against its own Store table): the host calls it in parallel across decorators under a ~200ms per-group budget. The host only ever passes refs the requesting user may already see (the caller filters the listing first), so a decorator cannot be used to probe objects outside the user's view.

type GetRooter

type GetRooter interface {
	GetRoot(ctx context.Context, storageID string) (Obj, error)
}

GetRooter resolves the storage's root object (9.0 §6).

type Getter

type Getter interface {
	Get(ctx context.Context, storageID, path string) (Obj, error)
}

Getter fetches an object directly by path, saving a List (9.0 §6).

type HTTPClient

type HTTPClient interface {
	Do(ctx context.Context, req *HTTPRequest) (*HTTPResponse, error)
}

HTTPClient is the host-gated outbound HTTP capability. Do rejects any request whose target host is not on the effective allowlist (Grant.Network) before the request leaves the process.

type HTTPRequest

type HTTPRequest struct {
	Method string      `json:"method"`
	URL    string      `json:"url"`
	Header http.Header `json:"header,omitempty"`
	Body   []byte      `json:"body,omitempty"`
}

HTTPRequest is the serializable outbound request a plugin asks the host to make (design §3, §6.3). The host enforces the network allowlist (global outbound policy ∩ the plugin's Grant.Network) before dialing; a host not on the allowlist yields ErrPermissionDenied.

type HTTPResponse

type HTTPResponse struct {
	Status int         `json:"status"`
	Header http.Header `json:"header,omitempty"`
	Body   []byte      `json:"body,omitempty"`
}

HTTPResponse is the serializable response for a HTTPClient.Do call.

type Host

type Host interface {
	Logger() Logger
	FS() FS
	Auth() Auth
	Store() Store
	Sign() Signer
	HTTP() HTTPClient
	Cache() Cache
	// LoadConfig returns the plugin's persisted config_form value as JSON, or
	// nil if none has been set.
	LoadConfig(ctx context.Context) ([]byte, error)
	// SaveConfig persists the plugin's own config_form value as JSON (cfg must
	// be valid JSON; an empty cfg clears it). Mirrors LoadConfig in the write
	// direction. A Host built without a config saver wired (e.g. no admin-
	// editable config_form) returns an ErrCodeNotImplement error rather than
	// silently discarding the write.
	SaveConfig(ctx context.Context, cfg []byte) error
	// Capabilities returns the names of the Host APIs (host_api vocabulary,
	// e.g. "host.fs"/"store"/"sign") this plugin was granted — introspection
	// over its own Grant, so a plugin can adapt its behavior to what it was
	// actually approved for instead of guessing from its own manifest request.
	Capabilities(ctx context.Context) ([]string, error)
}

Host is the set of capabilities the core exposes back to an extension (SDK doc §3, design v4-plugin-extensions-design.md §3). Every capability is gated at the host boundary by the plugin's runtime Grant (§6.3): a plugin that was not granted a capability sees ErrPermissionDenied from the corresponding accessor / its methods.

The capability sub-interfaces (FS/Auth/Store/Sign/HTTP/Cache) and their DTOs live in host.go. F0 wires FS/Store/HTTP end-to-end over the reverse gRPC bridge; Auth/Sign/Cache have host-side implementations (the reverse-bridge leg for those follows in a later wave). An in-process (native) plugin receives a Host that calls the core directly; a subprocess (gRPC) plugin receives a Host whose calls travel back over the plugin→host bridge.

type I18nBundle

type I18nBundle struct {
	Locale string `json:"locale"`
	Route  string `json:"route"`
}

I18nBundle points at a locale message bundle the extension ships.

type IndexProgress

type IndexProgress struct {
	ObjCount     uint64     `json:"obj_count"`
	IsDone       bool       `json:"is_done"`
	LastDoneTime *time.Time `json:"last_done_time"`
	Error        string     `json:"error"`
}

IndexProgress is the build-progress snapshot the host persists (as the index_progress setting) and the frontend polls: how many objects have been indexed, whether the build finished, when it last completed, and the last error if any.

type Info

type Info struct {
	Common     []Item       `json:"common"`
	Additional []Item       `json:"additional"`
	Config     DriverConfig `json:"config"`
}

Info is the host-facing description of a driver: the config form plus its static config.

type Item

type Item struct {
	Name     string `json:"name"`
	Type     string `json:"type"`
	Default  string `json:"default,omitempty"`
	Options  string `json:"options,omitempty"`
	Required bool   `json:"required,omitempty"`
	Help     string `json:"help,omitempty"`
}

Item is one field of a driver's declarative config form.

type JSONSchema

type JSONSchema struct {
	Type       string                 `json:"type,omitempty"` // object|string|number|integer|boolean
	Title      map[string]string      `json:"title,omitempty"`
	Properties map[string]*JSONSchema `json:"properties,omitempty"`
	Required   []string               `json:"required,omitempty"`
	Enum       []string               `json:"enum,omitempty"`
	Default    string                 `json:"default,omitempty"`
	Minimum    *float64               `json:"minimum,omitempty"`
	Maximum    *float64               `json:"maximum,omitempty"`

	// DependsOn / Assist are the SDK §4.1 extension keywords. The host carries
	// them verbatim to the frontend (page-zero assist); it does not act on them.
	DependsOn *DependsOn `json:"x-depends-on,omitempty"`
	Assist    *AssistRef `json:"x-assist,omitempty"`
}

JSONSchema is the declarative form/field description shared by config forms, wizard form steps, and assist sub-forms (SDK doc §4). It is a deliberately small subset of JSON Schema: enough for the host's wizard engine to validate answers and for the frontend to render a form. Unknown JSON keys are ignored on decode, so a manifest may carry richer schema the frontend understands without the host needing to model it.

type KV

type KV interface {
	Get(ctx context.Context, key string) (value []byte, ok bool, err error)
	Set(ctx context.Context, key string, value []byte) error
	Delete(ctx context.Context, key string) error
	// List returns the keys under prefix (all keys when prefix is "").
	List(ctx context.Context, prefix string) ([]string, error)
}

KV is a namespaced key/value store. All keys are implicitly scoped to the calling plugin's id on the host side — the plugin cannot address another plugin's namespace.

type Kind

type Kind string

Kind is an extension point. An extension declares one or more Kinds it implements (SDK doc §1). StorageDriver is the only Kind with a live implementation in 9.1; the rest are reserved so their identifiers are frozen alongside the rest of the contract.

const (
	KindStorageDriver Kind = "StorageDriver"
	// KindAuthProvider brokers a storage vendor's OAuth authorization-code
	// exchange (ext.AuthProvider): it turns a code into a storage *credential*,
	// with the client_id/secret exchange happening inside the plugin process.
	KindAuthProvider Kind = "AuthProvider"
	// KindAuthNMethod adds a user login method (ext.AuthNMethod): SSO/OIDC, LDAP,
	// TOTP, WebAuthn. It authenticates an AList *user* and, on success, lets the
	// host mint a session — the plugin never signs a token. This is a different
	// axis from KindAuthProvider (storage credential vs user session); the two
	// share no interface or code path. Login methods are native/official tier
	// only (design §5): closed-source third parties can never provide one.
	KindAuthNMethod         Kind = "AuthNMethod"
	KindOfflineDownloadTool Kind = "OfflineDownloadTool"
	KindArchiveTool         Kind = "ArchiveTool"
	KindSearchIndexer       Kind = "SearchIndexer"
	KindThumbnailer         Kind = "Thumbnailer"
	KindFileTransformer     Kind = "FileTransformer"
	KindMetadataProvider    Kind = "MetadataProvider"
	KindEventHook           Kind = "EventHook"
	KindScheduledJob        Kind = "ScheduledJob"
	KindNotification        Kind = "NotificationChannel"
	KindAction              Kind = "Action"
	KindPreviewer           Kind = "Previewer"
	KindUIPanel             Kind = "UIPanel"
	KindAPIRoute            Kind = "APIRoute"
	// KindAccessProtocol exposes the AList filesystem over an inbound network
	// protocol (ext.AccessProtocol): WebDAV/S3/MCP (TransportHTTP) or FTP/SFTP
	// (TransportConn). Orthogonal to StorageDriver — inbound vs outbound. Runs
	// native/official only (design §5): it owns sockets / speaks wire protocols,
	// so it never runs in a WASM sandbox.
	KindAccessProtocol Kind = "AccessProtocol"
	// KindBackgroundService is a long-running side task started after boot
	// (ext.BackgroundService): frp tunnel, a sync engine. The host supervises it
	// (restart/backoff, panic isolation) and polls Status() for the frontend
	// status card.
	KindBackgroundService Kind = "BackgroundService"
	// KindFeatureBundle is a composite feature (ext.FeatureBundle): share/labels.
	// It contributes several existing extension points at once (APIRoute +
	// PublicPage + EventHook + ScheduledJob) plus host Store/Sign, which the host
	// expands onto the F1 wiring points rather than inventing a new one.
	KindFeatureBundle Kind = "FeatureBundle"
	// KindFileDecorator supplies extension field groups for the file listing
	// (ext.FileDecorator): label badges, share markers. The host calls it in
	// parallel under a per-group time budget; a slow group is marked pending and
	// backfilled, never blocking the base listing (design §2.6).
	KindFileDecorator Kind = "FileDecorator"
	// KindFeature is a plain, host-registered core feature turned into a
	// manageable built-in extension (Project A-2): offline download, file search,
	// sharing, SSO login. Unlike FeatureBundle it contributes no interface and is
	// not expanded onto any wiring point — it is a pure registry entry that gives
	// an existing, natively-wired core feature a manageable/gate-able/aggregatable
	// identity. The host registers it enabled at boot (RegisterBuiltinExtension);
	// an admin may turn it off, and a RequireExtensionEnabled gate on the feature's
	// entry-point routes turns that off into a real 403 boundary.
	KindFeature Kind = "Feature"
)
type Link struct {
	ByteSource   ByteSource  `json:"byte_source"`
	URL          string      `json:"url,omitempty"`       // ByteSource==url
	Header       http.Header `json:"header,omitempty"`    // ByteSource==url
	StreamID     string      `json:"stream_id,omitempty"` // ByteSource==stream: echoed back on OpenRange to locate the session
	RangeSupport bool        `json:"range_support"`
	Expiration   int64       `json:"expiration,omitempty"`   // seconds; 0 = do not cache (v3 nil)
	IPCacheKey   bool        `json:"ip_cache_key,omitempty"` // cache key is scoped by client IP (v3 op/fs.go semantics)
	Concurrency  int         `json:"concurrency,omitempty"`
	PartSize     int         `json:"part_size,omitempty"`
}

Link describes how the host obtains a file's bytes (9.0 §1). Unlike v3's model.Link it carries no live Go handles, so it survives serialization.

type LinkArgs

type LinkArgs struct {
	IP       string      `json:"ip"`
	Header   http.Header `json:"header,omitempty"`
	Type     string      `json:"type,omitempty"`
	Redirect bool        `json:"redirect"`
}

LinkArgs is the serializable redefinition of v3's LinkArgs (which carried a *http.Request). The host extracts the whitelisted subset a driver needs instead of passing the whole request across the boundary (9.0 §1).

type ListArgs

type ListArgs struct {
	ReqPath string `json:"req_path,omitempty"`
	Refresh bool   `json:"refresh,omitempty"`
}

ListArgs are the arguments to StorageDriver.List.

type Logger

type Logger interface {
	Debugf(format string, args ...any)
	Infof(format string, args ...any)
	Warnf(format string, args ...any)
	Errorf(format string, args ...any)
}

Logger is the host's logger, namespaced to the extension.

type LoginMethod

type LoginMethod struct {
	ID    string            `json:"id"`
	Kind  string            `json:"kind"` // primary | second_factor | federated
	Label map[string]string `json:"label"`
	Icon  string            `json:"icon,omitempty"`
	UIRef string            `json:"ui_ref,omitempty"`
}

LoginMethod renders a login affordance for an AuthNMethod (design §4). Kind selects placement: "primary"/"federated" methods appear as login-page buttons that drive the host login flow; a "second_factor" method renders a prompt component after the password step. UIRef optionally points at a custom component bundle (iframe); otherwise the frontend uses a generic renderer.

type Manifest

type Manifest struct {
	ID           string            `json:"id"`       // globally unique, reverse-domain: com.aliyun.drive_open
	Name         map[string]string `json:"name"`     // i18n display name
	Version      string            `json:"version"`  // semver
	CoreAPI      string            `json:"core_api"` // semver constraint on the host core API (0.1.0 until GA), e.g. "^0.1.0"
	Platforms    []string          `json:"platforms,omitempty"`
	Kinds        []Kind            `json:"kinds"`
	Capabilities []Capability      `json:"capabilities,omitempty"` // optional interfaces implemented; validated at registration
	Provenance   Provenance        `json:"provenance"`
	Permissions  Permissions       `json:"permissions"`
	// Wizard and Contributions are the Step 10 declarative front-end surface:
	// the install flow the host drives and the UI contributions it aggregates.
	// Both are optional — a plain storage driver leaves them zero.
	Wizard        Wizard        `json:"wizard,omitempty"`
	Contributions Contributions `json:"contributions,omitempty"`
	// Info is an optional declarative storage-config form for StorageDriver
	// plugins. The host synthesizes the generic common fields itself; a plugin
	// only declares its driver-specific Additional items (and DriverConfig).
	// Without it, the "add storage" form for the plugin driver is empty.
	Info *Info `json:"info,omitempty"`
	// DisableWarning is the declarative replacement for a hardcoded front-end
	// confirm list (formerly CONFIRM_ON_DISABLE_IDS): an optional localized
	// (LangMap, en/zh) message shown to the admin BEFORE disabling this
	// extension, stating the real consequence of turning it off — e.g. share:
	// "关闭后所有公开分享链接将立即失效" / "Disabling this immediately invalidates all
	// public share links". A plugin or built-in with genuine disable
	// consequences declares the warning copy here; empty/absent means the
	// extension disables without a special confirm.
	DisableWarning map[string]string `json:"disable_warning,omitempty"`
	// Signature and Checksum are reserved and currently unused: 9.3 package
	// verification reads the sidecar checksums.txt + signature files instead.
	Signature string `json:"signature,omitempty"`
	Checksum  string `json:"checksum,omitempty"`
}

Manifest is the contract root of an extension, distributed with it as plugin.json (SDK doc §1).

type MenuAction struct {
	ID     string            `json:"id"`
	Label  map[string]string `json:"label"`
	Icon   string            `json:"icon,omitempty"`
	Target string            `json:"target"` // file | dir | selection | global
}

MenuAction is a context-menu / toolbar action. Triggering it makes the frontend POST /api/v4/extensions/{id}/actions/{action-id}; the host routes to the extension's Action handler (SDK §4).

type Mkdirer

type Mkdirer interface {
	Mkdir(ctx context.Context, parent ObjRef, name string) (Obj, error)
}

type Mover

type Mover interface {
	Move(ctx context.Context, src, dstDir ObjRef) (Obj, error)
}

type Notice

type Notice struct {
	// Level is the notice's severity.
	Level NoticeLevel `json:"level"`
	// Title is a short human-readable summary.
	Title string `json:"title"`
	// Body is an optional longer human-readable description.
	Body string `json:"body,omitempty"`
	// Source is the host subsystem that raised the notice, e.g. "task",
	// "storage" (empty when the raiser did not identify itself).
	Source string `json:"source,omitempty"`
	// UnixMilli is the host wall-clock time the notice was raised (0 when
	// unset).
	UnixMilli int64 `json:"unix_milli,omitempty"`
}

Notice is the serializable payload delivered to Notification.Notify. It carries only the notice's shape: never object bytes (a channel that needs more re-reads it through the Grant-gated Host) and never credentials.

type NoticeLevel

type NoticeLevel string

NoticeLevel is the severity of a Notice.

const (
	NoticeInfo  NoticeLevel = "info"
	NoticeWarn  NoticeLevel = "warn"
	NoticeError NoticeLevel = "error"
)

type Notification

type Notification interface {
	Extension

	// Notify delivers one notice to this channel. Its error/panic is isolated
	// by the host. It should return promptly; heavy delivery (an outbound HTTP
	// call, a retry queue) belongs on the plugin's own queue, not inline in the
	// host's dispatch.
	Notify(ctx context.Context, n Notice) error
}

Notification is a sink the host fans notices out to (design: NotificationChannel kind) — a plugin forwarding host notices to an external channel (email, webhook, Telegram, ...). Like EventHook it is a NOTIFICATION sink, not a veto: the host has already decided a notice is worth raising by the time Notify is called, so a channel cannot block or alter it — only forward or drop it (by failing) on its own.

Every Notify runs in failure isolation (the host recovers panics and swallows errors, logging them): one channel erroring never affects the host or the other channels. A channel that needs to borrow host capabilities (render a richer message, look up a display name) does so through the injected Host, which remains Grant-gated and Authorize-enforced (design §0, §6.3) — the Notice payload itself only carries the notice's shape, never object bytes or credentials.

type Obj

type Obj struct {
	Name     string `json:"name"`
	Size     int64  `json:"size"`
	Modified int64  `json:"modified"` // unix milliseconds (v3 ModTime)
	Created  int64  `json:"created"`  // unix milliseconds (v3 CreateTime)
	IsDir    bool   `json:"is_dir"`
	ID       string `json:"id"`   // driver-internal id (v3 GetID)
	Path     string `json:"path"` // driver-internal path (v3 GetPath)
	// Hashes MUST be exact whole-file digests of the named algorithm,
	// lowercase hex — never a chunk/partial hash, and never truncated or
	// padded. The host may use these for rapid upload matching and content
	// dedup (content-addressing on the value); a wrong value silently
	// produces wrong content for every consumer that trusts it. Keys are
	// registered hash names ("md5", "sha1", "sha256"); an unrecognized key
	// or a value whose length doesn't match that algorithm's digest width
	// is dropped when crossing the boundary.
	Hashes map[string]string `json:"hashes,omitempty"`
	Thumb  string            `json:"thumb,omitempty"` // v3 Thumb optional interface
	URL    string            `json:"url,omitempty"`   // v3 URL optional interface
}

Obj is the flat, serializable DTO for a filesystem object crossing the boundary (9.0 §7). It mirrors v3's model.Obj 8-method interface plus the optional Thumb/URL, with wire-friendly scalar fields.

The "…" placeholder in the pre-freeze SDK draft is nailed down here.

type ObjRef

type ObjRef struct {
	StorageID string `json:"storage_id"`
	Anchor    string `json:"anchor"` // pillar A: ID-anchored resource key
}

ObjRef is the SDK-side projection of the architecture's ResourceRef (pillar A). It identifies a resource by storage plus an opaque anchor key, replacing v3's path-threading across the boundary (9.0 §7).

type OfflineDownloadTool

type OfflineDownloadTool interface {
	Extension

	// Config reports the tool's identity (Name, matched against the
	// offline_download_tool setting to resolve the active tool).
	Config() OfflineDownloadToolConfig

	// AddURL hands the daemon a URL to fetch into args.TempDir and returns the
	// daemon-side gid identifying the resulting download.
	AddURL(ctx context.Context, args AddArgs) (gid string, err error)

	// Remove cancels/removes the download identified by gid from the daemon.
	Remove(ctx context.Context, gid string) error

	// Status reports the current progress/completion of the download gid.
	Status(ctx context.Context, gid string) (ToolStatus, error)
}

OfflineDownloadTool is the extension point behind offline download's daemon backends (design: 子项目 B). The v4 core ships a direct-download path (SimpleHttp: op.Put straight into the target storage) as the always-available base — SimpleHttp is NOT a tool, it is the offline_download base's built-in direct path. A OfflineDownloadTool adds an external download daemon that the base hands a URL to and then polls: aria2, qBittorrent and transmission each implement this interface as an independent built-in extension.

Like ext.SearchIndexer and ext.AuthNMethod, the contract is defined here so an out-of-process (gRPC) third-party backend is a stable future seam; this wave wires only in-process TRUSTED instances the host itself authors (RegisterTrustedOfflineDownloadToolInstance on the host side). The v3 coupling to a concrete *DownloadTask is deliberately INVERTED into value DTOs (AddArgs / ToolStatus) so a tool never reaches back into the task/db layer — it only takes a URL to add and reports status by gid.

type OfflineDownloadToolConfig

type OfflineDownloadToolConfig struct {
	Name string `json:"name"`
}

OfflineDownloadToolConfig is a tool's self-description. Name is the stable id the offline_download_tool setting selects on (e.g. "aria2", "qBittorrent", "Transmission").

type Other

type Other interface {
	Other(ctx context.Context, obj ObjRef, method string, data json.RawMessage) (json.RawMessage, error)
}

Other carries an opaque, driver-specific request/response (v3 Other's interface{} payload becomes json.RawMessage across the boundary, 9.0 §6).

type Permissions

type Permissions struct {
	Network       []string `json:"network,omitempty"`        // allowed host globs
	StorageTokens []string `json:"storage_tokens,omitempty"` // "self" or provider ids
	Filesystem    string   `json:"filesystem,omitempty"`     // none | temp | full
	HostAPIs      []string `json:"host_apis,omitempty"`      // requested Host methods
}

Permissions is a declarative permission grant, shown to the admin at install time for informed consent (SDK doc §1). StorageTokens is enforced when scoped tokens are issued (IssueScopedToken); the rest is informational — every plugin runs as a full-permission process (plugin parity).

type PreviewerReg

type PreviewerReg struct {
	ID     string   `json:"id"`
	Accept []string `json:"accept"`
	Route  string   `json:"route"`
}

PreviewerReg registers a custom previewer: accepted mime/extension globs and the plugin-UI route that renders them (served under /ui/).

type Principal

type Principal string

Principal is an opaque, host-issued identity handle. Across the plugin boundary it is only a random string: a plugin cannot construct one, nor read a role/permission out of it. The host holds the handle→identity mapping. It appears here so a second_factor method's Begin can be scoped to the already-identified user without exposing that user's real identity to the plugin (design §3).

type ProtocolInfo

type ProtocolInfo struct {
	// Scheme is the protocol name: "webdav" | "s3" | "mcp" | "ftp" | "sftp".
	Scheme string `json:"scheme"`
	// Transport selects how the host feeds the plugin traffic.
	Transport Transport `json:"transport"`
	// DefaultPort is the port the host listens on for a TransportConn protocol
	// when the admin has not overridden it in settings. Ignored for TransportHTTP
	// (which shares the host's HTTP listener under MountPattern).
	DefaultPort int `json:"default_port,omitempty"`
	// MountPattern is where the host mounts a TransportHTTP handler, e.g. "/dav"
	// or "/s3". It is checked against the reserved-prefix table at
	// install/registration. Ignored for TransportConn.
	MountPattern string `json:"mount_pattern,omitempty"`
}

ProtocolInfo describes an AccessProtocol's binding to the host.

type Provenance

type Provenance struct {
	Source    string `json:"source"`     // official | verified-community | third-party
	License   string `json:"license"`    // SPDX id or "proprietary"
	SourceURL string `json:"source_url"` // source repo; empty => treated as closed source
	Pricing   string `json:"pricing"`    // free | paid
}

Provenance decides the trust tier and execution mode (SDK doc §1).

type ProvisionHint

type ProvisionHint struct {
	DisplayName string            `json:"display_name,omitempty"`
	Email       string            `json:"email,omitempty"`
	Attributes  map[string]string `json:"attributes,omitempty"`
}

ProvisionHint is the attribute set a federated method suggests for a newly-provisioned user. The host decides which fields it honors; none of them can request a role or elevate privileges — the host assigns the configured minimal role and refuses to build an admin.

type PublicPage

type PublicPage struct {
	ID    string `json:"id"`
	Route string `json:"route"`
	UIRef string `json:"ui_ref,omitempty"`
}

PublicPage renders an extension's unauthenticated landing page (design §4): a share bundle's public page. Route is mounted under the Manifest-requested top-level short prefix (e.g. public_prefix="/s" → "/s/:share_id"); an extension that requests no prefix falls back to the default namespace (/api/v4/ext-public/{id}/…). UIRef points at the iframe bundle, which runs in a guest context (the Bridge exposes only the public method subset).

type PutURLer

type PutURLer interface {
	PutURL(ctx context.Context, dstDir ObjRef, name, url string) (Obj, error)
}

PutURLer uploads by handing a URL to the storage (v3 PutURL; offline SimpleHttp path).

type Putter

type Putter interface {
	Put(ctx context.Context, dstDir ObjRef, up UploadStream) (Obj, error)
}

Putter uploads a file. The bytes are pulled from the host-held UploadStream (9.0 §2); progress/rate-limit/cancel are host concerns.

type Quota

type Quota struct {
	Total int64 `json:"total"` // bytes; -1 = unknown/unlimited
	Used  int64 `json:"used"`  // bytes; -1 = unknown
	Free  int64 `json:"free"`  // bytes; -1 = unknown
}

type Quotaer

type Quotaer interface {
	Quota(ctx context.Context, storageID string) (Quota, error)
}

Quotaer reports a network drive's space usage (9.0 §6b).

type Range

type Range struct {
	Start  int64
	Length int64
}

Range is a byte range request. Length < 0 means "to the end" (9.0 §1).

type RangeOpener

type RangeOpener interface {
	OpenRange(ctx context.Context, ref ObjRef, streamID string, rng Range) (io.ReadCloser, error)
}

RangeOpener is the byte source for ByteSourceStream links. The host's stream layer adapts it to its internal range-reader. Under gRPC (9.2) this becomes a server-streaming RPC; in-process native drivers may take a zero-copy fast path (the SDK's in-process adapter recognizes it) that never hits the wire.

type RapidPutter

type RapidPutter interface {
	TryRapidUpload(ctx context.Context, dstDir ObjRef, name string, size int64, hashes map[string]string) (Obj, bool, error)
}

RapidPutter tries a hash-based instant ("rapid") upload: if the storage already has content matching the given full-file hashes, it links a new file without transferring bytes (SP-H). Semantics mirror the host's driver.PutRapid contract:

  • hit: (obj, true, nil)
  • miss: (Obj{}, false, nil) — caller falls back to streaming
  • hash flavor unavailable / not supported: error with code NOT_SUPPORT

Implementations MUST NOT read or spool bytes to compute a hash. hashes keys are utils hash names ("md5", "sha1", "sha256").

type RemoteDownloader

type RemoteDownloader interface {
	SupportedSchemes() []string
	AddRemoteDownload(ctx context.Context, dstDir ObjRef, url string, opts map[string]any) (RemoteTask, error)
	RemoteDownloadStatus(ctx context.Context, storageID, taskID string) (RemoteTaskStatus, error)
	RemoteDownloadCancel(ctx context.Context, storageID, taskID string) error
}

RemoteDownloader submits a remote resource to the network drive's own cloud download queue — bytes never pass through AList (9.0 §6b).

type RemoteTask

type RemoteTask struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

type RemoteTaskStatus

type RemoteTaskStatus struct {
	State    string  `json:"state"`    // pending|downloading|seeding|done|error
	Progress float64 `json:"progress"` // 0..1
	Speed    int64   `json:"speed"`    // bytes/s; -1 unknown
	Detail   string  `json:"detail"`
}

type Remover

type Remover interface {
	Remove(ctx context.Context, obj ObjRef) error
}

type Renamer

type Renamer interface {
	Rename(ctx context.Context, src ObjRef, newName string) (Obj, error)
}

type RouteDecl

type RouteDecl struct {
	// Prefix is the sub-path under the plugin namespace this route owns, e.g.
	// "/webhook" or "/s". It must be a rooted, non-traversing path segment set
	// ("/a/b" is fine, "../x" is not).
	Prefix string `json:"prefix"`
	// Public mounts the route under the unauthenticated public group. A public
	// route requires the "public_routes" grant and runs with a delegated-only
	// Host. Default false: mounted behind auth.
	Public bool `json:"public,omitempty"`
}

RouteDecl is one sub-route a plugin serves.

type ScheduledJob

type ScheduledJob interface {
	Extension

	// Schedule returns the job's cadence as an interval spec the host parses
	// (see ParseSchedule): "@every <dur>" (e.g. "@every 30s", "@every 1h"),
	// or the shorthands "@hourly"/"@daily"/"@weekly". An unparseable or
	// non-positive schedule disables the job (the host logs and skips it) rather
	// than defaulting to a surprise cadence.
	Schedule() string

	// Run performs one execution. Returning an error is logged by the host; it
	// does not stop future ticks. Run should honor ctx cancellation (host
	// shutdown) and return promptly when it fires.
	Run(ctx context.Context) error
}

ScheduledJob is a time-driven background task a plugin contributes: share expiry cleanup, index compaction, a periodic sync (design §2.5 notes share's expiry cleanup is a ScheduledJob — time-driven, not event-driven). The host supervises scheduling; the plugin only declares its cadence and does the work.

Run executes under the host's supervision: each invocation is panic-isolated and non-overlapping (the host skips a tick if the previous Run of the same job is still going), so a slow or crashing job degrades only itself. Any host capability the job borrows inside Run stays Grant-gated + Authorize-enforced by the F0 host layer; a background job with no user context uses the fs:system scope (design §6.1) if it needs storage access.

type SearchIndexer

type SearchIndexer interface {
	Extension

	// Config reports the backend's identity (Name, matched against the
	// `search_index` setting to resolve the active backend) and whether it
	// supports incremental auto-update.
	Config() SearchIndexerConfig

	// Search returns the nodes matching req plus the total hit count. The
	// returned nodes carry full virtual paths (path.Join(Parent, Name)); the
	// base re-authorizes each one per requesting user — Search itself performs
	// no access control.
	Search(ctx context.Context, req SearchReq) ([]SearchNode, int64, error)

	// Index adds or updates a single node.
	Index(ctx context.Context, node SearchNode) error

	// BatchIndex adds or updates many nodes at once (the build path fans out
	// through this to amortize per-write cost).
	BatchIndex(ctx context.Context, nodes []SearchNode) error

	// Get returns the direct children previously indexed under parent.
	Get(ctx context.Context, parent string) ([]SearchNode, error)

	// Del removes every node at or below prefix (a subtree delete).
	Del(ctx context.Context, prefix string) error

	// Clear drops the entire index.
	Clear(ctx context.Context) error
}

SearchIndexer is the extension point behind file search's real-index backends (design: 子项目 A). The v4 core ships an index-less, on-the-fly BFS search as the always-available fallback; a SearchIndexer adds a persistent index that the `search` base can build once (as admin, over the full path space) and then query cheaply — database (SQL LIKE), bleve (embedded full-text) and meilisearch (external service) each implement this interface as an independent built-in extension.

Like ext.AuthNMethod, the contract is defined here so an out-of-process (gRPC) third-party backend is a stable future seam; this wave wires only in-process TRUSTED instances the host itself authors (RegisterTrustedSearchIndexerInstance on the host side). The host alone owns the security invariant — the index is built as admin and stores full paths, and the `search` base re-authorizes every hit against the requesting user before returning it — so an indexer never makes an access decision; it only stores and matches nodes.

type SearchIndexerConfig

type SearchIndexerConfig struct {
	Name       string `json:"name"`
	AutoUpdate bool   `json:"auto_update"`
}

SearchIndexerConfig is a backend's self-description. Name is the stable id the `search_index` setting selects on; AutoUpdate advertises whether the backend can apply incremental updates (consulted by the later auto-update wave).

type SearchNode

type SearchNode struct {
	Parent string `json:"parent"`
	Name   string `json:"name"`
	IsDir  bool   `json:"is_dir"`
	Size   int64  `json:"size"`
}

SearchNode is one indexed filesystem object. It mirrors v3's model.SearchNode: there is deliberately NO path field — the virtual path is derived as path.Join(Parent, Name) — so the index stores each object once, keyed by its parent directory and name.

type SearchReq

type SearchReq struct {
	Parent   string `json:"parent"`
	Keywords string `json:"keywords"`
	Scope    int    `json:"scope"` // 0 all, 1 dir, 2 file
	Page     int    `json:"page"`
	PerPage  int    `json:"per_page"`
}

SearchReq parameterizes one index query. Scope selects object kind (0 all, 1 dir only, 2 file only); Page/PerPage paginate the surviving hits.

type ServiceStatus

type ServiceStatus struct {
	// State is the coarse lifecycle state: "running" | "stopped" | "error".
	State string `json:"state"`
	// Detail is an optional human-readable line (last error, current phase).
	Detail string `json:"detail,omitempty"`
	// Metrics is optional free-form structured telemetry for the status card
	// (e.g. {"proxies":3,"traffic_bytes":10485760}).
	Metrics map[string]any `json:"metrics,omitempty"`
}

ServiceStatus is the serializable status a BackgroundService reports.

type SettingsPage

type SettingsPage struct {
	ID    string            `json:"id"`
	Title map[string]string `json:"title"`
	Route string            `json:"route,omitempty"`
}

SettingsPage is a settings section the extension contributes.

type SignScope

type SignScope string

SignScope is the permitted use of a signed link.

const (
	// SignScopeDownload authorizes fetching the referenced object's bytes.
	SignScopeDownload SignScope = "download"
)

type Signer

type Signer interface {
	SignLink(ctx context.Context, p Principal, ref ObjRef, scope SignScope, ttl time.Duration) (token string, err error)
	VerifyLink(ctx context.Context, token string) (ref ObjRef, scope SignScope, err error)
}

Signer issues signed, ID-anchored, expiring links reusing the core key set — the plugin never sees the key (design §3). A link is anchored to an ObjRef the plugin has access to; SignLink authorizes the ref before signing so a plugin cannot mint a link to an object outside its grant (invariant 4).

type StatusProvider

type StatusProvider struct {
	ID    string            `json:"id"`
	Title map[string]string `json:"title"`
	// PollPath is the host status endpoint the frontend polls, e.g.
	// "/api/v4/extensions/{id}/status/{ID}" → the host resolves the extension's
	// Status().
	PollPath string `json:"poll_path"`
}

StatusProvider is a runtime status card the frontend renders on the system/services page (design §4). The host polls PollPath and hands the card the extension's reported status (an AccessProtocol's or BackgroundService's Status()): running state, port, metrics, a start/stop toggle.

type StorageDriver

type StorageDriver interface {
	Extension
	DriverConfig() DriverConfig

	// NewInstance delivers a mounted storage's Addition JSON to the driver and
	// builds its session. One mounted storage = one instance.
	NewInstance(ctx context.Context, storageID string, config json.RawMessage) error
	// DropInstance tears an instance down.
	DropInstance(ctx context.Context, storageID string) error

	// List lists a directory. dir.StorageID routes to the instance.
	List(ctx context.Context, dir ObjRef, args ListArgs) ([]Obj, error)
	// Link resolves how to fetch a file's bytes (see Link / ByteSource).
	Link(ctx context.Context, file ObjRef, args LinkArgs) (*Link, error)
}

StorageDriver is a storage backend extension (9.0 §3). On top of the process-level Extension lifecycle it adds instance-level lifecycle, because a single driver process multiplexes many mounted storages: ops route to an instance by ObjRef.StorageID.

type Store

type Store interface {
	KV() KV
	// Secrets returns a KV namespace whose values are encrypted at rest
	// (AES-256-GCM) on the host side — for a plugin holding OAuth tokens or API
	// keys it should not persist in the plaintext KV. It shares KV's shape and
	// per-plugin namespacing/grant gate, but its storage extent is disjoint from
	// KV()'s: a key set through Secrets() is never visible through KV() (and
	// vice versa), even when both hold the same key string.
	Secrets() KV
	// Table returns a named, schemaless document table: JSON rows addressed by
	// a host-assigned primary key. Unlike Secrets (a fully disjoint storage
	// extent), Table is a structured VIEW over the same backend as KV() —
	// isolation between tables (and from plain KV keys) is by key-prefix
	// design (a reserved "table:" prefix), not a separate storage extent. name
	// must not be empty and must not contain ':' (rejected — see the hostcap
	// implementation's escaping scheme).
	Table(name string) Table
}

Store is a plugin's private, host-managed persistence, namespaced by plugin id so plugin A can never read plugin B's data and no plugin can touch a core table (design §3). F0 exposes the KV half; a manifest-declared, host- migrated RELATIONAL schema (SQL-backed tables) remains a later extension — Table() here is the honest, schemaless intermediate: named tables of JSON-document rows addressed by a host-assigned primary key, layered over the same store backend as KV().

type Table

type Table interface {
	// Insert stores row as a new document and returns the host-assigned id
	// (a random, unique token — never derived from a counter or wall clock).
	Insert(ctx context.Context, row map[string]any) (id string, err error)
	// Get returns the row at id, or ok=false if no such row exists in this
	// table.
	Get(ctx context.Context, id string) (row map[string]any, ok bool, err error)
	// Update replaces the row at id. It is error-if-missing (not an upsert):
	// updating a nonexistent id returns an error rather than silently
	// creating it, so a caller's Update always means "I believe this row
	// already exists" — Insert is the only way to mint a new id.
	Update(ctx context.Context, id string, row map[string]any) error
	// Delete removes the row at id. Deleting a nonexistent id is a no-op
	// (mirrors KV.Delete's semantics).
	Delete(ctx context.Context, id string) error
	// List returns every row in this table (id + row), scanning the table's
	// key-prefix range. Order is unspecified.
	List(ctx context.Context) ([]TableRow, error)
}

Table is a per-plugin, schemaless document table: named rows of arbitrary JSON data addressed by a primary key the host assigns on Insert. This is the F0 realization of "structured storage beyond raw KV" — no SQL engine, no manifest-declared schema (that richer relational form is a later extension); just PK-addressed JSON rows with list/scan, namespaced by plugin id and table name on the host side.

type TableRow

type TableRow struct {
	ID  string
	Row map[string]any
}

TableRow is one row returned by Table.List: its host-assigned primary key paired with the JSON document stored at it.

type Theme

type Theme struct {
	ID     string            `json:"id"`
	Name   map[string]string `json:"name"`
	Author string            `json:"author,omitempty"`
	Tokens string            `json:"tokens,omitempty"` // relative path to the token JSON (spec §5.3)
	Css    string            `json:"css,omitempty"`    // optional relative path to the restricted CSS (§5.4)
	Fonts  []ThemeFont       `json:"fonts,omitempty"`  // optional same-origin bundled fonts (§5.5.3)
}

Theme is a UI theme the extension contributes (theming spec §5.2). Beyond the picker metadata (id/name/author) it points at the theme's token file and its optional restricted-css file + bundled fonts — all RELATIVE paths into the plugin's own asset tree, served same-origin under /plugin-assets/<plugin-id>/. The frontend fetches these, runs them through the shared sanitizer/validator gate, and registers the theme (no code or behavior crosses the boundary — a theme is declarative data + a gated, scoped stylesheet). A declaration with only id/name is metadata the frontend cannot load (nothing to fetch).

type ThemeFont

type ThemeFont struct {
	Family string `json:"family"`
	Src    string `json:"src"`
	Weight string `json:"weight,omitempty"`
	Style  string `json:"style,omitempty"`
}

ThemeFont is a bundled-font reference in a theme contribution: a family NAME + a RELATIVE path into the plugin's assets. The host rewrites the path to a same-origin /plugin-assets/<id>/… url and emits the @font-face — the plugin never writes a remote src. woff2 only (validated by the frontend's magic-byte sniff), backed by CSP font-src 'self'.

type Token

type Token struct {
	AccessToken  string         `json:"access_token"`
	RefreshToken string         `json:"refresh_token,omitempty"`
	ExpiresIn    int64          `json:"expires_in,omitempty"` // seconds; 0 = unknown/non-expiring
	TokenType    string         `json:"token_type,omitempty"`
	Raw          map[string]any `json:"raw,omitempty"`
}

Token is the provider-agnostic result of Exchange/Refresh: an opaque JSON blob (Raw) plus the few fields the host understands well enough to act on — surface it as a storage credential, know whether/when to refresh it. A provider may put vendor-specific fields (e.g. an account id) only in Raw; the host round-trips Raw verbatim as part of the resulting storage's Addition config.

type ToolStatus

type ToolStatus struct {
	TotalBytes int64   `json:"total_bytes"`
	Progress   float64 `json:"progress"`
	NewGID     string  `json:"new_gid"`
	Completed  bool    `json:"completed"`
	Status     string  `json:"status"`
	Err        error   `json:"-"`
}

ToolStatus is a value snapshot of one download's state as reported by the daemon. NewGID follows a daemon that re-keys a download (e.g. aria2 promoting a metadata download to the real file); Completed signals the base to transfer the finished files into the target storage; Err carries a daemon-side failure.

type Transport

type Transport uint8

Transport is how a protocol plugin receives inbound traffic.

const (
	// TransportHTTP: the host proxies inbound HTTP requests to ServeHTTP. Works
	// in-process or cross-process (gRPC). Used by WebDAV, S3, MCP.
	TransportHTTP Transport = iota
	// TransportConn: the host owns a listener and hands each accepted net.Conn to
	// HandleConn. Raw byte streams (FTP/SFTP); native/in-process only in v4 (a raw
	// conn does not cross the gRPC boundary).
	TransportConn
)

func (Transport) String

func (t Transport) String() string

String renders a Transport for logs / diagnostics.

type UIPanel

type UIPanel struct {
	ID        string            `json:"id"`
	Title     map[string]string `json:"title"`
	Route     string            `json:"route"`
	Placement string            `json:"placement,omitempty"` // sidebar | main | modal
}

UIPanel is a custom rich-interaction panel rendered in a sandboxed iframe from the plugin bundle (SDK §5).

type UploadInfo

type UploadInfo struct {
	Name     string            `json:"name"`
	Size     int64             `json:"size"`
	Mimetype string            `json:"mimetype,omitempty"`
	Hashes   map[string]string `json:"hashes,omitempty"` // host may precompute; empty => driver hashes itself via ReadAt
	Exist    *Obj              `json:"exist,omitempty"`  // carries v3 GetExist/SetExist overwrite semantics
}

UploadInfo is the metadata about an upload (9.0 §2).

type UploadStream

type UploadStream interface {
	Info() UploadInfo
	// ReadAt is a replayable random read served by the host from a temp/seekable
	// source. It follows io.ReaderAt semantics.
	ReadAt(p []byte, off int64) (n int, err error)
}

UploadStream is the host-held, replayable upload source handed to a driver's Putter (9.0 §2, "pull model"). The host lands the inbound stream in a temp/seekable source; the driver reads it by range via ReadAt. This satisfies two-pass reads (hash the whole file, then upload in parts), resumable upload, and header peeking of non-seekable streams.

Responsibilities the host keeps (so 90 drivers need not each reimplement them, 9.0 §2): rate limiting is applied host-side inside ReadAt; cancellation flows through ctx on the driver's request methods; progress is reported via a Host callback rather than a per-call progress func.

type Wizard

type Wizard struct {
	Steps []WizardStep `json:"steps,omitempty"`
}

Wizard is an extension's declarative install flow (SDK doc §4). The host runs it as a step state machine (internal/extension/wizard.go); the frontend renders each step natively.

type WizardOption

type WizardOption struct {
	ID       string            `json:"id"`
	Label    map[string]string `json:"label"`
	NextStep string            `json:"next_step,omitempty"`
}

WizardOption is one branch of a choice step. NextStep, when set, names the step id to jump to after the option is chosen (else the flow advances to the next step in order).

type WizardStep

type WizardStep struct {
	ID      string            `json:"id"`
	Type    string            `json:"type"`
	Title   map[string]string `json:"title,omitempty"`
	Body    map[string]string `json:"body,omitempty"`     // info / risk / consent copy
	Schema  *JSONSchema       `json:"schema,omitempty"`   // form: field definitions
	Options []WizardOption    `json:"options,omitempty"`  // choice: branches
	ProbeID string            `json:"probe_id,omitempty"` // test: probe selector
	// Provider is the AuthProvider extension id an oauth step drives (WS-8).
	// Required when Type == WizardOAuth; the host resolves it to a live
	// ext.AuthProvider and calls AuthURL/Exchange against it — locally, never
	// through an external broker.
	Provider string `json:"provider,omitempty"`
	// Scopes are the OAuth scopes requested from Provider. oauth only.
	Scopes []string `json:"scopes,omitempty"`
}

WizardStep is one node of the install flow.

Directories

Path Synopsis
Package grpcplugin is the plugin-side half of the Step 9.2 gRPC executor.
Package grpcplugin is the plugin-side half of the Step 9.2 gRPC executor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL