@theia/ai-core 1.75.0-next.8 → 1.75.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/lib/browser/agent-plugin-ui-bridge.d.ts +26 -0
- package/lib/browser/agent-plugin-ui-bridge.d.ts.map +1 -0
- package/lib/browser/agent-plugin-ui-bridge.js +20 -0
- package/lib/browser/agent-plugin-ui-bridge.js.map +1 -0
- package/lib/browser/ai-core-command-contribution.d.ts.map +1 -1
- package/lib/browser/ai-core-command-contribution.js +4 -1
- package/lib/browser/ai-core-command-contribution.js.map +1 -1
- package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-core-frontend-module.js +6 -0
- package/lib/browser/ai-core-frontend-module.js.map +1 -1
- package/lib/browser/ai-settings-service.js +1 -0
- package/lib/browser/ai-settings-service.js.map +1 -1
- package/lib/browser/frontend-language-model-alias-registry.js +1 -1
- package/lib/browser/frontend-language-model-registry.d.ts +2 -0
- package/lib/browser/frontend-language-model-registry.d.ts.map +1 -1
- package/lib/browser/frontend-language-model-registry.js +22 -1
- package/lib/browser/frontend-language-model-registry.js.map +1 -1
- package/lib/browser/generic-capabilities-variable-contribution.d.ts.map +1 -1
- package/lib/browser/generic-capabilities-variable-contribution.js +4 -1
- package/lib/browser/generic-capabilities-variable-contribution.js.map +1 -1
- package/lib/browser/hide-ai-preferences-contribution.d.ts +30 -0
- package/lib/browser/hide-ai-preferences-contribution.d.ts.map +1 -0
- package/lib/browser/hide-ai-preferences-contribution.js +90 -0
- package/lib/browser/hide-ai-preferences-contribution.js.map +1 -0
- package/lib/browser/hide-ai-preferences-contribution.spec.d.ts +2 -0
- package/lib/browser/hide-ai-preferences-contribution.spec.d.ts.map +1 -0
- package/lib/browser/hide-ai-preferences-contribution.spec.js +95 -0
- package/lib/browser/hide-ai-preferences-contribution.spec.js.map +1 -0
- package/lib/browser/index.d.ts +3 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +3 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/prompt-variable-contribution.d.ts +1 -1
- package/lib/browser/prompt-variable-contribution.d.ts.map +1 -1
- package/lib/browser/prompt-variable-contribution.js +1 -0
- package/lib/browser/prompt-variable-contribution.js.map +1 -1
- package/lib/browser/skill-prompt-coordinator.d.ts +9 -1
- package/lib/browser/skill-prompt-coordinator.d.ts.map +1 -1
- package/lib/browser/skill-prompt-coordinator.js +29 -14
- package/lib/browser/skill-prompt-coordinator.js.map +1 -1
- package/lib/browser/skill-prompt-coordinator.spec.d.ts +2 -0
- package/lib/browser/skill-prompt-coordinator.spec.d.ts.map +1 -0
- package/lib/browser/skill-prompt-coordinator.spec.js +98 -0
- package/lib/browser/skill-prompt-coordinator.spec.js.map +1 -0
- package/lib/browser/skill-registry-ui-bridge.d.ts +25 -0
- package/lib/browser/skill-registry-ui-bridge.d.ts.map +1 -0
- package/lib/browser/skill-registry-ui-bridge.js +20 -0
- package/lib/browser/skill-registry-ui-bridge.js.map +1 -0
- package/lib/browser/skill-service.d.ts +33 -8
- package/lib/browser/skill-service.d.ts.map +1 -1
- package/lib/browser/skill-service.js +95 -39
- package/lib/browser/skill-service.js.map +1 -1
- package/lib/browser/skill-service.spec.js +181 -2
- package/lib/browser/skill-service.spec.js.map +1 -1
- package/lib/browser/skills-variable-contribution.d.ts +4 -0
- package/lib/browser/skills-variable-contribution.d.ts.map +1 -1
- package/lib/browser/skills-variable-contribution.js +10 -1
- package/lib/browser/skills-variable-contribution.js.map +1 -1
- package/lib/browser/skills-variable-contribution.spec.js +6 -0
- package/lib/browser/skills-variable-contribution.spec.js.map +1 -1
- package/lib/browser/theia-variable-contribution.js +6 -12
- package/lib/browser/theia-variable-contribution.js.map +1 -1
- package/lib/common/ai-core-preferences.d.ts +17 -0
- package/lib/common/ai-core-preferences.d.ts.map +1 -1
- package/lib/common/ai-core-preferences.js +9 -1
- package/lib/common/ai-core-preferences.js.map +1 -1
- package/lib/common/ai-variable-resource.d.ts +2 -1
- package/lib/common/ai-variable-resource.d.ts.map +1 -1
- package/lib/common/ai-variable-resource.js +6 -1
- package/lib/common/ai-variable-resource.js.map +1 -1
- package/lib/common/capability-variable-contribution.d.ts +1 -1
- package/lib/common/capability-variable-contribution.d.ts.map +1 -1
- package/lib/common/capability-variable-contribution.js +1 -0
- package/lib/common/capability-variable-contribution.js.map +1 -1
- package/lib/common/language-model.d.ts +16 -2
- package/lib/common/language-model.d.ts.map +1 -1
- package/lib/common/language-model.js +39 -3
- package/lib/common/language-model.js.map +1 -1
- package/lib/common/language-model.spec.js +75 -0
- package/lib/common/language-model.spec.js.map +1 -1
- package/lib/common/prompt-service.d.ts.map +1 -1
- package/lib/common/prompt-service.js +7 -4
- package/lib/common/prompt-service.js.map +1 -1
- package/lib/common/prompt-service.spec.js +36 -0
- package/lib/common/prompt-service.spec.js.map +1 -1
- package/lib/common/skill.d.ts +31 -7
- package/lib/common/skill.d.ts.map +1 -1
- package/lib/common/skill.js +37 -14
- package/lib/common/skill.js.map +1 -1
- package/lib/common/skill.spec.js +70 -0
- package/lib/common/skill.spec.js.map +1 -1
- package/lib/common/today-variable-contribution.d.ts.map +1 -1
- package/lib/common/today-variable-contribution.js +1 -1
- package/lib/common/today-variable-contribution.js.map +1 -1
- package/lib/common/tool-invocation-registry.d.ts +2 -1
- package/lib/common/tool-invocation-registry.d.ts.map +1 -1
- package/lib/common/tool-invocation-registry.js +6 -1
- package/lib/common/tool-invocation-registry.js.map +1 -1
- package/lib/node/language-model-frontend-delegate.d.ts +2 -2
- package/lib/node/language-model-frontend-delegate.d.ts.map +1 -1
- package/lib/node/language-model-frontend-delegate.js +1 -0
- package/lib/node/language-model-frontend-delegate.js.map +1 -1
- package/package.json +12 -12
- package/src/browser/agent-plugin-ui-bridge.ts +43 -0
- package/src/browser/ai-core-command-contribution.ts +7 -1
- package/src/browser/ai-core-frontend-module.ts +11 -2
- package/src/browser/ai-settings-service.ts +2 -2
- package/src/browser/frontend-language-model-alias-registry.ts +1 -1
- package/src/browser/frontend-language-model-registry.ts +6 -2
- package/src/browser/generic-capabilities-variable-contribution.ts +4 -1
- package/src/browser/hide-ai-preferences-contribution.spec.ts +117 -0
- package/src/browser/hide-ai-preferences-contribution.ts +88 -0
- package/src/browser/index.ts +3 -0
- package/src/browser/prompt-variable-contribution.ts +3 -3
- package/src/browser/skill-prompt-coordinator.spec.ts +114 -0
- package/src/browser/skill-prompt-coordinator.ts +29 -17
- package/src/browser/skill-registry-ui-bridge.ts +41 -0
- package/src/browser/skill-service.spec.ts +229 -4
- package/src/browser/skill-service.ts +120 -43
- package/src/browser/skills-variable-contribution.spec.ts +6 -0
- package/src/browser/skills-variable-contribution.ts +14 -4
- package/src/browser/theia-variable-contribution.ts +12 -12
- package/src/common/ai-core-preferences.ts +19 -0
- package/src/common/ai-variable-resource.ts +6 -3
- package/src/common/capability-variable-contribution.ts +3 -3
- package/src/common/language-model.spec.ts +83 -0
- package/src/common/language-model.ts +35 -5
- package/src/common/prompt-service.spec.ts +43 -0
- package/src/common/prompt-service.ts +8 -6
- package/src/common/skill.spec.ts +99 -0
- package/src/common/skill.ts +52 -17
- package/src/common/today-variable-contribution.ts +2 -1
- package/src/common/tool-invocation-registry.ts +5 -2
- package/src/node/language-model-frontend-delegate.ts +3 -3
|
@@ -534,13 +534,21 @@ export const isCompactionResponsePart = (part: unknown): part is CompactionRespo
|
|
|
534
534
|
export interface ToolCallTextResult { type: 'text', text: string; };
|
|
535
535
|
export interface ToolCallImageResult extends Base64ImageContent { type: 'image' };
|
|
536
536
|
export interface ToolCallAudioResult { type: 'audio', data: string; mimeType: string };
|
|
537
|
+
export interface ToolCallHtmlAppResult { type: 'html'; html: string; title?: string };
|
|
537
538
|
export type ToolCallErrorKind = 'tool-not-available';
|
|
538
539
|
export interface ToolCallErrorResult { type: 'error', data: string; errorKind?: ToolCallErrorKind; };
|
|
539
|
-
export type ToolCallContentResult = ToolCallTextResult | ToolCallImageResult | ToolCallAudioResult | ToolCallErrorResult;
|
|
540
|
+
export type ToolCallContentResult = ToolCallTextResult | ToolCallImageResult | ToolCallAudioResult | ToolCallHtmlAppResult | ToolCallErrorResult;
|
|
540
541
|
export interface ToolCallContent {
|
|
541
542
|
content: ToolCallContentResult[];
|
|
542
543
|
}
|
|
543
544
|
|
|
545
|
+
export const isToolCallHtmlAppResult = (item: unknown): item is ToolCallHtmlAppResult =>
|
|
546
|
+
!!(item &&
|
|
547
|
+
typeof item === 'object' &&
|
|
548
|
+
'type' in item && (item as ToolCallHtmlAppResult).type === 'html' &&
|
|
549
|
+
'html' in item &&
|
|
550
|
+
typeof (item as ToolCallHtmlAppResult).html === 'string');
|
|
551
|
+
|
|
544
552
|
export const isToolCallContent = (result: unknown): result is ToolCallContent =>
|
|
545
553
|
!!(result && typeof result === 'object' && 'content' in result && Array.isArray((result as ToolCallContent).content));
|
|
546
554
|
|
|
@@ -560,6 +568,26 @@ export const createToolCallError = (message: string, errorKind?: ToolCallErrorKi
|
|
|
560
568
|
content: [errorKind ? { type: 'error', data: message, errorKind } : { type: 'error', data: message }]
|
|
561
569
|
});
|
|
562
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Serializes a {@link ToolCallResult} to a string suitable for sending back to the model.
|
|
573
|
+
*
|
|
574
|
+
* HTML app results are replaced with a compact placeholder so that large bundled HTML
|
|
575
|
+
* (e.g. Plotly charts) does not blow the model's context window. The full HTML is still
|
|
576
|
+
* available in the structured result for rendering in the UI (e.g. via McpAppFrame).
|
|
577
|
+
*/
|
|
578
|
+
export function formatToolCallContentForModel(result: ToolCallResult): string {
|
|
579
|
+
if (isToolCallContent(result)) {
|
|
580
|
+
return result.content.map(c => {
|
|
581
|
+
if (c.type === 'text') { return c.text; }
|
|
582
|
+
if (c.type === 'html') { return `[interactive app displayed to the user${c.title ? ': ' + c.title : ''}]`; }
|
|
583
|
+
if (c.type === 'error') { return c.data; }
|
|
584
|
+
return JSON.stringify(c);
|
|
585
|
+
}).join('\n');
|
|
586
|
+
}
|
|
587
|
+
if (typeof result === 'string') { return result; }
|
|
588
|
+
return JSON.stringify(result);
|
|
589
|
+
}
|
|
590
|
+
|
|
563
591
|
export type ToolCallResult = undefined | object | string | ToolCallContent;
|
|
564
592
|
export interface ToolCall {
|
|
565
593
|
id?: string;
|
|
@@ -703,8 +731,10 @@ export interface FrontendLanguageModelRegistry extends LanguageModelRegistry {
|
|
|
703
731
|
|
|
704
732
|
@injectable()
|
|
705
733
|
export class DefaultLanguageModelRegistryImpl implements LanguageModelRegistry {
|
|
706
|
-
|
|
707
|
-
|
|
734
|
+
|
|
735
|
+
@inject(ILogger) @named('ai-core:DefaultLanguageModelRegistryImpl')
|
|
736
|
+
protected readonly logger: ILogger;
|
|
737
|
+
|
|
708
738
|
@inject(ContributionProvider) @named(LanguageModelProvider)
|
|
709
739
|
protected readonly languageModelContributions: ContributionProvider<LanguageModelProvider>;
|
|
710
740
|
|
|
@@ -735,7 +765,7 @@ export class DefaultLanguageModelRegistryImpl implements LanguageModelRegistry {
|
|
|
735
765
|
addLanguageModels(models: LanguageModel[]): void {
|
|
736
766
|
models.forEach(model => {
|
|
737
767
|
if (this.languageModels.find(lm => lm.id === model.id)) {
|
|
738
|
-
|
|
768
|
+
this.logger.warn(`Tried to add already existing language model with id ${model.id}. The new model will be ignored.`);
|
|
739
769
|
return;
|
|
740
770
|
}
|
|
741
771
|
this.languageModels.push(model);
|
|
@@ -762,7 +792,7 @@ export class DefaultLanguageModelRegistryImpl implements LanguageModelRegistry {
|
|
|
762
792
|
this.languageModels.splice(index, 1);
|
|
763
793
|
this.changeEmitter.fire({ models: this.languageModels });
|
|
764
794
|
} else {
|
|
765
|
-
|
|
795
|
+
this.logger.warn(`Language model with id ${id} was requested to be removed, however it does not exist`);
|
|
766
796
|
}
|
|
767
797
|
});
|
|
768
798
|
}
|
|
@@ -669,3 +669,46 @@ describe('CustomAgentDescription', () => {
|
|
|
669
669
|
});
|
|
670
670
|
});
|
|
671
671
|
});
|
|
672
|
+
|
|
673
|
+
describe('PromptService variable arguments', () => {
|
|
674
|
+
let promptService: PromptService;
|
|
675
|
+
|
|
676
|
+
beforeEach(() => {
|
|
677
|
+
const container = new Container();
|
|
678
|
+
container.bind<PromptService>(PromptService).to(PromptServiceImpl).inSingletonScope();
|
|
679
|
+
|
|
680
|
+
const variableService = new DefaultAIVariableService({ getContributions: () => [] });
|
|
681
|
+
(variableService as unknown as Record<string, unknown>)['logger'] = sinon.createStubInstance(Logger);
|
|
682
|
+
// A resolver that echoes what it was given, so that the test asserts on the split of the
|
|
683
|
+
// reference into variable name and argument rather than on any resolver behaviour.
|
|
684
|
+
const fileVariable = { id: 'file', name: 'file', description: 'Echoes its argument' };
|
|
685
|
+
variableService.registerResolver(fileVariable, {
|
|
686
|
+
canResolve: () => 100,
|
|
687
|
+
resolve: async request => ({ variable: fileVariable, value: `arg=${request.arg}` })
|
|
688
|
+
});
|
|
689
|
+
container.bind<AIVariableService>(AIVariableService).toConstantValue(variableService);
|
|
690
|
+
container.bind<ILogger>(ILogger).toConstantValue(new MockLogger);
|
|
691
|
+
|
|
692
|
+
promptService = container.get<PromptService>(PromptService);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('keeps the whole argument of a variable reference whose argument contains further colons', async () => {
|
|
696
|
+
// Regression test: the reference used to be split with `split(':', 2)`, which truncates, so a
|
|
697
|
+
// Windows path lost everything from its drive-letter colon onwards.
|
|
698
|
+
promptService.addBuiltInPromptFragment({ id: 'windows-path', template: 'Read {{file:C:\\some\\path}}' });
|
|
699
|
+
const prompt = await promptService.getResolvedPromptFragment('windows-path');
|
|
700
|
+
expect(prompt?.text).to.equal('Read arg=C:\\some\\path');
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('keeps a single-colon argument intact', async () => {
|
|
704
|
+
promptService.addBuiltInPromptFragment({ id: 'simple-arg', template: 'Read {{file:workspace/path/name.ext}}' });
|
|
705
|
+
const prompt = await promptService.getResolvedPromptFragment('simple-arg');
|
|
706
|
+
expect(prompt?.text).to.equal('Read arg=workspace/path/name.ext');
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it('resolves a reference without an argument with an undefined argument', async () => {
|
|
710
|
+
promptService.addBuiltInPromptFragment({ id: 'no-arg', template: 'Read {{file}}' });
|
|
711
|
+
const prompt = await promptService.getResolvedPromptFragment('no-arg');
|
|
712
|
+
expect(prompt?.text).to.equal('Read arg=undefined');
|
|
713
|
+
});
|
|
714
|
+
});
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { Event, Emitter, URI, ILogger, DisposableCollection } from '@theia/core';
|
|
18
|
-
import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import { inject, injectable, optional, postConstruct, named } from '@theia/core/shared/inversify';
|
|
19
19
|
import { AIVariableArg, AIVariableContext, AIVariableService, createAIResolveVariableCache, ResolvedAIVariable } from './variable-service';
|
|
20
20
|
import { ToolInvocationRegistry } from './tool-invocation-registry';
|
|
21
21
|
import { toolRequestToPromptText } from './language-model-util';
|
|
@@ -607,7 +607,7 @@ export interface PromptService {
|
|
|
607
607
|
|
|
608
608
|
@injectable()
|
|
609
609
|
export class PromptServiceImpl implements PromptService {
|
|
610
|
-
@inject(ILogger)
|
|
610
|
+
@inject(ILogger) @named('ai-core:PromptServiceImpl')
|
|
611
611
|
protected readonly logger: ILogger;
|
|
612
612
|
|
|
613
613
|
@inject(AISettingsService) @optional()
|
|
@@ -983,10 +983,12 @@ export class PromptServiceImpl implements PromptService {
|
|
|
983
983
|
let variableName = variableAndArg;
|
|
984
984
|
let argument: string | undefined;
|
|
985
985
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
986
|
+
// First colon only, keeping the whole remainder: `split(':', 2)` truncates instead, which
|
|
987
|
+
// mangles every argument containing a colon, e.g. `{{file:C:\some\path}}`.
|
|
988
|
+
const separatorIndex = variableAndArg.indexOf(':');
|
|
989
|
+
if (separatorIndex >= 0) {
|
|
990
|
+
variableName = variableAndArg.substring(0, separatorIndex);
|
|
991
|
+
argument = variableAndArg.substring(separatorIndex + 1);
|
|
990
992
|
}
|
|
991
993
|
|
|
992
994
|
let replacementValue: string;
|
package/src/common/skill.spec.ts
CHANGED
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
import { expect } from 'chai';
|
|
18
18
|
import {
|
|
19
19
|
SKILL_FILE_NAME,
|
|
20
|
+
Skill,
|
|
20
21
|
SkillDescription,
|
|
22
|
+
SkillDirectoryEntry,
|
|
23
|
+
combineSkillDirectories,
|
|
21
24
|
isValidSkillName,
|
|
22
25
|
validateSkillDescription
|
|
23
26
|
} from './skill';
|
|
@@ -218,5 +221,101 @@ describe('Skill Types', () => {
|
|
|
218
221
|
const errors = validateSkillDescription(description, 'my-skill');
|
|
219
222
|
expect(errors).to.be.empty;
|
|
220
223
|
});
|
|
224
|
+
|
|
225
|
+
it('does not require the frontmatter name of an owned skill to carry its qualifier', () => {
|
|
226
|
+
// Qualification is layered on top of the name; it never changes what an artifact has to
|
|
227
|
+
// write into its SKILL.md, which would change the artifact's content hash.
|
|
228
|
+
const description: SkillDescription = {
|
|
229
|
+
name: 'query-builder',
|
|
230
|
+
description: 'Build SQL'
|
|
231
|
+
};
|
|
232
|
+
expect(validateSkillDescription(description, 'query-builder')).to.be.empty;
|
|
233
|
+
expect(Skill.qualifyName(description.name, 'bq')).to.equal('bq:query-builder');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('Skill.qualifyName', () => {
|
|
238
|
+
it('returns the plain name when there is no qualifier', () => {
|
|
239
|
+
expect(Skill.qualifyName('code-review')).to.equal('code-review');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('prefixes the name with the qualifier', () => {
|
|
243
|
+
expect(Skill.qualifyName('query-builder', 'bigquery-data-analytics')).to.equal('bigquery-data-analytics:query-builder');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('Skill.qualifierOf', () => {
|
|
248
|
+
const skill = (name: string, qualifiedName: string): Skill =>
|
|
249
|
+
({ name, description: 'd', location: '/skills/' + name + '/SKILL.md', qualifiedName });
|
|
250
|
+
|
|
251
|
+
it('returns undefined for a skill from a built-in root', () => {
|
|
252
|
+
expect(Skill.qualifierOf(skill('code-review', 'code-review'))).to.be.undefined;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('returns the qualifier of a contributed skill', () => {
|
|
256
|
+
expect(Skill.qualifierOf(skill('query-builder', 'bq:query-builder'))).to.equal('bq');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('round-trips with qualifyName', () => {
|
|
260
|
+
const qualifiedName = Skill.qualifyName('query-builder', 'io.github.acme_tools');
|
|
261
|
+
expect(Skill.qualifierOf(skill('query-builder', qualifiedName))).to.equal('io.github.acme_tools');
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('combineSkillDirectories with plugin directories', () => {
|
|
266
|
+
const pluginEntry: SkillDirectoryEntry = {
|
|
267
|
+
path: '/home/user/.agents/plugins/io.example.bq/skills',
|
|
268
|
+
tier: 'plugin',
|
|
269
|
+
qualifier: 'bq'
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
it('appends plugin directories after every user-controlled tier', () => {
|
|
273
|
+
const result = combineSkillDirectories(
|
|
274
|
+
['/workspace/.prompts/skills'],
|
|
275
|
+
['/custom/skills'],
|
|
276
|
+
['/home/user/.agents/skills'],
|
|
277
|
+
[pluginEntry]
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
expect(result).to.deep.equal([
|
|
281
|
+
{ path: '/workspace/.prompts/skills', tier: 'workspace' },
|
|
282
|
+
{ path: '/custom/skills', tier: 'configured' },
|
|
283
|
+
{ path: '/home/user/.agents/skills', tier: 'default' },
|
|
284
|
+
pluginEntry
|
|
285
|
+
]);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('keeps the qualifier of a plugin directory', () => {
|
|
289
|
+
const result = combineSkillDirectories([], [], [], [pluginEntry]);
|
|
290
|
+
expect(result[0].qualifier).to.equal('bq');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('drops a plugin directory whose path a user-controlled tier already claimed, so the user wins the collision', () => {
|
|
294
|
+
const result = combineSkillDirectories(
|
|
295
|
+
[],
|
|
296
|
+
[pluginEntry.path],
|
|
297
|
+
[],
|
|
298
|
+
[pluginEntry]
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
expect(result).to.deep.equal([
|
|
302
|
+
{ path: pluginEntry.path, tier: 'configured' }
|
|
303
|
+
]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('deduplicates two plugin directories with the same path, keeping the first', () => {
|
|
307
|
+
const second: SkillDirectoryEntry = {
|
|
308
|
+
...pluginEntry,
|
|
309
|
+
qualifier: 'other'
|
|
310
|
+
};
|
|
311
|
+
const result = combineSkillDirectories([], [], [], [pluginEntry, second]);
|
|
312
|
+
expect(result).to.deep.equal([pluginEntry]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('behaves exactly as before when no plugin directories are passed', () => {
|
|
316
|
+
expect(combineSkillDirectories(['/workspace/skills'], [], [])).to.deep.equal([
|
|
317
|
+
{ path: '/workspace/skills', tier: 'workspace' }
|
|
318
|
+
]);
|
|
319
|
+
});
|
|
221
320
|
});
|
|
222
321
|
});
|
package/src/common/skill.ts
CHANGED
|
@@ -87,6 +87,32 @@ export namespace SkillDescription {
|
|
|
87
87
|
export interface Skill extends SkillDescription {
|
|
88
88
|
/** Absolute file path to the SKILL.md file */
|
|
89
89
|
location: string;
|
|
90
|
+
/**
|
|
91
|
+
* What the runtime and the model address this skill by: `name` for a built-in root,
|
|
92
|
+
* `<qualifier>:<name>` for a contributed one. Unlike `name` this is unique across all discovered
|
|
93
|
+
* skills, which is why it keys the registry.
|
|
94
|
+
*/
|
|
95
|
+
qualifiedName: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export namespace Skill {
|
|
99
|
+
/**
|
|
100
|
+
* Never changes what is written in `SKILL.md` - the frontmatter `name` must still equal the
|
|
101
|
+
* skill's directory name; qualification is layered on top.
|
|
102
|
+
*/
|
|
103
|
+
export function qualifyName(name: string, qualifier?: string): string {
|
|
104
|
+
return qualifier ? `${qualifier}:${name}` : name;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The qualifier of {@link Skill.qualifiedName}, or `undefined` for a skill from a built-in root.
|
|
109
|
+
* Unambiguous because neither part can contain a colon: a skill name is validated as lowercase
|
|
110
|
+
* kebab-case, and a qualifier has to be a single path segment.
|
|
111
|
+
*/
|
|
112
|
+
export function qualifierOf(skill: Skill): string | undefined {
|
|
113
|
+
const separatorIndex = skill.qualifiedName.indexOf(':');
|
|
114
|
+
return separatorIndex < 0 ? undefined : skill.qualifiedName.substring(0, separatorIndex);
|
|
115
|
+
}
|
|
90
116
|
}
|
|
91
117
|
|
|
92
118
|
/**
|
|
@@ -144,10 +170,8 @@ export function parseSkillFile(content: string): { metadata: SkillDescription |
|
|
|
144
170
|
return { metadata, content: body };
|
|
145
171
|
}
|
|
146
172
|
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
*/
|
|
150
|
-
export type SkillDirectoryTier = 'workspace' | 'configured' | 'default';
|
|
173
|
+
/** Provenance tier of a skill directory, used to dispatch tier-specific processing. */
|
|
174
|
+
export type SkillDirectoryTier = 'workspace' | 'configured' | 'default' | 'plugin';
|
|
151
175
|
|
|
152
176
|
/**
|
|
153
177
|
* A skill directory paired with the tier it originates from.
|
|
@@ -157,32 +181,43 @@ export interface SkillDirectoryEntry {
|
|
|
157
181
|
path: string;
|
|
158
182
|
/** Tier the directory belongs to */
|
|
159
183
|
tier: SkillDirectoryTier;
|
|
184
|
+
/**
|
|
185
|
+
* Prefixes the names of skills beneath this root, as `<qualifier>:<name>`. Skills are addressed
|
|
186
|
+
* by one global name, so two artifacts shipping the same skill name could not otherwise coexist -
|
|
187
|
+
* and renaming the directory instead would change the artifact's content hash. Must be a single
|
|
188
|
+
* path segment, so that it can be told from the skill name it is joined to.
|
|
189
|
+
*/
|
|
190
|
+
qualifier?: string;
|
|
160
191
|
}
|
|
161
192
|
|
|
162
193
|
/**
|
|
163
|
-
* Combines skill directories with proper priority ordering and provenance
|
|
164
|
-
*
|
|
194
|
+
* Combines skill directories with proper priority ordering and provenance: workspace, then
|
|
195
|
+
* configured, then defaults, then installed artifacts - so a directory the user controls always wins.
|
|
165
196
|
* First occurrence of a path wins on duplicates; later occurrences (regardless of tier) are dropped.
|
|
166
197
|
*/
|
|
167
198
|
export function combineSkillDirectories(
|
|
168
199
|
workspaceSkillsDirs: string[],
|
|
169
200
|
configuredDirectories: string[],
|
|
170
|
-
defaultSkillsDirs: string[]
|
|
201
|
+
defaultSkillsDirs: string[],
|
|
202
|
+
pluginSkillsDirs: SkillDirectoryEntry[] = []
|
|
171
203
|
): SkillDirectoryEntry[] {
|
|
172
204
|
const seen = new Set<string>();
|
|
173
205
|
const result: SkillDirectoryEntry[] = [];
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
206
|
+
const candidates: SkillDirectoryEntry[] = [
|
|
207
|
+
...toSkillDirectoryEntries(workspaceSkillsDirs, 'workspace'),
|
|
208
|
+
...toSkillDirectoryEntries(configuredDirectories, 'configured'),
|
|
209
|
+
...toSkillDirectoryEntries(defaultSkillsDirs, 'default'),
|
|
210
|
+
...pluginSkillsDirs
|
|
178
211
|
];
|
|
179
|
-
for (const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
result.push({ path: dir, tier });
|
|
184
|
-
}
|
|
212
|
+
for (const candidate of candidates) {
|
|
213
|
+
if (!seen.has(candidate.path)) {
|
|
214
|
+
seen.add(candidate.path);
|
|
215
|
+
result.push(candidate);
|
|
185
216
|
}
|
|
186
217
|
}
|
|
187
218
|
return result;
|
|
188
219
|
}
|
|
220
|
+
|
|
221
|
+
function toSkillDirectoryEntries(dirs: string[], tier: SkillDirectoryTier): SkillDirectoryEntry[] {
|
|
222
|
+
return dirs.map(dir => ({ path: dir, tier }));
|
|
223
|
+
}
|
|
@@ -24,7 +24,8 @@ export namespace TodayVariableArgs {
|
|
|
24
24
|
|
|
25
25
|
export const TODAY_VARIABLE: AIVariable = {
|
|
26
26
|
id: 'today-provider',
|
|
27
|
-
description: nls.localize('theia/ai/core/todayVariable/
|
|
27
|
+
description: nls.localize('theia/ai/core/todayVariable/dateDescription',
|
|
28
|
+
'The current date. Resolves to a human-readable date string by default, or to ISO 8601 or Unix seconds via the format argument.'),
|
|
28
29
|
name: 'today',
|
|
29
30
|
args: [
|
|
30
31
|
{
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { inject, injectable, named, postConstruct, interfaces } from '@theia/core/shared/inversify';
|
|
18
18
|
import { ToolRequest } from './language-model';
|
|
19
|
-
import { ContributionProvider, Emitter, Event } from '@theia/core';
|
|
19
|
+
import { ContributionProvider, Emitter, Event, ILogger } from '@theia/core';
|
|
20
20
|
|
|
21
21
|
export const ToolInvocationRegistry = Symbol('ToolInvocationRegistry');
|
|
22
22
|
|
|
@@ -99,6 +99,9 @@ export class ToolInvocationRegistryImpl implements ToolInvocationRegistry {
|
|
|
99
99
|
@named(ToolProvider)
|
|
100
100
|
private providers: ContributionProvider<ToolProvider>;
|
|
101
101
|
|
|
102
|
+
@inject(ILogger) @named('ai-core:ToolInvocationRegistryImpl')
|
|
103
|
+
protected readonly logger: ILogger;
|
|
104
|
+
|
|
102
105
|
@postConstruct()
|
|
103
106
|
init(): void {
|
|
104
107
|
this.providers.getContributions().forEach(provider => {
|
|
@@ -135,7 +138,7 @@ export class ToolInvocationRegistryImpl implements ToolInvocationRegistry {
|
|
|
135
138
|
|
|
136
139
|
registerTool(tool: ToolRequest): void {
|
|
137
140
|
if (this.tools.has(tool.id)) {
|
|
138
|
-
|
|
141
|
+
this.logger.warn(`Function with id ${tool.id} is already registered.`);
|
|
139
142
|
} else {
|
|
140
143
|
this.tools.set(tool.id, tool);
|
|
141
144
|
this.onDidChangeEmitter.fire();
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
17
|
+
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
18
18
|
import { CancellationToken, CancellationTokenSource, ILogger, generateUuid } from '@theia/core';
|
|
19
19
|
import {
|
|
20
20
|
LanguageModelMetaData,
|
|
@@ -53,8 +53,8 @@ export class LanguageModelFrontendDelegateImpl implements LanguageModelFrontendD
|
|
|
53
53
|
@inject(LanguageModelRegistry)
|
|
54
54
|
private registry: LanguageModelRegistry;
|
|
55
55
|
|
|
56
|
-
@inject(ILogger)
|
|
57
|
-
|
|
56
|
+
@inject(ILogger) @named('ai-core:LanguageModelFrontendDelegateImpl')
|
|
57
|
+
protected readonly logger: ILogger;
|
|
58
58
|
|
|
59
59
|
private frontendDelegateClient: LanguageModelDelegateClient;
|
|
60
60
|
private requestCancellationTokenMap: Map<string, CancellationTokenSource> = new Map();
|