Comprehensive guide to the Tyk AI Studio Plugin SDK, including capabilities, interfaces, and development patterns for building plugins that run in both AI Studio and Edge Gateway contexts.
Tyk AI Studio provides a Unified Plugin SDK that works seamlessly in both AI Studio and Edge Gateway contexts with a single API. This guide covers the core SDK concepts, capabilities, and patterns.
type MyAuthPlugin struct { plugin_sdk.BasePlugin tokenStore map[string]*TokenConfig // Maps tokens to app/user IDs}// HandleAuth validates the credential and returns App/User IDsfunc (p *MyAuthPlugin) HandleAuth(ctx plugin_sdk.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) { // Extract token from request token := req.Credential if token == "" { return &pb.AuthResponse{ Authenticated: false, ErrorMessage: "No credential provided", }, nil } // Validate token and look up associated IDs tokenConfig, valid := p.tokenStore[token] if !valid { return &pb.AuthResponse{ Authenticated: false, ErrorMessage: "Invalid token", }, nil } // CRITICAL: Return the App ID - this links the credential to access control return &pb.AuthResponse{ Authenticated: true, AppId: tokenConfig.AppID, // Must be a valid App in the database UserId: tokenConfig.UserID, }, nil}// GetAppByCredential fetches the App object for access control enforcementfunc (p *MyAuthPlugin) GetAppByCredential(ctx plugin_sdk.Context, credential string) (*pb.App, error) { tokenConfig, ok := p.tokenStore[credential] if !ok { return nil, fmt.Errorf("unknown credential") } // Fetch the App from the Service API if ctx.Runtime == plugin_sdk.RuntimeGateway { return ctx.Services.Gateway().GetApp(ctx, tokenConfig.AppID) } return ctx.Services.Studio().GetApp(ctx, tokenConfig.AppID)}// GetUserByCredential fetches the User object for identity contextfunc (p *MyAuthPlugin) GetUserByCredential(ctx plugin_sdk.Context, credential string) (*pb.User, error) { tokenConfig, ok := p.tokenStore[credential] if !ok { return nil, fmt.Errorf("unknown credential") } // Fetch the User from the Service API if ctx.Runtime == plugin_sdk.RuntimeGateway { return ctx.Services.Gateway().GetUser(ctx, tokenConfig.UserID) } return ctx.Services.Studio().GetUser(ctx, tokenConfig.UserID)}
Receive data from edge (Edge Gateway) plugins. This enables the hub-and-spoke communication pattern where edge plugins can send data back to the control plane. See Edge-to-Control Communication for complete details.
type EdgePayloadReceiver interface { AcceptEdgePayload(ctx Context, payload *EdgePayload) (handled bool, err error)}type EdgePayload struct { Payload []byte // Raw payload data from edge plugin EdgeID string // Edge instance identifier EdgeNamespace string // Namespace of the edge instance CorrelationID string // Correlation ID for tracking Metadata map[string]string // Key-value metadata EdgeTimestamp int64 // Unix timestamp when generated at edge ReceivedTimestamp int64 // Unix timestamp when received at control}
Example:
Expandable
func (p *MyPlugin) AcceptEdgePayload(ctx plugin_sdk.Context, payload *plugin_sdk.EdgePayload) (bool, error) { // Check if this payload is for us if payload.Metadata["type"] != "my-plugin-data" { return false, nil // Not our payload } ctx.Services.Logger().Info("Received edge payload", "edge_id", payload.EdgeID, "correlation_id", payload.CorrelationID, ) // Process the payload if err := p.processEdgeData(payload.Payload); err != nil { return true, err } return true, nil}
type Context struct { Runtime Runtime // RuntimeStudio or RuntimeGateway AppID uint32 // Current application ID UserID uint32 // Current user ID (if authenticated) SessionID string // Chat session ID (if applicable) LLM *pb.LLM // LLM configuration (if applicable) Services Services // Service broker}
// Publish an event (flows up from edge to control)err := ctx.Services.Events().Publish(ctx, "cache.invalidate", payload, plugin_sdk.DirUp)// Subscribe to events on a specific topicsubscriptionID, err := ctx.Services.Events().Subscribe("cache.invalidate", func(ev plugin_sdk.Event) { // Handle event})// Subscribe to all eventssubscriptionID, err := ctx.Services.Events().SubscribeAll(func(ev plugin_sdk.Event) { // Handle any event})// Unsubscribe when doneerr := ctx.Services.Events().Unsubscribe(subscriptionID)
Note on Events:
Events enable real-time communication between plugins and across the hub-spoke architecture
Direction controls routing: DirLocal (stays local), DirUp (edge→control), DirDown (control→edge)
Plugins should extract the service broker ID during initialization for Service API access:
Expandable
func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { // Extract broker ID for Service API access brokerIDStr := "" if id, ok := config["_service_broker_id"]; ok { brokerIDStr = id } else if id, ok := config["service_broker_id"]; ok { brokerIDStr = id } if brokerIDStr != "" { var brokerID uint32 fmt.Sscanf(brokerIDStr, "%d", &brokerID) ai_studio_sdk.SetServiceBrokerID(brokerID) } // Parse plugin-specific config p.apiKey = config["api_key"] return nil}
Plugins running in AI Studio use a session-based broker pattern for Service API access. Understanding this pattern is critical for plugins that need to call host APIs (like ai_studio_sdk.CreateLLM(), ai_studio_sdk.ListApps(), etc.).
Plugins that need early access to Service APIs should implement SessionAware:
type SessionAware interface { OnSessionReady(ctx Context) // Called when broker connection is established OnSessionClosing(ctx Context) // Called before session closes}
Important: The go-plugin broker only accepts ONE connection per broker ID. If your plugin uses both the Event Service and the Management Service API, whichever dials first will succeed, and the connection must be shared.The SDK handles this automatically, but you should warm up the connection early in OnSessionReady to ensure it’s established before any RPC calls come in:
Expandable
type MyPlugin struct { plugin_sdk.BasePlugin services plugin_sdk.ServiceBroker}func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { p.services = ctx.Services return nil}// OnSessionReady implements plugin_sdk.SessionAware// This is called when the session-based broker connection is established.func (p *MyPlugin) OnSessionReady(ctx plugin_sdk.Context) { log.Printf("Session ready - warming up service API connection...") // Eagerly establish the broker connection by making a simple API call. // This "warms up" the connection so subsequent RPC calls don't need to dial. if ai_studio_sdk.IsInitialized() { // Make a lightweight API call to establish the connection _, err := ai_studio_sdk.GetPluginsCount(context.Background()) if err != nil { log.Printf("Service API warmup failed: %v", err) } else { log.Printf("Service API connection established successfully") } }}// OnSessionClosing implements plugin_sdk.SessionAwarefunc (p *MyPlugin) OnSessionClosing(ctx plugin_sdk.Context) { log.Printf("Session closing - cleaning up resources")}
Without warmup, you may encounter “timeout waiting for connection info” errors when your plugin tries to use the Service API during an RPC call. This happens because:
The broker connection is time-sensitive
Dialing late (during RPC) may fail if the broker has timed out
Event subscriptions and Service API calls share the same connection