Help us shape the future of Go! Take the 2026 Go Developer Survey

tello-go

module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: Apache-2.0

README

English | 한국어

tello-go

Go WebSocket SDK for the Tello /sdk protocol. The SDK is the "conversation brain": the gateway streams each caller turn from a live phone call, and your handler's reply is forwarded back into the call.

repo: tello-go · module: github.com/tello-ai/tello-go · package: tello

Transport is WebSocket only. There is no REST or webhook surface.

1. Install

go get github.com/tello-ai/tello-go

Requires Go 1.22+. The only runtime dependency is github.com/gorilla/websocket.

2. API key

NewClient("") reads TELLO_API_KEY; WithURL overrides the default ws://localhost:3000/sdk, which itself falls back to TELLO_URL.

Connect authenticates the API key internally: after the socket opens it sends an auth frame ({"event":"auth","data":{"token":"<apiKey>"}}) and returns only once the server confirms with auth.ok. No Authorization header or query-string token is used, and the key never appears in logs or error messages. Connect returns an error if authentication fails, the server closes with code 4401, or auth.ok does not arrive within WithOpenTimeout. No commands run before authentication completes.

3. Connect + start a call

package main

import (
	"context"
	"log"

	"github.com/tello-ai/tello-go/tello"
)

func main() {
	ctx := context.Background()
	client, err := tello.NewClient("")
	if err != nil {
		log.Fatal(err)
	}

	client.On(tello.EventTypeUserTurn, func(ctx context.Context, event tello.Event) error {
		return client.Answer(ctx, "heard: "+event.Text, "", "")
	})
	// Send DTMF digits: client.SendDtmf(ctx, "1234#", "", "")

	if err := client.Connect(ctx); err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	if err := client.CreateCall(ctx, "+821012345678", "reservation check", nil, ""); err != nil {
		log.Fatal(err)
	}
	if err := client.WaitClosed(ctx); err != nil {
		log.Fatal(err)
	}
}

Register handlers before Connect so no frame is missed.

4. Realtime turn events (pub/sub)

client.On(eventType string, handler func(context.Context, tello.Event) error) subscribes per event type. All events arrive as one tello.Event struct; the fields populated depend on the type, and event.Raw always holds the decoded frame.

constant value populated fields
EventTypeCallCreated call.created CallID, SessionID
EventTypeUserTurn user.turn TurnIndex, Text
EventTypeAgentTurn agent.turn TurnIndex, Text
EventTypeAnswerAccepted answer.accepted RequestID, MessageID
EventTypeDtmfAccepted dtmf.accepted RequestID, MessageID, Digits
EventTypeCallSummary call.summary RequestID, Status, DurationSeconds, Transcript, Summary, CreditCharged
EventTypeCallStatusChanged call.statusChanged Status, PreviousStatus
EventTypeCallCompleted call.completed Status
EventTypeCallNoAnswer call.noAnswer Status, FailureReason
EventTypeCallFailed call.failed Status, FailureReason
EventTypeError error Code, Message, RequestID, Question
EventTypeDisconnected disconnected SDK-local; emitted when the WS closes

auth.ok is consumed internally by Connect and never re-emitted.

5. Commands

client.CreateCall(ctx, to, prompt string, metadata map[string]any, requestID string) error
client.Answer(ctx, text, messageID, requestID string) error
client.SendDtmf(ctx, digits, messageID, requestID string) error
client.Cancel(ctx) error
client.GetSummary(ctx, callID, requestID string) error

Pass "" for any optional string. requestID correlates a command with its response frame; it is not an idempotency key.

client.WaitClosed(ctx) returns when the call reaches a terminal state (call.completed / call.noAnswer / call.failed, or a cancelled status) or the connection closes. Bound it with context.WithTimeout.

6. Error handling

Gateway error frames map 1:1 to typed errors:

gateway code error
unauthenticated *AuthenticationError (auth handshake; also close code 4401)
toRequired *ValidationError
callIdRequired *ValidationError
dtmfDigitsRequired *ValidationError
dtmfDigitsInvalid *ValidationError
callAlreadyActive *CallAlreadyActiveError
noActiveCall *NoActiveCallError
callNotFound *ValidationError
callNotCompleted *ValidationError
callRejected *CallRejectedError (with .Question)
internalError *TelloServerError

Every error carries the gateway code on .Code — branch on that, never on .Message, which is display text the gateway may reword.

createCall can also be refused before any call exists — no call.created, no callId, no charge. The gateway never retries these; any retry policy is yours.

gateway code error what to do
insufficientCredit *CallRefusedError tell the user to top up; do not resend
concurrentLimitExceeded *CallRefusedError wait for one of your own calls to end, then retry
callerNotVerified *CallRefusedError tell the user to verify the number; do not resend
noRepresentativeNumber *CallRefusedError tell the user to configure a caller number; do not resend
callProviderUnauthorized *CallProviderError service fault; report it, resending never helps
callProviderDraining *CallProviderError retry later at your own pace
callProviderUnavailable *CallProviderError retry later at your own pace
callSetupFailed *CallProviderError surface as a failure and report it

Command-level errors are also delivered to EventTypeError subscribers without closing the socket. WaitClosed returns the relevant error so a failed CreateCall (e.g. toRequired, callRejected) does not hang:

  • auth failure (unauthenticated frame, close 4401, or auth.ok timeout) → *AuthenticationError, returned from Connect
  • a call-start rejection → its mapped error above
  • the connection dropping mid-call → *ConnectionClosedError
  • the session being displaced (close 4429) → *SessionReplacedError

These types do not share a wrapper, so match each one with its own errors.As target:

var rejected *tello.CallRejectedError
if errors.As(err, &rejected) {
	log.Printf("call rejected: %s (%s)", rejected.Message, rejected.Question)
}

The gateway drives a WS-level ping heartbeat; gorilla/websocket answers pongs automatically. There is no reconnect/resume — treat an abnormal close as reconnect-worthy and restart the call.

7. Examples

Runnable programs live in examples/:

go run ./examples/basic-call      # connect, one call, answer each turn
go run ./examples/agent-callback  # full lifecycle, history, cancel, typed errors
go run ./examples/call-summary    # gated live scenario ending in call.summary

They place real calls. Read examples/README.md first.

8. Version compatibility

tello-go 0.1.x implements Tello WS protocol 1.0 (tello.ProtocolVersion).

The full frame contract is in docs/protocol/sdk-ws.v1.md, with docs/events/sdk-events.v1.schema.json and docs/errors/errors.v1.json. Those three files are generated copies of the canonical contract that lives beside the gateway implementation — read them here, edit them there.

Directories

Path Synopsis
examples
agent-callback command
Advanced example: an agent that holds a conversation over a live call.
Advanced example: an agent that holds a conversation over a live call.
basic-call command
Minimal end-to-end example: connect, start a call, answer each user turn.
Minimal end-to-end example: connect, start a call, answer each user turn.
call-summary command
Run a controlled call, then fetch its summary.
Run a controlled call, then fetch its summary.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL