syntax = "proto3";

package iop;

import "google/protobuf/struct.proto";

option go_package = "iop/proto/gen/iop";

enum RunSessionMode {
  RUN_SESSION_MODE_UNSPECIFIED       = 0; // default: create if missing
  RUN_SESSION_MODE_CREATE_IF_MISSING = 1;
  RUN_SESSION_MODE_REQUIRE_EXISTING  = 2;
}

enum CancelAction {
  CANCEL_ACTION_UNSPECIFIED       = 0; // default: cancel run only
  CANCEL_ACTION_CANCEL_RUN        = 1;
  CANCEL_ACTION_TERMINATE_SESSION = 2;
}

// RunRequest initiates an adapter execution on a node.
message RunRequest {
  string run_id                  = 1;
  string adapter                 = 2;
  string target                  = 3;
  string workspace               = 4;
  google.protobuf.Struct policy  = 5;
  google.protobuf.Struct input   = 6;
  int32  timeout_sec             = 7;
  map<string, string> metadata   = 8;
  string session_id              = 9;
  RunSessionMode session_mode    = 10;
  bool background                = 11;
}

// RunEvent is a streaming execution event.
message RunEvent {
  string run_id    = 1;
  string type      = 2; // start | delta | complete | error | cancelled
  string delta     = 3;
  string message   = 4;
  string error     = 5;
  Usage  usage     = 6;
  map<string, string> metadata = 7;
  int64  timestamp = 8; // unix nano
  string session_id = 9;
  bool background   = 10;
  string node_id    = 11;
  string node_alias = 12;
}

// EdgeNodeEvent is a general edge-node lifecycle/control event envelope.
// It is separate from RunEvent, which is reserved for adapter execution streams.
message EdgeNodeEvent {
  string event_id  = 1;
  string type      = 2; // node.connected | node.disconnected | edge.disconnected | ...
  string source    = 3; // edge | node
  string node_id   = 4;
  string alias     = 5;
  string reason    = 6;
  map<string, string> metadata = 7;
  int64 timestamp = 8; // unix nano
}

message Usage {
  int32 input_tokens  = 1;
  int32 output_tokens = 2;
}

// Heartbeat is sent by both sides to keep the connection alive.
message Heartbeat {
  int64 timestamp = 1;
}

// CancelRequest asks the node to cancel a running execution.
message CancelRequest {
  string run_id       = 1;
  string adapter      = 2;
  string target       = 3;
  string session_id   = 4;
  CancelAction action = 5;
}

enum NodeCommandType {
  NODE_COMMAND_TYPE_UNSPECIFIED      = 0;
  NODE_COMMAND_TYPE_USAGE_STATUS     = 1;
  NODE_COMMAND_TYPE_CAPABILITIES     = 2;
  NODE_COMMAND_TYPE_SESSION_LIST     = 3;
  NODE_COMMAND_TYPE_TRANSPORT_STATUS = 4;
  NODE_COMMAND_TYPE_OLLAMA_API       = 5;
}

message NodeCommandRequest {
  string request_id              = 1;
  NodeCommandType type           = 2;
  string adapter                 = 3;
  string target                  = 4;
  string session_id              = 5;
  int32 timeout_sec              = 6;
  map<string, string> metadata   = 7;
}

message NodeCommandResponse {
  string request_id              = 1;
  NodeCommandType type           = 2;
  string adapter                 = 3;
  string target                  = 4;
  string session_id              = 5;
  AgentUsageStatus usage_status  = 6;
  string error                   = 7;
  // result carries free-form key/value data for non-usage-status command types
  // (capabilities, session_list, transport_status). usage_status keeps its
  // dedicated typed payload.
  map<string, string> result     = 8;
  repeated ProviderSnapshot provider_snapshots = 9;
}

// ProviderSnapshot is the legacy wire name for a node resource/provider status
// snapshot. category identifies whether the resource is API, CLI, or local
// inference; provider-pool dispatch is limited to catalog resources referenced
// by models[].providers.
message ProviderSnapshot {
  string adapter    = 1;
  string status     = 2; // unknown|available|unavailable
  int32  capacity   = 3;
  int32  in_flight  = 4;
  int32  queued     = 5;

  // Provider catalog fields (MVP).
  // id uniquely identifies this provider within its node.
  string id              = 6;
  // type is the runtime type (e.g. "ollama", "vllm", "lemonade", "sglang", "openai_api", "cli").
  string type            = 7;
  // category classifies the provider; MVP values: api, cli, local_inference.
  string category        = 8;
  // served_models lists the model names this provider can actually serve.
  repeated string served_models = 9;
  // health is the observed provider health state (e.g. "healthy", "degraded", "unhealthy").
  string health          = 10;
  // load_ratio is the computed in_flight / capacity value.  When capacity is 0
  // or unknown the provider is treated as unavailable for selection.
  float  load_ratio      = 11;
  // lifecycle_capabilities lists coarse lifecycle capability flags (e.g.
  // "list_models", "load_model", "unload_model", "pull_model", "delete_model").
  repeated string lifecycle_capabilities = 12;
}

