Current section

Files

Jump to
claude_agent_sdk examples runtime_control subscriber_broadcast.exs
Raw

examples/runtime_control/subscriber_broadcast.exs

Code.require_file(Path.expand("support/mock_transport.exs", __DIR__))
defmodule Examples.RuntimeControl.SubscriberBroadcast do
@moduledoc false
alias ClaudeAgentSDK.{Client, Model, Options}
alias Examples.RuntimeControl.MockTransport
def main(args \\ System.argv()) do
case Enum.member?(args, "--live") do
true -> run_live()
false -> run_mock()
end
end
defp run_mock do
IO.puts("\n== Subscriber Broadcast Demo (Mock Transport) ==")
{:ok, client} =
Client.start_link(%Options{model: "haiku"},
transport: MockTransport,
transport_opts: [owner: self()]
)
transport_pid = wait_for(:mock_transport_started, 500)
streams =
Enum.map(1..2, fn idx ->
{idx,
Task.async(fn ->
Client.stream_messages(client)
|> Enum.take(3)
|> Enum.map(&summarize(idx, &1))
end)}
end)
initial = assistant_frame("Subscriber demo starting on haiku.")
MockTransport.push_json(transport_pid, initial)
switch_task = Task.async(fn -> Client.set_model(client, "opus") end)
set_model_frame = wait_for_set_model_request(500)
{:ok, request_id} = acknowledge_model_change(set_model_frame, transport_pid)
:ok = Task.await(switch_task, 1_000)
follow_up = assistant_frame("Model switch acknowledged for request #{request_id}")
MockTransport.push_json(transport_pid, follow_up)
result_frame = %{
"type" => "result",
"subtype" => "success",
"session_id" => "subscriber-demo",
"total_cost_usd" => 0.0
}
MockTransport.push_json(transport_pid, result_frame)
Enum.each(streams, fn {idx, task} ->
messages = Task.await(task, 1_500)
IO.puts("\nStream #{idx} received:")
Enum.each(messages, &IO.puts(" • #{&1}"))
end)
Client.stop(client)
end
defp run_live do
IO.puts("\n== Subscriber Broadcast Demo (CLI Transport) ==")
IO.puts("Subscribe with two local consumers while the CLI runs the session.\n")
case Client.start_link(%Options{model: "haiku"}) do
{:ok, client} ->
run_live_stream(client)
Client.stop(client)
{:error, reason} ->
IO.puts("Failed to start live client: #{inspect(reason)}")
end
end
defp run_live_stream(client) do
streams =
Enum.map(1..2, fn idx ->
{idx,
Task.async(fn ->
Client.stream_messages(client)
|> Enum.take(1)
|> Enum.map(&summarize(idx, &1))
end)}
end)
case Client.set_model(client, "opus") do
:ok -> IO.puts("Model switch requested; waiting for CLI broadcast...")
{:error, reason} -> IO.puts("Live model switch failed: #{inspect(reason)}")
end
Enum.each(streams, fn {idx, task} ->
messages = Task.await(task, 5_000)
IO.puts("\nStream #{idx} recorded: #{inspect(messages)}")
end)
end
defp assistant_frame(content) do
%{
"type" => "assistant",
"message" => %{"content" => content, "role" => "assistant"},
"session_id" => "subscriber-demo"
}
end
defp summarize(idx, %ClaudeAgentSDK.Message{
type: :assistant,
data: %{message: %{"content" => content}}
}) do
"Subscriber #{idx}: assistant -> #{content}"
end
defp summarize(idx, %ClaudeAgentSDK.Message{type: :result, subtype: :success}) do
"Subscriber #{idx}: result -> success"
end
defp summarize(idx, message) do
"Subscriber #{idx}: #{inspect(message.type)}"
end
defp wait_for(tag, timeout) do
receive do
{^tag, payload} -> payload
after
timeout -> raise "Timed out waiting for #{inspect(tag)}"
end
end
defp wait_for_set_model_request(timeout) do
frame = wait_for(:mock_transport_send, timeout)
decoded = Jason.decode!(frame)
case get_in(decoded, ["request", "subtype"]) do
"set_model" -> frame
_other -> wait_for_set_model_request(timeout)
end
end
defp acknowledge_model_change(frame, transport_pid) do
decoded = Jason.decode!(frame)
request_id = decoded["request_id"]
{:ok, normalized} = Model.validate(decoded["request"]["model"])
MockTransport.push_json(transport_pid, %{
"type" => "control_response",
"response" => %{
"request_id" => request_id,
"subtype" => "success",
"result" => %{"model" => normalized}
}
})
{:ok, request_id}
end
end
Examples.RuntimeControl.SubscriberBroadcast.main()