Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NOTTE_API_KEY=sk-notte-your-api-key-here
NOTTE_API_URL=https://api.notte.cc
NOTTE_CONSOLE_URL=https://console.notte.cc
NOTTE_REQUEST_ORIGIN=cli
6 changes: 6 additions & 0 deletions internal/api/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type NotteClient struct {
httpClient *http.Client
baseURL string
apiKey string
requestOrigin string
retryConfig *RetryConfig
circuitBreaker *CircuitBreaker
}
Expand All @@ -39,6 +40,13 @@ func WithCircuitBreaker(cb *CircuitBreaker) NotteClientOption {
}
}

// WithRequestOrigin sets a custom value for the x-notte-request-origin header
func WithRequestOrigin(origin string) NotteClientOption {
return func(c *NotteClient) {
c.requestOrigin = origin
}
}

// NewClient creates a new Notte API client
func NewClient(apiKey string, opts ...NotteClientOption) (*NotteClient, error) {
return NewClientWithURL(apiKey, DefaultBaseURL, "", opts...)
Expand All @@ -53,6 +61,7 @@ func NewClientWithURL(apiKey, baseURL, version string, opts ...NotteClientOption
nc := &NotteClient{
baseURL: baseURL,
apiKey: apiKey,
requestOrigin: "cli",
retryConfig: DefaultRetryConfig(),
circuitBreaker: NewCircuitBreaker(5, 30*time.Second),
}
Expand All @@ -68,6 +77,7 @@ func NewClientWithURL(apiKey, baseURL, version string, opts ...NotteClientOption
Transport: &resilientTransport{
apiKey: apiKey,
version: version,
requestOrigin: nc.requestOrigin,
retryConfig: nc.retryConfig,
circuitBreaker: nc.circuitBreaker,
base: &http.Transport{
Expand All @@ -94,6 +104,7 @@ func NewClientWithURL(apiKey, baseURL, version string, opts ...NotteClientOption
type resilientTransport struct {
apiKey string
version string
requestOrigin string
retryConfig *RetryConfig
circuitBreaker *CircuitBreaker
base http.RoundTripper
Expand All @@ -111,7 +122,7 @@ func (t *resilientTransport) RoundTrip(req *http.Request) (*http.Response, error
req.Header.Set("Authorization", "Bearer "+t.apiKey)

// Add tracking headers
req.Header.Set("x-notte-request-origin", "cli")
req.Header.Set("x-notte-request-origin", t.requestOrigin)
req.Header.Set("x-notte-sdk-version", t.version)

// Add idempotency key for mutating requests
Expand Down
28 changes: 28 additions & 0 deletions internal/api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,34 @@ func TestResilientTransport_AddsTrackingHeaders(t *testing.T) {
}
}

func TestResilientTransport_CustomRequestOrigin(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("x-notte-request-origin")
if origin != "my-custom-app" {
t.Errorf("got x-notte-request-origin %q, want %q", origin, "my-custom-app")
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{}`))
}))
defer server.Close()

client, err := NewClientWithURL("test-api-key", server.URL, "v1.2.3", WithRequestOrigin("my-custom-app"))
if err != nil {
t.Fatalf("failed to create client: %v", err)
}

req, _ := http.NewRequest("GET", server.URL+"/test", nil)
resp, err := client.httpClient.Do(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
t.Errorf("got status %d, want 200", resp.StatusCode)
}
}

func TestResilientTransport_RecordsFailureOn5xx(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
Expand Down
7 changes: 6 additions & 1 deletion internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ func GetClient() (*api.NotteClient, error) {
baseURL = api.DefaultBaseURL
}

return api.NewClientWithURL(apiKey, baseURL, Version)
var opts []api.NotteClientOption
if origin := os.Getenv(config.EnvRequestOrigin); origin != "" {
opts = append(opts, api.WithRequestOrigin(origin))
}

return api.NewClientWithURL(apiKey, baseURL, Version, opts...)
}

// GetContextWithTimeout wraps the provided context with a timeout
Expand Down
8 changes: 8 additions & 0 deletions internal/cmd/sessionstart_flags.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ const (
CurrentViewerURLFile = "current_viewer_url"
CurrentAgentFile = "current_agent"
CurrentSessionExpiryFile = "current_session_expiry"
DefaultRequestOrigin = "cli"
EnvConfigDir = "NOTTE_CONFIG_DIR"
EnvAPIURL = "NOTTE_API_URL"
EnvConsoleURL = "NOTTE_CONSOLE_URL"
EnvRequestOrigin = "NOTTE_REQUEST_ORIGIN"
EnvSessionID = "NOTTE_SESSION_ID"
EnvFunctionID = "NOTTE_FUNCTION_ID"
EnvAgentID = "NOTTE_AGENT_ID"
Expand Down
Loading