message AgentUsageStatus {
  string raw_output              = 1;
  string daily_limit             = 2;
  string daily_reset_time        = 3;
  string weekly_limit            = 4;
  string weekly_reset_time       = 5;
  map<string, string> metadata   = 6;
}


// Error is returned when a request fails at the transport layer.
message Error {
  string code    = 1;
  string message = 2;
}

// RegisterRequest is sent by node to edge immediately on connect.
message RegisterRequest {
  string token = 1;
}

// RegisterResponse is sent by edge to node in response to RegisterRequest.
message RegisterResponse {
  bool              accepted = 1;
  string            node_id  = 2;
  string            alias    = 3;
  string            reason   = 4; // rejection reason
  NodeConfigPayload config   = 5;
}

// NodeConfigPayload carries all configuration edge pushes to the node.
message NodeConfigPayload {
  repeated AdapterConfig    adapters = 1;
  NodeRuntimeConfig         runtime  = 2;
}

// AdapterConfig describes one adapter to enable on the node.
// name is the stable instance identity within a node; for single-instance
// adapters it may be empty (equivalent to the type name). When a node carries
// multiple instances of the same adapter type each must have a unique name.
message AdapterConfig {
  string                 type     = 1; // "mock" | "ollama" | "vllm" | "cli" | "openai_compat"
  bool                   enabled  = 2;
  google.protobuf.Struct settings = 3; // legacy/compat path; new adapters use oneof
  oneof config {
    CLIAdapterConfig          cli           = 4;
    OllamaAdapterConfig       ollama        = 5;
    VllmAdapterConfig         vllm          = 6;
    MockAdapterConfig         mock          = 7;
    OpenAICompatAdapterConfig openai_compat = 10;
  }
  string name   = 8; // stable instance identity; empty = legacy single-instance
  string target = 9; // optional default target/route hint for this instance
}

message MockAdapterConfig {}

message CLIAdapterConfig {
  map<string, CLIProfileConfig> profiles = 1;
}

message CLIProfileConfig {
  string   command                       = 1;
  repeated string args                   = 2;
  repeated string env                    = 3;
  bool     persistent                    = 4;
  bool     terminal                      = 5;
  int32    response_idle_timeout_ms      = 6;
  int32    startup_idle_timeout_ms       = 7;
  string   output_format                 = 8;
  CLICompletionMarker completion_marker  = 9;
  string   mode                          = 10;
  repeated string resume_args            = 11;
}

message CLICompletionMarker {
  string line  = 1;
  string regex = 2;
}

message OllamaAdapterConfig {
  string base_url           = 1;
  int32  context_size       = 2;
  int32  capacity           = 3;
  int32  max_queue          = 4;
  int32  queue_timeout_ms   = 5;
  int32  request_timeout_ms = 6;
}

message VllmAdapterConfig {
  string endpoint           = 1;
  int32  capacity           = 2;
  int32  max_queue          = 3;
  int32  queue_timeout_ms   = 4;
  int32  request_timeout_ms = 5;
}

message OpenAICompatAdapterConfig {
  string provider             = 1;
  string endpoint             = 2;
  map<string, string> headers = 3;
  int32  capacity             = 4;
  int32  max_queue            = 5;
  int32  queue_timeout_ms     = 6;
  int32  request_timeout_ms   = 7;
}

// NodeRuntimeConfig carries legacy node runtime metadata. Execution admission
// must not use this as a node-wide global gate; provider/resource capacity owns
// concurrency.
message NodeRuntimeConfig {
  int32 concurrency = 1; // legacy compatibility, 0/unset means no node-wide limit
  reserved 2;
}

enum NodeConfigRefreshStatus {
  NODE_CONFIG_REFRESH_STATUS_UNSPECIFIED     = 0;
  NODE_CONFIG_REFRESH_STATUS_APPLIED         = 1;
  NODE_CONFIG_REFRESH_STATUS_RESTART_REQUIRED = 2;
  NODE_CONFIG_REFRESH_STATUS_FAILED          = 3;
  NODE_CONFIG_REFRESH_STATUS_SKIPPED         = 4;
}

// NodeConfigRefreshRequest is sent by edge to a connected node to push a new config payload.
message NodeConfigRefreshRequest {
  string            request_id    = 1;
  NodeConfigPayload config        = 2;
  repeated string   changed_paths = 3;
}

// NodeConfigRefreshResponse is returned by the node to acknowledge or report failure.
message NodeConfigRefreshResponse {
  string                  request_id             = 1;
  NodeConfigRefreshStatus status                 = 2;
  repeated string         restart_required_paths = 3;
  string                  error                  = 4;
}
