ten-framework Documentation

Open-source framework for conversational voice AI agents

11,180 stars Python Python 99 files · ~200,161 tokens #ai#multi-modal#real-time#video#voice
RAW Doc

Technical Documentation: TEN-framework/ten-framework

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (README.md)

Table of Contents

Welcome to TEN

TEN is an open-source framework for real-time multimodal conversational AI.

TEN Ecosystem includes TEN Framework, Agent Examples, VAD, Turn Detection and Portal.

Community Channel Purpose
Follow on X Follow TEN Framework on X for updates and announcements
Discord TEN Community Join our Discord community to connect with developers
Follow on LinkedIn Follow TEN Framework on LinkedIn for updates and announcements
Hugging Face Space Join our Hugging Face community to explore our spaces and models

Agent Examples

Image

Multi-Purpose Voice Assistant — This low-latency, high-quality real-time assistant supports both RTC and WebSocket connections, and you can extend it with Memory, VAD, Turn Detection, and other extensions.

See the Example code for more details.

divider
divider

Image

Doodler — A doodle board that turns spoken or typed prompts into simple hand-drawn sketches, complete with a crayon palette and real-time drawing.

Example code

divider
divider

Image

Speaker Diarization — Real-time diarization that detects and labels speakers, the Who Likes What game shows an interactive use case.

Example code

divider
divider

Image

Lip Sync Avatars — Works with multiple avatar vendors, the main character features Kei, an anime character with MotionSync-powered lip sync, and also supports realistic avatars from Trulience, HeyGen, and Tavus.

See the Example code for different Live2D characters.

divider
divider

Image

SIP Call — SIP extension that enables phone calls powered by TEN.

Example code

divider
divider

Image

Transcription — A transcription tool that transcribes audio to text.

Example code

divider
divider

Image

ESP32-S3 Korvo V3 — Runs TEN agent example on the Espressif ESP32-S3 Korvo V3 development board to integrate LLM-powered communication with hardware.

See the integration guide for more details.

Quick Start with Agent Examples

Localhost

Step ⓵ - Prerequisites

Category Requirements
Keys • Agora App ID and App Certificate• OpenAI API key• Deepgram ASR • ElevenLabs TTS
Installation • Docker / Docker Compose• Node.js (LTS) v18
Minimum System Requirements • CPU >= 2 cores• RAM >= 4 GB

divider
divider

Step ⓶ - Build agent examples in VM

1. Clone the repo, `cd` into `ai_agents`, and create a `.env` file from `.env.example`

bash
cd ai_agents
cp ./.env.example ./.env

2. Set up the Agora App ID and App Certificate in `.env`

bash
AGORA_APP_ID=
AGORA_APP_CERTIFICATE=

# Deepgram (required for speech-to-text)
DEEPGRAM_API_KEY=

# OpenAI (required for language model)
OPENAI_API_KEY=

# ElevenLabs (required for text-to-speech)
ELEVENLABS_TTS_KEY=

3. Start agent development containers

bash
docker compose up -d

4. Enter the container

bash
docker exec -it ten_agent_dev bash

5. Build the agent with the default example (~5-8 min)

Check the agents/examples folder for additional samples.
Start with one of these defaults:

bash
# use the chained voice assistant
cd agents/examples/voice-assistant

# or use the speech-to-speech voice assistant in real time
cd agents/examples/voice-assistant-realtime

6. Start the web server

Run task install before the first launch and after changing dependencies or Go source code. It installs the TEN, Python, and frontend dependencies and builds the Go API server. Python-only source changes do not require reinstalling.

bash
task install
task run

7. Access the agent

Once the agent example is running, you can access the following interfaces:

localhost:49483 localhost:3000
Screenshot 1 Screenshot 2

divider
divider

Step ⓷ - Customize your agent example

  1. Open localhost:49483.
  2. Right-click the STT, LLM, and TTS extensions.
  3. Open their properties and enter the corresponding API keys.
  4. Submit your changes, now you can see the updated Agent Example in localhost:3000.

divider
divider

Run a transcriber app from TEN Manager without Docker (Beta)

TEN also provides a transcriber app that you can run from TEN Manager without using Docker.

Check the quick start guide for more details.

divider
divider

Codespaces

GitHub offers free Codespaces for each repository. You can run Agent Examples in Codespaces without using Docker. Codespaces typically start faster than local Docker environments.

Check out this guide for more details.

Agent Examples Self-Hosting

Deploying with Docker

Once you have customized your agent (either by using the TMAN Designer or editing property.json directly), you can deploy it by creating a release Docker image for your service.

Release as Docker image

Note: The following commands need to be executed outside of any Docker container.

Build image

bash
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .

Run

bash
docker run --rm -it --env-file .env -p 3000:3000 example-app

divider
divider

Deploying with other cloud services

You can split the deployment into two pieces when you want to host TEN on providers such as Vercel or Netlify.

  1. Run the TEN backend on any container-friendly platform (a VM with Docker, Fly.io, Render, ECS, Cloud Run, or similar). Use the example Docker image without modifying it and expose port 8080 from that service.

  2. Deploy only the frontend to Vercel or Netlify. Point the project root to ai_agents/agents/examples/<example>/frontend, run pnpm install (or bun install) followed by pnpm build (or bun run build), and keep the default .next output directory.

  3. Configure environment variables in your hosting dashboard so that AGENT_SERVER_URL points to the backend URL, and add any NEXT_PUBLIC_* keys the UI needs (for example, Agora credentials you surface to the browser).

  4. Ensure your backend accepts requests from the frontend origin — via open CORS or by using the built-in proxy middleware.

With this setup, the backend handles long-running worker processes, while the hosted frontend simply forwards API traffic to it.

Stay Tuned

Get instant notifications for new releases and updates. Your support helps us grow and improve TEN!

Image

TEN Ecosystem

Project Preview
️TEN FrameworkOpen-source framework for conversational AI Agents.
TEN VADLow-latency, lightweight and high-performance streaming voice activity detector (VAD).
️ TEN Turn DetectionTEN Turn Detection enables full-duplex dialogue communication.
TEN Agent ExamplesUsecases powered by TEN.
TEN PortalThe official site of the TEN Framework with documentation and a blog.

Questions

TEN Framework is available on these AI-powered Q&A platforms. They can help you find answers quickly and accurately in multiple languages, covering everything from basic setup to advanced implementation details.

Service Link
DeepWiki Ask DeepWiki
ReadmeX ReadmeX

Contributing

We welcome all forms of open-source collaboration! Whether you're fixing bugs, adding features, improving documentation, or sharing ideas, your contributions help advance personalized AI tools. Check out our GitHub Issues and Projects to find ways to contribute and show your skills. Together, we can build something amazing!

[!TIP]

Welcome all kinds of contributions 🙏

Join us in building TEN better! Every contribution makes a difference, from code to documentation. Share your TEN Agent projects on social media to inspire others!

Connect with one of the TEN maintainers @elliotchen200 on 𝕏 or @cyfyifanchen on GitHub for project updates, discussions, and collaboration opportunities.

divider
divider

Code Contributors

TEN

Contribution Guidelines

Contributions are welcome! Please read the contribution guidelines first.

divider
divider

License

  1. The entire TEN framework (except for the folders explicitly listed below) is released pursuant the Apache License, Version 2.0, with additional restrictions. For details, please refer to the LICENSE file located in the root directory of the TEN framework.

  2. The components within the packages directory are released under the Apache License, Version 2.0. For details, please refer to the LICENSE file located in each package's root directory.

  3. The third-party libraries used by the TEN framework are listed and described in detail. For more information, please refer to the third_party folder.


Third Party Curl Docs README (`third_party/curl/docs/README.md`)

Documentation

you will find a mix of various documentation in this directory and
subdirectories, using several different formats. Some of them are not ideal
for reading directly in your browser.

If you would rather see the rendered version of the documentation, check out the
curl website's documentation section for
general curl stuff or the libcurl section for
libcurl related documentation.


Third Party Nlohmann Json Docs README (`third_party/nlohmann_json/docs/README.md`)

Documentation

Generate documentation

Note on documentation: The source files contain links to the online documentation at https://json.nlohmann.me. This URL
contains the most recent documentation and should also be applicable to previous versions; documentation for deprecated
functions is not removed, but marked deprecated.

If you want to see the documentation for a specific tag or commit hash, you can generate it as follows (here for tag
v3.10.2):

shell
git clone https://github.com/nlohmann/json.git
cd json
git checkout v3.10.2
make install_venv serve -C docs/mkdocs

Open URL http://127.0.0.1:8000/ in your browser. Replace from any URL from the source code https://json.nlohmann.me
with http://127.0.0.1:8000 to see the documentation for your tag or commit hash.


Ai L1 L2 Index (`docs/ai/L1/L2/_index.md`)

Deep Dives Index

Document Summary Load When
extension_development.md Full extension creation guide, base classes, test configs, pre-submission checklist Creating a new TTS/ASR/LLM extension
asr_plugin_design_review.md Required ASR design rules, lifecycle pitfalls, and PR review checklist Adding or modifying any ASR extension
graph_configuration.md Graph nodes, connections, routing, property.json Modifying graphs or wiring extensions together
testing.md Core TTS and ASR guarder tests, pass criteria, config files, debugging Running or debugging tests for an extension
deployment.md Docker, Cloudflare, Nginx, Grafana monitoring Deploying to production or setting up monitoring
server_architecture.md Go server, property injection, worker lifecycle Understanding server internals or debugging
operations_restarts.md Full restart procedures, port debugging, recovery Restarting services, crash recovery, port conflicts

Third Party Curl Docs Examples README (`third_party/curl/docs/examples/README.md`)

libcurl examples

This directory is for libcurl programming examples. They are meant to show
some simple steps on how you can build your own application to take full
advantage of libcurl.

If you end up with other small but still useful example sources, please mail
them for submission in future packages and on the website.

Building

The Makefile.example is an example Makefile that could be used to build
these examples. Just edit the file according to your system and requirements
first.

Most examples should build fine using a command line like this:

text
`curl-config --cc --cflags --libs` -o example example.c

Some compilers do not like having the arguments in this order but instead
want you do reorganize them like:

text
`curl-config --cc` -o example example.c `curl-config --cflags --libs`

Please do not use the curl.se site as a test target for your libcurl
applications/experiments. Even if some of the examples use that site as a URL
at some places, it does not mean that the URLs work or that we expect you to
actually torture our website with your tests. Thanks.

Examples

Each example source code file is designed to be and work stand-alone and
rather self-explanatory. The examples may at times lack the level of error
checks you need in a real world, but that is then only for the sake of
readability: to make the code smaller and easier to follow.


Third Party Nlohmann Json Docs Docset README (`third_party/nlohmann_json/docs/docset/README.md`)

docset

The folder contains the required files to create a docset which can be used in
documentation browsers like Dash, Velocity, or
Zeal.

The docset can be created with

sh
make nlohmann_json.docset

The generated folder nlohmann_json.docset can then be opened in the documentation browser.

A recent version is also part of the Dash user contributions.

Licenses

The JSON logo is public domain.


Third Party Clingo Sys Clingo Third Party Catch Docs Readme (`third_party/clingo-sys/clingo/third_party/catch/docs/Readme.md`)

Reference

To get the most out of Catch2, start with the tutorial.
Once you're up and running consider the following reference material.

Writing tests:

Fine tuning:

Running:

Odds and ends:

Other:


Third Party Nlohmann Json Docs Mkdocs Docs Integration Index (`third_party/nlohmann_json/docs/mkdocs/docs/integration/index.md`)

Header only

json.hpp is the single required
file in single_include/nlohmann or released here. You need to add

cpp
#include <nlohmann/json.hpp>

// for convenience
using json = nlohmann::json;

to the files you want to process JSON and set the necessary switches to enable C++11 (e.g., -std=c++11 for GCC and
Clang).

You can further use file
single_include/nlohmann/json_fwd.hpp
for forward declarations.


Third Party Nlohmann Json Docs Mkdocs Docs Api Adl Serializer Index (`third_party/nlohmann_json/docs/mkdocs/docs/api/adl_serializer/index.md`)

nlohmann::adl_serializer

cpp
template<typename, typename>
struct adl_serializer;

Serializer that uses ADL (Argument-Dependent Lookup) to choose
to_json/from_json functions from the types' namespaces.

It is implemented similar to

cpp
template<typename ValueType>
struct adl_serializer {
    template<typename BasicJsonType>
    static void to_json(BasicJsonType& j, const T& value) {
        // calls the "to_json" method in T's namespace
    }

    template<typename BasicJsonType>
    static void from_json(const BasicJsonType& j, T& value) {
        // same thing, but with the "from_json" method
    }
};

Member functions

  • from_json - convert a JSON value to any value type
  • to_json - convert any value type to a JSON value

Version history

  • Added in version 2.1.0.

Third Party Nlohmann Json Docs Mkdocs Docs Api Basic Json Index (`third_party/nlohmann_json/docs/mkdocs/docs/api/basic_json/index.md`)

nlohmann::basic_json

Defined in header <nlohmann/json.hpp>

cpp
template<
    template<typename U, typename V, typename... Args> class ObjectType = std::map,
    template<typename U, typename... Args> class ArrayType = std::vector,
    class StringType = std::string,
    class BooleanType = bool,
    class NumberIntegerType = std::int64_t,
    class NumberUnsignedType = std::uint64_t,
    class NumberFloatType = double,
    template<typename U> class AllocatorType = std::allocator,
    template<typename T, typename SFINAE = void> class JSONSerializer = adl_serializer,
    class BinaryType = std::vector<std::uint8_t>,
    class CustomBaseClass = void
>
class basic_json;

Template parameters

Template parameter Description Derived type
ObjectType type for JSON objects object_t
ArrayType type for JSON arrays array_t
StringType type for JSON strings and object keys string_t
BooleanType type for JSON booleans boolean_t
NumberIntegerType type for JSON integer numbers number_integer_t
NumberUnsignedType type for JSON unsigned integer numbers number_unsigned_t
NumberFloatType type for JSON floating-point numbers number_float_t
AllocatorType type of the allocator to use
JSONSerializer the serializer to resolve internal calls to to_json() and from_json() json_serializer
BinaryType type for binary arrays binary_t
CustomBaseClass extension point for user code json_base_class_t

Specializations

  • json - default specialization
  • ordered_json - specialization that maintains the insertion order of object keys

Iterator invalidation

Todo

Requirements

The class satisfies the following concept requirements:

Basic

Layout

  • StandardLayoutType: JSON values have
    standard layout: All non-static data
    members are private and standard layout types, the class has no virtual functions or (virtual) base classes.

Library-wide

  • EqualityComparable: JSON values can be compared with
    ==, see operator==.
  • LessThanComparable: JSON values can be compared with
    <, see operator<.
  • Swappable: Any JSON lvalue or rvalue of can be swapped with
    any lvalue or rvalue of other compatible types, using unqualified function swap.
  • NullablePointer: JSON values can be compared against
    std::nullptr_t objects which are used to model the null value.

Container

  • Container: JSON values can be used like STL containers and
    provide iterator access.
  • ReversibleContainer: JSON values can be used like
    STL containers and provide reverse iterator access.

Member types

Exceptions

  • exception - general exception of the basic_json class
    • parse_error - exception indicating a parse error
    • invalid_iterator - exception indicating errors with iterators
    • type_error - exception indicating executing a member function with a wrong type
    • out_of_range - exception indicating access out of the defined range
    • other_error - exception indicating other library errors

Container types

Type Definition
value_type #!cpp basic_json
reference #!cpp value_type&
const_reference #!cpp const value_type&
difference_type #!cpp std::ptrdiff_t
size_type #!cpp std::size_t
allocator_type #!cpp AllocatorType
pointer #!cpp std::allocator_traits<allocator_type>::pointer
const_pointer #!cpp std::allocator_traits<allocator_type>::const_pointer
iterator LegacyBidirectionalIterator
const_iterator constant LegacyBidirectionalIterator
reverse_iterator reverse iterator, derived from iterator
const_reverse_iterator reverse iterator, derived from const_iterator
iteration_proxy helper type for items function

JSON value data types

Parser callback

Member functions

Object inspection

Functions to inspect the type of a JSON value.

Value access

Direct access to the stored value of a JSON value.

Element access

Access to the JSON value

  • at - access specified element with bounds checking
  • operator[] - access specified element
  • value - access specified object element with default value
  • front - access the first element
  • back - access the last element

Lookup

  • find - find an element in a JSON object
  • count - returns the number of occurrences of a key in a JSON object
  • contains - check the existence of an element in a JSON object

Iterators

  • begin - returns an iterator to the first element
  • cbegin - returns a const iterator to the first element
  • end - returns an iterator to one past the last element
  • cend - returns a const iterator to one past the last element
  • rbegin - returns an iterator to the reverse-beginning
  • rend - returns an iterator to the reverse-end
  • crbegin - returns a const iterator to the reverse-beginning
  • crend - returns a const iterator to the reverse-end
  • items - wrapper to access iterator member functions in range-based for

Capacity

  • empty - checks whether the container is empty
  • size - returns the number of elements
  • max_size - returns the maximum possible number of elements

Modifiers

  • clear - clears the contents
  • push_back - add a value to an array/object
  • operator+= - add a value to an array/object
  • emplace_back - add a value to an array
  • emplace - add a value to an object if key does not exist
  • erase - remove elements
  • insert - inserts elements
  • update - updates a JSON object from another object, overwriting existing keys
  • swap - exchanges the values

Lexicographical comparison operators

Serialization / Dumping

  • dump - serialization

Deserialization / Parsing

  • parse (static) - deserialize from a compatible input
  • accept (static) - check if the input is valid JSON
  • sax_parse (static) - generate SAX events

JSON Pointer functions

  • flatten - return flattened JSON value
  • unflatten - unflatten a previously flattened JSON value

JSON Patch functions

  • patch - applies a JSON patch
  • patch_inplace - applies a JSON patch in place
  • diff (static) - creates a diff as a JSON patch

JSON Merge Patch functions

Static functions

  • meta - returns version information on the library
  • get_allocator - returns the allocator associated with the container

Binary formats

  • from_bjdata (static) - create a JSON value from an input in BJData format
  • from_bson (static) - create a JSON value from an input in BSON format
  • from_cbor (static) - create a JSON value from an input in CBOR format
  • from_msgpack (static) - create a JSON value from an input in MessagePack format
  • from_ubjson (static) - create a JSON value from an input in UBJSON format
  • to_bjdata (static) - create a BJData serialization of a given JSON value
  • to_bson (static) - create a BSON serialization of a given JSON value
  • to_cbor (static) - create a CBOR serialization of a given JSON value
  • to_msgpack (static) - create a MessagePack serialization of a given JSON value
  • to_ubjson (static) - create a UBJSON serialization of a given JSON value

Non-member functions

Literals

Helper classes

Examples

??? example

text
The example shows how the library is used.

```cpp
--8<-- "examples/README.cpp"
```

Output:

```json
--8<-- "examples/README.output"
```

See also

Version history

  • Added in version 1.0.0.

Third Party Nlohmann Json Docs Mkdocs Docs Api Byte Container With Subtype Index (`third_party/nlohmann_json/docs/mkdocs/docs/api/byte_container_with_subtype/index.md`)

nlohmann::byte_container_with_subtype

cpp
template<typename BinaryType>
class byte_container_with_subtype : public BinaryType;

This type extends the template parameter BinaryType provided to basic_json with a subtype
used by BSON and MessagePack. This type exists so that the user does not have to specify a type themselves with a
specific naming scheme in order to override the binary type.

Template parameters

BinaryType
: container to store bytes (#!cpp std::vector<std::uint8_t> by default)

Member types

  • container_type - the type of the underlying container (BinaryType)
  • subtype_type - the type of the subtype (#!cpp std::uint64_t)

Member functions

Version history

  • Added in version 3.8.0.
  • Changed type of subtypes to #!cpp std::uint64_t in 3.10.0.

Third Party Nlohmann Json Docs Mkdocs Docs Api Json Pointer Index (`third_party/nlohmann_json/docs/mkdocs/docs/api/json_pointer/index.md`)

nlohmann::json_pointer

cpp
template<typename RefStringType>
class json_pointer;

A JSON pointer defines a string syntax for identifying a specific value within a JSON document. It can be used with
functions at and operator[]. Furthermore, JSON pointers
are the base for JSON patches.

Template parameters

RefStringType
: the string type used for the reference tokens making up the JSON pointer

!!! warning "Deprecation"

text
For backwards compatibility `RefStringType` may also be a specialization of [`basic_json`](../basic_json/index.md)
in which case `string_t` will be deduced as [`basic_json::string_t`](../basic_json/string_t.md). This feature is
deprecated and may be removed in a future major version.

Member types

  • string_t - the string type used for the reference tokens

Member functions

Literals

See also

Version history

  • Added in version 2.0.0.
  • Changed template parameter from basic_json to string type in version 3.11.0.

Third Party Nlohmann Json Docs Mkdocs Docs Api Json Sax Index (`third_party/nlohmann_json/docs/mkdocs/docs/api/json_sax/index.md`)

nlohmann::json_sax

cpp
template<typename BasicJsonType>
struct json_sax;

This class describes the SAX interface used by sax_parse. Each function is called in
different situations while the input is parsed. The boolean return value informs the parser whether to continue
processing the input.

Template parameters

BasicJsonType
: a specialization of basic_json

Member types

Member functions

  • binary (virtual) - a binary value was read
  • boolean (virtual) - a boolean value was read
  • end_array (virtual) - the end of an array was read
  • end_object (virtual) - the end of an object was read
  • key (virtual) - an object key was read
  • null (virtual) - a null value was read
  • number_float (virtual) - a floating-point number was read
  • number_integer (virtual) - an integer number was read
  • number_unsigned (virtual) - an unsigned integer number was read
  • parse_error (virtual) - a parse error occurred
  • start_array (virtual) - the beginning of an array was read
  • start_object (virtual) - the beginning of an object was read
  • string (virtual) - a string value was read

Version history

  • Added in version 3.2.0.
  • Support for binary values (binary_t, binary) added in version 3.8.0.

Third Party Nlohmann Json Docs Mkdocs Docs Api Macros Index (`third_party/nlohmann_json/docs/mkdocs/docs/api/macros/index.md`)

Macros

Some aspects of the library can be configured by defining preprocessor macros before including the json.hpp
header. See also the macro overview page.

Runtime assertions

Exceptions

Language support

Library version

Library namespace

Type conversions

Comparison behavior

Serialization/deserialization macros


Third Party Nlohmann Json Docs Mkdocs Docs Features Binary Formats Index (`third_party/nlohmann_json/docs/mkdocs/docs/features/binary_formats/index.md`)

Binary Formats

Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over
a network. Hence, the library supports

to efficiently encode JSON values to byte vectors and to decode such vectors.

Comparison

Completeness

Format Serialization Deserialization
BJData complete complete
BSON incomplete: top-level value must be an object incomplete, but all JSON types are supported
CBOR complete incomplete, but all JSON types are supported
MessagePack complete complete
UBJSON complete complete

Binary values

Format Binary values Binary subtypes
BJData not supported not supported
BSON supported supported
CBOR supported supported
MessagePack supported supported
UBJSON not supported not supported

See binary values for more information.

Sizes

Format canada.json twitter.json citm_catalog.json jeopardy.json
BJData 53.2 % 91.1 % 78.1 % 96.6 %
BJData (size) 58.6 % 92.1 % 86.7 % 97.4 %
BJData (size+tyoe) 58.6 % 92.1 % 86.5 % 97.4 %
BSON 85.8 % 95.2 % 95.8 % 106.7 %
CBOR 50.5 % 86.3 % 68.4 % 88.0 %
MessagePack 50.5 % 86.0 % 68.5 % 87.9 %
UBJSON 53.2 % 91.3 % 78.2 % 96.6 %
UBJSON (size) 58.6 % 92.3 % 86.8 % 97.4 %
UBJSON (size+type) 55.9 % 92.3 % 85.0 % 95.0 %

Sizes compared to minified JSON value.


Third Party Nlohmann Json Docs Mkdocs Docs Features Element Access Index (`third_party/nlohmann_json/docs/mkdocs/docs/features/element_access/index.md`)

Element Access

There are many ways elements in a JSON value can be accessed:

  • unchecked access via operator[]
  • checked access via at
  • access with default value via value
  • iterators
  • JSON pointers

Third Party Nlohmann Json Docs Mkdocs Docs Features Parsing Index (`third_party/nlohmann_json/docs/mkdocs/docs/features/parsing/index.md`)

Parsing

!!! note

text
This page is under construction.

Input

SAX vs. DOM parsing

Exceptions

See parsing and exceptions.


Third Party Nlohmann Json Docs Mkdocs Docs Features Types Index (`third_party/nlohmann_json/docs/mkdocs/docs/features/types/index.md`)

Types

This page gives an overview how JSON values are stored and how this can be configured.

Overview

By default, JSON values are stored as follows:

JSON type C++ type
object std::map<std::string, basic_json>
array std::vector
null std::nullptr_t
string std::string
boolean bool
number std::int64_t, std::uint64_t, and double

Note there are three different types for numbers - when parsing JSON text, the best fitting type is chosen.

Storage

plantuml
enum value_t {
    null
    object
    array
    string
    boolean
    number_integer
    number_unsigned
    number_float
    binary
    discarded
}

class json_value << (U,orchid) >> {
    object_t* object
    array_t* array
    string_t* string
    binary_t* binary
    boolean_t boolean
    number_integer_t number_integer
    number_unsigned_t number_unsigned
    number_float_t number_float
}

class basic_json {
    -- type and value --
    value_t m_type
    json_value m_value
    -- derived types --
    + <u>typedef</u> object_t
    + <u>typedef</u> array_t
    + <u>typedef</u> binary_t
    + <u>typedef</u> boolean_t
    + <u>typedef</u> number_integer_t
    + <u>typedef</u> number_unsigned_t
    + <u>typedef</u> number_float_t
}

basic_json .. json_value
basic_json .. value_t

Template arguments

The data types to store a JSON value are derived from the template arguments passed to class basic_json:

cpp
template<
    template<typename U, typename V, typename... Args> class ObjectType = std::map,
    template<typename U, typename... Args> class ArrayType = std::vector,
    class StringType = std::string,
    class BooleanType = bool,
    class NumberIntegerType = std::int64_t,
    class NumberUnsignedType = std::uint64_t,
    class NumberFloatType = double,
    template<typename U> class AllocatorType = std::allocator,
    template<typename T, typename SFINAE = void> class JSONSerializer = adl_serializer,
    class BinaryType = std::vector<std::uint8_t>
>
class basic_json;

Type json is an alias for basic_json<> and uses the default types.

From the template arguments, the following types are derived:

cpp
using object_comparator_t = std::less<>;
using object_t = ObjectType<StringType, basic_json, object_comparator_t,
                   AllocatorType<std::pair<const StringType, basic_json>>>;

using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;

using string_t = StringType;

using boolean_t = BooleanType;

using number_integer_t = NumberIntegerType;
using number_unsigned_t = NumberUnsignedType;
using number_float_t = NumberFloatType;

using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;

Objects

RFC 8259 describes JSON objects as follows:

An object is an unordered collection of zero or more name/value pairs, where a name is a string and a value is a string, number, boolean, null, object, or array.

Default type

With the default values for ObjectType (std::map), StringType (std::string), and AllocatorType (std::allocator), the default value for object_t is:

cpp
std::map<
  std::string, // key_type
  basic_json, // value_type
  std::less<>, // key_compare
  std::allocator<std::pair<const std::string, basic_json>> // allocator_type
>

Behavior

The choice of object_t influences the behavior of the JSON class. With the default type, objects have the following behavior:

  • When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings.
  • When the names within an object are not unique, it is unspecified which one of the values for a given key will be chosen. For instance, #!json {"key": 2, "key": 1} could be equal to either #!json {"key": 1} or #!json {"key": 2}.
  • Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see dump) in this order. For instance, both #!json {"b": 1, "a": 2} and #!json {"a": 2, "b": 1} will be stored and serialized as #!json {"a": 2, "b": 1}.
  • When comparing objects, the order of the name/value pairs is irrelevant. This makes objects interoperable in the sense that they will not be affected by these differences. For instance, #!json {"b": 1, "a": 2} and #!json {"a": 2, "b": 1} will be treated as equal.

Key order

The order name/value pairs are added to the object is not preserved by the library. Therefore, iterating an object may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in alphabetical order as std::map with std::less is used by default. Please note this behavior conforms to RFC 8259, because any order implements the specified "unordered" nature of JSON objects.

Limits

RFC 8259 specifies:

An implementation may set limits on the maximum depth of nesting.

In this class, the object's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the max_size function of a JSON object.

Storage

Objects are stored as pointers in a basic_json type. That is, for any access to object values, a pointer of type object_t* must be dereferenced.

Arrays

RFC 8259 describes JSON arrays as follows:

An array is an ordered sequence of zero or more values.

Default type

With the default values for ArrayType (std::vector) and AllocatorType (std::allocator), the default value for array_t is:

cpp
std::vector<
  basic_json, // value_type
  std::allocator<basic_json> // allocator_type
>

Limits

RFC 8259 specifies:

An implementation may set limits on the maximum depth of nesting.

In this class, the array's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the max_size function of a JSON array.

Storage

Arrays are stored as pointers in a basic_json type. That is, for any access to array values, a pointer of type array_t* must be dereferenced.

Strings

RFC 8259 describes JSON strings as follows:

A string is a sequence of zero or more Unicode characters.

Unicode values are split by the JSON class into byte-sized characters during deserialization.

Default type

With the default values for StringType (std::string), the default value for string_t is #!cpp std::string.

Encoding

Strings are stored in UTF-8 encoding. Therefore, functions like std::string::size() or std::string::length() return the number of bytes in the string rather than the number of characters or glyphs.

String comparison

RFC 8259 states:

Software implementations are typically required to test names of object members for equality. Implementations that transform the textual representation into sequences of Unicode code units and then perform the comparison numerically, code unit by code unit, are interoperable in the sense that implementations will agree in all cases on equality or inequality of two strings. For example, implementations that compare strings with escaped characters unconverted may incorrectly find that "a\\b" and "a\u005Cb" are not equal.

This implementation is interoperable as it does compare strings code unit by code unit.

Storage

String values are stored as pointers in a basic_json type. That is, for any access to string values, a pointer of type string_t* must be dereferenced.

Booleans

RFC 8259 implicitly describes a boolean as a type which differentiates the two literals true and false.

Default type

With the default values for BooleanType (#!cpp bool), the default value for boolean_t is #!cpp bool.

Storage

Boolean values are stored directly inside a basic_json type.

Numbers

See the number handling article for a detailed discussion on how numbers are handled by this library.

RFC 8259 describes numbers as follows:

The representation of numbers is similar to that used in most programming languages. A number is represented in base 10 using decimal digits. It contains an integer component that may be prefixed with an optional minus sign, which may be followed by a fraction part and/or an exponent part. Leading zeros are not allowed. (...) Numeric values that cannot be represented in the grammar below (such as Infinity and NaN) are not permitted.

This description includes both integer and floating-point numbers. However, C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, three different types, number_integer_t, number_unsigned_t, and number_float_t are used.

Default types

With the default values for NumberIntegerType (std::int64_t), the default value for number_integer_t is std::int64_t.
With the default values for NumberUnsignedType (std::uint64_t), the default value for number_unsigned_t is std::uint64_t.
With the default values for NumberFloatType (#!cpp double), the default value for number_float_t is #!cpp double.

Default behavior

  • The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For instance, the C++ integer literal #!c 010 will be serialized to #!c 8. During deserialization, leading zeros yield an error.
  • Not-a-number (NaN) values will be serialized to #!json null.

Limits

RFC 8259 specifies:

An implementation may set limits on the range and precision of numbers.

When the default type is used, the maximal integer number that can be stored is #!c 9223372036854775807 (INT64_MAX) and the minimal integer number that can be stored is #!c -9223372036854775808 (INT64_MIN). Integer numbers that are out of range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers will be automatically be stored as number_unsigned_t or number_float_t.

When the default type is used, the maximal unsigned integer number that can be stored is #!c 18446744073709551615 (UINT64_MAX) and the minimal integer number that can be stored is #!c 0. Integer numbers that are out of range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers will be automatically be stored as number_integer_t or number_float_t.

RFC 8259 further states:

Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are interoperable in the sense that implementations will agree exactly on their numeric values.

As this range is a subrange of the exactly supported range [INT64_MIN, INT64_MAX], this class's integer type is interoperable.

RFC 8259 states:

This specification allows implementations to set limits on the range and precision of numbers accepted. Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide, in the sense that implementations will approximate JSON numbers within the expected precision.

This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note values smaller than #!c -1.79769313486232e+308 and values greater than #!c 1.79769313486232e+308 will be stored as NaN internally and be serialized to #!json null.

Storage

Integer number values, unsigned integer number values, and floating-point number values are stored directly inside a basic_json type.


README CN (`docs/README-CN.md`)

目录

欢迎来到 TEN

TEN 是一个面向语音对话 AI 代理的开源框架。

TEN 生态 包含 TEN Framework、代理示例、VAD、Turn Detection 以及 Portal。

社区渠道 用途
Follow on X 在 X 上关注 TEN Framework,获取更新与公告
Discord TEN Community 加入 Discord 社区,与开发者交流
Follow on LinkedIn 在 LinkedIn 上关注 TEN Framework,获取动态和公告
Hugging Face Space 加入 Hugging Face 社区,探索我们的空间与模型
WeChat 加入微信社群,与中文社区讨论

代理示例

Image

多用途语音助手 —— 低延迟、高质量的实时助手,可通过 记忆、VAD、回合检测 等扩展能力进行增强。

详见 示例代码。

divider
divider

Image

Doodler —— 将语音或文本提示转换为手绘风格涂鸦的画板,配有蜡笔调色板并支持实时绘制。

示例代码

divider
divider

Image

唇形同步头像 —— 适配多个头像供应商,演示中包含 Live2D 唇形同步的动漫角色 Kei,并即将支持 Trulience、HeyGen、Tavus 等写实头像。

查看 Live2D 示例代码。

divider
divider

Image

语音分离(Diarization) —— 实时检测并标记不同说话人,“Who Likes What” 游戏展示了一个交互式场景。

示例代码

divider
divider

Image

SIP 通话 —— 通过 TEN 提供电话功能的 SIP 扩展。

示例代码

divider
divider

Image

转写(Transcription) —— 将音频实时转换为文本的工具。

示例代码

divider
divider

Image

ESP32-S3 Korvo V3 —— 在 Espressif ESP32-S3 Korvo V3 开发板上运行 TEN 代理示例,让硬件具备 LLM 驱动的交互能力。

更多细节请参阅 集成指南。

代理示例快速上手

本地环境

步骤 ⓵ - 前置条件

类别 要求
密钥 • Agora App ID 与 App Certificate(每月赠送免费分钟)• OpenAI API Key(兼容 OpenAI 协议的任意 LLM)• Deepgram ASR(注册即可获得免费额度)• ElevenLabs TTS(注册即可获得免费额度)
安装 • Docker / Docker Compose• Node.js (LTS) v18
最低系统要求 • CPU ≥ 2 核• RAM ≥ 4 GB

divider
divider

如果在国内,我们强烈建议在 SSH 中把代理打开,下载和安装的依赖的时候会更加丝滑。

bash
# 如果用的代理软件没有增强模式的话, 建议手动把所有代理协议都打开
# export 的有效期为一个 session
export https_proxy=http://127.0.0.1:<port>
export http_proxy=http://127.0.0.1:<port>
export all_proxy=socks5://127.0.0.1:<port>

# Docker
export https_proxy=http://host.docker.internal:<port>
export http_proxy=http://host.docker.internal:<port>
export all_proxy=http://host.docker.internal:<port>

# tman 镜像设置
mkdir -p ~/.tman && echo '{
  "registry": {
    "default": {
      "index": "https://registry-ten.rtcdeveloper.cn/api/ten-cloud-store/v1/packages"
    }
  }
}' > ~/.tman/config.json

# GO 代理设置
export GOPROXY=https://goproxy.cn,direct

# pip 代理设置, 此设置需要先安装 pip
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
export UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple

divider
divider

步骤 ⓶ - 在虚拟机中构建代理示例

1. 克隆仓库,进入 `ai_agents`,并用 `.env.example` 创建 `.env`

bash
cd ai_agents
cp ./.env.example ./.env

2. 在 `.env` 中配置 Agora App ID 与 App Certificate

bash
AGORA_APP_ID=
AGORA_APP_CERTIFICATE=

# 运行默认的语音助手示例
# Deepgram(语音转文本所需)
DEEPGRAM_API_KEY=your_deepgram_api_key_here

# OpenAI(语言模型所需)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4o

# ElevenLabs(语音合成所需)
ELEVENLABS_TTS_KEY=your_elevenlabs_api_key_here

3. 启动代理开发容器

bash
docker compose up -d

4. 进入容器

bash
docker exec -it ten_agent_dev bash

5. 使用默认示例构建代理(约 5-8 分钟)

在 agents/examples 文件夹中可以找到更多示例。
从以下默认示例之一开始:

bash
# 使用串联语音助手
cd agents/examples/voice-assistant

# 或者使用实时语音对话助手
cd agents/examples/voice-assistant-realtime

6. 启动 Web 服务

首次启动前,以及依赖或 Go 源码发生变化后,请运行 task install。它会安装 TEN、Python 和前端依赖,并构建 Go API 服务。仅修改 Python 源码时无需重新安装。

bash
task install
task run

7. 访问代理

启动示例后,可以访问以下界面:

localhost:49483 localhost:3000

divider
divider

步骤 ⓷ - 自定义代理示例

  1. 打开 localhost:49483。
  2. 右键单击 STT、LLM、TTS 扩展。
  3. 在属性面板中填写对应的 API Key。
  4. 提交更改后,即可在 localhost:3000 查看更新效果。

divider
divider

在不使用 Docker 的情况下从 TEN Manager 运行转录应用(Beta)

TEN 还提供了一个转录应用,无需 Docker 即可直接在 TEN Manager 中运行。

详情请查看快速入门指南。

divider
divider

Codespaces

GitHub 为每个仓库提供免费的 Codespaces。无需 Docker 即可运行代理示例,并且通常比本地 Docker 环境启动更快。

更多细节请查看这篇指南。

代理示例自托管

使用 Docker 部署

当你通过 TMAN Designer 或直接编辑 property.json 自定义完代理后,可以为服务构建发布用的 Docker 镜像并部署。

以 Docker 镜像方式发布

注意:以下命令需在 Docker 容器外部执行。

构建镜像

bash
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .

运行

bash
docker run --rm -it --env-file .env -p 3000:3000 example-app

divider
divider

部署到其他云服务

若需将 TEN 部署到 Vercel、Netlify 等平台,可以拆分为前后端两部分:

  1. 在任意支持容器的平台(Docker 主机、Fly.io、Render、ECS、Cloud Run 等)运行 TEN 后端。直接使用示例镜像,开放 8080 端口。
  2. 仅部署前端到 Vercel 或 Netlify。将项目根目录指向 ai_agents/agents/examples/<example>/frontend,执行 pnpm install(或 bun install)与 pnpm build(或 bun run build),并保留默认 .next 输出。
  3. 在托管平台的环境变量面板设置 AGENT_SERVER_URL 指向后端 URL,同时配置前端需要的 NEXT_PUBLIC_* 变量(例如需要暴露给浏览器的 Agora 凭据)。
  4. 确保后端允许来自前端域名的请求,可通过开放 CORS 或使用内置代理中间件。

这样后端负责长时间运行的工作进程,托管前端只需将 API 请求转发给后端即可。

保持关注

实时获取版本更新与最新动态。你的支持能帮助 TEN 变得更好!

Image

TEN 生态

项目 预览
️TEN Framework面向对话式 AI 代理的开源框架。
TEN VAD低延迟、轻量且高性能的流式语音活动检测。
️TEN Turn Detection实现全双工对话的回合检测。
TEN Agent Examples基于 TEN 的多种应用示例。
TEN PortalTEN 官方站点,提供文档与博客。

常见问题

TEN Framework 也可通过以下 AI 驱动的问答平台获取信息,它们支持多语言检索,覆盖从基础配置到高级实践的内容。

服务 链接
DeepWiki Ask DeepWiki
ReadmeX ReadmeX

参与贡献

欢迎所有形式的开源协作!无论是修复缺陷、添加功能、改进文档还是分享创意,你的参与都能推动个性化 AI 工具向前发展。访问 GitHub Issues 与 Projects 寻找合适的任务,展示你的技能,让我们一起构建更出色的 TEN!

[!TIP]

欢迎所有类型的贡献 🙏

帮助我们让 TEN 变得更好!从代码到文档,每一次贡献都弥足珍贵。也欢迎在社交平台分享你的 TEN 代理项目,激励更多创作者。

可以通过 𝕏 上的 @elliotchen200 或 GitHub 上的 @cyfyifanchen 与维护者联系,获取项目动态、讨论与合作机会。

divider
divider

代码贡献者

TEN

贡献指南

欢迎贡献!在提交之前,请先阅读贡献指南。

divider
divider

许可证

  1. 除下列目录外,TEN Framework 均以 Apache License 2.0(附加条款)发布,详见根目录下的 LICENSE 文件。
  2. packages 目录中的组件以 Apache License 2.0 发布,详情请参考各组件根目录内的 LICENSE 文件。
  3. TEN Framework 使用的第三方库均在 third_party 目录中列出并说明。


README ES (`docs/README-ES.md`)

Tabla de contenido

Bienvenido a TEN

TEN es un marco de código abierto para agentes conversacionales de voz impulsados por IA.

El ecosistema TEN incluye TEN Framework, Ejemplos de agente, VAD, Turn Detection y Portal.

Canal de la comunidad Propósito
Follow on X Sigue TEN Framework en X para enterarte de novedades y anuncios
Discord TEN Community Únete a la comunidad de Discord y conecta con otras personas desarrolladoras
Follow on LinkedIn Sigue TEN Framework en LinkedIn para recibir actualizaciones y noticias
Hugging Face Space Explora nuestros espacios y modelos en la comunidad de Hugging Face
WeChat Únete al grupo de WeChat para conversar con la comunidad china

Ejemplos de agente

Image

Asistente de voz multipropósito — Un asistente en tiempo real, de baja latencia y alta calidad que puedes ampliar con memoria, VAD, detección de turnos y otras extensiones.

Consulta el código de ejemplo para obtener más detalles.

divider
divider

Image

Doodler — Un tablero de garabatos que convierte indicaciones habladas o escritas en bocetos sencillos a mano, con una paleta de crayones y dibujo en tiempo real.

Código de ejemplo

divider
divider

Image

Avatares con sincronización labial — Compatible con múltiples proveedores de avatares. La demo incluye a Kei, un personaje anime con sincronización labial gracias a Live2D, y pronto añadirá avatares realistas de Trulience, HeyGen y Tavus.

Revisa el código de ejemplo para Live2D.

divider
divider

Image

Diarización de voz — Detección y etiquetado de hablantes en tiempo real. El juego "Who Likes What" muestra un caso de uso interactivo.

Código de ejemplo

divider
divider

Image

Llamada SIP — Extensión SIP que habilita llamadas telefónicas impulsadas por TEN.

Código de ejemplo

divider
divider

Image

Transcripción — Herramienta de transcripción que convierte audio en texto.

Código de ejemplo

divider
divider

Image

ESP32-S3 Korvo V3 — Ejecuta el ejemplo de TEN Agent en la placa de desarrollo Espressif ESP32-S3 Korvo V3 para integrar comunicación impulsada por LLM con hardware.

Consulta la guía de integración para conocer más.

Inicio rápido con los ejemplos de agente

Entorno local

Paso ⓵ - Requisitos previos

Categoría Requisitos
Credenciales • Agora App ID y App Certificate (minutos gratuitos mensuales)• Clave de API de OpenAI (cualquier LLM compatible con el protocolo de OpenAI)• ASR de Deepgram (créditos gratuitos al registrarte)• TTS de ElevenLabs (créditos gratuitos al registrarte)
Instalación • Docker / Docker Compose• Node.js (LTS) v18
Requisitos mínimos del sistema • CPU ≥ 2 núcleos• RAM ≥ 4 GB

divider
divider

Paso ⓶ - Compila los ejemplos dentro de una VM

1. Clona el repositorio, entra en `ai_agents` y crea un `.env` a partir de `.env.example`

bash
cd ai_agents
cp ./.env.example ./.env

2. Configura el Agora App ID y App Certificate en `.env`

bash
AGORA_APP_ID=
AGORA_APP_CERTIFICATE=

# Ejecutar el ejemplo predeterminado del asistente de voz
# Deepgram (requerido para STT)
DEEPGRAM_API_KEY=your_deepgram_api_key_here

# OpenAI (requerido para el modelo de lenguaje)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4o

# ElevenLabs (requerido para TTS)
ELEVENLABS_TTS_KEY=your_elevenlabs_api_key_here

3. Inicia los contenedores de desarrollo del agente

bash
docker compose up -d

4. Entra en el contenedor

bash
docker exec -it ten_agent_dev bash

5. Compila el agente con el ejemplo predeterminado (≈5‑8 min)

En la carpeta agents/examples encontrarás más muestras.
Empieza con una de estas opciones:

bash
# Usa el asistente de voz encadenado
cd agents/examples/voice-assistant

# O usa el asistente voz-a-voz en tiempo real
cd agents/examples/voice-assistant-realtime

6. Inicia el servidor web

Ejecuta task install antes del primer inicio y después de cambiar las dependencias o el código fuente de Go. Este comando instala las dependencias de TEN, Python y frontend, y compila el servidor API de Go. Los cambios que solo afectan a Python no requieren reinstalación.

bash
task install
task run

7. Accede al agente

Cuando el ejemplo esté en marcha podrás usar estas interfaces:

localhost:49483 localhost:3000

divider
divider

Paso ⓷ - Personaliza tu ejemplo de agente

  1. Abre localhost:49483.
  2. Haz clic derecho en las extensiones STT, LLM y TTS.
  3. Rellena sus propiedades con las API keys correspondientes.
  4. Envía los cambios; el ejemplo actualizado aparecerá en localhost:3000.

divider
divider

Ejecuta una app de transcripción desde TEN Manager sin Docker (Beta)

TEN también ofrece una app de transcripción que puedes ejecutar en TEN Manager sin usar Docker.

Consulta la guía de inicio rápido para más detalles.

divider
divider

Codespaces

GitHub ofrece Codespaces gratuitos para cada repositorio. Puedes ejecutar los ejemplos de agente allí sin usar Docker, y normalmente inicia más rápido que un entorno local con contenedores.

Consulta esta guía para obtener más detalles.

Auto-hospedaje de los ejemplos de agente

Implementar con Docker

Cuando personalices tu agente (ya sea con TMAN Designer o editando property.json), crea una imagen de Docker lista para producción y despliega tu servicio.

Publicar como imagen de Docker

Nota: Ejecuta estos comandos fuera de cualquier contenedor Docker.

Compilar la imagen

bash
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .

Ejecutar

bash
docker run --rm -it --env-file .env -p 3000:3000 example-app

divider
divider

Implementar en otros servicios en la nube

Puedes dividir el despliegue en dos partes si deseas alojar TEN en proveedores como Vercel o Netlify.

  1. Ejecuta el backend de TEN en cualquier plataforma preparada para contenedores (una VM con Docker, Fly.io, Render, ECS, Cloud Run, etc.). Usa la imagen de ejemplo sin modificar y expón el puerto 8080.
  2. Despliega solo el frontend en Vercel o Netlify. Apunta la raíz del proyecto a ai_agents/agents/examples/<example>/frontend, ejecuta pnpm install (o bun install), luego pnpm build (o bun run build) y conserva el directorio de salida .next predeterminado.
  3. Configura las variables de entorno en el panel de tu hosting. AGENT_SERVER_URL debe apuntar al backend y añade cualquier clave NEXT_PUBLIC_* necesaria (por ejemplo, credenciales de Agora visibles para el navegador).
  4. Asegúrate de que tu backend acepte solicitudes desde el origen del frontend, ya sea mediante CORS abierto o usando el middleware proxy incluido.

Con esta arquitectura, el backend gestiona los procesos de larga duración y el frontend alojado solo reenvía el tráfico al backend.

Mantente al día

Recibe notificaciones instantáneas sobre nuevas versiones y actualizaciones. Tu apoyo nos ayuda a seguir mejorando TEN.

Image

Ecosistema TEN

Proyecto Vista previa
️TEN FrameworkMarco de código abierto para agentes conversacionales.
TEN VADDetector de actividad de voz (VAD) en streaming, ligero y de baja latencia.
️TEN Turn DetectionPermite diálogo full-duplex mediante detección de turnos.
TEN Agent ExamplesCasos de uso impulsados por TEN.
TEN PortalSitio oficial con documentación y blog.

Preguntas

TEN Framework también está disponible en plataformas de preguntas y respuestas impulsadas por IA. Ofrecen soporte multilingüe para todo, desde la configuración básica hasta la implementación avanzada.

Servicio Enlace
DeepWiki Ask DeepWiki
ReadmeX ReadmeX

Cómo contribuir

¡Toda forma de colaboración de código abierto es bienvenida! Ya sea que corrijas bugs, agregues funciones, mejores la documentación o compartas ideas, tus aportes ayudan a impulsar herramientas de IA personalizadas. Revisa los Issues y Projects de GitHub para encontrar oportunidades y mostrar tus habilidades. ¡Construyamos juntas y juntos algo increíble!

[!TIP]

Agradecemos todo tipo de contribuciones 🙏

Acompáñanos a mejorar TEN. Cada PR, issue o guía suma. Comparte tus proyectos con TEN Agent en redes sociales para inspirar a la comunidad.

Ponte en contacto con la persona mantenedora @elliotchen200 en 𝕏 o @cyfyifanchen en GitHub para recibir novedades, debatir ideas y explorar colaboraciones.

divider
divider

Personas contribuidoras

TEN

Guía de contribución

¡Contribuye cuando quieras! Lee primero la guía de contribución.

divider
divider

Licencia

  1. Todo TEN Framework, salvo los directorios listados más abajo, se publica bajo la licencia Apache 2.0 con restricciones adicionales. Consulta el archivo LICENSE en la raíz del proyecto.
  2. Los componentes dentro de packages se liberan bajo Apache License 2.0. Cada paquete contiene su propio archivo LICENSE con los detalles.
  3. Las dependencias de terceros que usa TEN Framework se describen en la carpeta third_party.


README FR (`docs/README-FR.md`)

Table des matières

Bienvenue chez TEN

TEN est un framework open source pour créer des agents conversationnels vocaux pilotés par l’IA.

L’écosystème TEN comprend TEN Framework, les Exemples d’agents, VAD, Turn Detection et Portal.

Canal communautaire Objectif
Follow on X Suivez TEN Framework sur X pour connaître les nouveautés et annonces
Discord TEN Community Rejoignez notre communauté Discord pour échanger avec d’autres développeurs
Follow on LinkedIn Abonnez-vous sur LinkedIn afin de recevoir nos actualités
Hugging Face Space Découvrez nos espaces et modèles sur Hugging Face
WeChat Rejoignez le groupe WeChat pour discuter avec la communauté chinoise

Exemples d’agents

Image

Assistant vocal polyvalent — Assistant temps réel, basse latence et haute qualité, extensible avec des modules de mémoire, de VAD, de détection de tours, etc.

Consultez le code d’exemple pour en savoir plus.

divider
divider

Image

Doodler — Un tableau de gribouillage qui transforme des prompts parlés ou saisis en croquis simples dessinés à la main, avec une palette de crayons et un dessin en temps réel.

Code d’exemple

divider
divider

Image

Avatars avec synchronisation labiale — Compatible avec plusieurs fournisseurs d’avatars. La démo met en scène Kei, un personnage animé avec synchronisation labiale Live2D, et proposera bientôt des avatars réalistes de Trulience, HeyGen et Tavus.

Voir le code d’exemple Live2D.

divider
divider

Image

Diarisation vocale — Détection et étiquetage des locuteurs en temps réel. Le jeu "Who Likes What" illustre un cas d’usage interactif.

Code d’exemple

divider
divider

Image

Appels SIP — Extension SIP qui permet d’effectuer des appels téléphoniques propulsés par TEN.

Code d’exemple

divider
divider

Image

Transcription — Outil de transcription qui convertit la voix en texte.

Code d’exemple

divider
divider

Image

ESP32-S3 Korvo V3 — Fait tourner un exemple TEN Agent sur la carte de développement Espressif ESP32-S3 Korvo V3 pour relier communication LLM et matériel.

Voir le guide d’intégration pour plus d’informations.

Démarrage rapide avec les exemples d’agents

En local

Étape ⓵ - Prérequis

Catégorie Exigences
Clés • Agora App ID et App Certificate (minutes gratuites chaque mois)• Clé API de OpenAI (n’importe quel LLM compatible OpenAI)• ASR Deepgram (crédits offerts à l’inscription)• TTS ElevenLabs (crédits offerts à l’inscription)
Installation • Docker / Docker Compose• Node.js (LTS) v18
Configuration minimale • CPU ≥ 2 cœurs• RAM ≥ 4 Go

divider
divider

Étape ⓶ - Compiler les exemples dans une VM

1. Clonez le dépôt, placez-vous dans `ai_agents` et créez `.env` à partir de `.env.example`

bash
cd ai_agents
cp ./.env.example ./.env

2. Configurez Agora App ID et App Certificate dans `.env`

bash
AGORA_APP_ID=
AGORA_APP_CERTIFICATE=

# Exécuter l’exemple d’assistant vocal par défaut
# Deepgram (requis pour la transcription)
DEEPGRAM_API_KEY=your_deepgram_api_key_here

# OpenAI (requis pour le modèle de langage)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4o

# ElevenLabs (requis pour la synthèse vocale)
ELEVENLABS_TTS_KEY=your_elevenlabs_api_key_here

3. Lancez les conteneurs de développement

bash
docker compose up -d

4. Entrez dans le conteneur

bash
docker exec -it ten_agent_dev bash

5. Compilez l’agent avec l’exemple par défaut (~5-8 min)

D’autres exemples sont disponibles dans agents/examples.
Commencez par l’une des options suivantes :

bash
# Assistant vocal chaîné
cd agents/examples/voice-assistant

# Assistant voix-à-voix temps réel
cd agents/examples/voice-assistant-realtime

6. Démarrez le serveur web

Exécutez task install avant le premier lancement et après toute modification des dépendances ou du code source Go. Cette commande installe les dépendances TEN, Python et frontend, puis compile le serveur API Go. Les modifications portant uniquement sur Python ne nécessitent pas de nouvelle installation.

bash
task install
task run

7. Accédez à l’agent

Une fois l’exemple démarré, ces interfaces sont disponibles :

localhost:49483 localhost:3000

divider
divider

Étape ⓷ - Personnaliser votre exemple

  1. Ouvrez localhost:49483.
  2. Cliquez droit sur les extensions STT, LLM et TTS.
  3. Renseignez les clés API correspondantes.
  4. Validez : la mise à jour apparaît sur localhost:3000.

divider
divider

Exécuter une application de transcription depuis TEN Manager sans Docker (Beta)

TEN propose aussi une application de transcription que vous pouvez lancer dans TEN Manager sans utiliser Docker.

Consultez le guide de démarrage rapide pour en savoir plus.

divider
divider

Codespaces

GitHub fournit des Codespaces gratuits par dépôt. Vous pouvez exécuter les exemples d’agents sans Docker, avec des temps de démarrage souvent plus courts qu’en local.

Consultez ce guide pour plus d’informations.

Auto-hébergement des exemples d’agents

Déployer avec Docker

Après avoir personnalisé votre agent (via TMAN Designer ou en modifiant property.json), générez une image Docker prête pour la production et déployez votre service.

Publier en image Docker

Remarque : exécutez ces commandes hors de tout conteneur Docker.

Construire l’image

bash
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .

Exécuter

bash
docker run --rm -it --env-file .env -p 3000:3000 example-app

divider
divider

Déployer sur d’autres services cloud

Divisez le déploiement en deux parties pour héberger TEN sur des plateformes comme Vercel ou Netlify.

  1. Exécutez le backend TEN sur une plateforme compatible conteneurs (VM Docker, Fly.io, Render, ECS, Cloud Run, etc.). Utilisez l’image fournie et exposez le port 8080.
  2. Déployez uniquement le frontend sur Vercel ou Netlify. Pointez la racine du projet vers ai_agents/agents/examples/<example>/frontend, lancez pnpm install (ou bun install) puis pnpm build (ou bun run build) et conservez le répertoire .next par défaut.
  3. Dans le tableau de bord d’hébergement, définissez AGENT_SERVER_URL vers l’URL du backend et ajoutez les variables NEXT_PUBLIC_* nécessaires (comme les identifiants Agora côté navigateur).
  4. Autorisez le frontend à contacter le backend via CORS ouvert ou le middleware proxy intégré.

Ainsi, le backend gère les workers longue durée tandis que le frontend hébergé achemine simplement les requêtes.

Restez informé·e

Recevez instantanément les nouvelles versions et les mises à jour. Votre soutien nous aide à faire grandir TEN.

Image

Écosystème TEN

Projet Aperçu
️TEN FrameworkFramework open source pour agents conversationnels.
TEN VADDétecteur d’activité vocale (VAD) léger et à faible latence.
️TEN Turn DetectionPermet des dialogues full-duplex grâce à la détection de tours.
TEN Agent ExamplesCas d'usage construits avec TEN.
TEN PortalSite officiel avec documentation et blog.

Questions

TEN Framework est présent sur des plateformes de questions/réponses alimentées par l’IA. Elles fournissent des réponses multilingues, de la configuration de base aux cas avancés.

Service Lien
DeepWiki Ask DeepWiki
ReadmeX ReadmeX

Contribuer

Nous accueillons toute forme de collaboration open source ! Corrections de bugs, nouvelles fonctionnalités, documentation ou idées : vos contributions font progresser les outils d’IA personnalisés. Consultez les Issues et Projects GitHub pour trouver des sujets sur lesquels intervenir et montrer votre expertise. Ensemble, faisons grandir TEN !

[!TIP]

Toutes les contributions comptent 🙏

Aidez-nous à améliorer TEN. Du code à la doc, chaque partage est précieux. Publiez vos projets TEN Agent sur les réseaux pour inspirer la communauté.

Contactez un mainteneur, @elliotchen200 sur 𝕏 ou @cyfyifanchen sur GitHub, pour suivre les actualités, échanger et collaborer.

divider
divider

Contributrices et contributeurs

TEN

Guide de contribution

Les contributions sont les bienvenues ! Lisez d’abord le guide de contribution.

divider
divider

Licence

  1. L’ensemble de TEN Framework (hors dossiers listés ci-dessous) est publié sous licence Apache 2.0 avec restrictions additionnelles. Voir le fichier LICENSE à la racine.
  2. Les composants du dossier packages sont publiés sous Apache 2.0. Référez-vous au fichier LICENSE propre à chaque package.
  3. Les bibliothèques tierces utilisées par TEN Framework sont référencées dans le dossier third_party.


README IT (`docs/README-IT.md`)

Indice

Benvenuto in TEN

TEN è un framework open source per creare agenti vocali conversazionali.

L’ecosistema TEN comprende TEN Framework, Esempi di agenti, VAD, Turn Detection e Portal.

Canale della community Scopo
Follow on X Segui TEN Framework su X per aggiornamenti e annunci
Discord TEN Community Unisciti alla community Discord per confrontarti con altri sviluppatori
Follow on LinkedIn Segui TEN Framework su LinkedIn per non perdere nessuna novità
Hugging Face Space Esplora i nostri spazi e modelli su Hugging Face
WeChat Entra nel gruppo WeChat per parlare con la community cinese

Esempi di agenti

Image

Assistente vocale multiuso — Assistente in tempo reale, a bassa latenza e alta qualità, estendibile con memoria, VAD, rilevamento dei turni e altre estensioni.

Consulta il codice di esempio per maggiori dettagli.

divider
divider

Image

Doodler — Una lavagna di scarabocchi che trasforma prompt vocali o digitati in semplici schizzi a mano, con una palette di pastelli e disegno in tempo reale.

Codice di esempio

divider
divider

Image

Avatar con lip sync — Supporta diversi provider di avatar. La demo mostra Kei, un personaggio anime con sincronizzazione labiale Live2D, e presto includerà avatar realistici di Trulience, HeyGen e Tavus.

Guarda il codice di esempio Live2D.

divider
divider

Image

Diarizzazione vocale — Rilevamento e etichettatura dei parlanti in tempo reale. Il gioco "Who Likes What" mostra un caso d’uso interattivo.

Codice di esempio

divider
divider

Image

Chiamata SIP — Estensione SIP che abilita chiamate telefoniche gestite da TEN.

Codice di esempio

divider
divider

Image

Trascrizione — Strumento che trascrive l’audio in testo.

Codice di esempio

divider
divider

Image

ESP32-S3 Korvo V3 — Esegue un esempio di TEN Agent sulla scheda di sviluppo Espressif ESP32-S3 Korvo V3 per portare comunicazioni basate su LLM sull’hardware.

Consulta la guida di integrazione per ulteriori informazioni.

Guida rapida agli esempi di agenti

Ambiente locale

Passaggio ⓵ - Prerequisiti

Categoria Requisiti
Chiavi • Agora App ID e App Certificate (minuti gratuiti ogni mese)• Chiave API OpenAI (qualsiasi LLM compatibile con OpenAI)• ASR Deepgram (crediti gratuiti alla registrazione)• TTS ElevenLabs (crediti gratuiti alla registrazione)
Installazione • Docker / Docker Compose• Node.js (LTS) v18
Requisiti minimi • CPU ≥ 2 core• RAM ≥ 4 GB

divider
divider

Passaggio ⓶ - Compila gli esempi nella VM

1. Clona il repo, entra in `ai_agents` e crea `.env` da `.env.example`

bash
cd ai_agents
cp ./.env.example ./.env

2. Configura Agora App ID e App Certificate in `.env`

bash
AGORA_APP_ID=
AGORA_APP_CERTIFICATE=

# Esegui l’esempio predefinito dell’assistente vocale
# Deepgram (necessario per STT)
DEEPGRAM_API_KEY=your_deepgram_api_key_here

# OpenAI (necessario per il modello linguistico)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4o

# ElevenLabs (necessario per TTS)
ELEVENLABS_TTS_KEY=your_elevenlabs_api_key_here

3. Avvia i container di sviluppo

bash
docker compose up -d

4. Entra nel container

bash
docker exec -it ten_agent_dev bash

5. Compila l’agente con l’esempio predefinito (≈5-8 min)

Trovi altre demo nella cartella agents/examples.
Inizia da una di queste:

bash
# Assistente vocale concatenato
cd agents/examples/voice-assistant

# Assistente voce-a-voce in tempo reale
cd agents/examples/voice-assistant-realtime

6. Avvia il server web

Esegui task install prima del primo avvio e dopo aver modificato le dipendenze o il codice sorgente Go. Questo comando installa le dipendenze TEN, Python e frontend e compila il server API Go. Le modifiche al solo codice Python non richiedono una nuova installazione.

bash
task install
task run

7. Accedi all’agente

Quando l’esempio è in esecuzione puoi usare queste interfacce:

localhost:49483 localhost:3000

divider
divider

Passaggio ⓷ - Personalizza l’esempio

  1. Apri localhost:49483.
  2. Fai clic con il tasto destro sulle estensioni STT, LLM e TTS.
  3. Inserisci le relative API key.
  4. Dopo aver salvato, la versione aggiornata sarà visibile su localhost:3000.

divider
divider

Esegui un’app di trascrizione da TEN Manager senza Docker (Beta)

TEN offre anche un’app di trascrizione che puoi eseguire in TEN Manager senza usare Docker.

Consulta la guida rapida per ulteriori dettagli.

divider
divider

Codespaces

GitHub offre Codespaces gratuiti per ogni repository. Puoi eseguire gli esempi senza Docker e, in genere, l’avvio è più rapido rispetto all’ambiente locale basato su container.

Consulta questa guida per i dettagli.

Auto-hosting degli esempi

Distribuire con Docker

Dopo aver personalizzato l’agente (con TMAN Designer o modificando property.json), crea un’immagine Docker di release e distribuiscila.

Pubblicare come immagine Docker

Nota: esegui i comandi al di fuori di qualsiasi container.

Build dell’immagine

bash
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .

Esecuzione

bash
docker run --rm -it --env-file .env -p 3000:3000 example-app

divider
divider

Distribuire su altri servizi cloud

Puoi dividere il deployment in due parti quando ospiti TEN su piattaforme come Vercel o Netlify.

  1. Esegui il backend TEN su una piattaforma compatibile con container (VM Docker, Fly.io, Render, ECS, Cloud Run, ecc.). Usa l’immagine di esempio senza modificarla ed esponi la porta 8080.
  2. Distribuisci solo il frontend su Vercel o Netlify. Imposta la radice del progetto su ai_agents/agents/examples/<example>/frontend, esegui pnpm install (o bun install) e poi pnpm build (o bun run build), mantenendo la cartella di output .next predefinita.
  3. Configura le variabili di ambiente dal pannello del provider: AGENT_SERVER_URL deve puntare al backend e aggiungi le chiavi NEXT_PUBLIC_* necessarie (per esempio le credenziali Agora esposte al browser).
  4. Consenti al backend di accettare richieste dall’origine del frontend tramite CORS aperto o usando il middleware proxy incluso.

In questo modo il backend gestisce i processi di lunga durata e il frontend hostato instrada semplicemente il traffico API.

Rimani aggiornato

Ricevi notifiche immediate su nuove release e aggiornamenti. Il tuo supporto ci aiuta a migliorare TEN!

Image

Ecosistema TEN

Progetto Anteprima
️TEN FrameworkFramework open source per agenti conversazionali.
TEN VADRilevatore di attività vocale in streaming, leggero e a bassa latenza.
️TEN Turn DetectionAbilita dialoghi full-duplex tramite rilevamento dei turni.
TEN Agent ExamplesCasi d'uso costruiti con TEN.
TEN PortalSito ufficiale con documentazione e blog.

Domande

TEN Framework è presente anche su piattaforme di Q&A alimentate dall’IA. Offrono risposte multilingue, dalla configurazione di base agli scenari avanzati.

Servizio Link
DeepWiki Ask DeepWiki
ReadmeX ReadmeX

Contribuire

Accogliamo qualsiasi forma di collaborazione open source! Bugfix, nuove funzionalità, documentazione o idee: ogni contributo aiuta a far crescere strumenti di IA personalizzati. Dai un’occhiata a Issues e Projects su GitHub per trovare attività su cui lavorare e mostrare le tue competenze. Costruiamo TEN insieme!

[!TIP]

Ogni contributo è importante 🙏

Aiutaci a migliorare TEN: dal codice alla documentazione, tutto conta. Condividi i tuoi progetti TEN Agent sui social per ispirare altre persone.

Contatta un maintainer — @elliotchen200 su 𝕏 o @cyfyifanchen su GitHub — per aggiornamenti, discussioni e collaborazioni.

divider
divider

Contributor del codice

TEN

Linee guida per contribuire

Le contribuzioni sono benvenute! Leggi prima le linee guida per contribuire.

divider
divider

Licenza

  1. L’intero TEN Framework (esclusi i folder elencati qui sotto) è rilasciato sotto Apache License 2.0 con restrizioni aggiuntive. Consulta il file LICENSE nella root del progetto.
  2. I componenti nella directory packages sono distribuiti sotto Apache License 2.0. Ogni package contiene il proprio file LICENSE.
  3. Le librerie di terze parti utilizzate da TEN Framework sono elencate nella cartella third_party.


README JP (`docs/README-JP.md`)

目次

TEN へようこそ

TEN は音声会話型 AI エージェント向けのオープンソースフレームワークです。

TEN エコシステム には TEN Framework、エージェント事例、VAD、Turn Detection、Portal が含まれます。

コミュニティ 目的
Follow on X X で TEN Framework をフォローして最新情報をチェック
Discord TEN Community Discord コミュニティに参加し、開発者同士で交流
Follow on LinkedIn LinkedIn で TEN Framework をフォローし、ニュースを受け取る
Hugging Face Space Hugging Face コミュニティでスペースやモデルを探索
WeChat 中国語コミュニティ向けの WeChat グループに参加

エージェント事例

Image

多目的ボイスアシスタント — 低レイテンシ・高品質のリアルタイムアシスタント。メモリ、VAD、ターン検出 などの拡張機能を追加できます。

詳細は サンプルコード を参照してください。

divider
divider

Image

Doodler — 音声またはテキストのプロンプトを手描き風のシンプルなスケッチに変換するお絵かきボード。クレヨンのパレットとリアルタイム描画を備えています。

サンプルコード

divider
divider

Image

リップシンク対応アバター — 複数のアバタープロバイダーに対応。デモでは Live2D のリップシンクを備えたアニメキャラクター Kei を紹介し、今後 Trulience、HeyGen、Tavus のリアルアバターにも対応予定です。

Live2D 用サンプルコード

divider
divider

Image

話者分離(Diarization) — 話者をリアルタイムで検出・ラベル付けします。ゲーム「Who Likes What」でインタラクティブな活用例を紹介しています。

サンプルコード

divider
divider

Image

SIP 通話 — TEN で電話を実現する SIP 拡張機能です。

サンプルコード

divider
divider

Image

文字起こし — 音声をテキストへ変換するトランスクリプションツール。

サンプルコード

divider
divider

Image

ESP32-S3 Korvo V3 — Espressif ESP32-S3 Korvo V3 開発ボード上で TEN Agent のサンプルを動作させ、LLM ベースのコミュニケーションをハードウェアに組み込みます。

統合ガイド を参照してください。

エージェント事例のクイックスタート

ローカル環境

ステップ ⓵ - 事前準備

カテゴリ 必要なもの
キー類 • Agora App ID と App Certificate(毎月無料分あり)• OpenAI API キー(OpenAI 互換の任意の LLM)• Deepgram ASR(登録で無料クレジット)• ElevenLabs TTS(登録で無料クレジット)
インストール • Docker / Docker Compose• Node.js (LTS) v18
最小システム要件 • CPU 2 コア以上• RAM 4 GB 以上

divider
divider

ステップ ⓶ - VM 内でサンプルをビルド

1. リポジトリをクローンし、`ai_agents` に移動して `.env.example` から `.env` を作成

bash
cd ai_agents
cp ./.env.example ./.env

2. `.env` に Agora App ID と App Certificate を設定

bash
AGORA_APP_ID=
AGORA_APP_CERTIFICATE=

# デフォルトのボイスアシスタント例を実行
# Deepgram(音声認識に必須)
DEEPGRAM_API_KEY=your_deepgram_api_key_here

# OpenAI(言語モデルに必須)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4o

# ElevenLabs(音声合成に必須)
ELEVENLABS_TTS_KEY=your_elevenlabs_api_key_here

3. 開発用コンテナを起動

bash
docker compose up -d

4. コンテナに入る

bash
docker exec -it ten_agent_dev bash

5. デフォルトサンプルでエージェントをビルド(約 5〜8 分)

agents/examples ディレクトリには他のサンプルもあります。
以下のいずれかで開始できます:

bash
# チェーン型ボイスアシスタント
cd agents/examples/voice-assistant

# リアルタイムの音声対音声アシスタント
cd agents/examples/voice-assistant-realtime

6. Web サーバーを起動

初回起動前、および依存関係や Go のソースコードを変更した後は task install を実行してください。このコマンドは TEN、Python、フロントエンドの依存関係をインストールし、Go API サーバーをビルドします。Python のソースコードのみを変更した場合、再インストールは不要です。

bash
task install
task run

7. エージェントにアクセス

サンプルが起動すると次の UI を利用できます。

localhost:49483 localhost:3000

divider
divider

ステップ ⓷ - サンプルをカスタマイズ

  1. localhost:49483 を開く。
  2. STT・LLM・TTS 拡張を右クリック。
  3. プロパティで対応する API キーを入力。
  4. 変更を保存すると localhost:3000 で更新内容を確認できます。

divider
divider

Docker なしで TEN Manager からトランスクリプションアプリを実行 (Beta)

TEN には、Docker を使わずに TEN Manager から実行できるトランスクリプションアプリも用意されています。

詳しくはクイックスタートガイドをご覧ください。

divider
divider

Codespaces

GitHub はリポジトリごとに無料の Codespaces を提供しています。Docker を使わずにエージェント事例を実行でき、通常ローカル環境よりも起動が速くなります。

詳細はこちらのガイドをご覧ください。

エージェント事例のセルフホスティング

Docker でデプロイ

TMAN Designer でカスタマイズするか property.json を編集したら、本番用の Docker イメージを作成してサービスをデプロイしましょう。

Docker イメージとして公開

注意: 以下のコマンドは Docker コンテナの外で実行してください。

イメージをビルド

bash
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .

実行

bash
docker run --rm -it --env-file .env -p 3000:3000 example-app

divider
divider

その他のクラウドサービスへデプロイ

TEN を Vercel や Netlify などでホストする場合、バックエンドとフロントエンドを分けて配置できます。

  1. Docker 対応の任意のプラットフォーム(Docker が動く VM、Fly.io、Render、ECS、Cloud Run など)で TEN バックエンドを実行。用意されたサンプルイメージをそのまま使い、ポート 8080 を公開します。
  2. フロントエンドのみ Vercel / Netlify にデプロイします。プロジェクトルートを ai_agents/agents/examples/<example>/frontend に設定し、pnpm install(または bun install)→ pnpm build(または bun run build)を実行し、デフォルトの .next 出力を保持します。
  3. ホスティング側の環境変数で AGENT_SERVER_URL をバックエンド URL に設定し、必要な NEXT_PUBLIC_* キー(ブラウザで使う Agora 資格情報など)を追加します。
  4. CORS を開放する、または内蔵のプロキシミドルウェアを使うなどして、フロントエンドのオリジンからバックエンドへのリクエストを許可します。

この構成では、バックエンドがワーカー処理を担い、ホストしたフロントエンドは API リクエストを転送するだけで済みます。

最新情報

新しいリリースやアップデートを即座に受け取れます。あなたのサポートが TEN を成長させます!

Image

TEN エコシステム

プロジェクト プレビュー
️TEN Framework会話型 AI エージェント向けオープンソースフレームワーク。
TEN VAD低遅延・軽量・高性能なストリーミング音声活動検出。
️TEN Turn Detection全二重会話を可能にするターン検出。
TEN Agent ExamplesTEN を使ったユースケース集。
TEN Portal公式サイト。ドキュメントとブログを掲載。

質問

TEN Framework は AI 駆動の Q&A プラットフォームにも掲載されています。マルチリンガルでの検索が可能で、初期設定から高度な実装までサポートします。

サービス リンク
DeepWiki Ask DeepWiki
ReadmeX ReadmeX

コントリビュート

バグ修正、機能追加、ドキュメント改善、アイデア共有など、あらゆる OSS での協力を歓迎します。GitHub の Issues や Projects をチェックして活躍の場を見つけ、スキルを発揮してください。一緒に TEN をより良いものにしましょう!

[!TIP]

すべてのコントリビューションに感謝します 🙏

コードでもドキュメントでも、どんな貢献も力になります。TEN Agent プロジェクトを SNS で紹介し、コミュニティを盛り上げましょう。

メンテナー @elliotchen200(𝕏)や @cyfyifanchen(GitHub)に連絡すると、最新情報や議論、コラボの機会を得られます。

divider
divider

コードコントリビューター

TEN

貢献ガイドライン

いつでも歓迎です!まずは貢献ガイドラインをご確認ください。

divider
divider

ライセンス

  1. 下記のディレクトリを除き、TEN Framework 全体は追加条件付きの Apache License 2.0 で配布されています。プロジェクトルートの LICENSE を参照してください。
  2. packages 配下のコンポーネントも Apache License 2.0 で提供されます。各パッケージの LICENSE ファイルをご確認ください。
  3. TEN Framework が利用するサードパーティライブラリは third_party ディレクトリで一覧化されています。


README KR (`docs/README-KR.md`)

목차

TEN 소개

TEN은 음성 대화형 AI 에이전트를 위한 오픈소스 프레임워크입니다.

TEN 에코시스템에는 TEN Framework, 에이전트 예시, VAD, Turn Detection, Portal 이 포함됩니다.

커뮤니티 채널 설명
Follow on X X에서 TEN Framework 소식을 받아보세요
Discord TEN Community Discord 커뮤니티에 참여해 개발자들과 교류하세요
Follow on LinkedIn LinkedIn에서 업데이트와 공지를 확인하세요
Hugging Face Space Hugging Face 커뮤니티에서 스페이스와 모델을 살펴보세요
WeChat 중국 커뮤니티와 대화할 수 있는 WeChat 그룹

에이전트 예시

Image

멀티 퍼포즈 보이스 어시스턴트 — 저지연·고품질 실시간 어시스턴트로, 메모리, VAD, 턴 감지 등으로 확장할 수 있습니다.

자세한 내용은 예시 코드를 확인하세요.

divider
divider

Image

Doodler — 음성 또는 텍스트 프롬프트를 손그림 스케치로 바꾸는 낙서 보드로, 크레용 팔레트와 실시간 드로잉을 제공합니다.

예시 코드

divider
divider

Image

립싱크 아바타 — 다양한 아바타 공급업체를 지원합니다. 데모에서는 Live2D 립싱크를 갖춘 애니메이션 캐릭터 Kei를 소개하며, 곧 Trulience, HeyGen, Tavus의 실사 아바타도 지원할 예정입니다.

Live2D 예시 코드

divider
divider

Image

음성 화자 분리 — 실시간으로 화자를 감지하고 라벨링합니다. "Who Likes What" 게임에서 인터랙티브한 사용 사례를 확인할 수 있습니다.

예시 코드

divider
divider

Image

SIP 통화 — TEN 기반 전화 통화를 가능하게 하는 SIP 확장입니다.

예시 코드

divider
divider

Image

전사(Transcription) — 오디오를 텍스트로 변환하는 전사 도구입니다.

예시 코드

divider
divider

Image

ESP32-S3 Korvo V3 — Espressif ESP32-S3 Korvo V3 개발 보드에서 TEN Agent 예시를 실행해 LLM 기반 커뮤니케이션을 하드웨어와 통합합니다.

통합 가이드를 참고하세요.

에이전트 예시 빠른 시작

로컬 환경

단계 ⓵ - 준비 사항

구분 요구 사항
키/토큰 • Agora App ID, App Certificate (매월 무료 분 제공)• OpenAI API 키 (OpenAI 호환 LLM)• Deepgram ASR (회원가입 시 무료 크레딧)• ElevenLabs TTS (회원가입 시 무료 크레딧)
설치 • Docker / Docker Compose• Node.js (LTS) v18
최소 사양 • CPU 2코어 이상• RAM 4GB 이상

divider
divider

단계 ⓶ - VM 안에서 예시 빌드

1. 저장소를 클론하고 `ai_agents`로 이동한 뒤 `.env.example`로부터 `.env` 생성

bash
cd ai_agents
cp ./.env.example ./.env

2. `.env`에 Agora App ID / App Certificate 설정

bash
AGORA_APP_ID=
AGORA_APP_CERTIFICATE=

# 기본 보이스 어시스턴트 예시 실행
# Deepgram (STT 필수)
DEEPGRAM_API_KEY=your_deepgram_api_key_here

# OpenAI (LLM 필수)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4o

# ElevenLabs (TTS 필수)
ELEVENLABS_TTS_KEY=your_elevenlabs_api_key_here

3. 개발용 컨테이너 시작

bash
docker compose up -d

4. 컨테이너 접속

bash
docker exec -it ten_agent_dev bash

5. 기본 예시로 에이전트 빌드 (약 5~8분)

agents/examples 폴더에서 다른 샘플도 확인할 수 있습니다.
다음 중 하나부터 시작하세요:

bash
# 체이닝형 보이스 어시스턴트
cd agents/examples/voice-assistant

# 실시간 음성-음성 어시스턴트
cd agents/examples/voice-assistant-realtime

6. 웹 서버 시작

처음 실행하기 전과 종속성 또는 Go 소스 코드를 변경한 후에는 task install을 실행하세요. 이 명령은 TEN, Python, 프런트엔드 종속성을 설치하고 Go API 서버를 빌드합니다. Python 소스 코드만 변경한 경우에는 다시 설치할 필요가 없습니다.

bash
task install
task run

7. 에이전트 접속

예시가 실행되면 아래 인터페이스를 이용할 수 있습니다.

localhost:49483 localhost:3000

divider
divider

단계 ⓷ - 예시 커스터마이징

  1. localhost:49483에 접속합니다.
  2. STT, LLM, TTS 확장을 우클릭합니다.
  3. 속성 창에서 해당 API 키를 입력합니다.
  4. 저장 후 localhost:3000에서 변경된 결과를 확인합니다.

divider
divider

Docker 없이 TEN Manager에서 전사 앱 실행 (Beta)

TEN은 Docker 없이 TEN Manager에서 바로 실행할 수 있는 전사 앱도 제공합니다.

자세한 내용은 빠른 시작 가이드를 확인하세요.

divider
divider

Codespaces

GitHub는 저장소마다 무료 Codespaces를 제공합니다. Docker 없이도 에이전트 예시를 실행할 수 있으며, 일반적으로 로컬 Docker 환경보다 빠르게 시작됩니다.

자세한 내용은 이 가이드를 참고하세요.

에이전트 예시 셀프 호스팅

Docker 배포

TMAN Designer 또는 property.json 수정으로 에이전트를 커스터마이징했다면, 서비스용 Docker 릴리스 이미지를 만들어 배포할 수 있습니다.

Docker 이미지로 배포

주의: 아래 명령은 Docker 컨테이너 밖에서 실행하세요.

이미지 빌드

bash
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .

실행

bash
docker run --rm -it --env-file .env -p 3000:3000 example-app

divider
divider

기타 클라우드 제공업체 배포

TEN을 Vercel이나 Netlify 같은 플랫폼에 호스팅할 때는 백엔드와 프런트엔드를 분리할 수 있습니다.

  1. Docker를 지원하는 플랫폼(도커 가능한 VM, Fly.io, Render, ECS, Cloud Run 등)에서 TEN 백엔드를 실행합니다. 예시 이미지를 그대로 사용하고 포트 8080을 개방하세요.
  2. 프런트엔드는 Vercel 또는 Netlify에만 배포합니다. 프로젝트 루트를 ai_agents/agents/examples/<example>/frontend로 지정하고 pnpm install(또는 bun install) 후 pnpm build(또는 bun run build)를 실행하며 기본 .next 출력 디렉터리를 유지합니다.
  3. 호스팅 대시보드에서 AGENT_SERVER_URL을 백엔드 주소로 설정하고, 브라우저에서 필요한 NEXT_PUBLIC_* 환경 변수를 추가합니다(예: Agora 자격 증명).
  4. 백엔드가 프런트엔드 오리진을 허용하도록 CORS를 열거나 기본 제공 프록시 미들웨어를 사용하세요.

이 구조에서는 백엔드가 장기 실행 작업을 처리하고, 호스팅된 프런트엔드는 API 요청을 전달하기만 하면 됩니다.

소식 받기

새 릴리스와 업데이트를 즉시 받아보세요. 여러분의 응원이 TEN을 성장시킵니다!

Image

TEN 에코시스템

프로젝트 미리보기
️TEN Framework대화형 AI 에이전트를 위한 오픈소스 프레임워크.
TEN VAD저지연·경량·고성능 스트리밍 음성 활동 감지.
️TEN Turn Detection풀 듀플렉스 대화를 지원하는 턴 감지.
TEN Agent ExamplesTEN 기반 활용 사례 모음.
TEN Portal문서와 블로그를 제공하는 공식 사이트.

질문

TEN Framework는 AI 기반 Q&A 플랫폼에서도 만나볼 수 있습니다. 멀티랭귀지를 지원하며 기본 설정부터 고급 구현까지 빠르게 답을 찾을 수 있습니다.

서비스 링크
DeepWiki Ask DeepWiki
ReadmeX ReadmeX

기여하기

버그 수정, 기능 추가, 문서 개선, 아이디어 공유 등 모든 형태의 오픈소스 협업을 환영합니다. GitHub Issues와 Projects에서 참여할 작업을 찾아 능력을 보여주세요. 함께 멋진 TEN을 만들어봅시다!

[!TIP]

어떤 형태의 기여든 환영합니다 🙏

코드부터 문서까지, 모든 기여가 TEN을 더 좋게 만듭니다. TEN Agent 프로젝트를 SNS에 공유해 다른 제작자에게 영감을 주세요.

𝕏의 @elliotchen200 또는 GitHub의 @cyfyifanchen에게 연락하면 업데이트, 토론, 협업 기회를 얻을 수 있습니다.

divider
divider

코드 기여자

TEN

기여 가이드

기여를 환영합니다! 먼저 기여 가이드를 읽어 주세요.

divider
divider

라이선스

  1. 아래에 명시된 디렉터리를 제외한 TEN Framework 전체는 추가 제한이 포함된 Apache License 2.0으로 배포됩니다. 루트 디렉터리의 LICENSE를 참고하세요.
  2. packages 디렉터리의 구성요소 역시 Apache License 2.0으로 배포되며, 각 패키지 루트의 LICENSE 파일에서 세부 내용을 확인할 수 있습니다.
  3. TEN Framework에서 사용하는 서드파티 라이브러리는 third_party 폴더에 정리되어 있습니다.


Ai L0 Repo Card (`docs/ai/L0_repo_card.md`)

TEN Framework — Repo Card

Open-source platform for building real-time multimodal AI agents

Identity

Field Value
Repo TEN-framework/ten-framework
Description Open-source platform for building real-time multimodal AI agents
Type distributed-system
Language Python (extensions), Go (API server), TypeScript/React (playground)
Deploy Target Docker container (ten_agent_dev), Taskfile-based build
Owner TEN Framework team
Last Reviewed 2026-06-29

L1 Index

File Purpose Audience
01_setup Docker, .env, ports, health checks, restart procedures Use & Maintain
02_architecture Extensions, graphs, connections, RTC-first design Use & Maintain
03_code_map Directory tree, key files, base classes, 90+ extensions Use & Maintain
04_conventions Naming, Pydantic configs, params pattern, formatting Use & Maintain
05_workflows Create extension, modify graph, test, restart, deploy Use & Maintain
06_interfaces REST API, connection schemas, base class abstract methods Use & Maintain
07_gotchas Property tuples, signal handlers, zombies, .env timing Use & Maintain
08_security API keys, .env, sensitive logging, git hooks Use & Maintain

Api Log.Cn (`docs/api/log.cn.md`)


title: 日志 _portal_target: api/log.cn.md

TEN framework 允许使用不同语言开发的 extension 在同一进程中运行。这使得需要以统一的日志格式和信息在统一的日志中查看所有这些 extension 的日志,从而简化调试。

为了解决这个问题,ten_env 提供了一个日志 API。在 extension 的每个回调中,都可以访问 ten_env 的实例。使用此实例,您可以调用日志 API,将使用各种语言开发的 extension 的日志输出到统一的日志输出中。

日志级别

TEN framework 支持以下日志级别,按严重程度递增:

  • DEBUG (1): 调试信息
  • INFO (2): 一般信息
  • WARN (3): 警告信息
  • ERROR (4): 错误信息

API 接口

C++ 语言

cpp
#include "ten_runtime/binding/cpp/ten.h"

class Extension {
public:
    void on_cmd(ten::ten_env_t& ten_env, std::unique_ptr<ten::cmd_t> cmd) {
        // 使用便捷宏
        TEN_ENV_LOG_DEBUG(ten_env, "Processing command");
        TEN_ENV_LOG_INFO(ten_env, "Command name: " + cmd->get_name());
        TEN_ENV_LOG_WARN(ten_env, "Warning message");
        TEN_ENV_LOG_ERROR(ten_env, "Error occurred");

        // 直接调用方法,支持 category 和 fields 参数
        ten_env.log(TEN_LOG_LEVEL_INFO, __func__, __FILE__, __LINE__,
                   "Direct log call", nullptr, nullptr);
    }
};

C++ 提供了以下便捷宏:

  • TEN_ENV_LOG_DEBUG(ten_env, msg)
  • TEN_ENV_LOG_INFO(ten_env, msg)
  • TEN_ENV_LOG_WARN(ten_env, msg)
  • TEN_ENV_LOG_ERROR(ten_env, msg)

Python 语言

python
from ten_runtime import Extension, TenEnv

class MyExtension(Extension):
    def on_cmd(self, ten_env: TenEnv, cmd):
        # 使用不同级别的日志方法
        ten_env.log_debug("调试信息")
        ten_env.log_info("一般信息")
        ten_env.log_warn("警告信息")
        ten_env.log_error("错误信息")

        # 支持格式化字符串和额外参数
        test_value = "example"
        ten_env.log_info(f"处理命令,测试值: {test_value}")

        # 支持 category 和 fields 参数
        ten_env.log_info("带分类的日志", category="my_extension")

Python 提供了以下方法:

  • log_debug(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]
  • log_info(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]
  • log_warn(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]
  • log_error(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]

Go 语言

go
package main

import "ten_runtime/ten"

type MyExtension struct {
    ten.DefaultExtension
}

func (ext *MyExtension) OnCmd(tenEnv ten.TenEnv, cmd ten.Cmd) {
    // 使用不同级别的日志方法
    tenEnv.LogDebug("调试信息")
    tenEnv.LogInfo("一般信息")
    tenEnv.LogWarn("警告信息")
    tenEnv.LogError("错误信息")

    // 支持格式化
    cmdName := cmd.GetName()
    tenEnv.LogInfo("处理命令: " + cmdName)

    // 使用完整的 Log 方法支持更多参数
    category := "my_extension"
    tenEnv.Log(ten.LogLevelInfo, "带分类的日志", &category, nil, nil)
}

Go 提供了以下方法:

  • LogDebug(msg string) error
  • LogInfo(msg string) error
  • LogWarn(msg string) error
  • LogError(msg string) error
  • Log(level LogLevel, msg string, category *string, fields *Value, option *TenLogOption) error

Node.js/TypeScript 语言

typescript
import { Extension, TenEnv, Cmd } from "ten-runtime-nodejs";

class MyExtension extends Extension {
    async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
        // 使用不同级别的日志方法
        tenEnv.logDebug("调试信息");
        tenEnv.logInfo("一般信息");
        tenEnv.logWarn("警告信息");
        tenEnv.logError("错误信息");

        // 支持字符串拼接
        const cmdName = cmd.getName();
        tenEnv.logInfo("处理命令: " + cmdName);

        // 支持 category 和 fields 参数
        tenEnv.logInfo("带分类的日志", "my_extension");
    }
}

Node.js 提供了以下方法:

  • logDebug(message: string, category?: string, fields?: Value): TenError | undefined
  • logInfo(message: string, category?: string, fields?: Value): TenError | undefined
  • logWarn(message: string, category?: string, fields?: Value): TenError | undefined
  • logError(message: string, category?: string, fields?: Value): TenError | undefined

使用示例

基本使用

python
# Python 示例
def on_start(self, ten_env: TenEnv):
    ten_env.log_info("extension 启动")
    ten_env.on_start_done()

def on_cmd(self, ten_env: TenEnv, cmd):
    cmd_name = cmd.get_name()
    ten_env.log_debug(f"收到命令: {cmd_name}")

    # 处理命令逻辑
    if cmd_name == "hello":
        ten_env.log_info("处理 hello 命令", category="command_handler")
        # ... 处理逻辑
    else:
        ten_env.log_warn(f"未知命令: {cmd_name}")

在线程中使用

python
import threading
import time

def log_routine(self, ten_env: TenEnv):
    """在单独线程中记录日志"""
    while not self.stop_flag:
        self.log_count += 1
        ten_env.log_info(f"日志消息 {self.log_count}")
        time.sleep(0.05)

def on_start(self, ten_env: TenEnv):
    # 启动日志线程
    self.log_thread = threading.Thread(
        target=self.log_routine, args=(ten_env,)
    )
    self.log_thread.start()
    ten_env.on_start_done()

错误处理

python
def on_cmd(self, ten_env: TenEnv, cmd):
    try:
        # 业务逻辑
        result = self.process_command(cmd)
        ten_env.log_info("命令处理成功")
    except ValueError as e:
        ten_env.log_error(f"参数错误: {str(e)}", category="error_handling")
    except Exception as e:
        ten_env.log_error(f"严重错误: {str(e)}", category="error_handling")
        raise

日志格式和输出

TEN framework 的日志系统会自动包含以下信息:

  • 时间戳: 日志记录的时间
  • 日志级别: DEBUG、INFO、WARN、ERROR
  • 函数名: 调用日志的函数名(如果可用)
  • 文件名: 调用日志的源文件名(如果可用)
  • 行号: 调用日志的代码行号(如果可用)
  • 分类: 可选的日志分类信息
  • 消息内容: 用户提供的日志消息

输出格式类似:

bash
2025-01-01 12:00:00.123 [INFO] [[email protected]:123] [category] 用户日志消息

高级日志配置

TEN framework 支持通过 property.json 文件配置高级日志功能。这允许您自定义日志的输出格式、目标和过滤规则。

配置结构

在 property.json 文件中,您可以在 ten.log 部分定义高级日志配置:

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "debug",
              "category": "my_extension"
            }
          ],
          "formatter": {
            "type": "plain",
            "colored": true
          },
          "emitter": {
            "type": "console",
            "config": {
              "stream": "stdout"
            }
          }
        }
      ]
    }
  }
}

配置组件说明

1. 日志处理器 (Handlers)

每个处理器包含三个主要组件:

  • matchers: 定义哪些日志消息会被此处理器处理
  • formatter: 定义日志的输出格式
  • emitter: 定义日志的输出目标

2. 匹配器 (Matchers)

匹配器用于过滤日志消息:

json
{
  "level": "info",      // 日志级别: "off", "debug", "info", "warn", "error"
  "category": "auth"    // 可选:日志分类(字符串)
}

支持的日志级别:

  • "off": 关闭日志
  • "debug": 调试级别
  • "info": 信息级别
  • "warn": 警告级别
  • "error": 错误级别

3. 格式器 (Formatter)

控制日志的输出格式:

json
{
  "type": "plain",      // 格式类型: "plain" 或 "json"
  "colored": true       // 可选:是否启用颜色输出(仅对 plain 格式有效)
}
  • plain: 人类可读的纯文本格式
  • json: JSON 结构化格式,便于日志分析工具处理

4. 发射器 (Emitter)

定义日志的输出目标:

控制台输出

json
{
  "type": "console",
  "config": {
    "stream": "stdout",  // "stdout" 或 "stderr"
    "encryption": {      // 可选:加密配置
      "enabled": true,
      "algorithm": "AES-CTR",
      "params": {
        "key": "your-encryption-key",
        "nonce": "your-nonce"
      }
    }
  }
}

文件输出

json
{
  "type": "file",
  "config": {
    "path": "/path/to/logfile.log",  // 日志文件路径
    "encryption": {                  // 可选:加密配置
      "enabled": true,
      "algorithm": "AES-CTR",
      "params": {
        "key": "your-encryption-key",
        "nonce": "your-nonce"
      }
    }
  }
}

OTLP 输出

OTLP (OpenTelemetry Protocol) 输出允许您将日志发送到支持 OpenTelemetry 的后端系统,如 Jaeger、Prometheus、Grafana 等。

json
{
  "type": "otlp",
  "config": {
    "endpoint": "http://localhost:4317",  // OTLP 接收端点地址
    "protocol": "grpc",                   // 可选:传输协议,"grpc"(默认)或 "http"
    "headers": {                          // 可选:自定义 HTTP 头
      "Authorization": "Bearer token",
      "X-Custom-Header": "value"
    },
    "service_name": "my-service"          // 可选:服务名称,用于标识日志来源
  }
}

配置参数说明:

  • endpoint: OTLP 接收端点的 URL(必需)
  • protocol: 传输协议,支持 "grpc" 或 "http",默认为 "grpc"
  • headers: 自定义 HTTP 请求头,用于认证或其他元数据传递(可选)
  • service_name: 服务名称,用于在日志聚合系统中标识不同的服务(可选)

配置示例

示例 1: 多级别日志分离

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "error"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "file",
            "config": {
              "path": "/var/log/app/errors.log"
            }
          }
        },
        {
          "matchers": [
            {
              "level": "info"
            }
          ],
          "formatter": {
            "type": "plain",
            "colored": true
          },
          "emitter": {
            "type": "console",
            "config": {
              "stream": "stdout"
            }
          }
        }
      ]
    }
  }
}

示例 2: 按分类分离日志

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "debug",
              "category": "database"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "file",
            "config": {
              "path": "/var/log/app/database.log"
            }
          }
        },
        {
          "matchers": [
            {
              "level": "info",
              "category": "api"
            }
          ],
          "formatter": {
            "type": "plain"
          },
          "emitter": {
            "type": "console",
            "config": {
              "stream": "stdout"
            }
          }
        }
      ]
    }
  }
}

示例 3: 加密日志输出

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "info"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "file",
            "config": {
              "path": "/secure/logs/app.log",
              "encryption": {
                "enabled": true,
                "algorithm": "AES-CTR",
                "params": {
                  "key": "your-32-character-encryption-key",
                  "nonce": "your-16-character-nonce"
                }
              }
            }
          }
        }
      ]
    }
  }
}

示例 4: OTLP 日志输出到 OpenTelemetry 收集器

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "info"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "otlp",
            "config": {
              "endpoint": "http://localhost:4317",
              "protocol": "grpc",
              "service_name": "ten-framework-app"
            }
          }
        },
        {
          "matchers": [
            {
              "level": "error"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "otlp",
            "config": {
              "endpoint": "https://api.monitoring.example.com/v1/logs",
              "protocol": "http",
              "headers": {
                "Authorization": "Bearer your-api-token",
                "X-Environment": "production"
              },
              "service_name": "ten-framework-app"
            }
          }
        }
      ]
    }
  }
}

配置注意事项

  1. 多个处理器: 可以定义多个处理器来实现复杂的日志路由策略
  2. 匹配优先级: 按照配置文件中的顺序匹配处理器
  3. 加密安全: 加密密钥和 nonce 应该安全存储,不要硬编码在配置文件中
  4. 文件权限: 确保日志文件路径具有适当的写入权限
  5. 性能考虑: JSON 格式可能会比 plain 格式产生更大的日志文件
  6. OTLP 端点:
    • 使用 gRPC 协议时,默认端口通常为 4317
    • 使用 HTTP 协议时,默认端口通常为 4318
    • 确保 OTLP 收集器端点可访问且配置正确
  7. 认证: 使用 OTLP 时,如果后端需要认证,请在 headers 中添加相应的认证信息

最佳实践

  1. 选择合适的日志级别:

    • 使用 DEBUG 记录详细的调试信息
    • 使用 INFO 记录正常的业务流程
    • 使用 WARN 记录潜在问题
    • 使用 ERROR 记录错误信息
  2. 提供有意义的日志消息:

    • 包含上下文信息(如命令名称、参数值等)
    • 使用描述性的消息而不是简单的标识符
    • 合理使用 category 参数对日志进行分类
  3. 避免过度日志记录:

    • 不要在高频循环中使用过多的日志
    • 在生产环境中适当调整日志级别
    • 使用高级日志配置的匹配器来过滤不必要的日志
  4. 线程安全:

    • TEN framework 的日志 API 是线程安全的
    • 可以在任何线程中安全调用日志方法
  5. 高级配置建议:

    • 为不同的模块使用不同的 category 便于日志分析
    • 在生产环境中考虑使用文件输出以便于日志持久化
    • 对敏感日志使用加密功能
    • 定期轮转日志文件以控制磁盘使用
    • 使用 JSON 格式便于与日志分析工具集成
    • 考虑使用 OTLP 输出将日志集成到现代可观测性平台(如 Grafana、Jaeger、Datadog 等)
    • OTLP 输出与 OpenTelemetry 生态系统无缝集成,便于实现分布式追踪和日志关联

整体效果如下图所示:


Api Log (`docs/api/log.md`)


title: Log _portal_target: api/log.md

The TEN framework allows extensions developed in different languages to run within the same process. This creates a need to view logs from all these extensions in a unified log with a consistent format and information, making debugging easier.

To address this, the ten_env provides a logging API. Within each callback of an extension, an instance of ten_env can be accessed. Using this instance, you can call the logging API to output logs from extensions developed in various languages to a unified log output.

Log Levels

The TEN framework supports the following log levels in increasing order of severity:

  • DEBUG (1): Debug information
  • INFO (2): General information
  • WARN (3): Warning information
  • ERROR (4): Error information

API Interface

C++ Language

cpp
#include "ten_runtime/binding/cpp/ten.h"

class Extension {
public:
    void on_cmd(ten::ten_env_t& ten_env, std::unique_ptr<ten::cmd_t> cmd) {
        // Using convenience macros
        TEN_ENV_LOG_DEBUG(ten_env, "Processing command");
        TEN_ENV_LOG_INFO(ten_env, "Command name: " + cmd->get_name());
        TEN_ENV_LOG_WARN(ten_env, "Warning message");
        TEN_ENV_LOG_ERROR(ten_env, "Error occurred");

        // Direct method call with category and fields support
        ten_env.log(TEN_LOG_LEVEL_INFO, __func__, __FILE__, __LINE__,
                   "Direct log call", nullptr, nullptr);
    }
};

C++ provides the following convenience macros:

  • TEN_ENV_LOG_DEBUG(ten_env, msg)
  • TEN_ENV_LOG_INFO(ten_env, msg)
  • TEN_ENV_LOG_WARN(ten_env, msg)
  • TEN_ENV_LOG_ERROR(ten_env, msg)

Python Language

python
from ten_runtime import Extension, TenEnv

class MyExtension(Extension):
    def on_cmd(self, ten_env: TenEnv, cmd):
        # Using different log level methods
        ten_env.log_debug("Debug information")
        ten_env.log_info("General information")
        ten_env.log_warn("Warning information")
        ten_env.log_error("Error information")

        # Supports formatted strings and additional parameters
        test_value = "example"
        ten_env.log_info(f"Processing command, test value: {test_value}")

        # Supports category and fields parameters
        ten_env.log_info("Log with category", category="my_extension")

Python provides the following methods:

  • log_debug(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]
  • log_info(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]
  • log_warn(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]
  • log_error(msg: str, category: str = None, fields: Value = None) -> Optional[TenError]

Go Language

go
package main

import "ten_runtime/ten"

type MyExtension struct {
    ten.DefaultExtension
}

func (ext *MyExtension) OnCmd(tenEnv ten.TenEnv, cmd ten.Cmd) {
    // Using different log level methods
    tenEnv.LogDebug("Debug information")
    tenEnv.LogInfo("General information")
    tenEnv.LogWarn("Warning information")
    tenEnv.LogError("Error information")

    // Supports formatting
    cmdName := cmd.GetName()
    tenEnv.LogInfo("Processing command: " + cmdName)

    // Using the full Log method for additional parameters
    category := "my_extension"
    tenEnv.Log(ten.LogLevelInfo, "Log with category", &category, nil, nil)
}

Go provides the following methods:

  • LogDebug(msg string) error
  • LogInfo(msg string) error
  • LogWarn(msg string) error
  • LogError(msg string) error
  • Log(level LogLevel, msg string, category *string, fields *Value, option *TenLogOption) error

Node.js/TypeScript Language

typescript
import { Extension, TenEnv, Cmd } from "ten-runtime-nodejs";

class MyExtension extends Extension {
    async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
        // Using different log level methods
        tenEnv.logDebug("Debug information");
        tenEnv.logInfo("General information");
        tenEnv.logWarn("Warning information");
        tenEnv.logError("Error information");

        // Supports string concatenation
        const cmdName = cmd.getName();
        tenEnv.logInfo("Processing command: " + cmdName);

        // Supports category and fields parameters
        tenEnv.logInfo("Log with category", "my_extension");
    }
}

Node.js provides the following methods:

  • logDebug(message: string, category?: string, fields?: Value): TenError | undefined
  • logInfo(message: string, category?: string, fields?: Value): TenError | undefined
  • logWarn(message: string, category?: string, fields?: Value): TenError | undefined
  • logError(message: string, category?: string, fields?: Value): TenError | undefined

Usage Examples

Basic Usage

python
# Python example
def on_start(self, ten_env: TenEnv):
    ten_env.log_info("Extension starting")
    ten_env.on_start_done()

def on_cmd(self, ten_env: TenEnv, cmd):
    cmd_name = cmd.get_name()
    ten_env.log_debug(f"Received command: {cmd_name}")

    # Command processing logic
    if cmd_name == "hello":
        ten_env.log_info("Processing hello command", category="command_handler")
        # ... processing logic
    else:
        ten_env.log_warn(f"Unknown command: {cmd_name}")

Using in Threads

python
import threading
import time

def log_routine(self, ten_env: TenEnv):
    """Log messages in a separate thread"""
    while not self.stop_flag:
        self.log_count += 1
        ten_env.log_info(f"Log message {self.log_count}")
        time.sleep(0.05)

def on_start(self, ten_env: TenEnv):
    # Start logging thread
    self.log_thread = threading.Thread(
        target=self.log_routine, args=(ten_env,)
    )
    self.log_thread.start()
    ten_env.on_start_done()

Error Handling

python
def on_cmd(self, ten_env: TenEnv, cmd):
    try:
        # Business logic
        result = self.process_command(cmd)
        ten_env.log_info("Command processed successfully")
    except ValueError as e:
        ten_env.log_error(f"Parameter error: {str(e)}", category="error_handling")
    except Exception as e:
        ten_env.log_error(f"Fatal error: {str(e)}", category="error_handling")
        raise

Log Format and Output

The TEN framework's logging system automatically includes the following information:

  • Timestamp: Time when the log was recorded
  • Log Level: DEBUG, INFO, WARN, ERROR
  • Function Name: Name of the function that called the log (if available)
  • File Name: Source file name where the log was called (if available)
  • Line Number: Code line number where the log was called (if available)
  • Category: Optional log category information
  • Message Content: User-provided log message

The output format is similar to:

bash
2025-01-01 12:00:00.123 [INFO] [[email protected]:123] [category] User log message

Advanced Log Configuration

The TEN framework supports advanced logging functionality through the property.json file configuration. This allows you to customize the log output format, destination, and filtering rules.

Configuration Structure

In the property.json file, you can define advanced log configuration in the ten.log section:

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "debug",
              "category": "my_extension"
            }
          ],
          "formatter": {
            "type": "plain",
            "colored": true
          },
          "emitter": {
            "type": "console",
            "config": {
              "stream": "stdout"
            }
          }
        }
      ]
    }
  }
}

Configuration Components

1. Log Handlers

Each handler contains three main components:

  • matchers: Define which log messages will be processed by this handler
  • formatter: Define the log output format
  • emitter: Define the log output destination

2. Matchers

Matchers are used to filter log messages:

json
{
  "level": "info",      // Log level: "off", "debug", "info", "warn", "error"
  "category": "auth"    // Optional: log category (string)
}

Supported log levels:

  • "off": Disable logging
  • "debug": Debug level
  • "info": Info level
  • "warn": Warning level
  • "error": Error level

3. Formatter

Controls the log output format:

json
{
  "type": "plain",      // Format type: "plain" or "json"
  "colored": true       // Optional: enable colored output (only for plain format)
}
  • plain: Human-readable plain text format
  • json: JSON structured format, convenient for log analysis tools

4. Emitter

Defines the log output destination:

Console Output

json
{
  "type": "console",
  "config": {
    "stream": "stdout",  // "stdout" or "stderr"
    "encryption": {      // Optional: encryption configuration
      "enabled": true,
      "algorithm": "AES-CTR",
      "params": {
        "key": "your-encryption-key",
        "nonce": "your-nonce"
      }
    }
  }
}

File Output

json
{
  "type": "file",
  "config": {
    "path": "/path/to/logfile.log",  // Log file path
    "encryption": {                  // Optional: encryption configuration
      "enabled": true,
      "algorithm": "AES-CTR",
      "params": {
        "key": "your-encryption-key",
        "nonce": "your-nonce"
      }
    }
  }
}

OTLP Output

OTLP (OpenTelemetry Protocol) output allows you to send logs to OpenTelemetry-compatible backend systems, such as Jaeger, Prometheus, Grafana, and more.

json
{
  "type": "otlp",
  "config": {
    "endpoint": "http://localhost:4317",  // OTLP receiver endpoint URL
    "protocol": "grpc",                   // Optional: transport protocol, "grpc" (default) or "http"
    "headers": {                          // Optional: custom HTTP headers
      "Authorization": "Bearer token",
      "X-Custom-Header": "value"
    },
    "service_name": "my-service"          // Optional: service name for identifying log source
  }
}

Configuration parameters:

  • endpoint: OTLP receiver endpoint URL (required)
  • protocol: Transport protocol, supports "grpc" or "http", defaults to "grpc"
  • headers: Custom HTTP request headers for authentication or metadata (optional)
  • service_name: Service name for identifying different services in log aggregation systems (optional)

Configuration Examples

Example 1: Multi-level Log Separation

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "error"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "file",
            "config": {
              "path": "/var/log/app/errors.log"
            }
          }
        },
        {
          "matchers": [
            {
              "level": "info"
            }
          ],
          "formatter": {
            "type": "plain",
            "colored": true
          },
          "emitter": {
            "type": "console",
            "config": {
              "stream": "stdout"
            }
          }
        }
      ]
    }
  }
}

Example 2: Category-based Log Separation

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "debug",
              "category": "database"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "file",
            "config": {
              "path": "/var/log/app/database.log"
            }
          }
        },
        {
          "matchers": [
            {
              "level": "info",
              "category": "api"
            }
          ],
          "formatter": {
            "type": "plain"
          },
          "emitter": {
            "type": "console",
            "config": {
              "stream": "stdout"
            }
          }
        }
      ]
    }
  }
}

Example 3: Encrypted Log Output

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "info"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "file",
            "config": {
              "path": "/secure/logs/app.log",
              "encryption": {
                "enabled": true,
                "algorithm": "AES-CTR",
                "params": {
                  "key": "your-32-character-encryption-key",
                  "nonce": "your-16-character-nonce"
                }
              }
            }
          }
        }
      ]
    }
  }
}

Example 4: OTLP Log Output to OpenTelemetry Collector

json
{
  "ten": {
    "log": {
      "handlers": [
        {
          "matchers": [
            {
              "level": "info"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "otlp",
            "config": {
              "endpoint": "http://localhost:4317",
              "protocol": "grpc",
              "service_name": "ten-framework-app"
            }
          }
        },
        {
          "matchers": [
            {
              "level": "error"
            }
          ],
          "formatter": {
            "type": "json"
          },
          "emitter": {
            "type": "otlp",
            "config": {
              "endpoint": "https://api.monitoring.example.com/v1/logs",
              "protocol": "http",
              "headers": {
                "Authorization": "Bearer your-api-token",
                "X-Environment": "production"
              },
              "service_name": "ten-framework-app"
            }
          }
        }
      ]
    }
  }
}

Configuration Notes

  1. Multiple Handlers: You can define multiple handlers to implement complex log routing strategies
  2. Matching Priority: Handlers are matched in the order they appear in the configuration file
  3. Encryption Security: Encryption keys and nonces should be stored securely, not hardcoded in configuration files
  4. File Permissions: Ensure log file paths have appropriate write permissions
  5. Performance Considerations: JSON format may produce larger log files than plain format
  6. OTLP Endpoints:
    • When using gRPC protocol, the default port is typically 4317
    • When using HTTP protocol, the default port is typically 4318
    • Ensure the OTLP collector endpoint is accessible and properly configured
  7. Authentication: When using OTLP, if the backend requires authentication, add appropriate credentials in the headers

Best Practices

  1. Choose appropriate log levels:

    • Use DEBUG for detailed debugging information
    • Use INFO for normal business processes
    • Use WARN for potential issues
    • Use ERROR for error information
  2. Provide meaningful log messages:

    • Include contextual information (such as command names, parameter values, etc.)
    • Use descriptive messages rather than simple identifiers
    • Use the category parameter appropriately to classify logs
  3. Avoid excessive logging:

    • Don't use too many logs in high-frequency loops
    • Appropriately adjust log levels in production environments
    • Use advanced log configuration matchers to filter unnecessary logs
  4. Thread safety:

    • The TEN framework's logging API is thread-safe
    • Log methods can be safely called from any thread
  5. Advanced configuration recommendations:

    • Use different categories for different modules to facilitate log analysis
    • Consider using file output in production environments for log persistence
    • Use encryption functionality for sensitive logs
    • Regularly rotate log files to control disk usage
    • Use JSON format for easier integration with log analysis tools
    • Consider using OTLP output to integrate logs into modern observability platforms (e.g., Grafana, Jaeger, Datadog)
    • OTLP output seamlessly integrates with the OpenTelemetry ecosystem, enabling distributed tracing and log correlation

The overall effect is shown in the image below:


Code Of Conduct Contributing (`docs/code-of-conduct/contributing.md`)

Contributing to TEN Framework

Welcome to the TEN Framework community! We're excited to have you contribute to building the future of real-time conversational AI agents. This guide will help you get started with contributing to the project.

🌟 Ways to Contribute

We welcome all forms of contributions:

  • Code contributions: Bug fixes, new features, performance improvements
  • Documentation: Improving docs, tutorials, examples
  • Bug reports: Identifying and reporting issues
  • Feature requests: Suggesting new functionality
  • Community support: Helping others in discussions and issues
  • Extensions: Creating new extensions for the ecosystem

📋 Before You Start

Required Reading

  1. Read our Code of Conduct - all contributors must follow our community guidelines
  2. Sign the Contributor License Agreement (CLA) - required for all contributions
  3. Review the project README to understand the architecture

Prerequisites

Make sure you have the required tools installed:

  • Docker / Docker Compose (for development environment)
  • Node.js v20+ (for frontend development)
  • Python 3.8+ (for AI agent development)
  • Go (for backend server development)
  • Task (task runner - install via npm install @go-task/cli)

🚀 Getting Started

1. Fork and Clone

bash
# Fork the repository on GitHub first, then:
git clone https://github.com/YOUR_USERNAME/ten-framework.git
cd ten-framework

2. Set Up Development Environment

bash
# Navigate to AI agents directory
cd ai_agents

# Copy environment template
cp .env.example .env

# Edit .env file with your API keys:
# - AGORA_APP_ID and AGORA_APP_CERTIFICATE
# - OpenAI, Deepgram, ElevenLabs API keys as needed

# Start development containers
docker compose up -d

# Enter the container
docker exec -it ten_agent_dev bash

# Build the agent (takes 5-8 minutes)
task use

# Build and run
task build
task run

3. Development Workflow

The project uses Task as the build system. Key commands:

bash
# Core development commands
task build          # Build the project
task test           # Run all tests
task lint           # Run linting
task format         # Format code
task clean          # Clean build artifacts

# AI Agents specific
task use AGENT=agents/examples/demo    # Switch to different agent
task run-server     # Run backend server
task run-gd-server  # Run graph designer

🏗️ Project Structure

text
ten-framework/
├── ai_agents/              # AI Agent implementations
│   ├── agents/             # Agent examples and configurations
│   ├── playground/         # Frontend playground (Next.js)
│   ├── server/             # Backend server (Go)
│   └── Taskfile.yml        # Build configuration
├── core/                   # TEN Framework core
├── packages/               # Framework packages
├── docs/                   # Documentation
├── tests/                  # Test suites
└── third_party/            # Third-party dependencies

🔧 Making Changes

Code Guidelines

  1. Follow existing patterns: Review similar code before implementing
  2. Use existing libraries: Check if the codebase already uses needed dependencies
  3. Security first: Never expose secrets or credentials
  4. No hardcoded comments: Don't add unnecessary code comments unless requested

Language-Specific Guidelines

Python (AI Agents)

  • Follow PEP 8 style guide
  • Use Black formatter: task format
  • Run pylint: task lint
  • Add type hints where appropriate

TypeScript/JavaScript (Playground)

  • Use existing ESLint configuration
  • Follow React best practices
  • Use TypeScript for new code

Go (Server)

  • Follow Go conventions
  • Use gofmt for formatting
  • Add proper error handling

Commit Guidelines

We use conventional commits:

bash
feat: add new ASR integration
fix: resolve memory leak in audio processing  
docs: update installation instructions
test: add integration tests for TTS module
refactor: improve error handling in server

🧪 Testing

Running Tests

bash
# Run all tests
task test

# Test specific extension
task test-extension EXTENSION=agents/ten_packages/extension/elevenlabs_tts_python

# Test server only
task test-server

# Test agent extensions
task test-agent-extensions

Writing Tests

  • Add unit tests for new functionality
  • Include integration tests for complex features
  • Ensure tests pass before submitting PRs
  • Test configurations are in tests/configs/ directories

📝 Documentation

  • Update documentation for new features
  • Include code examples where helpful
  • Update README if adding new dependencies
  • Document breaking changes clearly

🔄 Pull Request Process

Before Submitting

  1. Test your changes: Ensure all tests pass
  2. Format code: Run task format
  3. Lint code: Run task lint
  4. Update docs: Include any necessary documentation updates
  5. Rebase on main: Ensure your branch is up to date

PR Guidelines

  1. Clear title: Use descriptive, conventional commit style
  2. Detailed description: Explain what, why, and how
  3. Link issues: Reference related issues with "Fixes #123"
  4. Screenshots: Include for UI changes
  5. Breaking changes: Clearly document any breaking changes

PR Template

markdown
## Summary
Brief description of changes

## Type of Change
- [ ] Bug fix
- [ ] New feature  
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring

## Testing
- [ ] Tests added/updated
- [ ] All tests pass
- [ ] Manual testing completed

## Documentation
- [ ] Documentation updated
- [ ] Examples provided if needed

## Breaking Changes
List any breaking changes and migration steps

🐛 Reporting Issues

Bug Reports

Use the issue template and include:

  • Environment: OS, versions, Docker info
  • Steps to reproduce: Clear, minimal reproduction steps
  • Expected vs actual: What should happen vs what happens
  • Logs: Include relevant error messages
  • Screenshots: For UI issues

Feature Requests

  • Use case: Explain the problem you're solving
  • Proposed solution: Describe your ideal solution
  • Alternatives: Consider other approaches
  • Examples: Reference similar implementations if possible

🏆 Recognition

Contributors are recognized in multiple ways:

  • Contributors graph: Automatic GitHub recognition
  • Release notes: Significant contributions mentioned
  • Social media: Major features highlighted on @TenFramework
  • Community showcase: Featured implementations

📞 Getting Help

Community Channels

  • Discord: Join TEN Community
  • GitHub Discussions: For questions and ideas
  • GitHub Issues: For bugs and feature requests
  • WeChat: Chinese community group

Maintainer Contact

📜 License

By contributing to TEN Framework, you agree that your contributions will be licensed under the Apache License v2.0 with additional conditions.

Your contributions must:

  • Be your original work or properly attributed
  • Not include any proprietary or confidential information
  • Comply with the project's licensing terms

❤️ Thank You

Every contribution, no matter how small, helps make TEN Framework better for everyone. We appreciate your time and effort in making this project successful!

Happy Contributing! 🚀


Development How To Develop With Ext.Cn (`docs/development/how_to_develop_with_ext.cn.md`)


title: TEN 扩展开发完整指南 _portal_target: development/how_to_develop_with_ext.cn.md

TEN Framework 提供了丰富的扩展模板,帮助开发者快速创建扩展并完成从开发到测试的完整流程。本指南将通过实际操作演示,详细介绍如何使用 C++,Go,Python,Node.js 四种语言进行扩展开发的全流程。

开发前准备

环境要求

在开始扩展开发之前,请确保您的开发环境已正确配置。通过以下命令验证安装:

bash
tman --version

正常情况下,您应该看到类似以下的版本信息输出:

bash
TEN Framework version: <version>

重要提示:请确保您使用的 tman 版本 >= 0.10.12,如果版本过低,请参考 快速入门指南 安装最新版本。

开发流程概览

无论使用哪种编程语言,TEN 扩展开发都遵循以下标准化流程:

  1. 项目创建 - 使用官方模板生成扩展项目骨架
  2. 依赖安装 - 配置运行环境并安装必要的依赖包
  3. 核心开发 - 实现扩展的业务逻辑和功能代码
  4. 构建测试 - 编译项目(如需要)并执行单元测试
  5. 调试优化 - 使用专业调试工具定位并解决问题

C++ 扩展开发

C++ 扩展适用于对性能要求极高的应用场景,如实时音视频处理、高频数据计算、底层系统操作等。

创建项目

使用 TEN 官方提供的 C++ 扩展模板快速创建新项目:

bash
tman create extension my_example_ext_cpp --template default_extension_cpp

项目创建成功后,您会得到以下完整的项目结构:

bash
my_example_ext_cpp/
├── BUILD.gn              # GN 构建系统配置文件
├── manifest.json         # 扩展元数据和配置信息
├── property.json         # 扩展属性和参数配置
├── src/
│   └── main.cc           # 扩展核心实现代码
├── tests/
│   ├── basic.cc          # 基础功能测试用例
│   └── gtest_main.cc     # 测试框架程序入口
├── include/              # 头文件存放目录
├── tools/                # 辅助工具和脚本
└── .vscode/
    └── launch.json       # VSCode 调试配置文件

环境配置验证

进入项目目录并验证构建工具是否正常工作:

bash
cd my_example_ext_cpp
tgn --version

期望输出:

bash
0.1.0

如果命令无法执行,请参考 快速入门指南 检查 TEN Framework 开发环境是否正确安装。

依赖包安装

安装扩展运行时所需的全部依赖包:

bash
tman install --standalone

安装成功后,您将看到类似以下的详细安装日志:

bash
📦  Get all installed packages...
🔍  Filter compatible packages...
🔒  Creating manifest-lock.json...
📥  Installing packages...
  [00:00:00] [########################################]       3/3       Done

🏆  Install successfully in 1 second

项目构建

TEN framework 提供了两种便捷的构建方式供您选择:

方式一:手动分步构建

bash
# 第一步:生成构建配置文件(启用独立测试模式)
tgn gen linux x64 debug -- ten_enable_standalone_test=true

# 第二步:执行项目构建
tgn build linux x64 debug

方式二:使用 tman 快捷命令

bash
tman run build

构建完成后,检查生成的可执行测试文件:

bash
ls -la bin/
# 应该能看到:my_example_ext_cpp_test

运行测试

验证扩展功能是否正常工作:

方式一:直接执行测试文件

bash
./bin/my_example_ext_cpp_test

方式二:使用 tman 统一命令

bash
tman run test

测试执行成功时的输出示例:

bash
Running main() from ../../../tests/gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] 1 test from Test
[ RUN      ] Test.Basic
[       OK ] Test.Basic (20 ms)
[==========] 1 test from 1 test case ran. (79 ms total)
[  PASSED  ] 1 test.

成功标志:当您看到 [ PASSED ] 1 test. 时表示测试全部通过。

核心代码结构详解

扩展主体实现(src/main.cc)

C++ 扩展的核心类需要继承自 ten::extension_t 基类,并实现完整的生命周期管理方法:

cpp
class my_example_ext_cpp_t : public ten::extension_t {
 public:
  // 扩展初始化阶段 - 进行基础配置和资源预分配
  void on_init(ten::ten_env_t &ten_env) override;

  // 扩展启动阶段 - 正式开始处理业务逻辑
  void on_start(ten::ten_env_t &ten_env) override;

  // 命令处理器 - 处理来自其他扩展或应用的命令请求
  void on_cmd(ten::ten_env_t &ten_env, std::unique_ptr<ten::cmd_t> cmd) override;

  // 数据处理器 - 处理通用数据流
  void on_data(ten::ten_env_t &ten_env, std::unique_ptr<ten::data_t> data) override;

  // 音频帧处理器 - 处理实时音频流数据
  void on_audio_frame(ten::ten_env_t &ten_env, std::unique_ptr<ten::audio_frame_t> frame) override;

  // 视频帧处理器 - 处理实时视频流数据
  void on_video_frame(ten::ten_env_t &ten_env, std::unique_ptr<ten::video_frame_t> frame) override;

  // 扩展停止阶段 - 清理资源和优雅关闭
  void on_stop(ten::ten_env_t &ten_env) override;
};

测试框架实现(tests/basic.cc)

使用 TEN 专用测试框架编写完整的单元测试:

cpp
class my_example_ext_cpp_tester : public ten::extension_tester_t {
 public:
  void on_start(ten::ten_env_tester_t &ten_env) override {
    // 创建测试用的命令对象
    auto new_cmd = ten::cmd_t::create("foo");

    // 发送命令到扩展并验证响应结果
    ten_env.send_cmd(std::move(new_cmd), [](/* callback parameters */) {
      // 在这里验证测试结果的正确性
    });
  }
};

调试环境配置

VSCode 集成调试

  1. 安装 VSCode 的 CodeLLDB 扩展插件
  2. 在源代码中设置断点
  3. 选择 "standalone test (lldb, launch)" 调试配置
  4. 按 F5 键启动调试会话

调试配置文件 .vscode/launch.json 的内容:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "standalone test (lldb, launch)",
      "type": "lldb",
      "request": "launch",
      "program": "${workspaceFolder}/bin/my_example_ext_cpp_test",
      "cwd": "${workspaceFolder}"
    }
  ]
}

命令行调试

使用经典的 GDB 调试器进行命令行调试:

bash
gdb ./bin/my_example_ext_cpp_test
(gdb) run
(gdb) bt  # 查看完整的函数调用栈

Go 扩展开发

Go 扩展在高性能和开发效率之间提供了很好的平衡,特别适合构建网络服务、并发处理、微服务架构等应用场景。

创建项目

使用 Go 扩展模板创建新项目:

bash
tman create extension my_example_ext_go --template default_extension_go --template-data class_name_prefix=Example

项目创建成功后会显示:

bash
🏆  Package 'extension:my_example_ext_go' created successfully in '/path/to/your/project' in 1 second.

完整的项目结构如下:

bash
my_example_ext_go/
├── extension.go             # 扩展核心实现代码
├── go.mod                   # Go 模块依赖管理配置
├── manifest.json            # 扩展元数据信息
├── property.json            # 扩展属性配置
├── README.md                # 项目说明文档
├── tests/                   # 测试相关文件
│   ├── basic_tester.go      # 测试逻辑实现
│   ├── basic_tester_test.go # 测试用例定义
│   ├── main_test.go         # 测试程序入口
│   └── bin/start            # 测试启动脚本
└── .vscode/launch.json      # VSCode 调试配置

依赖包安装

安装项目运行所需的依赖包:

bash
tman install --standalone

运行测试

验证扩展功能的正确性:

方式一:使用启动脚本

bash
./tests/bin/start

方式二:使用 tman 命令

bash
tman run test

测试成功执行的输出示例:

bash
=== RUN   TestBasicExtensionTester
--- PASS: TestBasicExtensionTester (0.01s)
PASS

核心代码结构详解

扩展实现(extension.go)

Go 扩展的核心实现结构:

go
import (
    ten "ten_framework/ten_runtime"
)

type ExampleExtension struct {
    ten.DefaultExtension
}

// 扩展启动生命周期
func (e *ExampleExtension) OnStart(tenEnv ten.TenEnv) {
    tenEnv.LogDebug("OnStart")
    tenEnv.OnStartDone()
}

// 命令处理逻辑
func (e *ExampleExtension) OnCmd(tenEnv ten.TenEnv, cmd ten.Cmd) {
    tenEnv.LogDebug("OnCmd")
    cmdResult, _ := ten.NewCmdResult(ten.StatusCodeOk, cmd)
    tenEnv.ReturnResult(cmdResult, nil)
}

// 扩展停止生命周期
func (e *ExampleExtension) OnStop(tenEnv ten.TenEnv) {
    tenEnv.LogDebug("OnStop")
    tenEnv.OnStopDone()
}

测试框架(tests/basic_tester.go)

Go 扩展的测试框架实现:

go
type BasicExtensionTester struct {
    ten.DefaultExtensionTester
}

func (tester *BasicExtensionTester) OnStart(tenEnvTester ten.TenEnvTester) {
    // 创建用于测试的命令对象
    cmdTest, _ := ten.NewCmd("test")

    // 发送命令并处理响应结果
    tenEnvTester.SendCmd(cmdTest, func(tet ten.TenEnvTester, cr ten.CmdResult, err error) {
        if err != nil {
            panic(err)
        }
        statusCode, _ := cr.GetStatusCode()
        if statusCode != ten.StatusCodeOk {
            panic(statusCode)
        }
        tenEnvTester.StopTest(nil)
    })

    tenEnvTester.OnStartDone()
}

开发环境优化

为了提升开发体验和调试便利性,建议在扩展根目录创建 go.work 工作空间文件:

go
go 1.18

use (
    .
    .ten/app/ten_packages/system/ten_runtime_go/interface
)

调试环境配置

VSCode 集成调试

确保已安装 Go 官方扩展,然后使用以下调试配置:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "standalone test (go, launch)",
      "type": "go",
      "request": "launch",
      "mode": "test",
      "program": "${workspaceFolder}/tests/",
      "env": {
        "CGO_LDFLAGS": "-L${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime/lib -L${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime_go/lib -lten_runtime -lten_runtime_go",
        "LD_LIBRARY_PATH": "${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime/lib:${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime_go/lib"
      },
      "args": ["-test.v"]
    }
  ]
}

Python 扩展开发

Python 扩展具有最高的开发效率,特别适合快速原型开发、AI/ML 应用集成、复杂业务逻辑实现等场景。

创建项目

使用 Python 异步扩展模板创建项目:

bash
tman create extension my_example_ext_python --template default_async_extension_python --template-data class_name_prefix=Example

完整的项目结构:

bash
my_example_ext_python/
├── extension.py         # 扩展核心实现代码
├── addon.py             # 扩展插件注册入口
├── __init__.py          # Python 包初始化文件
├── requirements.txt     # Python 依赖包清单
├── manifest.json        # 扩展元数据配置
├── property.json        # 扩展属性配置
├── tests/               # 测试相关文件
│   ├── test_basic.py    # 基础测试用例
│   ├── conftest.py      # pytest 配置文件
│   └── bin/start        # 测试启动脚本
└── .vscode/launch.json  # VSCode 调试配置

依赖包安装

安装项目所需的 Python 依赖包:

bash
tman install --standalone

运行测试

验证 Python 扩展的功能:

方式一:使用启动脚本

bash
./tests/bin/start

方式二:使用 tman 命令

bash
tman run test

测试成功执行的输出示例:

bash
============================================ test session starts ============================================
platform linux -- Python 3.10.17, pytest-8.3.4, pluggy-1.5.0
tests/test_basic.py .                                                                                [100%]
============================================ 1 passed in 0.04s =======================================

核心代码结构详解

扩展实现(extension.py)

Python 扩展推荐使用现代异步编程模式,以获得更好的性能和并发处理能力:

python
from ten_runtime import (
    AudioFrame, VideoFrame, AsyncExtension, AsyncTenEnv,
    Cmd, StatusCode, CmdResult, Data
)

class ExampleExtension(AsyncExtension):
    async def on_init(self, ten_env: AsyncTenEnv) -> None:
        ten_env.log_debug("on_init")

    async def on_start(self, ten_env: AsyncTenEnv) -> None:
        ten_env.log_debug("on_start")
        # TODO: 在这里读取配置文件,初始化必要的资源

    async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None:
        cmd_name = cmd.get_name()
        ten_env.log_debug(f"on_cmd name {cmd_name}")

        # TODO: 在这里实现具体的业务逻辑处理
        cmd_result = CmdResult.create(StatusCode.OK, cmd)
        await ten_env.return_result(cmd_result)

    async def on_stop(self, ten_env: AsyncTenEnv) -> None:
        ten_env.log_debug("on_stop")
        # TODO: 在这里清理资源,进行优雅关闭

插件注册入口(addon.py)

扩展插件的注册和创建逻辑:

python
from ten_runtime import Addon, register_addon_as_extension, TenEnv
from .extension import ExampleExtension

@register_addon_as_extension("my_example_ext_python")
class ExampleExtensionAddon(Addon):
    def on_create_instance(self, ten_env: TenEnv, name: str, context) -> None:
        ten_env.log_info("on_create_instance")
        ten_env.on_create_instance_done(ExampleExtension(name), context)

测试实现(tests/test_basic.py)

完整的异步测试框架实现:

python
from ten_runtime import (
    AsyncExtensionTester, AsyncTenEnvTester, Cmd, StatusCode,
    TenError, TenErrorCode
)

class ExtensionTesterBasic(AsyncExtensionTester):
    async def on_start(self, ten_env: AsyncTenEnvTester) -> None:
        # 创建用于测试的命令对象
        new_cmd = Cmd.create("hello_world")

        ten_env.log_debug("send hello_world")
        result, err = await ten_env.send_cmd(new_cmd)

        # 验证测试结果的正确性
        if (err is not None or result is None
            or result.get_status_code() != StatusCode.OK):
            ten_env.stop_test(TenError.create(
                TenErrorCode.ErrorCodeGeneric,
                "Failed to send hello_world"
            ))
        else:
            ten_env.stop_test()

def test_basic():
    tester = ExtensionTesterBasic()
    tester.set_test_mode_single("my_example_ext_python")
    err = tester.run()
    if err is not None:
        assert False, err.error_message()

调试环境配置

VSCode 集成调试

确保已安装 Python 扩展和 debugpy 调试器,使用以下配置进行调试:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "standalone test (debugpy, launch)",
      "type": "debugpy",
      "request": "launch",
      "python": "/usr/bin/python3",
      "module": "pytest",
      "args": ["-s", "${workspaceFolder}/tests/test_basic.py"],
      "env": {
        "TEN_ENABLE_PYTHON_DEBUG": "true",
        "PYTHONPATH": "${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime_python/lib:${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime_python/interface:${workspaceFolder}"
      },
      "console": "integratedTerminal"
    }
  ]
}

Node.js 扩展开发

Node.js 扩展提供了现代 JavaScript/TypeScript 开发体验,特别适合 Web 应用集成、快速原型开发、前端技术栈扩展等场景。得益于 Node.js 的异步特性和丰富的生态系统,开发者可以轻松构建高效的实时应用。

创建项目

使用 TEN 官方提供的 Node.js 扩展模板快速创建新项目:

bash
tman create extension my_example_ext_nodejs --template default_extension_nodejs --template-data class_name_prefix=Example

项目创建成功后,您会得到以下完整的项目结构:

bash
my_example_ext_nodejs/
├── manifest.json         # 扩展元数据和配置信息
├── property.json         # 扩展属性和参数配置
├── package.json          # Node.js 依赖包管理配置
├── tsconfig.json         # TypeScript 编译器配置
├── src/
│   └── index.ts          # 扩展核心实现代码
├── tests/                # 测试相关文件
│   ├── src/
│   │   ├── index.ts      # 测试器实现
│   │   ├── index.spec.ts # 测试用例定义
│   │   └── main.spec.ts  # 测试框架配置
│   ├── bin/start         # 测试启动脚本
│   ├── package.json      # 测试依赖配置
│   └── tsconfig.json     # 测试 TypeScript 配置
├── tools/                # 辅助工具和脚本
└── .vscode/
    └── launch.json       # VSCode 调试配置文件

环境配置验证

进入项目目录并验证开发环境:

bash
cd my_example_ext_nodejs
node --version
npm --version

期望输出:

bash
v18.0.0 或更高版本
8.0.0 或更高版本

依赖包安装

安装扩展运行时所需的全部依赖包:

bash
tman install --standalone

安装成功后,您将看到类似以下的详细安装日志:

bash
📦  Get all installed packages...
🔍  Filter compatible packages...
🔒  Creating manifest-lock.json...
📥  Installing packages...
  [00:00:00] [########################################]       2/2       Done

🏆  Install successfully in 1 second

重要提示:tman install --standalone 会在项目目录下创建 .ten/app/ten_packages/extension/my_example_ext_nodejs/ 目录,后续的构建和测试操作都需要在这个目录下进行。

项目构建

Node.js 扩展使用 TypeScript 进行开发,需要先安装独立模式依赖,然后编译为 JavaScript:

方式一:手动构建

bash
# 进入扩展的安装目录
cd .ten/app/ten_packages/extension/my_example_ext_nodejs

# 安装独立模式的 Node.js 依赖
npm run standalone-install

# 编译 TypeScript 代码
npm run build

方式二:使用 tman 快捷命令

bash
tman run build

构建完成后,检查生成的编译结果:

bash
ls -la .ten/app/ten_packages/extension/my_example_ext_nodejs/build/
# 应该能看到:index.js 和相关映射文件

运行测试

验证扩展功能是否正常工作:

方式一:使用启动脚本

bash
# 进入扩展的安装目录
cd .ten/app/ten_packages/extension/my_example_ext_nodejs

# 运行测试
./tests/bin/start

方式二:使用 tman 统一命令

bash
tman run test

测试执行成功时的输出示例:

bash
MyExtensionTester onStart
MyExtensionTester onStop
MyExtensionTester onDeinit
deinit done
  ✓ case1 (1010ms)
MyExtensionTester onStart
MyExtensionTester onStop
MyExtensionTester onDeinit
deinit done
  ✓ case2 (1010ms)

  2 passing (2s)

成功标志:当您看到所有测试用例显示 ✓ 并且最后显示 passing 时表示测试全部通过。

核心代码结构详解

扩展主体实现(src/index.ts)

Node.js 扩展的核心类需要继承自 Extension 基类,并实现完整的生命周期管理方法:

typescript
import {
  Addon,
  RegisterAddonAsExtension,
  Extension,
  TenEnv,
  Cmd,
  CmdResult,
  StatusCode,
} from "ten-runtime-nodejs";

class DefaultExtension extends Extension {
  constructor(name: string) {
    super(name);
  }

  // 扩展配置阶段 - 进行配置参数的读取和验证
  async onConfigure(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onConfigure");
  }

  // 扩展初始化阶段 - 进行基础配置和资源预分配
  async onInit(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onInit");
  }

  // 扩展启动阶段 - 正式开始处理业务逻辑
  async onStart(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onStart");
  }

  // 命令处理器 - 处理来自其他扩展或应用的命令请求
  async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
    console.log("DefaultExtension onCmd", cmd.getName());

    const cmdResult = CmdResult.Create(StatusCode.OK, cmd);
    cmdResult.setPropertyString("detail", "This is a demo");
    tenEnv.returnResult(cmdResult);
  }

  // 扩展停止阶段 - 清理资源和优雅关闭
  async onStop(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onStop");
  }

  // 扩展销毁阶段 - 最终清理和资源释放
  async onDeinit(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onDeinit");
  }
}

插件注册入口

扩展插件的注册和创建逻辑:

typescript
@RegisterAddonAsExtension("default_extension_nodejs")
class DefaultExtensionAddon extends Addon {
  async onCreateInstance(
    _tenEnv: TenEnv,
    instanceName: string,
  ): Promise<Extension> {
    return new DefaultExtension(instanceName);
  }
}

测试框架实现(tests/src/index.ts)

使用 TEN 专用测试框架编写完整的单元测试:

typescript
import { ExtensionTester, TenEnvTester } from "ten-runtime-nodejs";

export class MyExtensionTester extends ExtensionTester {
  async onStart(tenEnvTester: TenEnvTester) {
    console.log("MyExtensionTester onStart");

    // 模拟异步操作和测试逻辑
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(true);
      }, 1000);
    }).then(() => {
      // 停止测试并返回结果
      tenEnvTester.stopTest();
    });
  }

  async onStop(tenEnvTester: TenEnvTester) {
    console.log("MyExtensionTester onStop");
  }

  async onDeinit(tenEnvTester: TenEnvTester) {
    console.log("MyExtensionTester onDeinit");
  }
}

测试用例定义(tests/src/index.spec.ts)

使用 Mocha 测试框架编写具体的测试用例:

typescript
import assert from "assert";
import { MyExtensionTester } from "./index.js";

const test_addon_name = "default_extension_nodejs";

describe("MyExtensionTester", () => {
  it("case1", async () => {
    const extensionTester = new MyExtensionTester();
    extensionTester.setTestModeSingle(test_addon_name, "{}");
    const result = await extensionTester.run();
    assert(result === null, "result should be null");

    console.log("deinit done");
  });

  it("case2", async () => {
    const extensionTester = new MyExtensionTester();
    extensionTester.setTestModeSingle(test_addon_name, "{}");
    const result = await extensionTester.run();
    assert(result === null, "result should be null");

    console.log("deinit done");
  });
});

TypeScript 配置

Node.js 扩展使用现代 TypeScript 配置,支持最新的语言特性:

json
{
  "compilerOptions": {
    "allowJs": false,
    "composite": true,
    "module": "NodeNext",
    "target": "es6",
    "moduleResolution": "NodeNext",
    "outDir": "build",
    "removeComments": false,
    "sourceMap": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "strictNullChecks": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "rootDir": "src",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "src/**/*"
  ]
}

调试环境配置

VSCode 集成调试

确保已安装 Node.js 官方扩展,然后使用以下调试配置:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "standalone test (nodejs) (mocha, launch)",
      "type": "node",
      "request": "launch",
      "program": "node_modules/mocha/bin/_mocha",
      "stopOnEntry": true,
      "args": [
        "--no-timeouts",
        "--package",
        "package.json",
      ],
      "cwd": "${workspaceFolder}/tests",
      "env": {
        "NODE_PATH": "../.ten/app/ten_packages/system/ten_runtime_nodejs/lib:$NODE_PATH",
      },
      "runtimeArgs": [
        "--expose-gc",
        "--loader",
        "ts-node/esm",
        "--no-warnings",
      ]
    }
  ]
}

命令行调试

使用 Node.js 内置调试器进行命令行调试:

bash
# 进入扩展的安装目录
cd .ten/app/ten_packages/extension/my_example_ext_nodejs

# 启动调试模式
node --inspect-brk ./tests/bin/start

# 或者使用 Chrome DevTools
node --inspect ./tests/bin/start

完整开发流程总结

为了帮助开发者快速上手,这里提供一个完整的 Node.js 扩展开发流程总结:

bash
# 1. 创建扩展项目
tman create extension my_example_ext_nodejs --template default_extension_nodejs --template-data class_name_prefix=Example

# 2. 进入项目目录
cd my_example_ext_nodejs

# 3. 安装依赖
tman install --standalone

# 4. 进入扩展安装目录
cd .ten/app/ten_packages/extension/my_example_ext_nodejs

# 5. 安装独立模式依赖
npm run standalone-install

# 6. 构建项目
npm run build

# 7. 运行测试
./tests/bin/start

工作目录说明:

  • 扩展源代码位于项目根目录的 src/ 文件夹
  • 实际的构建、测试和运行操作都在 .ten/app/ten_packages/extension/my_example_ext_nodejs/ 目录下进行
  • 这种设计确保了扩展的独立性和依赖管理的正确性

开发最佳实践

异步编程

Node.js 扩展充分利用异步编程模式,提供更好的性能:

typescript
// 推荐:使用 async/await
async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
  try {
    const result = await processCommand(cmd);
    const cmdResult = CmdResult.Create(StatusCode.OK, cmd);
    cmdResult.setPropertyString("result", result);
    tenEnv.returnResult(cmdResult);
  } catch (error) {
    const cmdResult = CmdResult.Create(StatusCode.ERROR, cmd);
    cmdResult.setPropertyString("error", error.message);
    tenEnv.returnResult(cmdResult);
  }
}

错误处理

实现完善的错误处理机制:

typescript
async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
  try {
    // 业务逻辑处理
    const result = await this.handleBusinessLogic(cmd);

    const cmdResult = CmdResult.Create(StatusCode.OK, cmd);
    cmdResult.setPropertyString("data", JSON.stringify(result));
    tenEnv.returnResult(cmdResult);
  } catch (error) {
    console.error("Command processing error:", error);

    const cmdResult = CmdResult.Create(StatusCode.ERROR, cmd);
    cmdResult.setPropertyString("error", error.message);
    tenEnv.returnResult(cmdResult);
  }
}

开发总结

通过遵循本指南提供的完整开发流程,您可以高效地进行 TEN 扩展的开发、测试和调试工作。无论选择 C++,Go,Python 还是 Node.js,TEN Framework 都为您提供了完整的工具链和最佳实践,帮助您充分发挥 TEN Framework 的强大功能,构建出高性能、高可靠性的扩展应用。

每种语言都有其独特的优势和适用场景:

  • C++:适用于对性能要求极高的场景,如实时音视频处理、高频计算
  • Go:在高性能和开发效率之间提供平衡,适合网络服务、并发处理
  • Python:具有最高的开发效率,特别适合AI/ML应用、快速原型开发
  • Node.js:提供现代 Web 开发体验,适用于前端技术栈扩展、实时应用

请根据您的具体需求和团队技术栈选择最合适的开发方案。在开发过程中,建议充分利用 TEN Framework 提供的调试工具和测试框架,以确保扩展的质量和稳定性。


Development How To Develop With Ext (`docs/development/how_to_develop_with_ext.md`)


title: Complete Guide to TEN Extension Development _portal_target: development/how_to_develop_with_ext.md

TEN Framework provides rich extension templates to help developers quickly create extensions and complete the entire process from development to testing. This guide will demonstrate in detail how to use C++, Go, Python, and Node.js for the complete extension development workflow through practical operations.

Pre-development Preparation

Environment Requirements

Before starting extension development, please ensure your development environment is properly configured. Verify the installation with the following command:

bash
tman --version

Under normal circumstances, you should see version information output similar to the following:

bash
TEN Framework version: <version>

Important Note: Please ensure you are using tman version >= 0.10.12. If your version is too low, please refer to the Quick Start Guide to install the latest version.

Development Workflow Overview

Regardless of which programming language you use, TEN extension development follows the following standardized workflow:

  1. Project Creation - Generate extension project skeleton using official templates
  2. Dependency Installation - Configure runtime environment and install necessary dependency packages
  3. Core Development - Implement extension business logic and functional code
  4. Build and Test - Compile project (if needed) and execute unit tests
  5. Debug and Optimize - Use professional debugging tools to locate and solve problems

C++ Extension Development

C++ extensions are suitable for application scenarios with extremely high performance requirements, such as real-time audio and video processing, high-frequency data computation, low-level system operations, etc.

Create Project

Use the official C++ extension template provided by TEN to quickly create a new project:

bash
tman create extension my_example_ext_cpp --template default_extension_cpp

After successful project creation, you will get the following complete project structure:

bash
my_example_ext_cpp/
├── BUILD.gn              # GN build system configuration file
├── manifest.json         # Extension metadata and configuration information
├── property.json         # Extension properties and parameter configuration
├── src/
│   └── main.cc           # Extension core implementation code
├── tests/
│   ├── basic.cc          # Basic functionality test cases
│   └── gtest_main.cc     # Test framework program entry
├── include/              # Header file storage directory
├── tools/                # Auxiliary tools and scripts
└── .vscode/
    └── launch.json       # VSCode debugging configuration file

Environment Configuration Verification

Enter the project directory and verify that the build tools are working properly:

bash
cd my_example_ext_cpp
tgn --version

Expected Output:

bash
0.1.0

If the command cannot be executed, please refer to the Quick Start Guide to check whether the TEN Framework development environment is correctly installed.

Dependency Package Installation

Install all dependency packages required for extension runtime:

bash
tman install --standalone

After successful installation, you will see detailed installation logs similar to the following:

bash
📦  Get all installed packages...
🔍  Filter compatible packages...
🔒  Creating manifest-lock.json...
📥  Installing packages...
  [00:00:00] [########################################]       3/3       Done

🏆  Install successfully in 1 second

Project Build

TEN framework provides two convenient build methods for you to choose from:

Method 1: Manual Step-by-step Build

bash
# Step 1: Generate build configuration files (enable standalone test mode)
tgn gen linux x64 debug -- ten_enable_standalone_test=true

# Step 2: Execute project build
tgn build linux x64 debug

Method 2: Use tman Shortcut Command

bash
tman run build

After the build is complete, check the generated executable test file:

bash
ls -la bin/
# You should see: my_example_ext_cpp_test

Run Tests

Verify that the extension functionality is working properly:

Method 1: Execute Test File Directly

bash
./bin/my_example_ext_cpp_test

Method 2: Use tman Unified Command

bash
tman run test

Example output when tests execute successfully:

bash
Running main() from ../../../tests/gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] 1 test from Test
[ RUN      ] Test.Basic
[       OK ] Test.Basic (20 ms)
[==========] 1 test from 1 test case ran. (79 ms total)
[  PASSED  ] 1 test.

Success Indicator: When you see [ PASSED ] 1 test. it means all tests have passed.

Core Code Structure Explanation

Extension Main Implementation (src/main.cc)

The core class of C++ extensions needs to inherit from the ten::extension_t base class and implement complete lifecycle management methods:

cpp
class my_example_ext_cpp_t : public ten::extension_t {
 public:
  // Extension initialization phase - basic configuration and resource pre-allocation
  void on_init(ten::ten_env_t &ten_env) override;

  // Extension startup phase - officially start processing business logic
  void on_start(ten::ten_env_t &ten_env) override;

  // Command handler - handle command requests from other extensions or applications
  void on_cmd(ten::ten_env_t &ten_env, std::unique_ptr<ten::cmd_t> cmd) override;

  // Data handler - handle general data flow
  void on_data(ten::ten_env_t &ten_env, std::unique_ptr<ten::data_t> data) override;

  // Audio frame handler - handle real-time audio stream data
  void on_audio_frame(ten::ten_env_t &ten_env, std::unique_ptr<ten::audio_frame_t> frame) override;

  // Video frame handler - handle real-time video stream data
  void on_video_frame(ten::ten_env_t &ten_env, std::unique_ptr<ten::video_frame_t> frame) override;

  // Extension stop phase - clean up resources and graceful shutdown
  void on_stop(ten::ten_env_t &ten_env) override;
};

Test Framework Implementation (tests/basic.cc)

Use TEN-specific test framework to write complete unit tests:

cpp
class my_example_ext_cpp_tester : public ten::extension_tester_t {
 public:
  void on_start(ten::ten_env_tester_t &ten_env) override {
    // Create command object for testing
    auto new_cmd = ten::cmd_t::create("foo");

    // Send command to extension and verify response results
    ten_env.send_cmd(std::move(new_cmd), [](/* callback parameters */) {
      // Verify the correctness of test results here
    });
  }
};

Debug Environment Configuration

VSCode Integrated Debugging

  1. Install the CodeLLDB extension plugin for VSCode
  2. Set breakpoints in source code
  3. Select the "standalone test (lldb, launch)" debug configuration
  4. Press F5 to start debugging session

Debug configuration file .vscode/launch.json content:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "standalone test (lldb, launch)",
      "type": "lldb",
      "request": "launch",
      "program": "${workspaceFolder}/bin/my_example_ext_cpp_test",
      "cwd": "${workspaceFolder}"
    }
  ]
}

Command Line Debugging

Use the classic GDB debugger for command-line debugging:

bash
gdb ./bin/my_example_ext_cpp_test
(gdb) run
(gdb) bt  # View complete function call stack

Go Extension Development

Go extensions provide a good balance between high performance and development efficiency, particularly suitable for building network services, concurrent processing, microservice architecture and other application scenarios.

Create Project

Create a new project using the Go extension template:

bash
tman create extension my_example_ext_go --template default_extension_go --template-data class_name_prefix=Example

After successful project creation, it will display:

bash
🏆  Package 'extension:my_example_ext_go' created successfully in '/path/to/your/project' in 1 second.

The complete project structure is as follows:

bash
my_example_ext_go/
├── extension.go             # Extension core implementation code
├── go.mod                   # Go module dependency management configuration
├── manifest.json            # Extension metadata information
├── property.json            # Extension property configuration
├── README.md                # Project documentation
├── tests/                   # Test-related files
│   ├── basic_tester.go      # Test logic implementation
│   ├── basic_tester_test.go # Test case definition
│   ├── main_test.go         # Test program entry
│   └── bin/start            # Test startup script
└── .vscode/launch.json      # VSCode debug configuration

Dependency Package Installation

Install dependency packages required for project runtime:

bash
tman install --standalone

Run Tests

Verify the correctness of extension functionality:

Method 1: Use Startup Script

bash
./tests/bin/start

Method 2: Use tman Command

bash
tman run test

Example output when tests execute successfully:

bash
=== RUN   TestBasicExtensionTester
--- PASS: TestBasicExtensionTester (0.01s)
PASS

Core Code Structure Explanation

Extension Implementation (extension.go)

Core implementation structure of Go extensions:

go
import (
    ten "ten_framework/ten_runtime"
)

type ExampleExtension struct {
    ten.DefaultExtension
}

// Extension startup lifecycle
func (e *ExampleExtension) OnStart(tenEnv ten.TenEnv) {
    tenEnv.LogDebug("OnStart")
    tenEnv.OnStartDone()
}

// Command handling logic
func (e *ExampleExtension) OnCmd(tenEnv ten.TenEnv, cmd ten.Cmd) {
    tenEnv.LogDebug("OnCmd")
    cmdResult, _ := ten.NewCmdResult(ten.StatusCodeOk, cmd)
    tenEnv.ReturnResult(cmdResult, nil)
}

// Extension stop lifecycle
func (e *ExampleExtension) OnStop(tenEnv ten.TenEnv) {
    tenEnv.LogDebug("OnStop")
    tenEnv.OnStopDone()
}

Test Framework (tests/basic_tester.go)

Go extension test framework implementation:

go
type BasicExtensionTester struct {
    ten.DefaultExtensionTester
}

func (tester *BasicExtensionTester) OnStart(tenEnvTester ten.TenEnvTester) {
    // Create command object for testing
    cmdTest, _ := ten.NewCmd("test")

    // Send command and handle response results
    tenEnvTester.SendCmd(cmdTest, func(tet ten.TenEnvTester, cr ten.CmdResult, err error) {
        if err != nil {
            panic(err)
        }
        statusCode, _ := cr.GetStatusCode()
        if statusCode != ten.StatusCodeOk {
            panic(statusCode)
        }
        tenEnvTester.StopTest(nil)
    })

    tenEnvTester.OnStartDone()
}

Development Environment Optimization

To improve development experience and debugging convenience, it is recommended to create a go.work workspace file in the extension root directory:

go
go 1.18

use (
    .
    .ten/app/ten_packages/system/ten_runtime_go/interface
)

Debug Environment Configuration

VSCode Integrated Debugging

Make sure the official Go extension is installed, then use the following debug configuration:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "standalone test (go, launch)",
      "type": "go",
      "request": "launch",
      "mode": "test",
      "program": "${workspaceFolder}/tests/",
      "env": {
        "CGO_LDFLAGS": "-L${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime/lib -L${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime_go/lib -lten_runtime -lten_runtime_go",
        "LD_LIBRARY_PATH": "${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime/lib:${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime_go/lib"
      },
      "args": ["-test.v"]
    }
  ]
}

Python Extension Development

Python extensions have the highest development efficiency, particularly suitable for rapid prototyping, AI/ML application integration, complex business logic implementation and other scenarios.

Create Project

Create a project using the Python async extension template:

bash
tman create extension my_example_ext_python --template default_async_extension_python --template-data class_name_prefix=Example

Complete project structure:

bash
my_example_ext_python/
├── extension.py         # Extension core implementation code
├── addon.py             # Extension plugin registration entry
├── __init__.py          # Python package initialization file
├── requirements.txt     # Python dependency package list
├── manifest.json        # Extension metadata configuration
├── property.json        # Extension property configuration
├── tests/               # Test-related files
│   ├── test_basic.py    # Basic test cases
│   ├── conftest.py      # pytest configuration file
│   └── bin/start        # Test startup script
└── .vscode/launch.json  # VSCode debug configuration

Dependency Package Installation

Install Python dependency packages required for the project:

bash
tman install --standalone

Run Tests

Verify Python extension functionality:

Method 1: Use Startup Script

bash
./tests/bin/start

Method 2: Use tman Command

bash
tman run test

Example output when tests execute successfully:

bash
============================================ test session starts ============================================
platform linux -- Python 3.10.17, pytest-8.3.4, pluggy-1.5.0
tests/test_basic.py .                                                                                [100%]
============================================ 1 passed in 0.04s =======================================

Core Code Structure Explanation

Extension Implementation (extension.py)

Python extensions recommend using modern async programming patterns for better performance and concurrent processing capabilities:

python
from ten_runtime import (
    AudioFrame, VideoFrame, AsyncExtension, AsyncTenEnv,
    Cmd, StatusCode, CmdResult, Data
)

class ExampleExtension(AsyncExtension):
    async def on_init(self, ten_env: AsyncTenEnv) -> None:
        ten_env.log_debug("on_init")

    async def on_start(self, ten_env: AsyncTenEnv) -> None:
        ten_env.log_debug("on_start")
        # TODO: Read configuration files here, initialize necessary resources

    async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None:
        cmd_name = cmd.get_name()
        ten_env.log_debug(f"on_cmd name {cmd_name}")

        # TODO: Implement specific business logic processing here
        cmd_result = CmdResult.create(StatusCode.OK, cmd)
        await ten_env.return_result(cmd_result)

    async def on_stop(self, ten_env: AsyncTenEnv) -> None:
        ten_env.log_debug("on_stop")
        # TODO: Clean up resources here, perform graceful shutdown

Plugin Registration Entry (addon.py)

Extension plugin registration and creation logic:

python
from ten_runtime import Addon, register_addon_as_extension, TenEnv
from .extension import ExampleExtension

@register_addon_as_extension("my_example_ext_python")
class ExampleExtensionAddon(Addon):
    def on_create_instance(self, ten_env: TenEnv, name: str, context) -> None:
        ten_env.log_info("on_create_instance")
        ten_env.on_create_instance_done(ExampleExtension(name), context)

Test Implementation (tests/test_basic.py)

Complete async test framework implementation:

python
from ten_runtime import (
    AsyncExtensionTester, AsyncTenEnvTester, Cmd, StatusCode,
    TenError, TenErrorCode
)

class ExtensionTesterBasic(AsyncExtensionTester):
    async def on_start(self, ten_env: AsyncTenEnvTester) -> None:
        # Create command object for testing
        new_cmd = Cmd.create("hello_world")

        ten_env.log_debug("send hello_world")
        result, err = await ten_env.send_cmd(new_cmd)

        # Verify the correctness of test results
        if (err is not None or result is None
            or result.get_status_code() != StatusCode.OK):
            ten_env.stop_test(TenError.create(
                TenErrorCode.ErrorCodeGeneric,
                "Failed to send hello_world"
            ))
        else:
            ten_env.stop_test()

def test_basic():
    tester = ExtensionTesterBasic()
    tester.set_test_mode_single("my_example_ext_python")
    err = tester.run()
    if err is not None:
        assert False, err.error_message()

Debug Environment Configuration

VSCode Integrated Debugging

Make sure you have installed the Python extension and debugpy debugger, use the following configuration for debugging:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "standalone test (debugpy, launch)",
      "type": "debugpy",
      "request": "launch",
      "python": "/usr/bin/python3",
      "module": "pytest",
      "args": ["-s", "${workspaceFolder}/tests/test_basic.py"],
      "env": {
        "TEN_ENABLE_PYTHON_DEBUG": "true",
        "PYTHONPATH": "${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime_python/lib:${workspaceFolder}/.ten/app/ten_packages/system/ten_runtime_python/interface:${workspaceFolder}"
      },
      "console": "integratedTerminal"
    }
  ]
}

Node.js Extension Development

Node.js extensions provide a modern JavaScript/TypeScript development experience, particularly suitable for Web application integration, rapid prototyping, frontend technology stack extensions and other scenarios. Thanks to Node.js's async characteristics and rich ecosystem, developers can easily build efficient real-time applications.

Create Project

Use the official Node.js extension template provided by TEN to quickly create a new project:

bash
tman create extension my_example_ext_nodejs --template default_extension_nodejs --template-data class_name_prefix=Example

After successful project creation, you will get the following complete project structure:

bash
my_example_ext_nodejs/
├── manifest.json         # Extension metadata and configuration information
├── property.json         # Extension properties and parameter configuration
├── package.json          # Node.js dependency package management configuration
├── tsconfig.json         # TypeScript compiler configuration
├── src/
│   └── index.ts          # Extension core implementation code
├── tests/                # Test-related files
│   ├── src/
│   │   ├── index.ts      # Tester implementation
│   │   ├── index.spec.ts # Test case definition
│   │   └── main.spec.ts  # Test framework configuration
│   ├── bin/start         # Test startup script
│   ├── package.json      # Test dependency configuration
│   └── tsconfig.json     # Test TypeScript configuration
├── tools/                # Auxiliary tools and scripts
└── .vscode/
    └── launch.json       # VSCode debug configuration file

Environment Configuration Verification

Enter the project directory and verify the development environment:

bash
cd my_example_ext_nodejs
node --version
npm --version

Expected Output:

bash
v18.0.0 or higher
8.0.0 or higher

Dependency Package Installation

Install all dependency packages required for extension runtime:

bash
tman install --standalone

After successful installation, you will see detailed installation logs similar to the following:

bash
📦  Get all installed packages...
🔍  Filter compatible packages...
🔒  Creating manifest-lock.json...
📥  Installing packages...
  [00:00:00] [########################################]       2/2       Done

🏆  Install successfully in 1 second

Important Note: tman install --standalone will create a .ten/app/ten_packages/extension/my_example_ext_nodejs/ directory in the project directory. Subsequent build and test operations need to be performed in this directory.

Project Build

Node.js extensions are developed using TypeScript and need to install standalone mode dependencies first, then compile to JavaScript:

Method 1: Manual Build

bash
# Enter the extension installation directory
cd .ten/app/ten_packages/extension/my_example_ext_nodejs

# Install standalone mode Node.js dependencies
npm run standalone-install

# Compile TypeScript code
npm run build

Method 2: Use tman Shortcut Command

bash
tman run build

After the build is complete, check the generated compilation results:

bash
ls -la .ten/app/ten_packages/extension/my_example_ext_nodejs/build/
# You should see: index.js and related mapping files

Run Tests

Verify that the extension functionality is working properly:

Method 1: Use Startup Script

bash
# Enter the extension installation directory
cd .ten/app/ten_packages/extension/my_example_ext_nodejs

# Run tests
./tests/bin/start

Method 2: Use tman Unified Command

bash
tman run test

Example output when tests execute successfully:

bash
MyExtensionTester onStart
MyExtensionTester onStop
MyExtensionTester onDeinit
deinit done
  ✓ case1 (1010ms)
MyExtensionTester onStart
MyExtensionTester onStop
MyExtensionTester onDeinit
deinit done
  ✓ case2 (1010ms)

  2 passing (2s)

Success Indicator: When you see all test cases showing ✓ and finally displaying passing, it means all tests have passed.

Core Code Structure Explanation

Extension Main Implementation (src/index.ts)

The core class of Node.js extensions needs to inherit from the Extension base class and implement complete lifecycle management methods:

typescript
import {
  Addon,
  RegisterAddonAsExtension,
  Extension,
  TenEnv,
  Cmd,
  CmdResult,
  StatusCode,
} from "ten-runtime-nodejs";

class DefaultExtension extends Extension {
  constructor(name: string) {
    super(name);
  }

  // Extension configuration phase - read and validate configuration parameters
  async onConfigure(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onConfigure");
  }

  // Extension initialization phase - basic configuration and resource pre-allocation
  async onInit(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onInit");
  }

  // Extension startup phase - officially start processing business logic
  async onStart(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onStart");
  }

  // Command handler - handle command requests from other extensions or applications
  async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
    console.log("DefaultExtension onCmd", cmd.getName());

    const cmdResult = CmdResult.Create(StatusCode.OK, cmd);
    cmdResult.setPropertyString("detail", "This is a demo");
    tenEnv.returnResult(cmdResult);
  }

  // Extension stop phase - clean up resources and graceful shutdown
  async onStop(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onStop");
  }

  // Extension destruction phase - final cleanup and resource release
  async onDeinit(_tenEnv: TenEnv): Promise<void> {
    console.log("DefaultExtension onDeinit");
  }
}

Plugin Registration Entry

Extension plugin registration and creation logic:

typescript
@RegisterAddonAsExtension("default_extension_nodejs")
class DefaultExtensionAddon extends Addon {
  async onCreateInstance(
    _tenEnv: TenEnv,
    instanceName: string,
  ): Promise<Extension> {
    return new DefaultExtension(instanceName);
  }
}

Test Framework Implementation (tests/src/index.ts)

Use TEN-specific test framework to write complete unit tests:

typescript
import { ExtensionTester, TenEnvTester } from "ten-runtime-nodejs";

export class MyExtensionTester extends ExtensionTester {
  async onStart(tenEnvTester: TenEnvTester) {
    console.log("MyExtensionTester onStart");

    // Simulate async operations and test logic
    new Promise((resolve) => {
      setTimeout(() => {
        resolve(true);
      }, 1000);
    }).then(() => {
      // Stop test and return results
      tenEnvTester.stopTest();
    });
  }

  async onStop(tenEnvTester: TenEnvTester) {
    console.log("MyExtensionTester onStop");
  }

  async onDeinit(tenEnvTester: TenEnvTester) {
    console.log("MyExtensionTester onDeinit");
  }
}

Test Case Definition (tests/src/index.spec.ts)

Use Mocha test framework to write specific test cases:

typescript
import assert from "assert";
import { MyExtensionTester } from "./index.js";

const test_addon_name = "default_extension_nodejs";

describe("MyExtensionTester", () => {
  it("case1", async () => {
    const extensionTester = new MyExtensionTester();
    extensionTester.setTestModeSingle(test_addon_name, "{}");
    const result = await extensionTester.run();
    assert(result === null, "result should be null");

    console.log("deinit done");
  });

  it("case2", async () => {
    const extensionTester = new MyExtensionTester();
    extensionTester.setTestModeSingle(test_addon_name, "{}");
    const result = await extensionTester.run();
    assert(result === null, "result should be null");

    console.log("deinit done");
  });
});

TypeScript Configuration

Node.js extensions use modern TypeScript configuration, supporting the latest language features:

json
{
  "compilerOptions": {
    "allowJs": false,
    "composite": true,
    "module": "NodeNext",
    "target": "es6",
    "moduleResolution": "NodeNext",
    "outDir": "build",
    "removeComments": false,
    "sourceMap": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "strictNullChecks": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "rootDir": "src",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "src/**/*"
  ]
}

Debug Environment Configuration

VSCode Integrated Debugging

Make sure you have installed the official Node.js extension, then use the following debug configuration:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "standalone test (nodejs) (mocha, launch)",
      "type": "node",
      "request": "launch",
      "program": "node_modules/mocha/bin/_mocha",
      "stopOnEntry": true,
      "args": [
        "--no-timeouts",
        "--package",
        "package.json",
      ],
      "cwd": "${workspaceFolder}/tests",
      "env": {
        "NODE_PATH": "../.ten/app/ten_packages/system/ten_runtime_nodejs/lib:$NODE_PATH",
      },
      "runtimeArgs": [
        "--expose-gc",
        "--loader",
        "ts-node/esm",
        "--no-warnings",
      ]
    }
  ]
}

Command Line Debugging

Use Node.js built-in debugger for command-line debugging:

bash
# Enter the extension installation directory
cd .ten/app/ten_packages/extension/my_example_ext_nodejs

# Start debug mode
node --inspect-brk ./tests/bin/start

# Or use Chrome DevTools
node --inspect ./tests/bin/start

Complete Development Process Summary

To help developers get started quickly, here is a complete Node.js extension development process summary:

bash
# 1. Create extension project
tman create extension my_example_ext_nodejs --template default_extension_nodejs --template-data class_name_prefix=Example

# 2. Enter project directory
cd my_example_ext_nodejs

# 3. Install dependencies
tman install --standalone

# 4. Enter extension installation directory
cd .ten/app/ten_packages/extension/my_example_ext_nodejs

# 5. Install standalone mode dependencies
npm run standalone-install

# 6. Build project
npm run build

# 7. Run tests
./tests/bin/start

Working Directory Note:

  • Extension source code is located in the src/ folder at the project root directory
  • Actual build, test, and run operations are performed in the .ten/app/ten_packages/extension/my_example_ext_nodejs/ directory
  • This design ensures extension independence and proper dependency management

Development Best Practices

Async Programming

Node.js extensions fully utilize async programming patterns for better performance:

typescript
// Recommended: Use async/await
async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
  try {
    const result = await processCommand(cmd);
    const cmdResult = CmdResult.Create(StatusCode.OK, cmd);
    cmdResult.setPropertyString("result", result);
    tenEnv.returnResult(cmdResult);
  } catch (error) {
    const cmdResult = CmdResult.Create(StatusCode.ERROR, cmd);
    cmdResult.setPropertyString("error", error.message);
    tenEnv.returnResult(cmdResult);
  }
}

Error Handling

Implement comprehensive error handling mechanisms:

typescript
async onCmd(tenEnv: TenEnv, cmd: Cmd): Promise<void> {
  try {
    // Business logic processing
    const result = await this.handleBusinessLogic(cmd);

    const cmdResult = CmdResult.Create(StatusCode.OK, cmd);
    cmdResult.setPropertyString("data", JSON.stringify(result));
    tenEnv.returnResult(cmdResult);
  } catch (error) {
    console.error("Command processing error:", error);

    const cmdResult = CmdResult.Create(StatusCode.ERROR, cmd);
    cmdResult.setPropertyString("error", error.message);
    tenEnv.returnResult(cmdResult);
  }
}

Development Summary

By following the complete development process provided in this guide, you can efficiently develop, test, and debug TEN extensions. Whether you choose C++, Go, Python, or Node.js, TEN Framework provides you with a complete toolchain and best practices to help you fully leverage the powerful features of TEN Framework and build high-performance, high-reliability extension applications.

Each language has its unique advantages and applicable scenarios:

  • C++: Suitable for scenarios with extremely high performance requirements, such as real-time audio and video processing, high-frequency computation
  • Go: Provides a balance between high performance and development efficiency, suitable for network services, concurrent processing
  • Python: Has the highest development efficiency, particularly suitable for AI/ML applications, rapid prototyping
  • Node.js: Provides modern Web development experience, suitable for frontend technology stack extensions, real-time applications

Please choose the most suitable development solution based on your specific needs and team technology stack. During the development process, it is recommended to fully utilize the debugging tools and testing frameworks provided by TEN Framework to ensure the quality and stability of your extensions.


Getting Started Quick Start.Cn (`docs/getting-started/quick-start.cn.md`)


title: TEN Framework 快速入门指南 _portal_target: getting-started/quick-start.cn.md

TEN Framework 快速入门指南

🎯 目标:5分钟内搭建开发环境并运行第一个 TEN 应用

系统要求

支持的操作系统:

  • Linux (x64)
  • Linux (arm64)
  • macOS Intel (x64)
  • macOS Apple Silicon (arm64)
  • Windows (x64)

必需的软件环境:

  • Python 3.10
  • Go 1.20+
  • Node.js / npm(用于安装和管理 JavaScript 依赖)

💡 Windows 用户注意:推荐使用 PowerShell 执行本指南中的命令。部分命令在 CMD 中可能不兼容。

第一步:检查环境

在开始之前,请确保你的系统已安装以下软件:

Python 3.10

bash
python3 --version
# 应显示: Python 3.10.x

💡 重要:TEN Framework 目前仅支持 Python 3.10。推荐使用 pyenv 或 venv 创建 Python 虚拟环境:

Linux / macOS:

bash
# 使用 pyenv 安装和管理 Python 3.10(推荐)
pyenv install 3.10.18
pyenv local 3.10.18

# 或使用 venv 创建虚拟环境
python3.10 -m venv ~/ten-venv
source ~/ten-venv/bin/activate

Windows:

从 https://www.python.org/downloads/release/python-31011/ 底部表格中下载 Windows installer,启动后安装 Python 3.10。

注意,点击Install Now前务必勾选 "Add Python to PATH"

bash
# Windows平台需要配置python3命令:

# 首先,确认python的安装路径:
where.exe python
# 输出示例: C:\Users\YourName\AppData\Local\Programs\Python\Python310\python.exe

# 然后,以管理员身份打开 PowerShell,在python.exe同目录下创建symlink:
New-Item -ItemType SymbolicLink -Path "C:\Users\YourName\AppData\Local\Programs\Python\Python310\python3.exe" -Target "C:\Users\YourName\AppData\Local\Programs\Python\Python310\python.exe"
# 请将上面的路径替换为 where.exe python 的实际输出路径

# 验证:
python3 --version
powershell
# 推荐安装后使用 venv 创建虚拟环境,在该环境中工作
py -3.10 -m venv $env:USERPROFILE\ten-venv
# 激活环境
& "$env:USERPROFILE\ten-venv\Scripts\Activate.ps1"

# 若有权限错误,关闭终端/IDE,右键选择“以管理员身份运行”重新打开
# 或者改变执行策略来允许ps1脚本执行
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

Go 1.20+

bash
go version
# 应显示: go version go1.20 或更高版本

$env:CGO_ENABLED = "1"
# Windows下需要显式启用CGO

Node.js / npm

bash
node --version
npm --version
# 确保 node 和 npm 命令可用

GCC (MinGW)

💡仅在windows上需要安装

bash
# 首先确定winget存在。
winget --version

# 若不存在则需要从 <https://apps.microsoft.com/detail/9nblggh4nns1?hl=en-US&gl=US> 安装。
#(系统需要满足:Windows10 高于 1709 (Build 16299),或者是Windows11)

#安装MinGW
winget install BrechtSanders.WinLibs.POSIX.MSVCRT

# 或查找后自行选择合适版本安装
winget search "mingw"

#检查安装
gcc --version

💡 提示:如果缺少上述环境,请先安装对应版本后再继续。

第二步:安装 TEN Manager (tman)

TEN Manager (tman) 是 TEN Framework 的命令行工具,用于创建项目、管理依赖和运行应用。

方式一:通过包管理器安装(推荐)

Linux (Ubuntu/Debian):

bash
sudo add-apt-repository ppa:ten-framework/ten-framework
sudo apt update
sudo apt install tman

macOS:

bash
brew install TEN-framework/ten-framework/tman

Windows:

powershell
winget install TEN-framework.tman

方式二:通过安装脚本

Linux / macOS:

bash
bash <(curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.sh)

Windows:

powershell
irm https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.ps1 | iex

方式三:如果你已经克隆了仓库,使用仓库内的安装脚本

bash
cd ten-framework

# Linux/MacOS
bash tools/tman/install_tman.sh
# Windows
& "C:\sw\ten-framework\tools\tman\install_tman.ps1"

💡 提示:如果系统中已经安装了 tman,安装脚本会询问是否重新安装/升级,输入 y 继续安装,输入 n 取消。

非交互式安装(适用于自动化脚本或 CI 环境,Windows不可用):

bash
# 远程安装
yes y | bash <(curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.sh)

# 本地安装
yes y | bash tools/tman/install_tman.sh

验证安装:

bash
tman --version

💡 提示:如果出现类似

  • tman: command not found(Linux/macOS)

  • 无法将"tman"项识别为 cmdlet、函数、脚本文件或可运行程序的名称(Windows)

    的提示,请确保 tman 所在目录在你的 PATH 中:

Linux / macOS:

bash
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc  # Linux
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc   # macOS
source ~/.bashrc  # 或 source ~/.zshrc

Windows:

powershell
# 检查 PATH 中是否包含 tman
$env:Path -split ";" | Select-String "tman"
# 如果不包含,请
# 1. 通过 "系统属性 → 环境变量" 手动添加,然后重启终端以应用
# 2. 运行以下命令来临时添加路径
$env:PATH += ";$env:LOCALAPPDATA\tman"

第三步:创建并运行示例应用

1. 创建应用

bash
# 创建一个新的 transcriber_demo 应用
tman install app transcriber_demo
cd transcriber_demo

2. 安装依赖

bash
# 安装 TEN 包依赖
tman install

# 安装 Python 和 npm 包的依赖
tman run install_deps

⏱️ 预计时间:1-2 分钟

3. 构建应用

bash
tman run build

⏱️ 预计时间:30 秒

4. 配置环境变量

在运行应用前,需要配置 ASR(语音识别)服务的密钥。当前示例使用 Azure ASR extension,你需要在 transcriber_demo/.env 文件中填写相关配置:

Linux / macOS:

bash
# 创建 .env 文件
cat > .env << EOF
# Azure Speech Service 配置
AZURE_STT_KEY=your_azure_speech_api_key
AZURE_STT_REGION=your_azure_region      # 例如:eastus
AZURE_STT_LANGUAGE=en-US                # 根据你的音频语种或实时录音语种设置,如:zh-CN, ja-JP, ko-KR 等
EOF

Windows(PowerShell):

powershell
# 创建 .env 文件
@"
# Azure Speech Service 配置
AZURE_STT_KEY=your_azure_speech_api_key
AZURE_STT_REGION=your_azure_region
AZURE_STT_LANGUAGE=en-US
"@ | Out-File -Encoding utf8 .env

💡 提示:Windows 用户也可以直接用文本编辑器创建 .env 文件并填写上述配置。

💡 提示:如果你想使用其他 ASR extension(如 OpenAI Whisper、Google Speech 等),可以从云商店下载并替换,同样将相应的 API key 等环境变量配置在 .env 文件中。

5. 运行应用

bash
tman run start

如果一切正常,你应该看到类似以下的输出:

text
[web_audio_control_go] Web server started on port 8001
[audio_file_player_python] AudioFilePlayerExtension on_start

6. 体验 Demo

打开浏览器访问:

text
http://localhost:8001

你应该能看到 Transcriber Demo 的 Web 界面,可以尝试:

  • 点击麦克风按钮进行实时语音转录
  • 上传音频文件进行转录
  • 查看实时转录以及字幕结果

恭喜!🎉

你已经成功运行了第一个 TEN 应用!

了解应用架构

这个 transcriber_demo 应用展示了 TEN Framework 的多语言扩展能力,它由以下组件构成:

  • Go - WebSocket 服务器扩展 (web_audio_control_go)
  • Python - ASR 语音识别扩展 (azure_asr_python)
  • TypeScript - VTT 字幕生成和音频录制扩展 (vtt_nodejs)

🎯 你已经可以运行这些多语言插件了!

下一步

现在你可以:

  1. 从云商店探索和下载更多插件,设计和编排你的应用

    bash
    tman designer  # 启动 TMAN Designer,在云商店中探索插件、下载插件并设计编排你的应用
  2. 选择一个语言,开发自己的插件

进阶:开发和构建 C++ 插件

如果你想开发和使用 C++ 扩展,推荐安装 TEN 构建工具链(tgn)。以下是完整的步骤:

1. 安装 tgn 构建工具

tgn 是 TEN Framework 的 C/C++ 构建系统,基于 Google 的 GN。

方式一:一键安装(推荐)

Linux / macOS:

bash
curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tgn/install_tgn.sh | bash

Windows:

powershell
irm https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tgn/install_tgn.ps1 | iex

方式二:从克隆的仓库安装

Linux / macOS:

bash
# 如果你已经克隆了 TEN Framework 仓库
cd ten-framework
bash tools/tgn/install_tgn.sh

Windows:

powershell
# 如果你已经克隆了 TEN Framework 仓库
& ".\tools\tgn\install_tgn.ps1"

安装完成后,确认 tgn 已添加到 PATH:

Linux / macOS:

bash
# 临时添加到当前会话
export PATH="/usr/local/ten_gn:$PATH"

# 或永久添加到 shell 配置(推荐)
echo 'export PATH="/usr/local/ten_gn:$PATH"' >> ~/.bashrc  # Linux
echo 'export PATH="/usr/local/ten_gn:$PATH"' >> ~/.zshrc   # macOS
source ~/.bashrc  # 或 source ~/.zshrc

Windows:

powershell
# 安装脚本会自动添加到用户 PATH,如需手动添加:
$env:Path += ";$env:LOCALAPPDATA\ten_gn"

验证安装:

bash
tgn --help

2. 安装 C++ 扩展

以 WebRTC VAD(语音活动检测)扩展为例,从云商店安装 C++ 扩展:

bash
cd transcriber_demo
tman install extension webrtc_vad_cpp

💡 提示:webrtc_vad_cpp 是一个用 C++ 实现的语音活动检测扩展,可以在实时语音识别场景中筛选出语音部分。

3. 编译 C++ 扩展

安装 C++ 扩展后,需要重新构建应用以编译 C++ 代码为动态库:

bash
tman run build

⏱️ 预计时间:首次编译 C++ 扩展可能需要 1-3 分钟,具体取决于你的机器性能。

4. 运行带有 VAD 功能的应用

bash
tman run start_with_vad

如果一切正常,你应该看到:

text
[web_audio_control_go] Web server started on port 8001
[vad] WebRTC VAD initialized with mode 2
[audio_file_player_python] AudioFilePlayerExtension on_start

现在打开浏览器访问 http://localhost:8001,进入麦克风实时转写页面,你会看到经过vad后的silence状态变化,当silence状态为true时,表示当前音频中没有语音。

C++ 开发环境要求

开发和编译 C++ 扩展需要安装 C++ 编译器(gcc 或 clang):

Linux:

bash
# Ubuntu/Debian
sudo apt-get install gcc g++

# 或使用 clang
sudo apt-get install clang

macOS:

bash
# 安装 Xcode Command Line Tools (包含 clang)
xcode-select --install

Windows:

powershell
# 方式一:安装 Visual Studio Build Tools(推荐)
# 从 https://visualstudio.microsoft.com/visual-cpp-build-tools/ 下载安装

# 方式二:使用 winget 安装
winget install Microsoft.VisualStudio.2022.BuildTools --override "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --passive"

# 💡注意:
#  - 安装时选择 "使用 C++ 的桌面开发" 工作负载
#  - 且务必勾选“适用于Windows的C++ Clang工具”

验证编译器安装:

bash
# 检查 gcc(Linux/macOS/MSYS2)
gcc --version
g++ --version

# 或检查 clang(Linux/macOS)
clang --version

Windows(使用 MSVC):

powershell
# 在 "Developer PowerShell for VS" 中执行
clang-cl --version

常见问题(C++ 扩展)

  1. tgn 命令找不到

    确保已经执行安装脚本并将 tgn 添加到 PATH:

    bash
    export PATH="/usr/local/ten_gn:$PATH"
  2. 编译失败:找不到编译器

    请参考上面的"C++ 开发环境要求"部分安装编译器。

了解更多

常见问题

1. macOS 上 Python 库加载失败

问题:运行应用时提示找不到 libpython3.10.dylib

解决方案:

bash
export DYLD_LIBRARY_PATH=/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.10/lib:$DYLD_LIBRARY_PATH

建议将这行添加到 ~/.zshrc 或 ~/.bash_profile 中。

2. tman 下载失败或速度很慢

问题:网络连接 GitHub 受限

解决方案:

  • 手动下载:访问 Releases 页面 下载对应平台的 tman 二进制文件

3. 端口 8001 已被占用

问题:启动时提示端口冲突

解决方案:

  • 查找占用端口的进程:
    • Linux/macOS:lsof -i :8001
    • Windows:netstat -ano | findstr :8001
  • 杀掉该进程:
    • Linux/macOS:kill -9
    • Windows:taskkill /PID /F
  • 或修改应用配置文件(transcriber_demo/ten_packages/extension/web_audio_control_go/property.json)中的端口号

4. Go build 失败

问题:构建时提示 Go module 相关错误

解决方案:

bash
# 设置代理
# Linux/macOS
export GOPROXY=https://goproxy.cn,direct
# Windows PowerShell
$env:GOPROXY = "https://goproxy.cn,direct"

# 清理 Go module 缓存
go clean -modcache

# 重新安装依赖
cd transcriber_demo
tman run build

5. Python 依赖安装失败

问题:pip 安装超时或失败

解决方案:使用国内镜像源

bash
pip3 install --index-url https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

6. Azure语音服务报错

问题:Azure语音服务相关错误,比如认证失败

解决方案:检查.env文件中的配置是否正确,确保 AZURE_STT_KEY 和 AZURE_STT_REGION 填写无误

7. Windows下权限问题

问题:Windows下在访问文件时报PermissionError

解决方案:右键点击PowerShell,选择“以管理员身份运行”

获取帮助


Getting Started Quick Start (`docs/getting-started/quick-start.md`)


title: TEN Framework Quick Start Guide _portal_target: getting-started/quick-start.md

TEN Framework Quick Start Guide

🎯 Goal: Set up your development environment and run your first TEN app in 5 minutes

System Requirements

Supported Operating Systems:

  • Linux (x64)
  • Linux (arm64)
  • macOS Intel (x64)
  • macOS Apple Silicon (arm64)
  • Windows (x64)

Required Software:

  • Python 3.10
  • Go 1.20+
  • Node.js / npm (for managing JavaScript dependencies)

💡 Windows users: It's recommended to use PowerShell to run the commands in this guide. Some commands may not be compatible with CMD.

Step 1: Check Your Environment

Before you begin, make sure the following software is installed on your system:

Python 3.10

bash
python3 --version
# Should display: Python 3.10.x

💡 Important: TEN Framework currently only supports Python 3.10. It's recommended to use pyenv or venv to manage your Python environment:

Linux / macOS:

bash
# Install and manage Python 3.10 using pyenv (recommended)
pyenv install 3.10.14
pyenv local 3.10.14

# Or create virtual environment using venv
python3.10 -m venv ~/ten-venv
source ~/ten-venv/bin/activate

Windows:

Download the Windows installer from the bottom table at https://www.python.org/downloads/release/python-31011/ and run it to install Python 3.10.

Make sure to check "Add Python to PATH" before clicking Install Now.

bash
# On Windows, configure the python3 command:

# First, find where python is installed:
where.exe python
# Example output: C:\Users\YourName\AppData\Local\Programs\Python\Python310\python.exe

# Then, open PowerShell as Administrator and create a symlink in the same directory:
New-Item -ItemType SymbolicLink -Path "C:\Users\YourName\AppData\Local\Programs\Python\Python310\python3.exe" -Target "C:\Users\YourName\AppData\Local\Programs\Python\Python310\python.exe"
# Replace the paths above with the actual output from where.exe python

# Verify:
python3 --version
powershell
# It's recommended to create a venv after installation
py -3.10 -m venv $env:USERPROFILE\ten-venv
# Activate the environment
& "$env:USERPROFILE\ten-venv\Scripts\Activate.ps1"

# If you encounter a permission error, close the terminal/IDE, right-click and select "Run as Administrator" to reopen
# Or change the execution policy to allow ps1 scripts
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

Go 1.20+

bash
go version
# Should display: go version go1.20 or higher

$env:CGO_ENABLED = "1"
# CGO must be explicitly enabled on Windows

Node.js / npm

bash
node --version
npm --version
# Ensure node and npm commands are available

GCC (MinGW)

💡 Only required on Windows

bash
# First, make sure winget is available.
winget --version

# If not, install it from <https://apps.microsoft.com/detail/9nblggh4nns1?hl=en-US&gl=US>
# (Requires Windows 10 version 1709 (Build 16299) or later, or Windows 11)

# Install MinGW
winget install BrechtSanders.WinLibs.POSIX.MSVCRT

# Or search and choose a suitable version to install
winget search "mingw"

# Verify installation
gcc --version

💡 Tip: If any of the above is missing, please install the required version before continuing.

Step 2: Install TEN Manager (tman)

TEN Manager (tman) is the command-line tool for TEN Framework, used to create projects, manage dependencies, and run applications.

Option 1: Install via Package Manager (Recommended)

Linux (Ubuntu/Debian):

bash
sudo add-apt-repository ppa:ten-framework/ten-framework
sudo apt update
sudo apt install tman

macOS:

bash
brew install TEN-framework/ten-framework/tman

Windows:

powershell
winget install TEN-framework.tman

Option 2: Install via Script

Linux / macOS:

bash
bash <(curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.sh)

Windows:

powershell
irm https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.ps1 | iex

Option 3: Install from cloned repository

bash
cd ten-framework

# Linux/macOS
bash tools/tman/install_tman.sh
# Windows
& "C:\sw\ten-framework\tools\tman\install_tman.ps1"

💡 Note: If tman is already installed on your system, the installation script will ask whether you want to reinstall/upgrade it. Press y to continue or n to cancel.

Non-interactive Installation (for automation scripts or CI environments, not available on Windows):

bash
# Remote installation
yes y | bash <(curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.sh)

# Local installation
yes y | bash tools/tman/install_tman.sh

Verify Installation:

bash
tman --version

💡 Tip: If you see tman: command not found (Linux/macOS) or a similar unrecognized command error (Windows), make sure the tman directory is in your PATH:

Linux / macOS:

bash
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc  # Linux
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc   # macOS
source ~/.bashrc  # or source ~/.zshrc

Windows:

powershell
# Check if tman is in PATH
$env:Path -split ";" | Select-String "tman"
# If not, either:
# 1. Add it manually via "System Properties → Environment Variables", then restart your terminal
# 2. Or temporarily add the path with:
$env:PATH += ";$env:LOCALAPPDATA\tman"

Step 3: Create and Run the Demo App

1. Create App

bash
# Create a new transcriber_demo app
tman install app transcriber_demo
cd transcriber_demo

2. Install Dependencies

bash
# Install TEN package dependencies
tman install

# Install Python and npm package dependencies
tman run install_deps

⏱️ Estimated Time: 1-2 minutes

3. Build the App

bash
tman run build

⏱️ Estimated Time: 30 seconds

4. Configure Environment Variables

Before running the app, you need to configure the ASR (Automatic Speech Recognition) service credentials. The current example uses Azure ASR extension. You need to fill in the configuration in the transcriber_demo/.env file:

Linux / macOS:

bash
# Create .env file
cat > .env << EOF
# Azure Speech Service Configuration
AZURE_STT_KEY=your_azure_speech_api_key
AZURE_STT_REGION=your_azure_region      # e.g., eastus
AZURE_STT_LANGUAGE=en-US                # Set according to your audio language or real-time recording language, e.g., zh-CN, ja-JP, ko-KR, etc.
EOF

Windows (PowerShell):

powershell
# Create .env file
@"
# Azure Speech Service Configuration
AZURE_STT_KEY=your_azure_speech_api_key
AZURE_STT_REGION=your_azure_region
AZURE_STT_LANGUAGE=en-US
"@ | Out-File -Encoding utf8 .env

💡 Tip: Windows users can also create the .env file directly with a text editor and fill in the configuration above.

💡 Tip: If you want to use other ASR extensions (such as OpenAI Whisper, Google Speech, etc.), you can download and replace them from the cloud store. Similarly, configure the corresponding API keys and environment variables in the .env file.

5. Run the App

bash
tman run start

If everything is working correctly, you should see output similar to:

text
[web_audio_control_go] Web server started on port 8001
[audio_file_player_python] AudioFilePlayerExtension on_start

6. Experience the Demo

Open your browser and visit:

text
http://localhost:8001

You should see the Transcriber Demo web interface. Try:

  • Click the microphone button for real-time voice transcription
  • Upload an audio file for transcription
  • View real-time transcription and subtitle results

Congratulations! 🎉

You've successfully run your first TEN application!

Understanding the App Architecture

This transcriber_demo app showcases TEN Framework's multi-language extension capabilities, consisting of:

  • Go - WebSocket server extension (web_audio_control_go)
  • Python - ASR speech recognition extension (azure_asr_python)
  • TypeScript - VTT subtitle generation and audio recording extension (vtt_nodejs)

🎯 You can now run extensions in multiple languages!

Next Steps

Now you can:

  1. Explore and download more extensions from the cloud store, design and orchestrate your app

    bash
    tman designer  # Launch TMAN Designer to explore extensions, download them, and design your app
  2. Choose a language and develop your own extension

Advanced: Developing and Building C++ Extensions

If you want to develop and use C++ extensions, you'll need to install the TEN build toolchain (tgn). Here's the complete process:

1. Install tgn Build Tool

tgn is TEN Framework's C/C++ build system, based on Google's GN.

Option 1: One-line Installation (Recommended)

Linux / macOS:

bash
curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tgn/install_tgn.sh | bash

Windows:

powershell
irm https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tgn/install_tgn.ps1 | iex

Option 2: Install from Cloned Repository

Linux / macOS:

bash
# If you've already cloned the TEN Framework repository
cd ten-framework
bash tools/tgn/install_tgn.sh

Windows:

powershell
# If you've already cloned the TEN Framework repository
& ".\tools\tgn\install_tgn.ps1"

After installation, ensure tgn is added to PATH:

Linux / macOS:

bash
# Temporarily add to current session
export PATH="/usr/local/ten_gn:$PATH"

# Or permanently add to shell configuration (recommended)
echo 'export PATH="/usr/local/ten_gn:$PATH"' >> ~/.bashrc  # Linux
echo 'export PATH="/usr/local/ten_gn:$PATH"' >> ~/.zshrc   # macOS
source ~/.bashrc  # or source ~/.zshrc

Windows:

powershell
# The install script adds to user PATH automatically. To add manually:
$env:Path += ";$env:LOCALAPPDATA\ten_gn"

Verify installation:

bash
tgn --help

2. Install C++ Extension

Using WebRTC VAD (Voice Activity Detection) extension as an example, install a C++ extension from the cloud store:

bash
cd transcriber_demo
tman install extension webrtc_vad_cpp

💡 Note: webrtc_vad_cpp is a voice activity detection extension implemented in C++. It can filter out non-speech segments in real-time speech recognition scenarios.

3. Compile C++ Extension

After installing the C++ extension, rebuild the app to compile the C++ code into a dynamic library:

bash
tman run build

⏱️ Expected Time: First-time compilation of C++ extensions may take 1-3 minutes, depending on your machine's performance.

4. Run App with VAD Functionality

bash
tman run start_with_vad

If everything works correctly, you should see:

text
[web_audio_control_go] Web server started on port 8001
[vad] WebRTC VAD initialized with mode 2
[audio_file_player_python] AudioFilePlayerExtension on_start

Now open your browser at http://localhost:8001 and navigate to the microphone real-time transcription page. You'll see the silence state changes after VAD processing. When the silence state is true, it indicates there is no speech in the current audio.

C++ Development Environment Requirements

Developing and compiling C++ extensions requires installing a C++ compiler (gcc or clang):

Linux:

bash
# Ubuntu/Debian
sudo apt-get install gcc g++

# Or use clang
sudo apt-get install clang

macOS:

bash
# Install Xcode Command Line Tools (includes clang)
xcode-select --install

Windows:

powershell
# Option 1: Install Visual Studio Build Tools (recommended)
# Download from https://visualstudio.microsoft.com/visual-cpp-build-tools/

# Option 2: Install via winget
winget install Microsoft.VisualStudio.2022.BuildTools --override "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --passive"

# 💡 Note:
#  - Select the "Desktop development with C++" workload during installation
#  - Make sure to check "C++ Clang tools for Windows"

Verify compiler installation:

bash
# Check gcc (Linux/macOS/MSYS2)
gcc --version
g++ --version

# Or check clang (Linux/macOS)
clang --version

Windows (MSVC):

powershell
# Run in "Developer PowerShell for VS"
clang-cl --version

Troubleshooting (C++ Extensions)

  1. tgn command not found

    Ensure you've run the installation script and added tgn to PATH:

    bash
    export PATH="/usr/local/ten_gn:$PATH"
  2. Compilation failed: Compiler not found

    Please refer to the "C++ Development Environment Requirements" section above to install the compiler.

Learn More

Troubleshooting

1. Python Library Loading Failure on macOS

Problem: Error indicating libpython3.10.dylib cannot be found when running the app

Solution:

bash
export DYLD_LIBRARY_PATH=/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.10/lib:$DYLD_LIBRARY_PATH

Consider adding this line to your ~/.zshrc or ~/.bash_profile.

2. tman Download Failed or Slow

Problem: Network connection to GitHub is restricted

Solution:

  • Manual download: Visit the Releases page to download the tman binary for your platform

3. Port 8001 Already in Use

Problem: Port conflict error when starting the app

Solution:

  • Find the process using the port:
    • Linux/macOS: lsof -i :8001
    • Windows: netstat -ano | findstr :8001
  • Kill the process:
    • Linux/macOS: kill -9
    • Windows: taskkill /PID /F
  • Or modify the port number in the app configuration file (transcriber_demo/ten_packages/extension/web_audio_control_go/property.json)

4. Go Build Failed

Problem: Go module related errors during build

Solution:

text
# Set proxy
# Linux/macOS
export GOPROXY=https://goproxy.cn,direct
# Windows PowerShell
$env:GOPROXY = "https://goproxy.cn,direct"

# Clean Go module cache
go clean -modcache

# Reinstall dependencies
cd transcriber_demo
tman run build

5. Python Dependencies Installation Failed

Problem: pip installation timeout or failure

Solution: Use a mirror source (for users in China)

bash
pip3 install --index-url https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

6. Azure Speech Service Error

Problem: Azure Speech Service related errors, such as authentication failure

Solution: Check that the configuration in your .env file is correct, and ensure AZURE_STT_KEY and AZURE_STT_REGION are filled in accurately

7. Permission Error on Windows

Problem: PermissionError when accessing files on Windows

Solution: Right-click PowerShell and select "Run as Administrator"

Get Help


Getting Started Websocket Voice Assistant Quick Start.Cn (`docs/getting-started/websocket-voice-assistant-quick-start.cn.md`)


title: 基于WebSocket的语音助手快速入门 _portal_target: getting-started/websocket-voice-assistant-quick-start.cn.md

基于 WebSocket 的语音助手快速入门

在不到 10 分钟内构建并运行一个基于 WebSocket 的实时语音助手。

简介

本指南将帮助您快速运行一个完整的语音助手 Demo,支持实时语音对话和工具调用。该示例集成了:

组件 服务商 用途
ASR Deepgram 语音转文字
LLM OpenAI 对话生成
TTS ElevenLabs 文字转语音
Tool WeatherAPI 天气查询(可选)

环境要求

前置依赖

请先参考 TEN Framework 快速入门指南 完成基础环境安装,确保以下命令通过检查:

bash
tman check env

预期输出(至少 Python 和 Go 为 Ready):

text
✅ Operating System: Supported
✅ Python:   Ready
✅ Go:       Ready

额外工具

安装以下工具用于构建和运行:

Task(任务运行器)

Linux / macOS:

bash
go install github.com/go-task/task/v3/cmd/task@latest
task --version  # 验证安装

Windows:

powershell
# 1. 使用winget安装(推荐)
# 从以下地址安装winget
# https://apps.microsoft.com/detail/9nblggh4nns1?hl=en-US&gl=US
# 系统需要满足:Windows10 高于 1709 (Build 16299),或者是Windows11
winget --version # 验证安装
winget install Task.Task
task --version  # 验证安装

# 2. 使用scoop安装
irm get.scoop.sh | iex
scoop --version # 验证安装
scoop install task
task --version # 验证安装

Bun(JavaScript 包管理器)

Linux / macOS:

bash
curl -fsSL https://bun.sh/install | bash
bun --version  # 验证安装

Windows:

powershell
powershell -c "irm bun.sh/install.ps1 | iex"
bun --version  # 验证安装

uv(Python 包管理器)

Linux / macOS:

bash
curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version  # 验证安装

Windows:

powershell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
uv --version  # 验证安装

💡 安装后需要重新加载终端配置:

  • Linux/macOS:source ~/.zshrc
  • Windows:重新打开 PowerShell 终端

快速开始

1. 克隆仓库

bash
git clone https://github.com/TEN-framework/ten-framework.git
cd ten-framework/ai_agents/agents/examples/websocket-example

2. 安装依赖

bash
task install

⏱️ 预计耗时:2-3 分钟

该命令会自动完成:

  • 安装 TEN 扩展包(45+ 个)
  • 编译 Go 应用和 API 服务器
  • 安装 Python 和前端依赖

3. 配置 API Keys

在 ai_agents/.env 文件中配置服务商密钥:

Linux / macOS:

bash
cd ../../../  # 回到 ai_agents 目录
vim .env

Windows:

powershell
cd ..\..\..   # 回到 ai_agents 目录
notepad .env

从.env.example复制所有内容后,填写以下配置:

bash
# Deepgram - 语音识别
DEEPGRAM_API_KEY=your_deepgram_api_key

# OpenAI - 大语言模型
OPENAI_API_KEY=your_openai_api_key
OPENAI_MODEL=your_openai_model

# ElevenLabs - 语音合成
ELEVENLABS_TTS_KEY=your_elevenlabs_api_key

# WeatherAPI - 天气工具(可选)
WEATHERAPI_API_KEY=your_weatherapi_key

4. 启动服务

bash
cd agents/examples/websocket-example
task run

服务启动后,自动运行:

5. 与助手对话

打开浏览器访问 http://localhost:3000,点击麦克风按钮开始语音对话。

尝试以下对话:

  • "你好,你是谁?"
  • "今天北京的天气怎么样?"(需配置 WeatherAPI)
  • "给我讲个笑话"

🎉 恭喜! 您已成功运行 TEN Agent 语音助手。

进阶配置

更换扩展

TEN Framework 支持灵活替换 ASR、LLM、TTS 等扩展。

方式 1:使用 TMAN Designer(推荐)

访问 http://localhost:49483,通过可视化界面:

  1. 点击扩展节点查看详情
  2. 选择替代扩展
  3. 配置参数
  4. 保存并重启

方式 2:编辑配置文件

编辑 tenapp/property.json,修改 predefined_graphs 中的扩展配置。

示例:更换 ASR 为 Azure

json
{
  "type": "extension",
  "name": "stt",
  "addon": "azure_asr_python",
  "extension_group": "stt",
  "property": {
    "params": {
      "key": "${env:AZURE_STT_KEY}",
      "region": "${env:AZURE_STT_REGION}",
      "language": "en-US"
    }
  }
}

示例:更换 TTS 为 Minimax

json
{
  "type": "extension",
  "name": "tts",
  "addon": "minimax_tts_websocket_python",
  "extension_group": "tts",
  "property": {
    "params": {
      "api_key": "${env:MINIMAX_TTS_API_KEY|}",
      "group_id": "${env:MINIMAX_TTS_GROUP_ID|}",
      "model": "speech-02-turbo",
      "audio_setting": {
        "sample_rate": 16000
      },
      "voice_setting": {
        "voice_id": "female-shaonv"
      }
    }
  }
}

修改后重启服务(Ctrl+C 然后 task run)。

配置监控

使用 Grafana 监控应用的 Metrics 和 Logs。

1. 启动监控栈

Linux / macOS:

bash
cd tools/grafana-monitoring
docker compose -f docker-compose.push.yml up -d

Windows:

powershell
cd tools\grafana-monitoring
docker compose -f docker-compose.push.yml up -d
# 若docker未安装,请通过https://apps.microsoft.com/detail/xp8cbj40xlbwkx 安装,并重启电脑后,启动docker desktop

启动的服务:

  • Grafana(端口 3001)- 可视化界面
  • Prometheus(端口 9091)- Metrics 存储
  • Loki(端口 3100)- Logs 存储
  • OpenTelemetry Collector(端口 4317/4318)- OTLP 数据接收

2. 配置应用遥测

编辑 agents/examples/websocket-example/tenapp/property.json:

配置 Logs 输出

在 ten.log.handlers 数组中添加:

json
{
  "matchers": [{ "level": "info" }],
  "formatter": { "type": "json", "colored": false },
  "emitter": {
    "type": "otlp",
    "config": {
      "endpoint": "http://localhost:4317",
      "service_name": "websocket-example"
    }
  }
}

配置 Metrics 输出

在 ten.services 中添加:

json
{
  "telemetry": {
    "enabled": true,
    "metrics": {
      "enabled": true,
      "exporter": {
        "type": "otlp",
        "config": {
          "endpoint": "http://localhost:4317",
          "service_name": "websocket-example"
        }
      }
    }
  }
}

3. 重启并访问 Grafana

bash
cd ../../../agents/examples/websocket-example
task run

访问 http://localhost:3001(用户名/密码:admin/admin)

查看 Metrics

进入 Dashboards → "TEN Framework" 查看:

  • 扩展启动耗时
  • Cmd 处理延迟
  • 消息队列等待时间

查看 Logs

进入 Explore → 选择 Loki,使用查询:

  • {service_name="websocket-example"} - 所有日志
  • {service_name="websocket-example", ten_extension_name="stt"} - ASR 日志
  • {service_name="websocket-example", ten_extension_name="llm"} - LLM 日志
  • {service_name="websocket-example", ten_extension_name="tts"} - TTS 日志

💡 使用 ten_extension_name 标签精确筛选扩展日志。

停止监控服务:

Linux / macOS:

bash
cd tools/grafana-monitoring
docker compose -f docker-compose.push.yml down

Windows:

powershell
cd tools\grafana-monitoring
docker compose -f docker-compose.push.yml down

常见问题

前端依赖安装失败

如果 bun install 报错版本不存在,切换到 npm 官方源:

bash
cd frontend
echo "registry=https://registry.npmjs.org/" > .npmrc
rm bun.lock
bun install

macOS Python 库加载失败

设置 Python 动态库路径:

bash
export DYLD_LIBRARY_PATH=/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.10/lib:$DYLD_LIBRARY_PATH
# 添加到 ~/.zshrc 永久生效

Windows Python 库加载失败

确保 Python 3.10 安装路径在系统 PATH 中:

powershell
# 检查 Python 路径
python -c "import sys; print(sys.prefix)"

# 将 Python DLL 目录添加到 PATH(根据实际路径调整)
$env:Path += ";C:\Python310;C:\Python310\DLLs"

如果安装 Python 时未勾选 "Add Python to PATH",可以重新运行安装程序选择 "Modify" 来添加。

端口被占用

查找并终止占用端口的进程:

Linux / macOS:

bash
lsof -i :3000  # 或 :8080
kill -9 <PID>

Windows:

powershell
netstat -ano | findstr :3000   # 或 :8080
taskkill /PID <PID> /F

Go module相关错误

bash
# 设置代理
# Linux/macOS
export GOPROXY=https://goproxy.cn,direct
# Windows PowerShell
$env:GOPROXY = "https://goproxy.cn,direct"

# 清理 Go module 缓存
go clean -modcache

# 重新安装依赖
cd ai_agents/agents/examples/websocket-example
task install

下一步

跟随这些指南,将您的语音 AI 应用带入真实世界。


Getting Started Websocket Voice Assistant Quick Start (`docs/getting-started/websocket-voice-assistant-quick-start.md`)


title: WebSocket Voice Assistant Quickstart _portal_target: getting-started/websocket-voice-assistant-quick-start.md

WebSocket Voice Assistant Quickstart

Build and run a real-time WebSocket voice assistant in under 10 minutes.

Overview

This guide helps you run a complete voice assistant demo locally. It supports real-time voice conversations and tool calling. The demo includes:

Component Vendor Purpose
ASR Deepgram Speech-to-text
LLM OpenAI Response generation
TTS ElevenLabs Text-to-speech
Tool WeatherAPI Weather lookup (optional)

Requirements

Prerequisites

Follow the TEN Framework Quick Start to install the base environment, then make sure the check below passes:

bash
tman check env

Expected output (at least Python and Go are Ready):

text
✅ Operating System: Supported
✅ Python:   Ready
✅ Go:       Ready

Extra tools

Install the following tools for build & run:

Task (task runner)

Linux / macOS:

bash
go install github.com/go-task/task/v3/cmd/task@latest
task --version  # verify

Windows:

powershell
# Option 1: Install via winget (recommended)
# Install winget from: https://apps.microsoft.com/detail/9nblggh4nns1?hl=en-US&gl=US
# Requires Windows 10 version 1709 (Build 16299) or higher, or Windows 11
winget --version  # verify winget
winget install Task.Task
task --version  # verify

# Option 2: Install via scoop
irm get.scoop.sh | iex
scoop --version  # verify scoop
scoop install task
task --version  # verify

Bun (JavaScript package manager)

Linux / macOS:

bash
curl -fsSL https://bun.sh/install | bash
bun --version  # verify

Windows:

powershell
powershell -c "irm bun.sh/install.ps1 | iex"
bun --version  # verify

uv (Python package manager)

Linux / macOS:

bash
curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version  # verify

Windows:

powershell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
uv --version  # verify

💡 After installation, reload your shell config:

  • Linux/macOS: source ~/.zshrc
  • Windows: reopen your PowerShell terminal

Quickstart

1. Clone the repo

bash
git clone https://github.com/TEN-framework/ten-framework.git
cd ten-framework/ai_agents/agents/examples/websocket-example

2. Install dependencies

bash
task install

⏱️ Estimated time: 2–3 minutes

This command will:

  • Install TEN extension packages (45+)
  • Build the Go app and API server
  • Install Python and frontend dependencies

3. Configure API keys

Configure vendor keys in ai_agents/.env:

Linux / macOS:

bash
cd ../../../  # back to ai_agents
vim .env

Windows:

powershell
cd ..\..\..   # back to ai_agents
notepad .env

Copy all content from .env.example, then fill in:

bash
# Deepgram - ASR
DEEPGRAM_API_KEY=your_deepgram_api_key

# OpenAI - LLM
OPENAI_API_KEY=your_openai_api_key
OPENAI_MODEL=your_openai_model

# ElevenLabs - TTS
ELEVENLABS_TTS_KEY=your_elevenlabs_api_key

# WeatherAPI - Tool (optional)
WEATHERAPI_API_KEY=your_weatherapi_key

4. Start services

bash
cd agents/examples/websocket-example
task run

After startup, the following services are available:

5. Talk to your assistant

Open http://localhost:3000, click the microphone button, and start speaking.

Try:

  • "Hello, who are you?"
  • "What's the weather in Beijing today?" (requires WeatherAPI)
  • "Tell me a joke."

Congratulations! Your TEN Agent voice assistant is running.

Advanced

Swap extensions

TEN Framework lets you swap ASR/LLM/TTS extensions flexibly.

Open http://localhost:49483, then in the visual editor:

  1. Click an extension node to inspect it
  2. Choose a replacement extension
  3. Configure parameters
  4. Save and restart

Option 2: Edit configuration file

Edit tenapp/property.json and modify the extension configs under predefined_graphs.

Example: switch ASR to Azure

json
{
  "type": "extension",
  "name": "stt",
  "addon": "azure_asr_python",
  "extension_group": "stt",
  "property": {
    "params": {
      "key": "${env:AZURE_STT_KEY}",
      "region": "${env:AZURE_STT_REGION}",
      "language": "en-US"
    }
  }
}

Example: switch TTS to Minimax

json
{
  "type": "extension",
  "name": "tts",
  "addon": "minimax_tts_websocket_python",
  "extension_group": "tts",
  "property": {
    "params": {
      "api_key": "${env:MINIMAX_TTS_API_KEY|}",
      "group_id": "${env:MINIMAX_TTS_GROUP_ID|}",
      "model": "speech-02-turbo",
      "audio_setting": {
        "sample_rate": 16000
      },
      "voice_setting": {
        "voice_id": "female-shaonv"
      }
    }
  }
}

Restart after changes (Ctrl+C then task run).

Monitoring (Grafana)

Use Grafana to observe application metrics and logs.

1. Start monitoring stack

Linux / macOS:

bash
cd tools/grafana-monitoring
docker-compose -f docker-compose.push.yml up -d

Windows:

powershell
cd tools\grafana-monitoring
docker compose -f docker-compose.push.yml up -d
# If Docker is not installed, install it from https://apps.microsoft.com/detail/xp8cbj40xlbwkx, restart your PC, then launch Docker Desktop

Services:

  • Grafana (port 3001) - UI
  • Prometheus (port 9091) - metrics store
  • Loki (port 3100) - log store
  • OpenTelemetry Collector (ports 4317/4318) - OTLP receiver

2. Configure app telemetry

Edit agents/examples/websocket-example/tenapp/property.json:

Logs (export)

Add to the ten.log.handlers array:

json
{
  "matchers": [{ "level": "info" }],
  "formatter": { "type": "json", "colored": false },
  "emitter": {
    "type": "otlp",
    "config": {
      "endpoint": "http://localhost:4317",
      "service_name": "websocket-example"
    }
  }
}

Metrics (export)

Add to ten.services:

json
{
  "telemetry": {
    "enabled": true,
    "metrics": {
      "enabled": true,
      "exporter": {
        "type": "otlp",
        "config": {
          "endpoint": "http://localhost:4317",
          "service_name": "websocket-example"
        }
      }
    }
  }
}

3. Restart and open Grafana

bash
cd ../../../agents/examples/websocket-example
task run

Open http://localhost:3001 (username/password: admin/admin).

Metrics (Dashboards)

Go to Dashboards → "TEN Framework" to view:

  • Extension startup latency
  • Cmd processing latency
  • Message queue wait time

Logs (Explore/Loki)

Go to Explore → select Loki, then query:

  • {service_name="websocket-example"} - all logs
  • {service_name="websocket-example", ten_extension_name="stt"} - ASR logs
  • {service_name="websocket-example", ten_extension_name="llm"} - LLM logs
  • {service_name="websocket-example", ten_extension_name="tts"} - TTS logs

💡 Use the ten_extension_name label to filter logs per extension.

Stop the monitoring stack:

Linux / macOS:

bash
cd tools/grafana-monitoring
docker-compose -f docker-compose.push.yml down

Windows:

powershell
cd tools\grafana-monitoring
docker compose -f docker-compose.push.yml down

Troubleshooting

Frontend dependency install fails

If bun install fails due to missing versions, switch to the official npm registry:

bash
cd frontend
echo "registry=https://registry.npmjs.org/" > .npmrc
rm bun.lock
bun install

macOS Python library loading fails

Set the Python dynamic library path:

bash
export DYLD_LIBRARY_PATH=/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.10/lib:$DYLD_LIBRARY_PATH
# Add to ~/.zshrc for persistence

Windows Python library loading fails

Make sure the Python 3.10 installation path is in your system PATH:

powershell
# Check Python path
python -c "import sys; print(sys.prefix)"

# Add Python DLL directory to PATH (adjust to your actual path)
$env:Path += ";C:\Python310;C:\Python310\DLLs"

If you didn't check "Add Python to PATH" during installation, re-run the installer and choose "Modify" to add it.

Port already in use

Find and kill the process:

Linux / macOS:

bash
lsof -i :3000  # or :8080
kill -9 <PID>

Windows:

powershell
netstat -ano | findstr :3000   # or :8080
taskkill /PID <PID> /F

Go module related errors

bash
# Set proxy
# Linux/macOS
export GOPROXY=https://goproxy.cn,direct
# Windows PowerShell
$env:GOPROXY = "https://goproxy.cn,direct"

# Clean Go module cache
go clean -modcache

# Reinstall dependencies
cd ai_agents/agents/examples/websocket-example
task install

Next steps


Ai L1 01 Setup (`docs/ai/L1/01_setup.md`)

01 Setup

Environment setup, local development, and quick commands for TEN Framework AI Agents.

Prerequisites

Requirement Version / Notes
Docker + Compose Required for container-based development
Node.js LTS v18+ on host; container has Node 22
API Keys Agora App ID, LLM provider, ASR provider, TTS provider
Hardware 2+ CPU cores, 4 GB RAM minimum

Docker Container

bash
cd /home/ubuntu/ten-framework/ai_agents
docker compose up -d
docker ps | grep ten_agent_dev   # Verify running

Container image: ghcr.io/ten-framework/ten_agent_build:0.7.14

Permissions: if your user is not in the docker group, prefix every
docker / docker exec / docker compose command in this guide with sudo.
A bare docker ps failing with permission denied while trying to connect to the docker API at unix:///var/run/docker.sock is the diagnostic — switch to
sudo docker ps and the rest works.

Environment Variables

Single .env file: ai_agents/.env — the ONLY source of environment config.

Variable Purpose Required
AGORA_APP_ID Agora RTC app identifier Yes
AGORA_APP_CERTIFICATE Agora RTC certificate No
OPENAI_API_KEY LLM provider Yes
OPENAI_MODEL Model name (e.g., gpt-4o) Yes
DEEPGRAM_API_KEY ASR provider Yes
ELEVENLABS_TTS_KEY TTS provider Yes
LOG_PATH Container log directory bind Yes
LOG_STDOUT Worker log visibility Yes (true)
SERVER_PORT API server port Yes (8080)
WORKERS_MAX Max concurrent sessions Yes (100)
WORKER_QUIT_TIMEOUT_SECONDS Worker idle timeout Yes (60)

See .env.example for the complete list. Extensions may require different provider
keys (Deepgram, ElevenLabs, Cartesia, Azure, AWS, Rime, etc.) — check extension
README files.

Install and Run

bash
# 1. Install Python dependencies (NOT persisted across container restarts)
docker exec ten_agent_dev bash -c \
  "cd /app/agents/examples/<example>/tenapp && \
   bash scripts/install_python_deps.sh"

# 2. Build and install (5-8 minutes first time)
docker exec ten_agent_dev bash -c \
  "cd /app/agents/examples/<example> && task install"

# 3. Start everything (API server + playground + TMAN Designer)
docker exec -d ten_agent_dev bash -c \
  "cd /app/agents/examples/<example> && \
   task run > /tmp/task_run.log 2>&1"

Typical choices:

  • voice-assistant for standard vendor iteration and demo graphs
  • voice-assistant-advanced for generated multi-graph setups

CRITICAL: Always use task run to start — never run ./bin/api directly.

Ports

Port Service
8080 Go API server
3000 Playground (Next.js)
49483 TMAN Designer

Health Checks

bash
curl -s http://localhost:8080/health
# {"code":"0","data":null,"msg":"ok"}

curl -s http://localhost:8080/graphs | jq -r '.data[].name'
# voice_assistant, voice_assistant_oracle, etc.

Restart Procedures

What Changed Container? Server? Frontend?
property.json (graphs added) No Nuclear restart Nuclear restart
property.json (config only) No No No
.env file Yes Yes No
Python extension code No Yes No
Go server code No Yes + task install No

Nuclear restart (safest after graph changes):

bash
sudo docker exec ten_agent_dev bash -c \
  "pkill -9 -f 'bin/api'; pkill -9 -f bun; pkill -9 -f node; \
   pkill -9 -f next-server; pkill -9 -f tman"
sudo docker exec ten_agent_dev bash -c "rm -f /app/playground/.next/dev/lock"
sleep 30
sudo docker exec -d ten_agent_dev bash -c \
  "cd /app/agents/examples/<example> && task run > /tmp/task_run.log 2>&1"

After container restart: always reinstall Python deps, then task run.

After .env changes: docker compose down && docker compose up -d, reinstall deps, task run.

Logs

bash
# All logs (inside container)
docker exec ten_agent_dev tail -f /tmp/task_run.log

# Filter by extension or channel
docker exec ten_agent_dev tail -f /tmp/task_run.log | grep --line-buffered "deepgram"

# If grep reports binary/noisy output, inspect printable strings instead
docker exec ten_agent_dev bash -c "strings /tmp/task_run.log | tail -n 80"
  • Deployment — Docker Compose, Cloudflare tunnel, Nginx, Grafana monitoring

Ai L1 02 Architecture (`docs/ai/L1/02_architecture.md`)

02 Architecture

System design overview: extensions, graphs, connections, and the server-worker model.

TEN Ecosystem

Component Purpose
TEN Framework Core runtime (C/C++, Go, Python, Node.js bindings)
TEN Agent Examples Pre-built agent configurations (this repo's ai_agents/)
TEN VAD Low-latency voice activity detection
TEN Turn Detection Full-duplex dialogue management
TEN Portal Documentation and blog site

Extension System

Extensions are modular components that process data — ASR, TTS, LLM, tools, RTC, avatars, etc.
Each extension has a lifecycle:

text
on_init() → on_start() → [process messages] → on_stop() → on_deinit()

Every extension contains:

File Purpose
addon.py Registration via @register_addon_as_extension
extension.py Main logic, inherits from a base class
manifest.json Metadata, dependencies, API interface
property.json Default configuration values

Base classes (in ten_ai_base/interface/ten_ai_base/):

Base Class Use For
AsyncASRBaseExtension Speech-to-text
AsyncTTS2BaseExtension Text-to-speech
AsyncLLMBaseExtension Chat completion
AsyncLLMToolBaseExtension LLM function tools
AsyncExtension Generic / custom

Graph-Based Configuration

Agents are assembled by defining graphs in property.json. A graph specifies
which extensions run (nodes) and how data flows between them (connections).

json
{
  "ten": {
    "predefined_graphs": [{
      "name": "voice_assistant",
      "auto_start": true,
      "graph": {
        "nodes": [
          {"type": "extension", "name": "stt", "addon": "deepgram_asr_python", "property": {}},
          {"type": "extension", "name": "llm", "addon": "openai_llm2_python", "property": {}},
          {"type": "extension", "name": "tts", "addon": "elevenlabs_tts2_python", "property": {}}
        ],
        "connections": [...]
      }
    }]
  }
}

Connection Types

Type Payload Example
cmd Named commands tool_register, on_user_joined, flush
data Named data messages asr_result, text_data, tts_text_input
audio_frame PCM audio streams pcm_frame (16-bit, mono, 16/24/48 kHz)
video_frame Video streams Raw video frames for vision/avatar

RTC-First Design

TEN uses Agora RTC (Real-Time Communication) as the default transport, not WebSockets.

Aspect RTC (default) WebSocket
Latency 50-150ms (UDP-based) Higher (TCP-based)
Codec support Opus, VP8, VP9, AV1 Raw PCM only
Bandwidth adapt Built-in adaptation + FEC Manual implementation
Use case Real-time voice/video Signaling, configuration

WebSockets are used for signaling and configuration; RTC handles the media path.

Server-Worker Model

text
┌─────────────────┐     ┌──────────────────┐
│  Go HTTP Server  │────▶│  Worker Process   │  (one per session)
│  (port 8080)     │     │  (tman run start) │
│                  │     │                   │
│  /start → spawn  │     │  Loads graph from │
│  /stop  → kill   │     │  property.json    │
│  /ping  → keep   │     │  Runs extensions  │
└─────────────────┘     └──────────────────┘
  • POST /start spawns a worker process for a channel/session
  • POST /stop terminates the worker
  • POST /ping keeps the session alive (if timeout != -1)

Property Injection

When /start is called, the server auto-injects dynamic values into the graph:

  • channel_name → injected into every node that has a "channel" property
  • remote_stream_id, bot_stream_id, token → injected via startPropMap
  • req.Properties[extensionName] → merged into specific node properties

This is future-proof: any new extension with a "channel" property automatically
receives the dynamic channel value without code changes.

Component Diagram

text
Client (Browser/Mobile)
           │
           ▼
    ┌──────────────┐
    │  Playground   │  Next.js frontend (port 3000)
    │  (UI)         │
    └──────┬───────┘
           │ REST API
           ▼
    ┌──────────────┐        ┌──────────────────────────────────┐
    │  Go Server    │──spawn─▶│  Worker Process                   │
    │  (port 8080)  │        │  ┌─────┐  ┌─────┐  ┌─────┐     │
    │               │        │  │ ASR │─▶│ LLM │─▶│ TTS │     │
    │               │        │  └──┬──┘  └─────┘  └──┬──┘     │
    └──────────────┘        │     │                   │        │
                             │  ┌──┴───────────────────┴──┐    │
                             │  │      Agora RTC           │    │
                             │  └─────────────────────────┘    │
                             └──────────────────────────────────┘

Ai L1 03 Code Map (`docs/ai/L1/03_code_map.md`)

03 Code Map

Directory tree, module responsibilities, and key file locations.

Top-Level Structure

All AI agent development happens inside ai_agents/:

text
ai_agents/
├── agents/
│   ├── ten_packages/
│   │   ├── extension/           # 90+ extensions (ASR, TTS, LLM, tools, avatar)
│   │   └── system/              # Core runtime packages
│   │       ├── ten_ai_base/     # Base classes and API interface definitions
│   │       ├── ten_runtime_python/
│   │       └── ten_runtime_go/
│   ├── examples/                # 24+ example agent configurations
│   │   ├── voice-assistant/
│   │   ├── voice-assistant-advanced/
│   │   ├── voice-assistant-realtime/
│   │   ├── voice-assistant-video/
│   │   ├── doodler/
│   │   └── ...
│   ├── integration_tests/       # Test frameworks
│   │   ├── asr_guarder/         # ASR integration tests
│   │   └── tts_guarder/         # TTS integration tests
│   └── scripts/                 # Build and packaging scripts
├── server/                      # Go API server
│   ├── main.go
│   └── internal/
│       ├── http_server.go       # REST endpoints, property injection
│       └── config.go            # Parameter mapping (startPropMap)
├── playground/                  # Next.js frontend UI (port 3000)
│   └── src/                     # React components
├── esp32-client/                # ESP32 hardware client
├── Taskfile.yml                 # Root-level build/test tasks
├── docker-compose.yml           # Container config
├── .env                         # Environment variables (single source)
└── .env.example                 # Template with all variables

Other repo-root directories: core/ (C runtime), packages/ (example/core extensions),
docs/ (framework docs), tools/ (Grafana monitoring, profilers).

Extension Categories

Category Count Examples
ASR 10+ deepgram_asr_python, azure_asr_python, aws_asr_python
TTS 15+ deepgram_tts, elevenlabs_tts2_python, cartesia_tts
LLM 9+ openai_llm2_python, anthropic_llm2_python, gemini_llm2_python, bedrock_llm_python
Avatar 5+ heygen_avatar_python, anam_avatar_python
Tools 8+ bingsearch_tool_python, vision_tool_python
Transport 3+ agora_rtc, websocket_server, http_server_python
Other 10+ message_collector2, ten_vad_python, mcp_client_python

Extension File Structure

Every extension follows this layout:

File Purpose
__init__.py Package marker
addon.py @register_addon_as_extension registration
extension.py Main logic, inherits from base class
config.py Pydantic config model (optional but common)
manifest.json Metadata, dependencies, API interface imports
property.json Default config values with ${env:VAR} syntax
requirements.txt Python dependencies
README.md Usage documentation (often multilingual)
tests/ Standalone tests with bin/start entry point

Base Classes

Located in example tenapp directories under ten_packages/system/ten_ai_base/interface/ten_ai_base/:

File Class Purpose
asr.py AsyncASRBaseExtension Speech recognition
tts.py AsyncTTSBaseExtension Text-to-speech (basic)
tts2.py AsyncTTS2BaseExtension Text-to-speech (advanced)
llm.py AsyncLLMBaseExtension Language model completion
llm2.py AsyncLLM2BaseExtension Language model v2
llm_tool.py AsyncLLMToolBaseExtension LLM function calling tools
mllm.py AsyncMLLMBaseExtension Multimodal LLM

API Interface Definitions

Standard interfaces in ten_ai_base/api/:

File Defines
asr-interface.json ASR data/cmd/audio_frame schemas
tts-interface.json TTS data/cmd/audio_frame schemas
llm-interface.json LLM data/cmd schemas
mllm-interface.json Multimodal LLM schemas

Extensions reference these via manifest.json:

json
{"api": {"interface": [{"import_uri": "../../system/ten_ai_base/api/tts-interface.json"}]}}

Key Files Quick Reference

When working on... Look at
New extension Similar extension in agents/ten_packages/extension/
API interface changes ten_ai_base/api/*.json
Graph configuration agents/examples/*/tenapp/property.json
Server endpoints server/internal/http_server.go
Build/test tasks Taskfile.yml (root) and per-example
Test setup agents/ten_packages/extension/*/tests/bin/start

Ai L1 04 Conventions (`docs/ai/L1/04_conventions.md`)

04 Conventions

Coding patterns, naming, configuration, and formatting standards.

Naming Conventions

Item Pattern Example
Extension dir <vendor>_<type>_python deepgram_asr_python
Addon name Same as directory name deepgram_asr_python
Example dir voice-assistant-<variant> voice-assistant-realtime
Config class <Vendor><Type>Config(BaseModel) DeepgramTTSConfig
Client class <Vendor><Type>Client DeepgramTTSClient

Addon Registration

Every extension must register via decorator in addon.py:

python
from ten_runtime import Addon, register_addon_as_extension, TenEnv

@register_addon_as_extension("deepgram_asr_python")
class DeepgramASRExtensionAddon(Addon):
    def on_create_instance(self, ten: TenEnv, name: str, context) -> None:
        ten.on_create_instance_done(DeepgramASRExtension(name), context)

The decorator name must match the addon field in property.json graph nodes.

Base Class Selection

Need Base Class Key Methods You Implement Most Often
Speech-to-text AsyncASRBaseExtension vendor(), start_connection(), stop_connection(), send_audio(), finalize(), is_connected(), input_audio_sample_rate(), buffer_strategy()
Text-to-speech (HTTP) AsyncTTS2HttpExtension create_config(), create_client(), vendor(), synthesize_audio_sample_rate()
Text-to-speech (WS) AsyncTTS2BaseExtension vendor(), request_tts(), synthesize_audio_sample_rate()
Chat completion AsyncLLMBaseExtension on_call_chat_completion(), on_data_chat_completion()
LLM function tool AsyncLLMToolBaseExtension get_tool_metadata(), run_tool()
Generic / custom AsyncExtension on_cmd(), on_data(), etc.

Pydantic Configuration

Extensions use Pydantic models for config validation:

python
from pydantic import BaseModel, Field

class DeepgramTTSConfig(BaseModel):
    params: dict[str, Any] = Field(default_factory=dict)

Config is loaded from property.json in on_init():

python
config_json, _ = await ten_env.get_property_to_json("")
self.config = DeepgramTTSConfig(**json.loads(config_json))

Environment Variable Syntax

In property.json, reference env vars:

Syntax Behavior
${env:VAR_NAME} Required — error if missing
${env:VAR_NAME|} Optional — empty string if missing
${env:VAR_NAME|default} Optional — uses default if missing
json
{"params": {"api_key": "${env:DEEPGRAM_API_KEY}", "region": "${env:AZURE_REGION|}"}}

Params Dict Pattern

Extensions using HTTP/WebSocket services store all config in a params dictionary:

  1. Store api_key inside params dict in property.json and config
  2. Extract for authentication headers in the client constructor
  3. Keep convenience fields if needed, but preserve vendor params in config
  4. Strip secrets only when creating the HTTP payload or WS query
python
# In client constructor — extract for auth
self.api_key = config.params.get("api_key", "")
self.headers = {"Authorization": f"Bearer {self.api_key}"}

# In request method — strip before sending
vendor_params = {**self.config.params}
vendor_params.pop("api_key", None)

Sensitive Data Logging

Implement to_str() to encrypt sensitive fields before logging:

python
def to_str(self, sensitive_handling: bool = True) -> str:
    if not sensitive_handling:
        return f"{self}"
    config = copy.deepcopy(self)
    if config.params and "api_key" in config.params:
        config.params["api_key"] = utils.encrypt(config.params["api_key"])
    return f"{config}"

Logging

  • Use ten_env.log_info(), ten_env.log_warn(), ten_env.log_error(), ten_env.log_debug()
  • Categories: LOG_CATEGORY_KEY_POINT (lifecycle events), LOG_CATEGORY_VENDOR (vendor status)
  • Config log messages must use the config: prefix — QA automation matches on (?:config|LOG_CATEGORY_KEY_POINT|KEYPOINT config): to validate vendor config output:
    python
    ten_env.log_info(f"config: {self.config.to_str(sensitive_handling=True)}", category=LOG_CATEGORY_KEY_POINT)
  • All output goes to /tmp/task_run.log inside the container

Error Classification

Use ModuleErrorCode consistently:

Situation Severity Notes
Missing or invalid required config FATAL_ERROR Extension should not continue
Auth failure (401, 403, invalid API key) FATAL_ERROR Include vendor_info when available
Transient vendor disconnect / timeout NON_FATAL_ERROR Retry or reconnect if supported
Retry ceiling reached FATAL_ERROR Surface terminal failure clearly
Invalid user input for a single request NON_FATAL_ERROR Request fails, extension keeps running

Provider-originated failures should include vendor_info when possible:

python
ModuleErrorVendorInfo(vendor=self.vendor(), code="401", message="Unauthorized")

Before changing corner-case behavior such as empty TTS input, check the guarder
tests first. Repo test expectations override generic assumptions.

Import Convention

python
# Correct (v0.11+)
from ten_runtime import Addon, register_addon_as_extension, TenEnv

# Wrong (old v0.8.x — will not work)
from ten import Addon

Formatting

  • Black formatter with --line-length 80
  • Run: task format (from ai_agents/)
  • Check: task check
  • Excludes: third_party/, http_server_python/, ten_packages/system

Run before every push. CI runs both task check (black format check)
and task lint (pylint — any warning is fatal). Either failing blocks
merge. If you commit from a host shell (outside the container), the
pre-commit hook does not run and the PR fails CI.
The reliable habit is:

bash
sudo docker exec ten_agent_dev bash -c \
  "cd /app && task format && task check && task lint"

Run this after any change under agents/ten_packages/extension/ or
agents/examples/ and before git push. task lint is strict — even
a single W0611: unused-import warning fails CI.

Commit Messages

A separate CI job — Lint Commit Messages / commitlint — runs
@commitlint/config-conventional
against every commit in the PR (config: .github/configs/commitlint.config.mjs).
There is no local commit-msg hook, so a bad message is not caught until CI —
get it right when you write the commit.

Rules that actually fail CI (config-conventional defaults):

  • Conventional header: type(scope): subject — e.g. fix(tts): stream segments immediately.
  • Type must be one of: feat, fix, docs, chore, test, refactor,
    perf, build, ci, style, revert.
  • Subject: lowercase start, no trailing period.
  • Header line ≤ 100 chars.
  • Body lines ≤ 100 chars each — this is the one that bites: a long
    paragraph written with git commit -m "..." is a single >100-char line and
    fails body-max-line-length. Hard-wrap the body (≤72–80 cols is safe),
    e.g. write it in a file and use git commit -F msg.txt.
  • Blank line between subject and body (body-leading-blank).

Quick self-check before pushing (every commit added in the PR):

bash
mb=$(git merge-base origin/main HEAD)
for c in $(git rev-list $mb..HEAD); do
  git log -1 --format='%b' $c | awk -v c=$c 'length>100{print c": body line >100"}'
done

If you already pushed a long-body commit, reword/squash and force-push
(git push --force-with-lease) — commitlint checks the whole PR, so a new
commit on top will not clear the failure.

Design Principles

  • YAGNI: Only implement what is needed now, not what might be needed later
  • KISS: Prefer simple solutions; three similar lines > premature abstraction
  • No git-ignored files: Never modify auto-generated files (manifest-lock.json, out/, .ten/, bin/)

Ai L1 05 Workflows (`docs/ai/L1/05_workflows.md`)

05 Workflows

Step-by-step guides for common development tasks.

Create a New TTS / ASR / LLM Extension

Fastest path: Copy a similar extension and adapt it.

Type Copy From Base Class
TTS (HTTP) rime_http_tts AsyncTTS2HttpExtension
TTS (WS) deepgram_tts AsyncTTS2BaseExtension
ASR deepgram_asr_python AsyncASRBaseExtension
LLM openai_llm2_python AsyncLLMBaseExtension
bash
cp -r agents/ten_packages/extension/<existing_tts> \
  agents/ten_packages/extension/my_vendor_tts

Then:

  1. Rename addon decorator, class names, manifest.json name field
  2. Keep vendor config in params; extract secrets for auth and forward the rest to the vendor request layer
  3. Implement the abstract methods for your vendor API
  4. Create tests/configs/ with required config files (see below)
  5. Run guarder tests: task tts-guarder-test EXTENSION=my_vendor_tts
  6. Run formatter: task format

Typical required test config files for TTS: property_basic_audio_setting1.json,
property_basic_audio_setting2.json, property_dump.json, property_miss_required.json,
property_invalid.json

Some extensions also keep property.json as a default valid config, but the exact
set depends on the extension template and guarder harness in use.

Optional / vendor-dependent TTS configs:

  • property_subtitle_alignment.json if the vendor emits word/segment timestamps

Required test config files for ASR: property_en.json, property_zh.json,
property_invalid.json, property_dump.json

For full walkthrough with code and guarder expectations, see
Extension Development and Testing.

New ASR/TTS Extension Checklist

Use one recent strong example as the main template, not just any extension of
the same type.

For every new ASR extension or ASR behavior change, review against
ASR Plugin Design and Review Guide. Its
MUST rules and PR checklist are the merge baseline for lifecycle, connection
state, buffering, finalize, reconnect, result shape, metrics, and tests.

Type Strong Template Why
TTS (WS) deepgram_tts Better lifecycle and standalone test coverage
ASR openai_asr_python Stronger result-shape testing than thinner templates

Minimum end-to-end steps:

  1. Verify the vendor wire contract first: endpoint, event names, payload encoding, finalize primitive.
  2. Copy a recent extension with the same transport shape.
  3. Implement config loading, secret redaction, and config: logging.
  4. Implement error classification and vendor_info.
  5. Add standalone tests before running guarders.
  6. Run guarders sequentially, not in parallel, inside the same container.
  7. Add README and example graph wiring only after the extension tests are green.

Add Extension to a Graph

  1. Add node to predefined_graphs[].graph.nodes[] in the example's tenapp/property.json:

    json
    {"type": "extension", "name": "my_tts", "addon": "my_tts_python",
     "extension_group": "tts",
     "property": {"params": {"api_key": "${env:MY_API_KEY}"}}}
  2. Add connections — wire data flow between extensions:

    json
    {"extension": "my_tts",
     "data": [{"name": "tts_text_input", "source": [{"extension": "main"}]}],
     "audio_frame": [{"name": "pcm_frame", "dest": [{"extension": "agora_rtc"}]}]}
  3. Add dependency to example tenapp/manifest.json (local extensions use path):

    json
    {"path": "../../../ten_packages/extension/my_tts_python"}
  4. Install with task install (not bare tman install — the latter can wipe bin/worker).

  5. Nuclear restart — required when graphs are added/removed. See
    Setup → Restart Procedures for the exact
    command sequence.

See Graph Configuration for connection types and
routing patterns. When adding a TTS vendor, copy a confirmed working voice graph and
preserve its routing model. Some shipped graphs rely on explicit message connections,
while others keep part of the orchestration inside main_control.

For complex multi-graph setups (A/B testing vendors, avatar variants), use
rebuild_property.py instead of hand-editing. See
Generating property.json.

Customize the Main Extension

The "main" extension orchestrates agent behavior (greetings, tool routing, interruption).
Three implementation variants exist:

Variant File Use Case
Python Cascade main_cascade_python ASR → LLM → TTS pipeline
Python Realtime V2V main_realtime_python OpenAI Realtime API (voice-to-voice)
Node.js Cascade main_nodejs TypeScript implementation

Modify on_data() to change event routing, on_cmd() for tool handling.

Run Tests

bash
# Standalone (single extension, with install)
sudo docker exec ten_agent_dev bash -c \
  "cd /app && task test-extension EXTENSION=agents/ten_packages/extension/<ext>"

# Integration guarder (ASR or TTS — run sequentially, not in parallel)
sudo docker exec ten_agent_dev bash -c \
  "cd /app && task tts-guarder-test EXTENSION=<ext> CONFIG_DIR=tests/configs"

Other variants (task test, task test-extension-no-install, task asr-guarder-test) and
debugging guidance live in Testing.

Restart After Changes

What Changed Action
property.json (graphs added) Nuclear restart (kill all, remove lock, task run)
property.json (config only) No restart needed (loaded per session)
.env docker compose down && docker compose up -d + deps
Python code Restart server only
Go code task install then restart server
Container restart Reinstall Python deps, then task run

Build and Install

Always prefer task install over bare tman install:

bash
sudo docker exec ten_agent_dev bash -c \
  "cd /app/agents/examples/<example> && task install"

Python-deps-only and tman install variants are in
Setup → Install and Run.

Update Extension Code in Running Container

See Operations and Restarts for docker cp syntax,
symlink verification, and restart steps.

Pre-Commit / Pre-Push Checks

Run all of these before pushing — each maps to a CI job that blocks merge,
and there are no local hooks for most of them, so CI is the first thing that
catches a miss. For an extension change under
agents/ten_packages/extension/<ext>/:

bash
# 1. Format + static checks (CI: task check, task lint)
sudo docker exec ten_agent_dev bash -c \
  "cd /app && task format && task check && task lint"

# 2. Standalone tests for the extension
sudo docker exec ten_agent_dev bash -c \
  "cd /app && task test-extension EXTENSION=agents/ten_packages/extension/<ext>"

# 3. Guarder tests against the real vendor API (TTS shown; ASR uses asr-guarder-test)
sudo docker exec ten_agent_dev bash -c \
  "cd /app && task tts-guarder-test EXTENSION=<ext> CONFIG_DIR=tests/configs"

Then verify commit messages — a separate CI job
(Lint Commit Messages / commitlint) runs @commitlint/config-conventional
on every commit in the PR, with no local hook. The usual trip-up is
body-max-line-length (≤100 chars per body line) — hard-wrap the body and use
git commit -F msg.txt. Full rules and a self-check script:
Conventions → Commit Messages.

Checklist (all must be green before git push):

Gate Command / rule CI job
Black format task check (--line-length 80) format check
Pylint task lint (any warning is fatal) lint
Standalone tests task test-extension EXTENSION=... per-extension tests
Guarder tests task tts-guarder-test / task asr-guarder-test guarder
Commit messages conventional + body lines ≤100 commitlint

Note: a stale .ten/ left in an extension dir (e.g. from a prior
tman install --standalone) makes task check report spurious reformatting
and breaks the next standalone install with a symlink error. Remove it
(rm -rf <ext>/.ten) before re-running.


Ai L1 06 Interfaces (`docs/ai/L1/06_interfaces.md`)

06 Interfaces

REST API contracts, graph connection schemas, and base class abstract methods.

REST API Endpoints

The Go server (server/internal/http_server.go) exposes:

Endpoint Method Purpose Key Fields
/health GET Health check Returns {"code":"0"}
/graphs GET List available graphs Returns data[].name
/start POST Start agent session graph_name, channel_name
/stop POST Stop agent session channel_name
/ping POST Keep session alive channel_name
/list GET List active sessions Returns worker list
/token/generate POST Generate Agora RTC token channel_name, uid

POST /start Request Body

json
{
  "request_id": "uuid",
  "channel_name": "test_channel",
  "user_uid": 176573,
  "graph_name": "voice_assistant",
  "properties": {
    "llm": {"model": "gpt-4o-mini"}
  },
  "timeout": 60
}
  • properties — per-extension overrides merged into graph node properties
  • timeout — seconds of inactivity before auto-stop (-1 = never)

Graph Connection Types

Connections in property.json define data flow between extensions:

Command Connections (`cmd`)

json
{"extension": "main", "cmd": [
  {"names": ["tool_register"], "dest": [{"extension": "llm"}]},
  {"names": ["on_user_joined"], "source": [{"extension": "agora_rtc"}]}
]}

Common commands: tool_register, on_user_joined, flush, chat_completion_call,
update_configs

Data Connections (`data`)

json
{"extension": "llm", "data": [
  {"name": "text_data", "source": [{"extension": "main"}]},
  {"name": "text_data", "dest": [{"extension": "tts"}]}
]}

Common data: asr_result, text_data, tts_text_input, tts_audio_start,
tts_audio_end, error

Audio Frame Connections (`audio_frame`)

json
{"extension": "agora_rtc", "audio_frame": [
  {"name": "pcm_frame", "dest": [{"extension": "stt"}]}
]}

Video Frame Connections (`video_frame`)

json
{"extension": "agora_rtc", "video_frame": [
  {"name": "video_frame", "dest": [{"extension": "vision"}]}
]}

Base Class Abstract Methods

ASR (`AsyncASRBaseExtension`)

Method Returns Purpose
vendor() str Vendor name (e.g., "deepgram")
start_connection() None Connect to ASR service
stop_connection() None Disconnect
send_audio(frame, session_id) bool Send audio frame to service
finalize(session_id) None Drain pending audio
is_connected() bool Connection status check
input_audio_sample_rate() int Expected sample rate (e.g., 16000)
buffer_strategy() ASRBufferConfig Audio buffering behavior

Output helpers: send_asr_result(), send_asr_error(), send_asr_finalize_end(),
send_connect_delay_metrics(), send_vendor_metrics()

TTS (`AsyncTTS2BaseExtension`)

Method Returns Purpose
vendor() str Vendor name (e.g., "elevenlabs")
request_tts(tts_text_input) None Process queued text and emit audio via helpers
cancel_tts() None Handle flush/cancellation when overridden
synthesize_audio_sample_rate() int Output sample rate (e.g., 24000)
synthesize_audio_channels() int Channel count (default: 1)
synthesize_audio_sample_width() int Bytes per sample (default: 2)

Output helpers: send_tts_audio_data(), send_tts_audio_start(), send_tts_audio_end(),
send_tts_error(), send_tts_ttfb_metrics(), send_tts_text_result()

State machine: QUEUED → PROCESSING → FINALIZING → COMPLETED (per request)

TTS HTTP (`AsyncTTS2HttpExtension`)

Method Returns Purpose
create_config(config_json_str) AsyncTTS2HttpConfig Parse vendor config from property JSON
create_client(config, ten_env) AsyncTTS2HttpClient Build the vendor HTTP client
vendor() str Vendor name
synthesize_audio_sample_rate() int Output sample rate

LLM (`AsyncLLMBaseExtension`)

Method Returns Purpose
on_call_chat_completion() varies Handle sync command requests
on_data_chat_completion() varies Handle stream-based data input
on_tools_update(tool_metadata) None Handle new tool registration

Tool flow: Extensions register tools via CMD_TOOL_REGISTER → LLM stores in
available_tools → LLM calls tools during completion → results returned.

Manifest API Interface

Extensions declare their API interface in manifest.json:

json
{
  "api": {
    "interface": [
      {"import_uri": "../../system/ten_ai_base/api/tts-interface.json"}
    ],
    "property": {
      "properties": {
        "params": {
          "type": "object",
          "properties": {
            "api_key": {"type": "string"},
            "model": {"type": "string"},
            "sample_rate": {"type": "int32"}
          }
        }
      }
    }
  }
}

Interface JSON files define the standard cmd/data/audio_frame schemas for each extension type.

Canonical Payload Notes

  • ASR asr_result: keep session_id inside metadata, never as a top-level
    field. Locked-interim vs final lives at metadata.asr_info.locked.
  • Error payloads: include vendor_info (vendor name + vendor code + vendor
    message) whenever the failure originated upstream.
  • ASR connect_delay metrics are valid before session_id is known; other
    metrics should wait.

See Extension Development for full example payloads.

Portal References


Ai L1 07 Gotchas (`docs/ai/L1/07_gotchas.md`)

07 Gotchas

Critical pitfalls, tribal knowledge, and troubleshooting.

CRITICAL: Property Getters Return Tuples

All get_property_*() methods return (value, error_or_none), not the raw value.

python
# WRONG — causes TypeError
threshold = await ten_env.get_property_float("threshold")
if threshold > 0.5:  # TypeError: '>' not supported between 'float' and 'tuple'

# CORRECT — extract from tuple
threshold_result = await ten_env.get_property_float("threshold")
threshold = threshold_result[0] if isinstance(threshold_result, tuple) else threshold_result

This applies to get_property_string(), get_property_int(), get_property_float(),
get_property_bool(). Always extract [0].

CRITICAL: Signal Handlers Forbidden

Extensions run in worker threads. Signal handlers only work in the main thread.

python
# WRONG — raises ValueError: signal only works in main thread
signal.signal(signal.SIGTERM, handler)
atexit.register(cleanup)

# CORRECT — use extension lifecycle
async def on_stop(self, ten_env):
    await self.cleanup()

CRITICAL: Always Use `task run`

Never start the server with ./bin/api or ./bin/worker directly.
task run sets the correct PYTHONPATH and starts all services together
(API server + playground + TMAN Designer).

Zombie Worker Processes

Worker processes (bin/worker) can survive container and server restarts.
Always check for and kill zombies before restarting.

.env Loaded at Container Startup Only

Editing .env while the container is running has no effect. You must
docker compose down && docker compose up -d, then reinstall Python deps.

Next.js Lock File

After crashes, .next/dev/lock becomes stale, preventing restart. Delete it
and do a full restart. See Operations and Restarts.

Python Deps Not Persisted

Python dependencies are lost on container restart. Always reinstall after
docker compose down && up.

tman install Can Wipe bin/worker

Running tman install when system dependencies have newer versions replaces
the runtime packages and deletes bin/worker. Use task install (full
rebuild) instead of bare tman install. Signs: Worker fails with
bin/worker: No such file or directory in logs.

Never manually ln -s for extensions. Use tman install which resolves
dependencies and creates correct links. If a symlink is missing after
tman install, create it manually as a fallback.

docker cp Creates Nested Directories

When using docker cp to update extension code, trailing slashes create
nested directories. Use docker cp ./ext/. container:/path/ext/ syntax.
Signs: ModuleNotFoundError: No module named 'ten_packages.extension.X'.

Vendor "PCM" Is Not Always PCM16

The TEN pcm_frame contract expects signed 16-bit (PCM16) mono. A vendor's
response_format: "pcm" may be something else — e.g. Mistral/Voxtral streams
headerless float32 LE at 24 kHz. Sending those bytes through unconverted
produces noise. Convert in the client before yielding RESPONSE, and make
synthesize_audio_sample_rate() return the rate you actually emit (24000 for
Voxtral), not a default.

Two practical traps when converting a stream:

  • Don't split a sample across chunk boundaries. float32 is 4 bytes; buffer a
    trailing partial sample between aiter_bytes() chunks (see Float32ToPcm16 in
    mistral_tts_python).
  • Clamp before scaling to [-1, 1] and map NaN to silence so one corrupt
    sample can't crash the stream.

Requesting raw pcm (vs. a container like wav/mp3) also lowers
time-to-first-audio — there is no header to buffer before the first samples.

Audio Routing: Split at Source Only

When routing audio to multiple destinations, the split must happen at the
source node (e.g., agora_rtc), not at intermediate nodes. Splitting from
intermediate nodes can cause crashes.

Frontend Caches Graph List

The playground caches the /graphs API response. When adding or removing
graphs from property.json, a full restart is required — simple server
restart is not enough.

Guarder apps create their own ten_packages/extension/* symlinks. These can
point at dead host paths and break imports after worktree changes or manual
copies.

Typical symptom:
ModuleNotFoundError: No module named 'ten_packages.extension.<name>'

Reliable fix inside the container:

bash
ln -sf /app/agents/ten_packages/extension/<name> \
  /app/agents/integration_tests/<guarder>/ten_packages/extension/<name>

Run Guarders Sequentially

Do not run ASR and TTS guarders in parallel in the same container. Their build
scripts may collide on shared temp paths and fail with errors like:
/tmp/test: Text file busy

Run one guarder to completion, then start the other.

Production Graph Picker 403

If the playground requests /api/dev/v1/graphs in production, the graph list
may be empty even though /api/agents/graphs is healthy. Check
NEXT_PUBLIC_EDIT_GRAPH_MODE; production deployments should typically set it to
false.

Manifest Module Name Must Match

The name field in extension manifest.json must exactly match the addon
field used in graph nodes in property.json. Mismatches cause silent failures.

next-server Holds Port 3000

Killing node and bun is not enough — next-server is a separate process
that holds port 3000. If port 3000 is occupied, Next.js silently starts on
3001+ which isn't Docker-exposed, making the frontend appear down.

CI `lint` Runs Both Black and Pylint

The lint CI check on every PR runs two task targets:

  • task check — black --check (formatting)
  • task lint — pylint (any warning is fatal, including W0611 unused-import)

If you commit from a host shell rather than inside the container, the repo's
pre-commit hook does not run and either failure will surface only in CI.
Before every push:

bash
sudo docker exec ten_agent_dev bash -c \
  "cd /app && task format && task check && task lint"

Common foot-gun: leaving an unused import after a refactor. Pylint is strict
and a single W0611 fails the build. See Formatting.

Docker Permission Denied

docker ps fails with permission denied while trying to connect to the docker API at unix:///var/run/docker.sock when the current user is not in
the docker group. The fix is sudo docker … for every invocation, not
adding the user to the group mid-session. Examples in this doc set are
inconsistent (some show docker exec, others sudo docker exec) — on a
host where the bare form fails, treat every example as needing sudo.

Apple Silicon Docker

Docker containers may need Rosetta for x86 images on Apple Silicon Macs.
Enable in Docker Desktop: Settings > General > Use Rosetta.

Windows Line Endings

Before cloning on Windows: git config --global core.autocrlf false


Ai L1 08 Security (`docs/ai/L1/08_security.md`)

08 Security

Secret management, input validation, and repository hygiene.

API Key Management

  • Single source: All API keys live in ai_agents/.env (git-ignored)
  • Never hardcode keys in property.json — use ${env:VAR_NAME} substitution
  • Persistent storage: Keep a copy of keys outside the repo (e.g., ~/api_keys.txt)
    so branch switches don't lose them
  • See .env.example for the complete variable catalog

Environment Variable Substitution

In property.json, reference secrets via:

json
{
  "api_key": "${env:DEEPGRAM_API_KEY}",
  "region": "${env:AZURE_REGION|eastus}"
}
Syntax Behavior
${env:VAR} Required — error if missing
${env:VAR|} Optional — empty if missing
${env:VAR|default} Optional — default if missing

Sensitive Data in Logs

Extensions must encrypt sensitive fields before logging:

python
def to_str(self, sensitive_handling: bool = True) -> str:
    config = copy.deepcopy(self)
    if config.params and "api_key" in config.params:
        config.params["api_key"] = utils.encrypt(config.params["api_key"])
    return f"{config}"

Never log raw API keys, tokens, or credentials.

Server-Side Protections

The Go server (http_server.go) implements:

  • Path traversal prevention: Ignores client-requested tenapp_dir, always uses
    the launch-configured directory
  • Channel name sanitization: Validated before use in file operations
  • Safe type conversion: Property values are type-checked during merge
  • Recursive property merge: Prevents injection via nested config overrides

Pre-Commit Hooks

Hook What It Checks
pre-commit Scans staged files for API key patterns (API_KEY.*=[A-Za-z0-9]{20,})
pre-commit Black formatting compliance (line-length 80)
commit-msg Conventional commit format, blocks AI tool name references

Git-Ignored Files

These are auto-generated — never modify or commit them:

Pattern Source
manifest-lock.json tman dependency resolve
compile_commands.json Build system
BUILD.gn, .gn Build configuration
out/, build/ Build output
.ten/ TEN runtime files
bin/main, bin/worker Compiled binaries
.release/ Release packaging
node_modules/ JS dependencies
.env Environment secrets

Files That Should Never Be Committed

  • .env (API keys and secrets)
  • *.pem (certificates)
  • *.pcm (audio dumps)
  • Credential files, tokens, session data

Third Party Curl Docs ALTSVC (`third_party/curl/docs/ALTSVC.md`)

Alt-Svc

curl features support for the Alt-Svc: HTTP header.

Enable Alt-Svc in build

./configure --enable-alt-svc

(enabled by default since 7.73.0)

Standard

RFC 7838

Alt-Svc cache file format

This is a text based file with one line per entry and each line consists of nine
space separated fields.

Example

text
h2 quic.tech 8443 h3-22 quic.tech 8443 "20190808 06:18:37" 0 0

Fields

  1. The ALPN id for the source origin
  2. The host name for the source origin
  3. The port number for the source origin
  4. The ALPN id for the destination host
  5. The host name for the destination host
  6. The host number for the destination host
  7. The expiration date and time of this entry within double quotes. The date format is "YYYYMMDD HH:MM:SS" and the time zone is GMT.
  8. Boolean (1 or 0) if "persist" was set for this entry
  9. Integer priority value (not currently used)

TODO

  • handle multiple response headers, when one of them says clear (should
    override them all)
  • using Age: value for caching age as per spec
  • CURLALTSVC_IMMEDIATELY support

Third Party Curl Docs BINDINGS (`third_party/curl/docs/BINDINGS.md`)

libcurl bindings

Creative people have written bindings or interfaces for various environments
and programming languages. Using one of these allows you to take advantage of
curl powers from within your favourite language or system.

This is a list of all known interfaces as of this writing.

The bindings listed below are not part of the curl/libcurl distribution
archives, but must be downloaded and installed separately.

Ada95 Written by Andreas Almroth

Basic ScriptBasic bindings written by Peter Verhas

C++: curlpp Written by Jean-Philippe Barrette-LaPierre,
curlcpp by Giuseppe Persico and C++
Requests
by Huu Nguyen

Ch Written by Stephen Nestinger and Jonathan Rogado

Cocoa: BBHTTP written by Bruno de Carvalho
curlhandle Written by Dan Wood

Clojure: clj-curl by Lucas Severo

D Written by Kenneth Bogert

Delphi Written by Mikhail Merkuryev

Dylan Written by Chris Double

Eiffel Written by Eiffel Software

Euphoria Written by Ray Smith

Falcon

Ferite Written by Paul Querna

Fortran Written by Philipp Engel

Gambas

glib/GTK+ Written by Richard Atterer

Go: go-curl by ShuYu Wang

Guile Written by Michael L. Gran

Harbour Written by Viktor Szakats

Haskell Written by Galois, Inc

Hollywood hURL by Andreas Falkenhahn

Java

Julia Written by Amit Murthy

Kapito is an Erlang HTTP library around libcurl.

Lisp Written by Liam Healy

Lua: luacurl by Alexander Marinov, Lua-cURL by Jürgen Hötzel

Mono Written by Jeffrey Phillips

.NET libcurl-net by Jeffrey Phillips

Nim wrapper for libcurl

node.js node-libcurl by Jonathan Cardoso Machado

Object-Pascal Free Pascal, Delphi and Kylix binding written by Christophe Espern.

OCaml Written by Lars Nilsson and ygrek

Pascal Free Pascal, Delphi and Kylix binding written by Jeffrey Pohlmeyer.

Perl: WWW::Curl Maintained by Cris
Bailiff and Bálint Szilakszi,
perl6-net-curl by Ahmad M. Zawawi
NET::Curl by Przemyslaw Iskra

PHP Originally written by Sterling Hughes

PostgreSQL - HTTP client for PostgreSQL

PostgreSQL - cURL client for PostgreSQL

PureBasic uses libcurl in its "native" HTTP subsystem

Python PycURL by Kjetil Jacobsen

Q The libcurl module is part of the default install

R

Rexx Written Mark Hessling

Ring RingLibCurl by Mahmoud Fayed

RPG, support for ILE/RPG on OS/400 is included in source distribution

Ruby: curb written by Ross Bamford,
ruby-curl-multi by Kristjan Petursson and Keith Rarick

Rust curl-rust - by Carl Lerche

Scheme Bigloo binding by Kirill Lisovsky

Scilab binding by Sylvestre Ledru

S-Lang by John E Davis

Smalltalk Written by Danil Osipchuk

SP-Forth Written by Andrey Cherezov

SPL Written by Clifford Wolf

Tcl Tclcurl by Andrés García

Visual Basic libcurl-vb by Jeffrey Phillips

Visual Foxpro by Carlos Alloatti

wxWidgets Written by Casey O'Donnell

XBLite Written by David Szafranski

Xojo Written by Andrew Lambert


Third Party Curl Docs BUFQ (`third_party/curl/docs/BUFQ.md`)

bufq

This is an internal module for managing I/O buffers. A bufq can be written
to and read from. It manages read and write positions and has a maximum size.

read/write

Its basic read/write functions have a similar signature and return code handling
as many internal Curl read and write ones.

text
ssize_t Curl_bufq_write(struct bufq *q, const unsigned char *buf, size_t len, CURLcode *err);

- returns the length written into `q` or -1 on error.
- writing to a full `q` will return -1 and set *err to CURLE_AGAIN

ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, CURLcode *err);

- returns the length read from `q` or -1 on error.
- reading from an empty `q` will return -1 and set *err to CURLE_AGAIN

To pass data into a bufq without an extra copy, read callbacks can be used.

text
typedef ssize_t Curl_bufq_reader(void *reader_ctx, unsigned char *buf, size_t len,
                                 CURLcode *err);

ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader, void *reader_ctx, 
                        CURLcode *err);

Curl_bufq_slurp() will invoke the given reader callback, passing it its own internal
buffer memory to write to. It may invoke the reader several times, as long as it has space
and while the reader always returns the length that was requested. There are variations of slurp that call the reader at most once or only read in a
maximum amount of bytes.

The analog mechanism for write out buffer data is:

text
typedef ssize_t Curl_bufq_writer(void *writer_ctx, const unsigned char *buf, size_t len,
                                 CURLcode *err);

ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer, void *writer_ctx, 
                       CURLcode *err);

Curl_bufq_pass() will invoke the writer, passing its internal memory and remove the
amount that writer reports.

peek and skip

It is possible to get access to the memory of data stored in a bufq with:

text
bool Curl_bufq_peek(const struct bufq *q, const unsigned char **pbuf, size_t *plen);

On returning TRUE, pbuf will point to internal memory with plen bytes that one may read. This will only
be valid until another operation on bufq is performed.

Instead of reading bufq data, one may simply skip it:

text
void Curl_bufq_skip(struct bufq *q, size_t amount);

This will remove amount number of bytes from the bufq.

lifetime

bufq is initialized and freed similar to the dynbuf module. Code using bufq will
hold a struct bufq somewhere. Before it uses it, it invokes:

text
void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks);

The bufq is told how many "chunks" of data it shall hold at maximum and how large those
"chunks" should be. There are some variants of this, allowing for more options. How "chunks" are handled in a bufq is presented in the section about memory management.

The user of the bufq has the responsibility to call:

text
void Curl_bufq_free(struct bufq *q);

to free all resources held by q. It is possible to reset a bufq to empty via:

text
void Curl_bufq_reset(struct bufq *q);

memory management

Internally, a bufq uses allocation of fixed size, e.g. the "chunk_size", up to a maximum number, e.g. "max_chunks". These chunks are allocated on demand, therefore writing to a bufq may return CURLE_OUT_OF_MEMORY. Once the max number of chunks are used, the bufq will report that it is "full".

Each chunks has a read and write index. A bufq keeps its chunks in a list. Reading happens always at the head chunk, writing always goes to the tail chunk. When the head chunk becomes empty, it is removed. When the tail chunk becomes full, another chunk is added to the end of the list, becoming the new tail.

Chunks that are no longer used are returned to a spare list by default. If the bufq is created with option BUFQ_OPT_NO_SPARES those chunks will be freed right away.

If a bufq is created with a bufc_pool, the no longer used chunks are returned to the pool. Also bufq will ask the pool for a chunk when it needs one. More in section "pools".

empty, full and overflow

One can ask about the state of a bufq with methods such as Curl_bufq_is_empty(q),
Curl_bufq_is_full(q), etc. The amount of data held by a bufq is the sum of the data in all its chunks. This is what is reported by Curl_bufq_len(q).

Note that a bufq length and it being "full" are only loosely related. A simple example:

  • create a bufq with chunk_size=1000 and max_chunks=4.
  • write 4000 bytes to it, it will report "full"
  • read 1 bytes from it, it will still report "full"
  • read 999 more bytes from it, and it will no longer be "full"

The reason for this is that full really means: bufq uses max_chunks and the last one cannot be written to.

So when you read 1 byte from the head chunk in the example above, the head still hold 999 unread bytes. Only when those are also read, can the head chunk be removed and a new tail be added.

There is another variation to this. If you initialized a bufq with option BUFQ_OPT_SOFT_LIMIT, it will allow writes beyond the max_chunks. It will report full, but one can still write. This option is necessary, if partial writes need to be avoided. But it means that you will need other checks to keep the bufq from growing ever larger and larger.

pools

A struct bufc_pool may be used to create chunks for a bufq and keep spare ones around. It is initialized
and used via:

text
void Curl_bufcp_init(struct bufc_pool *pool, size_t chunk_size, size_t spare_max);

void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool, size_t max_chunks, int opts);

The pool gets the size and the mount of spares to keep. The bufq gets the pool and the max_chunks. It no longer needs to know the chunk sizes, as those are managed by the pool.

A pool can be shared between many bufqs, as long as all of them operate in the same thread. In curl that would be true for all transfers using the same multi handle. The advantages of a pool are:

  • when all bufqs are empty, only memory for max_spare chunks in the pool is used. Empty bufqs will hold no memory.
  • the latest spare chunk is the first to be handed out again, no matter which bufq needs it. This keeps the footprint of "recently used" memory smaller.

Third Party Curl Docs BUFREF (`third_party/curl/docs/BUFREF.md`)

bufref

This is an internal module for handling buffer references. A referenced
buffer is associated with its destructor function that is implicitly called
when the reference is invalidated. Once referenced, a buffer cannot be
reallocated.

A data length is stored within the reference for binary data handling
purposes; it is not used by the bufref API.

The struct bufref is used to hold data referencing a buffer. The members of
that structure MUST NOT be accessed or modified without using the dedicated
bufref API.

`init`

c
void Curl_bufref_init(struct bufref *br);

Initializes a bufref structure. This function MUST be called before any
other operation is performed on the structure.

Upon completion, the referenced buffer is NULL and length is zero.

This function may also be called to bypass referenced buffer destruction while
invalidating the current reference.

`free`

c
void Curl_bufref_free(struct bufref *br);

Destroys the previously referenced buffer using its destructor and
reinitializes the structure for a possible subsequent reuse.

`set`

c
void Curl_bufref_set(struct bufref *br, const void *buffer, size_t length,
                     void (*destructor)(void *));

Releases the previously referenced buffer, then assigns the new buffer to
the structure, associated with its destructor function. The latter can be
specified as NULL: this will be the case when the referenced buffer is
static.

if buffer is NULL, length must be zero.

`memdup`

c
CURLcode Curl_bufref_memdup(struct bufref *br, const void *data, size_t length);

Releases the previously referenced buffer, then duplicates the length-byte
data into a buffer allocated via malloc() and references the latter
associated with destructor curl_free().

An additional trailing byte is allocated and set to zero as a possible string
null-terminator; it is not counted in the stored length.

Returns CURLE_OK if successful, else CURLE_OUT_OF_MEMORY.

`ptr`

c
const unsigned char *Curl_bufref_ptr(const struct bufref *br);

Returns a const unsigned char * to the referenced buffer.

`len`

c
size_t Curl_bufref_len(const struct bufref *br);

Returns the stored length of the referenced buffer.


Third Party Curl Docs BUG BOUNTY (`third_party/curl/docs/BUG-BOUNTY.md`)

The curl bug bounty

The curl project runs a bug bounty program in association with
HackerOne and the Internet Bug
Bounty
.

How does it work?

Start out by posting your suspected security vulnerability directly to curl's
HackerOne program
.

After you have reported a security issue, it has been deemed credible, and a
patch and advisory has been made public, you may be eligible for a bounty from
this program. See the SECURITY-PROCESS document for how
we work with security issues.

What are the reward amounts?

The curl project offers monetary compensation for reported and published
security vulnerabilities. The amount of money that is rewarded depends on how
serious the flaw is determined to be.

Since 2021, the Bug Bounty is managed in association with the Internet Bug
Bounty and they will set the reward amounts. If it would turn out that they
set amounts that are way lower than we can accept, the curl project intends to
"top up" rewards.

In 2022, typical "Medium" rated vulnerabilities have been rewarded 2,400 USD
each.

Who is eligible for a reward?

Everyone and anyone who reports a security problem in a released curl version
that has not already been reported can ask for a bounty.

Dedicated - paid for - security audits that are performed in collaboration
with curl developers are not eligible for bounties.

Vulnerabilities in features that are off by default and documented as
experimental are not eligible for a reward.

The vulnerability has to be fixed and publicly announced (by the curl project)
before a bug bounty will be considered.

Once the vulnerability has been published by curl, the researcher can request
their bounty from the Internet Bug Bounty.

Bounties need to be requested within twelve months from the publication of the
vulnerability.

Product vulnerabilities only

This bug bounty only concerns the curl and libcurl products and thus their
respective source codes - when running on existing hardware. It does not
include curl documentation, curl websites, or other curl related
infrastructure.

The curl security team is the sole arbiter if a reported flaw is subject to a
bounty or not.

How are vulnerabilities graded?

The grading of each reported vulnerability that makes a reward claim will be
performed by the curl security team. The grading will be based on the CVSS
(Common Vulnerability Scoring System) 3.0.

How are reward amounts determined?

The curl security team gives the vulnerability a score or severity level, as
mentioned above. The actual monetary reward amount is decided and paid by the
Internet Bug Bounty..

Regarding taxes, etc. on the bounties

In the event that the individual receiving a bug bounty needs to pay taxes on
the reward money, the responsibility lies with the receiver. The curl project
or its security team never actually receive any of this money, hold the money,
or pay out the money.


Third Party Curl Docs BUGS (`third_party/curl/docs/BUGS.md`)

BUGS

There are still bugs

Curl and libcurl keep being developed. Adding features and changing code
means that bugs will sneak in, no matter how hard we try to keep them out.

Of course there are lots of bugs left. And lots of misfeatures.

To help us make curl the stable and solid product we want it to be, we need
bug reports and bug fixes.

Where to report

If you cannot fix a bug yourself and submit a fix for it, try to report an as
detailed report as possible to a curl mailing list to allow one of us to have
a go at a solution. You can optionally also submit your problem in curl's
bug tracking system
.

Please read the rest of this document below first before doing that.

If you feel you need to ask around first, find a suitable mailing list and post your questions there.

Security bugs

If you find a bug or problem in curl or libcurl that you think has a security
impact, for example a bug that can put users in danger or make them
vulnerable if the bug becomes public knowledge, then please report that bug
using our security development process.

Security related bugs or bugs that are suspected to have a security impact,
should be reported on the curl security tracker at
HackerOne
.

This ensures that the report reaches the curl security team so that they
first can deal with the report away from the public to minimize the harm
and impact it will have on existing users out there who might be using the
vulnerable versions.

The curl project's process for handling security related issues is
documented separately.

What to report

When reporting a bug, you should include all information that will help us
understand what is wrong, what you expected to happen and how to repeat the
bad behavior. You therefore need to tell us:

  • your operating system's name and version number

  • what version of curl you are using (curl -V is fine)

  • versions of the used libraries that libcurl is built to use

  • what URL you were working with (if possible), at least which protocol

and anything and everything else you think matters. Tell us what you expected
to happen, tell use what did happen, tell us how you could make it work
another way. Dig around, try out, test. Then include all the tiny bits and
pieces in your report. You will benefit from this yourself, as it will enable
us to help you quicker and more accurately.

Since curl deals with networks, it often helps us if you include a protocol
debug dump with your bug report. The output you get by using the -v or
--trace options.

If curl crashed, causing a core dump (in Unix), there is hardly any use to
send that huge file to anyone of us. Unless we have the same system setup as
you, we cannot do much with it. Instead, we ask you to get a stack trace and
send that (much smaller) output to us instead.

The address and how to subscribe to the mailing lists are detailed in the
MANUAL.md file.

libcurl problems

When you have written your own application with libcurl to perform transfers,
it is even more important to be specific and detailed when reporting bugs.

Tell us the libcurl version and your operating system. Tell us the name and
version of all relevant sub-components like for example the SSL library
you are using and what name resolving your libcurl uses. If you use SFTP or
SCP, the libssh2 version is relevant etc.

Showing us a real source code example repeating your problem is the best way
to get our attention and it will greatly increase our chances to understand
your problem and to work on a fix (if we agree it truly is a problem).

Lots of problems that appear to be libcurl problems are actually just abuses
of the libcurl API or other malfunctions in your applications. It is advised
that you run your problematic program using a memory debug tool like valgrind
or similar before you post memory-related or "crashing" problems to us.

Who will fix the problems

If the problems or bugs you describe are considered to be bugs, we want to
have the problems fixed.

There are no developers in the curl project that are paid to work on bugs.
All developers that take on reported bugs do this on a voluntary basis. We do
it out of an ambition to keep curl and libcurl excellent products and out of
pride.

Please do not assume that you can just lump over something to us and it will
then magically be fixed after some given time. Most often we need feedback
and help to understand what you have experienced and how to repeat a
problem. Then we may only be able to assist YOU to debug the problem and to
track down the proper fix.

We get reports from many people every month and each report can take a
considerable amount of time to really go to the bottom with.

How to get a stack trace

First, you must make sure that you compile all sources with -g and that you
do not 'strip' the final executable. Try to avoid optimizing the code as well,
remove -O, -O2 etc from the compiler options.

Run the program until it cores.

Run your debugger on the core file, like <debugger> curl core. <debugger> should be replaced with the name of your debugger, in
most cases that will be gdb, but dbx and others also occur.

When the debugger has finished loading the core file and presents you a
prompt, enter where (without quotes) and press return.

The list that is presented is the stack trace. If everything worked, it is
supposed to contain the chain of functions that were called when curl
crashed. Include the stack trace with your detailed bug report, it will help a
lot.

Bugs in libcurl bindings

There will of course pop up bugs in libcurl bindings. You should then
primarily approach the team that works on that particular binding and see
what you can do to help them fix the problem.

If you suspect that the problem exists in the underlying libcurl, then please
convert your program over to plain C and follow the steps outlined above.

Bugs in old versions

The curl project typically releases new versions every other month, and we
fix several hundred bugs per year. For a huge table of releases, number of
bug fixes and more, see: https://curl.se/docs/releases.html

The developers in the curl project do not have bandwidth or energy enough to
maintain several branches or to spend much time on hunting down problems in
old versions when chances are we already fixed them or at least that they have
changed nature and appearance in later versions.

When you experience a problem and want to report it, you really SHOULD
include the version number of the curl you are using when you experience the
issue. If that version number shows us that you are using an out-of-date curl,
you should also try out a modern curl version to see if the problem persists
or how/if it has changed in appearance.

Even if you cannot immediately upgrade your application/system to run the
latest curl version, you can most often at least run a test version or
experimental build or similar, to get this confirmed or not.

At times people insist that they cannot upgrade to a modern curl version, but
instead, they "just want the bug fixed". That is fine, just do not count on us
spending many cycles on trying to identify which single commit, if that is
even possible, that at some point in the past fixed the problem you are now
experiencing.

Security wise, it is almost always a bad idea to lag behind the current curl
versions by a lot. We keep discovering and reporting security problems
over time see you can see in this
table

Bug fixing procedure

What happens on first filing

When a new issue is posted in the issue tracker or on the mailing list, the
team of developers first needs to see the report. Maybe they took the day off,
maybe they are off in the woods hunting. Have patience. Allow at least a few
days before expecting someone to have responded.

In the issue tracker, you can expect that some labels will be set on the issue
to help categorize it.

First response

If your issue/bug report was not perfect at once (and few are), chances are
that someone will ask follow-up questions. Which version did you use? Which
options did you use? How often does the problem occur? How can we reproduce
this problem? Which protocols does it involve? Or perhaps much more specific
and deep diving questions. It all depends on your specific issue.

You should then respond to these follow-up questions and provide more info
about the problem, so that we can help you figure it out. Or maybe you can
help us figure it out. An active back-and-forth communication is important
and the key for finding a cure and landing a fix.

Not reproducible

We may require further work from you who actually see or experience the
problem if we cannot reproduce it and cannot understand it even after having
gotten all the info we need and having studied the source code over again.

Unresponsive

If the problem have not been understood or reproduced, and there is nobody
responding to follow-up questions or questions asking for clarifications or
for discussing possible ways to move forward with the task, we take that as a
strong suggestion that the bug is unimportant.

Unimportant issues will be closed as inactive sooner or later as they cannot
be fixed. The inactivity period (waiting for responses) should not be shorter
than two weeks but may extend months.

Lack of time/interest

Bugs that are filed and are understood can unfortunately end up in the
"nobody cares enough about it to work on it" category. Such bugs are
perfectly valid problems that should get fixed but apparently are not. We
try to mark such bugs as KNOWN_BUGS material after a time of inactivity and
if no activity is noticed after yet some time those bugs are added to the
KNOWN_BUGS document and are closed in the issue tracker.

`KNOWN_BUGS`

This is a list of known bugs. Bugs we know exist and that have been pointed
out but that have not yet been fixed. The reasons for why they have not been
fixed can involve anything really, but the primary reason is that nobody has
considered these problems to be important enough to spend the necessary time
and effort to have them fixed.

The KNOWN_BUGS items are always up for grabs and we love the ones who bring
one of them back to life and offer solutions to them.

The KNOWN_BUGS document has a sibling document known as TODO.

`TODO`

Issues that are filed or reported that are not really bugs but more missing
features or ideas for future improvements and so on are marked as
'enhancement' or 'feature-request' and will be added to the TODO document
and the issues are closed. We do not keep TODO items open in the issue
tracker.

The TODO document is full of ideas and suggestions of what we can add or
fix one day. You are always encouraged and free to grab one of those items and
take up a discussion with the curl development team on how that could be
implemented or provided in the project so that you can work on ticking it odd
that document.

If an issue is rather a bug and not a missing feature or functionality, it is
listed in KNOWN_BUGS instead.

Closing off stalled bugs

The issue and pull request trackers only
hold "active" entries open (using a non-precise definition of what active
actually is, but they are at least not completely dead). Those that are
abandoned or in other ways dormant will be closed and sometimes added to
TODO and KNOWN_BUGS instead.

This way, we only have "active" issues open on GitHub. Irrelevant issues and
pull requests will not distract developers or casual visitors.


Third Party Curl Docs CHECKSRC (`third_party/curl/docs/CHECKSRC.md`)

checksrc

This is the tool we use within the curl project to scan C source code and
check that it adheres to our Source Code Style guide.

Usage

text
checksrc.pl [options] [file1] [file2] ...

Command line options

-W[file] skip that file and exclude it from being checked. Helpful
when, for example, one of the files is generated.

-D[dir] directory name to prepend to file names when accessing them.

-h shows the help output, that also lists all recognized warnings

What does `checksrc` warn for?

checksrc does not check and verify the code against the entire style guide.
The script is an effort to detect the most common mistakes and syntax mistakes
that contributors make before they get accustomed to our code style. Heck,
many of us regulars do the mistakes too and this script helps us keep the code
in shape.

text
checksrc.pl -h

Lists how to use the script and it lists all existing warnings it has and
problems it detects. At the time of this writing, the existing checksrc
warnings are:

  • ASSIGNWITHINCONDITION: Assignment within a conditional expression. The
    code style mandates the assignment to be done outside of it.

  • ASTERISKNOSPACE: A pointer was declared like char* name instead of the
    more appropriate char *name style. The asterisk should sit next to the
    name.

  • ASTERISKSPACE: A pointer was declared like char * name instead of the
    more appropriate char *name style. The asterisk should sit right next to
    the name without a space in between.

  • BADCOMMAND: There's a bad checksrc instruction in the code. See the
    Ignore certain warnings section below for details.

  • BANNEDFUNC: A banned function was used. The functions sprintf, vsprintf,
    strcat, strncat, gets are never allowed in curl source code.

  • BRACEELSE: '} else' on the same line. The else is supposed to be on the
    following line.

  • BRACEPOS: wrong position for an open brace ({).

  • BRACEWHILE: more than once space between end brace and while keyword

  • COMMANOSPACE: a comma without following space

  • COPYRIGHT: the file is missing a copyright statement

  • CPPCOMMENTS: // comment detected, that is not C89 compliant

  • DOBRACE: only use one space after do before open brace

  • EMPTYLINEBRACE: found empty line before open brace

  • EQUALSNOSPACE: no space after = sign

  • EQUALSNULL: comparison with == NULL used in if/while. We use !var.

  • EXCLAMATIONSPACE: space found after exclamations mark

  • FOPENMODE: fopen() needs a macro for the mode string, use it

  • INDENTATION: detected a wrong start column for code. Note that this
    warning only checks some specific places and will certainly miss many bad
    indentations.

  • LONGLINE: A line is longer than 79 columns.

  • MULTISPACE: Multiple spaces were found where only one should be used.

  • NOSPACEEQUALS: An equals sign was found without preceding space. We prefer
    a = 2 and not a=2.

  • NOTEQUALSZERO: check found using != 0. We use plain if(var).

  • ONELINECONDITION: do not put the conditional block on the same line as if()

  • OPENCOMMENT: File ended with a comment (/*) still "open".

  • PARENBRACE: ){ was used without sufficient space in between.

  • RETURNNOSPACE: return was used without space between the keyword and the
    following value.

  • SEMINOSPACE: There was no space (or newline) following a semicolon.

  • SIZEOFNOPAREN: Found use of sizeof without parentheses. We prefer
    sizeof(int) style.

  • SNPRINTF - Found use of snprintf(). Since we use an internal replacement
    with a different return code etc, we prefer msnprintf().

  • SPACEAFTERPAREN: there was a space after open parenthesis, ( text.

  • SPACEBEFORECLOSE: there was a space before a close parenthesis, text ).

  • SPACEBEFORECOMMA: there was a space before a comma, one , two.

  • SPACEBEFOREPAREN: there was a space before an open parenthesis, if (,
    where one was not expected

  • SPACESEMICOLON: there was a space before semicolon, ;.

  • TABS: TAB characters are not allowed

  • TRAILINGSPACE: Trailing whitespace on the line

  • TYPEDEFSTRUCT: we frown upon (most) typedefed structs

  • UNUSEDIGNORE: a checksrc inlined warning ignore was asked for but not
    used, that is an ignore that should be removed or changed to get used.

Extended warnings

Some warnings are quite computationally expensive to perform, so they are
turned off by default. To enable these warnings, place a .checksrc file in
the directory where they should be activated with commands to enable the
warnings you are interested in. The format of the file is to enable one
warning per line like so: enable <EXTENDEDWARNING>

Currently these are the extended warnings which can be enabled:

  • COPYRIGHTYEAR: the current changeset has not updated the copyright year in
    the source file

  • STRERROR: use of banned function strerror()

Ignore certain warnings

Due to the nature of the source code and the flaws of the checksrc tool,
there is sometimes a need to ignore specific warnings. checksrc allows a few
different ways to do this.

Inline ignore

You can control what to ignore within a specific source file by providing
instructions to checksrc in the source code itself. See examples below. The
instruction can ask to ignore a specific warning a specific number of times or
you ignore all of them until you mark the end of the ignored section.

Inline ignores are only done for that single specific source code file.

Example

text
/* !checksrc! disable LONGLINE all */

This will ignore the warning for overly long lines until it is re-enabled with:

text
/* !checksrc! enable LONGLINE */

If the enabling is not performed before the end of the file, it will be enabled
automatically for the next file.

You can also opt to ignore just N violations so that if you have a single long
line you just cannot shorten and is agreed to be fine anyway:

text
/* !checksrc! disable LONGLINE 1 */

... and the warning for long lines will be enabled again automatically after
it has ignored that single warning. The number 1 can of course be changed to
any other integer number. It can be used to make sure only the exact intended
instances are ignored and nothing extra.

Directory wide ignore patterns

This is a method we have transitioned away from. Use inline ignores as far as
possible.

Make a checksrc.skip file in the directory of the source code with the
false positive, and include the full offending line into this file.


Third Party Curl Docs CIPHERS (`third_party/curl/docs/CIPHERS.md`)

Ciphers

With curl's options
CURLOPT_SSL_CIPHER_LIST
and
--ciphers
users can control which ciphers to consider when negotiating TLS connections.

TLS 1.3 ciphers are supported since curl 7.61 for OpenSSL 1.1.1+, and since
curl 7.85 for Schannel with options
CURLOPT_TLS13_CIPHERS
and
--tls13-ciphers
. If you are using a different SSL backend you can try setting TLS 1.3 cipher
suites by using the respective regular cipher option.

The names of the known ciphers differ depending on which TLS backend that
libcurl was built to use. This is an attempt to list known cipher names.

OpenSSL

(based on OpenSSL docs)

When specifying multiple cipher names, separate them with colon (:).

SSL3 cipher suites

NULL-MD5
NULL-SHA
RC4-MD5
RC4-SHA
IDEA-CBC-SHA
DES-CBC3-SHA
DH-DSS-DES-CBC3-SHA
DH-RSA-DES-CBC3-SHA
DHE-DSS-DES-CBC3-SHA
DHE-RSA-DES-CBC3-SHA
ADH-RC4-MD5
ADH-DES-CBC3-SHA

TLS v1.0 cipher suites

NULL-MD5
NULL-SHA
RC4-MD5
RC4-SHA
IDEA-CBC-SHA
DES-CBC3-SHA
DHE-DSS-DES-CBC3-SHA
DHE-RSA-DES-CBC3-SHA
ADH-RC4-MD5
ADH-DES-CBC3-SHA

AES cipher suites from RFC3268, extending TLS v1.0

AES128-SHA
AES256-SHA
DH-DSS-AES128-SHA
DH-DSS-AES256-SHA
DH-RSA-AES128-SHA
DH-RSA-AES256-SHA
DHE-DSS-AES128-SHA
DHE-DSS-AES256-SHA
DHE-RSA-AES128-SHA
DHE-RSA-AES256-SHA
ADH-AES128-SHA
ADH-AES256-SHA

SEED cipher suites from RFC4162, extending TLS v1.0

SEED-SHA
DH-DSS-SEED-SHA
DH-RSA-SEED-SHA
DHE-DSS-SEED-SHA
DHE-RSA-SEED-SHA
ADH-SEED-SHA

GOST cipher suites, extending TLS v1.0

GOST94-GOST89-GOST89
GOST2001-GOST89-GOST89
GOST94-NULL-GOST94
GOST2001-NULL-GOST94

Elliptic curve cipher suites

ECDHE-RSA-NULL-SHA
ECDHE-RSA-RC4-SHA
ECDHE-RSA-DES-CBC3-SHA
ECDHE-RSA-AES128-SHA
ECDHE-RSA-AES256-SHA
ECDHE-ECDSA-NULL-SHA
ECDHE-ECDSA-RC4-SHA
ECDHE-ECDSA-DES-CBC3-SHA
ECDHE-ECDSA-AES128-SHA
ECDHE-ECDSA-AES256-SHA
AECDH-NULL-SHA
AECDH-RC4-SHA
AECDH-DES-CBC3-SHA
AECDH-AES128-SHA
AECDH-AES256-SHA

TLS v1.2 cipher suites

NULL-SHA256
AES128-SHA256
AES256-SHA256
AES128-GCM-SHA256
AES256-GCM-SHA384
DH-RSA-AES128-SHA256
DH-RSA-AES256-SHA256
DH-RSA-AES128-GCM-SHA256
DH-RSA-AES256-GCM-SHA384
DH-DSS-AES128-SHA256
DH-DSS-AES256-SHA256
DH-DSS-AES128-GCM-SHA256
DH-DSS-AES256-GCM-SHA384
DHE-RSA-AES128-SHA256
DHE-RSA-AES256-SHA256
DHE-RSA-AES128-GCM-SHA256
DHE-RSA-AES256-GCM-SHA384
DHE-DSS-AES128-SHA256
DHE-DSS-AES256-SHA256
DHE-DSS-AES128-GCM-SHA256
DHE-DSS-AES256-GCM-SHA384
ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-SHA384
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-SHA256
ECDHE-ECDSA-AES256-SHA384
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ADH-AES128-SHA256
ADH-AES256-SHA256
ADH-AES128-GCM-SHA256
ADH-AES256-GCM-SHA384
AES128-CCM
AES256-CCM
DHE-RSA-AES128-CCM
DHE-RSA-AES256-CCM
AES128-CCM8
AES256-CCM8
DHE-RSA-AES128-CCM8
DHE-RSA-AES256-CCM8
ECDHE-ECDSA-AES128-CCM
ECDHE-ECDSA-AES256-CCM
ECDHE-ECDSA-AES128-CCM8
ECDHE-ECDSA-AES256-CCM8

Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2

ECDHE-ECDSA-CAMELLIA128-SHA256
ECDHE-ECDSA-CAMELLIA256-SHA384
ECDHE-RSA-CAMELLIA128-SHA256
ECDHE-RSA-CAMELLIA256-SHA384

TLS 1.3 cipher suites

(Note these ciphers are set with CURLOPT_TLS13_CIPHERS and --tls13-ciphers)

TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
TLS_AES_128_CCM_8_SHA256
TLS_AES_128_CCM_SHA256

NSS

Totally insecure

rc4
rc4-md5
rc4export
rc2
rc2export
des
desede3

SSL3/TLS cipher suites

rsa_rc4_128_md5
rsa_rc4_128_sha
rsa_3des_sha
rsa_des_sha
rsa_rc4_40_md5
rsa_rc2_40_md5
rsa_null_md5
rsa_null_sha
fips_3des_sha
fips_des_sha
fortezza
fortezza_rc4_128_sha
fortezza_null

TLS 1.0 Exportable 56-bit Cipher Suites

rsa_des_56_sha
rsa_rc4_56_sha

AES ciphers

dhe_dss_aes_128_cbc_sha
dhe_dss_aes_256_cbc_sha
dhe_rsa_aes_128_cbc_sha
dhe_rsa_aes_256_cbc_sha
rsa_aes_128_sha
rsa_aes_256_sha

ECC ciphers

ecdh_ecdsa_null_sha
ecdh_ecdsa_rc4_128_sha
ecdh_ecdsa_3des_sha
ecdh_ecdsa_aes_128_sha
ecdh_ecdsa_aes_256_sha
ecdhe_ecdsa_null_sha
ecdhe_ecdsa_rc4_128_sha
ecdhe_ecdsa_3des_sha
ecdhe_ecdsa_aes_128_sha
ecdhe_ecdsa_aes_256_sha
ecdh_rsa_null_sha
ecdh_rsa_128_sha
ecdh_rsa_3des_sha
ecdh_rsa_aes_128_sha
ecdh_rsa_aes_256_sha
ecdhe_rsa_null
ecdhe_rsa_rc4_128_sha
ecdhe_rsa_3des_sha
ecdhe_rsa_aes_128_sha
ecdhe_rsa_aes_256_sha
ecdh_anon_null_sha
ecdh_anon_rc4_128sha
ecdh_anon_3des_sha
ecdh_anon_aes_128_sha
ecdh_anon_aes_256_sha

HMAC-SHA256 cipher suites

rsa_null_sha_256
rsa_aes_128_cbc_sha_256
rsa_aes_256_cbc_sha_256
dhe_rsa_aes_128_cbc_sha_256
dhe_rsa_aes_256_cbc_sha_256
ecdhe_ecdsa_aes_128_cbc_sha_256
ecdhe_rsa_aes_128_cbc_sha_256

AES GCM cipher suites in RFC 5288 and RFC 5289

rsa_aes_128_gcm_sha_256
dhe_rsa_aes_128_gcm_sha_256
dhe_dss_aes_128_gcm_sha_256
ecdhe_ecdsa_aes_128_gcm_sha_256
ecdh_ecdsa_aes_128_gcm_sha_256
ecdhe_rsa_aes_128_gcm_sha_256
ecdh_rsa_aes_128_gcm_sha_256

cipher suites using SHA384

rsa_aes_256_gcm_sha_384
dhe_rsa_aes_256_gcm_sha_384
dhe_dss_aes_256_gcm_sha_384
ecdhe_ecdsa_aes_256_sha_384
ecdhe_rsa_aes_256_sha_384
ecdhe_ecdsa_aes_256_gcm_sha_384
ecdhe_rsa_aes_256_gcm_sha_384

chacha20-poly1305 cipher suites

ecdhe_rsa_chacha20_poly1305_sha_256
ecdhe_ecdsa_chacha20_poly1305_sha_256
dhe_rsa_chacha20_poly1305_sha_256

TLS 1.3 cipher suites

aes_128_gcm_sha_256
aes_256_gcm_sha_384
chacha20_poly1305_sha_256

GSKit

Ciphers are internally defined as numeric
codes
. libcurl
maps them to the following case-insensitive names.

SSL2 cipher suites (insecure: disabled by default)

rc2-md5
rc4-md5
exp-rc2-md5
exp-rc4-md5
des-cbc-md5
des-cbc3-md5

SSL3 cipher suites

null-md5
null-sha
rc4-md5
rc4-sha
exp-rc2-cbc-md5
exp-rc4-md5
exp-des-cbc-sha
des-cbc3-sha

TLS v1.0 cipher suites

null-md5
null-sha
rc4-md5
rc4-sha
exp-rc2-cbc-md5
exp-rc4-md5
exp-des-cbc-sha
des-cbc3-sha
aes128-sha
aes256-sha

TLS v1.1 cipher suites

null-md5
null-sha
rc4-md5
rc4-sha
exp-des-cbc-sha
des-cbc3-sha
aes128-sha
aes256-sha

TLS v1.2 cipher suites

null-md5
null-sha
null-sha256
rc4-md5
rc4-sha
des-cbc3-sha
aes128-sha
aes256-sha
aes128-sha256
aes256-sha256
aes128-gcm-sha256
aes256-gcm-sha384

WolfSSL

RC4-SHA,
RC4-MD5,
DES-CBC3-SHA,
AES128-SHA,
AES256-SHA,
NULL-SHA,
NULL-SHA256,
DHE-RSA-AES128-SHA,
DHE-RSA-AES256-SHA,
DHE-PSK-AES256-GCM-SHA384,
DHE-PSK-AES128-GCM-SHA256,
PSK-AES256-GCM-SHA384,
PSK-AES128-GCM-SHA256,
DHE-PSK-AES256-CBC-SHA384,
DHE-PSK-AES128-CBC-SHA256,
PSK-AES256-CBC-SHA384,
PSK-AES128-CBC-SHA256,
PSK-AES128-CBC-SHA,
PSK-AES256-CBC-SHA,
DHE-PSK-AES128-CCM,
DHE-PSK-AES256-CCM,
PSK-AES128-CCM,
PSK-AES256-CCM,
PSK-AES128-CCM-8,
PSK-AES256-CCM-8,
DHE-PSK-NULL-SHA384,
DHE-PSK-NULL-SHA256,
PSK-NULL-SHA384,
PSK-NULL-SHA256,
PSK-NULL-SHA,
HC128-MD5,
HC128-SHA,
HC128-B2B256,
AES128-B2B256,
AES256-B2B256,
RABBIT-SHA,
NTRU-RC4-SHA,
NTRU-DES-CBC3-SHA,
NTRU-AES128-SHA,
NTRU-AES256-SHA,
AES128-CCM-8,
AES256-CCM-8,
ECDHE-ECDSA-AES128-CCM,
ECDHE-ECDSA-AES128-CCM-8,
ECDHE-ECDSA-AES256-CCM-8,
ECDHE-RSA-AES128-SHA,
ECDHE-RSA-AES256-SHA,
ECDHE-ECDSA-AES128-SHA,
ECDHE-ECDSA-AES256-SHA,
ECDHE-RSA-RC4-SHA,
ECDHE-RSA-DES-CBC3-SHA,
ECDHE-ECDSA-RC4-SHA,
ECDHE-ECDSA-DES-CBC3-SHA,
AES128-SHA256,
AES256-SHA256,
DHE-RSA-AES128-SHA256,
DHE-RSA-AES256-SHA256,
ECDH-RSA-AES128-SHA,
ECDH-RSA-AES256-SHA,
ECDH-ECDSA-AES128-SHA,
ECDH-ECDSA-AES256-SHA,
ECDH-RSA-RC4-SHA,
ECDH-RSA-DES-CBC3-SHA,
ECDH-ECDSA-RC4-SHA,
ECDH-ECDSA-DES-CBC3-SHA,
AES128-GCM-SHA256,
AES256-GCM-SHA384,
DHE-RSA-AES128-GCM-SHA256,
DHE-RSA-AES256-GCM-SHA384,
ECDHE-RSA-AES128-GCM-SHA256,
ECDHE-RSA-AES256-GCM-SHA384,
ECDHE-ECDSA-AES128-GCM-SHA256,
ECDHE-ECDSA-AES256-GCM-SHA384,
ECDH-RSA-AES128-GCM-SHA256,
ECDH-RSA-AES256-GCM-SHA384,
ECDH-ECDSA-AES128-GCM-SHA256,
ECDH-ECDSA-AES256-GCM-SHA384,
CAMELLIA128-SHA,
DHE-RSA-CAMELLIA128-SHA,
CAMELLIA256-SHA,
DHE-RSA-CAMELLIA256-SHA,
CAMELLIA128-SHA256,
DHE-RSA-CAMELLIA128-SHA256,
CAMELLIA256-SHA256,
DHE-RSA-CAMELLIA256-SHA256,
ECDHE-RSA-AES128-SHA256,
ECDHE-ECDSA-AES128-SHA256,
ECDH-RSA-AES128-SHA256,
ECDH-ECDSA-AES128-SHA256,
ECDHE-RSA-AES256-SHA384,
ECDHE-ECDSA-AES256-SHA384,
ECDH-RSA-AES256-SHA384,
ECDH-ECDSA-AES256-SHA384,
ECDHE-RSA-CHACHA20-POLY1305,
ECDHE-ECDSA-CHACHA20-POLY1305,
DHE-RSA-CHACHA20-POLY1305,
ECDHE-RSA-CHACHA20-POLY1305-OLD,
ECDHE-ECDSA-CHACHA20-POLY1305-OLD,
DHE-RSA-CHACHA20-POLY1305-OLD,
ADH-AES128-SHA,
QSH,
RENEGOTIATION-INFO,
IDEA-CBC-SHA,
ECDHE-ECDSA-NULL-SHA,
ECDHE-PSK-NULL-SHA256,
ECDHE-PSK-AES128-CBC-SHA256,
PSK-CHACHA20-POLY1305,
ECDHE-PSK-CHACHA20-POLY1305,
DHE-PSK-CHACHA20-POLY1305,
EDH-RSA-DES-CBC3-SHA,

Schannel

Schannel allows the enabling and disabling of encryption algorithms, but not
specific cipher suites. They are
defined by
Microsoft.

There is also the case that the selected algorithm is not supported by the
protocol or does not match the ciphers offered by the server during the SSL
negotiation. In this case curl will return error
CURLE_SSL_CONNECT_ERROR (35) SEC_E_ALGORITHM_MISMATCH
and the request will fail.

CALG_MD2,
CALG_MD4,
CALG_MD5,
CALG_SHA,
CALG_SHA1,
CALG_MAC,
CALG_RSA_SIGN,
CALG_DSS_SIGN,
CALG_NO_SIGN,
CALG_RSA_KEYX,
CALG_DES,
CALG_3DES_112,
CALG_3DES,
CALG_DESX,
CALG_RC2,
CALG_RC4,
CALG_SEAL,
CALG_DH_SF,
CALG_DH_EPHEM,
CALG_AGREEDKEY_ANY,
CALG_HUGHES_MD5,
CALG_SKIPJACK,
CALG_TEK,
CALG_CYLINK_MEK,
CALG_SSL3_SHAMD5,
CALG_SSL3_MASTER,
CALG_SCHANNEL_MASTER_HASH,
CALG_SCHANNEL_MAC_KEY,
CALG_SCHANNEL_ENC_KEY,
CALG_PCT1_MASTER,
CALG_SSL2_MASTER,
CALG_TLS1_MASTER,
CALG_RC5,
CALG_HMAC,
CALG_TLS1PRF,
CALG_HASH_REPLACE_OWF,
CALG_AES_128,
CALG_AES_192,
CALG_AES_256,
CALG_AES,
CALG_SHA_256,
CALG_SHA_384,
CALG_SHA_512,
CALG_ECDH,
CALG_ECMQV,
CALG_ECDSA,
CALG_ECDH_EPHEM,

As of curl 7.77.0, you can also pass SCH_USE_STRONG_CRYPTO as a cipher name
to constrain the set of available ciphers as specified in the Schannel
documentation
.
Note that the supported ciphers in this case follow the OS version, so if you
are running an outdated OS you might still be supporting weak ciphers.

TLS 1.3 cipher suites

(Note these ciphers are set with CURLOPT_TLS13_CIPHERS and --tls13-ciphers)

TLS_AES_256_GCM_SHA384
TLS_AES_128_GCM_SHA256
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_CCM_8_SHA256
TLS_AES_128_CCM_SHA256

BearSSL

BearSSL ciphers can be specified by either the OpenSSL name (ECDHE-RSA-AES128-GCM-SHA256) or the IANA name (TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256).

Since BearSSL 0.1:

DES-CBC3-SHA
AES128-SHA
AES256-SHA
AES128-SHA256
AES256-SHA256
AES128-GCM-SHA256
AES256-GCM-SHA384
ECDH-ECDSA-DES-CBC3-SHA
ECDH-ECDSA-AES128-SHA
ECDH-ECDSA-AES256-SHA
ECDHE-ECDSA-DES-CBC3-SHA
ECDHE-ECDSA-AES128-SHA
ECDHE-ECDSA-AES256-SHA
ECDH-RSA-DES-CBC3-SHA
ECDH-RSA-AES128-SHA
ECDH-RSA-AES256-SHA
ECDHE-RSA-DES-CBC3-SHA
ECDHE-RSA-AES128-SHA
ECDHE-RSA-AES256-SHA
ECDHE-ECDSA-AES128-SHA256
ECDHE-ECDSA-AES256-SHA384
ECDH-ECDSA-AES128-SHA256
ECDH-ECDSA-AES256-SHA384
ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-SHA384
ECDH-RSA-AES128-SHA256
ECDH-RSA-AES256-SHA384
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDH-ECDSA-AES128-GCM-SHA256
ECDH-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDH-RSA-AES128-GCM-SHA256
ECDH-RSA-AES256-GCM-SHA384

Since BearSSL 0.2:

ECDHE-RSA-CHACHA20-POLY1305
ECDHE-ECDSA-CHACHA20-POLY1305

Since BearSSL 0.6:

AES128-CCM
AES256-CCM
AES128-CCM8
AES256-CCM8
ECDHE-ECDSA-AES128-CCM
ECDHE-ECDSA-AES256-CCM
ECDHE-ECDSA-AES128-CCM8
ECDHE-ECDSA-AES256-CCM8


Third Party Curl Docs CMakeLists (`third_party/curl/docs/CMakeLists.txt`)

#***************************************************************************

_ _ ____ _

Project ___| | | | _ \| |

/ __| | | | |_) | |

| (__| |_| | _ <| |___

\___|\___/|_| \_\_____|

This software is licensed as described in the file COPYING, which

you should have received as part of this distribution. The terms

You may opt to use, copy, modify, merge, publish, distribute and/or sell

copies of the Software, and permit persons to whom the Software is

furnished to do so, under the terms of the COPYING file.

This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY

KIND, either express or implied.

SPDX-License-Identifier: curl

###########################################################################
#add_subdirectory(examples)
add_subdirectory(libcurl)
add_subdirectory(cmdline-opts)


Third Party Curl Docs CODE OF CONDUCT (`third_party/curl/docs/CODE_OF_CONDUCT.md`)

Contributor Code of Conduct

As contributors and maintainers of this project, we pledge to respect all
people who contribute through reporting issues, posting feature requests,
updating documentation, submitting pull requests or patches, and other
activities.

We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, or religion.

Examples of unacceptable behavior by participants include the use of sexual
language or imagery, derogatory comments or personal attacks, trolling, public
or private harassment, insults, or other unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct. Project maintainers who do not
follow the Code of Conduct may be removed from the project team.

This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by opening an issue or contacting one or more of the project
maintainers.

This Code of Conduct is adapted from the Contributor
Covenant
, version 1.1.0, available at
https://contributor-covenant.org/version/1/1/0/


Third Party Curl Docs CODE REVIEW (`third_party/curl/docs/CODE_REVIEW.md`)

How to do code reviews for curl

Anyone and everyone is encouraged and welcome to review code submissions in
curl. This is a guide on what to check for and how to perform a successful
code review.

All submissions should get reviewed

All pull requests and patches submitted to the project should be reviewed by
at least one experienced curl maintainer before that code is accepted and
merged.

Let the tools and tests take the first rounds

On initial pull requests, let the tools and tests do their job first and then
start out by helping the submitter understand the test failures and tool
alerts.

How to provide feedback to author

Be nice. Ask questions. Provide examples or suggestions of improvements.
Assume the best intentions. Remember language barriers.

All first-time contributors can become regulars. Let's help them go there.

Is this a change we want?

If this is not a change that seems to be aligned with the project's path
forward and as such cannot be accepted, inform the author about this sooner
rather than later. Do it gently and explain why and possibly what could be
done to make it more acceptable.

API/ABI stability or changed behavior

Changing the API and the ABI may be fine in a change but it needs to be done
deliberately and carefully. If not, a reviewer must help the author to realize
the mistake.

curl and libcurl are similarly strict on not modifying existing behavior. API
and ABI stability is not enough, the behavior should also remain intact as far
as possible.

Code style

Most code style nits are detected by checksrc but not all. Only leave remarks
on style deviation once checksrc does not find anymore.

Minor nits from fresh submitters can also be handled by the maintainer when
merging, in case it seems like the submitter is not clear on what to do. We
want to make the process fun and exciting for new contributors.

Encourage consistency

Make sure new code is written in a similar style as existing code. Naming,
logic, conditions, etc.

Are pointers always non-NULL?

If a function or code rely on pointers being non-NULL, take an extra look if
that seems to be a fair assessment.

Asserts

Conditions that should never be false can be verified with DEBUGASSERT()
calls to get caught in tests and debugging easier, while not having an impact
on final or release builds.

Memory allocation

Can the mallocs be avoided? Do not introduce mallocs in any hot paths. If
there are (new) mallocs, can they be combined into fewer calls?

Are all allocations handled in error paths to avoid leaks and crashes?

Thread-safety

We do not like static variables as they break thread-safety and prevent
functions from being reentrant.

Should features be `#ifdef`ed?

Features and functionality may not be present everywhere and should therefore
be #ifdefed. Additionally, some features should be possible to switch on/off
in the build.

Write #ifdefs to be as little of a "maze" as possible.

Does it look portable enough?

curl runs "everywhere". Does the code take a reasonable stance and enough
precautions to be possible to build and run on most platforms?

Remember that we live by C89 restrictions.

Tests and testability

New features should be added in conjunction with one or more test cases.
Ideally, functions should also be written so that unit tests can be done to
test individual functions.

Documentation

New features or changes to existing functionality must be accompanied by
updated documentation. Submitting that in a separate follow-up pull request is
not OK. A code review must also verify that the submitted documentation update
matches the code submission.

English is not everyone's first language, be mindful of this and help the
submitter improve the text if it needs a rewrite to read better.

Code should not be hard to understand

Source code should be written to maximize readability and be easy to
understand.

Functions should not be large

A single function should never be large as that makes it hard to follow and
understand all the exit points and state changes. Some existing functions in
curl certainly violate this ground rule but when reviewing new code we should
propose splitting into smaller functions.

Duplication is evil

Anything that looks like duplicated code is a red flag. Anything that seems to
introduce code that we should already have or provide needs a closer check.

Sensitive data

When credentials are involved, take an extra look at what happens with this
data. Where it comes from and where it goes.

Variable types differ

size_t is not a fixed size. time_t can be signed or unsigned and have
different sizes. Relying on variable sizes is a red flag.

Also remember that endianness and >= 32 bit accesses to unaligned addresses
are problematic areas.

Integer overflows

Be careful about integer overflows. Some variable types can be either 32 bit
or 64 bit. Integer overflows must be detected and acted on before they
happen.

Dangerous use of functions

Maybe use of realloc() should rather use the dynbuf functions?

Do not allow new code that grows buffers without using dynbuf.

Use of C functions that rely on a terminating zero must only be used on data
that really do have a null-terminating zero.

Dangerous "data styles"

Make extra precautions and verify that memory buffers that need a terminating
zero always have exactly that. Buffers without a null-terminator must not be
used as input to string functions.

Commit messages

Tightly coupled with a code review is making sure that the commit message is
good. It is the responsibility of the person who merges the code to make sure
that the commit message follows our standard (detailed in the
CONTRIBUTE document). This includes making sure the PR
identifies related issues and giving credit to reporters and helpers.


Third Party Curl Docs CODE STYLE (`third_party/curl/docs/CODE_STYLE.md`)

curl C code style

Source code that has a common style is easier to read than code that uses
different styles in different places. It helps making the code feel like one
single code base. Easy-to-read is an important property of code and helps
making it easier to review when new things are added and it helps debugging
code when developers are trying to figure out why things go wrong. A unified
style is more important than individual contributors having their own personal
tastes satisfied.

Our C code has a few style rules. Most of them are verified and upheld by the
scripts/checksrc.pl script. Invoked with make checksrc or even by default
by the build system when built after ./configure --enable-debug has been
used.

It is normally not a problem for anyone to follow the guidelines, as you just
need to copy the style already used in the source code and there are no
particularly unusual rules in our set of rules.

We also work hard on writing code that are warning-free on all the major
platforms and in general on as many platforms as possible. Code that obviously
will cause warnings will not be accepted as-is.

Naming

Try using a non-confusing naming scheme for your new functions and variable
names. It does not necessarily have to mean that you should use the same as in
other places of the code, just that the names should be logical,
understandable and be named according to what they are used for. File-local
functions should be made static. We like lower case names.

See the INTERNALS document on
how we name non-exported library-global symbols.

Indenting

We use only spaces for indentation, never TABs. We use two spaces for each new
open brace.

c
if(something_is_true) {
  while(second_statement == fine) {
    moo();
  }
}

Comments

Since we write C89 code, // comments are not allowed. They were not
introduced in the C standard until C99. We use only /* comments */.

c
/* this is a comment */

Long lines

Source code in curl may never be wider than 79 columns and there are two
reasons for maintaining this even in the modern era of large and high
resolution screens:

  1. Narrower columns are easier to read than wide ones. There's a reason
    newspapers have used columns for decades or centuries.

  2. Narrower columns allow developers to easier show multiple pieces of code
    next to each other in different windows. It allows two or three source
    code windows next to each other on the same screen - as well as multiple
    terminal and debugging windows.

Braces

In if/while/do/for expressions, we write the open brace on the same line as
the keyword and we then set the closing brace on the same indentation level as
the initial keyword. Like this:

c
if(age < 40) {
  /* clearly a youngster */
}

You may omit the braces if they would contain only a one-line statement:

c
if(!x)
  continue;

For functions the opening brace should be on a separate line:

c
int main(int argc, char **argv)
{
  return 1;
}

'else' on the following line

When adding an else clause to a conditional expression using braces, we
add it on a new line after the closing brace. Like this:

c
if(age < 40) {
  /* clearly a youngster */
}
else {
  /* probably grumpy */
}

No space before parentheses

When writing expressions using if/while/do/for, there shall be no space
between the keyword and the open parenthesis. Like this:

c
while(1) {
  /* loop forever */
}

Use boolean conditions

Rather than test a conditional value such as a bool against TRUE or FALSE, a
pointer against NULL or != NULL and an int against zero or not zero in
if/while conditions we prefer:

c
result = do_something();
if(!result) {
  /* something went wrong */
  return result;
}

No assignments in conditions

To increase readability and reduce complexity of conditionals, we avoid
assigning variables within if/while conditions. We frown upon this style:

c
if((ptr = malloc(100)) == NULL)
  return NULL;

and instead we encourage the above version to be spelled out more clearly:

c
ptr = malloc(100);
if(!ptr)
  return NULL;

New block on a new line

We never write multiple statements on the same source line, even for short
if() conditions.

c
if(a)
  return TRUE;
else if(b)
  return FALSE;

and NEVER:

c
if(a) return TRUE;
else if(b) return FALSE;

Space around operators

Please use spaces on both sides of operators in C expressions. Postfix (),
[], ->, ., ++, --
and Unary +, -, !, ~, & operators excluded they should
have no space.

Examples:

c
bla = func();
who = name[0];
age += 1;
true = !false;
size += -2 + 3 * (a + b);
ptr->member = a++;
struct.field = b--;
ptr = &address;
contents = *pointer;
complement = ~bits;
empty = (!*string) ? TRUE : FALSE;

No parentheses for return values

We use the 'return' statement without extra parentheses around the value:

c
int works(void)
{
  return TRUE;
}

Parentheses for sizeof arguments

When using the sizeof operator in code, we prefer it to be written with
parentheses around its argument:

c
int size = sizeof(int);

Column alignment

Some statements cannot be completed on a single line because the line would be
too long, the statement too hard to read, or due to other style guidelines
above. In such a case the statement will span multiple lines.

If a continuation line is part of an expression or sub-expression then you
should align on the appropriate column so that it's easy to tell what part of
the statement it is. Operators should not start continuation lines. In other
cases follow the 2-space indent guideline. Here are some examples from
libcurl:

c
if(Curl_pipeline_wanted(handle->multi, CURLPIPE_HTTP1) &&
   (handle->set.httpversion != CURL_HTTP_VERSION_1_0) &&
   (handle->set.httpreq == HTTPREQ_GET ||
    handle->set.httpreq == HTTPREQ_HEAD))
  /* did not ask for HTTP/1.0 and a GET or HEAD */
  return TRUE;

If no parenthesis, use the default indent:

c
data->set.http_disable_hostname_check_before_authentication =
  (0 != va_arg(param, long)) ? TRUE : FALSE;

Function invoke with an open parenthesis:

c
if(option) {
  result = parse_login_details(option, strlen(option),
                               (userp ? &user : NULL),
                               (passwdp ? &passwd : NULL),
                               NULL);
}

Align with the "current open" parenthesis:

c
DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing "
             "server response left\n",
             (int)clipamount));

Platform dependent code

Use #ifdef HAVE_FEATURE to do conditional code. We avoid checking for
particular operating systems or hardware in the #ifdef lines. The HAVE_FEATURE
shall be generated by the configure script for unix-like systems and they are
hard-coded in the config-[system].h files for the others.

We also encourage use of macros/functions that possibly are empty or defined
to constants when libcurl is built without that feature, to make the code
seamless. Like this example where the magic() function works differently
depending on a build-time conditional:

c
#ifdef HAVE_MAGIC
void magic(int a)
{
  return a + 2;
}
#else
#define magic(x) 1
#endif

int content = magic(3);

No typedefed structs

Use structs by all means, but do not typedef them. Use the struct name way
of identifying them:

c
struct something {
   void *valid;
   size_t way_to_write;
};
struct something instance;

Not okay:

c
typedef struct {
   void *wrong;
   size_t way_to_write;
} something;
something instance;

Third Party Curl Docs CONNECTION FILTERS (`third_party/curl/docs/CONNECTION-FILTERS.md`)

curl connection filters

Connection filters is a design in the internals of curl, not visible in its public API. They were added
in curl v7.xx.x. This document describes the concepts, its high level implementation and the motivations.

Filters

A "connection filter" is a piece of code that is responsible for handling a range of operations
of curl's connections: reading, writing, waiting on external events, connecting and closing down - to name the most important ones.

The most important feat of connection filters is that they can be stacked on top of each other (or "chained" if you prefer that metaphor). In the common scenario that you want to retrieve a https: url with curl, you need 2 basic things to send the request and get the response: a TCP connection, represented by a socket and a SSL instance en- and decrypt over that socket. You write your request to the SSL instance, which encrypts and writes that data to the socket, which then sends the bytes over the network.

With connection filters, curl's internal setup will look something like this (cf for connection filter):

text
Curl_easy *data         connectdata *conn        cf-ssl        cf-socket
+----------------+      +-----------------+      +-------+     +--------+
|https://curl.se/|----> | properties      |----> | keys  |---> | socket |--> OS --> network
+----------------+      +-----------------+      +-------+     +--------+

 Curl_write(data, buffer)
  --> Curl_cfilter_write(data, data->conn, buffer)
       ---> conn->filter->write(conn->filter, data, buffer)

While connection filters all do different things, they look the same from the "outside". The code in data and conn does not really know which filters are installed. conn just writes into the first filter, whatever that is.

Same is true for filters. Each filter has a pointer to the next filter. When SSL has encrypted the data, it does not write to a socket, it writes to the next filter. If that is indeed a socket, or a file, or an HTTP/2 connection is of no concern to the SSL filter.

And this allows the stacking, as in:

text
Direct:
  http://localhost/      conn -> cf-socket
  https://curl.se/       conn -> cf-ssl -> cf-socket
Via http proxy tunnel:
  http://localhost/      conn -> cf-http-proxy -> cf-socket 
  https://curl.se/       conn -> cf-ssl -> cf-http-proxy -> cf-socket
Via https proxy tunnel:
  http://localhost/      conn -> cf-http-proxy -> cf-ssl -> cf-socket 
  https://curl.se/       conn -> cf-ssl -> cf-http-proxy -> cf-ssl -> cf-socket
Via http proxy tunnel via SOCKS proxy:
  http://localhost/      conn -> cf-http-proxy -> cf-socks -> cf-socket

Connecting/Closing

Before Curl_easy can send the request, the connection needs to be established. This means that all connection filters have done, whatever they need to do: waiting for the socket to be connected, doing the TLS handshake, performing the HTTP tunnel request, etc. This has to be done in reverse order: the last filter has to do its connect first, then the one above can start, etc.

Each filter does in principle the following:

text
static CURLcode 
myfilter_cf_connect(struct Curl_cfilter *cf,
                    struct Curl_easy *data,
                    bool *done)
{
  CURLcode result;

  if(cf->connected) {            /* we and all below are done */
    *done = TRUE;
    return CURLE_OK;
  }
                                 /* Let the filters below connect */
  result = cf->next->cft->connect(cf->next, data, blocking, done);
  if(result || !*done)    
    return result;               /* below errored/not finished yet */

  /* MYFILTER CONNECT THINGS */  /* below connected, do out thing */
  *done = cf->connected = TRUE;  /* done, remember, return */
  return CURLE_OK;
}

Closing a connection then works similar. The conn tells the first filter to close. Contrary to connecting,
the filter does its own things first, before telling the next filter to close.

Efficiency

There are two things curl is concerned about: efficient memory use and fast transfers.

The memory footprint of a filter is relatively small:

text
struct Curl_cfilter {
  const struct Curl_cftype *cft; /* the type providing implementation */
  struct Curl_cfilter *next;     /* next filter in chain */
  void *ctx;                     /* filter type specific settings */
  struct connectdata *conn;      /* the connection this filter belongs to */
  int sockindex;                 /* TODO: like to get rid off this */
  BIT(connected);                /* != 0 iff this filter is connected */
};

The filter type cft is a singleton, one static struct for each type of filter. The ctx is where a filter will hold its specific data. That varies by filter type. An http-proxy filter will keep the ongoing state of the CONNECT here, but free it after its has been established. The SSL filter will keep the SSL* (if OpenSSL is used) here until the connection is closed. So, this varies.

conn is a reference to the connection this filter belongs to, so nothing extra besides the pointer itself.

Several things, that before were kept in struct connectdata, will now go into the filter->ctx when needed. So, the memory footprint for connections that do not use an http proxy, or socks, or https will be lower.

As to transfer efficiency, writing and reading through a filter comes at near zero cost if the filter does not transform the data. An http proxy or socks filter, once it is connected, will just pass the calls through. Those filters implementations will look like this:

text
ssize_t  Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data,
                          const void *buf, size_t len, CURLcode *err)
{
  return cf->next->cft->do_send(cf->next, data, buf, len, err);
}

The recv implementation is equivalent.

Filter Types

The (currently) existing filter types are: SOCKET, SOCKET-ACCEPT, SSL, HTTP-PROXY and SOCKS-PROXY. Vital to establishing and read/writing a connection. But filters are also a good way to implement tasks for managing a connection:

  • Statistics: a filter that counts the number of bytes sent/received. Place one in front of SOCKET and one higher up and get the number of raw and "easy" bytes transferred. They may track the speed as well, or number of partial writes, etc.
  • Timeout: enforce timeouts, e.g. fail if a connection cannot be established in a certain amount of time.
  • Progress: report progress on a connection.
  • Pacing: limit read/write rates.
  • Testing: simulate network condition or failures.

As you see, filters are a good way to add functionality to curl's internal handling of transfers without impact on other code.

Easy Filters?

Some things that curl needs to manage are not directly tied to a specific connection but the property of the Curl_easy handle, e.g. a particular transfer. When using HTTP/2 or HTTP/3, many transfers can use the same connection. If one wants to monitor of the transfer itself or restricting its speed alone, a connection filter is not the right place to do this.

So we might add "easy filters" one day. Who knows?


Third Party Curl Docs CONTRIBUTE (`third_party/curl/docs/CONTRIBUTE.md`)

Contributing to the curl project

This document is intended to offer guidelines on how to best contribute to the
curl project. This concerns new features as well as corrections to existing
flaws or bugs.

Join the Community

Skip over to https://curl.se/mail/ and join
the appropriate mailing list(s). Read up on details before you post
questions. Read this file before you start sending patches. We prefer
questions sent to and discussions being held on the mailing list(s), not sent
to individuals.

Before posting to one of the curl mailing lists, please read up on the
mailing list etiquette.

We also hang out on IRC in #curl on libera.chat

If you are at all interested in the code side of things, consider clicking
'watch' on the curl repo on GitHub to be
notified of pull requests and new issues posted there.

When contributing with code, you agree to put your changes and new code under
the same license curl and libcurl is already using unless stated and agreed
otherwise.

If you add a larger piece of code, you can opt to make that file or set of
files to use a different license as long as they do not enforce any changes to
the rest of the package and they make sense. Such "separate parts" can not be
GPL licensed (as we do not want copyleft to affect users of libcurl) but they
must use "GPL compatible" licenses (as we want to allow users to use libcurl
properly in GPL licensed environments).

When changing existing source code, you do not alter the copyright of the
original file(s). The copyright will still be owned by the original creator(s)
or those who have been assigned copyright by the original author(s).

By submitting a patch to the curl project, you are assumed to have the right
to the code and to be allowed by your employer or whatever to hand over that
patch/code to us. We will credit you for your changes as far as possible, to
give credit but also to keep a trace back to who made what changes. Please
always provide us with your full real name when contributing,

What To Read

Source code, the man pages, the INTERNALS
document
,
TODO,
KNOWN_BUGS and the most recent
changes
in git. Just lurking on
the curl-library mailing
list
will give you a
lot of insights on what's going on right now. Asking there is a good idea too.

Write a good patch

Follow code style

When writing C code, follow the
CODE_STYLE already established in
the project. Consistent style makes code easier to read and mistakes less
likely to happen. Run make checksrc before you submit anything, to make sure
you follow the basic style. That script does not verify everything, but if it
complains you know you have work to do.

Non-clobbering All Over

When you write new functionality or fix bugs, it is important that you do not
fiddle all over the source files and functions. Remember that it is likely
that other people have done changes in the same source files as you have and
possibly even in the same functions. If you bring completely new
functionality, try writing it in a new source file. If you fix bugs, try to
fix one bug at a time and send them as separate patches.

Write Separate Changes

It is annoying when you get a huge patch from someone that is said to fix 511
odd problems, but discussions and opinions do not agree with 510 of them - or
509 of them were already fixed in a different way. Then the person merging
this change needs to extract the single interesting patch from somewhere
within the huge pile of source, and that creates a lot of extra work.

Preferably, each fix that corrects a problem should be in its own patch/commit
with its own description/commit message stating exactly what they correct so
that all changes can be selectively applied by the maintainer or other
interested parties.

Also, separate changes enable bisecting much better for tracking problems
and regression in the future.

Patch Against Recent Sources

Please try to get the latest available sources to make your patches against.
It makes the lives of the developers so much easier. The best is if you get
the most up-to-date sources from the git repository, but the latest release
archive is quite OK as well.

Documentation

Writing docs is dead boring and one of the big problems with many open source
projects. But someone's gotta do it. It makes things a lot easier if you
submit a small description of your fix or your new features with every
contribution so that it can be swiftly added to the package documentation.

The documentation is always made in man pages (nroff formatted) or plain
ASCII files. All HTML files on the website and in the release archives are
generated from the nroff/ASCII versions.

Test Cases

Since the introduction of the test suite, we can quickly verify that the main
features are working as they are supposed to. To maintain this situation and
improve it, all new features and functions that are added need to be tested
in the test suite. Every feature that is added should get at least one valid
test case that verifies that it works as documented. If every submitter also
posts a few test cases, it will not end up as a heavy burden on a single person!

If you do not have test cases or perhaps you have done something that is hard
to write tests for, do explain exactly how you have otherwise tested and
verified your changes.

Submit Your Changes

How to get your changes into the main sources

Ideally you file a pull request on
GitHub
, but you can also send your plain
patch to the curl-library mailing
list
.

If you opt to post a patch on the mailing list, chances are someone will
convert it into a pull request for you, to have the CI jobs verify it proper
before it can be merged. Be prepared that some feedback on the proposed change
might then come on GitHub.

Your change will be reviewed and discussed and you will be expected to correct
flaws pointed out and update accordingly, or the change risks stalling and
eventually just getting deleted without action. As a submitter of a change,
you are the owner of that change until it has been merged.

Respond on the list or on GitHub about the change and answer questions and/or
fix nits/flaws. This is important. We will take lack of replies as a sign that
you are not anxious to get your patch accepted and we tend to simply drop such
changes.

About pull requests

With GitHub it is easy to send a pull
request
to the curl project to have
changes merged.

We strongly prefer pull requests to mailed patches, as it makes it a proper
git commit that is easy to merge and they are easy to track and not that easy
to lose in the flood of many emails, like they sometimes do on the mailing
lists.

Every pull request submitted will automatically be tested in several different
ways. See the CI document for more
information
.

Sometimes the tests fail due to a dependency service temporarily being offline
or otherwise unavailable, e.g. package downloads. In this case you can just
try to update your pull requests to rerun the tests later as described below.

You can update your pull requests by pushing new commits or force-pushing
changes to existing commits. Force-pushing an amended commit without any
actual content changed also allows you to retrigger the tests for that commit.

When you adjust your pull requests after review, consider squashing the
commits so that we can review the full updated version more easily.

A pull request sent to the project might get labeled needs-votes by a
project maintainer. This label means that in addition to meeting all other
checks and qualifications this pull request must also receive more "votes" of
user support. More signs that people want this to happen. It could be in the
form of messages saying so, or thumbs-up reactions on GitHub.

Making quality changes

Make the patch against as recent source versions as possible.

If you have followed the tips in this document and your patch still has not
been incorporated or responded to after some weeks, consider resubmitting it
to the list or better yet: change it to a pull request.

Commit messages

A short guide to how to write git commit messages in the curl project.

text
---- start ----
[area]: [short line describing the main effect]
       -- empty line --
[full description, no wider than 72 columns that describes as much as
possible as to why this change is made, and possibly what things
it fixes and everything else that is related, with unwieldy URLs replaced
with references like [0], [1], etc.]
       -- empty line --
[[0] URL - Reference to a URL in the description, almost like Markdown;
    the last numbered reference is followed by an -- empty line -- ]
[Follow-up to {shorthash} - if this fixes or continues a previous commit;
    add a Ref: that commit's PR or issue if it's not a small, obvious fix;
    followed by an -- empty line -- ]
[Bug: URL to the source of the report or more related discussion; use Fixes
    for GitHub issues instead when that is appropriate]
[Approved-by: John Doe - credit someone who approved the PR; if you are
    committing this for someone else using --author=... you don't need this
    as you are implicitly approving it by committing]
[Authored-by: John Doe - credit the original author of the code; only use
    this if you can't use "git commit --author=..."]
{Signed-off-by: John Doe - we don't use this, but don't bother removing it]
[whatever-else-by: credit all helpers, finders, doers; try to use one of
    the following keywords if at all possible, for consistency:
    Acked-by:, Assisted-by:, Co-authored-by:, Found-by:, Reported-by:,
    Reviewed-by:, Suggested-by:, Tested-by:]
[Ref: #1234 - if this is related to a GitHub issue or PR, possibly one that
              has already been closed]
[Ref: URL to more information about the commit; use Bug: instead for
    a reference to a bug on another bug tracker]
[Fixes #1234 - if this closes a GitHub issue; GitHub will actually
    close the issue once this commit is merged]
[Closes #1234 - if this closes a GitHub PR; GitHub will actually
    close the PR once this commit is merged]
---- stop ----

The first line is a succinct description of the change:

  • use the imperative, present tense: "change" not "changed" nor "changes"
  • do not capitalize the first letter
  • no period (.) at the end

The [area] in the first line can be http2, cookies, openssl or
similar. There's no fixed list to select from but using the same "area" as
other related changes could make sense.

Do not forget to use commit --author=... if you commit someone else's work, and
make sure that you have your own user and email setup correctly in git before
you commit.

Add whichever header lines as appropriate, with one line per person if more
than one person was involved. There's no need to credit yourself unless you are
using --author=... which hides your identity. Don't include people's e-mail
addresses in headers to avoid spam, unless they're already public from a
previous commit; saying {userid} on github is OK.

Write Access to git Repository

If you are a frequent contributor, you may be given push access to the git
repository and then you will be able to push your changes straight into the git
repo instead of sending changes as pull requests or by mail as patches.

Just ask if this is what you would want. You will be required to have posted
several high quality patches first, before you can be granted push access.

How To Make a Patch with git

You need to first checkout the repository:

text
git clone https://github.com/curl/curl.git

You then proceed and edit all the files you like and you commit them to your
local repository:

text
git commit [file]

As usual, group your commits so that you commit all changes at once that
constitute a logical change.

Once you have done all your commits and you are happy with what you see, you
can make patches out of your changes that are suitable for mailing:

text
git format-patch remotes/origin/master

This creates files in your local directory named NNNN-[name].patch for each
commit.

Now send those patches off to the curl-library list. You can of course opt to
do that with the 'git send-email' command.

How To Make a Patch without git

Keep a copy of the unmodified curl sources. Make your changes in a separate
source tree. When you think you have something that you want to offer the
curl community, use GNU diff to generate patches.

If you have modified a single file, try something like:

text
diff -u unmodified-file.c my-changed-one.c > my-fixes.diff

If you have modified several files, possibly in different directories, you
can use diff recursively:

text
diff -ur curl-original-dir curl-modified-sources-dir > my-fixes.diff

The GNU diff and GNU patch tools exist for virtually all platforms, including
all kinds of Unixes and Windows.

Useful resources

There is a CI job called REUSE compliance / check that will run on every
pull request and commit to verify that the REUSE state of all files are
still fine.

This means that all files need to have their license and copyright information
clearly stated. Ideally by having the standard curl source code header, with
an accurate copyright year range and the SPDX-License-Identifier included. If
the header does not work, you can use a smaller header or add the information
for a specific file to the .reuse/dep5 file.

We update copyright year ranges to end on the year of the most recent change
of the individual file.

You can manually verify the copyright and compliance status by running the
./scripts/copyright.pl script in the root of the git repository.


Third Party Curl Docs CURL DISABLE (`third_party/curl/docs/CURL-DISABLE.md`)

Code defines to disable features and protocols

`CURL_DISABLE_ALTSVC`

Disable support for Alt-Svc: HTTP headers.

`CURL_DISABLE_COOKIES`

Disable support for HTTP cookies.

`CURL_DISABLE_CRYPTO_AUTH`

Disable support for authentication methods using crypto.

`CURL_DISABLE_DICT`

Disable the DICT protocol

`CURL_DISABLE_DOH`

Disable DNS-over-HTTPS

`CURL_DISABLE_FILE`

Disable the FILE protocol

`CURL_DISABLE_FTP`

Disable the FTP (and FTPS) protocol

`CURL_DISABLE_GETOPTIONS`

Disable the curl_easy_options API calls that lets users get information
about existing options to curl_easy_setopt.

`CURL_DISABLE_GOPHER`

Disable the GOPHER protocol.

`CURL_DISABLE_HEADERS_API`

Disable the HTTP header API.

`CURL_DISABLE_HSTS`

Disable the HTTP Strict Transport Security support.

`CURL_DISABLE_HTTP`

Disable the HTTP(S) protocols. Note that this then also disable HTTP proxy
support.

`CURL_DISABLE_HTTP_AUTH`

Disable support for all HTTP authentication methods.

`CURL_DISABLE_IMAP`

Disable the IMAP(S) protocols.

`CURL_DISABLE_LDAP`

Disable the LDAP(S) protocols.

`CURL_DISABLE_LDAPS`

Disable the LDAPS protocol.

`CURL_DISABLE_LIBCURL_OPTION`

Disable the --libcurl option from the curl tool.

`CURL_DISABLE_MIME`

Disable MIME support.

`CURL_DISABLE_MQTT`

Disable MQTT support.

`CURL_DISABLE_NETRC`

Disable the netrc parser.

`CURL_DISABLE_NTLM`

Disable support for NTLM.

`CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG`

Disable the auto load config support in the OpenSSL backend.

`CURL_DISABLE_PARSEDATE`

Disable date parsing

`CURL_DISABLE_POP3`

Disable the POP3 protocol

`CURL_DISABLE_PROGRESS_METER`

Disable the built-in progress meter

`CURL_DISABLE_PROXY`

Disable support for proxies

`CURL_DISABLE_RTSP`

Disable the RTSP protocol.

`CURL_DISABLE_SHUFFLE_DNS`

Disable the shuffle DNS feature

`CURL_DISABLE_SMB`

Disable the SMB(S) protocols

`CURL_DISABLE_SMTP`

Disable the SMTP(S) protocols

`CURL_DISABLE_SOCKETPAIR`

Disable the use of socketpair() internally to allow waking up and canceling
curl_multi_poll().

`CURL_DISABLE_TELNET`

Disable the TELNET protocol

`CURL_DISABLE_TFTP`

Disable the TFTP protocol

`CURL_DISABLE_VERBOSE_STRINGS`

Disable verbose strings and error messages.


Third Party Curl Docs DEPRECATE (`third_party/curl/docs/DEPRECATE.md`)

Items to be removed from future curl releases

If any of these deprecated features is a cause for concern for you, please
email the
curl-library mailing list
as soon as possible and explain to us why this is a problem for you and
how your use case cannot be satisfied properly using a workaround.

NSS

We remove support for building curl with the NSS TLS library in August 2023.

  • There are few users left who use curl+NSS
  • NSS has few users outside of curl as well (primarily Firefox)
  • NSS is harder than ever to find documentation for
  • NSS was always "best" used with Red Hat Linux when they provided additional
    features on top of the regular NSS that is not shipped by the vanilla library

Starting in 7.82.0, building curl to use NSS configure requires the additional
flag --with-nss-deprecated in an attempt to highlight these plans.

gskit

We remove support for building curl with the gskit TLS library in August 2023.

  • This is a niche TLS library, only running on some IBM systems
  • no regular curl contributors use this backend
  • no CI builds use or verify this backend
  • gskit, or the curl adaption for it, lacks many modern TLS features making it
    an inferior solution
  • build breakages in this code take weeks or more to get detected
  • fixing gskit code is mostly done "flying blind"

mingw v1

We remove support for building curl with the original legacy mingw version 1
in September 2023.

During the deprecation period you can enable the support with the configure
option --with-mingw1-deprecated.

mingw version 1 is old and deprecated software. There are much better and
still support build environments to use to build curl and other software. For
example MinGW-w64.

space-separated `NOPROXY` patterns

When specifying patterns/domain names for curl that should not go through a
proxy, the curl tool features the --noproxy command line option and the
library supports the NO_PROXY environment variable and the CURLOPT_NOPROXY
libcurl option.

They all set the same list of patterns. This list is documented to be a set of
comma-separated names, but can also be provided separated with just
space. The ability to just use spaces for this has never been documented but
some users may still have come to rely on this.

Several other tools and utilities also parse the NO_PROXY environment
variable but do not consider a space to be a valid separator. Using spaces for
separator is probably less portable and might cause more friction than commas
do. Users should use commas for this for greater portability.

curl will remove the support for space-separated names in July 2024.

past removals

  • Pipelining
  • axTLS
  • PolarSSL
  • NPN
  • Support for systems without 64 bit data types

Third Party Curl Docs DYNBUF (`third_party/curl/docs/DYNBUF.md`)

dynbuf

This is the internal module for creating and handling "dynamic buffers". This
means buffers that can be appended to, dynamically and grow to adapt.

There will always be a terminating zero put at the end of the dynamic buffer.

The struct dynbuf is used to hold data for each instance of a dynamic
buffer. The members of that struct MUST NOT be accessed or modified
without using the dedicated dynbuf API.

`Curl_dyn_init`

c
void Curl_dyn_init(struct dynbuf *s, size_t toobig);

This initializes a struct to use for dynbuf and it cannot fail. The toobig
value must be set to the maximum size we allow this buffer instance to
grow to. The functions below will return CURLE_OUT_OF_MEMORY when hitting
this limit.

`Curl_dyn_free`

c
void Curl_dyn_free(struct dynbuf *s);

Free the associated memory and clean up. After a free, the dynbuf struct can
be re-used to start appending new data to.

`Curl_dyn_addn`

c
CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len);

Append arbitrary data of a given length to the end of the buffer.

If this function fails it calls Curl_dyn_free on dynbuf.

`Curl_dyn_add`

c
CURLcode Curl_dyn_add(struct dynbuf *s, const char *str);

Append a C string to the end of the buffer.

If this function fails it calls Curl_dyn_free on dynbuf.

`Curl_dyn_addf`

c
CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...);

Append a printf()-style string to the end of the buffer.

If this function fails it calls Curl_dyn_free on dynbuf.

`Curl_dyn_vaddf`

c
CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap);

Append a vprintf()-style string to the end of the buffer.

If this function fails it calls Curl_dyn_free on dynbuf.

`Curl_dyn_reset`

c
void Curl_dyn_reset(struct dynbuf *s);

Reset the buffer