@kognai/tool-router 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +47 -2
- package/dist/adapters/awesome-list.d.ts +10 -0
- package/dist/adapters/awesome-list.js +68 -0
- package/dist/adapters/mcp-registry.d.ts +12 -0
- package/dist/adapters/mcp-registry.js +51 -0
- package/dist/adapters/mcp-tools.d.ts +49 -0
- package/dist/adapters/mcp-tools.js +60 -0
- package/dist/adapters/openapi.d.ts +25 -0
- package/dist/adapters/openapi.js +107 -0
- package/dist/adapters/pay-skills.d.ts +13 -0
- package/dist/adapters/pay-skills.js +119 -0
- package/dist/adapters/raw.d.ts +82 -0
- package/dist/adapters/raw.js +5 -0
- package/dist/adapters/x402-discovery.d.ts +6 -0
- package/dist/adapters/x402-discovery.js +100 -0
- package/dist/catalog/file-store.d.ts +27 -0
- package/dist/catalog/file-store.js +102 -0
- package/dist/catalog/ingest.d.ts +53 -0
- package/dist/catalog/ingest.js +276 -0
- package/dist/catalog/lifecycle.d.ts +24 -0
- package/dist/catalog/lifecycle.js +51 -0
- package/dist/catalog/memory-store.d.ts +49 -0
- package/dist/catalog/memory-store.js +92 -0
- package/dist/catalog/risk.d.ts +22 -0
- package/dist/catalog/risk.js +73 -0
- package/dist/catalog/sanitise.d.ts +17 -0
- package/dist/catalog/sanitise.js +109 -0
- package/dist/classify/classifier.d.ts +12 -0
- package/dist/classify/classifier.js +52 -0
- package/dist/classify/decompose.d.ts +10 -0
- package/dist/classify/decompose.js +107 -0
- package/dist/classify/rules.d.ts +46 -0
- package/dist/classify/rules.js +249 -0
- package/dist/classify/tagger.d.ts +5 -0
- package/dist/classify/tagger.js +29 -0
- package/dist/classify/taxonomy.d.ts +31 -0
- package/dist/classify/taxonomy.js +221 -0
- package/dist/classify/text.d.ts +9 -0
- package/dist/classify/text.js +38 -0
- package/dist/eligibility/engine.d.ts +11 -0
- package/dist/eligibility/engine.js +19 -0
- package/dist/eligibility/rules.d.ts +33 -0
- package/dist/eligibility/rules.js +121 -0
- package/dist/flags.d.ts +33 -0
- package/dist/flags.js +34 -0
- package/dist/hash.d.ts +8 -0
- package/dist/hash.js +30 -0
- package/dist/ids.d.ts +17 -0
- package/dist/ids.js +54 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +95 -0
- package/dist/money.d.ts +10 -0
- package/dist/money.js +41 -0
- package/dist/rank/confidence.d.ts +15 -0
- package/dist/rank/confidence.js +48 -0
- package/dist/rank/ranker.d.ts +34 -0
- package/dist/rank/ranker.js +81 -0
- package/dist/rank/weights.d.ts +40 -0
- package/dist/rank/weights.js +46 -0
- package/dist/retrieve/bm25.d.ts +21 -0
- package/dist/retrieve/bm25.js +59 -0
- package/dist/retrieve/capability.d.ts +1 -0
- package/dist/retrieve/capability.js +13 -0
- package/dist/retrieve/hash-embed.d.ts +4 -0
- package/dist/retrieve/hash-embed.js +41 -0
- package/dist/retrieve/hybrid.d.ts +74 -0
- package/dist/retrieve/hybrid.js +125 -0
- package/dist/retrieve/schema-fit.d.ts +9 -0
- package/dist/retrieve/schema-fit.js +38 -0
- package/dist/retrieve/tokenise.d.ts +2 -0
- package/dist/retrieve/tokenise.js +51 -0
- package/dist/retrieve/vector.d.ts +18 -0
- package/dist/retrieve/vector.js +47 -0
- package/dist/router.d.ts +37 -0
- package/dist/router.js +114 -0
- package/dist/schema/tool.schema.json +117 -0
- package/dist/schema/validate.d.ts +9 -0
- package/dist/schema/validate.js +92 -0
- package/dist/shortlist/generate.d.ts +21 -0
- package/dist/shortlist/generate.js +75 -0
- package/dist/types/ports.d.ts +71 -0
- package/dist/types/ports.js +2 -0
- package/dist/types/route.d.ts +121 -0
- package/dist/types/route.js +5 -0
- package/dist/types/tool.d.ts +123 -0
- package/dist/types/tool.js +17 -0
- package/package.json +39 -4
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REASONING_CUES = exports.familyOf = exports.CAPABILITY_IDS = exports.capabilityById = exports.CAPABILITIES = void 0;
|
|
4
|
+
const NET = ['read_only', 'network'];
|
|
5
|
+
exports.CAPABILITIES = [
|
|
6
|
+
// ── places, time, units ───────────────────────────────────────────────────
|
|
7
|
+
{ id: 'weather.forecast', family: 'weather', default_risk: NET, needs_tool: true, args: ['location'],
|
|
8
|
+
cues: { en: ['weather', 'forecast', 'temperature', 'will it rain', 'rain(ing|y)?', 'sunny', 'humidity', 'wind speed', 'snow(ing)?'], fr: ['meteo', 'previsions?', 'temps qu.il (fait|fera)', 'quel temps', 'il (fera|fait) (beau|chaud|froid)', 'pluie', 'pleuvoir', 'il pleut', 'temperatures?'], derja: ['ta9s', 'taqs', 'el jaw', 'mtar', 'طقس', 'الجو', 'مطر', 'سخانه', 'برد اليوم'] },
|
|
9
|
+
tool_cues: ['weather', 'forecasts?', 'meteorolog\\w*', 'climate data', 'open.?meteo'] },
|
|
10
|
+
{ id: 'places.search', family: 'places', default_risk: NET, needs_tool: true, args: ['location'],
|
|
11
|
+
cues: { en: ['near me', 'nearby', 'directions to', 'route to', 'how far', 'address of', 'geocod\\w*', 'places? (to|in|near)', 'restaurants? (in|near)', 'map of'], fr: ['pres de (moi|chez)', 'itineraire', 'adresse de', 'comment aller', 'carte de'], derja: ['9rib mni', 'qrib', 'kifech nemchi', 'وين نلقى', 'قريب مني', 'كيفاش نمشي'] },
|
|
12
|
+
tool_cues: ['maps?', 'geocoding', 'points? of interest', 'location data', 'places api'] },
|
|
13
|
+
{ id: 'time.lookup', family: 'time', default_risk: [], needs_tool: false,
|
|
14
|
+
cues: { en: ['what time is it', 'time zone', 'timezone', 'current time in', 'what day is'], fr: ['quelle heure', 'fuseau horaire'], derja: ['9adeh el wa9t', 'qaddech el wa9t', 'قداش الوقت', 'قداه الوقت'] } },
|
|
15
|
+
{ id: 'units.convert', family: 'units', default_risk: [], needs_tool: false,
|
|
16
|
+
cues: { en: ['convert \\d+(\\.\\d+)? ?(km|kilometers?|miles?|kg|pounds?|lbs?|celsius|fahrenheit|meters?|feet|inches|cm|liters?|gallons?)', '(celsius|fahrenheit|kilometers?|miles) (to|into|in) (celsius|fahrenheit|kilometers?|miles)', 'unit conversion'], fr: ['convertis \\d+ ?(km|kilometres?|miles?|kg|livres?)', 'conversion d.unites'], derja: ['حول \\d+'] } },
|
|
17
|
+
{ id: 'math.calculate', family: 'math', default_risk: [], needs_tool: false,
|
|
18
|
+
cues: { en: ['calculate', 'compute', 'how much is \\d', '\\d+(\\.\\d+)? ?[*×/+] ?\\d+', '\\d+ x \\d+', 'percent(age)? of', 'square root'], fr: ['calcule', 'combien font', 'pourcentage de'], derja: ['e7seb', 'a7seb', 'احسب', 'قداش يعمل'] } },
|
|
19
|
+
{ id: 'finance.fx', family: 'finance', default_risk: NET, needs_tool: true,
|
|
20
|
+
cues: { en: ['exchange rate', 'convert \\d+ ?(usd|eur|tnd|gbp|dollars?|euros?|dinars?)', '(usd|eur|tnd|dollars?|euros?|dinars?) (to|in|into) (usd|eur|tnd|dollars?|euros?|dinars?)'], fr: ['taux de change', 'combien (de )?(dinars?|euros?|dollars?) (en|pour)'], derja: ['sarf', 'صرف', 'الدينار باليورو'] },
|
|
21
|
+
tool_cues: ['currency (rates?|conversion|exchange)', 'forex', 'fx rates?'] },
|
|
22
|
+
// ── search and research ───────────────────────────────────────────────────
|
|
23
|
+
{ id: 'web.search', family: 'search', default_risk: NET, needs_tool: true, args: ['query'],
|
|
24
|
+
cues: { en: ['search (the web|online|the internet|google)', 'google (it|this|for)', 'look (it )?up online', 'find (online|on the web|on the internet)', 'web search', 'search for'], fr: ['cherche sur (internet|le web|google)', 'recherche (sur|en ligne)', 'trouve sur internet'], derja: ['lawej', 'lawejli', 'nlawej', '9alleb', 'لوج', 'نلوج', 'لوجلي', 'ابحث', 'دور على'] },
|
|
25
|
+
tool_cues: ['web search', 'search engine', 'serp', 'search api', 'internet search', 'neural search', 'search results'] },
|
|
26
|
+
{ id: 'web.fetch', family: 'search', default_risk: ['network'], needs_tool: true, args: ['url'],
|
|
27
|
+
cues: { en: ['https?://[^ ]+', 'open (this|the) (link|url|page)', 'read (this|the) (page|link|website|url)', 'scrape', 'crawl', 'fetch (the|this) (page|url)'], fr: ['ouvre (ce|le) lien', 'lis (cette|la) page', 'ce site'], derja: ['7ell el lien', 'هاللينك', 'افتح الرابط'] },
|
|
28
|
+
tool_cues: ['web scraping', 'scraper', 'crawler', 'extract (content|data) from (any )?(url|website|web page)', 'browser automation', 'headless browser', 'fetch (any )?url'] },
|
|
29
|
+
{ id: 'news.search', family: 'search', default_risk: NET, needs_tool: true, args: ['query'],
|
|
30
|
+
cues: { en: ['news', 'headlines', 'latest on', 'what happened (today|yesterday)'], fr: ['actualites?', 'nouvelles', 'infos du jour'], derja: ['akhbar', 'أخبار', 'اخبار', 'شنوه صار'] },
|
|
31
|
+
tool_cues: ['news (api|feeds?|search|articles|headlines)', 'headlines'] },
|
|
32
|
+
{ id: 'literature.search', family: 'research', default_risk: NET, needs_tool: true, args: ['query'],
|
|
33
|
+
cues: { en: ['pubmed', 'papers?', 'studies', 'study on', 'scientific literature', 'research articles?', 'peer.reviewed', 'publications? on', 'literature (review|search)', 'meta.analys[ie]s', 'systematic review', 'journal articles?', 'arxiv', 'semantic scholar'], fr: ['articles scientifiques', 'etudes (sur|cliniques sur|scientifiques)', 'litterature (scientifique|medicale)', 'publications scientifiques'], derja: ['دراسات', 'بحوث علميه', 'مقالات علميه'] },
|
|
34
|
+
tool_cues: ['pubmed', 'research papers?', 'scientific (papers|literature|articles)', 'peer.reviewed', 'biomedical literature', 'scholarly', 'academic (papers|search)', 'abstracts', 'citations?', 'europe ?pmc', 'crossref', 'openalex', 'research papers'] },
|
|
35
|
+
{ id: 'trial.search', family: 'research', default_risk: NET, needs_tool: true, args: ['query'],
|
|
36
|
+
cues: { en: ['clinical trials?', 'clinicaltrials(\\.gov)?', 'phase (1|2|3|i|ii|iii) trials?', 'ongoing trials?', 'recruiting trials?'], fr: ['essais? cliniques?'], derja: ['تجارب سريريه'] },
|
|
37
|
+
tool_cues: ['clinical trials?', 'trial registry', 'study registry'] },
|
|
38
|
+
{ id: 'patent.search', family: 'research', default_risk: NET, needs_tool: true, args: ['query'],
|
|
39
|
+
cues: { en: ['patents?', 'prior art', 'uspto', 'espacenet'], fr: ['brevets?'], derja: ['براءه اختراع'] }, tool_cues: ['patents?', 'prior art'] },
|
|
40
|
+
{ id: 'evidence.synthesise', family: 'research', default_risk: [], needs_tool: false,
|
|
41
|
+
cues: { en: ['real.world evidence', 'rwe', 'synthes[iz]e', 'evidence (on|for|about)', 'what does the evidence say', 'state of the evidence'], fr: ['donnees en vie reelle', 'synthese (des|de la) (preuves|litterature)'], derja: ['خلاصه الدراسات'] } },
|
|
42
|
+
// ── language work the model does itself ───────────────────────────────────
|
|
43
|
+
{ id: 'text.summarise', family: 'text', default_risk: [], needs_tool: false,
|
|
44
|
+
cues: { en: ['summari[sz]e', 'summary of', 'tl;?dr', 'key points of'], fr: ['resumer', 'fais (moi )?un resume', 'resume (moi )?(ce|cet|cette|le|la)'], derja: ['lakhes', 'lakhasli', 'لخص', 'لخصلي'] } },
|
|
45
|
+
{ id: 'text.translate', family: 'text', default_risk: [], needs_tool: false,
|
|
46
|
+
cues: { en: ['translate', 'translation', 'in (french|english|arabic|spanish|german|italian) please'], fr: ['tradui[st]', 'traduire', 'traduction'], derja: ['tarjem', 'tarjemli', 'ترجم', 'ترجملي'] },
|
|
47
|
+
tool_cues: ['translat\\w+', 'localization'] },
|
|
48
|
+
{ id: 'text.write', family: 'text', default_risk: [], needs_tool: false,
|
|
49
|
+
cues: { en: ['write (me )?(an? |the )?(email|poem|essay|letter|story|speech|bio|cover letter|paragraph|toast|haiku)', 'draft (an? )?(email|letter|reply)', 'poem', 'essay', 'cover letter', 'rephrase', 'rewrite', 'proofread'], fr: ['redige', 'ecris(-| )moi (un|une)', 'poeme', 'lettre de motivation', 'reformule', 'corrige (ce|mon) texte'], derja: ['ekteb', 'ekteblli', 'اكتبلي', 'اكتب لي', 'قصيده'] },
|
|
50
|
+
tool_cues: ['copywriting', 'text generation', 'llm', 'language model', 'chat completion'] },
|
|
51
|
+
{ id: 'script.write', family: 'creative', default_risk: [], needs_tool: false,
|
|
52
|
+
cues: { en: ['script', 'screenplay', 'storyboard', 'dialogue (for|between)', 'scene by scene'], fr: ['scenario', 'script', 'storyboard'], derja: ['سيناريو', 'سكريبت'] } },
|
|
53
|
+
{ id: 'code.review', family: 'code', default_risk: [], needs_tool: false,
|
|
54
|
+
cues: { en: ['review (my|this) code', 'code review', 'explain (this|my) code', 'find (the )?bugs? in (this|my)', 'refactor (this|my)'], fr: ['explique (ce|mon) code', 'revue de code'], derja: ['fasser el code'] } },
|
|
55
|
+
// ── creative production ───────────────────────────────────────────────────
|
|
56
|
+
{ id: 'video.generate', family: 'video', default_risk: ['network'], needs_tool: true, product_id: 'create-video',
|
|
57
|
+
cues: { en: ['videos?', 'clip', 'short film', 'film', 'reel', 'animation', 'cinematic', 'movie', 'explainer'], fr: ['videos?', 'court metrage', 'film', 'clip', 'animation'], derja: ['fidyo', 'vidyo', 'vedio', 'فيديو', 'فيلم', 'كليب'] },
|
|
58
|
+
tool_cues: ['text.to.video', 'video generation', 'generate videos?', 'video model', 'seedance', 'kling', 'sora', 'veo', 'runway'] },
|
|
59
|
+
{ id: 'video.edit', family: 'video', default_risk: [], needs_tool: true, args: ['file_ref'],
|
|
60
|
+
cues: { en: ['edit (the|my|this) video', 'cut (the|my|this) video', 'trim', 'add (subtitles|captions) to', 'merge (the )?clips', 'montage', 'add music to (the|my|this) video', 'vertical version', '9:16'], fr: ['monte(r)? (la|ma) video', 'sous.titres', 'couper la video'], derja: ['nmonti', 'مونتاج', 'قص الفيديو'] },
|
|
61
|
+
tool_cues: ['video editing', 'ffmpeg', 'concatenat\\w+', 'subtitl\\w+', 'post.production', 'grade', 'lufs'] },
|
|
62
|
+
{ id: 'image.generate', family: 'image', default_risk: ['network'], needs_tool: true, product_id: 'create-image',
|
|
63
|
+
cues: { en: ['images?', 'pictures?', 'photo(graph)?', 'illustration', 'logo', 'poster', 'drawing', 'artwork', 'thumbnail', 'cover art', 'banner'], fr: ['image', 'photo', 'illustration', 'logo', 'affiche', 'dessin', 'visuel'], derja: ['taswira', 'tsawer', 'tasswira', 'rasma', 'صوره', 'تصويره', 'رسمه', 'لوغو'] },
|
|
64
|
+
tool_cues: ['text.to.image', 'image generation', 'generate images?', 'diffusion', 'flux', 'dall.?e', 'midjourney', 'nano.banana'] },
|
|
65
|
+
{ id: 'image.edit', family: 'image', default_risk: [], needs_tool: true, args: ['file_ref'],
|
|
66
|
+
cues: { en: ['remove (the )?background', 'edit (this|my|the) (photo|image|picture)', 'retouch', 'upscale', 'inpaint'], fr: ['detourer', 'enleve(r)? le fond', 'retouche'], derja: ['na77i el fond', 'نحي الخلفيه'] },
|
|
67
|
+
tool_cues: ['background removal', 'image editing', 'upscal\\w+'] },
|
|
68
|
+
{ id: 'character.design', family: 'image', default_risk: ['network'], needs_tool: true,
|
|
69
|
+
cues: { en: ['consistent characters?', 'character (sheet|design)', 'characters?', 'mascot', 'same (character|face)'], fr: ['personnages?', 'mascotte'], derja: ['شخصيات', 'شخصيه'] },
|
|
70
|
+
tool_cues: ['character consistency', 'reference images?'] },
|
|
71
|
+
{ id: 'voice.speak', family: 'audio', default_risk: ['network'], needs_tool: true,
|
|
72
|
+
cues: { en: ['voice.?over', 'voice', 'text.to.speech', 'tts', 'narrat(e|ion|or)', 'read (it|this) (aloud|out loud)', 'audiobook', 'dubbing', 'talking'], fr: ['voix off', 'voix', 'lis (le|ce) (texte|a voix haute)', 'doublage', 'narration'], derja: ['soute', 'صوت', 'تعليق صوتي'] },
|
|
73
|
+
tool_cues: ['speech synthesis', 'tts', 'voices', 'elevenlabs', 'kokoro'] },
|
|
74
|
+
{ id: 'audio.transcribe', family: 'audio', default_risk: [], needs_tool: true, args: ['file_ref'],
|
|
75
|
+
cues: { en: ['transcri(be|ption)', 'speech.to.text', 'voice note to text', 'what (does|did) (he|she|they) say in'], fr: ['transcri(s|re|ption)', 'note vocale en texte'], derja: ['ktebli el vocal', 'اكتبلي الفوكال', 'فرغ الصوت'] },
|
|
76
|
+
tool_cues: ['speech.to.text', 'stt', 'whisper', 'asr', 'speech recognition'] },
|
|
77
|
+
{ id: 'music.generate', family: 'audio', default_risk: ['network'], needs_tool: true,
|
|
78
|
+
cues: { en: ['music', 'song', 'soundtrack', 'jingle', 'beat', 'score for'], fr: ['musique', 'chanson', 'bande son'], derja: ['mouzika', 'ghneya', 'موسيقى', 'غنايه', 'اغنيه'] },
|
|
79
|
+
tool_cues: ['music', 'music generation', 'musicgen', 'audio generation', 'sound effects?', 'songs?'] },
|
|
80
|
+
{ id: 'image.understand', family: 'vision', default_risk: [], needs_tool: true, args: ['file_ref'],
|
|
81
|
+
cues: { en: ['what.?s in (this|the) (image|photo|picture)', 'describe (this|the) (image|photo|picture)', 'describe what.{0,12}(image|photo|picture|screenshot)', 'what (is|do you see) in (this|the|my) (image|photo|picture|screenshot)', 'look at (this|the) (image|photo|screenshot)'], fr: ['decris (cette|l.)(image|photo)', 'qu.y a.t.il (sur|dans) (cette|la) (photo|image)'], derja: ['chnowa fi taswira', 'شنوه في التصويره'] },
|
|
82
|
+
tool_cues: ['vision model', 'image (analysis|understanding|captioning)', 'visual question'] },
|
|
83
|
+
{ id: 'ocr.extract', family: 'vision', default_risk: [], needs_tool: true, args: ['file_ref'],
|
|
84
|
+
cues: { en: ['ocr', 'extract (the )?text from (this|the|a) (image|pdf|scan|photo|receipt)', 'read (this|the|my) receipt', 'scan (this|the|my) (receipt|invoice|document)'], fr: ['ocr', 'lis (ce|le) ticket', 'extrais le texte'], derja: ['a9ra el ticket', 'اقرا التيكي'] },
|
|
85
|
+
tool_cues: ['optical character recognition', 'text extraction', 'document parsing', 'pdf parsing'] },
|
|
86
|
+
// ── the owner's private accounts ──────────────────────────────────────────
|
|
87
|
+
{ id: 'mail.read', family: 'mail', default_risk: ['read_only', 'sensitive_data'], needs_tool: true,
|
|
88
|
+
cues: { en: ['my (e.?mails?|inbox|mails?|mailbox)', 'e.?mails? of', 'unread (e.?mails?|messages)', 'gmail', 'outlook', 'e.?mails? (this|last) week', 'in my e.?mails?'], fr: ['mes (mails|e.?mails|courriels|messages)', 'boite (mail|de reception)', 'courriels?'], derja: ['el mail mte3i', 'mail mte3i', 'mailet', 'الميلات متاعي', 'الايميل متاعي', 'الايميلات'] },
|
|
89
|
+
tool_cues: ['gmail', 'outlook', 'mailbox', 'imap', 'inbox', 'e.?mails?'] },
|
|
90
|
+
{ id: 'mail.search', family: 'mail', default_risk: ['read_only', 'sensitive_data'], needs_tool: true,
|
|
91
|
+
cues: { en: ['(find|search|look for) (an? |the |my )?e.?mails? (from|about)', 'e.?mails? from', 'mails? from', '(search|look through) my (inbox|mailbox|e.?mails?)', 'messages from (the|my) '], fr: ['(cherche|trouve) (le|un|les) (mail|courriel)s? de', 'mails? de'], derja: ['lawajli 3al mail', 'لوجلي على الميل'] } },
|
|
92
|
+
{ id: 'mail.extract_actions', family: 'mail', default_risk: [], needs_tool: false,
|
|
93
|
+
cues: { en: ['action items', 'to.?dos?', 'tasks from', 'what (do i|should i) (need to )?do', 'follow.?ups?', 'deadlines in'], fr: ['actions a faire', 'taches a faire', 'ce que je dois faire', 'les relances'], derja: ['chnowa lezemni na3mel', 'شنوه لازمني نعمل'] } },
|
|
94
|
+
{ id: 'mail.send', family: 'mail', default_risk: ['publish_or_message', 'sensitive_data'], needs_tool: true, args: ['recipient'],
|
|
95
|
+
cues: { en: ['send (an? |the )?e.?mail', 'e.?mail (him|her|them|it to)', 'reply to (this|the|that|his|her) e.?mail', 'forward (this|the) e.?mail'], fr: ['envoie (un|le) (mail|courriel)', 'reponds a (ce|son) mail', 'transfere (ce|le) mail'], derja: ['ab3ath mail', 'ab3athlou mail', 'ابعث ميل', 'ابعثلو ميل'] },
|
|
96
|
+
tool_cues: ['send e.?mails?', 'smtp', 'transactional e.?mail', 'outbound e.?mail'] },
|
|
97
|
+
{ id: 'calendar.read', family: 'calendar', default_risk: ['read_only', 'sensitive_data'], needs_tool: true,
|
|
98
|
+
cues: { en: ['my (calendar|agenda|schedule)', 'meetings? (today|tomorrow|this week)', 'am i free', 'what.?s on my (calendar|schedule)'], fr: ['mon agenda', 'mes rendez.vous', 'suis.je libre'], derja: ['el agenda mte3i', 'الأجندا متاعي', 'الاجندا متاعي', 'المواعيد متاعي'] },
|
|
99
|
+
tool_cues: ['google calendar', 'calendar events?', 'ical', 'caldav'] },
|
|
100
|
+
{ id: 'calendar.create', family: 'calendar', default_risk: ['data_modification', 'sensitive_data'], needs_tool: true,
|
|
101
|
+
cues: { en: ['schedule (a|an) (meeting|call)', 'book (a|an) (meeting|appointment|slot)', 'add (an? )?(event|meeting) to my calendar', 'set up a (call|meeting)', 'create (an? )?(event|invite)'], fr: ['planifie (une|un) (reunion|rendez.vous)', 'ajoute (un|une) (evenement|rendez.vous)'], derja: ['7ott rendez.vous', 'حط موعد'] },
|
|
102
|
+
tool_cues: ['create events?', 'scheduling', 'book appointments?'] },
|
|
103
|
+
{ id: 'drive.search', family: 'files', default_risk: ['read_only', 'sensitive_data'], needs_tool: true,
|
|
104
|
+
cues: { en: ['my (drive|files|documents|google drive|onedrive|dropbox)', 'find (the|my) (document|file|doc|pdf)'], fr: ['mes fichiers', 'mon drive', 'trouve (le|mon) (document|fichier)'], derja: ['el fichiet mte3i', 'الفيشيات متاعي'] },
|
|
105
|
+
tool_cues: ['google drive', 'onedrive', 'dropbox', 'file storage', 'cloud storage', 'file search'] },
|
|
106
|
+
{ id: 'doc.read', family: 'files', default_risk: ['read_only'], needs_tool: true, args: ['file_ref'],
|
|
107
|
+
cues: { en: ['read (this|the|my) (pdf|document|doc|file|contract)', '(this|the) attached (pdf|file|document)', 'in (this|the) (pdf|document)'], fr: ['lis (ce|le) (pdf|document|fichier)', 'dans (ce|le) (pdf|document)'], derja: ['a9ra el pdf', 'اقرا الملف'] },
|
|
108
|
+
tool_cues: ['pdf', 'document (reader|parsing|extraction)', 'docx'] },
|
|
109
|
+
// ── social and messaging ──────────────────────────────────────────────────
|
|
110
|
+
{ id: 'social.read', family: 'social', default_risk: ['read_only', 'sensitive_data'], needs_tool: true,
|
|
111
|
+
cues: { en: ['my (posts|profile|followers|tweets) on', 'my (last|latest|recent) (posts|tweets)', 'my (instagram|facebook|linkedin|x|twitter) (posts|profile|account)'], fr: ['mes (publications|posts) sur', 'mon profil (instagram|facebook|linkedin)'], derja: ['el posts mte3i', 'البوستات متاعي'] },
|
|
112
|
+
tool_cues: ['social media (analytics|data)', 'profile data', 'posts? (data|history)', 'twitter api', 'instagram api'] },
|
|
113
|
+
{ id: 'social.post', family: 'social', default_risk: ['publish_or_message'], needs_tool: true,
|
|
114
|
+
cues: { en: ['post (this|it|that) (on|to)', 'post (on|to) (my )?(instagram|facebook|linkedin|x|twitter|tiktok)', 'tweet (this|it|that)', 'publish (it|this|that)? ?(on|to)', 'share (it|this) on'], fr: ['publie (ca|le|la|ce|cette)? ?sur', 'poste (ca|le)? ?sur'], derja: ['ensher', 'انشر', 'حطو على الفيسبوك', 'publiyi'] },
|
|
115
|
+
tool_cues: ['publish posts?', 'post to (social|x|twitter|instagram|facebook)', 'schedule posts?', 'auto.?post\\w*'] },
|
|
116
|
+
{ id: 'social.content', family: 'social', default_risk: [], needs_tool: true, product_id: 'go-viral',
|
|
117
|
+
cues: { en: ['(write|draft|make|create) (an? |some |\\d+ )?(post|posts|caption|captions|tweet|tweets|thread|hashtags)', 'caption for', 'content (plan|calendar) for', 'go viral', 'social media (post|content)'], fr: ['(ecris|redige|fais|prepare)(.moi)? (un|une|des|\\d+) (publications?|posts?|legendes?)', 'legende pour', 'faire le buzz'], derja: ['a3melli post', 'ekteblli post', 'اكتبلي بوست', 'منشور'] },
|
|
118
|
+
tool_cues: ['social media content', 'content calendar', 'captions?', 'hashtags?'] },
|
|
119
|
+
{ id: 'message.send', family: 'messaging', default_risk: ['publish_or_message'], needs_tool: true, args: ['recipient'],
|
|
120
|
+
cues: { en: ['send (an? )?(sms|text|text message|whatsapp|telegram message|message|dm) to', 'text (him|her|them|my)', 'message (him|her|them|my)', 'dm (him|her|them)'], fr: ['envoie (un|le) (sms|message|texto) a', 'ecris a (mon|ma)'], derja: ['ab3ath msg', 'ab3athlou message', 'ابعثلو مساج', 'ابعث ميساج'] },
|
|
121
|
+
tool_cues: ['sms', 'send (sms|messages?|texts?)', 'whatsapp', 'telegram bot', 'messaging api', 'phone calls?'] },
|
|
122
|
+
// ── office ────────────────────────────────────────────────────────────────
|
|
123
|
+
{ id: 'sheet.edit', family: 'office', default_risk: ['data_modification'], needs_tool: true, args: ['file_ref'],
|
|
124
|
+
cues: { en: ['(add|insert) (a |an )?([a-z]+ )?column', '(sort|filter) (the|my|this) (sheet|spreadsheet|table|excel)', '(edit|update|modify) (the|my|this) (excel|spreadsheet|sheet|xlsx)', 'formula'], fr: ['ajoute une colonne', '(modifie|trie) (le|mon) (tableau|fichier excel|classeur)', 'formule'], derja: ['zid colonne', 'زيد كولون'] },
|
|
125
|
+
tool_cues: ['spreadsheets?', 'excel', 'xlsx', 'google sheets'] },
|
|
126
|
+
{ id: 'sheet.analyse', family: 'office', default_risk: ['read_only'], needs_tool: true, args: ['file_ref'],
|
|
127
|
+
cues: { en: ['(analy[sz]e|summari[sz]e|total) (this|my|the) (excel|spreadsheet|sheet|csv|xlsx)', 'totals? (in|of) (the|my|this) (sheet|spreadsheet|excel)', 'pivot table', 'excel', 'spreadsheet', 'csv'], fr: ['(analyse|total) (ce|mon|le) (fichier excel|tableau|classeur)', 'tableur', 'excel'], derja: ['el excel', 'الاكسال', 'اكسال'] },
|
|
128
|
+
tool_cues: ['spreadsheet analysis', 'csv', 'data analysis'] },
|
|
129
|
+
{ id: 'deck.make', family: 'office', default_risk: ['data_modification'], needs_tool: true,
|
|
130
|
+
cues: { en: ['power ?point', 'presentation', 'slides?', 'slide deck', 'pitch deck', 'deck', 'pptx'], fr: ['presentation', 'diaporama', 'power ?point'], derja: ['présentation', 'بريزنتاسيون', 'باوربوينت'] },
|
|
131
|
+
tool_cues: ['presentations?', 'slides?', 'pptx'] },
|
|
132
|
+
{ id: 'doc.make', family: 'office', default_risk: ['data_modification'], needs_tool: true,
|
|
133
|
+
cues: { en: ['word document', 'docx', '(as|into) a (word )?document', 'report (in|as) word'], fr: ['document word', 'fichier word', 'rapport word'], derja: ['ورد'] },
|
|
134
|
+
tool_cues: ['docx', 'word documents?', 'document generation'] },
|
|
135
|
+
// ── markets, tokens, money ────────────────────────────────────────────────
|
|
136
|
+
{ id: 'market.signals', family: 'market', default_risk: NET, needs_tool: true, product_id: 'voxight',
|
|
137
|
+
cues: { en: ['(crypto|market|coin|token) (trends?|narratives?|signals?|sentiment|attention|hype)', '(memecoins?|coins?|tokens?) (are )?trending', 'what.?s (pumping|trending) in crypto', 'trending (coins|tokens|narratives)', 'market intel(ligence)?', 'narratives? (in|on) (crypto|web3|solana)'], fr: ['tendances? (crypto|du marche crypto)', '(memecoins?|cryptos?|tokens?|coins?) (sont )?(en )?tendance', 'signaux (crypto|du marche)', 'narratifs? crypto', 'ce qui monte en crypto'], derja: ['ترند الكريبتو', 'شنوه يتحكى في الكريبتو'] },
|
|
138
|
+
tool_cues: ['social sentiment', 'narratives?', 'market sentiment', 'trend signals?', 'kol', 'attention signals?'] },
|
|
139
|
+
{ id: 'token.analyse', family: 'token', default_risk: NET, needs_tool: true, product_id: 'golden-kog', args: ['token'],
|
|
140
|
+
cues: { en: ['(analy[sz]e|audit|check|scan) (this |the |that )?(token|coin|memecoin|contract|mint)', '(is|is this) (token|coin|it) (safe|a scam|a rug|legit)', 'rug ?pull', 'honeypot', 'token risk', 'holders? (of|distribution)', 'top holders'], fr: ['(analyse|verifie) (ce|le) (token|jeton|coin)', 'arnaque', 'est.ce (un|une) (scam|arnaque)'], derja: ['7allel token', '7allelli el token', 'حلل التوكن', 'حللي التوكن'] },
|
|
141
|
+
tool_cues: ['token (risk|security|audit|analysis|verdict)', 'holder (distribution|concentration)', 'rug ?check', 'contract (risk|audit|scan)', 'liquidity (lock|analysis)', 'token metadata', 'mint authority'] },
|
|
142
|
+
{ id: 'token.price', family: 'token', default_risk: NET, needs_tool: true, args: ['token'],
|
|
143
|
+
cues: { en: ['(price|value) of (bitcoin|btc|eth|ethereum|sol|solana|\\$[a-z]+|the token|this token)', 'how much is (bitcoin|btc|eth|sol|one btc)', 'market cap of', '(bitcoin|btc|eth|sol) price'], fr: ['prix du (bitcoin|btc|token|sol)', 'cours du (bitcoin|btc)'], derja: ['soum el bitcoin', 'سوم البيتكوين', 'قداش البيتكوين'] },
|
|
144
|
+
tool_cues: ['token prices?', 'price feeds?', 'ohlcv', 'dex (data|prices)', 'coingecko', 'spot prices?', 'crypto prices?', 'coin prices?'] },
|
|
145
|
+
{ id: 'token.trade', family: 'token', default_risk: ['payment', 'sensitive_data'], needs_tool: true,
|
|
146
|
+
cues: { en: ['(buy|sell|swap|trade|snipe|ape into) (some |\\d+ |\\$?[0-9.]+ ?)?(bitcoin|btc|eth|sol|tokens?|crypto|coins?|memecoins?|usdc|\\$[a-z]+)', 'place (a|an) (buy|sell|limit) order', 'execute (a )?trade', 'snipe'], fr: ['(achete|vends|echange) (du|des|de la)? ?(bitcoin|btc|sol|tokens?|crypto)'], derja: ['ech3ri', 'ochri', 'bi3', 'اشري', 'بيع التوكن', 'شري توكن'] },
|
|
147
|
+
tool_cues: ['token swaps?', 'dex trading', 'trading (bot|api|execution)', 'order execution', 'perpetuals', 'limit orders?'] },
|
|
148
|
+
{ id: 'wallet.transfer', family: 'wallet', default_risk: ['payment', 'sensitive_data'], needs_tool: true, args: ['amount', 'address'],
|
|
149
|
+
cues: { en: ['send \\d+(\\.\\d+)? ?(usdc|usdt|sol|eth|btc|kgc|dollars?|euros?)', '(send|transfer|wire) (money|funds|crypto|usdc|sol|eth|tokens?)', 'transfer \\d+', 'pay (him|her|them|\\w+) \\d+'], fr: ['envoie \\d+ ?(usdc|sol|euros?|dinars?)', '(transfere|vire) (de l.argent|\\d+)', 'virement'], derja: ['ab3ath flous', 'ab3athlou \\d+', 'ابعث فلوس', 'ابعثلو \\d+', 'حول فلوس'] },
|
|
150
|
+
tool_cues: ['(token|crypto|usdc|wallet) transfers?', 'transfer (funds|usdc|tokens|crypto|sol|eth)', 'send (funds|tokens|usdc|crypto|payments?)', 'payouts?', 'disburse\\w*', 'remittance'] },
|
|
151
|
+
{ id: 'wallet.balance', family: 'wallet', default_risk: ['read_only', 'sensitive_data'], needs_tool: true,
|
|
152
|
+
cues: { en: ['my (wallet )?balance', 'how much (usdc|sol|eth|money|kgc) (do i have|is in my wallet)', 'balance of my wallet'], fr: ['mon solde', 'solde de mon (wallet|portefeuille)'], derja: ['9adeh 3andi fil wallet', 'قداش عندي في الوالت'] },
|
|
153
|
+
tool_cues: ['wallet balances?', 'portfolio (balance|tracker)', 'account balance'] },
|
|
154
|
+
{ id: 'payment.pay', family: 'wallet', default_risk: ['payment', 'sensitive_data'], needs_tool: true,
|
|
155
|
+
cues: { en: ['pay (my|this|the) (bill|invoice|subscription|rent)', 'make a payment', 'checkout', 'purchase (this|it)'], fr: ['paye(r)? (ma|la|cette) facture', 'regle(r)? la facture'], derja: ['khalles el facture', 'خلص الفاتوره'] },
|
|
156
|
+
tool_cues: ['payments? (api|processing|gateway)', 'checkout', 'card payments?', 'stripe'] },
|
|
157
|
+
{ id: 'invoice.make', family: 'business', default_risk: ['data_modification'], needs_tool: true, product_id: 'invoica',
|
|
158
|
+
cues: { en: ['invoices?', 'invoicing', 'bill (a|my) client', 'quote for a client'], fr: ['factures?', 'facturer', 'devis'], derja: ['facture', 'فاتوره', 'فاتورات', 'فواتير'] },
|
|
159
|
+
tool_cues: ['invoic\\w+', 'billing'] },
|
|
160
|
+
{ id: 'accounting.books', family: 'business', default_risk: ['data_modification'], needs_tool: true, product_id: 'akonta',
|
|
161
|
+
cues: { en: ['bookkeeping', 'accounting', 'accountant', 'vat (return|report)?', 'ledger', 'my books'], fr: ['comptabilite', 'comptable', 'tva', 'livre de caisse', 'bilan comptable'], derja: ['kontabilite', 'el kontabl', 'محاسبه', 'كونتابيليتي', 'دفتر الحسابات'] },
|
|
162
|
+
tool_cues: ['bookkeeping', 'accounting', 'ledger', 'vat', 'tax filing'] },
|
|
163
|
+
{ id: 'business.plan', family: 'business', default_risk: [], needs_tool: true, product_id: 'business-coach',
|
|
164
|
+
cues: { en: ['business ?plan', 'launch my (business|company|startup)', 'start a (business|company|startup)', 'startup plan', 'go.to.market plan'], fr: ['plan d.affaires?', 'business ?plan', 'lancer (mon|ma|une) (projet|entreprise|startup|activite)'], derja: ['bizness plan', 'خطه عمل', 'بيزنس بلان', 'نحب نحل مشروع'] } },
|
|
165
|
+
{ id: 'learning.program', family: 'learning', default_risk: [], needs_tool: true, product_id: 'learn',
|
|
166
|
+
cues: { en: ['teach me', 'i want to learn', 'learn (how to|to|about)', 'a course (on|in)', 'study plan', 'learning program(me)?', 'lessons (on|in)'], fr: ['apprendre', 'apprends.moi', 'formation (en|sur)', 'cours (de|sur)', 'programme d.apprentissage'], derja: ['net3allem', 'nit3allem', 'n7eb nt3allem', 'نتعلم', 'تعلمني', 'كورس'] } },
|
|
167
|
+
{ id: 'agent.hire', family: 'agents', default_risk: ['data_modification'], needs_tool: true,
|
|
168
|
+
cues: { en: ['hire (an? )?(agent|freelancer)', 'find (an? )?agent (for|to|that)', 'on lax', 'lax agent'], fr: ['engage(r)? un agent', 'trouve(r)? un agent'], derja: ['جيبلي ايجنت'] },
|
|
169
|
+
tool_cues: ['hire', 'agent marketplace', 'freelanc\\w+'] },
|
|
170
|
+
// ── developer and infrastructure ──────────────────────────────────────────
|
|
171
|
+
{ id: 'code.repo', family: 'code', default_risk: NET, needs_tool: true,
|
|
172
|
+
cues: { en: ['github', 'gitlab', 'repo(sitory)?', 'pull requests?', 'open issues?', 'commits?', 'git (log|history)'], fr: ['depot (git|github)', 'pull request'], derja: ['el repo'] },
|
|
173
|
+
tool_cues: ['github', 'gitlab', 'git', 'repositor\\w+', 'pull requests?', 'issues?'] },
|
|
174
|
+
{ id: 'code.run', family: 'code', default_risk: ['privileged_infra'], needs_tool: true,
|
|
175
|
+
cues: { en: ['run (this|my|the) (code|script|python|program)', 'execute (this|the) (code|script)', 'sandbox', 'jupyter', 'notebook'], fr: ['execute (ce|le) (code|script)', 'lance (ce|le) script'], derja: ['ran el code', 'شغل الكود'] },
|
|
176
|
+
tool_cues: ['code execution', 'sandbox', 'interpreter', 'run (python|code|scripts)', 'jupyter'] },
|
|
177
|
+
{ id: 'infra.deploy', family: 'infra', default_risk: ['privileged_infra'], needs_tool: true,
|
|
178
|
+
cues: { en: ['deploy', 'ssh', 'kubernetes', 'kubectl', 'docker', 'restart (the )?(server|service|pm2|gateway)', 'provision (a )?(server|vm)', 'dns record', 'terraform'], fr: ['deploie(r)?', 'redemarre(r)? le (serveur|service)'], derja: ['deployi', 'ريستارتي السيرفر'] },
|
|
179
|
+
tool_cues: ['deploy\\w*', 'kubernetes', 'docker', 'terraform', 'cloud (infrastructure|computers?|vms?)', 'vercel', 'hetzner', 'process manager', 'pm2', 'tailscale', 'firewalls?', 'provision\\w*'] },
|
|
180
|
+
{ id: 'infra.monitor', family: 'infra', default_risk: ['read_only'], needs_tool: true,
|
|
181
|
+
cues: { en: ['monitor(ing)?', 'uptime', 'is (the )?(site|server|api) down', 'cpu usage', 'memory usage', 'server (logs|status|health)', 'error logs'], fr: ['surveille(r)?', 'le site est.il en panne', 'logs du serveur'], derja: ['السيرفر طايح'] },
|
|
182
|
+
tool_cues: ['monitoring', 'observability', 'uptime', 'apm', 'error tracking', 'system (monitor|resources?)', 'log aggregation'] },
|
|
183
|
+
{ id: 'db.query', family: 'data', default_risk: ['read_only'], needs_tool: true,
|
|
184
|
+
cues: { en: ['sql', 'query (the|my|our) database', 'postgres(ql)?', 'supabase', 'select .* from'], fr: ['requete sql', 'base de donnees'], derja: ['الداتابيز'] },
|
|
185
|
+
tool_cues: ['databases?', 'sql', 'postgres\\w*', 'mysql', 'sqlite', 'mongodb', 'bigquery', 'supabase', 'redis'] },
|
|
186
|
+
{ id: 'db.write', family: 'data', default_risk: ['data_modification'], needs_tool: true,
|
|
187
|
+
cues: { en: ['insert into', 'update (the )?(table|rows?|records?)', 'migrate the database', 'write to the database'], fr: ['mets a jour la (table|base)'], derja: [] },
|
|
188
|
+
tool_cues: ['write to (the )?database', 'insert', 'upsert'] },
|
|
189
|
+
{ id: 'file.delete', family: 'data', default_risk: ['destructive'], needs_tool: true,
|
|
190
|
+
cues: { en: ['delete (all )?(my |the |these |those )?(files?|e.?mails?|records?|database|repo|account|photos?|messages?|posts?)', 'wipe', 'erase', 'remove all'], fr: ['supprime(r)?', 'efface(r)?'], derja: ['fasa5', 'na77i kol', 'فسخ', 'امسح'] },
|
|
191
|
+
tool_cues: ['delet\\w+', 'remov\\w+ files', 'purge'] },
|
|
192
|
+
{ id: 'security.scan', family: 'security', default_risk: NET, needs_tool: true,
|
|
193
|
+
cues: { en: ['vulnerabilit(y|ies)', 'security (audit|scan)', 'pentest', 'cve', 'ssl certificate', 'malware scan'], fr: ['audit de securite', 'failles? de securite'], derja: [] },
|
|
194
|
+
tool_cues: ['security (scan|scanner|audit)', 'vulnerabilit\\w+', 'threat intel\\w*', 'malware', 'phishing', 'cve'] },
|
|
195
|
+
{ id: 'finance.stock_quote', family: 'finance', default_risk: NET, needs_tool: true,
|
|
196
|
+
cues: { en: ['stock price', 'share price', 'nasdaq', 's&p ?500', 'ticker', 'stock quote', 'earnings of'], fr: ['cours de l.action', 'bourse de'], derja: ['البورصه'] },
|
|
197
|
+
tool_cues: ['stock (quotes?|prices?|market data)', 'equities', 'equity (data|prices)', 'tokenized stocks?', 'share prices?'] },
|
|
198
|
+
// ── engine side (build tool layer); rarely asked by a person ──────────────
|
|
199
|
+
{ id: 'text.embed', family: 'engine', default_risk: [], needs_tool: true,
|
|
200
|
+
cues: { en: ['embeddings?', 'embed (these|this|the|my)', 'vectori[sz]e', 'as vectors'], fr: [], derja: [] }, tool_cues: ['embeddings?', 'vector'] },
|
|
201
|
+
{ id: 'payment.meter', family: 'engine', default_risk: ['payment'], needs_tool: true,
|
|
202
|
+
cues: { en: ['meter(ing)? (calls|usage)'], fr: [], derja: [] }, tool_cues: ['metering', 'micropayment', 'per.call billing'] },
|
|
203
|
+
{ id: 'compliance.check', family: 'engine', default_risk: ['read_only'], needs_tool: true,
|
|
204
|
+
cues: { en: ['gdpr', 'hipaa', 'compliance check'], fr: ['rgpd', 'conformite'], derja: [] }, tool_cues: ['gdpr', 'hipaa', 'compliance'] },
|
|
205
|
+
{ id: 'identity.verify', family: 'engine', default_risk: ['read_only'], needs_tool: true,
|
|
206
|
+
cues: { en: ['verify (the|this) (identity|mandate|agent)', 'erc.?8004'], fr: [], derja: [] }, tool_cues: ['erc.?8004', 'kyc', 'identity verification', 'mandate (signatures?|verification)', 'agent identit(y|ies)'] },
|
|
207
|
+
];
|
|
208
|
+
const capabilityById = (id) => BY_ID.get(id);
|
|
209
|
+
exports.capabilityById = capabilityById;
|
|
210
|
+
const BY_ID = new Map(exports.CAPABILITIES.map((c) => [c.id, c]));
|
|
211
|
+
exports.CAPABILITY_IDS = exports.CAPABILITIES.map((c) => c.id);
|
|
212
|
+
const familyOf = (id) => BY_ID.get(id)?.family ?? null;
|
|
213
|
+
exports.familyOf = familyOf;
|
|
214
|
+
/** Words that ask for analysis, comparison or synthesis: REASONING whatever the tool count (plan.md S2). */
|
|
215
|
+
exports.REASONING_CUES = [
|
|
216
|
+
'compare', 'comparison', 'versus', 'vs\\.?', 'evaluate', 'assess', 'real.world evidence', 'rwe', 'meta.analys[ie]s',
|
|
217
|
+
'systematic review', 'pros and cons', 'trade.?offs?', 'why (does|do|is|are|did)', 'which is better', 'synthes[iz]e',
|
|
218
|
+
'critically', 'weigh the evidence', 'root cause',
|
|
219
|
+
'compar(e|er|aison)', 'evalue(r)?', 'avantages et inconvenients', 'pourquoi', 'quelle est la meilleure',
|
|
220
|
+
'3lech', 'alech', 'علاش', 'قارن', 'شنوه الافضل',
|
|
221
|
+
];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text normalisation shared by the classifier and the tokeniser: lower case, Latin accents folded, Arabic letter
|
|
3
|
+
* variants unified and diacritics dropped, typographic apostrophes and quotes unified. Tunisian derja in Latin
|
|
4
|
+
* letters keeps its digits (3 = ain, 7 = ha, 9 = qaf), as route-request.ts does.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalise(s: string): string;
|
|
7
|
+
export declare const ARABIC: RegExp;
|
|
8
|
+
/** Very small language guess: Arabic script or Latin derja markers = derja; French vs English by stop words. */
|
|
9
|
+
export declare function detectLanguage(text: string): 'en' | 'fr' | 'derja' | 'other';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ARABIC = void 0;
|
|
4
|
+
exports.normalise = normalise;
|
|
5
|
+
exports.detectLanguage = detectLanguage;
|
|
6
|
+
/**
|
|
7
|
+
* Text normalisation shared by the classifier and the tokeniser: lower case, Latin accents folded, Arabic letter
|
|
8
|
+
* variants unified and diacritics dropped, typographic apostrophes and quotes unified. Tunisian derja in Latin
|
|
9
|
+
* letters keeps its digits (3 = ain, 7 = ha, 9 = qaf), as route-request.ts does.
|
|
10
|
+
*/
|
|
11
|
+
function normalise(s) {
|
|
12
|
+
return String(s ?? '')
|
|
13
|
+
.normalize('NFKD').replace(/[̀-ͯ]/g, '')
|
|
14
|
+
.replace(/[ً-ْٰـ]/g, '') // Arabic diacritics and tatweel
|
|
15
|
+
.replace(/[أإآٱ]/g, 'ا').replace(/ى/g, 'ي').replace(/ة/g, 'ه').replace(/ؤ/g, 'و').replace(/ئ/g, 'ي')
|
|
16
|
+
.replace(/[‘’ʼ`´]/g, "'").replace(/[“”«»]/g, '"')
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(/\s+/g, ' ')
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
exports.ARABIC = /[-ۿ]/;
|
|
22
|
+
/** Very small language guess: Arabic script or Latin derja markers = derja; French vs English by stop words. */
|
|
23
|
+
function detectLanguage(text) {
|
|
24
|
+
const t = normalise(text);
|
|
25
|
+
if (!t)
|
|
26
|
+
return 'other';
|
|
27
|
+
if (exports.ARABIC.test(t))
|
|
28
|
+
return 'derja';
|
|
29
|
+
if (/\b[a-z]*[379][a-z]+\b/.test(t) && /\b(a3mel|a3mil|n7eb|nheb|chnowa|chnoua|kifech|kifach|9adeh|9adech|qaddech|mte3i|mta3i|barcha|lawej|ta9s|net3allem|e7seb|ab3ath|ba3d|wa9t|fi|el|w)\b/.test(t))
|
|
30
|
+
return 'derja';
|
|
31
|
+
if (/\b(chnowa|kifech|barcha|mte3i|mta3i|n7eb|nheb|lawej|a3mel|a3melli|3lech|ghodwa|lyoum)\b/.test(t))
|
|
32
|
+
return 'derja';
|
|
33
|
+
const fr = (t.match(/\b(le|la|les|des|du|un|une|est|et|pour|avec|dans|sur|mes|mon|ma|je|tu|vous|quel|quelle|quoi|fais|moi|ce|cette|qui|pas|il|elle|demain|au|aux|svp|merci|fera|sont|ou|nous|notre|mes)\b/g) || []).length;
|
|
34
|
+
const en = (t.match(/\b(the|an|is|are|and|for|with|in|on|my|me|i|you|what|which|make|this|that|of|to|do|not|please|will|it)\b/g) || []).length;
|
|
35
|
+
if (fr === 0 && en === 0)
|
|
36
|
+
return /^[\x00-\x7F]+$/.test(t) ? 'en' : 'other';
|
|
37
|
+
return fr > en ? 'fr' : 'en';
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies the eligibility rules to retrieved candidates and counts exclusions per rule. Counts, never the excluded
|
|
3
|
+
* tools' text, go back to the caller, so it can say "2 tools exist but need a payment approval".
|
|
4
|
+
*/
|
|
5
|
+
import type { Retrieved } from '../retrieve/hybrid';
|
|
6
|
+
import { type EligibilityInput, type RuleId } from './rules';
|
|
7
|
+
/** `relevant` limits the counts to candidates that would have been worth showing (fit above the floor). */
|
|
8
|
+
export declare function filter(cands: Retrieved[], base: Omit<EligibilityInput, 'rec' | 'op'>, relevant?: (c: Retrieved) => boolean): {
|
|
9
|
+
eligible: Retrieved[];
|
|
10
|
+
excluded: Partial<Record<RuleId, number>>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filter = filter;
|
|
4
|
+
const rules_1 = require("./rules");
|
|
5
|
+
/** `relevant` limits the counts to candidates that would have been worth showing (fit above the floor). */
|
|
6
|
+
function filter(cands, base, relevant = () => true) {
|
|
7
|
+
const eligible = [];
|
|
8
|
+
const excluded = {};
|
|
9
|
+
for (const c of cands) {
|
|
10
|
+
const f = (0, rules_1.firstFailure)({ ...base, rec: c.item.rec, op: c.item.op });
|
|
11
|
+
if (f) {
|
|
12
|
+
if (relevant(c))
|
|
13
|
+
excluded[f] = (excluded[f] || 0) + 1;
|
|
14
|
+
}
|
|
15
|
+
else
|
|
16
|
+
eligible.push(c);
|
|
17
|
+
}
|
|
18
|
+
return { eligible, excluded };
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eligibility (plan.md S3), applied before any scoring, in order; the first failing rule excludes. Missing evidence is
|
|
3
|
+
* never safe: each rule says what happens when the evidence is absent. E10 (Kognai first) and E11 (authorisation) do
|
|
4
|
+
* not exclude: E10 is applied by the shortlist (fallbacks), E11 marks auth_needed on the candidate.
|
|
5
|
+
*/
|
|
6
|
+
import type { KtrFlags } from '../flags';
|
|
7
|
+
import type { CallerContext, PlanNode, RouteRequest } from '../types/route';
|
|
8
|
+
import type { RiskClass, ToolOperation, ToolRecord } from '../types/tool';
|
|
9
|
+
export type RuleId = 'E1' | 'E2' | 'E3' | 'E4' | 'E5' | 'E6' | 'E7' | 'E8' | 'E9';
|
|
10
|
+
export declare const RULE_IDS: RuleId[];
|
|
11
|
+
export declare const RULE_LABEL: Record<RuleId, string>;
|
|
12
|
+
export declare const NEVER_AUTONOMOUS: RiskClass[];
|
|
13
|
+
export interface EligibilityInput {
|
|
14
|
+
rec: ToolRecord;
|
|
15
|
+
op: ToolOperation;
|
|
16
|
+
node: PlanNode;
|
|
17
|
+
ctx: CallerContext;
|
|
18
|
+
mode: 'discover' | 'execute_plan';
|
|
19
|
+
flags: Pick<KtrFlags, 'stopped' | 'enabled'>;
|
|
20
|
+
offline: boolean;
|
|
21
|
+
constraints?: RouteRequest['constraints'];
|
|
22
|
+
}
|
|
23
|
+
export declare function isKognaiProduct(rec: ToolRecord): boolean;
|
|
24
|
+
export declare function budgetMicro(ctx: CallerContext, c?: RouteRequest['constraints']): bigint | null;
|
|
25
|
+
export declare function priceMicroUsd(rec: ToolRecord): bigint | null;
|
|
26
|
+
/** The first rule that excludes, or null when eligible. */
|
|
27
|
+
export declare function firstFailure(x: EligibilityInput): RuleId | null;
|
|
28
|
+
/** E11: what the caller must still provide before this tool could run. Never an exclusion. */
|
|
29
|
+
export declare function authNeeded(rec: ToolRecord, op: ToolOperation, ctx: CallerContext): {
|
|
30
|
+
kind: 'oauth' | 'api_key' | 'x402' | 'approval';
|
|
31
|
+
provider?: string;
|
|
32
|
+
scopes?: string[];
|
|
33
|
+
} | null;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NEVER_AUTONOMOUS = exports.RULE_LABEL = exports.RULE_IDS = void 0;
|
|
4
|
+
exports.isKognaiProduct = isKognaiProduct;
|
|
5
|
+
exports.budgetMicro = budgetMicro;
|
|
6
|
+
exports.priceMicroUsd = priceMicroUsd;
|
|
7
|
+
exports.firstFailure = firstFailure;
|
|
8
|
+
exports.authNeeded = authNeeded;
|
|
9
|
+
const money_1 = require("../money");
|
|
10
|
+
exports.RULE_IDS = ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'];
|
|
11
|
+
exports.RULE_LABEL = {
|
|
12
|
+
E1: 'not active (draft, deprecated or retired)',
|
|
13
|
+
E2: 'trust: revoked, deprecated, or quarantined outside discover mode',
|
|
14
|
+
E3: 'KTR stopped or disabled',
|
|
15
|
+
E4: 'risk class not allowed without an approval path (payment, destructive, privileged infrastructure; publish or message for citizens)',
|
|
16
|
+
E5: 'takes a URL from the conversation (no fetch from chat)',
|
|
17
|
+
E6: 'business tier above the caller, or not yet classified',
|
|
18
|
+
E7: 'private data of someone other than the principal',
|
|
19
|
+
E8: 'region or network outside the caller constraints',
|
|
20
|
+
E9: 'price above the budget, or unknown with a budget',
|
|
21
|
+
};
|
|
22
|
+
exports.NEVER_AUTONOMOUS = ['payment', 'destructive', 'privileged_infra'];
|
|
23
|
+
const TIER_RANK = { free: 0, citizen_plus: 1, citizen_pro: 2, citizen_pro_max: 3, enterprise: 4 };
|
|
24
|
+
/** 3000 KGC = 1 USD (policy/kgc-revenue.txt): 1 KGC = 333.33 micro USD, rounded up so a KGC price never looks cheaper. */
|
|
25
|
+
const KGC_TO_MICRO = (kgc) => (kgc * 1000000n + 2999n) / 3000n;
|
|
26
|
+
function isKognaiProduct(rec) {
|
|
27
|
+
return rec.origin === 'kognai' && rec.provider.kognai_owned === true;
|
|
28
|
+
}
|
|
29
|
+
function budgetMicro(ctx, c) {
|
|
30
|
+
const a = (0, money_1.microOf)(ctx.budget_remaining_micro ?? null);
|
|
31
|
+
const b = (0, money_1.microOf)(c?.max_cost_micro ?? null);
|
|
32
|
+
if (a === null)
|
|
33
|
+
return b;
|
|
34
|
+
if (b === null)
|
|
35
|
+
return a;
|
|
36
|
+
return a < b ? a : b;
|
|
37
|
+
}
|
|
38
|
+
function priceMicroUsd(rec) {
|
|
39
|
+
if (rec.pricing.model === 'free')
|
|
40
|
+
return 0n;
|
|
41
|
+
const p = rec.pricing.price;
|
|
42
|
+
if (!p)
|
|
43
|
+
return null;
|
|
44
|
+
const m = (0, money_1.microOf)(p.amount_micro);
|
|
45
|
+
if (m === null)
|
|
46
|
+
return null;
|
|
47
|
+
return p.currency === 'KGC' ? KGC_TO_MICRO(m) : m;
|
|
48
|
+
}
|
|
49
|
+
/** The first rule that excludes, or null when eligible. */
|
|
50
|
+
function firstFailure(x) {
|
|
51
|
+
const { rec, op, node, ctx, mode } = x;
|
|
52
|
+
// E4 for the step itself comes first: a payment, destructive or privileged step is refused whatever tool would do it
|
|
53
|
+
if (exports.NEVER_AUTONOMOUS.some((r) => node.risk_classes.includes(r)))
|
|
54
|
+
return 'E4';
|
|
55
|
+
// E1 lifecycle
|
|
56
|
+
if (rec.lifecycle !== 'active')
|
|
57
|
+
return 'E1';
|
|
58
|
+
// E2 trust
|
|
59
|
+
if (rec.trust_state === 'revoked' || rec.trust_state === 'deprecated')
|
|
60
|
+
return 'E2';
|
|
61
|
+
if (rec.trust_state === 'quarantined' && mode !== 'discover')
|
|
62
|
+
return 'E2';
|
|
63
|
+
// E3 kill switch and flags
|
|
64
|
+
if (x.flags.stopped || (!x.flags.enabled && !x.offline))
|
|
65
|
+
return 'E3';
|
|
66
|
+
// E4 risk policy, for the tool's operation and for the step asked
|
|
67
|
+
const risks = new Set([...op.risk_classes, ...node.risk_classes]);
|
|
68
|
+
if (exports.NEVER_AUTONOMOUS.some((r) => risks.has(r)))
|
|
69
|
+
return 'E4';
|
|
70
|
+
if (ctx.kind === 'citizen' && risks.has('publish_or_message') && !(isKognaiProduct(rec) && rec.tags.includes('founder_approval')))
|
|
71
|
+
return 'E4';
|
|
72
|
+
// E5 no fetch from chat
|
|
73
|
+
if (ctx.kind === 'citizen' && op.url_from_user)
|
|
74
|
+
return 'E5';
|
|
75
|
+
// E6 business tier
|
|
76
|
+
if (rec.business_tier === null) {
|
|
77
|
+
if (ctx.kind === 'citizen')
|
|
78
|
+
return 'E6';
|
|
79
|
+
}
|
|
80
|
+
else if (TIER_RANK[rec.business_tier] > TIER_RANK[ctx.business_tier])
|
|
81
|
+
return 'E6';
|
|
82
|
+
// E7 privacy: private and sensitive tools only for the data owner's own principal
|
|
83
|
+
if (rec.privacy_class === 'private' || rec.privacy_class === 'sensitive') {
|
|
84
|
+
const owner = ctx.data_owner_id ?? ctx.principal_id;
|
|
85
|
+
if (!ctx.principal_id || owner !== ctx.principal_id || ctx.kind === 'external')
|
|
86
|
+
return 'E7';
|
|
87
|
+
}
|
|
88
|
+
// E8 regions and networks, when the caller sets constraints
|
|
89
|
+
const c = x.constraints;
|
|
90
|
+
if (c?.regions?.length && !rec.regions.includes('*') && !rec.regions.some((r) => c.regions.includes(r)))
|
|
91
|
+
return 'E8';
|
|
92
|
+
if (c?.networks?.length) {
|
|
93
|
+
const paid = rec.auth.kind === 'x402' || (rec.pricing.model !== 'free' && rec.pricing.model !== 'unknown');
|
|
94
|
+
if (paid && !rec.networks.some((n) => c.networks.includes(n)))
|
|
95
|
+
return 'E8';
|
|
96
|
+
}
|
|
97
|
+
// E9 budget
|
|
98
|
+
const budget = budgetMicro(ctx, c);
|
|
99
|
+
if (budget !== null) {
|
|
100
|
+
const price = priceMicroUsd(rec);
|
|
101
|
+
if (price === null || price > budget)
|
|
102
|
+
return 'E9';
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
/** E11: what the caller must still provide before this tool could run. Never an exclusion. */
|
|
107
|
+
function authNeeded(rec, op, ctx) {
|
|
108
|
+
if (rec.tags.includes('founder_approval') || op.risk_classes.includes('publish_or_message'))
|
|
109
|
+
return { kind: 'approval' };
|
|
110
|
+
if (rec.auth.kind === 'oauth') {
|
|
111
|
+
const conn = ctx.connected.find((c) => c.provider === rec.auth.provider);
|
|
112
|
+
if (!conn || op.scopes.some((s) => !conn.scopes.includes(s)))
|
|
113
|
+
return { kind: 'oauth', provider: rec.auth.provider, scopes: op.scopes };
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (rec.auth.kind === 'x402')
|
|
117
|
+
return { kind: 'x402' };
|
|
118
|
+
if (rec.auth.kind === 'api_key' && rec.origin !== 'kognai')
|
|
119
|
+
return { kind: 'api_key', provider: rec.provider.name };
|
|
120
|
+
return null;
|
|
121
|
+
}
|
package/dist/flags.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTR flags (plan.md section 7). All off by default. "true" or "1" turns a flag on, anything else is off (the
|
|
3
|
+
* X402_METER_ENABLED convention). Pure: the caller passes the env object and whether the stop file exists, so this
|
|
4
|
+
* module never touches the file system. The file switch .swarm-state/ktr-stop overrides every flag.
|
|
5
|
+
*
|
|
6
|
+
* Two flags are added to the plan's list for milestone 1, both off:
|
|
7
|
+
* TOOL_ROUTER_SNAPSHOT_FETCH the snapshot refresh CLI may fetch public catalogs over the network
|
|
8
|
+
* TOOL_ROUTER_MCP_LIST_LIVE the MCP adapter may call tools/list on an approved REMOTE server (never a package)
|
|
9
|
+
*/
|
|
10
|
+
export interface KtrFlags {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
shadow: boolean;
|
|
13
|
+
externalDiscovery: boolean;
|
|
14
|
+
modelFallback: boolean;
|
|
15
|
+
executionEnabled: boolean;
|
|
16
|
+
experienceCapture: boolean;
|
|
17
|
+
skillCrystallisation: boolean;
|
|
18
|
+
marketplaceEnabled: boolean;
|
|
19
|
+
rentalMainnet: boolean;
|
|
20
|
+
snapshotFetch: boolean;
|
|
21
|
+
mcpListLive: boolean;
|
|
22
|
+
/** S6b (Founder 2026-10-24 "yes" to the migration): the router also reads approved and listed skill v2 records */
|
|
23
|
+
skillsV2Read: boolean;
|
|
24
|
+
/** .swarm-state/ktr-stop exists: everything is off, whatever the env says. */
|
|
25
|
+
stopped: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare const FLAG_ENV: Record<Exclude<keyof KtrFlags, 'stopped'>, string>;
|
|
28
|
+
export declare const STOP_FILE = ".swarm-state/ktr-stop";
|
|
29
|
+
export declare function readFlags(env?: Record<string, string | undefined>, opts?: {
|
|
30
|
+
stopFileExists?: boolean;
|
|
31
|
+
}): KtrFlags;
|
|
32
|
+
/** Every flag off: the default a test or a library caller starts from. */
|
|
33
|
+
export declare const ALL_OFF: KtrFlags;
|
package/dist/flags.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALL_OFF = exports.STOP_FILE = exports.FLAG_ENV = void 0;
|
|
4
|
+
exports.readFlags = readFlags;
|
|
5
|
+
exports.FLAG_ENV = {
|
|
6
|
+
enabled: 'TOOL_ROUTER_ENABLED',
|
|
7
|
+
shadow: 'TOOL_ROUTER_SHADOW_MODE',
|
|
8
|
+
externalDiscovery: 'TOOL_ROUTER_EXTERNAL_DISCOVERY',
|
|
9
|
+
modelFallback: 'TOOL_ROUTER_MODEL_FALLBACK',
|
|
10
|
+
executionEnabled: 'TOOL_ROUTER_EXECUTION_ENABLED',
|
|
11
|
+
experienceCapture: 'TOOL_EXPERIENCE_CAPTURE',
|
|
12
|
+
skillCrystallisation: 'TOOL_SKILL_CRYSTALLISATION',
|
|
13
|
+
marketplaceEnabled: 'SKILLS_MARKETPLACE_ENABLED',
|
|
14
|
+
rentalMainnet: 'SKILL_RENTAL_MAINNET_ENABLED',
|
|
15
|
+
snapshotFetch: 'TOOL_ROUTER_SNAPSHOT_FETCH',
|
|
16
|
+
mcpListLive: 'TOOL_ROUTER_MCP_LIST_LIVE',
|
|
17
|
+
skillsV2Read: 'TOOL_SKILLS_V2_READ',
|
|
18
|
+
};
|
|
19
|
+
exports.STOP_FILE = '.swarm-state/ktr-stop';
|
|
20
|
+
const on = (v) => v === 'true' || v === '1';
|
|
21
|
+
function readFlags(env = process.env, opts = {}) {
|
|
22
|
+
const stopped = opts.stopFileExists === true;
|
|
23
|
+
const out = { stopped };
|
|
24
|
+
for (const [k, name] of Object.entries(exports.FLAG_ENV)) {
|
|
25
|
+
out[k] = !stopped && on(env[name]);
|
|
26
|
+
}
|
|
27
|
+
// Milestone 1 was offline (decision D2). From S4 the Execution Gateway may execute when TOOL_ROUTER_EXECUTION_ENABLED
|
|
28
|
+
// is set (default off; read only native tools only, core/tool-gateway). Marketplace and mainnet stay off even when set.
|
|
29
|
+
out.marketplaceEnabled = false;
|
|
30
|
+
out.rentalMainnet = false;
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/** Every flag off: the default a test or a library caller starts from. */
|
|
34
|
+
exports.ALL_OFF = readFlags({});
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function canonical(v: unknown): string;
|
|
2
|
+
export declare function sha256(s: string | Buffer): string;
|
|
3
|
+
/** sha256 of the canonical input and output schemas of every operation, in operation order. */
|
|
4
|
+
export declare function schemaHash(ops: Array<{
|
|
5
|
+
op: string;
|
|
6
|
+
input_schema: unknown;
|
|
7
|
+
output_schema: unknown;
|
|
8
|
+
}>): string;
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonical = canonical;
|
|
4
|
+
exports.sha256 = sha256;
|
|
5
|
+
exports.schemaHash = schemaHash;
|
|
6
|
+
/** Canonical JSON (sorted keys, no whitespace) and sha256, the basis of schema hashes and snapshot ids. */
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
function canonical(v) {
|
|
9
|
+
if (v === undefined)
|
|
10
|
+
return 'null';
|
|
11
|
+
if (v === null || typeof v !== 'object') {
|
|
12
|
+
if (typeof v === 'number' && !Number.isFinite(v))
|
|
13
|
+
return 'null';
|
|
14
|
+
if (typeof v === 'bigint')
|
|
15
|
+
return JSON.stringify(v.toString());
|
|
16
|
+
return JSON.stringify(v);
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(v))
|
|
19
|
+
return `[${v.map(canonical).join(',')}]`;
|
|
20
|
+
const o = v;
|
|
21
|
+
const keys = Object.keys(o).filter((k) => o[k] !== undefined).sort();
|
|
22
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`).join(',')}}`;
|
|
23
|
+
}
|
|
24
|
+
function sha256(s) {
|
|
25
|
+
return (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
|
|
26
|
+
}
|
|
27
|
+
/** sha256 of the canonical input and output schemas of every operation, in operation order. */
|
|
28
|
+
function schemaHash(ops) {
|
|
29
|
+
return sha256(canonical(ops.map((o) => ({ op: o.op, input: o.input_schema ?? null, output: o.output_schema ?? null }))));
|
|
30
|
+
}
|