Thanks to visit codestin.com
Credit goes to github.com

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
4 changes: 4 additions & 0 deletions site/.knip.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"ignore": [
"**/*Generated.ts",
"src/api/chatModelOptions.ts",
// TODO(ai-settings): aiProviders.ts queries are staged in PR 2 of the
// AI settings stack; they are consumed by the provider pages in PR 4.
// Remove this exclusion once those pages land.
"src/api/queries/aiProviders.ts",
// TODO(devtools): debugPanelUtils.ts is staged in PR 7; its exports are
// consumed by the Debug panel components in PRs 8 and 9. Remove this
// exclusion once the panel components land.
Expand Down
4 changes: 4 additions & 0 deletions site/permissions.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
"object": { "resource_type": "aibridge_interception", "any_org": true },
"action": "read"
},
"viewAnyAIProvider": {
"object": { "resource_type": "ai_provider" },
"action": "read"
},
"createOAuth2App": {
"object": { "resource_type": "oauth2_app" },
"action": "create"
Expand Down
69 changes: 55 additions & 14 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3090,6 +3090,47 @@ class ApiMethods {
const response = await this.axios.get<string[]>(url);
return response.data;
};

getAIProviders = async (): Promise<TypesGen.AIProvider[]> => {
const response = await this.axios.get<TypesGen.AIProvider[]>(
"/api/v2/ai/providers",
);
return response.data;
};

getAIProvider = async (idOrName: string): Promise<TypesGen.AIProvider> => {
const response = await this.axios.get<TypesGen.AIProvider>(
`/api/v2/ai/providers/${encodeURIComponent(idOrName)}`,
);
return response.data;
};

createAIProvider = async (
req: TypesGen.CreateAIProviderRequest,
): Promise<TypesGen.AIProvider> => {
const response = await this.axios.post<TypesGen.AIProvider>(
"/api/v2/ai/providers",
req,
);
return response.data;
};

updateAIProvider = async (
idOrName: string,
req: TypesGen.UpdateAIProviderRequest,
): Promise<TypesGen.AIProvider> => {
const response = await this.axios.patch<TypesGen.AIProvider>(
`/api/v2/ai/providers/${encodeURIComponent(idOrName)}`,
req,
);
return response.data;
};

deleteAIProvider = async (idOrName: string): Promise<void> => {
await this.axios.delete(
`/api/v2/ai/providers/${encodeURIComponent(idOrName)}`,
);
};
}

export type TaskFeedbackRating = "good" | "okay" | "bad";
Expand Down Expand Up @@ -3162,6 +3203,20 @@ class ExperimentalApiMethods {
};

// Chat API methods
getChatACL = async (chatId: string): Promise<TypesGen.ChatACL> => {
const response = await this.axios.get<TypesGen.ChatACL>(
`/api/experimental/chats/${chatId}/acl`,
);
return response.data;
};

updateChatACL = async (
chatId: string,
req: TypesGen.UpdateChatACL,
): Promise<void> => {
await this.axios.patch(`/api/experimental/chats/${chatId}/acl`, req);
};

getChats = async (req?: {
after_id?: string;
limit?: number;
Expand All @@ -3179,20 +3234,6 @@ class ExperimentalApiMethods {
);
return response.data;
};
getChatACL = async (chatId: string): Promise<TypesGen.ChatACL> => {
const response = await this.axios.get<TypesGen.ChatACL>(
`/api/experimental/chats/${chatId}/acl`,
);
return response.data;
};

updateChatACL = async (
chatId: string,
req: TypesGen.UpdateChatACL,
): Promise<void> => {
await this.axios.patch(`/api/experimental/chats/${chatId}/acl`, req);
};

getChatMessages = async (
chatId: string,
opts?: { before_id?: number; after_id?: number; limit?: number },
Expand Down
55 changes: 55 additions & 0 deletions site/src/api/queries/aiProviders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { QueryClient } from "react-query";
import { API } from "#/api/api";
import type {
AIProvider,
CreateAIProviderRequest,
UpdateAIProviderRequest,
} from "#/api/typesGenerated";

const aiProvidersListKey = ["ai", "providers"] as const;

const aiProviderKeyFor = (idOrName: string) =>
[...aiProvidersListKey, idOrName] as const;

export const aiProvidersList = () => ({
queryKey: aiProvidersListKey,
queryFn: (): Promise<AIProvider[]> => API.getAIProviders(),
});

export const aiProvider = (idOrName: string) => ({
queryKey: aiProviderKeyFor(idOrName),
queryFn: (): Promise<AIProvider> => API.getAIProvider(idOrName),
});

export const createAIProviderMutation = (queryClient: QueryClient) => ({
mutationFn: (request: CreateAIProviderRequest): Promise<AIProvider> =>
API.createAIProvider(request),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: aiProvidersListKey });
},
});

export const updateAIProviderMutation = (
queryClient: QueryClient,
idOrName: string,
) => ({
mutationFn: (request: UpdateAIProviderRequest): Promise<AIProvider> =>
API.updateAIProvider(idOrName, request),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: aiProvidersListKey });
await queryClient.invalidateQueries({
queryKey: aiProviderKeyFor(idOrName),
});
},
});

