-
Notifications
You must be signed in to change notification settings - Fork 3
feat: conditionally add actor headers #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package intercept | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| ant_option "github.com/anthropics/anthropic-sdk-go/option" | ||
| "github.com/coder/aibridge/context" | ||
| oai_option "github.com/openai/openai-go/v3/option" | ||
| ) | ||
|
|
||
| const ( | ||
| prefix = "X-AI-Bridge-Actor" | ||
| ) | ||
|
|
||
| func ActorIDHeader() string { | ||
| return fmt.Sprintf("%s-ID", prefix) | ||
| } | ||
|
|
||
| func ActorMetadataHeader(name string) string { | ||
| return fmt.Sprintf("%s-Metadata-%s", prefix, name) | ||
| } | ||
|
|
||
| func IsActorHeader(name string) bool { | ||
| return strings.HasPrefix(strings.ToLower(name), strings.ToLower(prefix)) | ||
| } | ||
|
|
||
| // ActorHeadersAsOpenAIOpts produces a slice of headers using OpenAI's RequestOption type. | ||
| func ActorHeadersAsOpenAIOpts(actor *context.Actor) []oai_option.RequestOption { | ||
| var opts []oai_option.RequestOption | ||
|
|
||
| headers := headersFromActor(actor) | ||
| if len(headers) == 0 { | ||
| return nil | ||
| } | ||
|
|
||
| for k, v := range headers { | ||
| // [k] will be canonicalized, see [http.Header]'s [Add] method. | ||
| opts = append(opts, oai_option.WithHeaderAdd(k, v)) | ||
| } | ||
|
|
||
| return opts | ||
| } | ||
|
|
||
| // ActorHeadersAsAnthropicOpts produces a slice of headers using Anthropic's RequestOption type. | ||
| func ActorHeadersAsAnthropicOpts(actor *context.Actor) []ant_option.RequestOption { | ||
| var opts []ant_option.RequestOption | ||
|
|
||
| headers := headersFromActor(actor) | ||
| if len(headers) == 0 { | ||
| return nil | ||
| } | ||
|
|
||
| for k, v := range headers { | ||
| // [k] will be canonicalized, see [http.Header]'s [Add] method. | ||
| opts = append(opts, ant_option.WithHeaderAdd(k, v)) | ||
| } | ||
|
|
||
| return opts | ||
| } | ||
|
|
||
| // headersFromActor produces a map of headers from a given [context.Actor]. | ||
| func headersFromActor(actor *context.Actor) map[string]string { | ||
| if actor == nil { | ||
| return nil | ||
| } | ||
|
|
||
| headers := make(map[string]string, len(actor.Metadata)+1) | ||
|
|
||
| // Add actor ID. | ||
| headers[ActorIDHeader()] = actor.ID | ||
dannykopping marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Add headers for provided metadata. | ||
| for k, v := range actor.Metadata { | ||
| headers[ActorMetadataHeader(k)] = fmt.Sprintf("%v", v) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we should probably be sanitizing the headers/vallues to make sure they don't have whitespaces or invalid characters
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whitespace is fine in headers (well, not newlines) and I believe
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or did you perhaps mean the header key? |
||
| } | ||
|
|
||
| return headers | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package intercept | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/coder/aibridge/context" | ||
| "github.com/coder/aibridge/recorder" | ||
| "github.com/google/uuid" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestNilActor(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| require.Nil(t, ActorHeadersAsOpenAIOpts(nil)) | ||
| require.Nil(t, ActorHeadersAsAnthropicOpts(nil)) | ||
| } | ||
|
|
||
| func TestBasic(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| actorID := uuid.NewString() | ||
| actor := &context.Actor{ | ||
| ID: actorID, | ||
| } | ||
|
|
||
| // We can't peek inside since these opts require an internal type to apply onto. | ||
| // All we can do is check the length. | ||
| // See TestActorHeaders for an integration test. | ||
| oaiOpts := ActorHeadersAsOpenAIOpts(actor) | ||
| require.Len(t, oaiOpts, 1) | ||
| antOpts := ActorHeadersAsAnthropicOpts(actor) | ||
| require.Len(t, antOpts, 1) | ||
| } | ||
|
|
||
| func TestBasicAndMetadata(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| actorID := uuid.NewString() | ||
| actor := &context.Actor{ | ||
| ID: actorID, | ||
| Metadata: recorder.Metadata{ | ||
| "This": "That", | ||
| "And": "The other", | ||
| }, | ||
| } | ||
|
|
||
| // We can't peek inside since these opts require an internal type to apply onto. | ||
| // All we can do is check the length. | ||
| // See TestActorHeaders for an integration test. | ||
| oaiOpts := ActorHeadersAsOpenAIOpts(actor) | ||
| require.Len(t, oaiOpts, 1+len(actor.Metadata)) | ||
| antOpts := ActorHeadersAsAnthropicOpts(actor) | ||
| require.Len(t, antOpts, 1+len(actor.Metadata)) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.