Class: Kward::RPC::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/kward/rpc/server.rb

Overview

Experimental JSON-RPC backend for UI clients.

The server speaks LSP-style Content-Length framing over stdin/stdout, exposes capabilities during initialize, redacts secrets in errors and notifications, and coordinates auth, config, sessions, turns, tools, memory, commands, and startup resources.

Server should stay focused on protocol concerns: framing, JSON-RPC error codes, method dispatch, capability reporting, and redaction at the wire boundary. Delegate stateful product behavior to manager objects such as SessionManager, AuthManager, and ConfigManager. When adding an RPC feature, update dispatch, capabilities, docs, and tests together so clients can trust initialize as the source of supported behavior.

Constant Summary collapse

PROTOCOL_VERSION =
1
JSONRPC_VERSION =
"2.0"
BUILTIN_SLASH_COMMAND_NAMES =
PromptCommands::BUILTIN_RESERVED_COMMAND_NAMES
ERROR_CODES =
{
  parse_error: -32_700,
  invalid_request: -32_600,
  method_not_found: -32_601,
  invalid_params: -32_602,
  internal_error: -32_603
}.freeze
SESSION_METHODS =
[
  "sessions/create", "sessions/resume", "sessions/list", "sessions/rename",
  "sessions/clone", "sessions/compact", "sessions/forkMessages", "sessions/fork",
  "sessions/tree", "sessions/tree/setLabel", "sessions/tree/navigate",
  "sessions/export", "sessions/delete", "sessions/close", "sessions/transcript"
].freeze
MODEL_METHODS =
["models/list", "models/current", "models/set", "reasoning/set", "openrouter/catalog"].freeze
AUTH_METHODS =
[
  "auth/status", "auth/providers", "auth/loginWithApiKey", "auth/logoutProvider",
  "auth/loginWithOAuth", "auth/startOpenAILogin", "auth/submitOpenAICode", "auth/loginStatus"
].freeze
MEMORY_METHODS =
[
  "memory/status", "memory/enable", "memory/disable", "memory/autoSummary/enable",
  "memory/autoSummary/disable", "memory/list", "memory/add", "memory/addCore",
  "memory/forget", "memory/promote", "memory/relax", "memory/inspect",
  "memory/why", "memory/summarize"
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(input: $stdin, output: $stdout, error_output: $stderr, client: Client.new) ⇒ Server

Creates the RPC server and its stateful managers.



65
66
67
68
69
70
71
72
# File 'lib/kward/rpc/server.rb', line 65

def initialize(input: $stdin, output: $stdout, error_output: $stderr, client: Client.new)
  @transport = Transport.new(input: input, output: output)
  @error_output = error_output
  @config_manager = ConfigManager.new
  @session_manager = SessionManager.new(server: self, client: client, config_manager: @config_manager)
  @auth_manager = AuthManager.new(server: self, config_manager: @config_manager)
  @shutdown = false
end

Instance Method Details

#error_payload(error) ⇒ Hash

Builds redacted diagnostics suitable for JSON-RPC error data.

Parameters:

  • error (Exception)

Returns:

  • (Hash)


106
107
108
109
110
111
112
# File 'lib/kward/rpc/server.rb', line 106

def error_payload(error)
  Redactor.redact({
    code: error.class.name,
    message: error.message,
    backtrace: Array(error.backtrace).first(8)
  })
end

#log_error(error) ⇒ Object



114
115
116
# File 'lib/kward/rpc/server.rb', line 114

def log_error(error)
  @error_output.puts("Kward RPC error: #{Redactor.redact_string(error.message)}") if @error_output
end

#notify(method, params = {}) ⇒ Object

Sends a redacted JSON-RPC notification to the client.

Parameters:

  • method (String)

    notification method name

  • params (Hash) (defaults to: {})

    notification params



98
99
100
# File 'lib/kward/rpc/server.rb', line 98

def notify(method, params = {})
  @transport.write_message({ jsonrpc: JSONRPC_VERSION, method: method, params: Redactor.redact(params) })
end

#runvoid

This method returns an undefined value.

Reads framed JSON-RPC messages until shutdown or EOF.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/kward/rpc/server.rb', line 77

def run
  until @shutdown
    begin
      message = @transport.read_message
      break unless message

      handle_message(message)
    rescue JSON::ParserError => e
      write_error(nil, ERROR_CODES[:parse_error], "Parse error", e)
    rescue StandardError => e
      write_error(nil, ERROR_CODES[:invalid_request], e.message, e)
    end
  end
ensure
  @session_manager.cleanup_unused_sessions
end