Skip to content

Commit cae3f33

Browse files
authored
[Agent Builder] add conditional availability for built-in agents (elastic#245497)
## Summary Equivalent of elastic#238274 for built-in agents
1 parent 8a2519f commit cae3f33

13 files changed

Lines changed: 623 additions & 24 deletions

File tree

x-pack/platform/packages/shared/onechat/onechat-server/agents/builtin_definition.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
* 2.0.
66
*/
77

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

1013
/** Same type for now */
@@ -18,4 +21,57 @@ export type BuiltInAgentDefinition = Pick<
1821
'id' | 'name' | 'description' | 'labels' | 'avatar_icon' | 'avatar_symbol' | 'avatar_color'
1922
> & {
2023
configuration: BuiltInAgentConfiguration;
24+
/**
25+
* Optional dynamic availability configuration.
26+
*/
27+
availability?: AgentAvailabilityConfig;
2128
};
29+
30+
/**
31+
* Information exposed to the {@link AgentAvailabilityHandler}.
32+
*/
33+
export interface AgentAvailabilityContext {
34+
request: KibanaRequest;
35+
uiSettings: IUiSettingsClient;
36+
spaceId: string;
37+
}
38+
39+
/**
40+
* Information exposed to the {@link AgentAvailabilityHandler}.
41+
*/
42+
export interface AgentAvailabilityResult {
43+
/**
44+
* Whether the agent is available or not.
45+
*/
46+
status: 'available' | 'unavailable';
47+
/**
48+
* Optional reason for why the agent is unavailable.
49+
*/
50+
reason?: string;
51+
}
52+
53+
/**
54+
* Availability handler for an agent.
55+
*/
56+
export type AgentAvailabilityHandler = (
57+
context: AgentAvailabilityContext
58+
) => MaybePromise<AgentAvailabilityResult>;
59+
60+
export interface AgentAvailabilityConfig {
61+
/**
62+
* handler which can be defined to add conditional availability of the agent.
63+
*/
64+
handler: AgentAvailabilityHandler;
65+
/**
66+
* Cache mode for the result
67+
* - global: the result will be cached globally, for all spaces
68+
* - space: the result will be cached per-space
69+
* - none: the result shouldn't be cached (warning: this can lead to performance issues)
70+
*/
71+
cacheMode: 'global' | 'space' | 'none';
72+
/**
73+
* Optional TTL for the cached result, *in seconds*.
74+
* Default to 300 seconds (5 minutes).
75+
*/
76+
cacheTtl?: number;
77+
}

x-pack/platform/packages/shared/onechat/onechat-server/agents/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,11 @@ export type {
2121
ScopedRunnerRunAgentParams,
2222
RunAgentOnEventFn,
2323
} from './runner';
24-
export type { BuiltInAgentDefinition, BuiltInAgentConfiguration } from './builtin_definition';
24+
export type {
25+
BuiltInAgentDefinition,
26+
BuiltInAgentConfiguration,
27+
AgentAvailabilityContext,
28+
AgentAvailabilityHandler,
29+
AgentAvailabilityResult,
30+
AgentAvailabilityConfig,
31+
} from './builtin_definition';

x-pack/platform/plugins/shared/onechat/server/services/agents/agent_registry.ts

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
* 2.0.
66
*/
77

8+
import type { MaybePromise } from '@kbn/utility-types';
89
import type { KibanaRequest } from '@kbn/core-http-server';
910
import { createAgentNotFoundError, createBadRequestError } from '@kbn/onechat-common';
1011
import type { AgentDefinition } from '@kbn/onechat-common/agents';
1112
import { validateAgentId } from '@kbn/onechat-common/agents';
13+
import type { AgentAvailabilityContext, AgentAvailabilityResult } from '@kbn/onechat-server/agents';
14+
import type { UiSettingsServiceStart } from '@kbn/core-ui-settings-server';
15+
import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server';
1216
import type {
1317
AgentCreateRequest,
1418
AgentListOptions,
@@ -18,8 +22,14 @@ import type {
1822
import type { WritableAgentProvider, ReadonlyAgentProvider } from './agent_source';
1923
import { isReadonlyProvider } from './agent_source';
2024

21-
// for now it's the same
22-
export type InternalAgentDefinition = AgentDefinition;
25+
// internal definition for our agents
26+
export type InternalAgentDefinition = AgentDefinition & {
27+
isAvailable: InternalAgentDefinitionAvailabilityHandler;
28+
};
29+
30+
export type InternalAgentDefinitionAvailabilityHandler = (
31+
ctx: AgentAvailabilityContext
32+
) => MaybePromise<AgentAvailabilityResult>;
2333

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

3343
interface CreateAgentRegistryOpts {
3444
request: KibanaRequest;
35-
space: string;
45+
spaceId: string;
3646
persistedProvider: WritableAgentProvider;
3747
builtinProvider: ReadonlyAgentProvider;
48+
uiSettings: UiSettingsServiceStart;
49+
savedObjects: SavedObjectsServiceStart;
3850
}
3951

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

4456
class AgentRegistryImpl implements AgentRegistry {
57+
private readonly request: KibanaRequest;
58+
private readonly spaceId: string;
4559
private readonly persistedProvider: WritableAgentProvider;
4660
private readonly builtinProvider: ReadonlyAgentProvider;
47-
48-
constructor({ persistedProvider, builtinProvider }: CreateAgentRegistryOpts) {
61+
private readonly uiSettings: UiSettingsServiceStart;
62+
private readonly savedObjects: SavedObjectsServiceStart;
63+
64+
constructor({
65+
request,
66+
spaceId,
67+
persistedProvider,
68+
builtinProvider,
69+
uiSettings,
70+
savedObjects,
71+
}: CreateAgentRegistryOpts) {
72+
this.request = request;
73+
this.spaceId = spaceId;
4974
this.persistedProvider = persistedProvider;
5075
this.builtinProvider = builtinProvider;
76+
this.uiSettings = uiSettings;
77+
this.savedObjects = savedObjects;
5178
}
5279

5380
private get orderedProviders() {
@@ -66,7 +93,11 @@ class AgentRegistryImpl implements AgentRegistry {
6693
async get(agentId: string): Promise<InternalAgentDefinition> {
6794
for (const provider of this.orderedProviders) {
6895
if (await provider.has(agentId)) {
69-
return provider.get(agentId);
96+
const agent = await provider.get(agentId);
97+
if (!(await this.isAvailable(agent))) {
98+
throw createBadRequestError(`Agent ${agentId} is not available`);
99+
}
100+
return agent;
70101
}
71102
}
72103
throw createAgentNotFoundError({ agentId });
@@ -76,7 +107,11 @@ class AgentRegistryImpl implements AgentRegistry {
76107
const allAgents: InternalAgentDefinition[] = [];
77108
for (const provider of this.orderedProviders) {
78109
const providerAgents = await provider.list(opts);
79-
allAgents.push(...providerAgents);
110+
for (const agent of providerAgents) {
111+
if (await this.isAvailable(agent)) {
112+
allAgents.push(agent);
113+
}
114+
}
80115
}
81116
return allAgents;
82117
}
@@ -121,4 +156,18 @@ class AgentRegistryImpl implements AgentRegistry {
121156
}
122157
throw createAgentNotFoundError({ agentId });
123158
}
159+
160+
private async isAvailable(agent: InternalAgentDefinition): Promise<boolean> {
161+
const soClient = this.savedObjects.getScopedClient(this.request);
162+
const uiSettingsClient = this.uiSettings.asScopedToClient(soClient);
163+
164+
const context: AgentAvailabilityContext = {
165+
spaceId: this.spaceId,
166+
request: this.request,
167+
uiSettings: uiSettingsClient,
168+
};
169+
170+
const status = await agent.isAvailable(context);
171+
return status.status === 'available';
172+
}
124173
}

x-pack/platform/plugins/shared/onechat/server/services/agents/agents_service.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {
1010
SecurityServiceStart,
1111
ElasticsearchServiceStart,
1212
KibanaRequest,
13+
UiSettingsServiceStart,
14+
SavedObjectsServiceStart,
1315
} from '@kbn/core/server';
1416
import { isAllowedBuiltinAgent } from '@kbn/onechat-server/allow_lists';
1517
import type { SpacesPluginStart } from '@kbn/spaces-plugin/server';
@@ -34,6 +36,8 @@ export interface AgentsServiceStartDeps {
3436
security: SecurityServiceStart;
3537
spaces?: SpacesPluginStart;
3638
elasticsearch: ElasticsearchServiceStart;
39+
uiSettings: UiSettingsServiceStart;
40+
savedObjects: SavedObjectsServiceStart;
3741
getRunner: () => Runner;
3842
toolsService: ToolsServiceStart;
3943
}
@@ -69,7 +73,8 @@ export class AgentsService {
6973
}
7074

7175
const { logger } = this.setupDeps;
72-
const { getRunner, security, elasticsearch, spaces, toolsService } = startDeps;
76+
const { getRunner, security, elasticsearch, spaces, toolsService, uiSettings, savedObjects } =
77+
startDeps;
7378

7479
const builtinProviderFn = createBuiltinProviderFn({ registry: this.builtinRegistry });
7580
const persistedProviderFn = createPersistedProviderFn({
@@ -83,7 +88,9 @@ export class AgentsService {
8388
const space = getCurrentSpaceId({ request, spaces });
8489
return createAgentRegistry({
8590
request,
86-
space,
91+
spaceId: space,
92+
uiSettings,
93+
savedObjects,
8794
builtinProvider: await builtinProviderFn({ request, space }),
8895
persistedProvider: await persistedProviderFn({ request, space }),
8996
});

0 commit comments

Comments
 (0)