syntax = "proto3";

package spearlet;

// RouterFilterService: Router Filter is a server; Spearlet dials it as a gRPC client.
// RouterFilterService：Router Filter 作为服务端；Spearlet 作为 gRPC client 主动连接并发起过滤请求。
//
// Best practice:
// - Keep payload small and bounded (signals + optional payload snippet).
// - Fail-open is controlled by Spearlet config.
// 业界实践：
// - 控制请求体积（signals + 可选 payload 片段），避免大包影响路由延迟。
// - fail-open 策略由 Spearlet 配置控制。
service RouterFilterService {
  // Filter asks the server to filter/score candidates for a single routing decision.
  // Filter 请求服务端对一次路由决策进行候选过滤/打分。
  rpc Filter(FilterRequest) returns (FilterResponse);
}

// Operation is aligned with CanonicalRequestEnvelope.operation.
// Operation 与 CanonicalRequestEnvelope.operation 对齐。
enum Operation {
  OPERATION_UNSPECIFIED = 0;
  OPERATION_CHAT_COMPLETIONS = 1;
  OPERATION_EMBEDDINGS = 2;
  OPERATION_IMAGE_GENERATION = 3;
  OPERATION_SPEECH_TO_TEXT = 4;
  OPERATION_TEXT_TO_SPEECH = 5;
  OPERATION_REALTIME_VOICE = 6;
}

// FilterRequest is sent by Spearlet to ask server to filter/score candidates.
// FilterRequest 由 Spearlet 发送，要求服务端对候选集过滤/打分。
message FilterRequest {
  // Correlation id used for multiplexing concurrent requests on a single stream.
  // 用于观测/排障关联一次路由决策的关联 ID。
  string correlation_id = 1;

  // CanonicalRequestEnvelope.request_id
  // CanonicalRequestEnvelope.request_id
  string request_id = 2;

  // CanonicalRequestEnvelope.operation
  // CanonicalRequestEnvelope.operation
  Operation operation = 3;

  // Decision time budget in milliseconds.
  // 决策时间预算（毫秒）。
  uint32 decision_timeout_ms = 4;

  // Non-secret request metadata.
  // 非 secret 的请求元信息。
  map<string, string> meta = 5;

  // Routing hints derived from request / normalize.
  // 来自请求 / normalize 的路由提示。
  RoutingHints routing = 6;

  // Required features/transports derived from normalize layer.
  // normalize 层产出的 required features/transports。
  Requirements requirements = 7;

  // Operation-specific, size-limited request signals.
  // 面向 operation 的、受大小限制的请求 signals。
  RequestSignals signals = 8;

  // Candidate set after hard filtering and routing hints.
  // 硬过滤与 routing hints 后的候选集。
  repeated Candidate candidates = 9;

  // Optional request payload snippet for filtering (size-bounded by Spearlet config).
  // 可选：用于过滤的请求 payload 片段（大小由 Spearlet 配置限制）。
  //
  // For privacy and latency, this should be disabled by default and only enabled when needed.
  // 出于隐私与延迟考虑，默认应关闭，仅在确有需要时开启。
  string request_content_type = 10;
  bytes request_payload = 11;
}

// RoutingHints describes hard routing constraints and requested model.
// RoutingHints 描述硬路由约束与请求的模型名。
message RoutingHints {
  // Optional: force backend name before filter (already applied by router).
  // 可选：指定 backend 名称（router 已先行应用）。
  string backend = 1;
  repeated string allowlist = 2;
  repeated string denylist = 3;
  string requested_model = 4;
}

// Requirements describes normalized required features/transports.
// Requirements 描述 normalize 后的 required features/transports。
message Requirements {
  repeated string required_features = 1;
  repeated string required_transports = 2;
}

// RequestSignals provides small, operation-specific properties.
// RequestSignals 提供小体积、面向 operation 的属性信号。
message RequestSignals {
  // Chat / 聊天
  string model = 1;
  uint32 message_count = 2;
  uint32 approx_text_bytes = 3;
  bool uses_tools = 4;
  bool uses_json_schema = 5;

  // ASR / 语音识别
  string audio_codec = 10;
  uint32 audio_sample_rate_hz = 11;
  uint32 audio_channels = 12;
  uint32 audio_duration_ms = 13;
  string audio_language_hint = 14;
}

