Skip to content

Commit 381f36a

Browse files
fullsend-ai-coder[bot]gabemonteroclaude
authored
feat(#3308): add kagenti and llamastack entity provider packages (#3574)
* feat(#3308): add kagenti and llamastack entity provider packages Create independently deployable entity provider packages that emit AI domain objects as Backstage catalog entities: - kagenti-entity-provider: KagentiAgentEntityProvider emits agents as Component/ai-agent entities, KagentiToolEntityProvider emits tools as Resource/ai-tool entities. Polls Kagenti A2A API across configured namespaces. - llamastack-entity-provider: LlamaStackModelEntityProvider polls /v1/models and emits Resource/ai-model entities, LlamaStackAgentEntityProvider reads agent configs from app-config.yaml and emits Component/ai-agent entities with dependsOn relations for tools and handoff targets. Both packages implement the two-layer polling model: each provider manages its own upstream refresh interval (configurable via app-config.yaml under boost.entityProviders.*) independently of Backstage's catalog polling schedule. Default intervals: 60s for models, 5m for agents/tools. Both register as catalog backend modules via catalogProcessingExtensionPoint, deployable standalone as RHDH dynamic plugins without the full boost plugin. Shared entity concerns implemented: 4-stage lifecycle mapping (draft/pending->experimental, published->production, archived->deprecated), createdBy->spec.owner ownership mapping, entity name sanitization. All tests pass. Lint passes. Closes #3308 * fix: address review — sanitizeEntityName fallback, mapOwner for models, remove dangling tool refs - sanitizeEntityName returns 'unnamed-entity' for all-invalid input (both packages) - LlamaStackModelEntityProvider uses mapOwner() for valid entity refs - Remove tool dependsOn from LlamaStackAgentEntityProvider (no tool provider exists yet) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: strip trailing hyphens after truncation, fix misleading JSDoc - sanitizeEntityName applies trailing-hyphen strip after substring(0, 63) (both packages) - LlamaStackAgentEntityProvider JSDoc corrected: config read once at init, not re-read Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Co-authored-by: gabemontero <gmontero@redhat.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 83bcd25 commit 381f36a

26 files changed

Lines changed: 5817 additions & 147 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"name": "@red-hat-developer-hub/backstage-plugin-kagenti-entity-provider",
3+
"version": "0.1.0",
4+
"license": "Apache-2.0",
5+
"description": "Kagenti entity provider for the Backstage catalog — emits AI agents and tools as catalog entities",
6+
"main": "src/index.ts",
7+
"types": "src/index.ts",
8+
"publishConfig": {
9+
"access": "public"
10+
},
11+
"backstage": {
12+
"role": "backend-plugin-module",
13+
"pluginId": "catalog",
14+
"pluginPackage": "@backstage/plugin-catalog-backend",
15+
"pluginPackages": [
16+
"@red-hat-developer-hub/backstage-plugin-kagenti-entity-provider"
17+
]
18+
},
19+
"exports": {
20+
".": "./src/index.ts",
21+
"./package.json": "./package.json"
22+
},
23+
"typesVersions": {
24+
"*": {
25+
"package.json": [
26+
"package.json"
27+
]
28+
}
29+
},
30+
"dependencies": {
31+
"@backstage/backend-plugin-api": "^1.9.1",
32+
"@backstage/catalog-model": "^1.7.7",
33+
"@backstage/plugin-catalog-node": "^2.1.0"
34+
},
35+
"devDependencies": {
36+
"@backstage/backend-test-utils": "^1.11.1",
37+
"@backstage/cli": "^0.34.5"
38+
},
39+
"sideEffects": false,
40+
"scripts": {
41+
"build": "backstage-cli package build",
42+
"clean": "backstage-cli package clean",
43+
"lint": "backstage-cli package lint",
44+
"lint:check": "backstage-cli package lint",
45+
"lint:fix": "backstage-cli package lint --fix",
46+
"test": "backstage-cli package test --passWithNoTests --coverage",
47+
"tsc": "tsc",
48+
"start": "backstage-cli package start",
49+
"prepack": "backstage-cli package prepack",
50+
"postpack": "backstage-cli package postpack"
51+
},
52+
"files": [
53+
"dist"
54+
],
55+
"repository": {
56+
"type": "git",
57+
"url": "git+https://github.com/redhat-developer/rhdh-plugins.git",
58+
"directory": "workspaces/boost/plugins/kagenti-entity-provider"
59+
},
60+
"homepage": "https://red.ht/rhdh",
61+
"bugs": "https://github.com/redhat-developer/rhdh-plugins/issues"
62+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## API Report File for "@red-hat-developer-hub/backstage-plugin-kagenti-entity-provider"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
import { BackendFeature } from '@backstage/backend-plugin-api';
7+
8+
// @public
9+
const catalogModuleKagentiEntityProvider: BackendFeature;
10+
export default catalogModuleKagentiEntityProvider;
11+
```
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Kagenti entity provider for the Backstage catalog.
19+
*
20+
* Emits AI agents and tools as catalog entities, independently
21+
* deployable as an RHDH dynamic plugin.
22+
*
23+
* @packageDocumentation
24+
*/
25+
26+
export { catalogModuleKagentiEntityProvider as default } from './module';
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {
18+
coreServices,
19+
createBackendModule,
20+
} from '@backstage/backend-plugin-api';
21+
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
22+
23+
import { KagentiAgentEntityProvider } from './providers/KagentiAgentEntityProvider';
24+
import { KagentiToolEntityProvider } from './providers/KagentiToolEntityProvider';
25+
import type { KagentiEntityProviderConfig } from './types';
26+
27+
/**
28+
* Default upstream refresh interval for agent entities (5 minutes).
29+
*/
30+
const DEFAULT_AGENT_REFRESH_SECONDS = 300;
31+
32+
/**
33+
* Default upstream refresh interval for tool entities (5 minutes).
34+
*/
35+
const DEFAULT_TOOL_REFRESH_SECONDS = 300;
36+
37+
/**
38+
* Catalog backend module that registers Kagenti entity providers.
39+
*
40+
* Independently deployable as an RHDH dynamic plugin — emits AI agents
41+
* (kind: Component, spec.type: ai-agent) and tools (kind: Resource,
42+
* spec.type: ai-tool) as Backstage catalog entities without requiring
43+
* the full boost plugin.
44+
*
45+
* Configuration (app-config.yaml):
46+
* ```yaml
47+
* boost:
48+
* entityProviders:
49+
* kagenti:
50+
* baseUrl: http://localhost:8080
51+
* namespaces:
52+
* - default
53+
* agentRefreshIntervalSeconds: 300
54+
* toolRefreshIntervalSeconds: 300
55+
* ```
56+
*
57+
* @public
58+
*/
59+
export const catalogModuleKagentiEntityProvider = createBackendModule({
60+
pluginId: 'catalog',
61+
moduleId: 'kagenti-entity-provider',
62+
register(reg) {
63+
reg.registerInit({
64+
deps: {
65+
catalog: catalogProcessingExtensionPoint,
66+
config: coreServices.rootConfig,
67+
logger: coreServices.logger,
68+
scheduler: coreServices.scheduler,
69+
},
70+
async init({ catalog, config, logger, scheduler }) {
71+
logger.info('Initializing Kagenti entity providers');
72+
73+
const providerConfig = readKagentiEntityProviderConfig(config);
74+
75+
const agentRefreshSeconds =
76+
providerConfig.agentRefreshIntervalSeconds ??
77+
DEFAULT_AGENT_REFRESH_SECONDS;
78+
const toolRefreshSeconds =
79+
providerConfig.toolRefreshIntervalSeconds ??
80+
DEFAULT_TOOL_REFRESH_SECONDS;
81+
82+
catalog.addEntityProvider(
83+
new KagentiAgentEntityProvider({
84+
config: providerConfig,
85+
logger,
86+
taskRunner: scheduler.createScheduledTaskRunner({
87+
frequency: { seconds: agentRefreshSeconds },
88+
timeout: { minutes: 5 },
89+
}),
90+
}),
91+
);
92+
93+
catalog.addEntityProvider(
94+
new KagentiToolEntityProvider({
95+
config: providerConfig,
96+
logger,
97+
taskRunner: scheduler.createScheduledTaskRunner({
98+
frequency: { seconds: toolRefreshSeconds },
99+
timeout: { minutes: 5 },
100+
}),
101+
}),
102+
);
103+
104+
logger.info(
105+
`Kagenti entity providers registered (agents: ${agentRefreshSeconds}s, tools: ${toolRefreshSeconds}s)`,
106+
);
107+
},
108+
});
109+
},
110+
});
111+
112+
/**
113+
* Read Kagenti entity provider configuration from app-config.yaml.
114+
*/
115+
function readKagentiEntityProviderConfig(
116+
config: typeof coreServices.rootConfig extends { T: infer T } ? T : never,
117+
): KagentiEntityProviderConfig {
118+
// Try the entity-provider-specific config first
119+
const epConfig = config.getOptionalConfig('boost.entityProviders.kagenti');
120+
121+
if (epConfig) {
122+
return {
123+
baseUrl: epConfig.getString('baseUrl'),
124+
namespaces: epConfig.getOptionalStringArray('namespaces'),
125+
agentRefreshIntervalSeconds: epConfig.getOptionalNumber(
126+
'agentRefreshIntervalSeconds',
127+
),
128+
toolRefreshIntervalSeconds: epConfig.getOptionalNumber(
129+
'toolRefreshIntervalSeconds',
130+
),
131+
};
132+
}
133+
134+
// Fall back to the provider module config for composed mode
135+
const providerConfig = config.getOptionalConfig('boost.providers.kagenti');
136+
137+
if (providerConfig) {
138+
return {
139+
baseUrl: providerConfig.getString('baseUrl'),
140+
namespaces: providerConfig.getOptionalStringArray('namespaces'),
141+
};
142+
}
143+
144+
// Default to localhost
145+
return {
146+
baseUrl: 'http://localhost:8080',
147+
};
148+
}

0 commit comments

Comments
 (0)