// Proposed contract, version 1. Status: proposal for review, not confirmed design.
//
// Every call to a language model. The gateway holds the provider keys, routes by data class and plan tier,
// checks the prompt and the answer with Model Armor, reserves usage against the Organization's entitlement before
// the call and settles it after, and writes the ledger. No prompt is stored.
// Callers: moochat (completions of a turn; req 120, 121, 125), grant (OCR and extraction of documents and calls;
// batches through BatchesService, decided 2026-10-31, D23: the provider keys never leave the gateway).
// Deadline: set by the caller, at most 120 s; a stream carries partial answers.
// The callers listed are authentication only (decided 2026-11-11): each RPC below names who may call it and what
// it sees, and refuses anyone else with PERMISSION_DENIED.
// x-moonitoring-callers: moochat, grant
// x-moonitoring-requirements: 120, 121, 125, 69, 205, 206
syntax = "proto3";

package moonitoring.ai_gateway.completions.v1;

service CompletionsService {
  // A chat completion, streamed. The first message of the stream is the routing decision; the last carries usage.
  // Callers: moochat and grant. Sees: the answer to its own request, charged to the Organization it names.
  rpc Complete(CompleteRequest) returns (stream CompleteResponse);
  // Embeddings for texts.
  // Callers: moochat and grant. Sees: the embeddings of the texts it sent.
  rpc Embed(EmbedRequest) returns (EmbedResponse);
}

message CompleteRequest {
  // The caller's request id: the same id in Cloud Logging, in AgentMessage.request_id and in the ledger.
  string request_id = 1;
  // Exactly one of organization_id and moonitoring_user_id: who is metered.
  string organization_id = 2;
  string moonitoring_user_id = 3;
  // The metered feature, e.g. moochat.chat.
  string feature = 4;
  // The AgentModelProfile to use; the gateway resolves provider, model, region and parameters from it.
  string model_profile_id = 5;
  // The highest data class in the prompt; personal goes only to a profile that allows it, in the EU.
  DataClass data_class = 6;
  repeated Message messages = 7;
  // Tools the model may call, as JSON schemas; the caller runs them.
  repeated ToolDefinition tools = 8;
  // Optional overrides within the profile's bounds.
  Parameters parameters = 9;
}

enum DataClass {
  DATA_CLASS_UNSPECIFIED = 0;
  DATA_CLASS_PUBLIC = 1;
  DATA_CLASS_PERSONAL = 2;
}

message Message {
  Role role = 1;
  string content = 2;
  // For a tool result: the call it answers.
  string tool_call_id = 3;
}

enum Role {
  ROLE_UNSPECIFIED = 0;
  ROLE_SYSTEM = 1;
  ROLE_USER = 2;
  ROLE_ASSISTANT = 3;
  ROLE_TOOL = 4;
}

message ToolDefinition {
  string name = 1;
  string description = 2;
  string input_schema_json = 3;
}

message Parameters {
  // Omission keeps the profile value; an explicit zero remains an override within the profile's bounds.
  optional float temperature = 1;
  optional int32 max_output_tokens = 2;
  optional int32 thinking_budget = 3;
}

message CompleteResponse {
  oneof part {
    Routing routing = 1;
    string text_delta = 2;
    ToolCall tool_call = 3;
    Usage usage = 4;
    Blocked blocked = 5;
  }
}

message Routing {
  string provider = 1;
  string model = 2;
  string region = 3;
  string model_profile_id = 4;
}

message ToolCall {
  string id = 1;
  string name = 2;
  string arguments_json = 3;
}

message Usage {
  int32 input_tokens = 1;
  int32 output_tokens = 2;
  int64 cost_micros = 3;
  int32 latency_ms = 4;
  FinishReason finish_reason = 5;
}

enum FinishReason {
  FINISH_REASON_UNSPECIFIED = 0;
  FINISH_REASON_STOP = 1;
  FINISH_REASON_TOOL_CALLS = 2;
  FINISH_REASON_LENGTH = 3;
}

// The call was not made, or its answer withheld.
message Blocked {
  BlockReason reason = 1;
  string detail = 2;
}

enum BlockReason {
  BLOCK_REASON_UNSPECIFIED = 0;
  BLOCK_REASON_LIMIT_REACHED = 1;
  BLOCK_REASON_NOT_ENTITLED = 2;
  BLOCK_REASON_MODEL_ARMOR = 3;
  BLOCK_REASON_DATA_CLASS = 4;
}

// Provider batch jobs (OpenRouter Batch, Gemini Batch) for work that waits hours: the caller journals each request by
// its body hash and the gateway submits once per hash, so a resumed or repeated caller never pays twice.
service BatchesService {
  // Submits a batch of requests to a provider. Idempotent by request_hash: a hash already submitted returns its job.
  rpc SubmitBatch(SubmitBatchRequest) returns (SubmitBatchResponse);
  // Where a batch job stands; when finished, where its answers were written.
  rpc GetBatch(GetBatchRequest) returns (GetBatchResponse);
}

message SubmitBatchRequest {
  // SHA-256 of the exact request body, the identity of the work.
  string request_hash = 1;
  string organization_id = 2;
  string moonitoring_user_id = 3;
  string feature = 4;
  string model_profile_id = 5;
  DataClass data_class = 6;
  // gs:// object holding the batch input in the provider's format; the gateway reads it with its own identity.
  string input_uri = 7;
  // gs:// prefix the gateway writes the answers under, create-only.
  string output_prefix = 8;
}

message SubmitBatchResponse {
  string batch_job_id = 1;
  // True when the hash was already submitted and this is the existing job.
  bool existing = 2;
}

message GetBatchRequest {
  string batch_job_id = 1;
}

message GetBatchResponse {
  string batch_job_id = 1;
  BatchStatus status = 2;
  string provider = 3;
  string provider_job_id = 4;
  string output_uri = 5;
  Usage usage = 6;
  string error = 7;
}

enum BatchStatus {
  BATCH_STATUS_UNSPECIFIED = 0;
  BATCH_STATUS_SUBMITTED = 1;
  BATCH_STATUS_RUNNING = 2;
  BATCH_STATUS_COMPLETED = 3;
  BATCH_STATUS_FAILED = 4;
}

message EmbedRequest {
  string request_id = 1;
  string organization_id = 2;
  string moonitoring_user_id = 3;
  string feature = 4;
  string model_profile_id = 5;
  DataClass data_class = 6;
  repeated string texts = 7;
}

message EmbedResponse {
  repeated Embedding embeddings = 1;
  Usage usage = 2;
}

message Embedding {
  repeated float values = 1;
}