// Candidate describes a backend instance candidate.
// Candidate 描述一个 backend instance 候选。
message Candidate {
  // BackendInstance.name
  // BackendInstance.name
  string name = 1;

  // Backend kind (openai_chat_completion / ollama_chat / ...).
  // backend kind（openai_chat_completion / ollama_chat / ...）。
  string kind = 2;

  // Optional base_url (may be omitted for privacy).
  // 可选 base_url（出于隐私可能省略）。
  string base_url = 3;

  // BackendInstance.model (if bound).
  // BackendInstance.model（若绑定）。
  string model = 4;

  // BackendInstance.weight
  // BackendInstance.weight
  uint32 weight = 5;

  // BackendInstance.priority
  // BackendInstance.priority
  int32 priority = 6;

  // capabilities.ops (string form).
  // capabilities.ops（字符串形式）。
  repeated string ops = 7;

  // capabilities.features
  // capabilities.features
  repeated string features = 8;

  // capabilities.transports
  // capabilities.transports
  repeated string transports = 9;

  // Explicit local/remote hint from Spearlet.
  // Spearlet 提供的显式 local/remote 提示。
  bool is_local = 21;

  // Optional runtime hints.
  // 可选：运行时提示。
  CandidateRuntimeHints runtime = 20;
}

// CandidateRuntimeHints provides optional runtime metrics.
// CandidateRuntimeHints 提供可选的运行时指标。
message CandidateRuntimeHints {
  double ewma_latency_ms = 1;
  double recent_error_rate = 2;
  uint32 inflight = 3;
  bool healthy = 4;
}

// FilterResponse is returned by server to carry decisions.
// FilterResponse 由服务端返回，携带决策结果。
message FilterResponse {
  // Must match FilterRequest.correlation_id.
  // 必须与 FilterRequest.correlation_id 一致。
  string correlation_id = 1;

  // Optional decision id for observability.
  // 可选：用于观测的 decision id。
  string decision_id = 2;

  // Per-candidate decisions.
  // 针对候选的决策列表。
  repeated CandidateDecision decisions = 3;

  // Optional request-level final action.
  // 可选：请求级最终动作。
  FinalAction final_action = 4;

  // Optional debug kv (size-limited by spearlet).
  // 可选 debug kv（由 spearlet 侧限制大小）。
  map<string, string> debug = 5;
}

// CandidateDecision describes keep/drop and optional overrides for one candidate.
// CandidateDecision 描述单个候选的 keep/drop 与可选覆盖。
message CandidateDecision {
  // Candidate.name / 候选名称
  string name = 1;

  // KEEP keeps candidate; DROP removes it.
  // KEEP 表示保留；DROP 表示移除。
  DecisionAction action = 2;

  // Optional overrides.
  // 可选覆盖值。
  optional uint32 weight_override = 3;
  optional int32 priority_override = 4;
  optional double score = 5;

  // Reason codes for observability.
  // 原因码（用于观测与排障）。
  repeated string reason_codes = 6;
}

// DecisionAction defines per-candidate action.
// DecisionAction 定义单候选动作。
enum DecisionAction {
  DECISION_ACTION_UNSPECIFIED = 0;
  DECISION_ACTION_KEEP = 1;
  DECISION_ACTION_DROP = 2;
}

// FinalAction defines request-level forced action.
// FinalAction 定义请求级强制动作。
message FinalAction {
  // If true, router rejects the request (fail-closed).
  // 若为 true，router 直接拒绝请求（fail-closed）。
  bool reject_request = 1;

  // Reject code/message.
  // 拒绝码/消息。
  string reject_code = 2;
  string reject_message = 3;

  // Optional: force selecting a specific backend name.
  // 可选：强制选择某个 backend name。
  string force_backend = 10;
}
