fix(ai): point quickstart to owned license endpoint

This commit is contained in:
rcourtman 2026-03-25 17:33:51 +00:00
parent 9f4d4fa247
commit 6f7ceee404
3 changed files with 75 additions and 6 deletions

View file

@ -80,6 +80,14 @@ governed floor is ready.
`internal/ai/` is the live backend AI engine. It owns chat execution, Patrol
orchestration, findings generation, investigation support, quickstart and
provider selection, remediation flow, and cost persistence.
That quickstart ownership includes the public proxy dependency under
`internal/ai/providers/quickstart.go`: the runtime must default to the owned
commercial API edge at `https://license.pulserelay.pro/v1/quickstart/patrol`
instead of depending on an ungoverned standalone hostname. Runtime overrides
may exist only as an explicit environment-controlled rollout escape hatch, and
the canonical quickstart proxy contract remains an OpenAI-compatible server-owned
surface that lives behind the public license API rather than a tenant-local or
mobile-local adapter.
The same runtime ownership now includes the customer-facing AI usage and cost
surface. `frontend-modern/src/components/AI/AICostDashboard.tsx` is the

View file

@ -7,15 +7,17 @@ import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/rs/zerolog/log"
)
const (
// quickstartProxyURL is the Pulse-hosted proxy that forwards to MiniMax.
// defaultQuickstartProxyURL is the owned public quickstart proxy surface.
// The API key lives server-side — self-hosted binaries never see it.
quickstartProxyURL = "https://api.pulserelay.pro/v1/quickstart/patrol"
defaultQuickstartProxyURL = "https://license.pulserelay.pro/v1/quickstart/patrol"
quickstartModel = "minimax-2.5m"
quickstartRequestTimeout = 300 * time.Second // 5 minutes
@ -24,14 +26,21 @@ const (
)
// QuickstartClient implements the Provider interface for the Pulse-hosted
// quickstart proxy. It forwards patrol chat requests to the MiniMax 2.5M
// model via api.pulserelay.pro so users don't need their own API key.
// quickstart proxy. It forwards patrol chat requests through the public
// commercial API edge so users don't need their own API key.
type QuickstartClient struct {
// licenseID identifies the workspace (for rate limiting / credit tracking server-side).
licenseID string
client *http.Client
}
func quickstartProxyURL() string {
if override := strings.TrimSpace(os.Getenv("PULSE_AI_QUICKSTART_PROXY_URL")); override != "" {
return override
}
return defaultQuickstartProxyURL
}
// NewQuickstartClient creates a quickstart provider that uses the hosted proxy.
func NewQuickstartClient(licenseID string) *QuickstartClient {
return &QuickstartClient{
@ -90,7 +99,7 @@ func (c *QuickstartClient) Chat(ctx context.Context, req ChatRequest) (*ChatResp
}
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, quickstartProxyURL, bytes.NewReader(body))
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, quickstartProxyURL(), bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("quickstart: create request: %w", err)
}
@ -144,7 +153,7 @@ func (c *QuickstartClient) Chat(ctx context.Context, req ChatRequest) (*ChatResp
// TestConnection validates connectivity to the quickstart proxy.
func (c *QuickstartClient) TestConnection(ctx context.Context) error {
// Simple connectivity check — send a minimal request.
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, quickstartProxyURL, bytes.NewReader([]byte(`{"messages":[],"license_id":"`+c.licenseID+`"}`)))
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, quickstartProxyURL(), bytes.NewReader([]byte(`{"messages":[],"license_id":"`+c.licenseID+`"}`)))
if err != nil {
return fmt.Errorf("quickstart: create test request: %w", err)
}

View file

@ -0,0 +1,52 @@
package providers
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestQuickstartProxyURL_Default(t *testing.T) {
t.Setenv("PULSE_AI_QUICKSTART_PROXY_URL", "")
if got := quickstartProxyURL(); got != defaultQuickstartProxyURL {
t.Fatalf("quickstartProxyURL()=%q want %q", got, defaultQuickstartProxyURL)
}
}
func TestQuickstartClientChat_UsesOverrideProxyURL(t *testing.T) {
var seenLicenseID string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req quickstartRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Fatalf("decode request: %v", err)
}
seenLicenseID = req.LicenseID
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(quickstartResponse{
Content: "hello",
Model: quickstartModel,
StopReason: "end_turn",
}); err != nil {
t.Fatalf("encode response: %v", err)
}
}))
defer server.Close()
t.Setenv("PULSE_AI_QUICKSTART_PROXY_URL", server.URL)
client := NewQuickstartClient("lic_test")
resp, err := client.Chat(context.Background(), ChatRequest{
Messages: []Message{{Role: "user", Content: "Hi"}},
})
if err != nil {
t.Fatalf("Chat(): %v", err)
}
if seenLicenseID != "lic_test" {
t.Fatalf("license_id=%q want lic_test", seenLicenseID)
}
if resp.Content != "hello" {
t.Fatalf("content=%q want hello", resp.Content)
}
}