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
initializeresult. 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
-
.all_names ⇒ Object
All method-like names across the three surfaces.
-
.known?(name) ⇒ Boolean
Whether
nameis a canonical client → host method. -
.method_names ⇒ Object
The client → host method names, in registry order.
-
.notification?(name) ⇒ Boolean
Whether
nameis a host → client notification. -
.request?(name) ⇒ Boolean
Whether
nameis a host → client reverse request. -
.validate_params!(name, params) ⇒ true
Validate a params hash against a method's params spec.
-
.validate_result!(name, result) ⇒ true
Validate a result hash against a method's result spec.
- .validate_shape!(name, shape, value) ⇒ Object private
Class Method Details
.all_names ⇒ Object
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.
328 329 330 |
# File 'lib/ask/session_protocol/methods.rb', line 328 def known?(name) METHODS.key?(name) end |
.method_names ⇒ Object
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.
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.
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.
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.
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 |