Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c1b0cd9
feat(ai): add AI provider selector and key management
olexii4 Apr 2, 2026
6818fce
fix(ai): adjust icon alignment and tooltip spacing
olexii4 Apr 4, 2026
521c0ff
fix(ai): dispatch request action after authorization check
olexii4 Apr 4, 2026
90a2bdb
fix(ai): improve AI Provider Keys table for small screens
olexii4 Apr 4, 2026
8f953e9
fix(ai): add hover effect to API Key tooltip icon
olexii4 Apr 4, 2026
08ce5d5
fix(ai): polish UI alignment and move AI Providers tab to end
olexii4 Apr 5, 2026
e39c09e
feat(ai): move AI registry to ConfigMap and hide widgets when empty
olexii4 Apr 5, 2026
b9b6609
test(ai): add unit tests for AI registry and provider components
olexii4 Apr 6, 2026
d569983
fix(ai): update injector image references from okurinny to oorel
olexii4 Apr 6, 2026
89a9e11
fix(ai): update OverviewTab snapshots after AiToolFormGroup condition…
olexii4 Apr 6, 2026
62b52e6
fix(ai): add aiConfig state to startWorkspace test mock store
olexii4 Apr 6, 2026
71e3465
fix(ai): make setupCommand non-fatal in postStart lifecycle hook
olexii4 Apr 6, 2026
aada447
feat(ai): auto-update outdated tools, guard key status, bulk delete, …
olexii4 Apr 8, 2026
9c2cfb5
refactor(ai): move cleanup into init containers and remove postStart …
olexii4 Apr 8, 2026
b35c180
docs(ai): add security note for setupCommand in buildPostStartCommand…
olexii4 Apr 8, 2026
f6713cf
fix(ai): send API key as plain text and let backend handle encoding
olexii4 Apr 8, 2026
f4d71c5
fix(ai): use stringData instead of data for API key Secret
olexii4 Apr 8, 2026
51215a4
fix(ai): move side effect out of setState updater in AiSelector
olexii4 Apr 8, 2026
331151a
fix(ai): restore cleanup postStart commands for stale binary removal
olexii4 Apr 8, 2026
9489bcd
fix(ai): run cleanup postStart commands in background
olexii4 Apr 8, 2026
334cb47
fix(ai): remove orphaned cleanup commands on next workspace start
olexii4 Apr 8, 2026
dd9c53c
fix(ai): address deep review issues across AI widget
olexii4 Apr 8, 2026
70be13b
chore: replace AI agent name with generic identifier
olexii4 Apr 8, 2026
3c969fc
fix(ai): clean up editor volume mounts and PATH when all tools removed
olexii4 Apr 9, 2026
c31ae7a
fix(ai): track pending cleanup via annotation to survive cold start
olexii4 Apr 9, 2026
2c25ff5
fix(ai): add type guard for JSON.parse result in AI registry API
olexii4 Apr 9, 2026
1ea932c
test(ai): add unit tests to restore function coverage above 85%
olexii4 Apr 9, 2026
b2da606
fix(ai): persist cleanup annotation during workspace start
olexii4 Apr 10, 2026
37f5bc4
test(ai): add coverage for AI tool sanitization in startWorkspace
olexii4 Apr 10, 2026
26b698f
fix: close ReconnectingWebSocket in test cleanup
olexii4 Apr 30, 2026
da6acaf
feat(ai): add Tech-Preview label to AI provider cards
olexii4 May 9, 2026
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
42 changes: 42 additions & 0 deletions packages/common/src/dto/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,37 @@ export interface IExternalDevfileRegistry {
url: string;
}

export interface AiToolDefinition {
/** Links this tool to an AiProviderDefinition.id, e.g. 'anthropic/claude' */
providerId: string;
/** Version tag, e.g. 'latest' */
tag: string;
name: string;
url: string;
/** Binary name available in PATH after injection, e.g. 'claude' */
binary: string;
/** init: single binary copied; bundle: full runtime dir copied */
pattern: 'init' | 'bundle';
/** Full injector image, e.g. 'quay.io/oorel/claude-code:next' */
injectorImage: string;
/** API key env var required by this tool, if any */
envVarName?: string;
/** One-time setup command run in the editor container at postStart */
setupCommand?: string;
}

export interface AiProviderDefinition {
id: string;
name: string;
publisher: string;
description?: string;
docsUrl?: string;
/** URL to the provider's SVG icon */
icon?: string;
/** Optional labels, e.g. ['Tech-Preview'] */
tags?: string[];
}

