aibridge
packagemoduleThis package is not in the latest version of its module.
Details
Validgo.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used, modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
- Learn more about best practices
Repository
Links
README¶
aibridge
Documentation¶
Index¶
- Constants
- Variables
- func AsActor(ctx context.Context, actorID string, metadata Metadata) context.Context
- type AWSBedrockConfig
- type AnthropicConfig
- type AnthropicErrorResponse
- type AnthropicMessagesBlockingInterception
- type AnthropicMessagesInterceptionBase
- type AnthropicMessagesStreamingInterception
- type AnthropicProvider
- func (p *AnthropicProvider) AuthHeader() string
- func (p *AnthropicProvider) BaseURL() string
- func (p *AnthropicProvider) BridgedRoutes() []string
- func (p *AnthropicProvider) CreateInterceptor(w http.ResponseWriter, r *http.Request) (Interceptor, error)
- func (p *AnthropicProvider) InjectAuthHeader(headers *http.Header)
- func (p *AnthropicProvider) Name() string
- func (p *AnthropicProvider) PassthroughRoutes() []string
- type AsyncRecorder
- func (a *AsyncRecorder) RecordInterception(ctx context.Context, req *InterceptionRecord) error
- func (a *AsyncRecorder) RecordInterceptionEnded(ctx context.Context, req *InterceptionRecordEnded) error
- func (a *AsyncRecorder) RecordPromptUsage(_ context.Context, req *PromptUsageRecord) error
- func (a *AsyncRecorder) RecordTokenUsage(_ context.Context, req *TokenUsageRecord) error
- func (a *AsyncRecorder) RecordToolUsage(_ context.Context, req *ToolUsageRecord) error
- func (a *AsyncRecorder) Wait()
- func (a *AsyncRecorder) WithInitiatorID(initiatorID string)
- func (a *AsyncRecorder) WithMetrics(metrics *Metrics)
- func (a *AsyncRecorder) WithModel(model string)
- func (a *AsyncRecorder) WithProvider(provider string)
- type ChatCompletionNewParamsWrapper
- type Config
- type InterceptionRecord
- type InterceptionRecordEnded
- type Interceptor
- type MessageNewParamsWrapper
- type Metadata
- type Metrics
- type OpenAIBlockingChatInterception
- type OpenAIChatInterceptionBase
- type OpenAIConfig
- type OpenAIErrorResponse
- type OpenAIProvider
- func (p *OpenAIProvider) AuthHeader() string
- func (p *OpenAIProvider) BaseURL() string
- func (p *OpenAIProvider) BridgedRoutes() []string
- func (p *OpenAIProvider) CreateInterceptor(w http.ResponseWriter, r *http.Request) (Interceptor, error)
- func (p *OpenAIProvider) InjectAuthHeader(headers *http.Header)
- func (p *OpenAIProvider) Name() string
- func (p *OpenAIProvider) PassthroughRoutes() []string
- type OpenAIStreamingChatInterception
- type PromptUsageRecord
- type Provider
- type ProviderConfig
- type Recorder
- type RecorderWrapper
- func (r *RecorderWrapper) RecordInterception(ctx context.Context, req *InterceptionRecord) error
- func (r *RecorderWrapper) RecordInterceptionEnded(ctx context.Context, req *InterceptionRecordEnded) error
- func (r *RecorderWrapper) RecordPromptUsage(ctx context.Context, req *PromptUsageRecord) error
- func (r *RecorderWrapper) RecordTokenUsage(ctx context.Context, req *TokenUsageRecord) error
- func (r *RecorderWrapper) RecordToolUsage(ctx context.Context, req *ToolUsageRecord) error
- type RequestBridge
- type SSEEvent
- type SSEParser
- type TokenUsageRecord
- type ToolArgs
- type ToolUsageRecord
Constants¶
const (InterceptionCountStatusFailed = "failed"InterceptionCountStatusCompleted = "completed")
const (SSEEventTypeMessage = "message"SSEEventTypeError = "error"SSEEventTypePing = "ping")
const (ProviderAnthropic = "anthropic")const (ProviderOpenAI = "openai")Variables¶
var ErrEventStreamClosed =errors.New("event stream closed")var UnknownRoute =errors.New("unknown route")Functions¶
Types¶
typeAWSBedrockConfig¶added inv0.1.6
typeAnthropicConfig¶added inv0.1.6
type AnthropicConfigProviderConfig
typeAnthropicErrorResponse¶
type AnthropicErrorResponse struct {*anthropic.ErrorResponseStatusCodeint `json:"-"`}func (*AnthropicErrorResponse)Error¶
func (a *AnthropicErrorResponse) Error()string
typeAnthropicMessagesBlockingInterception¶
type AnthropicMessagesBlockingInterception struct {AnthropicMessagesInterceptionBase}funcNewAnthropicMessagesBlockingInterception¶
func NewAnthropicMessagesBlockingInterception(iduuid.UUID, req *MessageNewParamsWrapper, cfgAnthropicConfig, bedrockCfg *AWSBedrockConfig) *AnthropicMessagesBlockingInterception
func (*AnthropicMessagesBlockingInterception)ProcessRequest¶
func (i *AnthropicMessagesBlockingInterception) ProcessRequest(whttp.ResponseWriter, r *http.Request)error
func (*AnthropicMessagesBlockingInterception)Setup¶
func (s *AnthropicMessagesBlockingInterception) Setup(loggerslog.Logger, recorderRecorder, mcpProxymcp.ServerProxier)
func (*AnthropicMessagesBlockingInterception)Streaming¶added inv0.2.0
func (s *AnthropicMessagesBlockingInterception) Streaming()bool
typeAnthropicMessagesInterceptionBase¶
type AnthropicMessagesInterceptionBase struct {// contains filtered or unexported fields}func (*AnthropicMessagesInterceptionBase)ID¶
func (i *AnthropicMessagesInterceptionBase) ID()uuid.UUID
func (*AnthropicMessagesInterceptionBase)Model¶
func (i *AnthropicMessagesInterceptionBase) Model()string
func (*AnthropicMessagesInterceptionBase)Setup¶
func (i *AnthropicMessagesInterceptionBase) Setup(loggerslog.Logger, recorderRecorder, mcpProxymcp.ServerProxier)
typeAnthropicMessagesStreamingInterception¶
type AnthropicMessagesStreamingInterception struct {AnthropicMessagesInterceptionBase}funcNewAnthropicMessagesStreamingInterception¶
func NewAnthropicMessagesStreamingInterception(iduuid.UUID, req *MessageNewParamsWrapper, cfgAnthropicConfig, bedrockCfg *AWSBedrockConfig) *AnthropicMessagesStreamingInterception
func (*AnthropicMessagesStreamingInterception)ProcessRequest¶
func (i *AnthropicMessagesStreamingInterception) ProcessRequest(whttp.ResponseWriter, r *http.Request)error
ProcessRequest handles a request to /v1/messages.This API has a state-machine behind it, which is described inhttps://docs.claude.com/en/docs/build-with-claude/streaming#event-types.
Each stream uses the following event flow:- `message_start`: contains a Message object with empty content.- A series of content blocks, each of which have a `content_block_start`, one or more `content_block_delta` events, and a `content_block_stop` event.- Each content block will have an index that corresponds to its index in the final Message content array.- One or more `message_delta` events, indicating top-level changes to the final Message object.- A final `message_stop` event.
It will inject any tools which have been provided by themcp.ServerProxier.
When a response from the server includes an event indicating that a tool must be invoked, a conditionalflow takes place:
a) if the tool is not injected (i.e. defined by the client), relay the event unmodifiedb) if the tool is injected, it will be invoked by themcp.ServerProxier in the remote MCP server, and itsresults relayed to the SERVER. The response from the server will be handled synchronously, and this loopcan continue until all injected tool invocations are completed and the response is relayed to the client.
func (*AnthropicMessagesStreamingInterception)Setup¶
func (s *AnthropicMessagesStreamingInterception) Setup(loggerslog.Logger, recorderRecorder, mcpProxymcp.ServerProxier)
func (*AnthropicMessagesStreamingInterception)Streaming¶added inv0.2.0
func (s *AnthropicMessagesStreamingInterception) Streaming()bool
typeAnthropicProvider¶
type AnthropicProvider struct {// contains filtered or unexported fields}AnthropicProvider allows for interactions with the Anthropic API.
funcNewAnthropicProvider¶
func NewAnthropicProvider(cfgAnthropicConfig, bedrockCfg *AWSBedrockConfig) *AnthropicProvider
func (*AnthropicProvider)AuthHeader¶
func (p *AnthropicProvider) AuthHeader()string
func (*AnthropicProvider)BaseURL¶
func (p *AnthropicProvider) BaseURL()string
func (*AnthropicProvider)BridgedRoutes¶
func (p *AnthropicProvider) BridgedRoutes() []string
func (*AnthropicProvider)CreateInterceptor¶
func (p *AnthropicProvider) CreateInterceptor(whttp.ResponseWriter, r *http.Request) (Interceptor,error)
func (*AnthropicProvider)InjectAuthHeader¶
func (p *AnthropicProvider) InjectAuthHeader(headers *http.Header)
func (*AnthropicProvider)Name¶
func (p *AnthropicProvider) Name()string
func (*AnthropicProvider)PassthroughRoutes¶
func (p *AnthropicProvider) PassthroughRoutes() []string
typeAsyncRecorder¶
type AsyncRecorder struct {// contains filtered or unexported fields}AsyncRecorder callsRecorder methods asynchronously and logs any errors which may occur.
func (*AsyncRecorder)RecordInterception¶
func (a *AsyncRecorder) RecordInterception(ctxcontext.Context, req *InterceptionRecord)error
RecordInterception must NOT be called asynchronously.If an interception cannot be recorded, the whole request should fail.
func (*AsyncRecorder)RecordInterceptionEnded¶added inv0.1.5
func (a *AsyncRecorder) RecordInterceptionEnded(ctxcontext.Context, req *InterceptionRecordEnded)error
func (*AsyncRecorder)RecordPromptUsage¶
func (a *AsyncRecorder) RecordPromptUsage(_context.Context, req *PromptUsageRecord)error
func (*AsyncRecorder)RecordTokenUsage¶
func (a *AsyncRecorder) RecordTokenUsage(_context.Context, req *TokenUsageRecord)error
func (*AsyncRecorder)RecordToolUsage¶
func (a *AsyncRecorder) RecordToolUsage(_context.Context, req *ToolUsageRecord)error
func (*AsyncRecorder)Wait¶
func (a *AsyncRecorder) Wait()
func (*AsyncRecorder)WithInitiatorID¶added inv0.2.0
func (a *AsyncRecorder) WithInitiatorID(initiatorIDstring)
func (*AsyncRecorder)WithMetrics¶added inv0.2.0
func (a *AsyncRecorder) WithMetrics(metrics *Metrics)
func (*AsyncRecorder)WithModel¶added inv0.2.0
func (a *AsyncRecorder) WithModel(modelstring)
func (*AsyncRecorder)WithProvider¶added inv0.2.0
func (a *AsyncRecorder) WithProvider(providerstring)
typeChatCompletionNewParamsWrapper¶
type ChatCompletionNewParamsWrapper struct {openai.ChatCompletionNewParams `json:""`Streambool `json:"stream,omitempty"`}ChatCompletionNewParamsWrapper exists because the "stream" param is not included in openai.ChatCompletionNewParams.
func (*ChatCompletionNewParamsWrapper)LastUserPrompt¶
func (c *ChatCompletionNewParamsWrapper) LastUserPrompt() (*string,error)
func (ChatCompletionNewParamsWrapper)MarshalJSON¶
func (cChatCompletionNewParamsWrapper) MarshalJSON() ([]byte,error)
func (*ChatCompletionNewParamsWrapper)UnmarshalJSON¶
func (c *ChatCompletionNewParamsWrapper) UnmarshalJSON(raw []byte)error
typeConfig¶
type Config struct {OpenAIProviderConfigAnthropicProviderConfigBedrockAWSBedrockConfig}typeInterceptionRecordEnded¶added inv0.1.5
typeInterceptor¶
type Interceptor interface {// ID returns the unique identifier for this interception.ID()uuid.UUID// Setup injects some required dependencies. This MUST be called before using the interceptor// to process requests.Setup(loggerslog.Logger, recorderRecorder, mcpProxymcp.ServerProxier)// Model returns the model in use for this [Interceptor].Model()string// ProcessRequest handles the HTTP request.ProcessRequest(whttp.ResponseWriter, r *http.Request)error// Specifies whether an interceptor handles streaming or not.Streaming()bool}Interceptor describes a (potentially) stateful interaction with an AI provider.
typeMessageNewParamsWrapper¶
type MessageNewParamsWrapper struct {anthropic.MessageNewParams `json:""`Streambool `json:"stream,omitempty"`}MessageNewParamsWrapper exists because the "stream" param is not included in anthropic.MessageNewParams.
func (*MessageNewParamsWrapper)LastUserPrompt¶
func (b *MessageNewParamsWrapper) LastUserPrompt() (*string,error)
func (MessageNewParamsWrapper)MarshalJSON¶
func (bMessageNewParamsWrapper) MarshalJSON() ([]byte,error)
func (*MessageNewParamsWrapper)UnmarshalJSON¶
func (b *MessageNewParamsWrapper) UnmarshalJSON(raw []byte)error
func (*MessageNewParamsWrapper)UseStreaming¶
func (b *MessageNewParamsWrapper) UseStreaming()bool
typeMetrics¶added inv0.2.0
type Metrics struct {// Interception-related metrics.InterceptionDuration *prometheus.HistogramVecInterceptionCount *prometheus.CounterVecInterceptionsInflight *prometheus.GaugeVecPassthroughCount *prometheus.CounterVec// Prompt-related metrics.PromptCount *prometheus.CounterVec// Token-related metrics.TokenUseCount *prometheus.CounterVec// Tool-related metrics.InjectedToolUseCount *prometheus.CounterVecNonInjectedToolUseCount *prometheus.CounterVec}funcNewMetrics¶added inv0.2.0
func NewMetrics(regprometheus.Registerer) *Metrics
NewMetrics creates AND registers metrics. It will panic if a collector has already been registered.Note: we are not specifying namespace in the metrics; the provided registerer may specify a "namespace"usingprometheus.WrapRegistererWithPrefix.
typeOpenAIBlockingChatInterception¶
type OpenAIBlockingChatInterception struct {OpenAIChatInterceptionBase}funcNewOpenAIBlockingChatInterception¶
func NewOpenAIBlockingChatInterception(iduuid.UUID, req *ChatCompletionNewParamsWrapper, baseURL, keystring) *OpenAIBlockingChatInterception
func (*OpenAIBlockingChatInterception)ProcessRequest¶
func (i *OpenAIBlockingChatInterception) ProcessRequest(whttp.ResponseWriter, r *http.Request)error
func (*OpenAIBlockingChatInterception)Setup¶
func (s *OpenAIBlockingChatInterception) Setup(loggerslog.Logger, recorderRecorder, mcpProxymcp.ServerProxier)
func (*OpenAIBlockingChatInterception)Streaming¶added inv0.2.0
func (s *OpenAIBlockingChatInterception) Streaming()bool
typeOpenAIChatInterceptionBase¶
type OpenAIChatInterceptionBase struct {// contains filtered or unexported fields}func (*OpenAIChatInterceptionBase)ID¶
func (i *OpenAIChatInterceptionBase) ID()uuid.UUID
func (*OpenAIChatInterceptionBase)Model¶
func (i *OpenAIChatInterceptionBase) Model()string
func (*OpenAIChatInterceptionBase)Setup¶
func (i *OpenAIChatInterceptionBase) Setup(loggerslog.Logger, recorderRecorder, mcpProxymcp.ServerProxier)
typeOpenAIConfig¶added inv0.1.6
type OpenAIConfigProviderConfig
typeOpenAIErrorResponse¶
type OpenAIErrorResponse struct {ErrorObject *shared.ErrorObject `json:"error"`StatusCodeint `json:"-"`}func (*OpenAIErrorResponse)Error¶
func (a *OpenAIErrorResponse) Error()string
typeOpenAIProvider¶
type OpenAIProvider struct {// contains filtered or unexported fields}OpenAIProvider allows for interactions with the OpenAI API.
funcNewOpenAIProvider¶
func NewOpenAIProvider(cfgOpenAIConfig) *OpenAIProvider
func (*OpenAIProvider)AuthHeader¶
func (p *OpenAIProvider) AuthHeader()string
func (*OpenAIProvider)BaseURL¶
func (p *OpenAIProvider) BaseURL()string
func (*OpenAIProvider)BridgedRoutes¶
func (p *OpenAIProvider) BridgedRoutes() []string
func (*OpenAIProvider)CreateInterceptor¶
func (p *OpenAIProvider) CreateInterceptor(whttp.ResponseWriter, r *http.Request) (Interceptor,error)
func (*OpenAIProvider)InjectAuthHeader¶
func (p *OpenAIProvider) InjectAuthHeader(headers *http.Header)
func (*OpenAIProvider)Name¶
func (p *OpenAIProvider) Name()string
func (*OpenAIProvider)PassthroughRoutes¶
func (p *OpenAIProvider) PassthroughRoutes() []string
PassthroughRoutes define the routes which are not currently interceptedbut must be passed through to the upstream.The /v1/completions legacy API is deprecated and will not be passed through.Seehttps://platform.openai.com/docs/api-reference/completions.
typeOpenAIStreamingChatInterception¶
type OpenAIStreamingChatInterception struct {OpenAIChatInterceptionBase}funcNewOpenAIStreamingChatInterception¶
func NewOpenAIStreamingChatInterception(iduuid.UUID, req *ChatCompletionNewParamsWrapper, baseURL, keystring) *OpenAIStreamingChatInterception
func (*OpenAIStreamingChatInterception)ProcessRequest¶
func (i *OpenAIStreamingChatInterception) ProcessRequest(whttp.ResponseWriter, r *http.Request)error
ProcessRequest handles a request to /v1/chat/completions.Seehttps://platform.openai.com/docs/api-reference/chat-streaming/streaming.
It will inject any tools which have been provided by themcp.ServerProxier.
When a response from the server includes an event indicating that a tool must be invoked, a conditionalflow takes place:
a) if the tool is not injected (i.e. defined by the client), relay the event unmodifiedb) if the tool is injected, it will be invoked by themcp.ServerProxier in the remote MCP server, and itsresults relayed to the SERVER. The response from the server will be handled synchronously, and this loopcan continue until all injected tool invocations are completed and the response is relayed to the client.
func (*OpenAIStreamingChatInterception)Setup¶
func (i *OpenAIStreamingChatInterception) Setup(loggerslog.Logger, recorderRecorder, mcpProxymcp.ServerProxier)
func (*OpenAIStreamingChatInterception)Streaming¶added inv0.2.0
func (i *OpenAIStreamingChatInterception) Streaming()bool
typeProvider¶
type Provider interface {// Name returns the provider's name.Name()string// BaseURL defines the base URL endpoint for this provider's API.BaseURL()string// CreateInterceptor starts a new [Interceptor] which is responsible for intercepting requests,// communicating with the upstream provider and formulating a response to be sent to the requesting client.CreateInterceptor(whttp.ResponseWriter, r *http.Request) (Interceptor,error)// BridgedRoutes returns a slice of [http.ServeMux]-compatible routes which will have special handling.// Seehttps://pkg.go.dev/net/http#hdr-Patterns-ServeMux.BridgedRoutes() []string// PassthroughRoutes returns a slice of whitelisted [http.ServeMux]-compatible* routes which are// not currently intercepted and must be handled by the upstream directly.//// * only path routes can be specified, not ones containing HTTP methods. (i.e. GET /route).// By default, these passthrough routes will accept any HTTP method.PassthroughRoutes() []string// AuthHeader returns the name of the header which the provider expects to find its authentication// token in.AuthHeader()string// InjectAuthHeader allows [Provider]s to set its authentication header.InjectAuthHeader(*http.Header)}Provider describes an AI provider client's behaviour.Provider clients are responsible for interacting with upstream AI providers.
typeProviderConfig¶
type ProviderConfig struct {BaseURL, Keystring}typeRecorder¶
type Recorder interface {// RecordInterception records metadata about an interception with an upstream AI provider.RecordInterception(ctxcontext.Context, req *InterceptionRecord)error// RecordInterceptionEnded records that given interception has completed.RecordInterceptionEnded(ctxcontext.Context, req *InterceptionRecordEnded)error// RecordTokenUsage records the tokens used in an interception with an upstream AI provider.RecordTokenUsage(ctxcontext.Context, req *TokenUsageRecord)error// RecordPromptUsage records the prompts used in an interception with an upstream AI provider.RecordPromptUsage(ctxcontext.Context, req *PromptUsageRecord)error// RecordToolUsage records the tools used in an interception with an upstream AI provider.RecordToolUsage(ctxcontext.Context, req *ToolUsageRecord)error}Recorder describes all the possible usage information we need to capture during interactions with AI providers.Additionally, it introduces the concept of an "Interception", which includes information about which provider/model wasused and by whom. All usage records should reference this Interception by ID.
typeRecorderWrapper¶
type RecorderWrapper struct {// contains filtered or unexported fields}RecorderWrapper is a convenience struct which implements RecorderClient and resolves a client before calling each method.It also sets the start/creation time of each record.
funcNewRecorder¶
func NewRecorder(loggerslog.Logger, clientFn func() (Recorder,error)) *RecorderWrapper
func (*RecorderWrapper)RecordInterception¶
func (r *RecorderWrapper) RecordInterception(ctxcontext.Context, req *InterceptionRecord)error
func (*RecorderWrapper)RecordInterceptionEnded¶added inv0.1.5
func (r *RecorderWrapper) RecordInterceptionEnded(ctxcontext.Context, req *InterceptionRecordEnded)error
func (*RecorderWrapper)RecordPromptUsage¶
func (r *RecorderWrapper) RecordPromptUsage(ctxcontext.Context, req *PromptUsageRecord)error
func (*RecorderWrapper)RecordTokenUsage¶
func (r *RecorderWrapper) RecordTokenUsage(ctxcontext.Context, req *TokenUsageRecord)error
func (*RecorderWrapper)RecordToolUsage¶
func (r *RecorderWrapper) RecordToolUsage(ctxcontext.Context, req *ToolUsageRecord)error
typeRequestBridge¶
type RequestBridge struct {// contains filtered or unexported fields}RequestBridge is anhttp.Handler which is capable of masquerading as AI providers' APIs;specifically, OpenAI's & Anthropic's at present.RequestBridge intercepts requests to - and responses from - these upstream services to providea centralized governance layer.
RequestBridge has no concept of authentication or authorization. It does have a concept of identity,in the narrow sense that it expects an [actor] to be defined in the context, to record the initiatorof each interception.
RequestBridge is safe for concurrent use.
funcNewRequestBridge¶
func NewRequestBridge(ctxcontext.Context, providers []Provider, recorderRecorder, mcpProxymcp.ServerProxier, metrics *Metrics, loggerslog.Logger) (*RequestBridge,error)
NewRequestBridge creates a new *RequestBridge and registers the HTTP routes defined by the given providers.Any routes which are requested but not registered will be reverse-proxied to the upstream service.
ARecorder is also required to record prompt, tool, and token use.
mcpProxy will be closed when theRequestBridge is closed.
func (*RequestBridge)InflightRequests¶
func (b *RequestBridge) InflightRequests()int32
func (*RequestBridge)ServeHTTP¶
func (b *RequestBridge) ServeHTTP(rwhttp.ResponseWriter, r *http.Request)
ServeHTTP exposes the internal http.Handler, which has all [Provider]s' routes registered.It also tracks inflight requests.
typeSSEParser¶
type SSEParser struct {// contains filtered or unexported fields}funcNewSSEParser¶
func NewSSEParser() *SSEParser
func (*SSEParser)EventsByType¶
func (*SSEParser)MessageEvents¶
typeTokenUsageRecord¶
type TokenUsageRecord struct {InterceptionIDstringMsgIDstringInput, Outputint64// ExtraTokenTypes holds token types which *may* exist over and above input/output.// These should ultimately get merged into [Metadata], but it's useful to keep these// with their actual type (int64) since [Metadata] is a map[string]any.ExtraTokenTypes map[string]int64MetadataMetadataCreatedAttime.Time}
Source Files¶
- anthropic.go
- api.go
- bridge.go
- config.go
- context.go
- intercept_anthropic_messages_base.go
- intercept_anthropic_messages_blocking.go
- intercept_anthropic_messages_streaming.go
- intercept_openai_chat_base.go
- intercept_openai_chat_blocking.go
- intercept_openai_chat_streaming.go
- interception.go
- metrics.go
- openai.go
- passthrough.go
- provider.go
- provider_anthropic.go
- provider_openai.go
- recorder.go
- sse_parser.go
- streaming.go