export const deleteAIProviderMutation = (
queryClient: QueryClient,
idOrName: string,
) => ({
mutationFn: () => API.deleteAIProvider(idOrName),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: aiProvidersListKey });
queryClient.removeQueries({ queryKey: aiProviderKeyFor(idOrName) });
},
});
3 changes: 2 additions & 1 deletion site/src/modules/permissions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export const canViewDeploymentSettings = (
permissions.viewAllUsers ||
permissions.viewAnyGroup ||
permissions.viewNotificationTemplate ||
permissions.viewOrganizationIDPSyncSettings)
permissions.viewOrganizationIDPSyncSettings ||
permissions.viewAnyAIProvider)
);
};

Expand Down
69 changes: 69 additions & 0 deletions site/src/testHelpers/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3298,6 +3298,7 @@ export const MockPermissions: Permissions = {
viewAnyIdpSyncSettings: true,
viewAnyMembers: true,
viewAnyAIBridgeInterception: true,
viewAnyAIProvider: true,
createOAuth2App: true,
editOAuth2App: true,
deleteOAuth2App: true,
Expand Down Expand Up @@ -3332,6 +3333,7 @@ export const MockNoPermissions: Permissions = {
viewAnyIdpSyncSettings: false,
viewAnyMembers: false,
viewAnyAIBridgeInterception: true,
viewAnyAIProvider: false,
createOAuth2App: false,
editOAuth2App: false,
deleteOAuth2App: false,
Expand Down Expand Up @@ -5515,3 +5517,70 @@ export const MockSession: TypesGen.AIBridgeSession = {
last_prompt: "But *can* I really fix it?",
last_active_at: "2026-03-09T10:28:15.03152Z",
};

/** @lintignore Consumed by component stories landing in the next PR of the AI settings stack. */
export const MockAIProviderOpenAI: TypesGen.AIProvider = {
id: "7a5d6b6a-5f02-4a9c-9c4e-2b3e2a3d2f01",
type: "openai",
name: "openai",
display_name: "OpenAI",
base_url: "https://api.openai.com",
enabled: false,
api_keys: [
{
id: "6d7c1f3a-1f0b-4a12-a1b5-0fb1f8e72e01",
masked: "sk-***\u2026***ABCD",
created_at: "2026-05-14T10:00:00Z",
},
],
settings: null as unknown as TypesGen.AIProviderSettings,
created_at: "2026-05-14T10:00:00Z",
updated_at: "2026-05-14T10:00:00Z",
};

/** @lintignore Consumed by component stories landing in the next PR of the AI settings stack. */
export const MockAIProviderAnthropic: TypesGen.AIProvider = {
id: "4f81f1ee-37c1-4a37-a9d5-7e0c1c8c0c11",
type: "anthropic",
name: "anthropic",
display_name: "Anthropic",
base_url: "https://api.anthropic.com",
enabled: false,
api_keys: [],
settings: null as unknown as TypesGen.AIProviderSettings,
created_at: "2026-05-14T10:00:00Z",
updated_at: "2026-05-14T10:00:00Z",
};

/**
* Bedrock providers come over the wire with `type: "anthropic"` and a
* `settings._type: "bedrock"` discriminator. `isBedrockProvider` and the
* backend (see `coderd/ai_providers.go`) enforce this convention.
*
* @lintignore Consumed by component stories landing in the next PR of the AI settings stack.
*/
export const MockAIProviderBedrock: TypesGen.AIProvider = {
id: "9c2e3b41-2e9f-4c97-9a4f-2e1a3d8f9f21",
type: "anthropic",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use bedrock type in Bedrock mock provider

MockAIProviderBedrock is defined with type: "anthropic", which makes this fixture represent the wrong provider kind. Any stories or tests that branch on provider type (for example, showing Bedrock-only settings fields or validation) will exercise Anthropic behavior instead and can hide real Bedrock regressions.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushing back on this one. The Bedrock wire representation is type: "anthropic" + settings._type: "bedrock", not type: "bedrock". Sources:

  • The SDK constants in codersdk/aiproviders.go document that AIProviderBedrockSettings is "Only meaningful for AIProviderTypeAnthropic", and the AIProviderTypeBedrock constant is reserved for future native gateway support.
  • coderd/ai_providers.go:324 rejects Bedrock settings on any provider whose type is not database.AiProviderTypeAnthropic.
  • isBedrockProvider (and every other helper that branches on it) requires provider.type === "anthropic" before reading the discriminator.

Flipping the mock to "bedrock" would make stories and tests assert against a wire shape the backend cannot emit. I left the mock at type: "anthropic" and added a JSDoc comment on the export explaining the convention so future readers do not get caught by the same trap.

Reply from Coder Agents on behalf of Jake Howell.

name: "bedrock",
display_name: "Bedrock",
base_url: "https://bedrock-runtime.us-east-2.amazonaws.com",
enabled: true,
api_keys: [],
settings: {
_type: "bedrock",
_version: 1,
region: "us-east-2",
model: "anthropic.claude-opus-4-7",
small_fast_model: "anthropic.claude-haiku-4-5",
} as unknown as TypesGen.AIProviderSettings,
created_at: "2026-05-14T10:00:00Z",
updated_at: "2026-05-14T10:00:00Z",
};

/** @lintignore Consumed by page stories landing in PR 4 of the AI settings stack. */
export const MockAIProviders: TypesGen.AIProvider[] = [
MockAIProviderOpenAI,
MockAIProviderAnthropic,
MockAIProviderBedrock,
];
Loading