export interface IServerConfig {
containerBuild: {
containerBuildConfiguration?: {
Expand Down Expand Up @@ -152,6 +183,17 @@ export interface IServerConfig {
allowedSourceUrls: string[];
}

/**
* AI tool registry read from a cluster ConfigMap.
* Contains providers (who makes the tool), tools (how to inject it),
* and the default selection for new workspaces.
*/
export interface IAiRegistry {
providers: AiProviderDefinition[];
tools: AiToolDefinition[];
defaultAiProviders: string[];
}

export interface IAdvancedAuthorization {
allowUsers?: string[];
allowGroups?: string[];
Expand Down
6 changes: 6 additions & 0 deletions packages/dashboard-backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { registerCors } from '@/plugins/cors';
import { registerStaticServer } from '@/plugins/staticServer';
import { registerSwagger } from '@/plugins/swagger';
import { registerWebSocket } from '@/plugins/webSocket';
import { registerAiConfigRoutes } from '@/routes/api/aiConfig';
import { registerAiRegistryRoute } from '@/routes/api/aiRegistry';
import { registerAirGapSampleRoute } from '@/routes/api/airGapSample';
import { registerBackupRoutes } from '@/routes/api/backup';
import { registerClusterConfigRoute } from '@/routes/api/clusterConfig';
Expand Down Expand Up @@ -143,5 +145,9 @@ export default async function buildApp(server: FastifyInstance): Promise<unknown
registerAirGapSampleRoute(server),

registerBackupRoutes(server),

registerAiConfigRoutes(server),

registerAiRegistryRoute(isLocalRun(), server),
]);
}
2 changes: 1 addition & 1 deletion packages/dashboard-backend/src/constants/k8s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Red Hat, Inc. - initial API and implementation
*/

// Generated by Claude Opus 4.6
// Generated by AI Assistant

// DevWorkspaceOperatorConfig constants
export const DEVWORKSPACE_OPERATOR_CONFIG_GROUP = 'controller.devfile.io';
Expand Down
37 changes: 37 additions & 0 deletions packages/dashboard-backend/src/constants/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,43 @@ export const sshKeyParamsSchema: JSONSchema7 = {
required: ['namespace', 'name'],
};

export const aiProviderKeyBodySchema: JSONSchema7 = {
type: 'object',
properties: {
toolId: {
type: 'string',
pattern: '^[a-zA-Z0-9_./-]+$',
maxLength: 63,
},
envVarName: {
type: 'string',
pattern: '^[A-Z][A-Z0-9_]*$',
maxLength: 128,
},
apiKey: {
type: 'string',
minLength: 1,
maxLength: 8192,
},
},
required: ['toolId', 'envVarName', 'apiKey'],
};

export const aiProviderKeyParamsSchema: JSONSchema7 = {
type: 'object',
properties: {
namespace: {
type: 'string',
},
toolId: {
type: 'string',
pattern: '^[a-zA-Z0-9_./-]+$',
maxLength: 63,
},
},
required: ['namespace', 'toolId'],
};

// namespaced schemas

export const namespacedSchema: JSONSchema7 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
*/

import {
IAiProviderKeyApi,
IAiRegistryApi,
IAirGapSampleApi,
IDevWorkspaceApi,
IDevWorkspaceClusterApi,
Expand Down Expand Up @@ -85,6 +87,14 @@ export class DevWorkspaceClient implements IDevWorkspaceClient {
get editorsApi(): IEditorsApi {
throw new Error('Method not implemented.');
}

get aiProviderKeyApi(): IAiProviderKeyApi {
throw new Error('Method not implemented.');
}

get aiRegistryApi(): IAiRegistryApi {
throw new Error('Method not implemented.');
}
}

export class DevWorkspaceSingletonClient implements IDevWorkspaceSingletonClient {
Expand Down
12 changes: 12 additions & 0 deletions packages/dashboard-backend/src/devworkspaceClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import * as k8s from '@kubernetes/client-node';

import { AiProviderKeyApiService } from '@/devworkspaceClient/services/aiProviderKeyApi';
import { AiRegistryApiService } from '@/devworkspaceClient/services/aiRegistryApi';
import { AirGapSampleApiService } from '@/devworkspaceClient/services/airGapSampleApi';
import { DevWorkspaceApiService } from '@/devworkspaceClient/services/devWorkspaceApi';
import { DevWorkspaceClusterApiService } from '@/devworkspaceClient/services/devWorkspaceClusterApiService';
Expand All @@ -31,6 +33,8 @@ import { SshKeysService } from '@/devworkspaceClient/services/sshKeysApi';
import { UserProfileApiService } from '@/devworkspaceClient/services/userProfileApi';
import { WorkspacePreferencesApiService } from '@/devworkspaceClient/services/workspacePreferencesApi';
import {
IAiProviderKeyApi,
IAiRegistryApi,
IAirGapSampleApi,
IDevWorkspaceApi,
IDevWorkspaceClient,
Expand Down Expand Up @@ -132,4 +136,12 @@ export class DevWorkspaceClient implements IDevWorkspaceClient {
get workspacePreferencesApi(): IWorkspacePreferencesApi {
return new WorkspacePreferencesApiService(this.kubeConfig);
}

get aiProviderKeyApi(): IAiProviderKeyApi {
return new AiProviderKeyApiService(this.kubeConfig);
}

get aiRegistryApi(): IAiRegistryApi {
return new AiRegistryApiService(this.kubeConfig);
}
}
Loading
Loading