siyuan-note/siyuan · error

invalid plugin service response options

Error message

invalid plugin service response options

What it means

An endpoint declared as a plugin service must use PluginServiceContent as its Data type and must not combine the plugin-service output with any other response options: SSE, Proxy, WebSocket definitions, DataOnError, or a custom ErrorStatus. validatePluginServiceDefinition returns this error when any of these conflicting options are present, because plugin service responses carry their mode/status in private response metadata set by the StreamPluginService-style constructors, not in the shared option fields.

Solutions

  1. Set definition.Data to reflect.TypeFor[apicontract.PluginServiceContent]()
  2. Remove SSE, Proxy, WebSocket, DataOnError, and ErrorStatus from the plugin-service definition
  3. If the endpoint needs those other options, it is not a plugin service — use the appropriate output mode instead

Example fix

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

Strategy: validation

Validate before calling

if definition.Output == apicontract.PluginServiceOutput {
    if definition.Data != reflect.TypeFor[apicontract.PluginServiceContent]() ||
        definition.SSE != nil || definition.Proxy != nil || definition.WebSocket != nil ||
        definition.DataOnError || definition.ErrorStatus != 0 {
        return errors.New("plugin service definitions must use PluginServiceContent and no other response options")
    }
}

Try / catch

if err := apicontract.BuildBundle(endpoints); err != nil {
    if strings.Contains(err.Error(), "response options") { log.Fatalf("conflicting endpoint options: %v", err) }
    return err
}

Prevention

When it happens

Trigger: BuildBundle validates a Definition whose Output is PluginServiceOutput but whose Data is a concrete payload type (not reflect.TypeFor[PluginServiceContent]()), or which also sets SSE, Proxy, WebSocket, DataOnError, or ErrorStatus.

Common situations: Converting an ordinary JSON endpoint into a plugin service by only changing the Output mode while leaving Data typed and ErrorStatus set, mixing SSE/event features into a plugin service endpoint, or merging definitions from two templates.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


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

Appendix: source

Thrown at kernel/apicontract/plugin_service_protocol.go:125

			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
	for _, variant := range PluginServiceOptions().PluginService.Variants {
		if variant.Mode == mode {
			known = true
			break
		}
	}

View on GitHub (pinned to 9f775e8a12)