Plugin Migration Guide
Overview
Section titled “Overview”DeepIntShield v1.4.x introduces a new plugin interface for HTTP transport layer interception. This guide helps you migrate existing plugins from the v1.3.x TransportInterceptor pattern to the v1.4.x HTTPTransportPreHook and HTTPTransportPostHook pattern.
What Changed?
Section titled “What Changed?”The HTTP transport interception mechanism changed from a simple function that receives and returns headers/body to a dual-hook pattern that works with both native .so plugins and WASM plugins.
Key Differences
Section titled “Key Differences”| Aspect | v1.3.x (TransportInterceptor) | v1.4.x+ (Pre/Post Hooks) |
|---|---|---|
| Signature | TransportInterceptor(ctx, url, headers, body) | HTTPTransportPreHook(ctx, req) + HTTPTransportPostHook(ctx, req, resp) |
| Return type | (headers, body, error) | Pre: (*HTTPResponse, error), Post: error |
| Request type | Separate headers map, body map | Unified *HTTPRequest struct |
| Response access | Not available | Post-hook receives *HTTPResponse |
| Modification | Return modified maps | Modify req/resp in-place |
| Short-circuit | Return error | Return *HTTPResponse |
| WASM support | No | Yes |
| Context | Limited DeepIntShieldContext | Full *DeepIntShieldContext with SetValue/Value |
Why the Change?
Section titled “Why the Change?”The new dual-hook pattern provides:
- WASM plugin support - Serializable types work across WASM boundary
- Response interception - Post-hook can modify responses before returning to client
- Simpler API - No middleware wrapper, direct function call
- Better testability - No fasthttp dependency in plugin tests
- Full context access - DeepIntShieldContext available for sharing data between hooks
- Custom response short-circuits - Return a full response to short-circuit
Migration Steps
Section titled “Migration Steps”Step 1: Update Imports
Section titled “Step 1: Update Imports”Remove the fasthttp import if present:
import ( "fmt"
"github.com/maximhq/deepintshield/core/schemas" // Remove: "github.com/valyala/fasthttp")Step 2: Replace the Function
Section titled “Step 2: Replace the Function”Before (v1.3.x):
// TransportInterceptor modifies raw HTTP headers and bodyfunc TransportInterceptor(ctx *schemas.DeepIntShieldContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error) { // Add custom header headers["X-Custom-Header"] = "value"
// Modify body body["custom_field"] = "custom_value"
return headers, body, nil}After (v1.4.x+):
// HTTPTransportPreHook intercepts requests BEFORE they enter DeepIntShield core// Modify req in-place. Return (*HTTPResponse, nil) to short-circuit.func HTTPTransportPreHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error) { // Add custom header (in-place modification) req.Headers["x-custom-header"] = "value"
// Modify body (in-place modification) var body map[string]any sonic.Unmarshal(req.Body, &body) body["custom_field"] = "custom_value" req.Body, _ = sonic.Marshal(body)
// Store values in context for use in post-hook ctx.SetValue(schemas.DeepIntShieldContextKey("my-plugin-key"), "my-value")
// Return nil to continue, or return &HTTPResponse{} to short-circuit return nil, nil}
// HTTPTransportPostHook intercepts responses AFTER they exit DeepIntShield core// Modify resp in-place. Called in reverse order of pre-hooks.func HTTPTransportPostHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error { // Add response header resp.Headers["x-processed-by"] = "my-plugin"
// Read values set in pre-hook if val := ctx.Value(schemas.DeepIntShieldContextKey("my-plugin-key")); val != nil { fmt.Println("Context value:", val) }
// Return nil to continue, or return error to short-circuit return nil}Step 3: Update Body Modification Logic
Section titled “Step 3: Update Body Modification Logic”In v1.3.x, you received the body as a map[string]any. In v1.4.x, you work with req.Body bytes:
Before (v1.3.x):
func TransportInterceptor(ctx *schemas.DeepIntShieldContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error) { // Direct map access body["model"] = "gpt-4" return headers, body, nil}After (v1.4.x+):
import "github.com/bytedance/sonic"
func HTTPTransportPreHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error) { // Parse body var body map[string]any if err := sonic.Unmarshal(req.Body, &body); err == nil { // Modify body body["model"] = "gpt-4" // Update req.Body in-place req.Body, _ = sonic.Marshal(body) } return nil, nil}
func HTTPTransportPostHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error { // Modify response body if needed var respBody map[string]any if err := sonic.Unmarshal(resp.Body, &respBody); err == nil { respBody["plugin_processed"] = true resp.Body, _ = sonic.Marshal(respBody) } return nil}Common Migration Patterns
Section titled “Common Migration Patterns”Adding Headers
Section titled “Adding Headers”v1.3.x:
headers["authorization"] = "Bearer " + tokenreturn headers, body, nilv1.4.x+:
// In HTTPTransportPreHook - modify request headersreq.Headers["authorization"] = "Bearer " + tokenreturn nil, nil
// In HTTPTransportPostHook - modify response headersresp.Headers["x-request-id"] = requestIDreturn nilReading Headers
Section titled “Reading Headers”v1.3.x:
apiKey := headers["X-API-Key"]v1.4.x+:
// Use case-insensitive helper for reading (recommended)apiKey := req.CaseInsensitiveHeaderLookup("X-API-Key")
// Or direct map access (case-sensitive)apiKey := req.Headers["x-api-key"]Conditional Processing
Section titled “Conditional Processing”v1.3.x:
func TransportInterceptor(ctx *schemas.DeepIntShieldContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error) { if headers["x-skip-processing"] == "true" { return headers, body, nil } // Process... return headers, body, nil}v1.4.x+:
func HTTPTransportPreHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error) { if req.CaseInsensitiveHeaderLookup("x-skip-processing") == "true" { return nil, nil // Continue without modification } // Process... return nil, nil}
func HTTPTransportPostHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error { // Post-hook always runs unless pre-hook short-circuited return nil}Error Handling / Short-Circuit
Section titled “Error Handling / Short-Circuit”v1.3.x:
func TransportInterceptor(ctx *schemas.DeepIntShieldContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error) { if headers["x-api-key"] == "" { return nil, nil, fmt.Errorf("missing API key") } return headers, body, nil}v1.4.x+:
func HTTPTransportPreHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error) { if req.CaseInsensitiveHeaderLookup("x-api-key") == "" { // Return a custom response to short-circuit return &schemas.HTTPResponse{ StatusCode: 401, Headers: map[string]string{"Content-Type": "application/json"}, Body: []byte(`{"error": "missing API key"}`), }, nil } return nil, nil}
func HTTPTransportPostHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error { // Not called if pre-hook short-circuited return nil}Accessing Request Method and Path
Section titled “Accessing Request Method and Path”v1.3.x:
// url parameter contained the full URLfunc TransportInterceptor(ctx *schemas.DeepIntShieldContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error) { // Limited access to URL return headers, body, nil}v1.4.x+:
func HTTPTransportPreHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error) { // Full access to request properties method := req.Method // "GET", "POST", etc. path := req.Path // "/v1/chat/completions" query := req.Query // map[string]string of query params pathParams := req.PathParams // map[string]string of path variables (e.g., {model}) return nil, nil}
func HTTPTransportPostHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error { // Access both request and response statusCode := resp.StatusCode responseHeaders := resp.Headers responseBody := resp.Body _ = statusCode // Use variables... _ = responseHeaders _ = responseBody return nil}Testing Your Migration
Section titled “Testing Your Migration”-
Build your updated plugin:
Terminal window go build -buildmode=plugin -o my-plugin.so main.go -
Update DeepIntShield to v1.4.x:
Terminal window go get github.com/maximhq/deepintshield/core@v1.4.0 -
Test with a simple request:
Terminal window curl -X POST http://localhost:8080/v1/chat/completions \-H "Content-Type: application/json" \-d '{"model": "openai/gpt-4o-mini", "messages": [{"role": "user", "content": "Hello"}]}' -
Verify logs show both hooks being called:
HTTPTransportPreHook calledPreLLMHook calledPostLLMHook calledHTTPTransportPostHook called
Troubleshooting
Section titled “Troubleshooting”Plugin fails to load after migration
Section titled “Plugin fails to load after migration”Error: plugin: symbol TransportInterceptor not found
This error occurs if DeepIntShield v1.4.x is looking for the old function. Make sure:
- You’ve updated to
HTTPTransportPreHookandHTTPTransportPostHook - The function signatures match exactly:
func HTTPTransportPreHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error)func HTTPTransportPostHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error
- You’ve rebuilt the plugin with the correct core version
Body modification not working
Section titled “Body modification not working”Make sure you’re assigning back to req.Body in the pre-hook:
// Wrong - body changes lostvar body map[string]anysonic.Unmarshal(req.Body, &body)body["model"] = "gpt-4"// Missing: req.Body = ...
// Correct - body changes appliedvar body map[string]anysonic.Unmarshal(req.Body, &body)body["model"] = "gpt-4"req.Body, _ = sonic.Marshal(body) // Assign back!Response modification not working
Section titled “Response modification not working”Make sure you’re modifying resp in the post-hook:
func HTTPTransportPostHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error { // Modify response headers resp.Headers["x-custom-header"] = "value"
// Modify response body var body map[string]any sonic.Unmarshal(resp.Body, &body) body["extra_field"] = "value" resp.Body, _ = sonic.Marshal(body)
return nil}Headers not being set
Section titled “Headers not being set”Make sure you’re modifying req.Headers or resp.Headers directly:
// Set request header in pre-hookreq.Headers["x-custom-header"] = "value"
// Set response header in post-hookresp.Headers["x-custom-header"] = "value"
// Read headers using case-insensitive helpervalue := req.CaseInsensitiveHeaderLookup("X-Custom-Header")Context values not available in post-hook
Section titled “Context values not available in post-hook”Make sure you’re using the correct context key type:
// In pre-hook - set valuectx.SetValue(schemas.DeepIntShieldContextKey("my-key"), "my-value")
// In post-hook - read valueif val := ctx.Value(schemas.DeepIntShieldContextKey("my-key")); val != nil { // Use val}Streaming Chunk Hook (v1.4.x)
Section titled “Streaming Chunk Hook (v1.4.x)”DeepIntShield v1.4.x introduces a new hook for intercepting streaming response chunks:
HTTPTransportStreamChunkHook
Section titled “HTTPTransportStreamChunkHook”This hook is called for each chunk during streaming responses, allowing plugins to modify or filter chunks before they’re sent to the client.
// HTTPTransportStreamChunkHook intercepts streaming chunks BEFORE they're written to the client.// Modify chunk data or return nil to skip the chunk entirely.// Only called for streaming responses when using HTTP transport (deepintshield-http).func HTTPTransportStreamChunkHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, chunk *schemas.DeepIntShieldStreamChunk) (*schemas.DeepIntShieldStreamChunk, error) { // chunk is a typed struct containing one of: // - DeepIntShieldTextCompletionResponse (text completion streaming) // - DeepIntShieldChatResponse (chat completion streaming) // - DeepIntShieldResponsesStreamResponse (responses API streaming) // - DeepIntShieldSpeechStreamResponse (speech synthesis streaming) // - DeepIntShieldTranscriptionStreamResponse (transcription streaming) // - DeepIntShieldImageGenerationStreamResponse (image generation streaming) // - DeepIntShieldError (error during streaming)
// Return chunk unchanged to pass through return chunk, nil
// Return nil to skip/filter this chunk // return nil, nil
// Return modified chunk // modifiedChunk := &schemas.DeepIntShieldStreamChunk{DeepIntShieldChatResponse: ...} // return modifiedChunk, nil}Key differences from HTTPTransportPostHook:
| Aspect | HTTPTransportPostHook | HTTPTransportStreamChunkHook |
|---|---|---|
| When called | After complete response | Per-chunk during streaming |
| Input | Full HTTPResponse | *DeepIntShieldStreamChunk (typed struct) |
| Can modify | Full response | Individual chunk struct |
| Can skip | N/A | Return nil to skip chunk |
Migration for Existing Plugins
Section titled “Migration for Existing Plugins”If your plugin implements HTTPTransportPostHook and you want to also handle streaming responses, add the new hook:
// Existing hook for non-streaming responsesfunc HTTPTransportPostHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error { // Handle complete responses return nil}
// NEW: Add this for streaming responsesfunc HTTPTransportStreamChunkHook(ctx *schemas.DeepIntShieldContext, req *schemas.HTTPRequest, chunk *schemas.DeepIntShieldStreamChunk) (*schemas.DeepIntShieldStreamChunk, error) { // Handle streaming chunks (typed struct, not raw bytes) // Return chunk unchanged if no modification needed return chunk, nil}Need Help?
Section titled “Need Help?”- Discord Community: Join our Discord
- GitHub Issues: Report bugs or request features
- Writing Plugins Guide: Full plugin documentation