{"id":47593022,"url":"https://github.com/deadcode-walker/asterisk-rs","last_synced_at":"2026-04-01T17:43:29.747Z","repository":{"id":345339973,"uuid":"1185607964","full_name":"deadcode-walker/asterisk-rs","owner":"deadcode-walker","description":"Async Rust client for Asterisk AMI, AGI, and ARI","archived":false,"fork":false,"pushed_at":"2026-03-25T12:20:04.000Z","size":796,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-26T13:37:27.404Z","etag":null,"topics":["agi","ami","ari","asterisk","async","pbx","rust","sip","telephony","tokio","voip"],"latest_commit_sha":null,"homepage":"https://deadcode-walker.github.io/asterisk-rs/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/deadcode-walker.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-18T19:02:14.000Z","updated_at":"2026-03-25T12:16:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/deadcode-walker/asterisk-rs","commit_stats":null,"previous_names":["deadcode-walker/asterisk-rs"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/deadcode-walker/asterisk-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deadcode-walker%2Fasterisk-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deadcode-walker%2Fasterisk-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deadcode-walker%2Fasterisk-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deadcode-walker%2Fasterisk-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/deadcode-walker","download_url":"https://codeload.github.com/deadcode-walker/asterisk-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deadcode-walker%2Fasterisk-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290557,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agi","ami","ari","asterisk","async","pbx","rust","sip","telephony","tokio","voip"],"created_at":"2026-04-01T17:43:28.798Z","updated_at":"2026-04-01T17:43:29.738Z","avatar_url":"https://github.com/deadcode-walker.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# asterisk-rs\n\n[![crates.io](https://img.shields.io/crates/v/asterisk-rs.svg)](https://crates.io/crates/asterisk-rs)\n[![docs.rs](https://img.shields.io/docsrs/asterisk-rs)](https://docs.rs/asterisk-rs)\n[![CI](https://github.com/deadcode-walker/asterisk-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/deadcode-walker/asterisk-rs/actions/workflows/ci.yml)\n[![MSRV](https://img.shields.io/badge/MSRV-1.83-blue)](https://blog.rust-lang.org/2024/12/05/Rust-1.83.0.html)\n[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](#license)\n\nAsync Rust client for Asterisk PBX. Originate calls, handle events, control\nchannels, bridges, queues, and recordings across all three Asterisk interfaces.\n\n- **AMI** -- monitor and control Asterisk over TCP. Typed events, actions, automatic reconnection, MD5 auth.\n- **AGI** -- run dialplan logic from your Rust service. FastAGI server with typed async commands.\n- **ARI** -- full call control via REST + WebSocket. Resource handles, typed events with metadata.\n\n## Quick Example\n\n```rust,ignore\nuse asterisk_rs::ami::{AmiClient, AmiEvent};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    tracing_subscriber::fmt::init();\n\n    let client = AmiClient::builder()\n        .host(\"10.0.0.1\")\n        .credentials(\"admin\", \"secret\")\n        .build()\n        .await?;\n\n    // subscribe to hangup events only\n    let mut hangups = client.subscribe_filtered(|e| {\n        e.event_name() == \"Hangup\"\n    });\n\n    while let Some(event) = hangups.recv().await {\n        if let AmiEvent::Hangup { channel, cause, cause_txt, .. } = event {\n            tracing::info!(%channel, %cause, %cause_txt, \"channel hung up\");\n        }\n    }\n\n    Ok(())\n}\n```\n\n## Install\n\nUse the umbrella crate to pull in whichever protocols you need:\n\n```toml\n[dependencies]\nasterisk-rs = \"0.7\"\n```\n\nOr add individual protocol crates directly:\n\n```toml\n[dependencies]\nasterisk-rs-ami = \"0.7\"   # AMI only\nasterisk-rs-agi = \"0.7\"   # AGI only\nasterisk-rs-ari = \"0.7\"   # ARI only\n```\n\n## Feature Selection\n\nThe umbrella crate enables all protocols by default. To select only what you need:\n\n```toml\n[dependencies]\nasterisk-rs = { version = \"0.7\", default-features = false, features = [\"ami\"] }\n# or: features = [\"agi\"]\n# or: features = [\"ari\"]\n# or: features = [\"ami\", \"ari\"]\n```\n\nAvailable features: `ami`, `agi`, `ari`. The `pbx` abstraction requires `ami`.\n\n## Protocols\n\n| Protocol | Default Port | Transport | Use Case |\n|----------|-------------|-----------|----------|\n| [AMI](https://docs.rs/asterisk-rs-ami) | 5038 | TCP | Monitoring, call control, system management |\n| [AGI](https://docs.rs/asterisk-rs-agi) | 4573 | TCP | Dialplan logic, IVR, call routing |\n| [ARI](https://docs.rs/asterisk-rs-ari) | 8088 | HTTP + WS | Stasis applications, full media control |\n\n## Capabilities\n\n- Typed actions, events, and commands for the full Asterisk protocol surface\n- Filtered event subscriptions -- receive only what you need\n- Event-collecting actions -- `send_collecting()` gathers multi-event responses (Status, QueueStatus, etc.)\n- Automatic reconnection with exponential backoff, jitter, and re-authentication\n- **Call tracker** -- correlates AMI events into `CompletedCall` records (channel, duration, cause, full event log)\n- **PBX abstraction** -- `Pbx::dial()` wraps originate + OriginateResponse correlation into one async call\n- **Pending resources** -- ARI `PendingChannel`/`PendingBridge` pre-subscribe before REST to eliminate event races\n- **Transport modes** -- ARI supports HTTP (request/response) or WebSocket (bidirectional streaming)\n- **Outbound WebSocket server** -- `AriServer` accepts Asterisk 22+ outbound WS connections\n- **Media channel** -- low-level audio I/O over WebSocket for external media applications\n- Resource handles for ARI (ChannelHandle, BridgeHandle, PlaybackHandle, RecordingHandle)\n- Domain types for hangup causes, channel states, device states, dial statuses, and more\n- ARI event metadata (application, timestamp, asterisk_id) on every event\n- AMI command output capture for `Response: Follows`\n- URL-safe query encoding, HTTP timeouts, WebSocket lifecycle management\n- `#[non_exhaustive]` enums -- new variants won't break your code\n- Structured logging via `tracing`\n\n## More Examples\n\n### AMI: call tracker\n\n```rust,ignore\nuse asterisk_rs::ami::AmiClient;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    tracing_subscriber::fmt::init();\n\n    let client = AmiClient::builder()\n        .host(\"127.0.0.1\")\n        .credentials(\"admin\", \"secret\")\n        .build()\n        .await?;\n\n    let (tracker, mut rx) = client.call_tracker();\n\n    while let Some(call) = rx.recv().await {\n        tracing::info!(\n            channel = %call.channel,\n            duration = ?call.duration,\n            cause = %call.cause_txt,\n            \"call completed\"\n        );\n    }\n\n    tracker.shutdown();\n    Ok(())\n}\n```\n\n### AGI: IVR handler\n\n```rust,ignore\nuse asterisk_rs::agi::{AgiChannel, AgiHandler, AgiRequest, AgiServer};\n\nstruct IvrHandler;\n\nimpl AgiHandler for IvrHandler {\n    async fn handle(\u0026self, _request: AgiRequest, mut channel: AgiChannel)\n        -\u003e asterisk_rs::agi::error::Result\u003c()\u003e\n    {\n        channel.answer().await?;\n        channel.stream_file(\"welcome\", \"#\").await?;\n        let response = channel.get_data(\"press-ext\", 5000, 4).await?;\n        tracing::info!(digits = response.result, \"caller input\");\n        channel.hangup(None).await?;\n        Ok(())\n    }\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    tracing_subscriber::fmt::init();\n\n    let (server, _shutdown) = AgiServer::builder()\n        .bind(\"0.0.0.0:4573\")\n        .handler(IvrHandler)\n        .max_connections(100)\n        .build()\n        .await?;\n\n    server.run().await?;\n    Ok(())\n}\n```\n\n### ARI: pending channel\n\n```rust,ignore\nuse asterisk_rs::ari::config::AriConfigBuilder;\nuse asterisk_rs::ari::{AriClient, AriEvent, PendingChannel, TransportMode};\nuse asterisk_rs::ari::resources::channel::OriginateParams;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    tracing_subscriber::fmt::init();\n\n    let config = AriConfigBuilder::new(\"my-app\")\n        .host(\"127.0.0.1\")\n        .port(8088)\n        .username(\"asterisk\")\n        .password(\"asterisk\")\n        .build()?;\n\n    let client = AriClient::connect(config).await?;\n\n    // pre-subscribe before originate so no events are missed\n    let pending = client.channel();\n    let params = OriginateParams {\n        endpoint: \"PJSIP/100\".into(),\n        app: Some(\"my-app\".into()),\n        ..Default::default()\n    };\n    let (handle, mut events) = pending.originate(params).await?;\n\n    while let Some(msg) = events.recv().await {\n        match msg.event {\n            AriEvent::StasisStart { .. } =\u003e {\n                handle.answer().await?;\n                handle.play(\"sound:hello-world\").await?;\n                handle.hangup(None).await?;\n            }\n            AriEvent::ChannelDestroyed { cause_txt, .. } =\u003e {\n                tracing::info!(%cause_txt, \"channel destroyed\");\n                break;\n            }\n            _ =\u003e {}\n        }\n    }\n\n    Ok(())\n}\n```\n\n### PBX: dial and wait\n\n```rust,ignore\nuse asterisk_rs::ami::AmiClient;\nuse asterisk_rs::pbx::{DialOptions, Pbx};\nuse std::time::Duration;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    tracing_subscriber::fmt::init();\n\n    let client = AmiClient::builder()\n        .host(\"127.0.0.1\")\n        .credentials(\"admin\", \"secret\")\n        .build()\n        .await?;\n\n    let mut pbx = Pbx::new(client);\n\n    let call = pbx.dial(\n        \"PJSIP/100\",\n        \"200\",\n        Some(\n            DialOptions::new()\n                .caller_id(\"Rust PBX \u003c100\u003e\")\n                .timeout_ms(30000),\n        ),\n    ).await?;\n\n    call.wait_for_answer(Duration::from_secs(30)).await?;\n    tracing::info!(\"call answered\");\n\n    call.hangup().await?;\n\n    if let Some(completed) = pbx.next_completed_call().await {\n        tracing::info!(duration = ?completed.duration, cause = %completed.cause_txt, \"call record\");\n    }\n\n    Ok(())\n}\n```\n\n## Documentation\n\n- [API Reference (docs.rs)](https://docs.rs/asterisk-rs)\n- [AMI crate docs](https://docs.rs/asterisk-rs-ami)\n- [AGI crate docs](https://docs.rs/asterisk-rs-agi)\n- [ARI crate docs](https://docs.rs/asterisk-rs-ari)\n- [User Guide](https://deadcode-walker.github.io/asterisk-rs/)\n\n## MSRV\n\n1.83 -- required for `async fn` in traits (RPITIT).\n\n## License\n\nLicensed under either of\n\n- [Apache License, Version 2.0](LICENSE-APACHE)\n- [MIT License](LICENSE-MIT)\n\nat your option.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeadcode-walker%2Fasterisk-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdeadcode-walker%2Fasterisk-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeadcode-walker%2Fasterisk-rs/lists"}