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
36 changes: 36 additions & 0 deletions docs/cli-agents/claude-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,42 @@ Claude Code supports multiple authentication methods. Choose the one that matche
claude
```

## Setup with the VS Code extension

If you prefer using Claude Code inside VS Code, install the [Claude Code extension](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code) and point it to Bifrost using the same environment variables you use for the CLI.

### 1. Install the extension

1. Open **Extensions** in VS Code (`Cmd+Shift+X` on macOS)
2. Search for **Claude Code**
3. Install **Claude Code** by Anthropic

### 2. Configure Bifrost endpoint

Add Bifrost variables in your editor settings:

```json
{
"claudeCode.environmentVariables": [
{
"name": "ANTHROPIC_BASE_URL",
"value": "http://localhost:8080/anthropic"
},
{
"name": "ANTHROPIC_DEFAULT_SONNET_MODEL",
"value": "vertex/claude-sonnet-4-6"
},
{
"name": "ANTHROPIC_DEFAULT_HAIKU_MODEL",
"value": "vertex/claude-haiku-4-5"
}
]
}
```

If virtual keys are enabled, also add `ANTHROPIC_API_KEY` in the same list (use your Bifrost virtual key value). If not, set it to `dummy`.


## Amazon Bedrock via Bifrost

### Setup
Expand Down
6 changes: 0 additions & 6 deletions docs/enterprise/setting-up-okta.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,6 @@ You can also map any custom attributes to any entity (role, team or business uni
- **Team and business unit mappings**: All matching rules apply — users can be placed on multiple teams and business units simultaneously.
- **Claim values**: Can be strings, arrays, or nested objects. Bifrost resolves dotted paths (e.g., `realm_access.roles`).

<Note>
If a user matches multiple role mapping rules, the highest privilege role is assigned. If no
mapping matches, the first user to sign in receives the **Admin** role, and subsequent users receive the **Viewer**
role.
</Note>

5. Click **Save Configuration**

<Warning>After saving, you'll need to restart your Bifrost server for the changes to take effect.</Warning>
Expand Down
6 changes: 5 additions & 1 deletion transports/bifrost-http/integrations/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ func createAnthropicMessagesRouteConfig(pathPrefix string, logger schemas.Logger
StreamConfig: &StreamConfig{
ResponsesStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostResponsesStreamResponse) (string, interface{}, error) {
if shouldUsePassthrough(ctx, resp.ExtraFields.Provider, resp.ExtraFields.OriginalModelRequested, resp.ExtraFields.ResolvedModelUsed) {
if resp.ExtraFields.RawResponse != nil {
// Skip passthrough for ContentPartAdded: it's a synthetic bifrost event whose
// RawResponse carries the parent content_block_start already emitted by OutputItemAdded.
// Passing through here would produce a duplicate content_block_start that causes
// the Anthropic SDK to error and drop all subsequent content_block_delta events.
if resp.ExtraFields.RawResponse != nil && resp.Type != schemas.ResponsesStreamResponseTypeContentPartAdded {
raw, ok := resp.ExtraFields.RawResponse.(string)
if !ok {
return "", nil, fmt.Errorf("expected RawResponse string, got %T", resp.ExtraFields.RawResponse)
Expand Down
102 changes: 99 additions & 3 deletions ui/app/workspace/governance/teams/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,105 @@
import { TeamsView } from "@enterprise/components/user-groups/teamsView";
import FullPageLoader from "@/components/fullPageLoader";
import { useDebouncedValue } from "@/hooks/useDebounce";
import { getErrorMessage, useGetCustomersQuery, useGetTeamsQuery, useGetVirtualKeysQuery } from "@/lib/store";
import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import TeamsTable from "@/app/workspace/governance/views/teamsTable";

const POLLING_INTERVAL = 5000;
const PAGE_SIZE = 25;

export default function GovernanceTeamsPage() {
const hasVirtualKeysAccess = useRbac(RbacResource.VirtualKeys, RbacOperation.View);
const hasCustomersAccess = useRbac(RbacResource.Customers, RbacOperation.View);
const hasTeamsAccess = useRbac(RbacResource.Teams, RbacOperation.View);
const shownErrorsRef = useRef(new Set<string>());

const [search, setSearch] = useState("");
const [offset, setOffset] = useState(0);
const debouncedSearch = useDebouncedValue(search, 300);

useEffect(() => {
setOffset(0);
}, [debouncedSearch]);

const {
data: virtualKeysData,
error: vkError,
isLoading: vkLoading,
} = useGetVirtualKeysQuery(undefined, {
skip: !hasVirtualKeysAccess,
pollingInterval: POLLING_INTERVAL,
});
const {
data: customersData,
error: customersError,
isLoading: customersLoading,
} = useGetCustomersQuery(undefined, {
skip: !hasCustomersAccess,
pollingInterval: POLLING_INTERVAL,
});
const {
data: teamsData,
error: teamsError,
isLoading: teamsLoading,
} = useGetTeamsQuery(
{
limit: PAGE_SIZE,
offset,
search: debouncedSearch || undefined,
},
{
skip: !hasTeamsAccess,
pollingInterval: POLLING_INTERVAL,
},
);

const teamsTotal = teamsData?.total_count ?? 0;

// Snap offset back when total shrinks past current page (e.g. delete last item on last page)
useEffect(() => {
if (!teamsData || offset < teamsTotal) return;
setOffset(teamsTotal === 0 ? 0 : Math.floor((teamsTotal - 1) / PAGE_SIZE) * PAGE_SIZE);
}, [teamsTotal, offset]);
Comment thread
akshaydeo marked this conversation as resolved.

const isLoading = vkLoading || customersLoading || teamsLoading;

useEffect(() => {
if (!vkError && !customersError && !teamsError) {
shownErrorsRef.current.clear();
return;
}
const errorKey = `${!!vkError}-${!!customersError}-${!!teamsError}`;
if (shownErrorsRef.current.has(errorKey)) return;
shownErrorsRef.current.add(errorKey);
if (vkError && customersError && teamsError) {
toast.error("Failed to load governance data.");
} else {
if (vkError) toast.error(`Failed to load virtual keys: ${getErrorMessage(vkError)}`);
if (customersError) toast.error(`Failed to load customers: ${getErrorMessage(customersError)}`);
if (teamsError) toast.error(`Failed to load teams: ${getErrorMessage(teamsError)}`);
}
}, [vkError, customersError, teamsError]);

if (isLoading) {
return <FullPageLoader />;
}

return (
<div className="mx-auto w-full max-w-7xl">
<TeamsView />
<TeamsTable
teams={teamsData?.teams || []}
totalCount={teamsData?.total_count || 0}
customers={customersData?.customers || []}
virtualKeys={virtualKeysData?.virtual_keys || []}
search={search}
debouncedSearch={debouncedSearch}
onSearchChange={setSearch}
offset={offset}
limit={PAGE_SIZE}
onOffsetChange={setOffset}
/>
</div>
);
}
}
4 changes: 2 additions & 2 deletions ui/app/workspace/virtual-keys/hooks/useVirtualKeyUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function useVirtualKeyUsage(vk: VirtualKey | null | undefined): {
const isManagedByProfile = managingProfile !== undefined;

const displayBudgets: Budget[] | undefined = managingProfile
? (managingProfile.budgets ?? []).map((line) => ({
? (managingProfile.budget_lines ?? []).map((line) => ({
id: line.id,
max_limit: line.max_limit,
reset_duration: line.reset_duration,
Expand All @@ -50,7 +50,7 @@ export function useVirtualKeyUsage(vk: VirtualKey | null | undefined): {
}))
: vk?.budgets;

const apRL = managingProfile?.rate_limit;
const apRL = managingProfile?.rate_limits;
const hasApRateLimit = !!(apRL && (apRL.token_max_limit != null || apRL.request_max_limit != null));
// When profile-managed, never fall back to raw VK rate limits (that would contradict the
// locked edit/delete UX). If the profile has no rate limit, displayRateLimit is undefined.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export default function VirtualKeyDetailSheet({ virtualKey, onClose }: VirtualKe
<div className="space-y-4">
<h3 className="font-semibold">
Budget Information
{isManagedByProfile && managingProfile?.budgets?.length ? (
{isManagedByProfile && managingProfile?.budget_lines?.length ? (
<span className="text-muted-foreground ml-2 text-xs font-normal">(from {managingProfile.name})</span>
) : null}
</h3>
Expand Down
2 changes: 1 addition & 1 deletion ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ export default function VirtualKeySheet({ virtualKey, teams, customers, defaultT
<fieldset
disabled={isManagedByProfile}
aria-disabled={isManagedByProfile}
inert={isManagedByProfile ? "" : undefined}
inert={isManagedByProfile ? true : undefined}
className={isManagedByProfile ? "pointer-events-none space-y-4 opacity-50" : "space-y-4"}
>
<div className="space-y-4">
Expand Down
Loading