This guide covers configuration, authentication, API behavior, streaming re-subscription, and A2A client examples. It is the canonical document for implementation-level protocol contracts and JSON-RPC extension details; README stays at overview level.
- The service supports both transports:
- HTTP+JSON (REST endpoints such as
/v1/message:send) - JSON-RPC (
POST /)
- HTTP+JSON (REST endpoints such as
- Agent Card keeps
preferredTransport=HTTP+JSONand also exposes JSON-RPC inadditional_interfaces. - Payload schema is transport-specific and should not be mixed:
- REST send payload usually uses
message.contentand role values likeROLE_USER - JSON-RPC
message/sendpayload usesparams.message.partsand role valuesuser/agent
- REST send payload usually uses
This section keeps only the protocol-relevant variables.
For the full runtime variable catalog and defaults, see
../src/opencode_a2a/config.py.
Deployment supervision is intentionally out of scope for this project; use your
own process manager, container runtime, or host orchestration.
Key variables to understand protocol behavior:
A2A_BEARER_TOKEN: required for all authenticated runtime requests.OPENCODE_BASE_URL: upstream OpenCode HTTP endpoint. Default:http://127.0.0.1:4096. In two-process deployments, set it explicitly.OPENCODE_WORKSPACE_ROOT: service-level default workspace root exposed to OpenCode when clients do not request a narrower directory override.A2A_ALLOW_DIRECTORY_OVERRIDE: controls whether clients may passmetadata.opencode.directory.A2A_ENABLE_SESSION_SHELL: gates high-risk JSON-RPC methodopencode.sessions.shell.A2A_SANDBOX_MODE/A2A_SANDBOX_FILESYSTEM_SCOPE/A2A_SANDBOX_WRITABLE_ROOTS: declarative execution-boundary metadata for sandbox mode, filesystem scope, and optional writable roots.A2A_NETWORK_ACCESS/A2A_NETWORK_ALLOWED_DOMAINS: declarative execution-boundary metadata for network policy and optional allowlist disclosure.A2A_APPROVAL_POLICY/A2A_APPROVAL_ESCALATION_BEHAVIOR: declarative execution-boundary metadata for approval workflow.A2A_WRITE_ACCESS_SCOPE/A2A_WRITE_ACCESS_OUTSIDE_WORKSPACE: declarative execution-boundary metadata for write scope and whether writes may extend outside the primary workspace boundary.A2A_HOST/A2A_PORT: runtime bind address. Defaults:127.0.0.1:8000.A2A_PUBLIC_URL: public base URL advertised by the Agent Card. Default:http://127.0.0.1:8000.A2A_LOG_LEVEL: runtime log level. Default:WARNING.A2A_LOG_PAYLOADS/A2A_LOG_BODY_LIMIT: payload logging behavior and truncation. WhenA2A_LOG_LEVEL=DEBUG, upstream OpenCode stream events are also logged with preview truncation controlled byA2A_LOG_BODY_LIMIT.A2A_MAX_REQUEST_BODY_BYTES: runtime request-body limit. Oversized requests return HTTP413.A2A_SESSION_CACHE_TTL_SECONDS/A2A_SESSION_CACHE_MAXSIZE: session cache behavior for(identity, contextId) -> session_id.A2A_INTERRUPT_REQUEST_TTL_SECONDS: active retention window for the in-memory interrupt request binding cache used bya2a.interrupt.*callback methods. Default:10800seconds (180minutes).A2A_INTERRUPT_REQUEST_TOMBSTONE_TTL_SECONDS: retention window for expired interrupt tombstones after active TTL has elapsed. During this window, repeated replies keep returningINTERRUPT_REQUEST_EXPIREDinstead of falling through toINTERRUPT_REQUEST_NOT_FOUND. Default:600seconds (10minutes).A2A_CANCEL_ABORT_TIMEOUT_SECONDS: best-effort timeout for upstreamsession.abortin cancel flow.OPENCODE_TIMEOUT/OPENCODE_TIMEOUT_STREAM: upstream request timeout and optional stream timeout override.A2A_CLIENT_TIMEOUT_SECONDS: outbound client timeout. Default:30seconds.A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS: outbound Agent Card fetch timeout. Default:5seconds.A2A_CLIENT_USE_CLIENT_PREFERENCE: whether the outbound client prefers its own transport choices.A2A_CLIENT_BEARER_TOKEN: optional bearer token attached to outbound peer calls made by the embedded A2A client anda2a_calltool path.A2A_CLIENT_SUPPORTED_TRANSPORTS: ordered outbound transport preference list.- Runtime authentication is bearer-token only via
A2A_BEARER_TOKEN. - The same outbound client flags are also honored by the server-side embedded
A2A client used for peer calls and
a2a_calltool execution:A2A_CLIENT_TIMEOUT_SECONDSA2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDSA2A_CLIENT_USE_CLIENT_PREFERENCEA2A_CLIENT_BEARER_TOKENA2A_CLIENT_SUPPORTED_TRANSPORTS
opencode-a2a now includes a minimal client bootstrap module in
src/opencode_a2a/client/ to support downstream consumer usage while keeping
server and client concerns separate.
Boundary separation:
- Server code owns runtime request handling, transport orchestration, stream behavior, and public compatibility profile exposure.
- Client code owns peer card discovery, SDK client construction, operation call helpers, and protocol error normalization.
Current client facade API:
A2AClient.get_agent_card()A2AClient.send()/A2AClient.send_message()A2AClient.get_task()A2AClient.cancel_task()A2AClient.resubscribe_task()
Server-side outbound peer calls use bearer auth only for now. Configure
A2A_CLIENT_BEARER_TOKEN when the remote agent protects its runtime surface.
CLI outbound calls may pass --token explicitly or use
A2A_CLIENT_BEARER_TOKEN.
Execution-boundary metadata is intentionally declarative deployment metadata:
it is published through RuntimeProfile, Agent Card, OpenAPI, and /health,
and should not be interpreted as a live per-request privilege snapshot or a
runtime CLI self-inspection result.
Recommended two-process example:
opencode serve --hostname 127.0.0.1 --port 4096Configure provider auth and the default model on the OpenCode side before starting that upstream process:
- Add credentials with
opencode auth loginor/connect. - Check available model IDs with
opencode modelsoropencode models <provider>. - Set the default model in
opencode.json, for example:
{
"$schema": "https://opencode.ai/config.json",
"model": "google/gemini-3-pro"
}If your provider uses environment variables for auth, export them before
starting opencode serve.
Do not assume startup-script env vars always erase previously persisted
OpenCode auth state for the deployed user. When debugging provider-auth
surprises, inspect the deployed user's HOME/XDG config directories and the
OpenCode files stored there before concluding that opencode-a2a changed the
credential selection.
Then start opencode-a2a against that explicit upstream URL:
OPENCODE_BASE_URL=http://127.0.0.1:4096 \
A2A_BEARER_TOKEN=dev-token \
A2A_HOST=127.0.0.1 \
A2A_PORT=8000 \
A2A_PUBLIC_URL=http://127.0.0.1:8000 \
OPENCODE_WORKSPACE_ROOT=/abs/path/to/workspace \
opencode-a2aIf one deployment works while another fails against the same upstream provider,
check the deployed OpenCode user's local state before assuming the difference
comes from the opencode-a2a package itself.
- Provider auth and service-level model defaults belong to
opencode serve. - The deployed user's HOME/XDG config directories are operational input.
- Existing OpenCode auth/config files may still influence runtime behavior even when you also inject provider env vars from a process manager or shell wrapper.
- Compare the deployed user's OpenCode auth/config files, HOME/XDG values, and effective workspace directory before blaming the A2A adapter layer.
- For OpenCode-specific auth/config troubleshooting, inspect files such as
~/.local/share/opencode/auth.jsonand~/.config/opencode/opencode.json(or the equivalent XDG-resolved paths for that service user).
- The service forwards A2A
message:sendto OpenCode session/message calls. - Main chat requests may override the upstream model for one request through
metadata.shared.model. - Provider/model catalog discovery is available through
opencode.providers.listandopencode.models.list. - Main chat input supports structured A2A
partspassthrough:TextPartis forwarded as an OpenCode text part.FilePart(FileWithBytes)is forwarded as afilepart with adata:URL.FilePart(FileWithUri)is forwarded as afilepart with the original URI.DataPartis currently rejected explicitly; it is not silently downgraded.
- Task state defaults to
completedfor successful turns. - The deployment profile is single-tenant and shared-workspace. For detailed isolation principles and security boundaries, see SECURITY.md.
- Streaming is always enabled in this server profile;
message:streamis part of the stable runtime baseline. - Streaming (
/v1/message:stream) emits incrementalTaskArtifactUpdateEventand thenTaskStatusUpdateEvent(final=true). - Stream artifacts carry
artifact.metadata.shared.stream.block_typewith valuestext/reasoning/tool_call. - All chunks share one stream artifact ID and preserve original timeline via
artifact.metadata.shared.stream.event_id. artifact.metadata.shared.stream.message_idremains best-effort metadata: when upstream omitsmessage_id, the service falls back to a stable request-scoped message identity.artifact.metadata.shared.stream.sequencecarries the canonical per-request stream sequence.- A final snapshot is emitted only when streaming chunks did not already produce the same final text.
- Stream routing is schema-first: the service classifies chunks primarily by
OpenCode
part.typeandpart_idstate rather than inline text markers. message.part.deltaandmessage.part.updatedare merged perpart_id; out-of-order deltas are buffered and replayed when the correspondingpart.updatedarrives.- Structured
toolparts are emitted astool_callblocks backed byDataPart(data={...}), whiletextandreasoningcontinue to useTextPart. tool_callblock payloads are normalized structured objects that may expose fields such ascall_id,tool,status,title,subtitle,input,output, anderror.- Final status event metadata may include normalized token usage at
metadata.shared.usagewith fields such asinput_tokens,output_tokens,total_tokens, optionalreasoning_tokens, optionalcache_tokens.read_tokens/cache_tokens.write_tokens, and optionalcost. - Usage is extracted from documented info payloads and supported usage parts
such as
step-finish; non-usage parts with similar fields are ignored. - Interrupt events (
permission.asked/question.asked) are mapped toTaskStatusUpdateEvent(final=false, state=input-required)with details atmetadata.shared.interrupt, includingrequest_id, interrupttype,phase=asked, and a normalized minimal callback payload. - Resolved interrupt events (
permission.replied/question.replied/question.rejected) are emitted asTaskStatusUpdateEvent(final=false, state=working)withmetadata.shared.interrupt.phase=resolvedand a normalizedmetadata.shared.interrupt.resolution. - Duplicate or unknown resolved events are suppressed unless the matching request is still pending.
- Non-streaming requests return a
Taskdirectly. - Non-streaming
message:sendresponses may include normalized token usage atTask.metadata.shared.usagewith the same field schema.
- Requests require
Authorization: Bearer <token>; otherwise401is returned. Agent Card endpoints are public. - Requests above
A2A_MAX_REQUEST_BODY_BYTESare rejected with HTTP413before transport handling. - For validation failures, missing context (
task_id/context_id), or internal errors, the service attempts to return standard A2A failure events viaevent_queue. - Failure events include concrete error details with
failedstate.
- Clients can pass
metadata.opencode.directory, but it must stay inside${OPENCODE_WORKSPACE_ROOT}or the service runtime root when no workspace root is configured. OPENCODE_WORKSPACE_ROOTis the service-level default workspace root used when clients do not request a narrower directory override.- All paths are normalized with
realpathto prevent..or symlink boundary bypass. - If
A2A_ALLOW_DIRECTORY_OVERRIDE=false, only the default directory is accepted.
The service publishes a machine-readable wire contract through Agent Card and OpenAPI metadata to describe the current runtime method boundary.
Use it to answer:
- which JSON-RPC methods are part of the current A2A core baseline
- which JSON-RPC methods are custom extensions
- which methods are deployment-conditional rather than currently active
- what error shape is returned for unsupported JSON-RPC methods
Current behavior:
- Core JSON-RPC methods are declared under
core.jsonrpc_methods. - Core HTTP endpoints are declared under
core.http_endpoints. - Extension JSON-RPC methods are declared under
extensions.jsonrpc_methods. - Deployment-conditional methods are declared under
extensions.conditionally_available_methods. - Shared metadata extension URIs such as session binding and streaming are
listed under
extensions.extension_uris. all_jsonrpc_methodsis the runtime truth for the current deployment.
When A2A_ENABLE_SESSION_SHELL=false, opencode.sessions.shell is omitted from
all_jsonrpc_methods and exposed only through
extensions.conditionally_available_methods.
Unsupported method contract:
- JSON-RPC error code:
-32601 - Error message:
Unsupported method: <method> - Error data fields:
type=METHOD_NOT_SUPPORTEDmethodsupported_methodsprotocol_version
Consumer guidance:
- Discover custom JSON-RPC methods from Agent Card / OpenAPI before calling them.
- Treat
supported_methodsinerror.dataas the runtime truth for the current deployment, especially when a deployment-conditional method is disabled.
The service also publishes a machine-readable compatibility profile through Agent Card and OpenAPI metadata.
Its purpose is to declare:
- the stable A2A core interoperability baseline
- which custom JSON-RPC methods are deployment extensions
- which extension surfaces are required runtime metadata contracts
- which methods are deployment-conditional rather than always available
Current profile shape:
profile_id=opencode-a2a-single-tenant-coding-v1- Deployment semantics are declared under
deployment:id=single_tenant_shared_workspacesingle_tenant=trueshared_workspace_across_consumers=truetenant_isolation=none
- Runtime features are declared under
runtime_features:directory_binding.allow_override=true|falsedirectory_binding.scope=workspace_root_or_descendant|workspace_root_onlysession_shell.enabled=true|falsesession_shell.availability=enabled|disabledexecution_environment.sandbox.mode=unknown|read-only|workspace-write|danger-full-access|customexecution_environment.sandbox.filesystem_scope=unknown|workspace_only|workspace_and_declared_roots|unrestricted|customexecution_environment.network.access=unknown|disabled|enabled|restricted|customexecution_environment.approval.policy=unknown|never|on-request|on-failure|untrusted|customexecution_environment.approval.escalation_behavior=unknown|manual|automatic|unsupported|customexecution_environment.write_access.scope=unknown|none|workspace_only|workspace_and_declared_roots|unrestricted|customexecution_environment.write_access.outside_workspace=unknown|allowed|disallowed|customservice_features.streaming.enabled=trueservice_features.health_endpoint.enabled=true
- Optional disclosure fields are emitted only when explicitly configured:
execution_environment.sandbox.writable_rootsexecution_environment.network.allowed_domains
- Core methods and endpoints are declared under
core. - Extension retention policy is declared under
extension_retention. - Per-method retention and availability are declared under
method_retention. - Extension params and
/healthexpose the same structuredprofileobject; there is no separate legacy deployment-context shape. - Execution-environment values are deployment declarations, not a per-turn runtime approval or sandbox result.
Retention guidance:
- Treat core A2A methods as the generic client interoperability baseline.
- Treat session binding, request-scoped model selection, and streaming metadata contracts as required for the current deployment model.
- Treat
a2a.interrupt.*methods as shared extensions. - Treat
opencode.sessions.*,opencode.providers.*, andopencode.models.*as provider-private OpenCode extensions rather than portable A2A baseline capabilities. - Treat
opencode.sessions.shellas deployment-conditional and discover it from the declared profile and current wire contract before calling it.
Minimal JSON-RPC example with text + file input:
curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": "req-1",
"method": "message/send",
"params": {
"message": {
"messageId": "msg-multipart-1",
"role": "user",
"parts": [
{
"kind": "text",
"text": "Please summarize this file."
},
{
"kind": "file",
"file": {
"name": "report.pdf",
"mimeType": "application/pdf",
"uri": "file:///workspace/report.pdf"
}
}
]
}
}
}'Current compatibility note:
TextPartandFilePartare supported.DataPartinput is not supported and is rejected with an explicit error.
The README provides product positioning and quick start guidance. This guide focuses on how to consume the declared capabilities.
Important distinction:
- Agent Card extension declarations answer "what capability is available?"
- Runtime payload metadata answers "what happened on this request/stream?"
- Clients should not treat runtime metadata alone as a substitute for capability discovery when an extension URI is already declared.
Agent Card capability:
- URI:
urn:a2a:session-binding/v1
To continue a historical OpenCode session, include this metadata key in each invoke request:
metadata.shared.session.id: target upstream session ID
Server behavior:
- If provided, the request is sent to that exact OpenCode session.
- If omitted, a new session is created and cached by
(identity, contextId) -> session_id. contextIdremains the A2A conversation context key for task continuity; it is not a replacement for the upstream session identifier.- OpenCode-private context such as
metadata.opencode.directorymay be supplied alongsidemetadata.shared.session.id, but it does not change the shared session-binding key.
Consumer guidance:
- Use this extension declaration to decide whether the server explicitly supports shared session rebinding.
- On the request path, write the upstream session identity to
metadata.shared.session.id. - On the response/query path, treat
metadata.shared.sessionas runtime metadata and not as a separate capability declaration.
Minimal example:
curl -sS http://127.0.0.1:8000/v1/message:send \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"message": {
"messageId": "msg-continue-1",
"role": "ROLE_USER",
"content": [{"text": "Continue the previous session and restate the key conclusion."}]
},
"metadata": {
"shared": {
"session": {
"id": "<session_id>"
}
}
}
}'Agent Card capability:
- URI:
urn:a2a:model-selection/v1
This extension declares that the main chat path accepts a request-scoped model override through shared metadata:
metadata.shared.model.providerIDmetadata.shared.model.modelID
Declaration versus runtime:
- The URI
urn:a2a:model-selection/v1is the capability declaration. - The actual request payload carries the runtime override under
metadata.shared.model.
Behavior:
- The override is optional and scoped to one main chat request.
- Both
providerIDandmodelIDmust be present together. - When both fields are present, the service forwards them to the upstream OpenCode request as a model preference.
- When the fields are absent, the upstream OpenCode default behavior applies.
Consumer guidance:
- Use Agent Card discovery to confirm the shared model-selection contract is available before sending overrides.
- Treat
metadata.shared.modelas request-scoped preference data rather than deployment configuration. - Provider auth and service-level model defaults belong to
opencode serve, not toopencode-a2a.
Minimal example:
curl -sS http://127.0.0.1:8000/v1/message:send \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"message": {
"messageId": "msg-model-1",
"role": "ROLE_USER",
"content": [{"text": "Explain the current branch status."}]
},
"metadata": {
"shared": {
"model": {
"providerID": "google",
"modelID": "gemini-2.5-flash"
}
}
}
}'Agent Card capability:
- URI:
urn:a2a:stream-hints/v1
This extension declares that streaming and final task payloads use canonical shared metadata for block, usage, interrupt, and session hints.
Declaration versus runtime:
- The URI
urn:a2a:stream-hints/v1is the capability declaration. - The actual request/stream payloads carry the runtime hints under shared metadata fields.
Shared runtime fields:
metadata.shared.stream- block-level stream metadata such as
block_type,source,message_id,event_id,sequence, androle
- block-level stream metadata such as
metadata.shared.usage- normalized usage data such as
input_tokens,output_tokens,total_tokens, optionalreasoning_tokens, optionalcache_tokens.read_tokens/cache_tokens.write_tokens, and optionalcost
- normalized usage data such as
metadata.shared.interrupt- normalized interrupt request or resolution metadata including
request_id,type,phase, optionalresolution, and callback-safe details
- normalized interrupt request or resolution metadata including
metadata.shared.session- session-level metadata such as the bound upstream session ID and session title when available
Consumer guidance:
- Use the extension declaration to know the server emits canonical shared stream hints.
- Use runtime metadata to render block timelines, token usage, and interactive interruptions.
- Do not infer capability support only from seeing one runtime field on one response; rely on Agent Card discovery first when possible.
- Treat
metadata.shared.interruptas observation data. Callback operations are a separate shared capability declared byurn:a2a:interactive-interrupt/v1.
Minimal stream semantics summary:
text,reasoning, andtool_callare emitted as canonical block typestextandreasoningblocks useTextPart, whiletool_callusesDataPartmessage_idandevent_idpreserve stable timeline identity where possiblesequenceis the per-request canonical stream sequence- final task/status metadata may repeat normalized usage and interrupt context even after the streaming phase ends
This service exposes OpenCode session list/message-history queries and session
control methods via A2A JSON-RPC extension methods (default endpoint: POST /).
No extra custom REST endpoint is introduced.
- Trigger: call extension methods through A2A JSON-RPC
- Auth: same
Authorization: Bearer <token> - Privacy guard: when
A2A_LOG_PAYLOADS=true, request/response bodies are still suppressed formethod=opencode.sessions.* - Endpoint discovery: prefer
additional_interfaces[]withtransport=jsonrpcfrom Agent Card - Notification behavior: for
opencode.sessions.*, requests withoutidreturn HTTP204 No Content - Result format (query methods):
result.itemsis always an array of A2A standard objects- session list =>
Taskwithstatus.state=completed - message history =>
Message - limit pagination defaults to
20; requests above100are rejected contextIdis an A2A context key derived by the adapter (format:ctx:opencode-session:<session_id>, not raw OpenCode session ID)- OpenCode session identity is exposed explicitly at
metadata.shared.session.id - session title is available at
metadata.shared.session.title
curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "opencode.sessions.list",
"params": {"limit": 20}
}'curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "opencode.sessions.messages.list",
"params": {
"session_id": "<session_id>",
"limit": 50
}
}'curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 21,
"method": "opencode.sessions.prompt_async",
"params": {
"session_id": "<session_id>",
"request": {
"parts": [{"type": "text", "text": "Continue and summarize next steps."}],
"noReply": true,
"model": {
"providerID": "google",
"modelID": "gemini-2.5-flash"
}
},
"metadata": {
"opencode": {
"directory": "/path/inside/workspace"
}
}
}
}'Response:
- success =>
{"ok": true, "session_id": "<session_id>"}(JSON-RPC result) - notification (no
id) => HTTP204 No Content - error types:
SESSION_NOT_FOUNDSESSION_FORBIDDENMETHOD_DISABLED(not applicable to prompt_async)UPSTREAM_UNREACHABLEUPSTREAM_HTTP_ERRORUPSTREAM_PAYLOAD_ERROR
Validation notes:
metadata.opencode.directoryfollows the same normalization and boundary rules as message send (realpath+ workspace boundary check).request.modeluses the same shape asmetadata.shared.modeland is scoped only to the current session-control request.- Control methods enforce session owner guard based on request identity.
curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 22,
"method": "opencode.sessions.command",
"params": {
"session_id": "<session_id>",
"request": {
"command": "/review",
"arguments": "focus on security findings",
"model": {
"providerID": "google",
"modelID": "gemini-2.5-flash"
}
},
"metadata": {
"opencode": {
"directory": "/path/inside/workspace"
}
}
}
}'Response:
- success =>
{"item": <A2A Message>}(JSON-RPC result) - notification (no
id) => HTTP204 No Content
opencode.sessions.shell is disabled by default. Enable with
A2A_ENABLE_SESSION_SHELL=true.
Security warning:
- This is a high-risk method because it can execute shell commands in the workspace context.
- Enable only for trusted operators/internal scenarios.
- Keep bearer-token rotation, owner/directory guard checks, and audit log monitoring enabled before turning it on.
curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 23,
"method": "opencode.sessions.shell",
"params": {
"session_id": "<session_id>",
"request": {
"agent": "code-reviewer",
"command": "git status --short"
}
}
}'Response:
- success =>
{"item": <A2A Message>}(JSON-RPC result) - disabled => JSON-RPC error
METHOD_DISABLED - notification (no
id) => HTTP204 No Content
Returns normalized provider summaries from the upstream OpenCode provider catalog.
curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 24,
"method": "opencode.providers.list",
"params": {}
}'Response:
- success =>
{"items": [...], "default_by_provider": {...}, "connected": [...]}(JSON-RPC result)
Returns normalized, flattened model summaries. Supports optional provider filter:
params.provider_id
curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 25,
"method": "opencode.models.list",
"params": {
"provider_id": "openai"
}
}'Response:
- success =>
{"items": [...], "default_by_provider": {...}, "connected": [...]}(JSON-RPC result)
When stream metadata reports an interrupt request at metadata.shared.interrupt,
clients can reply through JSON-RPC extension methods:
a2a.interrupt.permission.reply- required:
request_id - required:
reply(once/always/reject) - optional:
message - optional:
metadata.opencode.directory
- required:
a2a.interrupt.question.reply- required:
request_id - required:
answers(Array<Array<string>>) - optional:
metadata.opencode.directory
- required:
a2a.interrupt.question.reject- required:
request_id - optional:
metadata.opencode.directory
- required:
Notes:
request_idmust be a live interrupt request observed from stream metadata (metadata.shared.interrupt.request_id).- The server keeps an in-memory interrupt binding cache; callbacks with unknown
or expired
request_idare rejected. - The cache retention windows are controlled by
A2A_INTERRUPT_REQUEST_TTL_SECONDS(default:10800seconds /180minutes) andA2A_INTERRUPT_REQUEST_TOMBSTONE_TTL_SECONDS(default:600seconds /10minutes). After the active TTL elapses, the server keeps a short-lived tombstone so repeated replies continue to returnINTERRUPT_REQUEST_EXPIREDbefore eventually aging out toINTERRUPT_REQUEST_NOT_FOUND. - These values are deployment/runtime settings and are intentionally not part of the shared extension method contract.
- Callback requests are validated against interrupt type and caller identity.
- Callback context variables use the shared method contract plus
OpenCode-private metadata when needed
(
params.metadata.opencode.directory). - Successful callback responses are minimal: only
okandrequest_id. - Error types:
INTERRUPT_REQUEST_NOT_FOUNDINTERRUPT_REQUEST_EXPIREDINTERRUPT_TYPE_MISMATCHUPSTREAM_UNREACHABLEUPSTREAM_HTTP_ERROR
Permission reply example:
curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "a2a.interrupt.permission.reply",
"params": {
"request_id": "<request_id>",
"reply": "once",
"metadata": {
"opencode": {
"directory": "/path/inside/workspace"
}
}
}
}'curl -sS http://127.0.0.1:8000/v1/message:send \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"message": {
"messageId": "msg-1",
"role": "ROLE_USER",
"content": [{"text": "Explain what this repository does."}]
}
}'curl -sS http://127.0.0.1:8000/ \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{
"jsonrpc": "2.0",
"id": 101,
"method": "message/send",
"params": {
"message": {
"messageId": "msg-1",
"role": "user",
"parts": [{"kind": "text", "text": "Explain what this repository does."}]
}
}
}'If an SSE connection drops, use GET /v1/tasks/{task_id}:subscribe to re-subscribe while the task is still non-terminal.
- The service first marks the A2A task as
canceledand keeps cancel requests responsive. - For running tasks, the service attempts upstream OpenCode
POST /session/{sessionID}/abortto stop generation. - Upstream interruption is best-effort: if upstream returns 404, network errors, or other HTTP errors, A2A cancellation still completes with
TaskState.canceled. - Idempotency contract: repeated
tasks/cancelon an alreadycanceledtask returns the current terminal task state without error. - Terminal subscribe contract: calling
subscribeon a terminal task replays one terminalTasksnapshot and then closes the stream. - These two semantics are also declared as machine-readable
service_behaviorsin the compatibility profile and wire contract extensions. - The service emits lightweight metric log records (
logger=opencode_a2a.execution.executor):a2a_stream_requests_totala2a_stream_active(value=1when a stream starts,value=-1when it closes)opencode_stream_retries_totaltool_call_chunks_emitted_totalinterrupt_requests_totalinterrupt_resolved_total
- The cancel path also emits:
a2a_cancel_requests_totala2a_cancel_abort_attempt_totala2a_cancel_abort_success_totala2a_cancel_abort_timeout_totala2a_cancel_abort_error_totala2a_cancel_duration_ms(withabort_outcomelabel)