@get-technology-inc/jamf-docs-mcp-server 3.0.5 → 3.0.7
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/dist/core/constants/doc-types.d.ts +48 -0
- package/dist/core/constants/doc-types.d.ts.map +1 -0
- package/dist/core/constants/doc-types.js +58 -0
- package/dist/core/constants/doc-types.js.map +1 -0
- package/dist/core/constants/index.d.ts +16 -0
- package/dist/core/constants/index.d.ts.map +1 -0
- package/dist/core/constants/index.js +12 -0
- package/dist/core/constants/index.js.map +1 -0
- package/dist/core/constants/limits.d.ts +39 -0
- package/dist/core/constants/limits.d.ts.map +1 -0
- package/dist/core/constants/limits.js +51 -0
- package/dist/core/constants/limits.js.map +1 -0
- package/dist/core/constants/locales.d.ts +34 -0
- package/dist/core/constants/locales.d.ts.map +1 -0
- package/dist/core/constants/locales.js +20 -0
- package/dist/core/constants/locales.js.map +1 -0
- package/dist/core/constants/products.d.ts +127 -0
- package/dist/core/constants/products.d.ts.map +1 -0
- package/dist/core/constants/products.js +135 -0
- package/dist/core/constants/products.js.map +1 -0
- package/dist/core/constants/topics.d.ts +168 -0
- package/dist/core/constants/topics.d.ts.map +1 -0
- package/dist/core/constants/topics.js +186 -0
- package/dist/core/constants/topics.js.map +1 -0
- package/dist/core/constants.d.ts +7 -404
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +16 -427
- package/dist/core/constants.js.map +1 -1
- package/dist/core/http-client.d.ts +14 -2
- package/dist/core/http-client.d.ts.map +1 -1
- package/dist/core/http-client.js +148 -17
- package/dist/core/http-client.js.map +1 -1
- package/dist/core/index.d.ts +12 -12
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +12 -13
- package/dist/core/index.js.map +1 -1
- package/dist/core/resources/index.js +1 -1
- package/dist/core/resources/index.js.map +1 -1
- package/dist/core/schemas/index.d.ts +3 -1
- package/dist/core/schemas/index.d.ts.map +1 -1
- package/dist/core/schemas/index.js +12 -1
- package/dist/core/schemas/index.js.map +1 -1
- package/dist/core/schemas/output.d.ts +7 -0
- package/dist/core/schemas/output.d.ts.map +1 -1
- package/dist/core/schemas/output.js +7 -0
- package/dist/core/schemas/output.js.map +1 -1
- package/dist/core/services/article-service.d.ts +24 -0
- package/dist/core/services/article-service.d.ts.map +1 -0
- package/dist/core/services/article-service.js +118 -0
- package/dist/core/services/article-service.js.map +1 -0
- package/dist/core/services/content-parser.d.ts +38 -0
- package/dist/core/services/content-parser.d.ts.map +1 -0
- package/dist/core/services/content-parser.js +146 -0
- package/dist/core/services/content-parser.js.map +1 -0
- package/dist/core/services/ft-client.d.ts +52 -0
- package/dist/core/services/ft-client.d.ts.map +1 -0
- package/dist/core/services/ft-client.js +87 -0
- package/dist/core/services/ft-client.js.map +1 -0
- package/dist/core/services/glossary.d.ts +9 -5
- package/dist/core/services/glossary.d.ts.map +1 -1
- package/dist/core/services/glossary.js +174 -107
- package/dist/core/services/glossary.js.map +1 -1
- package/dist/core/services/interfaces/cache.d.ts +6 -6
- package/dist/core/services/interfaces/cache.d.ts.map +1 -1
- package/dist/core/services/interfaces/index.d.ts +3 -3
- package/dist/core/services/interfaces/index.d.ts.map +1 -1
- package/dist/core/services/interfaces/logger.d.ts +17 -10
- package/dist/core/services/interfaces/logger.d.ts.map +1 -1
- package/dist/core/services/interfaces/metadata.d.ts +0 -10
- package/dist/core/services/interfaces/metadata.d.ts.map +1 -1
- package/dist/core/services/interfaces/providers.d.ts +26 -10
- package/dist/core/services/interfaces/providers.d.ts.map +1 -1
- package/dist/core/services/logging.d.ts +38 -18
- package/dist/core/services/logging.d.ts.map +1 -1
- package/dist/core/services/logging.js +78 -29
- package/dist/core/services/logging.js.map +1 -1
- package/dist/core/services/maps-registry.d.ts +82 -0
- package/dist/core/services/maps-registry.d.ts.map +1 -0
- package/dist/core/services/maps-registry.js +232 -0
- package/dist/core/services/maps-registry.js.map +1 -0
- package/dist/core/services/metadata.d.ts +18 -9
- package/dist/core/services/metadata.d.ts.map +1 -1
- package/dist/core/services/metadata.js +113 -254
- package/dist/core/services/metadata.js.map +1 -1
- package/dist/core/services/search-service.d.ts +31 -0
- package/dist/core/services/search-service.d.ts.map +1 -0
- package/dist/core/services/search-service.js +471 -0
- package/dist/core/services/search-service.js.map +1 -0
- package/dist/core/services/toc-service.d.ts +30 -0
- package/dist/core/services/toc-service.d.ts.map +1 -0
- package/dist/core/services/toc-service.js +101 -0
- package/dist/core/services/toc-service.js.map +1 -0
- package/dist/core/services/tokenizer.d.ts +1 -1
- package/dist/core/services/tokenizer.d.ts.map +1 -1
- package/dist/core/services/tokenizer.js +3 -3
- package/dist/core/services/tokenizer.js.map +1 -1
- package/dist/core/services/topic-resolver.d.ts +57 -0
- package/dist/core/services/topic-resolver.d.ts.map +1 -0
- package/dist/core/services/topic-resolver.js +185 -0
- package/dist/core/services/topic-resolver.js.map +1 -0
- package/dist/core/tools/batch-get-articles.d.ts +0 -2
- package/dist/core/tools/batch-get-articles.d.ts.map +1 -1
- package/dist/core/tools/batch-get-articles.js +12 -46
- package/dist/core/tools/batch-get-articles.js.map +1 -1
- package/dist/core/tools/get-article.d.ts.map +1 -1
- package/dist/core/tools/get-article.js +29 -112
- package/dist/core/tools/get-article.js.map +1 -1
- package/dist/core/tools/get-toc.js +1 -1
- package/dist/core/tools/get-toc.js.map +1 -1
- package/dist/core/tools/search.js +5 -5
- package/dist/core/tools/search.js.map +1 -1
- package/dist/core/types/context.d.ts +5 -2
- package/dist/core/types/context.d.ts.map +1 -1
- package/dist/core/types.d.ts +141 -31
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/utils/bundle.d.ts +14 -1
- package/dist/core/utils/bundle.d.ts.map +1 -1
- package/dist/core/utils/bundle.js +19 -2
- package/dist/core/utils/bundle.js.map +1 -1
- package/dist/core/utils/format-article.d.ts +45 -0
- package/dist/core/utils/format-article.d.ts.map +1 -0
- package/dist/core/utils/format-article.js +138 -0
- package/dist/core/utils/format-article.js.map +1 -0
- package/dist/core/utils/ft-metadata.d.ts +22 -0
- package/dist/core/utils/ft-metadata.d.ts.map +1 -0
- package/dist/core/utils/ft-metadata.js +30 -0
- package/dist/core/utils/ft-metadata.js.map +1 -0
- package/dist/core/utils/sanitize.d.ts.map +1 -1
- package/dist/core/utils/sanitize.js +0 -2
- package/dist/core/utils/sanitize.js.map +1 -1
- package/dist/core/utils/url.d.ts.map +1 -1
- package/dist/core/utils/url.js +0 -1
- package/dist/core/utils/url.js.map +1 -1
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/platforms/node/http-server.d.ts +13 -0
- package/dist/platforms/node/http-server.d.ts.map +1 -0
- package/dist/platforms/node/http-server.js +231 -0
- package/dist/platforms/node/http-server.js.map +1 -0
- package/dist/platforms/node/index.d.ts +1 -1
- package/dist/platforms/node/index.d.ts.map +1 -1
- package/dist/platforms/node/index.js +1 -1
- package/dist/platforms/node/index.js.map +1 -1
- package/dist/platforms/node/logger.d.ts +13 -2
- package/dist/platforms/node/logger.d.ts.map +1 -1
- package/dist/platforms/node/logger.js +17 -4
- package/dist/platforms/node/logger.js.map +1 -1
- package/dist/transport/http-handler.d.ts +25 -0
- package/dist/transport/http-handler.d.ts.map +1 -0
- package/dist/transport/http-handler.js +193 -0
- package/dist/transport/http-handler.js.map +1 -0
- package/dist/transport/http-types.d.ts +22 -0
- package/dist/transport/http-types.d.ts.map +1 -0
- package/dist/transport/http-types.js +65 -0
- package/dist/transport/http-types.js.map +1 -0
- package/dist/transport/http.d.ts +4 -8
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +4 -376
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/index.js +2 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/rate-limiter.d.ts +16 -0
- package/dist/transport/rate-limiter.d.ts.map +1 -0
- package/dist/transport/rate-limiter.js +55 -0
- package/dist/transport/rate-limiter.js.map +1 -0
- package/package.json +3 -3
- package/dist/core/services/interfaces.d.ts +0 -94
- package/dist/core/services/interfaces.d.ts.map +0 -1
- package/dist/core/services/interfaces.js +0 -8
- package/dist/core/services/interfaces.js.map +0 -1
- package/dist/core/services/scraper.d.ts +0 -90
- package/dist/core/services/scraper.d.ts.map +0 -1
- package/dist/core/services/scraper.js +0 -966
- package/dist/core/services/scraper.js.map +0 -1
- package/dist/core/utils/doc-type.d.ts +0 -12
- package/dist/core/utils/doc-type.d.ts.map +0 -1
- package/dist/core/utils/doc-type.js +0 -24
- package/dist/core/utils/doc-type.js.map +0 -1
- package/dist/platforms/node/metadata.d.ts +0 -18
- package/dist/platforms/node/metadata.d.ts.map +0 -1
- package/dist/platforms/node/metadata.js +0 -35
- package/dist/platforms/node/metadata.js.map +0 -1
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search service — Fluid Topics powered search
|
|
3
|
+
*/
|
|
4
|
+
import { JAMF_PRODUCTS, JAMF_TOPICS, DOC_TYPE_CONTENT_TYPE_MAP, DOC_TYPE_LABEL_MAP, CONTENT_LIMITS, TOKEN_CONFIG, PAGINATION_CONFIG, DEFAULT_LOCALE, } from '../constants.js';
|
|
5
|
+
import { search as ftSearch } from './ft-client.js';
|
|
6
|
+
import { buildDisplayUrl } from './topic-resolver.js';
|
|
7
|
+
import { cleanSnippet } from './content-parser.js';
|
|
8
|
+
import { getMetaValues, FT_META } from '../utils/ft-metadata.js';
|
|
9
|
+
import { estimateTokens, calculatePagination, truncateItemsToTokenLimit, } from './tokenizer.js';
|
|
10
|
+
// ─── Helpers ───────────────────────────────────────────────────
|
|
11
|
+
/** Pre-computed lowercase keywords per topic */
|
|
12
|
+
const TOPIC_KEYWORDS_LOWER = Object.fromEntries(Object.keys(JAMF_TOPICS).map(id => [
|
|
13
|
+
id,
|
|
14
|
+
JAMF_TOPICS[id].keywords.map(k => k.toLowerCase()),
|
|
15
|
+
]));
|
|
16
|
+
const ALL_TOPIC_IDS = Object.keys(JAMF_TOPICS);
|
|
17
|
+
function matchTopics(title, snippet) {
|
|
18
|
+
const searchText = `${title} ${snippet}`.toLowerCase();
|
|
19
|
+
return ALL_TOPIC_IDS.filter(topicId => TOPIC_KEYWORDS_LOWER[topicId].some(kw => searchText.includes(kw)));
|
|
20
|
+
}
|
|
21
|
+
/** Pre-computed reverse lookup: product display name → ProductId */
|
|
22
|
+
const PRODUCT_NAME_TO_ID = Object.fromEntries(Object.keys(JAMF_PRODUCTS).map(id => [JAMF_PRODUCTS[id].name, id]));
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a product display name (e.g. 'Jamf Pro') to its ProductId
|
|
25
|
+
* (e.g. 'jamf-pro'). Returns null when the name is unknown.
|
|
26
|
+
*/
|
|
27
|
+
function productNameToId(name) {
|
|
28
|
+
if (name === null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return PRODUCT_NAME_TO_ID[name] ?? null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Extract product ID (e.g. 'jamf-pro') from a legacy metadata value
|
|
35
|
+
* like 'product-pro'. Falls back to scanning known searchLabel values.
|
|
36
|
+
*/
|
|
37
|
+
function extractProductFromZoominMeta(metadata) {
|
|
38
|
+
const values = getMetaValues(metadata, FT_META.ZOOMIN_METADATA);
|
|
39
|
+
for (const val of values) {
|
|
40
|
+
// Match against known searchLabels in JAMF_PRODUCTS
|
|
41
|
+
const matched = Object.entries(JAMF_PRODUCTS).find(([, product]) => product.searchLabel === val);
|
|
42
|
+
if (matched !== undefined) {
|
|
43
|
+
return matched[1].name;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Derive docType from FT metadata `jamf:contentType`.
|
|
50
|
+
* Falls back to 'documentation'.
|
|
51
|
+
*
|
|
52
|
+
* Note: The FT API uses 'Technical Documentation' for multiple doc types
|
|
53
|
+
* (documentation, training, solution-guide, getting-started). Because
|
|
54
|
+
* DOC_TYPE_CONTENT_TYPE_MAP is iterated in insertion order, this reverse
|
|
55
|
+
* lookup always returns 'documentation' for any of those types. This is
|
|
56
|
+
* a known FT API limitation — there is no metadata to distinguish them.
|
|
57
|
+
*/
|
|
58
|
+
function docTypeFromFtMetadata(metadata) {
|
|
59
|
+
const values = getMetaValues(metadata, FT_META.CONTENT_TYPE);
|
|
60
|
+
if (values.length > 0) {
|
|
61
|
+
// Reverse-lookup DOC_TYPE_CONTENT_TYPE_MAP
|
|
62
|
+
for (const [docType, contentType] of Object.entries(DOC_TYPE_CONTENT_TYPE_MAP)) {
|
|
63
|
+
if (values.includes(contentType)) {
|
|
64
|
+
return docType;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return 'documentation';
|
|
69
|
+
}
|
|
70
|
+
// ─── Filter Construction ───────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Build Fluid Topics search filters from search params.
|
|
73
|
+
*
|
|
74
|
+
* - product → `zoominmetadata` filter using searchLabel
|
|
75
|
+
* - docType → `jamf:contentType` filter using DOC_TYPE_CONTENT_TYPE_MAP
|
|
76
|
+
* - version → `version` filter
|
|
77
|
+
* - auto-adds `latestVersion=yes` when no version specified
|
|
78
|
+
*/
|
|
79
|
+
export function buildSearchFilters(params) {
|
|
80
|
+
const filters = [];
|
|
81
|
+
// Product filter
|
|
82
|
+
if (params.product !== undefined) {
|
|
83
|
+
const productDef = JAMF_PRODUCTS[params.product];
|
|
84
|
+
filters.push({
|
|
85
|
+
key: FT_META.ZOOMIN_METADATA,
|
|
86
|
+
values: [productDef.searchLabel],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Document type filter
|
|
90
|
+
if (params.docType !== undefined) {
|
|
91
|
+
const contentType = DOC_TYPE_CONTENT_TYPE_MAP[params.docType];
|
|
92
|
+
if (contentType !== undefined) {
|
|
93
|
+
filters.push({
|
|
94
|
+
key: FT_META.CONTENT_TYPE,
|
|
95
|
+
values: [contentType],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Version handling
|
|
100
|
+
if (params.version !== undefined && params.version !== '' && params.version !== 'current') {
|
|
101
|
+
filters.push({
|
|
102
|
+
key: FT_META.VERSION,
|
|
103
|
+
values: [params.version],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Auto-add latestVersion filter when no specific version requested
|
|
108
|
+
filters.push({
|
|
109
|
+
key: FT_META.LATEST_VERSION,
|
|
110
|
+
values: ['yes'],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return filters;
|
|
114
|
+
}
|
|
115
|
+
// ─── Result Transformation ─────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Transform a Fluid Topics search entry into an enriched SearchResult.
|
|
118
|
+
*/
|
|
119
|
+
export function transformFtSearchResult(entry) {
|
|
120
|
+
if (entry.type === 'TOPIC' && entry.topic !== undefined) {
|
|
121
|
+
return transformTopicEntry(entry);
|
|
122
|
+
}
|
|
123
|
+
if (entry.type === 'MAP' && entry.map !== undefined) {
|
|
124
|
+
return transformMapEntry(entry);
|
|
125
|
+
}
|
|
126
|
+
// Fallback for unexpected entry shapes
|
|
127
|
+
return {
|
|
128
|
+
title: 'Untitled',
|
|
129
|
+
url: '',
|
|
130
|
+
snippet: '',
|
|
131
|
+
product: null,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Shared builder: turns the common fields of a TOPIC or MAP entry
|
|
136
|
+
* into a fully-populated SearchResult.
|
|
137
|
+
*/
|
|
138
|
+
function buildSearchResult(fields) {
|
|
139
|
+
const { metadata } = fields;
|
|
140
|
+
const title = fields.title !== '' ? fields.title : 'Untitled';
|
|
141
|
+
const product = extractProductFromZoominMeta(metadata);
|
|
142
|
+
const snippet = cleanSnippet(fields.htmlExcerpt, title, product);
|
|
143
|
+
const versionValues = getMetaValues(metadata, FT_META.VERSION);
|
|
144
|
+
const docType = docTypeFromFtMetadata(metadata);
|
|
145
|
+
const result = {
|
|
146
|
+
title,
|
|
147
|
+
url: fields.url,
|
|
148
|
+
snippet,
|
|
149
|
+
product,
|
|
150
|
+
docType,
|
|
151
|
+
mapId: fields.mapId,
|
|
152
|
+
};
|
|
153
|
+
if (fields.contentId !== undefined) {
|
|
154
|
+
result.contentId = fields.contentId;
|
|
155
|
+
}
|
|
156
|
+
const firstVersion = versionValues[0];
|
|
157
|
+
if (firstVersion !== undefined) {
|
|
158
|
+
result.version = firstVersion;
|
|
159
|
+
}
|
|
160
|
+
if (fields.breadcrumb !== undefined && fields.breadcrumb.length > 0) {
|
|
161
|
+
result.breadcrumb = fields.breadcrumb;
|
|
162
|
+
}
|
|
163
|
+
if (fields.mapTitle !== undefined && fields.mapTitle !== '') {
|
|
164
|
+
result.mapTitle = fields.mapTitle;
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
function resolveTopicUrl(topic) {
|
|
169
|
+
const prettyUrls = getMetaValues(topic.metadata, FT_META.PRETTY_URL);
|
|
170
|
+
const rawPrettyUrl = prettyUrls[0];
|
|
171
|
+
if (rawPrettyUrl !== undefined) {
|
|
172
|
+
const normalizedPath = rawPrettyUrl.startsWith('/')
|
|
173
|
+
? rawPrettyUrl
|
|
174
|
+
: `/r/${rawPrettyUrl}`;
|
|
175
|
+
return buildDisplayUrl(normalizedPath);
|
|
176
|
+
}
|
|
177
|
+
return buildDisplayUrl(`/r/en-US/${topic.mapId}/${topic.contentId}`);
|
|
178
|
+
}
|
|
179
|
+
function resolveMapUrl(map) {
|
|
180
|
+
if (map.readerUrl !== '') {
|
|
181
|
+
return buildDisplayUrl(map.readerUrl);
|
|
182
|
+
}
|
|
183
|
+
if (map.mapUrl !== '') {
|
|
184
|
+
return buildDisplayUrl(map.mapUrl);
|
|
185
|
+
}
|
|
186
|
+
return '';
|
|
187
|
+
}
|
|
188
|
+
function transformTopicEntry(entry) {
|
|
189
|
+
const { topic } = entry;
|
|
190
|
+
if (topic === undefined) {
|
|
191
|
+
return { title: 'Untitled', url: '', snippet: '', product: null };
|
|
192
|
+
}
|
|
193
|
+
return buildSearchResult({
|
|
194
|
+
title: topic.title,
|
|
195
|
+
url: resolveTopicUrl(topic),
|
|
196
|
+
htmlExcerpt: topic.htmlExcerpt,
|
|
197
|
+
metadata: topic.metadata,
|
|
198
|
+
mapId: topic.mapId,
|
|
199
|
+
contentId: topic.contentId,
|
|
200
|
+
breadcrumb: topic.breadcrumb,
|
|
201
|
+
mapTitle: topic.mapTitle,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function transformMapEntry(entry) {
|
|
205
|
+
const { map } = entry;
|
|
206
|
+
if (map === undefined) {
|
|
207
|
+
return { title: 'Untitled', url: '', snippet: '', product: null };
|
|
208
|
+
}
|
|
209
|
+
return buildSearchResult({
|
|
210
|
+
title: map.title,
|
|
211
|
+
url: resolveMapUrl(map),
|
|
212
|
+
htmlExcerpt: map.htmlExcerpt,
|
|
213
|
+
metadata: map.metadata,
|
|
214
|
+
mapId: map.mapId,
|
|
215
|
+
mapTitle: map.title,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// ─── Filter Relaxation ─────────────────────────────────────────
|
|
219
|
+
/**
|
|
220
|
+
* Build active filters from search params for progressive relaxation.
|
|
221
|
+
* These are client-side post-filters for topic matching (which FT
|
|
222
|
+
* doesn't support server-side).
|
|
223
|
+
*/
|
|
224
|
+
function buildActiveFilters(params) {
|
|
225
|
+
const activeFilters = [];
|
|
226
|
+
if (params.product !== undefined) {
|
|
227
|
+
const productId = params.product;
|
|
228
|
+
activeFilters.push({
|
|
229
|
+
name: 'product',
|
|
230
|
+
value: productId,
|
|
231
|
+
apply: (results) => results.filter(r => r.bundleSlug === productId),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
if (params.topic !== undefined) {
|
|
235
|
+
const topicFilter = params.topic;
|
|
236
|
+
activeFilters.push({
|
|
237
|
+
name: 'topic',
|
|
238
|
+
value: topicFilter,
|
|
239
|
+
apply: (results) => results.filter(r => r.matchedTopics.includes(topicFilter)),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (params.docType !== undefined) {
|
|
243
|
+
const docTypeFilter = params.docType;
|
|
244
|
+
const targetLabelKey = DOC_TYPE_LABEL_MAP[docTypeFilter];
|
|
245
|
+
if (targetLabelKey !== undefined) {
|
|
246
|
+
activeFilters.push({
|
|
247
|
+
name: 'docType',
|
|
248
|
+
value: docTypeFilter,
|
|
249
|
+
apply: (results) => results.filter(r => {
|
|
250
|
+
if (r.labelKeys.length === 0) {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
return r.labelKeys.includes(targetLabelKey);
|
|
254
|
+
}),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return activeFilters;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Apply filters with progressive relaxation when results are zero.
|
|
262
|
+
* Relaxation order: docType -> topic -> product
|
|
263
|
+
*/
|
|
264
|
+
function applyFiltersWithFallback(allResults, activeFilters) {
|
|
265
|
+
let filtered = allResults;
|
|
266
|
+
for (const filter of activeFilters) {
|
|
267
|
+
filtered = filter.apply(filtered);
|
|
268
|
+
}
|
|
269
|
+
if (filtered.length > 0 || activeFilters.length === 0) {
|
|
270
|
+
return { filtered };
|
|
271
|
+
}
|
|
272
|
+
// Progressive relaxation
|
|
273
|
+
const relaxOrder = ['docType', 'topic', 'product'];
|
|
274
|
+
const removed = [];
|
|
275
|
+
const original = {};
|
|
276
|
+
for (const filterName of relaxOrder) {
|
|
277
|
+
if (filtered.length > 0) {
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
const filterIndex = activeFilters.findIndex(f => f.name === filterName);
|
|
281
|
+
if (filterIndex === -1) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const removedFilter = activeFilters[filterIndex];
|
|
285
|
+
if (removedFilter === undefined) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
removed.push(removedFilter.name);
|
|
289
|
+
original[removedFilter.name] = removedFilter.value;
|
|
290
|
+
activeFilters.splice(filterIndex, 1);
|
|
291
|
+
// Re-apply remaining filters
|
|
292
|
+
filtered = allResults;
|
|
293
|
+
for (const filter of activeFilters) {
|
|
294
|
+
filtered = filter.apply(filtered);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (removed.length > 0) {
|
|
298
|
+
return {
|
|
299
|
+
filtered,
|
|
300
|
+
relaxation: {
|
|
301
|
+
removed,
|
|
302
|
+
original,
|
|
303
|
+
message: `No results with all filters applied. Removed filter(s): ${removed.join(', ')}. Try broader search terms or fewer filters.`,
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return { filtered };
|
|
308
|
+
}
|
|
309
|
+
// ─── Token Truncation ──────────────────────────────────────────
|
|
310
|
+
function resultToString(r) {
|
|
311
|
+
return `${r.title}\n${r.snippet}\n${r.url}`;
|
|
312
|
+
}
|
|
313
|
+
function truncateSearchResults(paginatedResults, maxTokens) {
|
|
314
|
+
const { items, tokenInfo } = truncateItemsToTokenLimit(paginatedResults, maxTokens, resultToString, 1, paginatedResults.length);
|
|
315
|
+
if (!tokenInfo.truncated) {
|
|
316
|
+
return {
|
|
317
|
+
finalResults: items,
|
|
318
|
+
finalTokenCount: tokenInfo.tokenCount,
|
|
319
|
+
truncated: false,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const omittedResults = paginatedResults.slice(items.length);
|
|
323
|
+
const truncatedContent = {
|
|
324
|
+
omittedCount: omittedResults.length,
|
|
325
|
+
omittedItems: omittedResults.map(r => ({
|
|
326
|
+
title: r.title,
|
|
327
|
+
estimatedTokens: estimateTokens(resultToString(r)),
|
|
328
|
+
})),
|
|
329
|
+
};
|
|
330
|
+
return {
|
|
331
|
+
finalResults: items,
|
|
332
|
+
finalTokenCount: tokenInfo.tokenCount,
|
|
333
|
+
truncated: true,
|
|
334
|
+
truncatedContent,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
// ─── Convert flat SearchResult to SearchResultWithMeta ─────────
|
|
338
|
+
function toSearchResultWithMeta(result, needsTopicMatching) {
|
|
339
|
+
// Derive bundleSlug from the product display name (extracted from
|
|
340
|
+
// zoominmetadata). Do NOT use result.mapId — FT API mapIds are
|
|
341
|
+
// opaque hashes (e.g. 'uRhiWJWbjHyL1vegaHmj8g'), not readable
|
|
342
|
+
// bundle stems.
|
|
343
|
+
const bundleSlug = productNameToId(result.product);
|
|
344
|
+
const labelKeys = result.docType !== undefined
|
|
345
|
+
? [DOC_TYPE_LABEL_MAP[result.docType]]
|
|
346
|
+
: [];
|
|
347
|
+
return {
|
|
348
|
+
result,
|
|
349
|
+
bundleSlug,
|
|
350
|
+
matchedTopics: needsTopicMatching
|
|
351
|
+
? matchTopics(result.title, result.snippet)
|
|
352
|
+
: [],
|
|
353
|
+
labelKeys,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
// ─── Main Search Function ──────────────────────────────────────
|
|
357
|
+
/**
|
|
358
|
+
* Search Jamf documentation using the Fluid Topics clustered-search API.
|
|
359
|
+
*
|
|
360
|
+
* 1. Checks SearchProvider first (custom backend injection).
|
|
361
|
+
* 2. Calls ft-client.search() with constructed filters.
|
|
362
|
+
* 3. Transforms results and applies post-processing pipeline:
|
|
363
|
+
* - Client-side topic/docType filtering with progressive relaxation
|
|
364
|
+
* - Pagination
|
|
365
|
+
* - Token truncation
|
|
366
|
+
*/
|
|
367
|
+
export async function searchDocumentation(ctx, params) {
|
|
368
|
+
const log = ctx.logger.createLogger('search-service');
|
|
369
|
+
const page = params.page ?? PAGINATION_CONFIG.DEFAULT_PAGE;
|
|
370
|
+
const pageSize = params.limit ?? CONTENT_LIMITS.DEFAULT_SEARCH_RESULTS;
|
|
371
|
+
const maxTokens = params.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS;
|
|
372
|
+
let allResults;
|
|
373
|
+
let searchError;
|
|
374
|
+
try {
|
|
375
|
+
allResults = await resolveSearchResults(ctx, params, log);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
const message = String(error);
|
|
379
|
+
log.error(`Search error: ${message}`);
|
|
380
|
+
allResults = [];
|
|
381
|
+
searchError = message;
|
|
382
|
+
}
|
|
383
|
+
// Build and apply filters with progressive relaxation
|
|
384
|
+
const activeFilters = buildActiveFilters(params);
|
|
385
|
+
const { filtered: filteredResults, relaxation: filterRelaxation } = applyFiltersWithFallback(allResults, activeFilters);
|
|
386
|
+
// Calculate pagination
|
|
387
|
+
const paginationInfo = calculatePagination(filteredResults.length, page, pageSize);
|
|
388
|
+
// Get paginated results
|
|
389
|
+
const paginatedResults = filteredResults
|
|
390
|
+
.slice(paginationInfo.startIndex, paginationInfo.endIndex)
|
|
391
|
+
.map(r => r.result);
|
|
392
|
+
// Truncate results to fit within token budget
|
|
393
|
+
const { finalResults, finalTokenCount, truncated, truncatedContent } = truncateSearchResults(paginatedResults, maxTokens);
|
|
394
|
+
return {
|
|
395
|
+
results: finalResults,
|
|
396
|
+
pagination: {
|
|
397
|
+
page: paginationInfo.page,
|
|
398
|
+
pageSize: paginationInfo.pageSize,
|
|
399
|
+
totalPages: paginationInfo.totalPages,
|
|
400
|
+
totalItems: filteredResults.length,
|
|
401
|
+
hasNext: paginationInfo.hasNext,
|
|
402
|
+
hasPrev: paginationInfo.hasPrev,
|
|
403
|
+
},
|
|
404
|
+
tokenInfo: {
|
|
405
|
+
tokenCount: finalTokenCount,
|
|
406
|
+
truncated,
|
|
407
|
+
maxTokens,
|
|
408
|
+
},
|
|
409
|
+
...(filterRelaxation !== undefined ? { filterRelaxation } : {}),
|
|
410
|
+
...(truncatedContent !== undefined ? { truncatedContent } : {}),
|
|
411
|
+
...(searchError !== undefined ? { searchError } : {}),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// ─── Internal: Resolve results from provider or FT API ─────────
|
|
415
|
+
/**
|
|
416
|
+
* Build a deterministic cache key for FT search results.
|
|
417
|
+
*
|
|
418
|
+
* Derived from query + filters + locale (NOT page/limit, since
|
|
419
|
+
* pagination is applied client-side after caching).
|
|
420
|
+
*/
|
|
421
|
+
function buildSearchCacheKey(query, filters, locale) {
|
|
422
|
+
const sortedFilters = [...filters]
|
|
423
|
+
.sort((a, b) => a.key.localeCompare(b.key))
|
|
424
|
+
.map(f => `${f.key}=${f.values.sort().join(',')}`)
|
|
425
|
+
.join('|');
|
|
426
|
+
return `ft-search:${locale}:${query}:${sortedFilters}`;
|
|
427
|
+
}
|
|
428
|
+
async function resolveSearchResults(ctx, params, log) {
|
|
429
|
+
const needsTopicMatching = params.topic !== undefined;
|
|
430
|
+
// 1. Try SearchProvider first (custom backend injection — no caching)
|
|
431
|
+
if (ctx.searchProvider !== undefined) {
|
|
432
|
+
const provided = await ctx.searchProvider.search(params);
|
|
433
|
+
if (provided !== null) {
|
|
434
|
+
return provided.map(r => toSearchResultWithMeta(r, needsTopicMatching));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// 2. Build FT search request
|
|
438
|
+
const locale = params.language ?? DEFAULT_LOCALE;
|
|
439
|
+
const filters = buildSearchFilters(params);
|
|
440
|
+
const cacheKey = buildSearchCacheKey(params.query, filters, locale);
|
|
441
|
+
// 3. Check cache before hitting the FT API
|
|
442
|
+
const cached = await ctx.cache.get(cacheKey);
|
|
443
|
+
if (cached !== null) {
|
|
444
|
+
log.debug(`Search cache hit: key="${cacheKey}", ${cached.length} results`);
|
|
445
|
+
return cached;
|
|
446
|
+
}
|
|
447
|
+
const perPage = Math.min(CONTENT_LIMITS.MAX_SEARCH_RESULTS, CONTENT_LIMITS.FILTER_OVERFETCH_CAP);
|
|
448
|
+
log.debug(`FT search: query="${params.query}", product=${params.product ?? 'all'}, ` +
|
|
449
|
+
`locale=${locale}, filters=${JSON.stringify(filters)}`);
|
|
450
|
+
const ftResponse = await ftSearch({
|
|
451
|
+
query: params.query,
|
|
452
|
+
contentLocale: locale,
|
|
453
|
+
paging: { perPage, page: 1 },
|
|
454
|
+
filters,
|
|
455
|
+
});
|
|
456
|
+
// 4. Flatten clusters and transform entries
|
|
457
|
+
const results = [];
|
|
458
|
+
for (const cluster of ftResponse.results) {
|
|
459
|
+
for (const entry of cluster.entries) {
|
|
460
|
+
const searchResult = transformFtSearchResult(entry);
|
|
461
|
+
if (searchResult.url !== '') {
|
|
462
|
+
results.push(toSearchResultWithMeta(searchResult, needsTopicMatching));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// 5. Cache the raw results (before client-side filtering)
|
|
467
|
+
await ctx.cache.set(cacheKey, results, ctx.config.cacheTtl.search);
|
|
468
|
+
log.debug(`FT search returned ${results.length} results (cached)`);
|
|
469
|
+
return results;
|
|
470
|
+
}
|
|
471
|
+
//# sourceMappingURL=search-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-service.js","sourceRoot":"","sources":["../../../src/core/services/search-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOC Service — fetch and transform Fluid Topics TOC into TocEntry[]
|
|
3
|
+
*
|
|
4
|
+
* Replaces the scraper-based TOC fetching with the FT API
|
|
5
|
+
* via ft-client + MapsRegistry.
|
|
6
|
+
*/
|
|
7
|
+
import type { ProductId } from '../constants.js';
|
|
8
|
+
import type { ServerContext } from '../types/context.js';
|
|
9
|
+
import type { FtTocNode, TocEntry, FetchTocOptions, FetchTocResult } from '../types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Recursively convert FtTocNode[] → TocEntry[]
|
|
12
|
+
*
|
|
13
|
+
* Enriches each entry with:
|
|
14
|
+
* - url: buildDisplayUrl(prettyUrl)
|
|
15
|
+
* - contentId / tocId from the FT node
|
|
16
|
+
* - recursively transformed children
|
|
17
|
+
*/
|
|
18
|
+
export declare function transformFtTocToTocEntries(nodes: FtTocNode[]): TocEntry[];
|
|
19
|
+
/**
|
|
20
|
+
* Fetch table of contents for a product via the Fluid Topics API.
|
|
21
|
+
*
|
|
22
|
+
* Resolution order:
|
|
23
|
+
* 1. ctx.tocProvider (if configured)
|
|
24
|
+
* 2. MapsRegistry → mapId → ft-client.fetchMapToc
|
|
25
|
+
* 3. Transform FtTocNode[] → TocEntry[]
|
|
26
|
+
*
|
|
27
|
+
* Results are cached under `ft-toc:{locale}:{product}:{version}`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function fetchTableOfContents(ctx: ServerContext, product: ProductId, version?: string, options?: FetchTocOptions): Promise<FetchTocResult>;
|
|
30
|
+
//# sourceMappingURL=toc-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toc-service.d.ts","sourceRoot":"","sources":["../../../src/core/services/toc-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAkB,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKxG;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,QAAQ,EAAE,CAezE;AA0BD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,SAAS,EAClB,OAAO,SAAY,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CA+DzB"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOC Service — fetch and transform Fluid Topics TOC into TocEntry[]
|
|
3
|
+
*
|
|
4
|
+
* Replaces the scraper-based TOC fetching with the FT API
|
|
5
|
+
* via ft-client + MapsRegistry.
|
|
6
|
+
*/
|
|
7
|
+
import { fetchMapToc } from './ft-client.js';
|
|
8
|
+
import { buildDisplayUrl } from './topic-resolver.js';
|
|
9
|
+
import { calculatePagination, truncateItemsToTokenLimit } from './tokenizer.js';
|
|
10
|
+
import { JAMF_PRODUCTS, DEFAULT_LOCALE, PAGINATION_CONFIG, TOKEN_CONFIG, } from '../constants.js';
|
|
11
|
+
import { JamfDocsError, JamfDocsErrorCode } from '../types.js';
|
|
12
|
+
// ─── Transform helpers ─────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Recursively convert FtTocNode[] → TocEntry[]
|
|
15
|
+
*
|
|
16
|
+
* Enriches each entry with:
|
|
17
|
+
* - url: buildDisplayUrl(prettyUrl)
|
|
18
|
+
* - contentId / tocId from the FT node
|
|
19
|
+
* - recursively transformed children
|
|
20
|
+
*/
|
|
21
|
+
export function transformFtTocToTocEntries(nodes) {
|
|
22
|
+
return nodes.map((node) => {
|
|
23
|
+
const entry = {
|
|
24
|
+
title: node.title,
|
|
25
|
+
url: buildDisplayUrl(node.prettyUrl),
|
|
26
|
+
contentId: node.contentId,
|
|
27
|
+
tocId: node.tocId,
|
|
28
|
+
};
|
|
29
|
+
if (node.children.length > 0) {
|
|
30
|
+
entry.children = transformFtTocToTocEntries(node.children);
|
|
31
|
+
}
|
|
32
|
+
return entry;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// ─── Counting / serialisation helpers ──────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Count total TOC entries including nested children
|
|
38
|
+
*/
|
|
39
|
+
function countTocEntries(entries) {
|
|
40
|
+
return entries.reduce((count, entry) => count + 1 + (entry.children !== undefined ? countTocEntries(entry.children) : 0), 0);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Serialise a single TOC entry for token estimation
|
|
44
|
+
*/
|
|
45
|
+
function tocEntryToString(entry, depth = 0) {
|
|
46
|
+
const indent = ' '.repeat(depth);
|
|
47
|
+
const childrenStr = entry.children?.map(c => tocEntryToString(c, depth + 1)).join('') ?? '';
|
|
48
|
+
return `${indent}- ${entry.title}\n${childrenStr}`;
|
|
49
|
+
}
|
|
50
|
+
// ─── Main fetch function ───────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Fetch table of contents for a product via the Fluid Topics API.
|
|
53
|
+
*
|
|
54
|
+
* Resolution order:
|
|
55
|
+
* 1. ctx.tocProvider (if configured)
|
|
56
|
+
* 2. MapsRegistry → mapId → ft-client.fetchMapToc
|
|
57
|
+
* 3. Transform FtTocNode[] → TocEntry[]
|
|
58
|
+
*
|
|
59
|
+
* Results are cached under `ft-toc:{locale}:{product}:{version}`.
|
|
60
|
+
*/
|
|
61
|
+
export async function fetchTableOfContents(ctx, product, version = 'current', options = {}) {
|
|
62
|
+
if (ctx.tocProvider !== undefined) {
|
|
63
|
+
const provided = await ctx.tocProvider.getTableOfContents(product, version, options);
|
|
64
|
+
if (provided !== null) {
|
|
65
|
+
return provided;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const page = options.page ?? PAGINATION_CONFIG.DEFAULT_PAGE;
|
|
69
|
+
const maxTokens = options.maxTokens ?? TOKEN_CONFIG.DEFAULT_MAX_TOKENS;
|
|
70
|
+
const locale = options.locale ?? DEFAULT_LOCALE;
|
|
71
|
+
const cacheKey = `ft-toc:${locale}:${product}:${version}`;
|
|
72
|
+
let allToc = await ctx.cache.get(cacheKey);
|
|
73
|
+
if (allToc === null) {
|
|
74
|
+
const productInfo = JAMF_PRODUCTS[product];
|
|
75
|
+
const mapId = await ctx.mapsRegistry.resolveMapId(productInfo.bundleId, version !== 'current' ? version : undefined, locale);
|
|
76
|
+
if (mapId === null) {
|
|
77
|
+
throw new JamfDocsError(`Could not resolve map for ${product} version ${version} locale ${locale}`, JamfDocsErrorCode.NOT_FOUND);
|
|
78
|
+
}
|
|
79
|
+
const ftNodes = await fetchMapToc(mapId);
|
|
80
|
+
allToc = transformFtTocToTocEntries(ftNodes);
|
|
81
|
+
await ctx.cache.set(cacheKey, allToc, ctx.config.cacheTtl.article);
|
|
82
|
+
}
|
|
83
|
+
// ─── Pagination & token truncation ───────────────────────────
|
|
84
|
+
const totalItems = countTocEntries(allToc);
|
|
85
|
+
const topLevelCount = allToc.length;
|
|
86
|
+
const pageSize = PAGINATION_CONFIG.DEFAULT_PAGE_SIZE;
|
|
87
|
+
const paginationCalc = calculatePagination(topLevelCount, page, pageSize);
|
|
88
|
+
const paginatedToc = allToc.slice(paginationCalc.startIndex, paginationCalc.endIndex);
|
|
89
|
+
// Token-truncate the paginated slice
|
|
90
|
+
const { items: finalToc, tokenInfo } = truncateItemsToTokenLimit(paginatedToc, maxTokens, tocEntryToString, 1, paginatedToc.length);
|
|
91
|
+
const pagination = {
|
|
92
|
+
page: paginationCalc.page,
|
|
93
|
+
pageSize: paginationCalc.pageSize,
|
|
94
|
+
totalPages: paginationCalc.totalPages,
|
|
95
|
+
totalItems,
|
|
96
|
+
hasNext: paginationCalc.hasNext,
|
|
97
|
+
hasPrev: paginationCalc.hasPrev,
|
|
98
|
+
};
|
|
99
|
+
return { toc: finalToc, pagination, tokenInfo };
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=toc-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toc-service.js","sourceRoot":"","sources":["../../../src/core/services/toc-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -65,7 +65,7 @@ export declare function extractSection(content: string, sectionIdentifier: strin
|
|
|
65
65
|
* - Correctly closes code blocks
|
|
66
66
|
* - Lists remaining sections when truncated
|
|
67
67
|
*/
|
|
68
|
-
export declare function truncateToTokenLimit(content: string, maxTokens?: number): TruncateResult;
|
|
68
|
+
export declare function truncateToTokenLimit(content: string, maxTokens?: number, preExtractedSections?: ArticleSection[]): TruncateResult;
|
|
69
69
|
/**
|
|
70
70
|
* Extract summary from Markdown content
|
|
71
71
|
* Returns the first paragraph and an outline of sections with token estimates
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenizer.d.ts","sourceRoot":"","sources":["../../../src/core/services/tokenizer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,iBAAiB,CAAC,EAAE,cAAc,EAAE,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,SAAS,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAgBtE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,UAAQ,GAChB,SAAS,CAMX;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,MAAM,CAQR;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CA0CjE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ7C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,MAAM,EACzB,SAAS,GAAE,MAAwC,GAClD,oBAAoB,CAoDtB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,MAAwC,
|
|
1
|
+
{"version":3,"file":"tokenizer.d.ts","sourceRoot":"","sources":["../../../src/core/services/tokenizer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,iBAAiB,CAAC,EAAE,cAAc,EAAE,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,SAAS,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAgBtE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,UAAQ,GAChB,SAAS,CAMX;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,MAAM,CAQR;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CA0CjE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ7C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,MAAM,EACzB,SAAS,GAAE,MAAwC,GAClD,oBAAoB,CAoDtB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,MAAwC,EACnD,oBAAoB,CAAC,EAAE,cAAc,EAAE,GACtC,cAAc,CAqFhB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAAwC,GAClD,aAAa,CAwFf;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf;IACD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAcA;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EACzC,KAAK,EAAE,CAAC,EAAE,EACV,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EACjC,IAAI,SAAI,EACR,QAAQ,SAAK,GACZ;IACD,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;CACH,CAsCA"}
|