-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmain.go
More file actions
123 lines (106 loc) · 3.52 KB
/
main.go
File metadata and controls
123 lines (106 loc) · 3.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// This is an example server demonstrating aibridge usage.
// Run with: go run *.go
package main
import (
"context"
"database/sql"
"net/http"
"os"
"regexp"
"cdr.dev/slog/v3"
"cdr.dev/slog/v3/sloggers/sloghuman"
"github.com/coder/aibridge"
aibcontext "github.com/coder/aibridge/context"
"github.com/coder/aibridge/mcp"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel"
_ "modernc.org/sqlite"
)
func main() {
ctx := context.Background()
logger := slog.Make(sloghuman.Sink(os.Stderr)).Leveled(slog.LevelDebug)
// Initialize SQLite database with WAL mode for better concurrency.
db, err := sql.Open("sqlite", "aibridge.db?_journal_mode=WAL&_busy_timeout=5000")
if err != nil {
logger.Fatal(ctx, "open database", slog.Error(err))
}
defer db.Close()
db.SetMaxOpenConns(1) // SQLite only supports one writer at a time.
if err := initSchema(db); err != nil {
logger.Fatal(ctx, "init schema", slog.Error(err))
}
recorder, err := NewSQLiteRecorder(db, logger)
if err != nil {
logger.Fatal(ctx, "create recorder", slog.Error(err))
}
defer recorder.Close()
// Configure providers.
providers := []aibridge.Provider{
aibridge.NewAnthropicProvider(aibridge.AnthropicConfig{
Key: os.Getenv("ANTHROPIC_API_KEY"),
}, nil),
aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{
Key: os.Getenv("OPENAI_API_KEY"),
}),
}
// Setup metrics.
reg := prometheus.NewRegistry()
metrics := aibridge.NewMetrics(reg)
// Setup tracing
tracer := otel.GetTracerProvider().Tracer("exampleTracer")
// Optional: Configure MCP server for centralized tool injection.
// DeepWiki provides free access to public GitHub repo documentation.
// See: https://mcp.deepwiki.com
var mcpProxy mcp.ServerProxier
deepwikiProxy, err := mcp.NewStreamableHTTPServerProxy(
"deepwiki", // server name (tools prefixed as bmcp_deepwiki_*)
"https://mcp.deepwiki.com/mcp", // no auth required for public repos
nil, // headers
regexp.MustCompile(`^ask_question$`), // allowlist: only ask_question tool
nil, // denylist
logger.Named("mcp.deepwiki"),
tracer,
)
if err != nil {
logger.Fatal(ctx, "create deepwiki mcp proxy", slog.Error(err))
}
mcpProxy = mcp.NewServerProxyManager(map[string]mcp.ServerProxier{"deepwiki": deepwikiProxy}, tracer)
if err := mcpProxy.Init(ctx); err != nil {
logger.Warn(ctx, "mcp init warning", slog.Error(err))
}
// Create the bridge with SQLite recorder.
bridge, err := aibridge.NewRequestBridge(
ctx,
providers,
recorder,
mcpProxy,
logger,
metrics,
tracer,
)
if err != nil {
logger.Fatal(ctx, "create bridge", slog.Error(err))
}
defer bridge.Shutdown(ctx)
// Setup HTTP routes.
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
mux.Handle("/", actorMiddleware(bridge))
logger.Info(ctx, "listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
logger.Fatal(ctx, "http server error", slog.Error(err))
}
}
// actorMiddleware injects actor identity into request context.
// In production, the user ID should be extracted from auth headers/tokens.
func actorMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("X-User-ID")
if userID == "" {
userID = "anonymous"
}
ctx := aibcontext.AsActor(r.Context(), userID, nil)
next.ServeHTTP(w, r.WithContext(ctx))
})
}