Technical Documentation: TEN-framework/ten-framework
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (README.md)
Table of Contents
- Welcome to TEN
- Agent Examples
- Quick Start with Agent Examples
- Agent Examples Self-Hosting
- Stay Tuned
- TEN Ecosystem
- Questions
- Contributing
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.
Agent Examples
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.
Doodler — A doodle board that turns spoken or typed prompts into simple hand-drawn sketches, complete with a crayon palette and real-time drawing.
Speaker Diarization — Real-time diarization that detects and labels speakers, the Who Likes What game shows an interactive use case.
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.
SIP Call — SIP extension that enables phone calls powered by TEN.
Transcription — A transcription tool that transcribes audio to text.
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 |
Step ⓶ - Build agent examples in VM
1. Clone the repo, `cd` into `ai_agents`, and create a `.env` file from `.env.example`
cd ai_agents
cp ./.env.example ./.env2. Set up the Agora App ID and App Certificate in `.env`
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
docker compose up -d4. Enter the container
docker exec -it ten_agent_dev bash5. Build the agent with the default example (~5-8 min)
Check the agents/examples folder for additional samples.
Start with one of these defaults:
# 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-realtime6. 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.
task install
task run7. Access the agent
Once the agent example is running, you can access the following interfaces:
| localhost:49483 | localhost:3000 |
|---|---|
- TMAN Designer: localhost:49483
- Agent Examples UI: localhost:3000
Step ⓷ - Customize your agent example
- Open localhost:49483.
- Right-click the STT, LLM, and TTS extensions.
- Open their properties and enter the corresponding API keys.
- Submit your changes, now you can see the updated Agent Example in localhost:3000.
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.
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
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .Run
docker run --rm -it --env-file .env -p 3000:3000 example-appDeploying 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.
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
8080from that service.Deploy only the frontend to Vercel or Netlify. Point the project root to
ai_agents/agents/examples/<example>/frontend, runpnpm install(orbun install) followed bypnpm build(orbun run build), and keep the default.nextoutput directory.Configure environment variables in your hosting dashboard so that
AGENT_SERVER_URLpoints to the backend URL, and add anyNEXT_PUBLIC_*keys the UI needs (for example, Agora credentials you surface to the browser).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!
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 | |
| 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.
Code Contributors
Contribution Guidelines
Contributions are welcome! Please read the contribution guidelines first.
License
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.
The components within the
packagesdirectory are released under the Apache License, Version 2.0. For details, please refer to theLICENSEfile located in each package's root directory.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 tagv3.10.2):
git clone https://github.com/nlohmann/json.git
cd json
git checkout v3.10.2
make install_venv serve -C docs/mkdocsOpen 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:
`curl-config --cc --cflags --libs` -o example example.cSome compilers do not like having the arguments in this order but instead
want you do reorganize them like:
`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
make nlohmann_json.docsetThe 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:
- Assertion macros
- Matchers (asserting complex properties)
- Comparing floating point numbers
- Logging macros
- Test cases and sections
- Test fixtures
- Explicitly skipping, passing, and failing tests at runtime
- Reporters (output customization)
- Event Listeners
- Data Generators (value parameterized tests)
- Other macros
- Micro benchmarking
Fine tuning:
Running:
Odds and ends:
- Frequently Asked Questions (FAQ)
- Best practices and other tips
- CMake integration
- Tooling integration (CI, test runners, other)
- Known limitations
Other:
- Why Catch2?
- Migrating from v2 to v3
- Open Source Projects using Catch2
- Commercial Projects using Catch2
- Contributing
- Release Notes
- Deprecations and incoming changes
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
#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 filesingle_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
template<typename, typename>
struct adl_serializer;Serializer that uses ADL (Argument-Dependent Lookup) to chooseto_json/from_json functions from the types' namespaces.
It is implemented similar to
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
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>
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
- DefaultConstructible: JSON values can be default
constructed. The result will be a JSON null value. - MoveConstructible: A JSON value can be constructed
from an rvalue argument. - CopyConstructible: A JSON value can be
copy-constructed from an lvalue expression. - MoveAssignable: A JSON value can be assigned from an
rvalue argument. - CopyAssignable: A JSON value can be copy-assigned from
an lvalue expression. - Destructible: JSON values can be destructed.
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
==, seeoperator==. - LessThanComparable: JSON values can be compared with
<, seeoperator<. - Swappable: Any JSON lvalue or rvalue of can be swapped with
any lvalue or rvalue of other compatible types, using unqualified functionswap. - NullablePointer: JSON values can be compared against
std::nullptr_tobjects which are used to model thenullvalue.
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
- adl_serializer - the default serializer
- value_t - the JSON type enumeration
- json_pointer - JSON Pointer implementation
- json_serializer - type of the serializer to for conversions from/to JSON
- error_handler_t - type to choose behavior on decoding errors
- cbor_tag_handler_t - type to choose how to handle CBOR tags
- initializer_list_t - type for initializer lists of
basic_jsonvalues - input_format_t - type to choose the format to parse
- json_sax_t - type for SAX events
Exceptions
- exception - general exception of the
basic_jsonclass- 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
- array_t - type for arrays
- binary_t - type for binary arrays
- boolean_t - type for booleans
- default_object_comparator_t - default comparator for objects
- number_float_t - type for numbers (floating-point)
- number_integer_t - type for numbers (integer)
- number_unsigned_t - type for numbers (unsigned)
- object_comparator_t - comparator for objects
- object_t - type for objects
- string_t - type for strings
Parser callback
- parse_event_t - parser event types
- parser_callback_t - per-element parser callback type
Member functions
- (constructor)
- (destructor)
- operator= - copy assignment
- array (static) - explicitly create an array
- binary (static) - explicitly create a binary array
- object (static) - explicitly create an object
Object inspection
Functions to inspect the type of a JSON value.
- type - return the type of the JSON value
- operator value_t - return the type of the JSON value
- type_name - return the type as string
- is_primitive - return whether type is primitive
- is_structured - return whether type is structured
- is_null - return whether value is null
- is_boolean - return whether value is a boolean
- is_number - return whether value is a number
- is_number_integer - return whether value is an integer number
- is_number_unsigned - return whether value is an unsigned integer number
- is_number_float - return whether value is a floating-point number
- is_object - return whether value is an object
- is_array - return whether value is an array
- is_string - return whether value is a string
- is_binary - return whether value is a binary array
- is_discarded - return whether value is discarded
Value access
Direct access to the stored value of a JSON value.
- get - get a value
- get_to - get a value and write it to a destination
- get_ptr - get a pointer value
- get_ref - get a reference value
- operator ValueType - get a value
- get_binary - get a binary 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
- operator== - comparison: equal
- operator!= - comparison: not equal
- operator< - comparison: less than
- operator> - comparison: greater than
- operator<= - comparison: less than or equal
- operator>= - comparison: greater than or equal
- operator<=> - comparison: 3-way
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
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
- merge_patch - applies a JSON Merge Patch
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
- operator<<(std::ostream&) - serialize to stream
- operator>>(std::istream&) - deserialize from stream
- to_string - user-defined
to_stringfunction for JSON values
Literals
- operator""_json - user-defined string literal for JSON values
Helper classes
- std::hash<basic_json> - return a hash value for a JSON object
- std::swap<basic_json> - exchanges the values of two JSON objects
Examples
??? example
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
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
- (constructor)
- operator== - comparison: equal
- operator!= - comparison: not equal
- set_subtype - sets the binary subtype
- subtype - return the binary subtype
- has_subtype - return whether the value has a subtype
- clear_subtype - clears the binary subtype
Version history
- Added in version 3.8.0.
- Changed type of subtypes to
#!cpp std::uint64_tin 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
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"
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
- (constructor)
- to_string - return a string representation of the JSON pointer
- operator string_t - return a string representation of the JSON pointer
- operator== - compare: equal
- operator!= - compare: not equal
- operator/= - append to the end of the JSON pointer
- operator/ - create JSON Pointer by appending
- parent_pointer - returns the parent of this JSON pointer
- pop_back - remove last reference token
- back - return last reference token
- push_back - append an unescaped token at the end of the pointer
- empty - return whether pointer points to the root document
Literals
- operator""_json_pointer - user-defined string literal for JSON pointers
See also
Version history
- Added in version 2.0.0.
- Changed template parameter from
basic_jsonto 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
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
- number_integer_t -
BasicJsonType's type for numbers (integer) - number_unsigned_t -
BasicJsonType's type for numbers (unsigned) - number_float_t -
BasicJsonType's type for numbers (floating-point) - string_t -
BasicJsonType's type for strings - binary_t -
BasicJsonType's type for binary arrays
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
- JSON_ASSERT(x) - control behavior of runtime assertions
Exceptions
- **JSON_CATCH_USER(exception)****JSON_THROW_USER(exception)**JSON_TRY_USER - control exceptions
- JSON_DIAGNOSTICS - control extended diagnostics
- JSON_NOEXCEPTION - switch off exceptions
Language support
- JSON_HAS_CPP_11JSON_HAS_CPP_14JSON_HAS_CPP_17****JSON_HAS_CPP_20 - set supported C++ standard
- JSON_HAS_FILESYSTEM****JSON_HAS_EXPERIMENTAL_FILESYSTEM - control
std::filesystemsupport - JSON_HAS_RANGES - control
std::rangessupport - JSON_HAS_THREE_WAY_COMPARISON - control 3-way comparison support
- JSON_NO_IO - switch off functions relying on certain C++ I/O headers
- JSON_SKIP_UNSUPPORTED_COMPILER_CHECK - do not warn about unsupported compilers
- JSON_USE_GLOBAL_UDLS - place user-defined string literals (UDLs) into the global namespace
Library version
- JSON_SKIP_LIBRARY_VERSION_CHECK - skip library version check
- NLOHMANN_JSON_VERSION_MAJORNLOHMANN_JSON_VERSION_MINORNLOHMANN_JSON_VERSION_PATCH
- library version information
Library namespace
- NLOHMANN_JSON_NAMESPACE - full name of the
nlohmannnamespace - NLOHMANN_JSON_NAMESPACE_BEGIN****NLOHMANN_JSON_NAMESPACE_END - open and
close the library namespace - NLOHMANN_JSON_NAMESPACE_NO_VERSION - disable the version component of
the inline namespace
Type conversions
- JSON_DISABLE_ENUM_SERIALIZATION - switch off default serialization/deserialization functions for enums
- JSON_USE_IMPLICIT_CONVERSIONS - control implicit conversions
Comparison behavior
- JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON -
control comparison of discarded values
Serialization/deserialization macros
- **NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(type, member...)
- serialization/deserialization of types with access to private variables - **NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...)**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(type, member...)
- serialization/deserialization of types without access to private variables - NLOHMANN_JSON_SERIALIZE_ENUM(type, ...) - serialization/deserialization of enum types
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
- BJData (Binary JData),
- BSON (Binary JSON),
- CBOR (Concise Binary Object Representation),
- MessagePack, and
- UBJSON (Universal Binary JSON)
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
This page is under construction.Input
SAX vs. DOM parsing
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
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_tTemplate arguments
The data types to store a JSON value are derived from the template arguments passed to class basic_json:
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:
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:
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:
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 010will 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。
| 社区渠道 | 用途 |
|---|---|
| 在 X 上关注 TEN Framework,获取更新与公告 | |
| 加入 Discord 社区,与开发者交流 | |
| 在 LinkedIn 上关注 TEN Framework,获取动态和公告 | |
| 加入 Hugging Face 社区,探索我们的空间与模型 | |
| 加入微信社群,与中文社区讨论 |
代理示例
多用途语音助手 —— 低延迟、高质量的实时助手,可通过 记忆、VAD、回合检测 等扩展能力进行增强。
详见 示例代码。
Doodler —— 将语音或文本提示转换为手绘风格涂鸦的画板,配有蜡笔调色板并支持实时绘制。
唇形同步头像 —— 适配多个头像供应商,演示中包含 Live2D 唇形同步的动漫角色 Kei,并即将支持 Trulience、HeyGen、Tavus 等写实头像。
查看 Live2D 示例代码。
语音分离(Diarization) —— 实时检测并标记不同说话人,“Who Likes What” 游戏展示了一个交互式场景。
SIP 通话 —— 通过 TEN 提供电话功能的 SIP 扩展。
转写(Transcription) —— 将音频实时转换为文本的工具。
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 |
如果在国内,我们强烈建议在 SSH 中把代理打开,下载和安装的依赖的时候会更加丝滑。
# 如果用的代理软件没有增强模式的话, 建议手动把所有代理协议都打开
# 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步骤 ⓶ - 在虚拟机中构建代理示例
1. 克隆仓库,进入 `ai_agents`,并用 `.env.example` 创建 `.env`
cd ai_agents
cp ./.env.example ./.env2. 在 `.env` 中配置 Agora App ID 与 App Certificate
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_here3. 启动代理开发容器
docker compose up -d4. 进入容器
docker exec -it ten_agent_dev bash5. 使用默认示例构建代理(约 5-8 分钟)
在 agents/examples 文件夹中可以找到更多示例。
从以下默认示例之一开始:
# 使用串联语音助手
cd agents/examples/voice-assistant
# 或者使用实时语音对话助手
cd agents/examples/voice-assistant-realtime6. 启动 Web 服务
首次启动前,以及依赖或 Go 源码发生变化后,请运行 task install。它会安装 TEN、Python 和前端依赖,并构建 Go API 服务。仅修改 Python 源码时无需重新安装。
task install
task run7. 访问代理
启动示例后,可以访问以下界面:
| localhost:49483 | localhost:3000 |
|---|
- TMAN Designer:http://localhost:49483
- 代理示例界面:http://localhost:3000
步骤 ⓷ - 自定义代理示例
- 打开 localhost:49483。
- 右键单击 STT、LLM、TTS 扩展。
- 在属性面板中填写对应的 API Key。
- 提交更改后,即可在 localhost:3000 查看更新效果。
在不使用 Docker 的情况下从 TEN Manager 运行转录应用(Beta)
TEN 还提供了一个转录应用,无需 Docker 即可直接在 TEN Manager 中运行。
详情请查看快速入门指南。
Codespaces
GitHub 为每个仓库提供免费的 Codespaces。无需 Docker 即可运行代理示例,并且通常比本地 Docker 环境启动更快。
更多细节请查看这篇指南。
代理示例自托管
使用 Docker 部署
当你通过 TMAN Designer 或直接编辑 property.json 自定义完代理后,可以为服务构建发布用的 Docker 镜像并部署。
以 Docker 镜像方式发布
注意:以下命令需在 Docker 容器外部执行。
构建镜像
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .运行
docker run --rm -it --env-file .env -p 3000:3000 example-app部署到其他云服务
若需将 TEN 部署到 Vercel、Netlify 等平台,可以拆分为前后端两部分:
- 在任意支持容器的平台(Docker 主机、Fly.io、Render、ECS、Cloud Run 等)运行 TEN 后端。直接使用示例镜像,开放
8080端口。 - 仅部署前端到 Vercel 或 Netlify。将项目根目录指向
ai_agents/agents/examples/<example>/frontend,执行pnpm install(或bun install)与pnpm build(或bun run build),并保留默认.next输出。 - 在托管平台的环境变量面板设置
AGENT_SERVER_URL指向后端 URL,同时配置前端需要的NEXT_PUBLIC_*变量(例如需要暴露给浏览器的 Agora 凭据)。 - 确保后端允许来自前端域名的请求,可通过开放 CORS 或使用内置代理中间件。
这样后端负责长时间运行的工作进程,托管前端只需将 API 请求转发给后端即可。
保持关注
实时获取版本更新与最新动态。你的支持能帮助 TEN 变得更好!
TEN 生态
| 项目 | 预览 |
|---|---|
| ️TEN Framework面向对话式 AI 代理的开源框架。 |
|
| TEN VAD低延迟、轻量且高性能的流式语音活动检测。 |
|
| ️TEN Turn Detection实现全双工对话的回合检测。 |
|
| TEN Agent Examples基于 TEN 的多种应用示例。 | |
| TEN PortalTEN 官方站点,提供文档与博客。 |
常见问题
TEN Framework 也可通过以下 AI 驱动的问答平台获取信息,它们支持多语言检索,覆盖从基础配置到高级实践的内容。
| 服务 | 链接 |
|---|---|
| DeepWiki | |
| ReadmeX |
参与贡献
欢迎所有形式的开源协作!无论是修复缺陷、添加功能、改进文档还是分享创意,你的参与都能推动个性化 AI 工具向前发展。访问 GitHub Issues 与 Projects 寻找合适的任务,展示你的技能,让我们一起构建更出色的 TEN!
[!TIP]
欢迎所有类型的贡献 🙏
帮助我们让 TEN 变得更好!从代码到文档,每一次贡献都弥足珍贵。也欢迎在社交平台分享你的 TEN 代理项目,激励更多创作者。
可以通过 𝕏 上的 @elliotchen200 或 GitHub 上的 @cyfyifanchen 与维护者联系,获取项目动态、讨论与合作机会。
代码贡献者
贡献指南
欢迎贡献!在提交之前,请先阅读贡献指南。
许可证
- 除下列目录外,TEN Framework 均以 Apache License 2.0(附加条款)发布,详见根目录下的 LICENSE 文件。
packages目录中的组件以 Apache License 2.0 发布,详情请参考各组件根目录内的LICENSE文件。- TEN Framework 使用的第三方库均在 third_party 目录中列出并说明。
README ES (`docs/README-ES.md`)
Tabla de contenido
- Bienvenido a TEN
- Ejemplos de agente
- Inicio rápido con los ejemplos de agente
- Auto-hospedaje de los ejemplos de agente
- Mantente al día
- Ecosistema TEN
- Preguntas
- Cómo contribuir
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.
Ejemplos de agente
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.
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.
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.
Diarización de voz — Detección y etiquetado de hablantes en tiempo real. El juego "Who Likes What" muestra un caso de uso interactivo.
Llamada SIP — Extensión SIP que habilita llamadas telefónicas impulsadas por TEN.
Transcripción — Herramienta de transcripción que convierte audio en texto.
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 |
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`
cd ai_agents
cp ./.env.example ./.env2. Configura el Agora App ID y App Certificate en `.env`
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_here3. Inicia los contenedores de desarrollo del agente
docker compose up -d4. Entra en el contenedor
docker exec -it ten_agent_dev bash5. 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:
# 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-realtime6. 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.
task install
task run7. Accede al agente
Cuando el ejemplo esté en marcha podrás usar estas interfaces:
| localhost:49483 | localhost:3000 |
|---|
- TMAN Designer: http://localhost:49483
- UI de ejemplos de agente: http://localhost:3000
Paso ⓷ - Personaliza tu ejemplo de agente
- Abre localhost:49483.
- Haz clic derecho en las extensiones STT, LLM y TTS.
- Rellena sus propiedades con las API keys correspondientes.
- Envía los cambios; el ejemplo actualizado aparecerá en localhost:3000.
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.
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
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .Ejecutar
docker run --rm -it --env-file .env -p 3000:3000 example-appImplementar en otros servicios en la nube
Puedes dividir el despliegue en dos partes si deseas alojar TEN en proveedores como Vercel o Netlify.
- 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. - Despliega solo el frontend en Vercel o Netlify. Apunta la raíz del proyecto a
ai_agents/agents/examples/<example>/frontend, ejecutapnpm install(obun install), luegopnpm build(obun run build) y conserva el directorio de salida.nextpredeterminado. - Configura las variables de entorno en el panel de tu hosting.
AGENT_SERVER_URLdebe apuntar al backend y añade cualquier claveNEXT_PUBLIC_*necesaria (por ejemplo, credenciales de Agora visibles para el navegador). - 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.
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 | |
| 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.
Personas contribuidoras
Guía de contribución
¡Contribuye cuando quieras! Lee primero la guía de contribución.
Licencia
- 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.
- Los componentes dentro de
packagesse liberan bajo Apache License 2.0. Cada paquete contiene su propio archivoLICENSEcon los detalles. - 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
- Exemples d’agents
- Démarrage rapide avec les exemples d’agents
- Auto-hébergement des exemples d’agents
- Restez informé·e
- Écosystème TEN
- Questions
- Contribuer
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.
Exemples d’agents
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.
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.
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.
Diarisation vocale — Détection et étiquetage des locuteurs en temps réel. Le jeu "Who Likes What" illustre un cas d’usage interactif.
Appels SIP — Extension SIP qui permet d’effectuer des appels téléphoniques propulsés par TEN.
Transcription — Outil de transcription qui convertit la voix en texte.
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 |
É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`
cd ai_agents
cp ./.env.example ./.env2. Configurez Agora App ID et App Certificate dans `.env`
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_here3. Lancez les conteneurs de développement
docker compose up -d4. Entrez dans le conteneur
docker exec -it ten_agent_dev bash5. 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 :
# Assistant vocal chaîné
cd agents/examples/voice-assistant
# Assistant voix-à-voix temps réel
cd agents/examples/voice-assistant-realtime6. 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.
task install
task run7. Accédez à l’agent
Une fois l’exemple démarré, ces interfaces sont disponibles :
| localhost:49483 | localhost:3000 |
|---|
- TMAN Designer : http://localhost:49483
- Interface des exemples : http://localhost:3000
Étape ⓷ - Personnaliser votre exemple
- Ouvrez localhost:49483.
- Cliquez droit sur les extensions STT, LLM et TTS.
- Renseignez les clés API correspondantes.
- Validez : la mise à jour apparaît sur localhost:3000.
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.
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
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .Exécuter
docker run --rm -it --env-file .env -p 3000:3000 example-appDéployer sur d’autres services cloud
Divisez le déploiement en deux parties pour héberger TEN sur des plateformes comme Vercel ou Netlify.
- 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. - Déployez uniquement le frontend sur Vercel ou Netlify. Pointez la racine du projet vers
ai_agents/agents/examples/<example>/frontend, lancezpnpm install(oubun install) puispnpm build(oubun run build) et conservez le répertoire.nextpar défaut. - Dans le tableau de bord d’hébergement, définissez
AGENT_SERVER_URLvers l’URL du backend et ajoutez les variablesNEXT_PUBLIC_*nécessaires (comme les identifiants Agora côté navigateur). - 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.
É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 | |
| 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.
Contributrices et contributeurs
Guide de contribution
Les contributions sont les bienvenues ! Lisez d’abord le guide de contribution.
Licence
- 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.
- Les composants du dossier
packagessont publiés sous Apache 2.0. Référez-vous au fichierLICENSEpropre à chaque package. - 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
- Esempi di agenti
- Guida rapida agli esempi di agenti
- Auto-hosting degli esempi
- Rimani aggiornato
- Ecosistema TEN
- Domande
- Contribuire
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.
Esempi di agenti
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.
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.
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.
Diarizzazione vocale — Rilevamento e etichettatura dei parlanti in tempo reale. Il gioco "Who Likes What" mostra un caso d’uso interattivo.
Chiamata SIP — Estensione SIP che abilita chiamate telefoniche gestite da TEN.
Trascrizione — Strumento che trascrive l’audio in testo.
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 |
Passaggio ⓶ - Compila gli esempi nella VM
1. Clona il repo, entra in `ai_agents` e crea `.env` da `.env.example`
cd ai_agents
cp ./.env.example ./.env2. Configura Agora App ID e App Certificate in `.env`
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_here3. Avvia i container di sviluppo
docker compose up -d4. Entra nel container
docker exec -it ten_agent_dev bash5. Compila l’agente con l’esempio predefinito (≈5-8 min)
Trovi altre demo nella cartella agents/examples.
Inizia da una di queste:
# Assistente vocale concatenato
cd agents/examples/voice-assistant
# Assistente voce-a-voce in tempo reale
cd agents/examples/voice-assistant-realtime6. 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.
task install
task run7. Accedi all’agente
Quando l’esempio è in esecuzione puoi usare queste interfacce:
| localhost:49483 | localhost:3000 |
|---|
- TMAN Designer: http://localhost:49483
- UI degli esempi: http://localhost:3000
Passaggio ⓷ - Personalizza l’esempio
- Apri localhost:49483.
- Fai clic con il tasto destro sulle estensioni STT, LLM e TTS.
- Inserisci le relative API key.
- Dopo aver salvato, la versione aggiornata sarà visibile su localhost:3000.
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.
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
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .Esecuzione
docker run --rm -it --env-file .env -p 3000:3000 example-appDistribuire su altri servizi cloud
Puoi dividere il deployment in due parti quando ospiti TEN su piattaforme come Vercel o Netlify.
- 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. - Distribuisci solo il frontend su Vercel o Netlify. Imposta la radice del progetto su
ai_agents/agents/examples/<example>/frontend, eseguipnpm install(obun install) e poipnpm build(obun run build), mantenendo la cartella di output.nextpredefinita. - Configura le variabili di ambiente dal pannello del provider:
AGENT_SERVER_URLdeve puntare al backend e aggiungi le chiaviNEXT_PUBLIC_*necessarie (per esempio le credenziali Agora esposte al browser). - 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!
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 | |
| 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.
Contributor del codice
Linee guida per contribuire
Le contribuzioni sono benvenute! Leggi prima le linee guida per contribuire.
Licenza
- 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.
- I componenti nella directory
packagessono distribuiti sotto Apache License 2.0. Ogni package contiene il proprio fileLICENSE. - 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 が含まれます。
| コミュニティ | 目的 |
|---|---|
| X で TEN Framework をフォローして最新情報をチェック | |
| Discord コミュニティに参加し、開発者同士で交流 | |
| LinkedIn で TEN Framework をフォローし、ニュースを受け取る | |
| Hugging Face コミュニティでスペースやモデルを探索 | |
| 中国語コミュニティ向けの WeChat グループに参加 |
エージェント事例
多目的ボイスアシスタント — 低レイテンシ・高品質のリアルタイムアシスタント。メモリ、VAD、ターン検出 などの拡張機能を追加できます。
詳細は サンプルコード を参照してください。
Doodler — 音声またはテキストのプロンプトを手描き風のシンプルなスケッチに変換するお絵かきボード。クレヨンのパレットとリアルタイム描画を備えています。
リップシンク対応アバター — 複数のアバタープロバイダーに対応。デモでは Live2D のリップシンクを備えたアニメキャラクター Kei を紹介し、今後 Trulience、HeyGen、Tavus のリアルアバターにも対応予定です。
話者分離(Diarization) — 話者をリアルタイムで検出・ラベル付けします。ゲーム「Who Likes What」でインタラクティブな活用例を紹介しています。
SIP 通話 — TEN で電話を実現する SIP 拡張機能です。
文字起こし — 音声をテキストへ変換するトランスクリプションツール。
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 以上 |
ステップ ⓶ - VM 内でサンプルをビルド
1. リポジトリをクローンし、`ai_agents` に移動して `.env.example` から `.env` を作成
cd ai_agents
cp ./.env.example ./.env2. `.env` に Agora App ID と App Certificate を設定
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_here3. 開発用コンテナを起動
docker compose up -d4. コンテナに入る
docker exec -it ten_agent_dev bash5. デフォルトサンプルでエージェントをビルド(約 5〜8 分)
agents/examples ディレクトリには他のサンプルもあります。
以下のいずれかで開始できます:
# チェーン型ボイスアシスタント
cd agents/examples/voice-assistant
# リアルタイムの音声対音声アシスタント
cd agents/examples/voice-assistant-realtime6. Web サーバーを起動
初回起動前、および依存関係や Go のソースコードを変更した後は task install を実行してください。このコマンドは TEN、Python、フロントエンドの依存関係をインストールし、Go API サーバーをビルドします。Python のソースコードのみを変更した場合、再インストールは不要です。
task install
task run7. エージェントにアクセス
サンプルが起動すると次の UI を利用できます。
| localhost:49483 | localhost:3000 |
|---|
- TMAN Designer: http://localhost:49483
- エージェント事例 UI: http://localhost:3000
ステップ ⓷ - サンプルをカスタマイズ
- localhost:49483 を開く。
- STT・LLM・TTS 拡張を右クリック。
- プロパティで対応する API キーを入力。
- 変更を保存すると localhost:3000 で更新内容を確認できます。
Docker なしで TEN Manager からトランスクリプションアプリを実行 (Beta)
TEN には、Docker を使わずに TEN Manager から実行できるトランスクリプションアプリも用意されています。
詳しくはクイックスタートガイドをご覧ください。
Codespaces
GitHub はリポジトリごとに無料の Codespaces を提供しています。Docker を使わずにエージェント事例を実行でき、通常ローカル環境よりも起動が速くなります。
詳細はこちらのガイドをご覧ください。
エージェント事例のセルフホスティング
Docker でデプロイ
TMAN Designer でカスタマイズするか property.json を編集したら、本番用の Docker イメージを作成してサービスをデプロイしましょう。
Docker イメージとして公開
注意: 以下のコマンドは Docker コンテナの外で実行してください。
イメージをビルド
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .実行
docker run --rm -it --env-file .env -p 3000:3000 example-appその他のクラウドサービスへデプロイ
TEN を Vercel や Netlify などでホストする場合、バックエンドとフロントエンドを分けて配置できます。
- Docker 対応の任意のプラットフォーム(Docker が動く VM、Fly.io、Render、ECS、Cloud Run など)で TEN バックエンドを実行。用意されたサンプルイメージをそのまま使い、ポート
8080を公開します。 - フロントエンドのみ Vercel / Netlify にデプロイします。プロジェクトルートを
ai_agents/agents/examples/<example>/frontendに設定し、pnpm install(またはbun install)→pnpm build(またはbun run build)を実行し、デフォルトの.next出力を保持します。 - ホスティング側の環境変数で
AGENT_SERVER_URLをバックエンド URL に設定し、必要なNEXT_PUBLIC_*キー(ブラウザで使う Agora 資格情報など)を追加します。 - CORS を開放する、または内蔵のプロキシミドルウェアを使うなどして、フロントエンドのオリジンからバックエンドへのリクエストを許可します。
この構成では、バックエンドがワーカー処理を担い、ホストしたフロントエンドは API リクエストを転送するだけで済みます。
最新情報
新しいリリースやアップデートを即座に受け取れます。あなたのサポートが TEN を成長させます!
TEN エコシステム
| プロジェクト | プレビュー |
|---|---|
| ️TEN Framework会話型 AI エージェント向けオープンソースフレームワーク。 |
|
| TEN VAD低遅延・軽量・高性能なストリーミング音声活動検出。 |
|
| ️TEN Turn Detection全二重会話を可能にするターン検出。 |
|
| TEN Agent ExamplesTEN を使ったユースケース集。 | |
| TEN Portal公式サイト。ドキュメントとブログを掲載。 |
質問
TEN Framework は AI 駆動の Q&A プラットフォームにも掲載されています。マルチリンガルでの検索が可能で、初期設定から高度な実装までサポートします。
| サービス | リンク |
|---|---|
| DeepWiki | |
| ReadmeX |
コントリビュート
バグ修正、機能追加、ドキュメント改善、アイデア共有など、あらゆる OSS での協力を歓迎します。GitHub の Issues や Projects をチェックして活躍の場を見つけ、スキルを発揮してください。一緒に TEN をより良いものにしましょう!
[!TIP]
すべてのコントリビューションに感謝します 🙏
コードでもドキュメントでも、どんな貢献も力になります。TEN Agent プロジェクトを SNS で紹介し、コミュニティを盛り上げましょう。
メンテナー @elliotchen200(𝕏)や @cyfyifanchen(GitHub)に連絡すると、最新情報や議論、コラボの機会を得られます。
コードコントリビューター
貢献ガイドライン
いつでも歓迎です!まずは貢献ガイドラインをご確認ください。
ライセンス
- 下記のディレクトリを除き、TEN Framework 全体は追加条件付きの Apache License 2.0 で配布されています。プロジェクトルートの LICENSE を参照してください。
packages配下のコンポーネントも Apache License 2.0 で提供されます。各パッケージのLICENSEファイルをご確認ください。- TEN Framework が利用するサードパーティライブラリは third_party ディレクトリで一覧化されています。
README KR (`docs/README-KR.md`)
목차
TEN 소개
TEN은 음성 대화형 AI 에이전트를 위한 오픈소스 프레임워크입니다.
TEN 에코시스템에는 TEN Framework, 에이전트 예시, VAD, Turn Detection, Portal 이 포함됩니다.
| 커뮤니티 채널 | 설명 |
|---|---|
| X에서 TEN Framework 소식을 받아보세요 | |
| Discord 커뮤니티에 참여해 개발자들과 교류하세요 | |
| LinkedIn에서 업데이트와 공지를 확인하세요 | |
| Hugging Face 커뮤니티에서 스페이스와 모델을 살펴보세요 | |
| 중국 커뮤니티와 대화할 수 있는 WeChat 그룹 |
에이전트 예시
멀티 퍼포즈 보이스 어시스턴트 — 저지연·고품질 실시간 어시스턴트로, 메모리, VAD, 턴 감지 등으로 확장할 수 있습니다.
자세한 내용은 예시 코드를 확인하세요.
Doodler — 음성 또는 텍스트 프롬프트를 손그림 스케치로 바꾸는 낙서 보드로, 크레용 팔레트와 실시간 드로잉을 제공합니다.
립싱크 아바타 — 다양한 아바타 공급업체를 지원합니다. 데모에서는 Live2D 립싱크를 갖춘 애니메이션 캐릭터 Kei를 소개하며, 곧 Trulience, HeyGen, Tavus의 실사 아바타도 지원할 예정입니다.
음성 화자 분리 — 실시간으로 화자를 감지하고 라벨링합니다. "Who Likes What" 게임에서 인터랙티브한 사용 사례를 확인할 수 있습니다.
SIP 통화 — TEN 기반 전화 통화를 가능하게 하는 SIP 확장입니다.
전사(Transcription) — 오디오를 텍스트로 변환하는 전사 도구입니다.
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 이상 |
단계 ⓶ - VM 안에서 예시 빌드
1. 저장소를 클론하고 `ai_agents`로 이동한 뒤 `.env.example`로부터 `.env` 생성
cd ai_agents
cp ./.env.example ./.env2. `.env`에 Agora App ID / App Certificate 설정
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_here3. 개발용 컨테이너 시작
docker compose up -d4. 컨테이너 접속
docker exec -it ten_agent_dev bash5. 기본 예시로 에이전트 빌드 (약 5~8분)
agents/examples 폴더에서 다른 샘플도 확인할 수 있습니다.
다음 중 하나부터 시작하세요:
# 체이닝형 보이스 어시스턴트
cd agents/examples/voice-assistant
# 실시간 음성-음성 어시스턴트
cd agents/examples/voice-assistant-realtime6. 웹 서버 시작
처음 실행하기 전과 종속성 또는 Go 소스 코드를 변경한 후에는 task install을 실행하세요. 이 명령은 TEN, Python, 프런트엔드 종속성을 설치하고 Go API 서버를 빌드합니다. Python 소스 코드만 변경한 경우에는 다시 설치할 필요가 없습니다.
task install
task run7. 에이전트 접속
예시가 실행되면 아래 인터페이스를 이용할 수 있습니다.
| localhost:49483 | localhost:3000 |
|---|
- TMAN Designer: http://localhost:49483
- 에이전트 예시 UI: http://localhost:3000
단계 ⓷ - 예시 커스터마이징
- localhost:49483에 접속합니다.
- STT, LLM, TTS 확장을 우클릭합니다.
- 속성 창에서 해당 API 키를 입력합니다.
- 저장 후 localhost:3000에서 변경된 결과를 확인합니다.
Docker 없이 TEN Manager에서 전사 앱 실행 (Beta)
TEN은 Docker 없이 TEN Manager에서 바로 실행할 수 있는 전사 앱도 제공합니다.
자세한 내용은 빠른 시작 가이드를 확인하세요.
Codespaces
GitHub는 저장소마다 무료 Codespaces를 제공합니다. Docker 없이도 에이전트 예시를 실행할 수 있으며, 일반적으로 로컬 Docker 환경보다 빠르게 시작됩니다.
자세한 내용은 이 가이드를 참고하세요.
에이전트 예시 셀프 호스팅
Docker 배포
TMAN Designer 또는 property.json 수정으로 에이전트를 커스터마이징했다면, 서비스용 Docker 릴리스 이미지를 만들어 배포할 수 있습니다.
Docker 이미지로 배포
주의: 아래 명령은 Docker 컨테이너 밖에서 실행하세요.
이미지 빌드
cd ai_agents
docker build -f agents/examples/<example-name>/Dockerfile -t example-app .실행
docker run --rm -it --env-file .env -p 3000:3000 example-app기타 클라우드 제공업체 배포
TEN을 Vercel이나 Netlify 같은 플랫폼에 호스팅할 때는 백엔드와 프런트엔드를 분리할 수 있습니다.
- Docker를 지원하는 플랫폼(도커 가능한 VM, Fly.io, Render, ECS, Cloud Run 등)에서 TEN 백엔드를 실행합니다. 예시 이미지를 그대로 사용하고 포트
8080을 개방하세요. - 프런트엔드는 Vercel 또는 Netlify에만 배포합니다. 프로젝트 루트를
ai_agents/agents/examples/<example>/frontend로 지정하고pnpm install(또는bun install) 후pnpm build(또는bun run build)를 실행하며 기본.next출력 디렉터리를 유지합니다. - 호스팅 대시보드에서
AGENT_SERVER_URL을 백엔드 주소로 설정하고, 브라우저에서 필요한NEXT_PUBLIC_*환경 변수를 추가합니다(예: Agora 자격 증명). - 백엔드가 프런트엔드 오리진을 허용하도록 CORS를 열거나 기본 제공 프록시 미들웨어를 사용하세요.
이 구조에서는 백엔드가 장기 실행 작업을 처리하고, 호스팅된 프런트엔드는 API 요청을 전달하기만 하면 됩니다.
소식 받기
새 릴리스와 업데이트를 즉시 받아보세요. 여러분의 응원이 TEN을 성장시킵니다!
TEN 에코시스템
| 프로젝트 | 미리보기 |
|---|---|
| ️TEN Framework대화형 AI 에이전트를 위한 오픈소스 프레임워크. |
|
| TEN VAD저지연·경량·고성능 스트리밍 음성 활동 감지. |
|
| ️TEN Turn Detection풀 듀플렉스 대화를 지원하는 턴 감지. |
|
| TEN Agent ExamplesTEN 기반 활용 사례 모음. | |
| TEN Portal문서와 블로그를 제공하는 공식 사이트. |
질문
TEN Framework는 AI 기반 Q&A 플랫폼에서도 만나볼 수 있습니다. 멀티랭귀지를 지원하며 기본 설정부터 고급 구현까지 빠르게 답을 찾을 수 있습니다.
| 서비스 | 링크 |
|---|---|
| DeepWiki | |
| ReadmeX |
기여하기
버그 수정, 기능 추가, 문서 개선, 아이디어 공유 등 모든 형태의 오픈소스 협업을 환영합니다. GitHub Issues와 Projects에서 참여할 작업을 찾아 능력을 보여주세요. 함께 멋진 TEN을 만들어봅시다!
[!TIP]
어떤 형태의 기여든 환영합니다 🙏
코드부터 문서까지, 모든 기여가 TEN을 더 좋게 만듭니다. TEN Agent 프로젝트를 SNS에 공유해 다른 제작자에게 영감을 주세요.
𝕏의 @elliotchen200 또는 GitHub의 @cyfyifanchen에게 연락하면 업데이트, 토론, 협업 기회를 얻을 수 있습니다.
코드 기여자
기여 가이드
기여를 환영합니다! 먼저 기여 가이드를 읽어 주세요.
라이선스
- 아래에 명시된 디렉터리를 제외한 TEN Framework 전체는 추가 제한이 포함된 Apache License 2.0으로 배포됩니다. 루트 디렉터리의 LICENSE를 참고하세요.
packages디렉터리의 구성요소 역시 Apache License 2.0으로 배포되며, 각 패키지 루트의LICENSE파일에서 세부 내용을 확인할 수 있습니다.- 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++ 语言
#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 语言
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 语言
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) errorLogInfo(msg string) errorLogWarn(msg string) errorLogError(msg string) errorLog(level LogLevel, msg string, category *string, fields *Value, option *TenLogOption) error
Node.js/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 | undefinedlogInfo(message: string, category?: string, fields?: Value): TenError | undefinedlogWarn(message: string, category?: string, fields?: Value): TenError | undefinedlogError(message: string, category?: string, fields?: Value): TenError | undefined
使用示例
基本使用
# 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}")在线程中使用
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()错误处理
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
- 函数名: 调用日志的函数名(如果可用)
- 文件名: 调用日志的源文件名(如果可用)
- 行号: 调用日志的代码行号(如果可用)
- 分类: 可选的日志分类信息
- 消息内容: 用户提供的日志消息
输出格式类似:
2025-01-01 12:00:00.123 [INFO] [[email protected]:123] [category] 用户日志消息高级日志配置
TEN framework 支持通过 property.json 文件配置高级日志功能。这允许您自定义日志的输出格式、目标和过滤规则。
配置结构
在 property.json 文件中,您可以在 ten.log 部分定义高级日志配置:
{
"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)
匹配器用于过滤日志消息:
{
"level": "info", // 日志级别: "off", "debug", "info", "warn", "error"
"category": "auth" // 可选:日志分类(字符串)
}支持的日志级别:
"off": 关闭日志"debug": 调试级别"info": 信息级别"warn": 警告级别"error": 错误级别
3. 格式器 (Formatter)
控制日志的输出格式:
{
"type": "plain", // 格式类型: "plain" 或 "json"
"colored": true // 可选:是否启用颜色输出(仅对 plain 格式有效)
}- plain: 人类可读的纯文本格式
- json: JSON 结构化格式,便于日志分析工具处理
4. 发射器 (Emitter)
定义日志的输出目标:
控制台输出
{
"type": "console",
"config": {
"stream": "stdout", // "stdout" 或 "stderr"
"encryption": { // 可选:加密配置
"enabled": true,
"algorithm": "AES-CTR",
"params": {
"key": "your-encryption-key",
"nonce": "your-nonce"
}
}
}
}文件输出
{
"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 等。
{
"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: 多级别日志分离
{
"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: 按分类分离日志
{
"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: 加密日志输出
{
"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 收集器
{
"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"
}
}
}
]
}
}
}配置注意事项
- 多个处理器: 可以定义多个处理器来实现复杂的日志路由策略
- 匹配优先级: 按照配置文件中的顺序匹配处理器
- 加密安全: 加密密钥和 nonce 应该安全存储,不要硬编码在配置文件中
- 文件权限: 确保日志文件路径具有适当的写入权限
- 性能考虑: JSON 格式可能会比 plain 格式产生更大的日志文件
- OTLP 端点:
- 使用 gRPC 协议时,默认端口通常为 4317
- 使用 HTTP 协议时,默认端口通常为 4318
- 确保 OTLP 收集器端点可访问且配置正确
- 认证: 使用 OTLP 时,如果后端需要认证,请在 headers 中添加相应的认证信息
最佳实践
选择合适的日志级别:
- 使用
DEBUG记录详细的调试信息 - 使用
INFO记录正常的业务流程 - 使用
WARN记录潜在问题 - 使用
ERROR记录错误信息
- 使用
提供有意义的日志消息:
- 包含上下文信息(如命令名称、参数值等)
- 使用描述性的消息而不是简单的标识符
- 合理使用 category 参数对日志进行分类
避免过度日志记录:
- 不要在高频循环中使用过多的日志
- 在生产环境中适当调整日志级别
- 使用高级日志配置的匹配器来过滤不必要的日志
线程安全:
- TEN framework 的日志 API 是线程安全的
- 可以在任何线程中安全调用日志方法
高级配置建议:
- 为不同的模块使用不同的 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
#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
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
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) errorLogInfo(msg string) errorLogWarn(msg string) errorLogError(msg string) errorLog(level LogLevel, msg string, category *string, fields *Value, option *TenLogOption) error
Node.js/TypeScript Language
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 | undefinedlogInfo(message: string, category?: string, fields?: Value): TenError | undefinedlogWarn(message: string, category?: string, fields?: Value): TenError | undefinedlogError(message: string, category?: string, fields?: Value): TenError | undefined
Usage Examples
Basic Usage
# 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
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
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")
raiseLog 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:
2025-01-01 12:00:00.123 [INFO] [[email protected]:123] [category] User log messageAdvanced 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:
{
"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:
{
"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:
{
"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
{
"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
{
"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.
{
"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
{
"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
{
"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
{
"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
{
"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
- Multiple Handlers: You can define multiple handlers to implement complex log routing strategies
- Matching Priority: Handlers are matched in the order they appear in the configuration file
- Encryption Security: Encryption keys and nonces should be stored securely, not hardcoded in configuration files
- File Permissions: Ensure log file paths have appropriate write permissions
- Performance Considerations: JSON format may produce larger log files than plain format
- 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
- Authentication: When using OTLP, if the backend requires authentication, add appropriate credentials in the headers
Best Practices
Choose appropriate log levels:
- Use
DEBUGfor detailed debugging information - Use
INFOfor normal business processes - Use
WARNfor potential issues - Use
ERRORfor error information
- Use
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
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
Thread safety:
- The TEN framework's logging API is thread-safe
- Log methods can be safely called from any thread
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
- Read our Code of Conduct - all contributors must follow our community guidelines
- Sign the Contributor License Agreement (CLA) - required for all contributions
- 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
# Fork the repository on GitHub first, then:
git clone https://github.com/YOUR_USERNAME/ten-framework.git
cd ten-framework2. Set Up Development Environment
# 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 run3. Development Workflow
The project uses Task as the build system. Key commands:
# 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
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
- Follow existing patterns: Review similar code before implementing
- Use existing libraries: Check if the codebase already uses needed dependencies
- Security first: Never expose secrets or credentials
- 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:
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
# 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-extensionsWriting 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
- Test your changes: Ensure all tests pass
- Format code: Run
task format - Lint code: Run
task lint - Update docs: Include any necessary documentation updates
- Rebase on main: Ensure your branch is up to date
PR Guidelines
- Clear title: Use descriptive, conventional commit style
- Detailed description: Explain what, why, and how
- Link issues: Reference related issues with "Fixes #123"
- Screenshots: Include for UI changes
- Breaking changes: Clearly document any breaking changes
PR Template
## 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
- Twitter: @elliotchen100
- GitHub: @cyfyifanchen
📜 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 四种语言进行扩展开发的全流程。
开发前准备
环境要求
在开始扩展开发之前,请确保您的开发环境已正确配置。通过以下命令验证安装:
tman --version正常情况下,您应该看到类似以下的版本信息输出:
TEN Framework version: <version>重要提示:请确保您使用的
tman版本 >= 0.10.12,如果版本过低,请参考 快速入门指南 安装最新版本。
开发流程概览
无论使用哪种编程语言,TEN 扩展开发都遵循以下标准化流程:
- 项目创建 - 使用官方模板生成扩展项目骨架
- 依赖安装 - 配置运行环境并安装必要的依赖包
- 核心开发 - 实现扩展的业务逻辑和功能代码
- 构建测试 - 编译项目(如需要)并执行单元测试
- 调试优化 - 使用专业调试工具定位并解决问题
C++ 扩展开发
C++ 扩展适用于对性能要求极高的应用场景,如实时音视频处理、高频数据计算、底层系统操作等。
创建项目
使用 TEN 官方提供的 C++ 扩展模板快速创建新项目:
tman create extension my_example_ext_cpp --template default_extension_cpp项目创建成功后,您会得到以下完整的项目结构:
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 调试配置文件环境配置验证
进入项目目录并验证构建工具是否正常工作:
cd my_example_ext_cpp
tgn --version期望输出:
0.1.0如果命令无法执行,请参考 快速入门指南 检查 TEN Framework 开发环境是否正确安装。
依赖包安装
安装扩展运行时所需的全部依赖包:
tman install --standalone安装成功后,您将看到类似以下的详细安装日志:
📦 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 提供了两种便捷的构建方式供您选择:
方式一:手动分步构建
# 第一步:生成构建配置文件(启用独立测试模式)
tgn gen linux x64 debug -- ten_enable_standalone_test=true
# 第二步:执行项目构建
tgn build linux x64 debug方式二:使用 tman 快捷命令
tman run build构建完成后,检查生成的可执行测试文件:
ls -la bin/
# 应该能看到:my_example_ext_cpp_test运行测试
验证扩展功能是否正常工作:
方式一:直接执行测试文件
./bin/my_example_ext_cpp_test方式二:使用 tman 统一命令
tman run test测试执行成功时的输出示例:
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 基类,并实现完整的生命周期管理方法:
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 专用测试框架编写完整的单元测试:
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 集成调试
- 安装 VSCode 的 CodeLLDB 扩展插件
- 在源代码中设置断点
- 选择 "standalone test (lldb, launch)" 调试配置
- 按 F5 键启动调试会话
调试配置文件 .vscode/launch.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 调试器进行命令行调试:
gdb ./bin/my_example_ext_cpp_test
(gdb) run
(gdb) bt # 查看完整的函数调用栈Go 扩展开发
Go 扩展在高性能和开发效率之间提供了很好的平衡,特别适合构建网络服务、并发处理、微服务架构等应用场景。
创建项目
使用 Go 扩展模板创建新项目:
tman create extension my_example_ext_go --template default_extension_go --template-data class_name_prefix=Example项目创建成功后会显示:
🏆 Package 'extension:my_example_ext_go' created successfully in '/path/to/your/project' in 1 second.完整的项目结构如下:
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 调试配置依赖包安装
安装项目运行所需的依赖包:
tman install --standalone运行测试
验证扩展功能的正确性:
方式一:使用启动脚本
./tests/bin/start方式二:使用 tman 命令
tman run test测试成功执行的输出示例:
=== RUN TestBasicExtensionTester
--- PASS: TestBasicExtensionTester (0.01s)
PASS核心代码结构详解
扩展实现(extension.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 扩展的测试框架实现:
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 1.18
use (
.
.ten/app/ten_packages/system/ten_runtime_go/interface
)调试环境配置
VSCode 集成调试
确保已安装 Go 官方扩展,然后使用以下调试配置:
{
"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 异步扩展模板创建项目:
tman create extension my_example_ext_python --template default_async_extension_python --template-data class_name_prefix=Example完整的项目结构:
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 依赖包:
tman install --standalone运行测试
验证 Python 扩展的功能:
方式一:使用启动脚本
./tests/bin/start方式二:使用 tman 命令
tman run test测试成功执行的输出示例:
============================================ 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 扩展推荐使用现代异步编程模式,以获得更好的性能和并发处理能力:
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)
扩展插件的注册和创建逻辑:
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)
完整的异步测试框架实现:
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 调试器,使用以下配置进行调试:
{
"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 扩展模板快速创建新项目:
tman create extension my_example_ext_nodejs --template default_extension_nodejs --template-data class_name_prefix=Example项目创建成功后,您会得到以下完整的项目结构:
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 调试配置文件环境配置验证
进入项目目录并验证开发环境:
cd my_example_ext_nodejs
node --version
npm --version期望输出:
v18.0.0 或更高版本
8.0.0 或更高版本依赖包安装
安装扩展运行时所需的全部依赖包:
tman install --standalone安装成功后,您将看到类似以下的详细安装日志:
📦 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:
方式一:手动构建
# 进入扩展的安装目录
cd .ten/app/ten_packages/extension/my_example_ext_nodejs
# 安装独立模式的 Node.js 依赖
npm run standalone-install
# 编译 TypeScript 代码
npm run build方式二:使用 tman 快捷命令
tman run build构建完成后,检查生成的编译结果:
ls -la .ten/app/ten_packages/extension/my_example_ext_nodejs/build/
# 应该能看到:index.js 和相关映射文件运行测试
验证扩展功能是否正常工作:
方式一:使用启动脚本
# 进入扩展的安装目录
cd .ten/app/ten_packages/extension/my_example_ext_nodejs
# 运行测试
./tests/bin/start方式二:使用 tman 统一命令
tman run test测试执行成功时的输出示例:
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 基类,并实现完整的生命周期管理方法:
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");
}
}插件注册入口
扩展插件的注册和创建逻辑:
@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 专用测试框架编写完整的单元测试:
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 测试框架编写具体的测试用例:
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 配置,支持最新的语言特性:
{
"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 官方扩展,然后使用以下调试配置:
{
"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 内置调试器进行命令行调试:
# 进入扩展的安装目录
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 扩展开发流程总结:
# 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 扩展充分利用异步编程模式,提供更好的性能:
// 推荐:使用 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);
}
}错误处理
实现完善的错误处理机制:
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:
tman --versionUnder normal circumstances, you should see version information output similar to the following:
TEN Framework version: <version>Important Note: Please ensure you are using
tmanversion >= 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:
- Project Creation - Generate extension project skeleton using official templates
- Dependency Installation - Configure runtime environment and install necessary dependency packages
- Core Development - Implement extension business logic and functional code
- Build and Test - Compile project (if needed) and execute unit tests
- 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:
tman create extension my_example_ext_cpp --template default_extension_cppAfter successful project creation, you will get the following complete project structure:
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 fileEnvironment Configuration Verification
Enter the project directory and verify that the build tools are working properly:
cd my_example_ext_cpp
tgn --versionExpected Output:
0.1.0If 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:
tman install --standaloneAfter successful installation, you will see detailed installation logs similar to the following:
📦 Get all installed packages...
🔍 Filter compatible packages...
🔒 Creating manifest-lock.json...
📥 Installing packages...
[00:00:00] [########################################] 3/3 Done
🏆 Install successfully in 1 secondProject Build
TEN framework provides two convenient build methods for you to choose from:
Method 1: Manual Step-by-step Build
# 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 debugMethod 2: Use tman Shortcut Command
tman run buildAfter the build is complete, check the generated executable test file:
ls -la bin/
# You should see: my_example_ext_cpp_testRun Tests
Verify that the extension functionality is working properly:
Method 1: Execute Test File Directly
./bin/my_example_ext_cpp_testMethod 2: Use tman Unified Command
tman run testExample output when tests execute successfully:
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:
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:
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
- Install the CodeLLDB extension plugin for VSCode
- Set breakpoints in source code
- Select the "standalone test (lldb, launch)" debug configuration
- Press F5 to start debugging session
Debug configuration file .vscode/launch.json content:
{
"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:
gdb ./bin/my_example_ext_cpp_test
(gdb) run
(gdb) bt # View complete function call stackGo 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:
tman create extension my_example_ext_go --template default_extension_go --template-data class_name_prefix=ExampleAfter successful project creation, it will display:
🏆 Package 'extension:my_example_ext_go' created successfully in '/path/to/your/project' in 1 second.The complete project structure is as follows:
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 configurationDependency Package Installation
Install dependency packages required for project runtime:
tman install --standaloneRun Tests
Verify the correctness of extension functionality:
Method 1: Use Startup Script
./tests/bin/startMethod 2: Use tman Command
tman run testExample output when tests execute successfully:
=== RUN TestBasicExtensionTester
--- PASS: TestBasicExtensionTester (0.01s)
PASSCore Code Structure Explanation
Extension Implementation (extension.go)
Core implementation structure of Go extensions:
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:
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 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:
{
"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:
tman create extension my_example_ext_python --template default_async_extension_python --template-data class_name_prefix=ExampleComplete project structure:
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 configurationDependency Package Installation
Install Python dependency packages required for the project:
tman install --standaloneRun Tests
Verify Python extension functionality:
Method 1: Use Startup Script
./tests/bin/startMethod 2: Use tman Command
tman run testExample output when tests execute successfully:
============================================ 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:
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 shutdownPlugin Registration Entry (addon.py)
Extension plugin registration and creation logic:
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:
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:
{
"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:
tman create extension my_example_ext_nodejs --template default_extension_nodejs --template-data class_name_prefix=ExampleAfter successful project creation, you will get the following complete project structure:
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 fileEnvironment Configuration Verification
Enter the project directory and verify the development environment:
cd my_example_ext_nodejs
node --version
npm --versionExpected Output:
v18.0.0 or higher
8.0.0 or higherDependency Package Installation
Install all dependency packages required for extension runtime:
tman install --standaloneAfter successful installation, you will see detailed installation logs similar to the following:
📦 Get all installed packages...
🔍 Filter compatible packages...
🔒 Creating manifest-lock.json...
📥 Installing packages...
[00:00:00] [########################################] 2/2 Done
🏆 Install successfully in 1 secondImportant Note:
tman install --standalonewill 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
# 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 buildMethod 2: Use tman Shortcut Command
tman run buildAfter the build is complete, check the generated compilation results:
ls -la .ten/app/ten_packages/extension/my_example_ext_nodejs/build/
# You should see: index.js and related mapping filesRun Tests
Verify that the extension functionality is working properly:
Method 1: Use Startup Script
# Enter the extension installation directory
cd .ten/app/ten_packages/extension/my_example_ext_nodejs
# Run tests
./tests/bin/startMethod 2: Use tman Unified Command
tman run testExample output when tests execute successfully:
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 displayingpassing, 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:
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:
@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:
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:
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:
{
"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:
{
"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:
# 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/startComplete Development Process Summary
To help developers get started quickly, here is a complete Node.js extension development process summary:
# 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/startWorking 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:
// 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:
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
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/activateWindows:
从 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 --versionpowershell# 推荐安装后使用 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+
go version
# 应显示: go version go1.20 或更高版本
$env:CGO_ENABLED = "1"
# Windows下需要显式启用CGONode.js / npm
node --version
npm --version
# 确保 node 和 npm 命令可用GCC (MinGW)
💡仅在windows上需要安装
# 首先确定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):
sudo add-apt-repository ppa:ten-framework/ten-framework
sudo apt update
sudo apt install tmanmacOS:
brew install TEN-framework/ten-framework/tmanWindows:
winget install TEN-framework.tman方式二:通过安装脚本
Linux / macOS:
bash <(curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.sh)Windows:
irm https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.ps1 | iex方式三:如果你已经克隆了仓库,使用仓库内的安装脚本
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
验证安装:
tman --version💡 提示:如果出现类似
tman: command not found(Linux/macOS)
无法将"tman"项识别为 cmdlet、函数、脚本文件或可运行程序的名称(Windows)的提示,请确保 tman 所在目录在你的 PATH 中:
Linux / macOS:
bashecho 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc # Linux echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc # macOS source ~/.bashrc # 或 source ~/.zshrcWindows:
powershell# 检查 PATH 中是否包含 tman $env:Path -split ";" | Select-String "tman" # 如果不包含,请 # 1. 通过 "系统属性 → 环境变量" 手动添加,然后重启终端以应用 # 2. 运行以下命令来临时添加路径 $env:PATH += ";$env:LOCALAPPDATA\tman"
第三步:创建并运行示例应用
1. 创建应用
# 创建一个新的 transcriber_demo 应用
tman install app transcriber_demo
cd transcriber_demo2. 安装依赖
# 安装 TEN 包依赖
tman install
# 安装 Python 和 npm 包的依赖
tman run install_deps⏱️ 预计时间:1-2 分钟
3. 构建应用
tman run build⏱️ 预计时间:30 秒
4. 配置环境变量
在运行应用前,需要配置 ASR(语音识别)服务的密钥。当前示例使用 Azure ASR extension,你需要在 transcriber_demo/.env 文件中填写相关配置:
Linux / macOS:
# 创建 .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 等
EOFWindows(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. 运行应用
tman run start如果一切正常,你应该看到类似以下的输出:
[web_audio_control_go] Web server started on port 8001
[audio_file_player_python] AudioFilePlayerExtension on_start6. 体验 Demo
打开浏览器访问:
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)
🎯 你已经可以运行这些多语言插件了!
下一步
现在你可以:
从云商店探索和下载更多插件,设计和编排你的应用
bashtman designer # 启动 TMAN Designer,在云商店中探索插件、下载插件并设计编排你的应用选择一个语言,开发自己的插件
- 支持 Go、Python、TypeScript/JavaScript、C++ 等多种语言
- 查看 TEN 扩展开发完整指南 了解详情
进阶:开发和构建 C++ 插件
如果你想开发和使用 C++ 扩展,推荐安装 TEN 构建工具链(tgn)。以下是完整的步骤:
1. 安装 tgn 构建工具
tgn 是 TEN Framework 的 C/C++ 构建系统,基于 Google 的 GN。
方式一:一键安装(推荐)
Linux / macOS:
curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tgn/install_tgn.sh | bashWindows:
irm https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tgn/install_tgn.ps1 | iex方式二:从克隆的仓库安装
Linux / macOS:
# 如果你已经克隆了 TEN Framework 仓库
cd ten-framework
bash tools/tgn/install_tgn.shWindows:
# 如果你已经克隆了 TEN Framework 仓库
& ".\tools\tgn\install_tgn.ps1"安装完成后,确认 tgn 已添加到 PATH:
Linux / macOS:
# 临时添加到当前会话
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 ~/.zshrcWindows:
# 安装脚本会自动添加到用户 PATH,如需手动添加:
$env:Path += ";$env:LOCALAPPDATA\ten_gn"验证安装:
tgn --help2. 安装 C++ 扩展
以 WebRTC VAD(语音活动检测)扩展为例,从云商店安装 C++ 扩展:
cd transcriber_demo
tman install extension webrtc_vad_cpp💡 提示:
webrtc_vad_cpp是一个用 C++ 实现的语音活动检测扩展,可以在实时语音识别场景中筛选出语音部分。
3. 编译 C++ 扩展
安装 C++ 扩展后,需要重新构建应用以编译 C++ 代码为动态库:
tman run build⏱️ 预计时间:首次编译 C++ 扩展可能需要 1-3 分钟,具体取决于你的机器性能。
4. 运行带有 VAD 功能的应用
tman run start_with_vad如果一切正常,你应该看到:
[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:
# Ubuntu/Debian
sudo apt-get install gcc g++
# 或使用 clang
sudo apt-get install clangmacOS:
# 安装 Xcode Command Line Tools (包含 clang)
xcode-select --installWindows:
# 方式一:安装 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工具”验证编译器安装:
# 检查 gcc(Linux/macOS/MSYS2)
gcc --version
g++ --version
# 或检查 clang(Linux/macOS)
clang --versionWindows(使用 MSVC):
# 在 "Developer PowerShell for VS" 中执行
clang-cl --version常见问题(C++ 扩展)
tgn 命令找不到
确保已经执行安装脚本并将 tgn 添加到 PATH:
bashexport PATH="/usr/local/ten_gn:$PATH"编译失败:找不到编译器
请参考上面的"C++ 开发环境要求"部分安装编译器。
了解更多
- ten_gn 构建系统 - TEN 的 C/C++ 构建工具
- C++ 扩展开发指南 - 完整的 C++ 扩展开发文档
常见问题
1. macOS 上 Python 库加载失败
问题:运行应用时提示找不到 libpython3.10.dylib
解决方案:
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:
- 杀掉该进程:
- Linux/macOS:
kill -9 - Windows:
taskkill /PID /F
- Linux/macOS:
- 或修改应用配置文件(
transcriber_demo/ten_packages/extension/web_audio_control_go/property.json)中的端口号
4. Go build 失败
问题:构建时提示 Go module 相关错误
解决方案:
# 设置代理
# 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 build5. Python 依赖安装失败
问题:pip 安装超时或失败
解决方案:使用国内镜像源
pip3 install --index-url https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt6. Azure语音服务报错
问题:Azure语音服务相关错误,比如认证失败
解决方案:检查.env文件中的配置是否正确,确保 AZURE_STT_KEY 和 AZURE_STT_REGION 填写无误
7. Windows下权限问题
问题:Windows下在访问文件时报PermissionError
解决方案:右键点击PowerShell,选择“以管理员身份运行”
获取帮助
- GitHub Issues:https://github.com/TEN-framework/ten-framework/issues
- 文档:https://theten.ai/cn/docs
- 贡献指南:contributing.md
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
python3 --version
# Should display: Python 3.10.x💡 Important: TEN Framework currently only supports Python 3.10. It's recommended to use
pyenvorvenvto 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/activateWindows:
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 --versionpowershell# 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+
go version
# Should display: go version go1.20 or higher
$env:CGO_ENABLED = "1"
# CGO must be explicitly enabled on WindowsNode.js / npm
node --version
npm --version
# Ensure node and npm commands are availableGCC (MinGW)
💡 Only required on Windows
# 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):
sudo add-apt-repository ppa:ten-framework/ten-framework
sudo apt update
sudo apt install tmanmacOS:
brew install TEN-framework/ten-framework/tmanWindows:
winget install TEN-framework.tmanOption 2: Install via Script
Linux / macOS:
bash <(curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.sh)Windows:
irm https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tman/install_tman.ps1 | iexOption 3: Install from cloned repository
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
yto continue ornto 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:
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:
bashecho 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc # Linux echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc # macOS source ~/.bashrc # or source ~/.zshrcWindows:
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
# Create a new transcriber_demo app
tman install app transcriber_demo
cd transcriber_demo2. Install Dependencies
# 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
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:
# 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.
EOFWindows (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
.envfile 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
.envfile.
5. Run the App
tman run startIf everything is working correctly, you should see output similar to:
[web_audio_control_go] Web server started on port 8001
[audio_file_player_python] AudioFilePlayerExtension on_start6. Experience the Demo
Open your browser and visit:
http://localhost:8001You 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:
Explore and download more extensions from the cloud store, design and orchestrate your app
bashtman designer # Launch TMAN Designer to explore extensions, download them, and design your appChoose a language and develop your own extension
- Supports Go, Python, TypeScript/JavaScript, C++, and more
- Check out the TEN Extension Development Guide for details
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:
curl -fsSL https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tgn/install_tgn.sh | bashWindows:
irm https://raw.githubusercontent.com/TEN-framework/ten-framework/main/tools/tgn/install_tgn.ps1 | iexOption 2: Install from Cloned Repository
Linux / macOS:
# If you've already cloned the TEN Framework repository
cd ten-framework
bash tools/tgn/install_tgn.shWindows:
# If you've already cloned the TEN Framework repository
& ".\tools\tgn\install_tgn.ps1"After installation, ensure tgn is added to PATH:
Linux / macOS:
# 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 ~/.zshrcWindows:
# The install script adds to user PATH automatically. To add manually:
$env:Path += ";$env:LOCALAPPDATA\ten_gn"Verify installation:
tgn --help2. Install C++ Extension
Using WebRTC VAD (Voice Activity Detection) extension as an example, install a C++ extension from the cloud store:
cd transcriber_demo
tman install extension webrtc_vad_cpp💡 Note:
webrtc_vad_cppis 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:
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
tman run start_with_vadIf everything works correctly, you should see:
[web_audio_control_go] Web server started on port 8001
[vad] WebRTC VAD initialized with mode 2
[audio_file_player_python] AudioFilePlayerExtension on_startNow 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:
# Ubuntu/Debian
sudo apt-get install gcc g++
# Or use clang
sudo apt-get install clangmacOS:
# Install Xcode Command Line Tools (includes clang)
xcode-select --installWindows:
# 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:
# Check gcc (Linux/macOS/MSYS2)
gcc --version
g++ --version
# Or check clang (Linux/macOS)
clang --versionWindows (MSVC):
# Run in "Developer PowerShell for VS"
clang-cl --versionTroubleshooting (C++ Extensions)
tgn command not found
Ensure you've run the installation script and added tgn to PATH:
bashexport PATH="/usr/local/ten_gn:$PATH"Compilation failed: Compiler not found
Please refer to the "C++ Development Environment Requirements" section above to install the compiler.
Learn More
- ten_gn Build System - TEN's C/C++ build tool
- C++ Extension Development Guide - Complete C++ extension development documentation
Troubleshooting
1. Python Library Loading Failure on macOS
Problem: Error indicating libpython3.10.dylib cannot be found when running the app
Solution:
export DYLD_LIBRARY_PATH=/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.10/lib:$DYLD_LIBRARY_PATHConsider 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
tmanbinary 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
- Linux/macOS:
- Kill the process:
- Linux/macOS:
kill -9 - Windows:
taskkill /PID /F
- Linux/macOS:
- 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:
# 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 build5. Python Dependencies Installation Failed
Problem: pip installation timeout or failure
Solution: Use a mirror source (for users in China)
pip3 install --index-url https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt6. 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
- GitHub Issues: https://github.com/TEN-framework/ten-framework/issues
- Documentation: https://theten.ai/docs
- Contributing Guide: contributing.md
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 快速入门指南 完成基础环境安装,确保以下命令通过检查:
tman check env预期输出(至少 Python 和 Go 为 Ready):
✅ Operating System: Supported
✅ Python: Ready
✅ Go: Ready额外工具
安装以下工具用于构建和运行:
Task(任务运行器)
Linux / macOS:
go install github.com/go-task/task/v3/cmd/task@latest
task --version # 验证安装Windows:
# 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:
curl -fsSL https://bun.sh/install | bash
bun --version # 验证安装Windows:
powershell -c "irm bun.sh/install.ps1 | iex"
bun --version # 验证安装uv(Python 包管理器)
Linux / macOS:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version # 验证安装Windows:
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
uv --version # 验证安装💡 安装后需要重新加载终端配置:
- Linux/macOS:
source ~/.zshrc- Windows:重新打开 PowerShell 终端
快速开始
1. 克隆仓库
git clone https://github.com/TEN-framework/ten-framework.git
cd ten-framework/ai_agents/agents/examples/websocket-example2. 安装依赖
task install⏱️ 预计耗时:2-3 分钟
该命令会自动完成:
- 安装 TEN 扩展包(45+ 个)
- 编译 Go 应用和 API 服务器
- 安装 Python 和前端依赖
3. 配置 API Keys
在 ai_agents/.env 文件中配置服务商密钥:
Linux / macOS:
cd ../../../ # 回到 ai_agents 目录
vim .envWindows:
cd ..\..\.. # 回到 ai_agents 目录
notepad .env从.env.example复制所有内容后,填写以下配置:
# 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_key4. 启动服务
cd agents/examples/websocket-example
task run服务启动后,自动运行:
- 前端 UI:http://localhost:3000
- API 服务器:http://localhost:8080
- TMAN Designer:http://localhost:49483
5. 与助手对话
打开浏览器访问 http://localhost:3000,点击麦克风按钮开始语音对话。
尝试以下对话:
- "你好,你是谁?"
- "今天北京的天气怎么样?"(需配置 WeatherAPI)
- "给我讲个笑话"
🎉 恭喜! 您已成功运行 TEN Agent 语音助手。
进阶配置
更换扩展
TEN Framework 支持灵活替换 ASR、LLM、TTS 等扩展。
方式 1:使用 TMAN Designer(推荐)
访问 http://localhost:49483,通过可视化界面:
- 点击扩展节点查看详情
- 选择替代扩展
- 配置参数
- 保存并重启
方式 2:编辑配置文件
编辑 tenapp/property.json,修改 predefined_graphs 中的扩展配置。
示例:更换 ASR 为 Azure
{
"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
{
"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:
cd tools/grafana-monitoring
docker compose -f docker-compose.push.yml up -dWindows:
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 数组中添加:
{
"matchers": [{ "level": "info" }],
"formatter": { "type": "json", "colored": false },
"emitter": {
"type": "otlp",
"config": {
"endpoint": "http://localhost:4317",
"service_name": "websocket-example"
}
}
}配置 Metrics 输出
在 ten.services 中添加:
{
"telemetry": {
"enabled": true,
"metrics": {
"enabled": true,
"exporter": {
"type": "otlp",
"config": {
"endpoint": "http://localhost:4317",
"service_name": "websocket-example"
}
}
}
}
}3. 重启并访问 Grafana
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:
cd tools/grafana-monitoring
docker compose -f docker-compose.push.yml downWindows:
cd tools\grafana-monitoring
docker compose -f docker-compose.push.yml down常见问题
前端依赖安装失败
如果 bun install 报错版本不存在,切换到 npm 官方源:
cd frontend
echo "registry=https://registry.npmjs.org/" > .npmrc
rm bun.lock
bun installmacOS Python 库加载失败
设置 Python 动态库路径:
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 中:
# 检查 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:
lsof -i :3000 # 或 :8080
kill -9 <PID>Windows:
netstat -ano | findstr :3000 # 或 :8080
taskkill /PID <PID> /FGo module相关错误
# 设置代理
# 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 应用带入真实世界。
- 扩展开发指南 - 开发自定义扩展
- 更多示例 - 探索其他 Agent 示例
- GitHub Issues - 报告问题或请求功能
- 文档中心 - 完整的 TEN Framework 文档
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:
tman check envExpected output (at least Python and Go are Ready):
✅ Operating System: Supported
✅ Python: Ready
✅ Go: ReadyExtra tools
Install the following tools for build & run:
Task (task runner)
Linux / macOS:
go install github.com/go-task/task/v3/cmd/task@latest
task --version # verifyWindows:
# 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 # verifyBun (JavaScript package manager)
Linux / macOS:
curl -fsSL https://bun.sh/install | bash
bun --version # verifyWindows:
powershell -c "irm bun.sh/install.ps1 | iex"
bun --version # verifyuv (Python package manager)
Linux / macOS:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version # verifyWindows:
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
git clone https://github.com/TEN-framework/ten-framework.git
cd ten-framework/ai_agents/agents/examples/websocket-example2. Install dependencies
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:
cd ../../../ # back to ai_agents
vim .envWindows:
cd ..\..\.. # back to ai_agents
notepad .envCopy all content from .env.example, then fill in:
# 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_key4. Start services
cd agents/examples/websocket-example
task runAfter startup, the following services are available:
- Frontend UI: http://localhost:3000
- API server: http://localhost:8080
- TMAN Designer: http://localhost:49483
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.
Option 1: Use TMAN Designer (recommended)
Open http://localhost:49483, then in the visual editor:
- Click an extension node to inspect it
- Choose a replacement extension
- Configure parameters
- 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
{
"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
{
"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:
cd tools/grafana-monitoring
docker-compose -f docker-compose.push.yml up -dWindows:
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 DesktopServices:
- 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:
{
"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:
{
"telemetry": {
"enabled": true,
"metrics": {
"enabled": true,
"exporter": {
"type": "otlp",
"config": {
"endpoint": "http://localhost:4317",
"service_name": "websocket-example"
}
}
}
}
}3. Restart and open Grafana
cd ../../../agents/examples/websocket-example
task runOpen 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_namelabel to filter logs per extension.
Stop the monitoring stack:
Linux / macOS:
cd tools/grafana-monitoring
docker-compose -f docker-compose.push.yml downWindows:
cd tools\grafana-monitoring
docker compose -f docker-compose.push.yml downTroubleshooting
Frontend dependency install fails
If bun install fails due to missing versions, switch to the official npm registry:
cd frontend
echo "registry=https://registry.npmjs.org/" > .npmrc
rm bun.lock
bun installmacOS Python library loading fails
Set the Python dynamic library path:
export DYLD_LIBRARY_PATH=/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.10/lib:$DYLD_LIBRARY_PATH
# Add to ~/.zshrc for persistenceWindows Python library loading fails
Make sure the Python 3.10 installation path is in your system PATH:
# 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:
lsof -i :3000 # or :8080
kill -9 <PID>Windows:
netstat -ano | findstr :3000 # or :8080
taskkill /PID <PID> /FGo module related errors
# 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 installNext steps
- Extension development guide - build custom extensions
- More examples - explore other agent demos
- GitHub Issues - report bugs or request features
- Documentation hub - full TEN Framework documentation
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
cd /home/ubuntu/ten-framework/ai_agents
docker compose up -d
docker ps | grep ten_agent_dev # Verify runningContainer image: ghcr.io/ten-framework/ten_agent_build:0.7.14
Permissions: if your user is not in the docker group, prefix everydocker / 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 tosudo 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
# 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-assistantfor standard vendor iteration and demo graphsvoice-assistant-advancedfor 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
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):
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
# 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"Related Deep Dives
- 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:
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).
{
"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
┌─────────────────┐ ┌──────────────────┐
│ 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"propertyremote_stream_id,bot_stream_id,token→ injected viastartPropMapreq.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
Client (Browser/Mobile)
│
▼
┌──────────────┐
│ Playground │ Next.js frontend (port 3000)
│ (UI) │
└──────┬───────┘
│ REST API
▼
┌──────────────┐ ┌──────────────────────────────────┐
│ Go Server │──spawn─▶│ Worker Process │
│ (port 8080) │ │ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ │ │ ASR │─▶│ LLM │─▶│ TTS │ │
│ │ │ └──┬──┘ └─────┘ └──┬──┘ │
└──────────────┘ │ │ │ │
│ ┌──┴───────────────────┴──┐ │
│ │ Agora RTC │ │
│ └─────────────────────────┘ │
└──────────────────────────────────┘Related Deep Dives
- Server Architecture — Go server internals, property injection pipeline
- Graph Configuration — Node schema, connection wiring, parallel routing
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/:
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 variablesOther 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:
{"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 |
Related Deep Dives
- Extension Development — Full creation guide with base class details
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:
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:
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():
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 |
{"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:
- Store
api_keyinsideparamsdict in property.json and config - Extract for authentication headers in the client constructor
- Keep convenience fields if needed, but preserve vendor params in config
- Strip secrets only when creating the HTTP payload or WS query
# 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:
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:pythonten_env.log_info(f"config: {self.config.to_str(sensitive_handling=True)}", category=LOG_CATEGORY_KEY_POINT) - All output goes to
/tmp/task_run.loginside 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:
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
# Correct (v0.11+)
from ten_runtime import Addon, register_addon_as_extension, TenEnv
# Wrong (old v0.8.x — will not work)
from ten import AddonFormatting
- Black formatter with
--line-length 80 - Run:
task format(fromai_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)
andtask 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:bashsudo 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/oragents/examples/and beforegit push.task lintis strict — even
a singleW0611: unused-importwarning 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 withgit commit -m "..."is a single >100-char line and
failsbody-max-line-length. Hard-wrap the body (≤72–80 cols is safe),
e.g. write it in a file and usegit commit -F msg.txt. - Blank line between subject and body (
body-leading-blank).
Quick self-check before pushing (every commit added in the PR):
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"}'
doneIf 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/)
Related Deep Dives
- Extension Development — Full creation guide with implementation walkthroughs
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 |
cp -r agents/ten_packages/extension/<existing_tts> \
agents/ten_packages/extension/my_vendor_ttsThen:
- Rename addon decorator, class names,
manifest.jsonnamefield - Keep vendor config in
params; extract secrets for auth and forward the rest to the vendor request layer - Implement the abstract methods for your vendor API
- Create
tests/configs/with required config files (see below) - Run guarder tests:
task tts-guarder-test EXTENSION=my_vendor_tts - 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.jsonif 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. ItsMUST 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:
- Verify the vendor wire contract first: endpoint, event names, payload encoding, finalize primitive.
- Copy a recent extension with the same transport shape.
- Implement config loading, secret redaction, and
config:logging. - Implement error classification and
vendor_info. - Add standalone tests before running guarders.
- Run guarders sequentially, not in parallel, inside the same container.
- Add README and example graph wiring only after the extension tests are green.
Add Extension to a Graph
Add node to
predefined_graphs[].graph.nodes[]in the example'stenapp/property.json:json{"type": "extension", "name": "my_tts", "addon": "my_tts_python", "extension_group": "tts", "property": {"params": {"api_key": "${env:MY_API_KEY}"}}}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"}]}]}Add dependency to example
tenapp/manifest.json(local extensions use path):json{"path": "../../../ten_packages/extension/my_tts_python"}Install with
task install(not baretman install— the latter can wipebin/worker).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), userebuild_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
# 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:
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 underagents/ten_packages/extension/<ext>/:
# 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 isbody-max-line-length (≤100 chars per body line) — hard-wrap the body and usegit 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 priortman install --standalone) makestask checkreport spurious reformatting
and breaks the next standalone install with a symlink error. Remove it
(rm -rf <ext>/.ten) before re-running.
Related Deep Dives
- Extension Development — Full extension creation with code examples
- Graph Configuration — Connection wiring and routing patterns
- Testing — Test infrastructure, guarder tests, debugging
- Operations and Restarts — Full restart procedures, recovery
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
{
"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 propertiestimeout— seconds of inactivity before auto-stop (-1 = never)
Graph Connection Types
Connections in property.json define data flow between extensions:
Command Connections (`cmd`)
{"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`)
{"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`)
{"extension": "agora_rtc", "audio_frame": [
{"name": "pcm_frame", "dest": [{"extension": "stt"}]}
]}Video Frame Connections (`video_frame`)
{"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 inavailable_tools → LLM calls tools during completion → results returned.
Manifest API Interface
Extensions declare their API interface in manifest.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: keepsession_idinsidemetadata, never as a top-level
field. Locked-interim vs final lives atmetadata.asr_info.locked. - Error payloads: include
vendor_info(vendor name + vendor code + vendor
message) whenever the failure originated upstream. - ASR
connect_delaymetrics are valid beforesession_idis known; other
metrics should wait.
See Extension Development for full example payloads.
Portal References
- API Events Reference [EXTERNAL] — REST event types and payloads
- API Schemas Reference [EXTERNAL] — request/response schema definitions
Related Deep Dives
- Extension Development — Implementing abstract methods
- Server Architecture — Endpoint handlers and property injection
- Graph Configuration — Full connection wiring examples
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.
# 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_resultThis 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.
# 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 mustdocker 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 afterdocker 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 withbin/worker: No such file or directory in logs.
tman Install Creates Symlinks
Never manually ln -s for extensions. Use tman install which resolves
dependencies and creates correct links. If a symlink is missing aftertman 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'sresponse_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 makesynthesize_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 betweenaiter_bytes()chunks (seeFloat32ToPcm16inmistral_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 Symlink Drift
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:
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. CheckNEXT_PUBLIC_EDIT_GRAPH_MODE; production deployments should typically set it tofalse.
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:
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
Related Deep Dives
- Operations and Restarts — Full restart procedures, port debugging, recovery
- Deployment — Production setup, persistent startup
- Server Architecture — Worker lifecycle, session management
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.examplefor the complete variable catalog
Environment Variable Substitution
In property.json, reference secrets via:
{
"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:
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
Related Deep Dives
- Deployment — Production security considerations
- Server Architecture — Server-side validation details
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
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
h2 quic.tech 8443 h3-22 quic.tech 8443 "20190808 06:18:37" 0 0Fields
- The ALPN id for the source origin
- The host name for the source origin
- The port number for the source origin
- The ALPN id for the destination host
- The host name for the destination host
- The host number for the destination host
- 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.
- Boolean (1 or 0) if "persist" was set for this entry
- 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_IMMEDIATELYsupport
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
Ferite Written by Paul Querna
Fortran Written by Philipp Engel
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
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
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.
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_AGAINTo pass data into a bufq without an extra copy, read callbacks can be used.
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:
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:
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:
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:
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:
void Curl_bufq_free(struct bufq *q);to free all resources held by q. It is possible to reset a bufq to empty via:
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
bufqwith 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:
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 formax_sparechunks in the pool is used. Emptybufqs will hold no memory. - the latest spare chunk is the first to be handed out again, no matter which
bufqneeds 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`
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`
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`
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`
CURLcode Curl_bufref_memdup(struct bufref *br, const void *data, size_t length);Releases the previously referenced buffer, then duplicates the length-bytedata 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`
const unsigned char *Curl_bufref_ptr(const struct bufref *br);Returns a const unsigned char * to the referenced buffer.
`len`
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 -Vis 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
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.
checksrc.pl -hLists 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 likechar* nameinstead of the
more appropriatechar *namestyle. The asterisk should sit next to the
name.ASTERISKSPACE: A pointer was declared likechar * nameinstead of the
more appropriatechar *namestyle. The asterisk should sit right next to
the name without a space in between.BADCOMMAND: There's a badchecksrcinstruction 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 keywordCOMMANOSPACE: a comma without following spaceCOPYRIGHT: the file is missing a copyright statementCPPCOMMENTS://comment detected, that is not C89 compliantDOBRACE: only use one space after do before open braceEMPTYLINEBRACE: found empty line before open braceEQUALSNOSPACE: no space after=signEQUALSNULL: comparison with== NULLused in if/while. We use!var.EXCLAMATIONSPACE: space found after exclamations markFOPENMODE:fopen()needs a macro for the mode string, use itINDENTATION: 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 prefera = 2and nota=2.NOTEQUALSZERO: check found using!= 0. We use plainif(var).ONELINECONDITION: do not put the conditional block on the same line asif()OPENCOMMENT: File ended with a comment (/*) still "open".PARENBRACE:){was used without sufficient space in between.RETURNNOSPACE:returnwas 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 prefersizeof(int)style.SNPRINTF- Found use ofsnprintf(). Since we use an internal replacement
with a different return code etc, we prefermsnprintf().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 expectedSPACESEMICOLON: there was a space before semicolon,;.TABS: TAB characters are not allowedTRAILINGSPACE: Trailing whitespace on the lineTYPEDEFSTRUCT: we frown upon (most) typedefed structsUNUSEDIGNORE: achecksrcinlined 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 fileSTRERROR: 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
/* !checksrc! disable LONGLINE all */This will ignore the warning for overly long lines until it is re-enabled with:
/* !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:
/* !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 optionsCURLOPT_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 optionsCURLOPT_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-MD5NULL-SHARC4-MD5RC4-SHAIDEA-CBC-SHADES-CBC3-SHADH-DSS-DES-CBC3-SHADH-RSA-DES-CBC3-SHADHE-DSS-DES-CBC3-SHADHE-RSA-DES-CBC3-SHAADH-RC4-MD5ADH-DES-CBC3-SHA
TLS v1.0 cipher suites
NULL-MD5NULL-SHARC4-MD5RC4-SHAIDEA-CBC-SHADES-CBC3-SHADHE-DSS-DES-CBC3-SHADHE-RSA-DES-CBC3-SHAADH-RC4-MD5ADH-DES-CBC3-SHA
AES cipher suites from RFC3268, extending TLS v1.0
AES128-SHAAES256-SHADH-DSS-AES128-SHADH-DSS-AES256-SHADH-RSA-AES128-SHADH-RSA-AES256-SHADHE-DSS-AES128-SHADHE-DSS-AES256-SHADHE-RSA-AES128-SHADHE-RSA-AES256-SHAADH-AES128-SHAADH-AES256-SHA
SEED cipher suites from RFC4162, extending TLS v1.0
SEED-SHADH-DSS-SEED-SHADH-RSA-SEED-SHADHE-DSS-SEED-SHADHE-RSA-SEED-SHAADH-SEED-SHA
GOST cipher suites, extending TLS v1.0
GOST94-GOST89-GOST89GOST2001-GOST89-GOST89GOST94-NULL-GOST94GOST2001-NULL-GOST94
Elliptic curve cipher suites
ECDHE-RSA-NULL-SHAECDHE-RSA-RC4-SHAECDHE-RSA-DES-CBC3-SHAECDHE-RSA-AES128-SHAECDHE-RSA-AES256-SHAECDHE-ECDSA-NULL-SHAECDHE-ECDSA-RC4-SHAECDHE-ECDSA-DES-CBC3-SHAECDHE-ECDSA-AES128-SHAECDHE-ECDSA-AES256-SHAAECDH-NULL-SHAAECDH-RC4-SHAAECDH-DES-CBC3-SHAAECDH-AES128-SHAAECDH-AES256-SHA
TLS v1.2 cipher suites
NULL-SHA256AES128-SHA256AES256-SHA256AES128-GCM-SHA256AES256-GCM-SHA384DH-RSA-AES128-SHA256DH-RSA-AES256-SHA256DH-RSA-AES128-GCM-SHA256DH-RSA-AES256-GCM-SHA384DH-DSS-AES128-SHA256DH-DSS-AES256-SHA256DH-DSS-AES128-GCM-SHA256DH-DSS-AES256-GCM-SHA384DHE-RSA-AES128-SHA256DHE-RSA-AES256-SHA256DHE-RSA-AES128-GCM-SHA256DHE-RSA-AES256-GCM-SHA384DHE-DSS-AES128-SHA256DHE-DSS-AES256-SHA256DHE-DSS-AES128-GCM-SHA256DHE-DSS-AES256-GCM-SHA384ECDHE-RSA-AES128-SHA256ECDHE-RSA-AES256-SHA384ECDHE-RSA-AES128-GCM-SHA256ECDHE-RSA-AES256-GCM-SHA384ECDHE-ECDSA-AES128-SHA256ECDHE-ECDSA-AES256-SHA384ECDHE-ECDSA-AES128-GCM-SHA256ECDHE-ECDSA-AES256-GCM-SHA384ADH-AES128-SHA256ADH-AES256-SHA256ADH-AES128-GCM-SHA256ADH-AES256-GCM-SHA384AES128-CCMAES256-CCMDHE-RSA-AES128-CCMDHE-RSA-AES256-CCMAES128-CCM8AES256-CCM8DHE-RSA-AES128-CCM8DHE-RSA-AES256-CCM8ECDHE-ECDSA-AES128-CCMECDHE-ECDSA-AES256-CCMECDHE-ECDSA-AES128-CCM8ECDHE-ECDSA-AES256-CCM8
Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2
ECDHE-ECDSA-CAMELLIA128-SHA256ECDHE-ECDSA-CAMELLIA256-SHA384ECDHE-RSA-CAMELLIA128-SHA256ECDHE-RSA-CAMELLIA256-SHA384
TLS 1.3 cipher suites
(Note these ciphers are set with CURLOPT_TLS13_CIPHERS and --tls13-ciphers)
TLS_AES_256_GCM_SHA384TLS_CHACHA20_POLY1305_SHA256TLS_AES_128_GCM_SHA256TLS_AES_128_CCM_8_SHA256TLS_AES_128_CCM_SHA256
NSS
Totally insecure
rc4rc4-md5rc4exportrc2rc2exportdesdesede3
SSL3/TLS cipher suites
rsa_rc4_128_md5rsa_rc4_128_sharsa_3des_sharsa_des_sharsa_rc4_40_md5rsa_rc2_40_md5rsa_null_md5rsa_null_shafips_3des_shafips_des_shafortezzafortezza_rc4_128_shafortezza_null
TLS 1.0 Exportable 56-bit Cipher Suites
rsa_des_56_sharsa_rc4_56_sha
AES ciphers
dhe_dss_aes_128_cbc_shadhe_dss_aes_256_cbc_shadhe_rsa_aes_128_cbc_shadhe_rsa_aes_256_cbc_sharsa_aes_128_sharsa_aes_256_sha
ECC ciphers
ecdh_ecdsa_null_shaecdh_ecdsa_rc4_128_shaecdh_ecdsa_3des_shaecdh_ecdsa_aes_128_shaecdh_ecdsa_aes_256_shaecdhe_ecdsa_null_shaecdhe_ecdsa_rc4_128_shaecdhe_ecdsa_3des_shaecdhe_ecdsa_aes_128_shaecdhe_ecdsa_aes_256_shaecdh_rsa_null_shaecdh_rsa_128_shaecdh_rsa_3des_shaecdh_rsa_aes_128_shaecdh_rsa_aes_256_shaecdhe_rsa_nullecdhe_rsa_rc4_128_shaecdhe_rsa_3des_shaecdhe_rsa_aes_128_shaecdhe_rsa_aes_256_shaecdh_anon_null_shaecdh_anon_rc4_128shaecdh_anon_3des_shaecdh_anon_aes_128_shaecdh_anon_aes_256_sha
HMAC-SHA256 cipher suites
rsa_null_sha_256rsa_aes_128_cbc_sha_256rsa_aes_256_cbc_sha_256dhe_rsa_aes_128_cbc_sha_256dhe_rsa_aes_256_cbc_sha_256ecdhe_ecdsa_aes_128_cbc_sha_256ecdhe_rsa_aes_128_cbc_sha_256
AES GCM cipher suites in RFC 5288 and RFC 5289
rsa_aes_128_gcm_sha_256dhe_rsa_aes_128_gcm_sha_256dhe_dss_aes_128_gcm_sha_256ecdhe_ecdsa_aes_128_gcm_sha_256ecdh_ecdsa_aes_128_gcm_sha_256ecdhe_rsa_aes_128_gcm_sha_256ecdh_rsa_aes_128_gcm_sha_256
cipher suites using SHA384
rsa_aes_256_gcm_sha_384dhe_rsa_aes_256_gcm_sha_384dhe_dss_aes_256_gcm_sha_384ecdhe_ecdsa_aes_256_sha_384ecdhe_rsa_aes_256_sha_384ecdhe_ecdsa_aes_256_gcm_sha_384ecdhe_rsa_aes_256_gcm_sha_384
chacha20-poly1305 cipher suites
ecdhe_rsa_chacha20_poly1305_sha_256ecdhe_ecdsa_chacha20_poly1305_sha_256dhe_rsa_chacha20_poly1305_sha_256
TLS 1.3 cipher suites
aes_128_gcm_sha_256aes_256_gcm_sha_384chacha20_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-md5rc4-md5exp-rc2-md5exp-rc4-md5des-cbc-md5des-cbc3-md5
SSL3 cipher suites
null-md5null-sharc4-md5rc4-shaexp-rc2-cbc-md5exp-rc4-md5exp-des-cbc-shades-cbc3-sha
TLS v1.0 cipher suites
null-md5null-sharc4-md5rc4-shaexp-rc2-cbc-md5exp-rc4-md5exp-des-cbc-shades-cbc3-shaaes128-shaaes256-sha
TLS v1.1 cipher suites
null-md5null-sharc4-md5rc4-shaexp-des-cbc-shades-cbc3-shaaes128-shaaes256-sha
TLS v1.2 cipher suites
null-md5null-shanull-sha256rc4-md5rc4-shades-cbc3-shaaes128-shaaes256-shaaes128-sha256aes256-sha256aes128-gcm-sha256aes256-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 errorCURLE_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_SHA384TLS_AES_128_GCM_SHA256TLS_CHACHA20_POLY1305_SHA256TLS_AES_128_CCM_8_SHA256TLS_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-SHAAES128-SHAAES256-SHAAES128-SHA256AES256-SHA256AES128-GCM-SHA256AES256-GCM-SHA384ECDH-ECDSA-DES-CBC3-SHAECDH-ECDSA-AES128-SHAECDH-ECDSA-AES256-SHAECDHE-ECDSA-DES-CBC3-SHAECDHE-ECDSA-AES128-SHAECDHE-ECDSA-AES256-SHAECDH-RSA-DES-CBC3-SHAECDH-RSA-AES128-SHAECDH-RSA-AES256-SHAECDHE-RSA-DES-CBC3-SHAECDHE-RSA-AES128-SHAECDHE-RSA-AES256-SHAECDHE-ECDSA-AES128-SHA256ECDHE-ECDSA-AES256-SHA384ECDH-ECDSA-AES128-SHA256ECDH-ECDSA-AES256-SHA384ECDHE-RSA-AES128-SHA256ECDHE-RSA-AES256-SHA384ECDH-RSA-AES128-SHA256ECDH-RSA-AES256-SHA384ECDHE-ECDSA-AES128-GCM-SHA256ECDHE-ECDSA-AES256-GCM-SHA384ECDH-ECDSA-AES128-GCM-SHA256ECDH-ECDSA-AES256-GCM-SHA384ECDHE-RSA-AES128-GCM-SHA256ECDHE-RSA-AES256-GCM-SHA384ECDH-RSA-AES128-GCM-SHA256ECDH-RSA-AES256-GCM-SHA384
Since BearSSL 0.2:
ECDHE-RSA-CHACHA20-POLY1305ECDHE-ECDSA-CHACHA20-POLY1305
Since BearSSL 0.6:
AES128-CCMAES256-CCMAES128-CCM8AES256-CCM8ECDHE-ECDSA-AES128-CCMECDHE-ECDSA-AES256-CCMECDHE-ECDSA-AES128-CCM8ECDHE-ECDSA-AES256-CCM8
Third Party Curl Docs CMakeLists (`third_party/curl/docs/CMakeLists.txt`)
#***************************************************************************
_ _ ____ _
Project ___| | | | _ \| |
/ __| | | | |_) | |
| (__| |_| | _ <| |___
\___|\___/|_| \_\_____|
Copyright (C) Daniel Stenberg, , et al.
This software is licensed as described in the file COPYING, which
you should have received as part of this distribution. The terms
are also available at https://curl.se/docs/copyright.html.
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 thescripts/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.
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 */.
/* 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:
Narrower columns are easier to read than wide ones. There's a reason
newspapers have used columns for decades or centuries.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:
if(age < 40) {
/* clearly a youngster */
}You may omit the braces if they would contain only a one-line statement:
if(!x)
continue;For functions the opening brace should be on a separate line:
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:
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:
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:
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:
if((ptr = malloc(100)) == NULL)
return NULL;and instead we encourage the above version to be spelled out more clearly:
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.
if(a)
return TRUE;
else if(b)
return FALSE;and NEVER:
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:
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:
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:
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:
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:
data->set.http_disable_hostname_check_before_authentication =
(0 != va_arg(param, long)) ? TRUE : FALSE;Function invoke with an open parenthesis:
if(option) {
result = parse_login_details(option, strlen(option),
(userp ? &user : NULL),
(passwdp ? &passwd : NULL),
NULL);
}Align with the "current open" parenthesis:
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:
#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:
struct something {
void *valid;
size_t way_to_write;
};
struct something instance;Not okay:
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):
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:
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-socketConnecting/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:
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:
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:
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.
License and copyright
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.
---- 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:
git clone https://github.com/curl/curl.gitYou then proceed and edit all the files you like and you commit them to your
local repository:
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:
git format-patch remotes/origin/masterThis 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:
diff -u unmodified-file.c my-changed-one.c > my-fixes.diffIf you have modified several files, possibly in different directories, you
can use diff recursively:
diff -ur curl-original-dir curl-modified-sources-dir > my-fixes.diffThe GNU diff and GNU patch tools exist for virtually all platforms, including
all kinds of Unixes and Windows.
Useful resources
Update copyright and license information
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 cancelingcurl_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`
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`
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`
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`
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`
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`
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`
void Curl_dyn_reset(struct dynbuf *s);Reset the buffer