mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-10 03:51:54 +00:00
fix(ai): point quickstart to owned license endpoint
This commit is contained in:
parent
9f4d4fa247
commit
6f7ceee404
3 changed files with 75 additions and 6 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
52
internal/ai/providers/quickstart_test.go
Normal file
52
internal/ai/providers/quickstart_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue