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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* 2.0.
*/

import type { MaybePromise } from '@kbn/utility-types';
import type { KibanaRequest } from '@kbn/core-http-server';
import type { IUiSettingsClient } from '@kbn/core-ui-settings-server';
import type { AgentDefinition, AgentConfiguration } from '@kbn/onechat-common';

/** Same type for now */
Expand All @@ -18,4 +21,57 @@ export type BuiltInAgentDefinition = Pick<
'id' | 'name' | 'description' | 'labels' | 'avatar_icon' | 'avatar_symbol' | 'avatar_color'
> & {
configuration: BuiltInAgentConfiguration;
/**
* Optional dynamic availability configuration.
*/
availability?: AgentAvailabilityConfig;
};

/**
* Information exposed to the {@link AgentAvailabilityHandler}.
*/
export interface AgentAvailabilityContext {
request: KibanaRequest;
uiSettings: IUiSettingsClient;
spaceId: string;
}

/**
* Information exposed to the {@link AgentAvailabilityHandler}.
*/
export interface AgentAvailabilityResult {
/**
* Whether the agent is available or not.
*/
status: 'available' | 'unavailable';
/**
* Optional reason for why the agent is unavailable.
*/
reason?: string;
}

/**
* Availability handler for an agent.
*/
export type AgentAvailabilityHandler = (
context: AgentAvailabilityContext
) => MaybePromise<AgentAvailabilityResult>;

export interface AgentAvailabilityConfig {
/**
* handler which can be defined to add conditional availability of the agent.
*/
handler: AgentAvailabilityHandler;
/**
* Cache mode for the result
* - global: the result will be cached globally, for all spaces
* - space: the result will be cached per-space
* - none: the result shouldn't be cached (warning: this can lead to performance issues)
*/
cacheMode: 'global' | 'space' | 'none';
/**
* Optional TTL for the cached result, *in seconds*.
* Default to 300 seconds (5 minutes).
*/
cacheTtl?: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@ export type {
ScopedRunnerRunAgentParams,
RunAgentOnEventFn,
} from './runner';
export type { BuiltInAgentDefinition, BuiltInAgentConfiguration } from './builtin_definition';
export type {
BuiltInAgentDefinition,
BuiltInAgentConfiguration,
AgentAvailabilityContext,
AgentAvailabilityHandler,
AgentAvailabilityResult,
AgentAvailabilityConfig,
} from './builtin_definition';
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
* 2.0.
*/

import type { MaybePromise } from '@kbn/utility-types';
import type { KibanaRequest } from '@kbn/core-http-server';
import { createAgentNotFoundError, createBadRequestError } from '@kbn/onechat-common';
import type { AgentDefinition } from '@kbn/onechat-common/agents';
import { validateAgentId } from '@kbn/onechat-common/agents';
import type { AgentAvailabilityContext, AgentAvailabilityResult } from '@kbn/onechat-server/agents';
import type { UiSettingsServiceStart } from '@kbn/core-ui-settings-server';
import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server';
import type {
AgentCreateRequest,
AgentListOptions,
Expand All @@ -18,8 +22,14 @@ import type {
import type { WritableAgentProvider, ReadonlyAgentProvider } from './agent_source';
import { isReadonlyProvider } from './agent_source';

// for now it's the same
export type InternalAgentDefinition = AgentDefinition;
// internal definition for our agents
export type InternalAgentDefinition = AgentDefinition & {
isAvailable: InternalAgentDefinitionAvailabilityHandler;
};

export type InternalAgentDefinitionAvailabilityHandler = (
ctx: AgentAvailabilityContext
) => MaybePromise<AgentAvailabilityResult>;

export interface AgentRegistry {
has(agentId: string): Promise<boolean>;
Expand All @@ -32,22 +42,39 @@ export interface AgentRegistry {

interface CreateAgentRegistryOpts {
request: KibanaRequest;
space: string;
spaceId: string;
persistedProvider: WritableAgentProvider;
builtinProvider: ReadonlyAgentProvider;
uiSettings: UiSettingsServiceStart;
savedObjects: SavedObjectsServiceStart;
}

export const createAgentRegistry = (opts: CreateAgentRegistryOpts): AgentRegistry => {
return new AgentRegistryImpl(opts);
};

class AgentRegistryImpl implements AgentRegistry {
private readonly request: KibanaRequest;
private readonly spaceId: string;
private readonly persistedProvider: WritableAgentProvider;
private readonly builtinProvider: ReadonlyAgentProvider;

constructor({ persistedProvider, builtinProvider }: CreateAgentRegistryOpts) {
private readonly uiSettings: UiSettingsServiceStart;
private readonly savedObjects: SavedObjectsServiceStart;

constructor({
request,
spaceId,
persistedProvider,
builtinProvider,
uiSettings,
savedObjects,
}: CreateAgentRegistryOpts) {
this.request = request;
this.spaceId = spaceId;
this.persistedProvider = persistedProvider;
this.builtinProvider = builtinProvider;
this.uiSettings = uiSettings;
this.savedObjects = savedObjects;
}

private get orderedProviders() {
Expand All @@ -66,7 +93,11 @@ class AgentRegistryImpl implements AgentRegistry {
async get(agentId: string): Promise<InternalAgentDefinition> {
for (const provider of this.orderedProviders) {
if (await provider.has(agentId)) {
return provider.get(agentId);
const agent = await provider.get(agentId);
if (!(await this.isAvailable(agent))) {
throw createBadRequestError(`Agent ${agentId} is not available`);
}
return agent;
}
}
throw createAgentNotFoundError({ agentId });
Expand All @@ -76,7 +107,11 @@ class AgentRegistryImpl implements AgentRegistry {
const allAgents: InternalAgentDefinition[] = [];
for (const provider of this.orderedProviders) {
const providerAgents = await provider.list(opts);
allAgents.push(...providerAgents);
for (const agent of providerAgents) {
if (await this.isAvailable(agent)) {
allAgents.push(agent);
}
}
}
return allAgents;
}
Expand Down Expand Up @@ -121,4 +156,18 @@ class AgentRegistryImpl implements AgentRegistry {
}
throw createAgentNotFoundError({ agentId });
}

private async isAvailable(agent: InternalAgentDefinition): Promise<boolean> {
const soClient = this.savedObjects.getScopedClient(this.request);
const uiSettingsClient = this.uiSettings.asScopedToClient(soClient);

const context: AgentAvailabilityContext = {
spaceId: this.spaceId,
request: this.request,
uiSettings: uiSettingsClient,
};

const status = await agent.isAvailable(context);
return status.status === 'available';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type {
SecurityServiceStart,
ElasticsearchServiceStart,
KibanaRequest,
UiSettingsServiceStart,
SavedObjectsServiceStart,
} from '@kbn/core/server';
import { isAllowedBuiltinAgent } from '@kbn/onechat-server/allow_lists';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/server';
Expand All @@ -34,6 +36,8 @@ export interface AgentsServiceStartDeps {
security: SecurityServiceStart;
spaces?: SpacesPluginStart;
elasticsearch: ElasticsearchServiceStart;
uiSettings: UiSettingsServiceStart;
savedObjects: SavedObjectsServiceStart;
getRunner: () => Runner;
toolsService: ToolsServiceStart;
}
Expand Down Expand Up @@ -69,7 +73,8 @@ export class AgentsService {
}

const { logger } = this.setupDeps;
const { getRunner, security, elasticsearch, spaces, toolsService } = startDeps;
const { getRunner, security, elasticsearch, spaces, toolsService, uiSettings, savedObjects } =
startDeps;

const builtinProviderFn = createBuiltinProviderFn({ registry: this.builtinRegistry });
const persistedProviderFn = createPersistedProviderFn({
Expand All @@ -83,7 +88,9 @@ export class AgentsService {
const space = getCurrentSpaceId({ request, spaces });
return createAgentRegistry({
request,
space,
spaceId: space,
uiSettings,
savedObjects,
builtinProvider: await builtinProviderFn({ request, space }),
persistedProvider: await persistedProviderFn({ request, space }),
});
Expand Down
Loading