Module: Ask::SessionProtocol::Methods

Defined in:
lib/ask/session_protocol/methods.rb

Overview

The RPC surface of the session protocol.

Direction conventions (JSON-RPC 2.0 over the host's transport):

METHODS       — client → host requests (host responds with a result)
NOTIFICATIONS — host → client notifications (no response expected):
              session/event carries the canonical event envelope
REQUESTS      — host → client reverse requests (client must respond):
              interaction/requestPermission and
              interaction/requestUserInput, defined for interop
              with the external app-server protocol standard.
              The canonical resolution path is the interaction/*
              and plan/* METHODS against event-visible interactions.

The registry below is the single source of truth for the method surface; the JSON Schema artifact is generated from it, and hosts and clients validate their implementations against it.

Constant Summary collapse

CAPABILITIES =

Host capabilities advertised in the initialize result. A client may gate UI features on them.

%w[
  sessionManagement eventStreaming midExecutionInjection
  interactions planMode todos artifacts workspace fileEvents
].freeze
ERROR_CODES =

Error codes, JSON-RPC 2.0 reserved range plus session/interaction application codes (matching the app-server protocol convention).

{
  parse_error: -32700,
  invalid_request: -32600,
  method_not_found: -32601,
  invalid_params: -32602,
  internal_error: -32603,
  session_not_found: -32004,
  session_already_exists: -32005,
  interaction_not_found: -32006,
  plan_not_found: -32007,
  workspace_error: -32008,
  not_implemented: -32009
}.freeze
METHODS =

Client → host methods with their params and result shapes. Each shape uses the same field-spec language as Events::TYPES.

{
  # ── Handshake ───────────────────────────────────────────────────
  "ping" => {
    description: "Liveness check.",
    params: {},
    result: {
      "status" => { type: :string, required: true },
      "version" => { type: :string, required: true, description: "Gem/host version." },
      "protocolVersion" => { type: :string, required: true, description: "Negotiated wire version." },
      "uptime" => { type: :integer, required: false, description: "Host uptime in seconds." },
      "sessions" => { type: :integer, required: false, description: "Live session count." }
    }
  },
  "initialize" => {
    description: "Handshake: negotiate protocol version and exchange capabilities.",
    params: {
      "client" => { type: :object, required: false, description: "{name, version} of the client." },
      "capabilities" => { type: :array, required: false, description: "Client capabilities (informational)." }
    },
    result: {
      "protocolVersion" => { type: :string, required: true },
      "capabilities" => { type: :array, required: true, description: "Host capabilities (see CAPABILITIES)." },
      "server" => { type: :object, required: true, description: "{name, version} of the host." }
    }
  },

  # ── Session lifecycle ────────────────────────────────────────────
  "session/create" => {
    description: "Create a session in a workspace.",
    params: {
      "workspace" => { type: :object, required: false, description: "{workspacePath} of the project." },
      "mode" => { type: :string, required: false, description: "Permission mode, e.g. on_request, full_access." },
      "model" => { type: :string, required: false, description: "Model identifier." },
      "tools" => { type: :array, required: false, description: "Tool names to enable." },
      "systemPrompt" => { type: :string, required: false, description: "System prompt override." }
    },
    result: {
      "session" => { type: :object, required: true, description: "{sessionId, model, createdAt}." }
    }
  },
  "session/list" => {
    description: "List sessions, most recent first.",
    params: {
      "limit" => { type: :integer, required: false, description: "Max sessions (default 20)." }
    },
    result: {
      "sessions" => { type: :array, required: true, description: "Session summaries." }
    }
  },
  "session/resume" => {
    description: "Resume/attach to an existing session.",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "sessionId" => { type: :string, required: true },
      "running" => { type: :boolean, required: true, description: "A turn is in progress." },
      "idle" => { type: :boolean, required: true, description: "The session is ready for a prompt." },
      "createdAt" => { type: :string, required: true, description: "ISO 8601 creation time." }
    }
  },
  "session/subscribe" => {
    description: "Subscribe to a session's event stream with replay support.",
    params: {
      "sessionId" => { type: :string, required: true },
      "deliveryKind" => { type: :string, required: false, enum: DELIVERY_KINDS, description: "Default: replay." },
      "afterSeq" => { type: :integer, required: false, description: "Replay events after this seq." },
      "includeSnapshot" => { type: :boolean, required: false, description: "Include a state snapshot in the result." }
    },
    result: {
      "subscription" => { type: :object, required: true, description: "{sessionId, deliveryKind}." },
      "snapshot" => { type: :array, required: false, description: "Events after afterSeq, when includeSnapshot." }
    }
  },
  "session/events" => {
    description: "Poll events after a seq (for clients without a subscription).",
    params: {
      "sessionId" => { type: :string, required: true },
      "afterSeq" => { type: :integer, required: false, description: "Default 0." },
      "limit" => { type: :integer, required: false, description: "Max events to return." }
    },
    result: {
      "events" => { type: :array, required: true, description: "Canonical event envelopes." }
    }
  },
  "session/send" => {
    description: "Inject a message mid-run (barge-in) or prompt an idle session.",
    params: {
      "sessionId" => { type: :string, required: true },
      "content" => { type: :string, required: true, description: "The message text." },
      "expectedTurnId" => { type: :string, required: false, description: "Staleness guard: only steer this turn." }
    },
    result: {
      "accepted" => { type: :boolean, required: true },
      "status" => { type: :string, required: false, enum: %w[queued steered stale], description: "steered: injected now; queued: next turn; stale: turn mismatch." },
      "turnId" => { type: :string, required: false, description: "The turn the message applies to." }
    }
  },
  "session/abort" => {
    description: "Abort the running turn.",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "aborted" => { type: :boolean, required: true },
      "sessionId" => { type: :string, required: true }
    }
  },
  "session/close" => {
    description: "Close the session and release its resources.",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "closed" => { type: :boolean, required: true },
      "sessionId" => { type: :string, required: true }
    }
  },

  # ── Artifacts ────────────────────────────────────────────────────
  "session/artifacts" => {
    description: "List the session's tool artifacts.",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "artifacts" => { type: :array, required: true, description: "Artifact summaries." }
    }
  },
  "session/artifact/get" => {
    description: "Fetch one artifact's content.",
    params: {
      "sessionId" => { type: :string, required: true },
      "artifactId" => { type: :string, required: true }
    },
    result: {
      "artifact" => { type: :object, required: true, description: "Artifact record with content or uri." }
    }
  },

  # ── Interactions (approval, user input) ──────────────────────────
  "interaction/list" => {
    description: "List pending interactions for a session.",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "interactions" => { type: :array, required: true, description: "Pending interaction records." }
    }
  },
  "interaction/approve" => {
    description: "Approve a pending interaction by id (approval.required).",
    params: {
      "sessionId" => { type: :string, required: true },
      "interactionId" => { type: :string, required: true }
    },
    result: {
      "approved" => { type: :boolean, required: true },
      "interactionId" => { type: :string, required: true }
    }
  },
  "interaction/reject" => {
    description: "Reject a pending interaction by id (approval.required).",
    params: {
      "sessionId" => { type: :string, required: true },
      "interactionId" => { type: :string, required: true }
    },
    result: {
      "rejected" => { type: :boolean, required: true },
      "interactionId" => { type: :string, required: true }
    }
  },
  "interaction/approve-all" => {
    description: "Approve every pending approval interaction.",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "approved" => { type: :integer, required: true, description: "Number approved." }
    }
  },
  "interaction/reject-all" => {
    description: "Reject every pending approval interaction.",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "rejected" => { type: :integer, required: true, description: "Number rejected." }
    }
  },
  "interaction/respond" => {
    description: "Answer a user_input interaction by id (elicitation).",
    params: {
      "sessionId" => { type: :string, required: true },
      "interactionId" => { type: :string, required: true },
      "response" => { type: :string, required: true }
    },
    result: {
      "responded" => { type: :boolean, required: true },
      "interactionId" => { type: :string, required: true }
    }
  },

  # ── Plan mode ────────────────────────────────────────────────────
  "plan/approve" => {
    description: "Approve the pending plan proposal (plan.proposed).",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "approved" => { type: :boolean, required: true }
    }
  },
  "plan/reject" => {
    description: "Reject the pending plan proposal; the agent stays in plan mode.",
    params: {
      "sessionId" => { type: :string, required: true }
    },
    result: {
      "rejected" => { type: :boolean, required: true }
    }
  },

  # ── Workspace ────────────────────────────────────────────────────
  "workspace/readState" => {
    description: "Read the host's workspace state (path, git, mode).",
    params: {},
    result: {
      "workspace" => { type: :object, required: true, description: "{path, name, gitBranch, mode}." }
    }
  }
}.freeze
NOTIFICATIONS =

Host → client notifications (fire-and-forget).

{
  "session/event" => {
    description: "A canonical session event envelope: {type, seq, payload}.",
    params: {
      "event" => { type: :object, required: true, description: "The canonical event envelope." }
    }
  }
}.freeze
REQUESTS =

Host → client reverse requests (client must respond) — defined for interop with the external app-server protocol standard. The canonical resolution path is the interaction/* methods.

{
  "interaction/requestPermission" => {
    description: "Ask the client to resolve a tool approval (app-server interop).",
    params: {
      "requestId" => { type: :string, required: true },
      "toolName" => { type: :string, required: true },
      "input" => { type: :any, required: false },
      "riskLevel" => { type: :string, required: false, enum: %w[low medium high critical] },
      "reason" => { type: :string, required: false }
    },
    result: {
      "decision" => { type: :string, required: true, enum: %w[allow deny] }
    }
  },
  "interaction/requestUserInput" => {
    description: "Ask the client for free-form user input (app-server interop).",
    params: {
      "requestId" => { type: :string, required: true },
      "prompt" => { type: :string, required: true },
      "options" => { type: :array, required: false }
    },
    result: {
      "response" => { type: :string, required: true }
    }
  }
}.freeze

Class Method Summary collapse

Class Method Details

.all_namesObject

All method-like names across the three surfaces.



343
344
345
# File 'lib/ask/session_protocol/methods.rb', line 343

def all_names
  METHODS.keys + NOTIFICATIONS.keys + REQUESTS.keys
end

.known?(name) ⇒ Boolean

Whether name is a canonical client → host method.

Returns:

  • (Boolean)


328
329
330
# File 'lib/ask/session_protocol/methods.rb', line 328

def known?(name)
  METHODS.key?(name)
end

.method_namesObject

The client → host method names, in registry order.



323
324
325
# File 'lib/ask/session_protocol/methods.rb', line 323

def method_names
  METHODS.keys
end

.notification?(name) ⇒ Boolean

Whether name is a host → client notification.

Returns:

  • (Boolean)


333
334
335
# File 'lib/ask/session_protocol/methods.rb', line 333

def notification?(name)
  NOTIFICATIONS.key?(name)
end

.request?(name) ⇒ Boolean

Whether name is a host → client reverse request.

Returns:

  • (Boolean)


338
339
340
# File 'lib/ask/session_protocol/methods.rb', line 338

def request?(name)
  REQUESTS.key?(name)
end

.validate_params!(name, params) ⇒ true

Validate a params hash against a method's params spec. Raises ArgumentError on unknown methods, missing required fields, or type/enum mismatches. Unknown extra fields are allowed.

Parameters:

  • name (String)

    client → host method name

  • params (Hash)

Returns:

  • (true)

    when valid

Raises:

  • (ArgumentError)


354
355
356
357
358
359
360
361
# File 'lib/ask/session_protocol/methods.rb', line 354

def validate_params!(name, params)
  spec = METHODS[name]
  raise ArgumentError, "unknown session protocol method: #{name.inspect}" unless spec
  raise ArgumentError, "params for #{name} must be a Hash" unless params.is_a?(Hash)

  validate_shape!(name, spec[:params], params)
  true
end

.validate_result!(name, result) ⇒ true

Validate a result hash against a method's result spec.

Parameters:

  • name (String)

    client → host method name

  • result (Hash)

Returns:

  • (true)

    when valid

Raises:

  • (ArgumentError)


368
369
370
371
372
373
374
375
# File 'lib/ask/session_protocol/methods.rb', line 368

def validate_result!(name, result)
  spec = METHODS[name]
  raise ArgumentError, "unknown session protocol method: #{name.inspect}" unless spec
  raise ArgumentError, "result for #{name} must be a Hash" unless result.is_a?(Hash)

  validate_shape!(name, spec[:result], result)
  true
end

.validate_shape!(name, shape, value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



378
379
380
381
382
383
384
385
386
387
388
# File 'lib/ask/session_protocol/methods.rb', line 378

def validate_shape!(name, shape, value)
  shape.each do |field, field_spec|
    field_value = value[field]
    if field_spec[:required] && field_value.nil?
      raise ArgumentError, "method #{name} missing required field #{field.inspect}"
    end
    next if field_value.nil?

    Events.validate_field!(name, field, field_spec, field_value)
  end
end