siyuan-note/siyuan · error

plugin service output requires a protocol declaration

Error message

plugin service output requires a protocol declaration

What it means

Endpoint definitions that declare Output = PluginServiceOutput must also supply a PluginService protocol declaration (the PluginServiceDefinition returned by PluginServiceOptions()); the two must be set or unset together. validatePluginServiceDefinition, run during bundle building (BuildBundle) and its tests, returns this error when the output mode says "pluginService" but the PluginService field is nil, or vice versa.

Solutions

  1. Assign definition.PluginService = &PluginServiceOptions().PluginService whenever Output is PluginServiceOutput
  2. Or set Output to the correct non-plugin mode if the endpoint is not a plugin service
  3. Use the framework's endpoint builder so Output mode and protocol declaration are set together

Example fix

// before
definition := apicontract.Definition{Output: apicontract.PluginServiceOutput}
// after
definition := apicontract.Definition{Output: apicontract.PluginServiceOutput,
    Data: reflect.TypeFor[apicontract.PluginServiceContent](),
    PluginService: &apicontract.PluginServiceOptions().PluginService}
Defensive patterns

Strategy: validation

Validate before calling

if (definition.Output == apicontract.PluginServiceOutput) != (definition.PluginService != nil) {
    return errors.New("plugin service output and protocol declaration must be set together")
}

Try / catch

if err := apicontract.BuildBundle(endpoints); err != nil {
    if strings.Contains(err.Error(), "protocol declaration") { log.Fatalf("endpoint definition mismatch: %v", err) }
    return err
}

Prevention

When it happens

Trigger: BuildBundle registers an endpoint whose Definition has Output set to PluginServiceOutput without assigning the PluginService definition, or a normal (non-plugin-service) endpoint that accidentally sets definition.PluginService while keeping a different Output mode.

Common situations: Hand-assembling a Definition struct instead of using the framework's builder, adding a plugin service endpoint by copying the output mode line but forgetting PluginServiceOptions(), or changing an existing endpoint's Output mode without removing the stale PluginService field.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/7d80f8f51686cd09. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/plugin_service_protocol.go:119

func (e Endpoint[Request, Data]) pluginServiceStatus(response Response[Data]) int {
	if e.definition.Output != PluginServiceOutput || e.definition.PluginService == nil {
		panic("endpoint does not declare plugin service output")
	}
	if response.stream == nil {
		if response.code == 0 {
			panic("plugin service requires a selected response mode")
		}
		return 200
	}
	if err := validatePluginServiceStatus(response.pluginServiceMode, response.httpStatus); err != nil {
		panic(err)
	}
	return response.httpStatus
}

func validatePluginServiceDefinition(definition Definition) error {
	if (definition.Output == PluginServiceOutput) != (definition.PluginService != nil) {
		return fmt.Errorf("plugin service output requires a protocol declaration")
	}
	if definition.PluginService == nil {
		return nil
	}
	if definition.Data != reflect.TypeFor[PluginServiceContent]() || definition.SSE != nil || definition.Proxy != nil || definition.WebSocket != nil || definition.DataOnError || definition.ErrorStatus != 0 {
		return fmt.Errorf("invalid plugin service response options")
	}
	if !reflect.DeepEqual(definition.PluginService, PluginServiceOptions().PluginService) {
		return fmt.Errorf("invalid plugin service protocol variants")
	}
	return nil
}

func validatePluginServiceStatus(mode PluginServiceMode, status int) error {
	if status < 100 || status > 999 {
		return fmt.Errorf("invalid plugin service HTTP status: %d", status)
	}
	known := false

View on GitHub (pinned to 9f775e8a12)