Twilight AI
A lightweight, idiomatic AI SDK for Go — inspired by Vercel AI SDK.

Features
- Simple API —
GenerateText, StreamText, Embed, EmbedMany, GenerateImage, EditImage, GenerateVideo, GenerateSpeech, and StreamSpeech cover most use cases
- Provider-agnostic — swap between OpenAI, Anthropic, Google, GitHub Copilot, Edge TTS, or any OpenAI-compatible endpoint
- Model discovery —
ListModels fetches available models, Test checks provider connectivity and model support
- Tool calling — define tools with Go structs, SDK infers JSON Schema and handles multi-step execution
- MCP support — connect to MCP servers and expose remote MCP tools as Twilight AI
sdk.Tool values
- Streaming — first-class channel-based streaming with fine-grained
StreamPart types
- Multi-step execution — automatic tool-call loop with configurable
MaxSteps
- Rich message types — text, images, files, reasoning content, tool calls/results
- Embeddings — generate embeddings with
Embed / EmbedMany, supports OpenAI and Google providers
- Image generation — generate and edit images with
GenerateImage / EditImage, supports OpenAI (dall-e, gpt-image) and Alibaba Cloud DashScope (Qwen-Image, Wan) models
- Video generation — create, poll, and download video jobs with OpenRouter and Ark/ModelArk providers
- Speech synthesis — generate speech with
GenerateSpeech / StreamSpeech, supports Edge TTS with an open provider model
- Approval flow — optional human-in-the-loop approval for sensitive tool calls
Installation
go get github.com/felinics/twilight
Requires Go 1.25+.
Quick Start
Generate Text (Chat Completions API)
package main
import (
"context"
"fmt"
"log"
"github.com/felinics/twilight/provider/openai/completions"
"github.com/felinics/twilight/sdk"
)
func main() {
provider := completions.New(
completions.WithAPIKey("sk-..."),
)
model := provider.ChatModel("gpt-4o-mini")
text, err := sdk.GenerateText(context.Background(),
sdk.WithModel(model),
sdk.WithMessages([]sdk.Message{
sdk.UserMessage("Explain Go channels in 3 sentences."),
}),
)
if err != nil {
log.Fatal(err)
}
fmt.Println(text)
}
Generate Text (Responses API)
import "github.com/felinics/twilight/provider/openai/responses"
provider := responses.New(
responses.WithAPIKey("sk-..."),
)
model := provider.ChatModel("gpt-4o-mini")
text, err := sdk.GenerateText(context.Background(),
sdk.WithModel(model),
sdk.WithMessages([]sdk.Message{
sdk.UserMessage("Explain Go channels in 3 sentences."),
}),
)
The Responses API is OpenAI's newer API with first-class support for reasoning models (o3, o4-mini), URL citation annotations, and a flat input format. See Providers for details.
Anthropic
import "github.com/felinics/twilight/provider/anthropic/messages"
provider := messages.New(
messages.WithAPIKey("sk-ant-..."),
)
model := provider.ChatModel("claude-sonnet-4-20250514")
maxTokens := 1024
text, err := sdk.GenerateText(context.Background(),
sdk.WithModel(model),
sdk.WithMaxTokens(maxTokens),
sdk.WithMessages([]sdk.Message{
sdk.UserMessage("Explain Go channels in 3 sentences."),
}),
)
For extended thinking (reasoning), configure the provider with WithThinking:
provider := messages.New(
messages.WithAPIKey("sk-ant-..."),
messages.WithThinking(messages.ThinkingConfig{
Type: "enabled",
BudgetTokens: 4000,
}),
)
Google Gemini
import "github.com/felinics/twilight/provider/google/generativeai"
provider := generativeai.New(
generativeai.WithAPIKey("AIza..."),
)
model := provider.ChatModel("gemini-2.5-flash")
text, err := sdk.GenerateText(context.Background(),
sdk.WithModel(model),
sdk.WithMessages([]sdk.Message{
sdk.UserMessage("Explain Go channels in 3 sentences."),
}),
)
GitHub Copilot Agent
import "github.com/felinics/twilight/provider/github/copilot"
provider := copilot.New(
// Use the inbound X-GitHub-Token value from your Copilot agent request.
copilot.WithGitHubToken("ghu_..."),
)
model := provider.ChatModel(copilot.AutoModel)
text, err := sdk.GenerateText(context.Background(),
sdk.WithModel(model),
sdk.WithMessages([]sdk.Message{
sdk.UserMessage("Explain Go channels in 3 sentences."),
}),
)
This provider targets GitHub Copilot agent / extension runtimes that can call api.githubcopilot.com/chat/completions. GitHub currently does not expose a public Copilot models discovery endpoint, so copilot.AutoModel tells the provider to let GitHub choose the backing model instead of inventing an undocumented model ID.
Stream Text
sr, err := sdk.StreamText(ctx,
sdk.WithModel(model),
sdk.WithMessages([]sdk.Message{
sdk.UserMessage("Write a haiku about concurrency."),
}),
)
if err != nil {
log.Fatal(err)
}
for part := range sr.Stream {
switch p := part.(type) {
case *sdk.TextDeltaPart:
fmt.Print(p.Text)
case *sdk.ErrorPart:
log.Fatal(p.Error)
}
}
Define a struct for your tool's parameters — the SDK infers the JSON Schema automatically:
type WeatherParams struct {
City string `json:"city" jsonschema:"City name"`
}
weatherTool := sdk.NewTool("get_weather", "Get current weather for a city",
func(ctx *sdk.ToolExecContext, input WeatherParams) (any, error) {
return map[string]any{"city": input.City, "temp": "22°C"}, nil
},
)
result, err := sdk.GenerateTextResult(ctx,
sdk.WithModel(model),
sdk.WithMessages([]sdk.Message{
sdk.UserMessage("What's the weather in Tokyo?"),
}),
sdk.WithTools([]sdk.Tool{weatherTool}),
sdk.WithMaxSteps(5),
)
You can also load tools from an MCP server and use them like normal Twilight AI tools:
import (
"context"
"log"
"os/exec"
"github.com/felinics/twilight/provider/openai/completions"
"github.com/felinics/twilight/sdk"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// HTTP / streamable MCP
mcpClient, err := sdk.CreateMCPClient(context.Background(), &sdk.MCPClientConfig{
Type: sdk.MCPTransportHTTP, // default; may be omitted
URL: "https://example.com/mcp",
Headers: map[string]string{
"Authorization": "Bearer <token>",
},
})
if err != nil {
log.Fatal(err)
}
defer mcpClient.Close()
tools, err := mcpClient.Tools(context.Background())
if err != nil {
log.Fatal(err)
}
provider := completions.New(completions.WithAPIKey("sk-..."))
model := provider.ChatModel("gpt-4o-mini")
result, err := sdk.GenerateTextResult(context.Background(),
sdk.WithModel(model),
sdk.WithMessages([]sdk.Message{
sdk.UserMessage("Use the available MCP tools to answer this request."),
}),
sdk.WithTools(tools),
sdk.WithMaxSteps(5),
)
if err != nil {
log.Fatal(err)
}
log.Println(result.Text)
For stdio, create the MCP transport yourself with the official MCP Go SDK and pass it in:
transport := &mcp.CommandTransport{
Command: exec.Command("my-mcp-server"),
}
mcpClient, err := sdk.CreateMCPClient(context.Background(), &sdk.MCPClientConfig{
Transport: transport,
})
Twilight AI converts mcp.Tool definitions into sdk.Tool automatically:
InputSchema is converted into *jsonschema.Schema
- tool execution calls
session.CallTool(...) under the hood
- MCP text content is returned as the tool output passed back into the model
Image Generation
Generate images from text prompts using OpenAI's image models:
import "github.com/felinics/twilight/provider/openai/images"
provider := images.New(images.WithAPIKey("sk-..."))
model := provider.GenerationModel("gpt-image-1")
result, err := sdk.GenerateImage(ctx,
sdk.WithImageGenerationModel(model),
sdk.WithImagePrompt("A sunset over mountains, oil painting style"),
sdk.WithImageSize("1024x1024"),
)
// result.Data[0].B64JSON contains the base64-encoded image
Edit existing images with inpainting or extensions:
model := provider.EditModel("gpt-image-1")
result, err := sdk.EditImage(ctx,
sdk.WithImageEditModel(model),
sdk.WithEditPrompt("Add a rainbow in the sky"),
sdk.WithEditImages(sdk.ImageInput{
Data: pngBytes,
Filename: "photo.png",
}),
)
Alibaba Cloud Model Studio (DashScope) image models work through the same API:
import "github.com/felinics/twilight/provider/alibabacloud/images"
provider := images.New(images.WithAPIKey("sk-..."))
model := provider.GenerationModel("qwen-image-max")
result, err := sdk.GenerateImage(ctx,
sdk.WithImageGenerationModel(model),
sdk.WithImagePrompt("A sunset over mountains, oil painting style"),
sdk.WithImageSize("1024x1024"),
)
// result.Data[0].URL contains the generated image URL
The DashScope provider routes Qwen-Image and Wan models to the right endpoint automatically and transparently polls async generation tasks. See Images for details.
Embeddings
Generate vector embeddings for text using OpenAI or Google:
import "github.com/felinics/twilight/provider/openai/embedding"
provider := embedding.New(embedding.WithAPIKey("sk-..."))
model := provider.EmbeddingModel("text-embedding-3-small")
// Single value
vec, err := sdk.Embed(ctx, "Hello world", sdk.WithEmbeddingModel(model))
// vec is []float64
// Multiple values
result, err := sdk.EmbedMany(ctx, []string{"Hello", "World"},
sdk.WithEmbeddingModel(model),
sdk.WithDimensions(256),
)
// result.Embeddings is [][]float64
// result.Usage.Tokens reports token consumption
Google Gemini embeddings:
import "github.com/felinics/twilight/provider/google/embedding"
provider := embedding.New(
embedding.WithAPIKey("AIza..."),
embedding.WithTaskType("RETRIEVAL_DOCUMENT"),
)
model := provider.EmbeddingModel("gemini-embedding-001")
vec, err := sdk.Embed(ctx, "Hello world", sdk.WithEmbeddingModel(model))
Speech Synthesis
Generate speech audio from text using Edge TTS (free, no API key required):
import "github.com/felinics/twilight/provider/edge/speech"
provider := speech.New()
model := provider.SpeechModel("edge-read-aloud")
// Generate complete audio
result, err := sdk.GenerateSpeech(ctx,
sdk.WithSpeechModel(model),
sdk.WithText("Hello, world!"),
sdk.WithSpeechConfig(map[string]any{
"voice": "en-US-EmmaMultilingualNeural",
"speed": 1.0,
}),
)
// result.Audio is []byte, result.ContentType is "audio/mpeg"
Stream audio chunks for low-latency playback:
sr, err := sdk.StreamSpeech(ctx,
sdk.WithSpeechModel(model),
sdk.WithText("你好,这是流式语音合成。"),
sdk.WithSpeechConfig(map[string]any{
"voice": "zh-CN-XiaoxiaoNeural",
}),
)
for chunk := range sr.Stream {
// write chunk to audio player or file
}
Provider Health Check & Model Discovery
Test connectivity and discover available models before making generation requests:
provider := completions.New(completions.WithAPIKey("sk-..."))
// Check provider connectivity
result := provider.Test(context.Background())
switch result.Status {
case sdk.ProviderStatusOK:
fmt.Println("Provider is healthy")
case sdk.ProviderStatusUnhealthy:
fmt.Println("Connected but unhealthy:", result.Message)
case sdk.ProviderStatusUnreachable:
fmt.Println("Cannot connect:", result.Message)
}
// List all available models
models, err := provider.ListModels(context.Background())
for _, m := range models {
fmt.Println(m.ID)
}
// Check if a specific model is supported
model := provider.ChatModel("gpt-4o")
testResult, err := model.Test(context.Background())
if testResult.Supported {
fmt.Println("Model is supported")
}
Documentation
| Document |
Description |
| Getting Started |
Installation, setup, and first request |
| Providers |
Provider interface, OpenAI, Anthropic, and Google Gemini |
| Images |
Generate and edit images with OpenAI and Alibaba Cloud DashScope image models |
| Embeddings |
Generate vector embeddings with OpenAI and Google |
| Speech |
Speech synthesis with Edge TTS and custom providers |
| Tool Calling |
Defining local tools, MCP tools, multi-step execution, approval flow |
| Streaming |
Channel-based streaming and StreamPart types |
| API Reference |
Complete type and function reference |
Supported Providers
| Provider |
Constructor |
API |
Status |
| OpenAI Chat Completions |
completions.New() |
/chat/completions |
✅ Stable |
| OpenAI Responses |
responses.New() |
/responses |
✅ Stable |
| OpenAI Codex |
codex.New() |
/codex/responses |
✅ Stable |
| OpenAI-compatible (DeepSeek, Groq, etc.) |
completions.New() + WithBaseURL |
/chat/completions |
✅ Stable |
| OpenRouter Responses |
responses.New() + WithBaseURL |
/responses |
✅ Stable |
| Anthropic |
messages.New() |
/messages |
✅ Stable |
| Google Gemini |
generativeai.New() |
Generative AI API |
✅ Stable |
| OpenAI Images |
images.New() |
/images/generations, /images/edits |
✅ Stable |
| Alibaba Cloud DashScope Images |
images.New() |
DashScope text2image / multimodal-generation |
✅ Stable |
| OpenAI Embeddings |
embedding.New() |
/embeddings |
✅ Stable |
| Google Embeddings |
embedding.New() |
embedContent / batchEmbedContents |
✅ Stable |
| Edge TTS |
speech.New() |
Bing WebSocket |
✅ Stable |
| OpenAI / compatible TTS |
speech.New() |
/audio/speech |
✅ Stable |
| Deepgram TTS |
speech.New() |
/v1/speak |
✅ Stable |
| ElevenLabs TTS |
speech.New() |
/v1/text-to-speech/{voice_id} |
✅ Stable |
| MiniMax TTS |
speech.New() |
/v1/t2a_v2 |
✅ Stable |
| MiMo TTS |
speech.New() |
/chat/completions + audio output |
✅ Stable |
| Alibaba Cloud CosyVoice |
speech.New() |
DashScope WebSocket |
✅ Stable |
| Volcengine SAMI TTS |
speech.New() |
/api/v1/invoke |
✅ Stable |
License
Apache License 2.0