@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-azure-devops 0.0.0-snapshot-20251029145101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/config.d.ts +68 -0
- package/dist/constants/default-file-types.cjs.js +6 -0
- package/dist/constants/default-file-types.cjs.js.map +1 -0
- package/dist/constants/default-repo-file-batch-size.cjs.js +6 -0
- package/dist/constants/default-repo-file-batch-size.cjs.js.map +1 -0
- package/dist/constants/default-wiki-page-batch-size.cjs.js +6 -0
- package/dist/constants/default-wiki-page-batch-size.cjs.js.map +1 -0
- package/dist/constants/module.cjs.js +6 -0
- package/dist/constants/module.cjs.js.map +1 -0
- package/dist/index.cjs.js +10 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/module.cjs.js +27 -0
- package/dist/module.cjs.js.map +1 -0
- package/dist/services/azure-devops/index.cjs.js +113 -0
- package/dist/services/azure-devops/index.cjs.js.map +1 -0
- package/dist/services/ingestor/index.cjs.js +41 -0
- package/dist/services/ingestor/index.cjs.js.map +1 -0
- package/dist/services/ingestor/repository.cjs.js +139 -0
- package/dist/services/ingestor/repository.cjs.js.map +1 -0
- package/dist/services/ingestor/wiki.cjs.js +116 -0
- package/dist/services/ingestor/wiki.cjs.js.map +1 -0
- package/dist/utils/flatten-wiki-pages.cjs.js +14 -0
- package/dist/utils/flatten-wiki-pages.cjs.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
package/config.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure DevOps ingestor configuration
|
|
3
|
+
* @visibility backend
|
|
4
|
+
*/
|
|
5
|
+
export interface Config {
|
|
6
|
+
aiAssistant: {
|
|
7
|
+
ingestors: {
|
|
8
|
+
azureDevOps: {
|
|
9
|
+
/**
|
|
10
|
+
* The Azure DevOps organization name
|
|
11
|
+
*/
|
|
12
|
+
organization: string;
|
|
13
|
+
/**
|
|
14
|
+
* The Azure DevOps project name
|
|
15
|
+
*/
|
|
16
|
+
project: string;
|
|
17
|
+
/**
|
|
18
|
+
* Personal Access Token for Azure DevOps
|
|
19
|
+
* @visibility secret
|
|
20
|
+
*/
|
|
21
|
+
token: string;
|
|
22
|
+
/**
|
|
23
|
+
* The types of resources to ingest from Azure DevOps
|
|
24
|
+
* Currently supports 'repository' and 'wiki' resources
|
|
25
|
+
*/
|
|
26
|
+
resourceTypes: ('repository' | 'wiki')[];
|
|
27
|
+
/**
|
|
28
|
+
* Optional list of file types to ingest (e.g., .md, .json). Defaults to .md and .json if not specified.
|
|
29
|
+
*/
|
|
30
|
+
fileTypes?: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Optional list of repositories to ingest. If not specified, all repositories in the project will be ingested.
|
|
33
|
+
*/
|
|
34
|
+
repositories?: {
|
|
35
|
+
/**
|
|
36
|
+
* The name of the repository to ingest
|
|
37
|
+
*/
|
|
38
|
+
name: string;
|
|
39
|
+
/**
|
|
40
|
+
* Optional list of file types to ingest for this repository. Overrides the global fileTypes setting for this repository only.
|
|
41
|
+
*/
|
|
42
|
+
fileTypes?: string[];
|
|
43
|
+
}[];
|
|
44
|
+
/**
|
|
45
|
+
* Optional batch size for processing repository files. Defaults to 50 files per batch.
|
|
46
|
+
* Lower values use less memory but may be slower, higher values are faster but use more memory.
|
|
47
|
+
*/
|
|
48
|
+
filesBatchSize?: number;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Optional list of wikis to ingest. If not specified, all wikis in the project will be ingested.
|
|
52
|
+
*/
|
|
53
|
+
wikis?: {
|
|
54
|
+
/**
|
|
55
|
+
* The name of the wiki to ingest
|
|
56
|
+
*/
|
|
57
|
+
name: string;
|
|
58
|
+
}[];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Optional batch size for processing wiki pages. Defaults to 50 pages per batch.
|
|
62
|
+
* Lower values use less memory but may be slower, higher values are faster but use more memory.
|
|
63
|
+
*/
|
|
64
|
+
pagesBatchSize?: number;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-file-types.cjs.js","sources":["../../src/constants/default-file-types.ts"],"sourcesContent":["export const DEFAULT_FILE_TYPES = ['.md', '.json'];\n"],"names":[],"mappings":";;AAAO,MAAM,kBAAA,GAAqB,CAAC,KAAA,EAAO,OAAO;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-repo-file-batch-size.cjs.js","sources":["../../src/constants/default-repo-file-batch-size.ts"],"sourcesContent":["export const DEFAULT_REPO_FILE_BATCH_SIZE = 50;\n"],"names":[],"mappings":";;AAAO,MAAM,4BAAA,GAA+B;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-wiki-page-batch-size.cjs.js","sources":["../../src/constants/default-wiki-page-batch-size.ts"],"sourcesContent":["export const DEFAULT_WIKI_PAGE_BATCH_SIZE = 50;\n"],"names":[],"mappings":";;AAAO,MAAM,4BAAA,GAA+B;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.cjs.js","sources":["../../src/constants/module.ts"],"sourcesContent":["export const MODULE_ID = 'azure-devops';\n"],"names":[],"mappings":";;AAAO,MAAM,SAAA,GAAY;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var index = require('./services/ingestor/index.cjs.js');
|
|
5
|
+
var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
|
|
6
|
+
|
|
7
|
+
const aiAssistantModuleIngestorAzureDevops = backendPluginApi.createBackendModule({
|
|
8
|
+
pluginId: "ai-assistant",
|
|
9
|
+
moduleId: "ingestor-azure-devops",
|
|
10
|
+
register(reg) {
|
|
11
|
+
reg.registerInit({
|
|
12
|
+
deps: {
|
|
13
|
+
dataIngestor: backstagePluginAiAssistantNode.dataIngestorExtensionPoint,
|
|
14
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
15
|
+
logger: backendPluginApi.coreServices.logger
|
|
16
|
+
},
|
|
17
|
+
async init({ config, logger, dataIngestor }) {
|
|
18
|
+
dataIngestor.registerIngestor(
|
|
19
|
+
await index.createAzureDevOpsIngestor({ config, logger })
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
exports.aiAssistantModuleIngestorAzureDevops = aiAssistantModuleIngestorAzureDevops;
|
|
27
|
+
//# sourceMappingURL=module.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["import {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { createAzureDevOpsIngestor } from './services/ingestor';\nimport { dataIngestorExtensionPoint } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nexport const aiAssistantModuleIngestorAzureDevops = createBackendModule({\n pluginId: 'ai-assistant',\n moduleId: 'ingestor-azure-devops',\n register(reg) {\n reg.registerInit({\n deps: {\n dataIngestor: dataIngestorExtensionPoint,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n },\n async init({ config, logger, dataIngestor }) {\n dataIngestor.registerIngestor(\n await createAzureDevOpsIngestor({ config, logger }),\n );\n },\n });\n },\n});\n"],"names":["createBackendModule","dataIngestorExtensionPoint","coreServices","createAzureDevOpsIngestor"],"mappings":";;;;;;AAOO,MAAM,uCAAuCA,oCAAA,CAAoB;AAAA,EACtE,QAAA,EAAU,cAAA;AAAA,EACV,QAAA,EAAU,uBAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,YAAA,EAAcC,yDAAA;AAAA,QACd,QAAQC,6BAAA,CAAa,UAAA;AAAA,QACrB,QAAQA,6BAAA,CAAa;AAAA,OACvB;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,cAAa,EAAG;AAC3C,QAAA,YAAA,CAAa,gBAAA;AAAA,UACX,MAAMC,+BAAA,CAA0B,EAAE,MAAA,EAAQ,QAAQ;AAAA,SACpD;AAAA,MACF;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
|
|
4
|
+
var azureDevopsNodeApi = require('azure-devops-node-api');
|
|
5
|
+
var GitInterfaces = require('azure-devops-node-api/interfaces/GitInterfaces');
|
|
6
|
+
var flattenWikiPages = require('../../utils/flatten-wiki-pages.cjs.js');
|
|
7
|
+
|
|
8
|
+
const createAzureDevOpsService = async ({
|
|
9
|
+
config,
|
|
10
|
+
logger
|
|
11
|
+
}) => {
|
|
12
|
+
const organization = config.getString(
|
|
13
|
+
"aiAssistant.ingestors.azureDevOps.organization"
|
|
14
|
+
);
|
|
15
|
+
const project = config.getString("aiAssistant.ingestors.azureDevOps.project");
|
|
16
|
+
const token = config.getString("aiAssistant.ingestors.azureDevOps.token");
|
|
17
|
+
const orgUrl = `https://dev.azure.com/${organization}`;
|
|
18
|
+
logger.info(
|
|
19
|
+
`Connecting to Azure DevOps organization: ${organization}, project: ${project}`
|
|
20
|
+
);
|
|
21
|
+
if (!organization || !project || !token) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Azure DevOps organization, project, and token are required"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const authHandler = azureDevopsNodeApi.getPersonalAccessTokenHandler(token);
|
|
27
|
+
const connection = new azureDevopsNodeApi.WebApi(orgUrl, authHandler);
|
|
28
|
+
logger.info(
|
|
29
|
+
`Connected to Azure DevOps organization: ${organization}, project: ${project}`
|
|
30
|
+
);
|
|
31
|
+
const gitApi = await connection.getGitApi();
|
|
32
|
+
const wikiApi = await connection.getWikiApi();
|
|
33
|
+
const getRepos = async () => {
|
|
34
|
+
const repos = await gitApi.getRepositories(project);
|
|
35
|
+
logger.info(`Found ${repos.length} repositories in project ${project}`);
|
|
36
|
+
return repos;
|
|
37
|
+
};
|
|
38
|
+
const getRepoItems = async (repoId, fileTypes) => {
|
|
39
|
+
const items = await gitApi.getItems(
|
|
40
|
+
repoId,
|
|
41
|
+
project,
|
|
42
|
+
void 0,
|
|
43
|
+
GitInterfaces.VersionControlRecursionType.Full
|
|
44
|
+
);
|
|
45
|
+
logger.info(
|
|
46
|
+
`Found ${items.length} items in Azure DevOps repository ${repoId}`
|
|
47
|
+
);
|
|
48
|
+
if (fileTypes && fileTypes.length > 0) {
|
|
49
|
+
const filteredItems = items.filter(
|
|
50
|
+
(item) => !item.isFolder && fileTypes.some((type) => item.path?.endsWith(type))
|
|
51
|
+
);
|
|
52
|
+
logger.info(
|
|
53
|
+
`Filtered to ${filteredItems.length} items with types: ${fileTypes.join(
|
|
54
|
+
", "
|
|
55
|
+
)}`
|
|
56
|
+
);
|
|
57
|
+
return filteredItems;
|
|
58
|
+
}
|
|
59
|
+
return items;
|
|
60
|
+
};
|
|
61
|
+
const getRepoItemContent = async (repoId, path) => {
|
|
62
|
+
const itemContent = await gitApi.getItemContent(
|
|
63
|
+
repoId,
|
|
64
|
+
path,
|
|
65
|
+
project,
|
|
66
|
+
void 0,
|
|
67
|
+
GitInterfaces.VersionControlRecursionType.None
|
|
68
|
+
);
|
|
69
|
+
return itemContent;
|
|
70
|
+
};
|
|
71
|
+
const getWikis = async () => {
|
|
72
|
+
const wikis = await wikiApi.getAllWikis(project);
|
|
73
|
+
logger.info(`Found ${wikis.length} wikis in project ${project}`);
|
|
74
|
+
return wikis;
|
|
75
|
+
};
|
|
76
|
+
const getWikiPages = async (wikiName) => {
|
|
77
|
+
const pagesStream = await wikiApi.getPageText(
|
|
78
|
+
project,
|
|
79
|
+
wikiName,
|
|
80
|
+
void 0,
|
|
81
|
+
GitInterfaces.VersionControlRecursionType.Full
|
|
82
|
+
);
|
|
83
|
+
const rootPage = JSON.parse(await backstagePluginAiAssistantNode.streamToString(pagesStream));
|
|
84
|
+
const allPages = flattenWikiPages.flattenWikiPages(rootPage);
|
|
85
|
+
logger.info(
|
|
86
|
+
`Found ${allPages.length} pages in Azure DevOps wiki: ${wikiName}`
|
|
87
|
+
);
|
|
88
|
+
return allPages;
|
|
89
|
+
};
|
|
90
|
+
const getWikiPageContent = async (wikiName, pageId) => {
|
|
91
|
+
const pageStream = await wikiApi.getPageByIdText(
|
|
92
|
+
project,
|
|
93
|
+
wikiName,
|
|
94
|
+
pageId,
|
|
95
|
+
GitInterfaces.VersionControlRecursionType.None,
|
|
96
|
+
true
|
|
97
|
+
);
|
|
98
|
+
return pageStream;
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
organization,
|
|
102
|
+
project,
|
|
103
|
+
getRepos,
|
|
104
|
+
getRepoItems,
|
|
105
|
+
getRepoItemContent,
|
|
106
|
+
getWikis,
|
|
107
|
+
getWikiPages,
|
|
108
|
+
getWikiPageContent
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
exports.createAzureDevOpsService = createAzureDevOpsService;
|
|
113
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../../src/services/azure-devops/index.ts"],"sourcesContent":["import {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { streamToString } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api';\nimport { VersionControlRecursionType } from 'azure-devops-node-api/interfaces/GitInterfaces';\nimport { WikiPage } from 'azure-devops-node-api/interfaces/WikiInterfaces';\nimport { flattenWikiPages } from '../../utils/flatten-wiki-pages';\n\nexport type AzureDevOpsService = Awaited<\n ReturnType<typeof createAzureDevOpsService>\n>;\n\nexport const createAzureDevOpsService = async ({\n config,\n logger,\n}: {\n config: RootConfigService;\n logger: LoggerService;\n}) => {\n // Get configuration values\n const organization = config.getString(\n 'aiAssistant.ingestors.azureDevOps.organization',\n );\n const project = config.getString('aiAssistant.ingestors.azureDevOps.project');\n const token = config.getString('aiAssistant.ingestors.azureDevOps.token');\n\n // Construct organization URL\n const orgUrl = `https://dev.azure.com/${organization}`;\n\n logger.info(\n `Connecting to Azure DevOps organization: ${organization}, project: ${project}`,\n );\n\n if (!organization || !project || !token) {\n throw new Error(\n 'Azure DevOps organization, project, and token are required',\n );\n }\n\n // Create authentication handler and connection\n const authHandler = getPersonalAccessTokenHandler(token);\n\n const connection = new WebApi(orgUrl, authHandler);\n\n logger.info(\n `Connected to Azure DevOps organization: ${organization}, project: ${project}`,\n );\n\n // Get Git API for repository operations\n const gitApi = await connection.getGitApi();\n\n // Get Wiki API for wiki operations\n const wikiApi = await connection.getWikiApi();\n\n /**\n * Get a list of repositories in the specified Azure DevOps project\n * @returns List of repositories in the specified Azure DevOps project\n */\n const getRepos = async () => {\n const repos = await gitApi.getRepositories(project);\n\n logger.info(`Found ${repos.length} repositories in project ${project}`);\n\n return repos;\n };\n\n /**\n * Get a list of items in the specified Azure DevOps repository\n * @param repoId The ID of the repository\n * @param fileTypes Optional list of file types to filter by\n * @returns List of items in the specified Azure DevOps repository\n */\n const getRepoItems = async (repoId: string, fileTypes?: string[]) => {\n const items = await gitApi.getItems(\n repoId,\n project,\n undefined,\n VersionControlRecursionType.Full,\n );\n\n logger.info(\n `Found ${items.length} items in Azure DevOps repository ${repoId}`,\n );\n\n if (fileTypes && fileTypes.length > 0) {\n const filteredItems = items.filter(\n item =>\n !item.isFolder && fileTypes.some(type => item.path?.endsWith(type)),\n );\n logger.info(\n `Filtered to ${filteredItems.length} items with types: ${fileTypes.join(\n ', ',\n )}`,\n );\n return filteredItems;\n }\n\n return items;\n };\n\n /**\n * Get the content of a specific item in an Azure DevOps repository\n * @param repoId The ID of the repository\n * @param path The path of the item\n * @returns The content of the item\n */\n const getRepoItemContent = async (repoId: string, path: string) => {\n const itemContent = await gitApi.getItemContent(\n repoId,\n path,\n project,\n undefined,\n VersionControlRecursionType.None,\n );\n\n return itemContent;\n };\n\n /* Gets all wikis in the specified Azure DevOps project */\n const getWikis = async () => {\n const wikis = await wikiApi.getAllWikis(project);\n logger.info(`Found ${wikis.length} wikis in project ${project}`);\n return wikis;\n };\n\n /**\n * Retrieves all pages and subpages in a specific Azure DevOps wiki and flattens them into a single list\n * @param wikiName The name of the wiki to get pages from\n * @returns A list of all pages in the specified wiki\n */\n const getWikiPages = async (wikiName: string) => {\n const pagesStream = await wikiApi.getPageText(\n project,\n wikiName,\n undefined,\n VersionControlRecursionType.Full,\n );\n\n const rootPage = JSON.parse(await streamToString(pagesStream)) as WikiPage;\n\n // Flatten all pages including subpages into a single array\n const allPages = flattenWikiPages(rootPage);\n\n logger.info(\n `Found ${allPages.length} pages in Azure DevOps wiki: ${wikiName}`,\n );\n\n return allPages;\n };\n\n /**\n * Get the content of a specific page in an Azure DevOps wiki\n * @param wikiName The name of the wiki\n * @param pageId The ID of the page\n * @returns The content of the specified wiki page\n */\n const getWikiPageContent = async (wikiName: string, pageId: number) => {\n const pageStream = await wikiApi.getPageByIdText(\n project,\n wikiName,\n pageId,\n VersionControlRecursionType.None,\n true,\n );\n\n return pageStream;\n };\n\n return {\n organization,\n project,\n getRepos,\n getRepoItems,\n getRepoItemContent,\n getWikis,\n getWikiPages,\n getWikiPageContent,\n };\n};\n"],"names":["getPersonalAccessTokenHandler","WebApi","VersionControlRecursionType","streamToString","flattenWikiPages"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('../azure-devops/index.cjs.js');
|
|
4
|
+
var module$1 = require('../../constants/module.cjs.js');
|
|
5
|
+
var repository = require('./repository.cjs.js');
|
|
6
|
+
var wiki = require('./wiki.cjs.js');
|
|
7
|
+
|
|
8
|
+
const createAzureDevOpsIngestor = async ({
|
|
9
|
+
config,
|
|
10
|
+
logger
|
|
11
|
+
}) => {
|
|
12
|
+
const resourceTypes = config.get("aiAssistant.ingestors.azureDevOps.resourceTypes");
|
|
13
|
+
const azureDevOpsService = await index.createAzureDevOpsService({ config, logger });
|
|
14
|
+
const repositoryIngestor = await repository.createRepositoryIngestor({
|
|
15
|
+
config,
|
|
16
|
+
logger,
|
|
17
|
+
azureDevOpsService
|
|
18
|
+
});
|
|
19
|
+
const wikiIngestor = await wiki.createWikiIngestor({
|
|
20
|
+
config,
|
|
21
|
+
logger,
|
|
22
|
+
azureDevOpsService
|
|
23
|
+
});
|
|
24
|
+
const ingest = async ({ saveDocumentsBatch }) => {
|
|
25
|
+
if (resourceTypes.includes("repository")) {
|
|
26
|
+
logger.info("Initializing Azure DevOps repository resource ingestor");
|
|
27
|
+
await repositoryIngestor.ingestRepositoriesBatch(saveDocumentsBatch);
|
|
28
|
+
}
|
|
29
|
+
if (resourceTypes.includes("wiki")) {
|
|
30
|
+
logger.info("Initializing Azure DevOps wiki resource ingestor");
|
|
31
|
+
await wikiIngestor.ingestWikisBatch(saveDocumentsBatch);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
id: module$1.MODULE_ID,
|
|
36
|
+
ingest
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
exports.createAzureDevOpsIngestor = createAzureDevOpsIngestor;
|
|
41
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../../src/services/ingestor/index.ts"],"sourcesContent":["import {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { createAzureDevOpsService } from '../azure-devops';\nimport { Ingestor } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { MODULE_ID } from '../../constants/module';\nimport { createRepositoryIngestor } from './repository';\nimport { createWikiIngestor } from './wiki';\nimport { Config } from '../../../config';\n\nexport const createAzureDevOpsIngestor = async ({\n config,\n logger,\n}: {\n config: RootConfigService;\n logger: LoggerService;\n}): Promise<Ingestor> => {\n // Get configuration values\n const resourceTypes = config.get<\n Config['aiAssistant']['ingestors']['azureDevOps']['resourceTypes']\n >('aiAssistant.ingestors.azureDevOps.resourceTypes');\n\n // Create Azure DevOps service\n const azureDevOpsService = await createAzureDevOpsService({ config, logger });\n\n // Create repository ingestor\n const repositoryIngestor = await createRepositoryIngestor({\n config,\n logger,\n azureDevOpsService,\n });\n\n // Create wiki ingestor\n const wikiIngestor = await createWikiIngestor({\n config,\n logger,\n azureDevOpsService,\n });\n\n const ingest: Ingestor['ingest'] = async ({ saveDocumentsBatch }) => {\n if (resourceTypes.includes('repository')) {\n logger.info('Initializing Azure DevOps repository resource ingestor');\n await repositoryIngestor.ingestRepositoriesBatch(saveDocumentsBatch);\n }\n if (resourceTypes.includes('wiki')) {\n logger.info('Initializing Azure DevOps wiki resource ingestor');\n await wikiIngestor.ingestWikisBatch(saveDocumentsBatch);\n }\n };\n\n return {\n id: MODULE_ID,\n ingest,\n };\n};\n"],"names":["createAzureDevOpsService","createRepositoryIngestor","createWikiIngestor","MODULE_ID"],"mappings":";;;;;;;AAWO,MAAM,4BAA4B,OAAO;AAAA,EAC9C,MAAA;AAAA,EACA;AACF,CAAA,KAGyB;AAEvB,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,GAAA,CAE3B,iDAAiD,CAAA;AAGnD,EAAA,MAAM,qBAAqB,MAAMA,8BAAA,CAAyB,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAG5E,EAAA,MAAM,kBAAA,GAAqB,MAAMC,mCAAA,CAAyB;AAAA,IACxD,MAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAGD,EAAA,MAAM,YAAA,GAAe,MAAMC,uBAAA,CAAmB;AAAA,IAC5C,MAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,MAAA,GAA6B,OAAO,EAAE,kBAAA,EAAmB,KAAM;AACnE,IAAA,IAAI,aAAA,CAAc,QAAA,CAAS,YAAY,CAAA,EAAG;AACxC,MAAA,MAAA,CAAO,KAAK,wDAAwD,CAAA;AACpE,MAAA,MAAM,kBAAA,CAAmB,wBAAwB,kBAAkB,CAAA;AAAA,IACrE;AACA,IAAA,IAAI,aAAA,CAAc,QAAA,CAAS,MAAM,CAAA,EAAG;AAClC,MAAA,MAAA,CAAO,KAAK,kDAAkD,CAAA;AAC9D,MAAA,MAAM,YAAA,CAAa,iBAAiB,kBAAkB,CAAA;AAAA,IACxD;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,EAAA,EAAIC,kBAAA;AAAA,IACJ;AAAA,GACF;AACF;;;;"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var defaultFileTypes = require('../../constants/default-file-types.cjs.js');
|
|
4
|
+
var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
|
|
5
|
+
var module$1 = require('../../constants/module.cjs.js');
|
|
6
|
+
var backstagePluginAiAssistantCommon = require('@sweetoburrito/backstage-plugin-ai-assistant-common');
|
|
7
|
+
var defaultRepoFileBatchSize = require('../../constants/default-repo-file-batch-size.cjs.js');
|
|
8
|
+
|
|
9
|
+
const createRepositoryIngestor = async ({
|
|
10
|
+
config,
|
|
11
|
+
logger,
|
|
12
|
+
azureDevOpsService
|
|
13
|
+
}) => {
|
|
14
|
+
const repositoriesFilter = config.getOptional("aiAssistant.ingestors.azureDevOps.repositories");
|
|
15
|
+
const fileTypes = config.getOptionalStringArray(
|
|
16
|
+
"aiAssistant.ingestors.azureDevOps.fileTypes"
|
|
17
|
+
) ?? defaultFileTypes.DEFAULT_FILE_TYPES;
|
|
18
|
+
const itemsBatchSize = config.getOptionalNumber(
|
|
19
|
+
"aiAssistant.ingestors.azureDevOps.filesBatchSize"
|
|
20
|
+
// Reuse the same config for consistency
|
|
21
|
+
) ?? defaultRepoFileBatchSize.DEFAULT_REPO_FILE_BATCH_SIZE;
|
|
22
|
+
const ingestRepoByFileBatch = async ({
|
|
23
|
+
repository,
|
|
24
|
+
items,
|
|
25
|
+
saveDocumentsBatch
|
|
26
|
+
}) => {
|
|
27
|
+
logger.info(
|
|
28
|
+
`Processing ${items.length} items from repository "${repository.name}" in batches of ${itemsBatchSize}`
|
|
29
|
+
);
|
|
30
|
+
logger.debug(`Items: ${JSON.stringify(items, null, 2)}`);
|
|
31
|
+
let totalDocumentsIngested = 0;
|
|
32
|
+
const totalBatches = Math.ceil(items.length / itemsBatchSize);
|
|
33
|
+
for (let batchStart = 0; batchStart < items.length; batchStart += itemsBatchSize) {
|
|
34
|
+
const batchEnd = Math.min(batchStart + itemsBatchSize, items.length);
|
|
35
|
+
const itemsBatch = items.slice(batchStart, batchEnd);
|
|
36
|
+
const batchNumber = Math.floor(batchStart / itemsBatchSize) + 1;
|
|
37
|
+
logger.info(
|
|
38
|
+
`Processing batch ${batchNumber}/${totalBatches} (${itemsBatch.length} items) for repository "${repository.name}"`
|
|
39
|
+
);
|
|
40
|
+
const documents = [];
|
|
41
|
+
for (let index = 0; index < itemsBatch.length; index++) {
|
|
42
|
+
const item = itemsBatch[index];
|
|
43
|
+
const globalIndex = batchStart + index;
|
|
44
|
+
const content = await azureDevOpsService.getRepoItemContent(
|
|
45
|
+
repository.id,
|
|
46
|
+
item.path
|
|
47
|
+
);
|
|
48
|
+
const completionStats = backstagePluginAiAssistantCommon.getProgressStats(globalIndex + 1, items.length);
|
|
49
|
+
logger.info(
|
|
50
|
+
`Retrieved content for Azure DevOps item: ${item.path} in repository: "${repository.name}" [Progress: ${completionStats.completed}/${completionStats.total} (${completionStats.percentage}%) completed of repository]`
|
|
51
|
+
);
|
|
52
|
+
const text = await backstagePluginAiAssistantNode.streamToString(content);
|
|
53
|
+
const document = {
|
|
54
|
+
metadata: {
|
|
55
|
+
source: module$1.MODULE_ID,
|
|
56
|
+
id: `${repository.id}:${item.path}`,
|
|
57
|
+
url: item.url,
|
|
58
|
+
organization: azureDevOpsService.organization,
|
|
59
|
+
project: azureDevOpsService.project,
|
|
60
|
+
repository: repository.name
|
|
61
|
+
},
|
|
62
|
+
content: text
|
|
63
|
+
};
|
|
64
|
+
documents.push(document);
|
|
65
|
+
}
|
|
66
|
+
await saveDocumentsBatch(documents);
|
|
67
|
+
totalDocumentsIngested += documents.length;
|
|
68
|
+
logger.info(
|
|
69
|
+
`Batch ${batchNumber}/${totalBatches} completed: ${documents.length} documents ingested for Azure DevOps repository: ${repository.name}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return { totalDocumentsIngested };
|
|
73
|
+
};
|
|
74
|
+
const ingestRepositoriesBatch = async (saveDocumentsBatch) => {
|
|
75
|
+
const repositoriesList = await azureDevOpsService.getRepos();
|
|
76
|
+
if (repositoriesList.length === 0) {
|
|
77
|
+
logger.warn("No repositories found in the Azure DevOps project");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
logger.info(
|
|
81
|
+
`Filtering for repositories: ${repositoriesFilter?.map((repo) => repo.name).join(", ")}`
|
|
82
|
+
);
|
|
83
|
+
const repositoriesToIngest = repositoriesFilter ? repositoriesList.filter(
|
|
84
|
+
(repo) => repositoriesFilter?.some(
|
|
85
|
+
(filteredRepo) => filteredRepo.name.toLowerCase() === repo.name.toLowerCase()
|
|
86
|
+
)
|
|
87
|
+
) : repositoriesList;
|
|
88
|
+
if (repositoriesToIngest.length === 0) {
|
|
89
|
+
logger.warn(
|
|
90
|
+
"No repositories found for ingestion after applying the filter"
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
logger.info(
|
|
95
|
+
`Ingesting ${repositoriesToIngest.length} repositories from Azure DevOps`
|
|
96
|
+
);
|
|
97
|
+
for (const repo of repositoriesToIngest) {
|
|
98
|
+
logger.info(
|
|
99
|
+
`Beginning ingestion for repository: ${repo.name} (${repo.id})`
|
|
100
|
+
);
|
|
101
|
+
const repositoryFileTypesFilter = repositoriesFilter?.find(
|
|
102
|
+
(r) => r.name.toLowerCase() === repo.name.toLowerCase()
|
|
103
|
+
)?.fileTypes ?? fileTypes;
|
|
104
|
+
logger.info(
|
|
105
|
+
`Processing file types for repository ${repo.name}: [${repositoryFileTypesFilter.join(", ")}]`
|
|
106
|
+
);
|
|
107
|
+
const items = await azureDevOpsService.getRepoItems(
|
|
108
|
+
repo.id,
|
|
109
|
+
repositoryFileTypesFilter
|
|
110
|
+
);
|
|
111
|
+
if (items.length === 0) {
|
|
112
|
+
logger.warn(
|
|
113
|
+
`No items found for ingestion in the Azure DevOps repository ${repo.name} (${repo.id}) with the specified file types filter: [${repositoryFileTypesFilter.join(
|
|
114
|
+
", "
|
|
115
|
+
)}]`
|
|
116
|
+
);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const { totalDocumentsIngested } = await ingestRepoByFileBatch({
|
|
120
|
+
repository: repo,
|
|
121
|
+
items,
|
|
122
|
+
saveDocumentsBatch
|
|
123
|
+
});
|
|
124
|
+
if (totalDocumentsIngested === 0) {
|
|
125
|
+
logger.warn(
|
|
126
|
+
`No documents were ingested and sent for embedding from the Azure DevOps repository ${repo.name} (${repo.id})`
|
|
127
|
+
);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
logger.info(
|
|
131
|
+
`Repository ingestion completed: ${totalDocumentsIngested} total documents ingested and sent for embedding for Azure DevOps repository: ${repo.name}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
return { ingestRepositoriesBatch };
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
exports.createRepositoryIngestor = createRepositoryIngestor;
|
|
139
|
+
//# sourceMappingURL=repository.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.cjs.js","sources":["../../../src/services/ingestor/repository.ts"],"sourcesContent":["import {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { DEFAULT_FILE_TYPES } from '../../constants/default-file-types';\nimport {\n EmbeddingDocument,\n IngestorOptions,\n streamToString,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { AzureDevOpsService } from '../azure-devops';\nimport { Config } from '../../../config';\nimport { MODULE_ID } from '../../constants/module';\nimport { getProgressStats } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { DEFAULT_REPO_FILE_BATCH_SIZE } from '../../constants/default-repo-file-batch-size';\nimport {\n GitItem,\n GitRepository,\n} from 'azure-devops-node-api/interfaces/GitInterfaces';\n\ntype RepositoryIngestorOptions = {\n config: RootConfigService;\n logger: LoggerService;\n azureDevOpsService: AzureDevOpsService;\n};\n\nexport const createRepositoryIngestor = async ({\n config,\n logger,\n azureDevOpsService,\n}: RepositoryIngestorOptions) => {\n // Get configuration values\n const repositoriesFilter = config.getOptional<\n Config['aiAssistant']['ingestors']['azureDevOps']['repositories']\n >('aiAssistant.ingestors.azureDevOps.repositories');\n\n // Default to common file types if none are specified\n const fileTypes =\n config.getOptionalStringArray(\n 'aiAssistant.ingestors.azureDevOps.fileTypes',\n ) ?? DEFAULT_FILE_TYPES;\n\n // Get batch size for processing repository items (default to 50 items per batch)\n const itemsBatchSize =\n config.getOptionalNumber(\n 'aiAssistant.ingestors.azureDevOps.filesBatchSize', // Reuse the same config for consistency\n ) ?? DEFAULT_REPO_FILE_BATCH_SIZE;\n\n /**\n * Ingest Azure DevOps repository items in batches\n * @param repository - The repository to ingest items from\n * @param items - The list of items to ingest from the repository\n * @param saveDocumentsBatch - Function to save a batch of embedding documents\n * @returns Total number of documents ingested and sent for embedding from the repository\n */\n const ingestRepoByFileBatch = async ({\n repository,\n items,\n saveDocumentsBatch,\n }: {\n repository: GitRepository;\n items: GitItem[];\n saveDocumentsBatch: IngestorOptions['saveDocumentsBatch'];\n }) => {\n logger.info(\n `Processing ${items.length} items from repository \"${repository.name}\" in batches of ${itemsBatchSize}`,\n );\n\n logger.debug(`Items: ${JSON.stringify(items, null, 2)}`);\n\n let totalDocumentsIngested = 0;\n\n // Process items in batches to manage memory and performance\n const totalBatches = Math.ceil(items.length / itemsBatchSize);\n\n for (\n let batchStart = 0;\n batchStart < items.length;\n batchStart += itemsBatchSize\n ) {\n const batchEnd = Math.min(batchStart + itemsBatchSize, items.length);\n const itemsBatch = items.slice(batchStart, batchEnd);\n const batchNumber = Math.floor(batchStart / itemsBatchSize) + 1;\n\n logger.info(\n `Processing batch ${batchNumber}/${totalBatches} (${itemsBatch.length} items) for repository \"${repository.name}\"`,\n );\n\n // Generate embedding documents for each item in the current batch\n const documents: EmbeddingDocument[] = [];\n\n for (let index = 0; index < itemsBatch.length; index++) {\n const item = itemsBatch[index];\n const globalIndex = batchStart + index;\n\n const content = await azureDevOpsService.getRepoItemContent(\n repository.id!,\n item.path!,\n );\n\n const completionStats = getProgressStats(globalIndex + 1, items.length);\n\n logger.info(\n `Retrieved content for Azure DevOps item: ${item.path} in repository: \"${repository.name}\" [Progress: ${completionStats.completed}/${completionStats.total} (${completionStats.percentage}%) completed of repository]`,\n );\n\n const text = await streamToString(content);\n\n const document: EmbeddingDocument = {\n metadata: {\n source: MODULE_ID,\n id: `${repository.id}:${item.path}`,\n url: item.url!,\n organization: azureDevOpsService.organization,\n project: azureDevOpsService.project,\n repository: repository.name!,\n },\n content: text,\n };\n\n documents.push(document);\n }\n\n // Save the current batch of documents\n await saveDocumentsBatch(documents);\n\n totalDocumentsIngested += documents.length;\n\n logger.info(\n `Batch ${batchNumber}/${totalBatches} completed: ${documents.length} documents ingested for Azure DevOps repository: ${repository.name}`,\n );\n }\n\n return { totalDocumentsIngested };\n };\n\n /** Ingest Azure DevOps repositories in batches\n * @param saveDocumentsBatch - Function to save a batch of embedding documents\n * @returns void\n */\n const ingestRepositoriesBatch = async (\n saveDocumentsBatch: IngestorOptions['saveDocumentsBatch'],\n ) => {\n const repositoriesList = await azureDevOpsService.getRepos();\n\n if (repositoriesList.length === 0) {\n logger.warn('No repositories found in the Azure DevOps project');\n return;\n }\n\n logger.info(\n `Filtering for repositories: ${repositoriesFilter\n ?.map(repo => repo.name)\n .join(', ')}`,\n );\n\n // Filter repositories if a filter is provided in the config\n const repositoriesToIngest = repositoriesFilter\n ? repositoriesList.filter(repo =>\n repositoriesFilter?.some(\n filteredRepo =>\n filteredRepo.name.toLowerCase() === repo.name!.toLowerCase(),\n ),\n )\n : repositoriesList;\n\n if (repositoriesToIngest.length === 0) {\n logger.warn(\n 'No repositories found for ingestion after applying the filter',\n );\n return;\n }\n\n logger.info(\n `Ingesting ${repositoriesToIngest.length} repositories from Azure DevOps`,\n );\n\n // Get items from each repository and create documents to be embedded\n for (const repo of repositoriesToIngest) {\n logger.info(\n `Beginning ingestion for repository: ${repo.name} (${repo.id})`,\n );\n\n // Determine the file types to use for this repository or use default\n const repositoryFileTypesFilter =\n repositoriesFilter?.find(\n r => r.name.toLowerCase() === repo.name!.toLowerCase(),\n )?.fileTypes ?? fileTypes;\n\n logger.info(\n `Processing file types for repository ${\n repo.name\n }: [${repositoryFileTypesFilter.join(', ')}]`,\n );\n\n // Get the items to be ingested from the repository based on the file types filter\n const items = await azureDevOpsService.getRepoItems(\n repo.id!,\n repositoryFileTypesFilter,\n );\n\n if (items.length === 0) {\n logger.warn(\n `No items found for ingestion in the Azure DevOps repository ${\n repo.name\n } (${\n repo.id\n }) with the specified file types filter: [${repositoryFileTypesFilter.join(\n ', ',\n )}]`,\n );\n continue;\n }\n\n const { totalDocumentsIngested } = await ingestRepoByFileBatch({\n repository: repo,\n items,\n saveDocumentsBatch,\n });\n\n if (totalDocumentsIngested === 0) {\n logger.warn(\n `No documents were ingested and sent for embedding from the Azure DevOps repository ${repo.name} (${repo.id})`,\n );\n continue;\n }\n\n logger.info(\n `Repository ingestion completed: ${totalDocumentsIngested} total documents ingested and sent for embedding for Azure DevOps repository: ${repo.name}`,\n );\n }\n };\n\n return { ingestRepositoriesBatch };\n};\n"],"names":["DEFAULT_FILE_TYPES","DEFAULT_REPO_FILE_BATCH_SIZE","getProgressStats","streamToString","MODULE_ID"],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
|
|
4
|
+
var module$1 = require('../../constants/module.cjs.js');
|
|
5
|
+
var backstagePluginAiAssistantCommon = require('@sweetoburrito/backstage-plugin-ai-assistant-common');
|
|
6
|
+
var defaultWikiPageBatchSize = require('../../constants/default-wiki-page-batch-size.cjs.js');
|
|
7
|
+
|
|
8
|
+
const createWikiIngestor = async ({
|
|
9
|
+
config,
|
|
10
|
+
logger,
|
|
11
|
+
azureDevOpsService
|
|
12
|
+
}) => {
|
|
13
|
+
const wikisFilter = config.getOptional("aiAssistant.ingestors.azureDevOps.wikis");
|
|
14
|
+
const pagesBatchSize = config.getOptionalNumber(
|
|
15
|
+
"aiAssistant.ingestors.azureDevOps.pagesBatchSize"
|
|
16
|
+
) ?? defaultWikiPageBatchSize.DEFAULT_WIKI_PAGE_BATCH_SIZE;
|
|
17
|
+
const ingestWikiByPageBatch = async ({
|
|
18
|
+
wiki,
|
|
19
|
+
pages,
|
|
20
|
+
saveDocumentsBatch
|
|
21
|
+
}) => {
|
|
22
|
+
logger.info(
|
|
23
|
+
`Processing ${pages.length} pages from wiki "${wiki.name}" in batches of ${pagesBatchSize}`
|
|
24
|
+
);
|
|
25
|
+
let totalDocumentsIngested = 0;
|
|
26
|
+
const totalBatches = Math.ceil(pages.length / pagesBatchSize);
|
|
27
|
+
for (let batchStart = 0; batchStart < pages.length; batchStart += pagesBatchSize) {
|
|
28
|
+
const batchEnd = Math.min(batchStart + pagesBatchSize, pages.length);
|
|
29
|
+
const pagesBatch = pages.slice(batchStart, batchEnd);
|
|
30
|
+
const batchNumber = Math.floor(batchStart / pagesBatchSize) + 1;
|
|
31
|
+
logger.info(
|
|
32
|
+
`Processing batch ${batchNumber}/${totalBatches} (${pagesBatch.length} pages) for wiki "${wiki.name}"`
|
|
33
|
+
);
|
|
34
|
+
const documents = [];
|
|
35
|
+
for (let index = 0; index < pagesBatch.length; index++) {
|
|
36
|
+
const page = pagesBatch[index];
|
|
37
|
+
const globalIndex = batchStart + index;
|
|
38
|
+
const content = await azureDevOpsService.getWikiPageContent(
|
|
39
|
+
wiki.id,
|
|
40
|
+
page.id
|
|
41
|
+
);
|
|
42
|
+
const completionStats = backstagePluginAiAssistantCommon.getProgressStats(globalIndex + 1, pages.length);
|
|
43
|
+
logger.info(
|
|
44
|
+
`Retrieved content for Azure DevOps page: "${page.path}" in wiki: "${wiki.name}" [Progress: ${completionStats.completed}/${completionStats.total} (${completionStats.percentage}%) completed of wiki]`
|
|
45
|
+
);
|
|
46
|
+
const text = await backstagePluginAiAssistantNode.streamToString(content);
|
|
47
|
+
const document = {
|
|
48
|
+
metadata: {
|
|
49
|
+
source: module$1.MODULE_ID,
|
|
50
|
+
id: `${wiki.id}:${page.path}`,
|
|
51
|
+
url: page.url,
|
|
52
|
+
organization: azureDevOpsService.organization,
|
|
53
|
+
project: azureDevOpsService.project,
|
|
54
|
+
wiki: wiki.name
|
|
55
|
+
},
|
|
56
|
+
content: text
|
|
57
|
+
};
|
|
58
|
+
documents.push(document);
|
|
59
|
+
}
|
|
60
|
+
await saveDocumentsBatch(documents);
|
|
61
|
+
totalDocumentsIngested += documents.length;
|
|
62
|
+
logger.info(
|
|
63
|
+
`Batch ${batchNumber}/${totalBatches} completed: ${documents.length} documents ingested for Azure DevOps wiki: ${wiki.name}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return { totalDocumentsIngested };
|
|
67
|
+
};
|
|
68
|
+
const ingestWikisBatch = async (saveDocumentsBatch) => {
|
|
69
|
+
const wikisList = await azureDevOpsService.getWikis();
|
|
70
|
+
if (wikisList.length === 0) {
|
|
71
|
+
logger.warn("No wikis found in the Azure DevOps project");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
logger.info(
|
|
75
|
+
`Filtering for wikis: ${wikisFilter?.map((repo) => repo.name).join(", ")}`
|
|
76
|
+
);
|
|
77
|
+
const wikisToIngest = wikisFilter ? wikisList.filter(
|
|
78
|
+
(wiki) => wikisFilter?.some(
|
|
79
|
+
(filteredWiki) => filteredWiki.name.toLowerCase() === wiki.name.toLowerCase()
|
|
80
|
+
)
|
|
81
|
+
) : wikisList;
|
|
82
|
+
if (wikisToIngest.length === 0) {
|
|
83
|
+
logger.warn("No wikis found for ingestion after applying the filter");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
logger.info(`Ingesting ${wikisToIngest.length} wikis from Azure DevOps`);
|
|
87
|
+
for (const wiki of wikisToIngest) {
|
|
88
|
+
logger.info(`Beginning ingestion for wiki: ${wiki.name} (${wiki.id})`);
|
|
89
|
+
const pages = await azureDevOpsService.getWikiPages(wiki.id);
|
|
90
|
+
if (pages.length === 0) {
|
|
91
|
+
logger.warn(
|
|
92
|
+
`No pages found for ingestion in the Azure DevOps wiki ${wiki.name} (${wiki.id})`
|
|
93
|
+
);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const { totalDocumentsIngested } = await ingestWikiByPageBatch({
|
|
97
|
+
wiki,
|
|
98
|
+
pages,
|
|
99
|
+
saveDocumentsBatch
|
|
100
|
+
});
|
|
101
|
+
if (totalDocumentsIngested === 0) {
|
|
102
|
+
logger.warn(
|
|
103
|
+
`No documents were ingested and sent for embedding from the Azure DevOps wiki ${wiki.name} (${wiki.id})`
|
|
104
|
+
);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
logger.info(
|
|
108
|
+
`Wiki ingestion completed: ${totalDocumentsIngested} total documents ingested and sent for embedding for Azure DevOps wiki: ${wiki.name}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return { ingestWikisBatch };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
exports.createWikiIngestor = createWikiIngestor;
|
|
116
|
+
//# sourceMappingURL=wiki.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki.cjs.js","sources":["../../../src/services/ingestor/wiki.ts"],"sourcesContent":["import {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport {\n EmbeddingDocument,\n IngestorOptions,\n streamToString,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { AzureDevOpsService } from '../azure-devops';\nimport { Config } from '../../../config';\nimport { MODULE_ID } from '../../constants/module';\nimport { getProgressStats } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { DEFAULT_WIKI_PAGE_BATCH_SIZE } from '../../constants/default-wiki-page-batch-size';\nimport {\n WikiPage,\n WikiV2,\n} from 'azure-devops-node-api/interfaces/WikiInterfaces';\n\ntype WikiIngestorOptions = {\n config: RootConfigService;\n logger: LoggerService;\n azureDevOpsService: AzureDevOpsService;\n};\n\nexport const createWikiIngestor = async ({\n config,\n logger,\n azureDevOpsService,\n}: WikiIngestorOptions) => {\n // Get configuration values\n const wikisFilter = config.getOptional<\n Config['aiAssistant']['ingestors']['azureDevOps']['wikis']\n >('aiAssistant.ingestors.azureDevOps.wikis');\n\n // Get batch size for processing pages (default to 50 pages per batch)\n const pagesBatchSize =\n config.getOptionalNumber(\n 'aiAssistant.ingestors.azureDevOps.pagesBatchSize',\n ) ?? DEFAULT_WIKI_PAGE_BATCH_SIZE;\n\n /** Ingest Azure DevOps wiki pages in batches\n * @param wiki - The wiki to ingest pages from\n * @param pages - The list of pages to ingest from the wiki\n * @param saveDocumentsBatch - Function to save a batch of embedding documents\n * @returns Total number of documents ingested and sent for embedding from the wiki\n */\n const ingestWikiByPageBatch = async ({\n wiki,\n pages,\n saveDocumentsBatch,\n }: {\n wiki: WikiV2;\n pages: WikiPage[];\n saveDocumentsBatch: IngestorOptions['saveDocumentsBatch'];\n }) => {\n logger.info(\n `Processing ${pages.length} pages from wiki \"${wiki.name}\" in batches of ${pagesBatchSize}`,\n );\n\n let totalDocumentsIngested = 0;\n\n // Process pages in batches to manage memory and performance\n\n // Calculate total number of batches\n const totalBatches = Math.ceil(pages.length / pagesBatchSize);\n\n // Process each batch\n for (\n let batchStart = 0;\n batchStart < pages.length;\n batchStart += pagesBatchSize\n ) {\n const batchEnd = Math.min(batchStart + pagesBatchSize, pages.length);\n const pagesBatch = pages.slice(batchStart, batchEnd);\n const batchNumber = Math.floor(batchStart / pagesBatchSize) + 1;\n\n logger.info(\n `Processing batch ${batchNumber}/${totalBatches} (${pagesBatch.length} pages) for wiki \"${wiki.name}\"`,\n );\n\n // Generate embedding documents for each page in the current batch\n const documents: EmbeddingDocument[] = [];\n\n for (let index = 0; index < pagesBatch.length; index++) {\n const page = pagesBatch[index];\n const globalIndex = batchStart + index;\n\n const content = await azureDevOpsService.getWikiPageContent(\n wiki.id!,\n page.id!,\n );\n\n const completionStats = getProgressStats(globalIndex + 1, pages.length);\n\n logger.info(\n `Retrieved content for Azure DevOps page: \"${page.path}\" in wiki: \"${wiki.name}\" [Progress: ${completionStats.completed}/${completionStats.total} (${completionStats.percentage}%) completed of wiki]`,\n );\n\n const text = await streamToString(content);\n\n const document: EmbeddingDocument = {\n metadata: {\n source: MODULE_ID,\n id: `${wiki.id}:${page.path}`,\n url: page.url!,\n organization: azureDevOpsService.organization,\n project: azureDevOpsService.project,\n wiki: wiki.name!,\n },\n content: text,\n };\n\n documents.push(document);\n }\n\n // Save the current batch of documents\n await saveDocumentsBatch(documents);\n\n totalDocumentsIngested += documents.length;\n\n logger.info(\n `Batch ${batchNumber}/${totalBatches} completed: ${documents.length} documents ingested for Azure DevOps wiki: ${wiki.name}`,\n );\n }\n\n return { totalDocumentsIngested };\n };\n\n /** Ingest Azure DevOps wikis in batches */\n const ingestWikisBatch = async (\n saveDocumentsBatch: IngestorOptions['saveDocumentsBatch'],\n ) => {\n const wikisList = await azureDevOpsService.getWikis();\n\n if (wikisList.length === 0) {\n logger.warn('No wikis found in the Azure DevOps project');\n return;\n }\n\n logger.info(\n `Filtering for wikis: ${wikisFilter?.map(repo => repo.name).join(', ')}`,\n );\n\n // Filter wikis if a filter is provided in the config\n const wikisToIngest = wikisFilter\n ? wikisList.filter(wiki =>\n wikisFilter?.some(\n filteredWiki =>\n filteredWiki.name.toLowerCase() === wiki.name!.toLowerCase(),\n ),\n )\n : wikisList;\n\n if (wikisToIngest.length === 0) {\n logger.warn('No wikis found for ingestion after applying the filter');\n return;\n }\n\n logger.info(`Ingesting ${wikisToIngest.length} wikis from Azure DevOps`);\n\n // Get items from each wiki and create documents to be embedded\n for (const wiki of wikisToIngest) {\n logger.info(`Beginning ingestion for wiki: ${wiki.name} (${wiki.id})`);\n\n // Get the pages to be ingested from the wiki based on the file types filter\n const pages = await azureDevOpsService.getWikiPages(wiki.id!);\n\n if (pages.length === 0) {\n logger.warn(\n `No pages found for ingestion in the Azure DevOps wiki ${wiki.name} (${wiki.id})`,\n );\n continue;\n }\n\n const { totalDocumentsIngested } = await ingestWikiByPageBatch({\n wiki,\n pages,\n saveDocumentsBatch,\n });\n\n if (totalDocumentsIngested === 0) {\n logger.warn(\n `No documents were ingested and sent for embedding from the Azure DevOps wiki ${wiki.name} (${wiki.id})`,\n );\n continue;\n }\n\n logger.info(\n `Wiki ingestion completed: ${totalDocumentsIngested} total documents ingested and sent for embedding for Azure DevOps wiki: ${wiki.name}`,\n );\n }\n };\n\n return { ingestWikisBatch };\n};\n"],"names":["DEFAULT_WIKI_PAGE_BATCH_SIZE","getProgressStats","streamToString","MODULE_ID"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const flattenWikiPages = (page) => {
|
|
4
|
+
const result = [page];
|
|
5
|
+
if (page.subPages && page.subPages.length > 0) {
|
|
6
|
+
for (const subPage of page.subPages) {
|
|
7
|
+
result.push(...flattenWikiPages(subPage));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
exports.flattenWikiPages = flattenWikiPages;
|
|
14
|
+
//# sourceMappingURL=flatten-wiki-pages.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten-wiki-pages.cjs.js","sources":["../../src/utils/flatten-wiki-pages.ts"],"sourcesContent":["import { WikiPage } from 'azure-devops-node-api/interfaces/WikiInterfaces';\n\n/**\n * Recursively flatten wiki pages structure into a single array\n * @param page The wiki page to flatten\n * @returns Array of all pages (including nested subpages)\n */\nexport const flattenWikiPages = (page: WikiPage): WikiPage[] => {\n const result: WikiPage[] = [page];\n\n if (page.subPages && page.subPages.length > 0) {\n for (const subPage of page.subPages) {\n result.push(...flattenWikiPages(subPage));\n }\n }\n\n return result;\n};\n"],"names":[],"mappings":";;AAOO,MAAM,gBAAA,GAAmB,CAAC,IAAA,KAA+B;AAC9D,EAAA,MAAM,MAAA,GAAqB,CAAC,IAAI,CAAA;AAEhC,EAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EAAG;AAC7C,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,QAAA,EAAU;AACnC,MAAA,MAAA,CAAO,IAAA,CAAK,GAAG,gBAAA,CAAiB,OAAO,CAAC,CAAA;AAAA,IAC1C;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-azure-devops",
|
|
3
|
+
"version": "0.0.0-snapshot-20251029145101",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"description": "The ingestor-azure-devops backend module for the ai-assistant plugin.",
|
|
6
|
+
"main": "dist/index.cjs.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"main": "dist/index.cjs.js",
|
|
11
|
+
"types": "dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"backstage": {
|
|
14
|
+
"role": "backend-plugin-module",
|
|
15
|
+
"pluginId": "ai-assistant",
|
|
16
|
+
"pluginPackage": "@sweetoburrito/backstage-plugin-ai-assistant-backend",
|
|
17
|
+
"features": {
|
|
18
|
+
".": "@backstage/BackendFeature"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "backstage-cli package start",
|
|
23
|
+
"build": "backstage-cli package build",
|
|
24
|
+
"lint": "backstage-cli package lint",
|
|
25
|
+
"test": "backstage-cli package test",
|
|
26
|
+
"clean": "backstage-cli package clean",
|
|
27
|
+
"prepack": "backstage-cli package prepack",
|
|
28
|
+
"postpack": "backstage-cli package postpack"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@backstage/backend-plugin-api": "^1.4.1",
|
|
32
|
+
"@sweetoburrito/backstage-plugin-ai-assistant-common": "0.0.0-snapshot-20251029145101",
|
|
33
|
+
"@sweetoburrito/backstage-plugin-ai-assistant-node": "^0.5.1",
|
|
34
|
+
"azure-devops-node-api": "^15.1.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@backstage/backend-test-utils": "^1.7.0",
|
|
38
|
+
"@backstage/cli": "^0.33.1"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"config.d.ts"
|
|
43
|
+
],
|
|
44
|
+
"configSchema": "config.d.ts",
|
|
45
|
+
"typesVersions": {
|
|
46
|
+
"*": {
|
|
47
|
+
"package.json": [
|
|
48
|
+
"package.json"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|