twilight

module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: Apache-2.0

README

Twilight AI

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

Go Reference License

Features

  • One call, one result — Model.Generate and Model.Stream take an sdk.Request and return a ModelResult or a stream of typed parts. Embed, EmbedMany, GenerateImage, EditImage, GenerateVideo, GenerateSpeech and StreamSpeech cover the other modalities
  • 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 — describe tools with ToolDefinition (or infer the schema from a Go struct with NewToolDefinition[T]); the model's calls come back as typed ToolCalls with ToolArguments
  • Streaming — first-class channel-based streaming with fine-grained StreamPart types
  • 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

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")

    result, err := model.Generate(context.Background(), sdk.Request{
        Messages: []sdk.Message{
            sdk.UserMessage("Explain Go channels in 3 sentences."),
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result.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")

result, err := model.Generate(context.Background(), sdk.Request{
    Messages: []sdk.Message{
        sdk.UserMessage("Explain Go channels in 3 sentences."),
    },
})
fmt.Println(result.Text)

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.

OpenCode Go
import opencodego "github.com/felinics/twilight/provider/opencode/go"

provider := opencodego.New(
    opencodego.WithAPIKey("your-opencode-go-key"),
    opencodego.WithHeaders(map[string]string{"User-Agent": "my-agent/1.0"}),
)
ctx := sdk.WithRequestHeaders(context.Background(), map[string]string{
    opencodego.SessionHeader: conversationID, // stable across turns and tool calls
})
result, err := provider.ChatModel("glm-5.2").Generate(ctx, sdk.Request{
    Messages: []sdk.Message{sdk.UserMessage("Explain this code")},
})
if err != nil {
    log.Fatal(err)
}
fmt.Println(result.Text)

Models route to Completions unless the official endpoint table lists them under Responses or Messages. See OpenCode Go for model discovery, route overrides and session handling.

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
result, err := model.Generate(context.Background(), sdk.Request{
    MaxTokens: &maxTokens,
    Messages: []sdk.Message{
        sdk.UserMessage("Explain Go channels in 3 sentences."),
    },
})
fmt.Println(result.Text)

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")

result, err := model.Generate(context.Background(), sdk.Request{
    Messages: []sdk.Message{
        sdk.UserMessage("Explain Go channels in 3 sentences."),
    },
})
fmt.Println(result.Text)
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)

result, err := model.Generate(context.Background(), sdk.Request{
    Messages: []sdk.Message{
        sdk.UserMessage("Explain Go channels in 3 sentences."),
    },
})
fmt.Println(result.Text)

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
stream, err := model.Stream(ctx, sdk.Request{
    Messages: []sdk.Message{
        sdk.UserMessage("Write a haiku about concurrency."),
    },
})
if err != nil {
    log.Fatal(err)
}

for part := range stream.Parts {
    switch p := part.(type) {
    case *sdk.TextDeltaPart:
        fmt.Print(p.Text)
    case *sdk.ErrorPart:
        log.Fatal(p.Error)
    }
}

// Once Parts is drained, the assembled result is available: text, usage,
// finish reason and any tool calls, identical to what Generate returns.
result, err := stream.Result()
Tool Calling

Describe the tool with a Go struct — the SDK infers the JSON Schema. The model asks for the call; the caller runs it and replays the step:

type WeatherParams struct {
    City string `json:"city" jsonschema:"City name"`
}

weather, err := sdk.NewToolDefinition[WeatherParams]("get_weather", "Get current weather for a city")
if err != nil {
    log.Fatal(err)
}

messages := []sdk.Message{sdk.UserMessage("What's the weather in Tokyo?")}
for {
    result, err := model.Generate(ctx, sdk.Request{Messages: messages, Tools: []sdk.ToolDefinition{weather}})
    if err != nil {
        log.Fatal(err)
    }
    if len(result.ToolCalls) == 0 {
        fmt.Println(result.Text)
        break
    }
    var assistant []sdk.MessagePart
    for _, rp := range result.ReasoningParts {
        assistant = append(assistant, rp) // reasoning first, with the provider's tokens
    }
    if result.Text != "" {
        assistant = append(assistant, sdk.TextPart{Text: result.Text, ProviderMetadata: result.TextProviderMetadata})
    }
    var results []sdk.ToolResultPart
    for _, call := range result.ToolCalls {
        assistant = append(assistant, sdk.ToolCallPart{ToolCallID: call.ToolCallID, ToolName: call.ToolName, Input: call.Input, ProviderMetadata: call.ProviderMetadata})
        var params WeatherParams
        if err := call.Input.Unmarshal(&params); err != nil { // not a JSON document: tell the model
            results = append(results, sdk.ToolResultPart{ToolCallID: call.ToolCallID, ToolName: call.ToolName, Result: sdk.TextOutput(err.Error()), IsError: true})
            continue
        }
        out, _ := sdk.JSONOutput(map[string]any{"city": params.City, "temp": "22°C"})
        results = append(results, sdk.ToolResultPart{ToolCallID: call.ToolCallID, ToolName: call.ToolName, Result: out})
    }
    messages = append(messages, sdk.Message{Role: sdk.MessageRoleAssistant, Content: assistant}, sdk.ToolMessage(results...))
}

Each iteration is one model call. See Tool Calling.

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
if err := provider.Test(context.Background()); err != nil {
    var apiErr *sdk.APIError
    switch {
    case sdk.KindOf(err) == sdk.KindAuthentication, sdk.KindOf(err) == sdk.KindPermissionDenied:
        fmt.Println("Credentials rejected:", err)
    case errors.As(err, &apiErr):
        fmt.Println("Connected but the check failed:", err)
    default:
        fmt.Println("Cannot connect:", err)
    }
}

// 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 Tool definitions, typed arguments and outputs, replaying a step
Streaming Model.Stream, the ModelStream and its 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
OpenCode Go opencodego.New() Per-model Completions / Responses / Messages New
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

Directories

Path Synopsis
internal
canonicaljson
Package canonicaljson is the one RFC 8785 (JCS) rule of the module: the SDK canonicalizes the raw JSON it first carries with it, and the agent's stable values re-canonicalize with it, so bytes produced on one side are the same bytes on the other.
Package canonicaljson is the one RFC 8785 (JCS) rule of the module: the SDK canonicalizes the raw JSON it first carries with it, and the agent's stable values re-canonicalize with it, so bytes produced on one side are the same bytes on the other.
errorformat
Package errorformat decodes the error body formats that more than one provider package receives, such as OpenAI's, which OpenAI-compatible APIs copy.
Package errorformat decodes the error body formats that more than one provider package receives, such as OpenAI's, which OpenAI-compatible APIs copy.
messagecompat
Package messagecompat normalizes provider-agnostic SDK messages for the instruction-role capabilities of a concrete provider protocol.
Package messagecompat normalizes provider-agnostic SDK messages for the instruction-role capabilities of a concrete provider protocol.
reqheaders
Package reqheaders carries per-request HTTP headers on a context.
Package reqheaders carries per-request HTTP headers on a context.
provider
alibabacloud/images
Package images provides an Alibaba Cloud Model Studio DashScope image provider.
Package images provides an Alibaba Cloud Model Studio DashScope image provider.
alibabacloud/speech
Package speech provides an Alibaba Cloud DashScope CosyVoice TTS provider.
Package speech provides an Alibaba Cloud DashScope CosyVoice TTS provider.
ark/videos
Package videos provides Ark / ModelArk video generation support for BytePlus ModelArk and Volcengine Ark data-plane APIs.
Package videos provides Ark / ModelArk video generation support for BytePlus ModelArk and Volcengine Ark data-plane APIs.
deepgram/speech
Package speech provides a Deepgram TTS provider.
Package speech provides a Deepgram TTS provider.
elevenlabs/speech
Package speech provides an ElevenLabs TTS provider.
Package speech provides an ElevenLabs TTS provider.
microsoft/speech
Package speech provides a Microsoft Azure Cognitive Services Text-to-Speech provider.
Package speech provides a Microsoft Azure Cognitive Services Text-to-Speech provider.
minimax/speech
Package speech provides a MiniMax TTS provider.
Package speech provides a MiniMax TTS provider.
openai/speech
Package speech provides an OpenAI-compatible TTS provider that targets the /audio/speech endpoint.
Package speech provides an OpenAI-compatible TTS provider that targets the /audio/speech endpoint.
opencode/go
Package opencodego provides OpenCode Go through its Completions endpoint, and through Responses or Anthropic Messages for the models documented there.
Package opencodego provides OpenCode Go through its Completions endpoint, and through Responses or Anthropic Messages for the models documented there.
openrouter/speech
Package speech provides an OpenRouter TTS provider.
Package speech provides an OpenRouter TTS provider.
openrouter/videos
Package videos provides OpenRouter video generation support.
Package videos provides OpenRouter video generation support.
providertest
Package providertest is the seam conformance suite for chat providers.
Package providertest is the seam conformance suite for chat providers.
volcengine/speech
Package speech provides a Volcengine SAMI TTS provider.
Package speech provides a Volcengine SAMI TTS provider.

Jump to

Keyboard shortcuts

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