@theia/ai-tool-sketchpad 1.74.0-next.53

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.
Files changed (43) hide show
  1. package/README.md +44 -0
  2. package/lib/browser/frontend-module.d.ts +5 -0
  3. package/lib/browser/frontend-module.d.ts.map +1 -0
  4. package/lib/browser/frontend-module.js +41 -0
  5. package/lib/browser/frontend-module.js.map +1 -0
  6. package/lib/browser/index.d.ts +4 -0
  7. package/lib/browser/index.d.ts.map +1 -0
  8. package/lib/browser/index.js +22 -0
  9. package/lib/browser/index.js.map +1 -0
  10. package/lib/browser/sketched-tool-contribution.d.ts +7 -0
  11. package/lib/browser/sketched-tool-contribution.d.ts.map +1 -0
  12. package/lib/browser/sketched-tool-contribution.js +42 -0
  13. package/lib/browser/sketched-tool-contribution.js.map +1 -0
  14. package/lib/browser/sketched-tool-service.d.ts +37 -0
  15. package/lib/browser/sketched-tool-service.d.ts.map +1 -0
  16. package/lib/browser/sketched-tool-service.js +266 -0
  17. package/lib/browser/sketched-tool-service.js.map +1 -0
  18. package/lib/browser/sketched-tool-widget.d.ts +23 -0
  19. package/lib/browser/sketched-tool-widget.d.ts.map +1 -0
  20. package/lib/browser/sketched-tool-widget.js +455 -0
  21. package/lib/browser/sketched-tool-widget.js.map +1 -0
  22. package/lib/common/index.d.ts +2 -0
  23. package/lib/common/index.d.ts.map +1 -0
  24. package/lib/common/index.js +20 -0
  25. package/lib/common/index.js.map +1 -0
  26. package/lib/common/sketched-tool.d.ts +51 -0
  27. package/lib/common/sketched-tool.d.ts.map +1 -0
  28. package/lib/common/sketched-tool.js +85 -0
  29. package/lib/common/sketched-tool.js.map +1 -0
  30. package/lib/package.spec.d.ts +1 -0
  31. package/lib/package.spec.d.ts.map +1 -0
  32. package/lib/package.spec.js +24 -0
  33. package/lib/package.spec.js.map +1 -0
  34. package/package.json +52 -0
  35. package/src/browser/frontend-module.ts +43 -0
  36. package/src/browser/index.ts +19 -0
  37. package/src/browser/sketched-tool-contribution.ts +37 -0
  38. package/src/browser/sketched-tool-service.ts +282 -0
  39. package/src/browser/sketched-tool-widget.tsx +707 -0
  40. package/src/browser/style/index.css +387 -0
  41. package/src/common/index.ts +17 -0
  42. package/src/common/sketched-tool.ts +118 -0
  43. package/src/package.spec.ts +25 -0
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2026 EclipseSource and others.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.SketchedToolService = exports.SKETCHED_TOOLS_PROVIDER_NAME = exports.SketchedToolDefinition = void 0;
19
+ const core_1 = require("@theia/core");
20
+ var SketchedToolDefinition;
21
+ (function (SketchedToolDefinition) {
22
+ function is(obj) {
23
+ // eslint-disable-next-line no-null/no-null
24
+ if (typeof obj !== 'object' || obj === null) {
25
+ return false;
26
+ }
27
+ const candidate = obj;
28
+ if (typeof candidate.id !== 'string' || typeof candidate.name !== 'string') {
29
+ return false;
30
+ }
31
+ if (typeof candidate.description !== 'string') {
32
+ return false;
33
+ }
34
+ if (!Array.isArray(candidate.parameters)) {
35
+ return false;
36
+ }
37
+ if (candidate.returnMode !== undefined && candidate.returnMode !== 'static' && candidate.returnMode !== 'askAtRuntime') {
38
+ return false;
39
+ }
40
+ // returnMode and staticReturn are optional here; normalize() fills in defaults.
41
+ if (candidate.staticReturn !== undefined && typeof candidate.staticReturn !== 'string') {
42
+ return false;
43
+ }
44
+ return true;
45
+ }
46
+ SketchedToolDefinition.is = is;
47
+ function normalize(obj) {
48
+ return {
49
+ ...obj,
50
+ returnMode: obj.returnMode ?? 'static',
51
+ staticReturn: obj.staticReturn ?? ''
52
+ };
53
+ }
54
+ SketchedToolDefinition.normalize = normalize;
55
+ /**
56
+ * Pattern accepted as a tool name by most LLM providers (e.g. OpenAI, Anthropic).
57
+ * The name is also used as the id under which the tool is registered, so it must be unique.
58
+ */
59
+ SketchedToolDefinition.NAME_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/;
60
+ /**
61
+ * Validates a tool name for use as a registered tool id.
62
+ *
63
+ * @param name the name to validate
64
+ * @param existingTools the currently defined tools, used to check for name collisions
65
+ * @param selfId the id of the tool being edited, so it is not compared against itself
66
+ * @returns a localized error message if the name is invalid, otherwise `undefined`
67
+ */
68
+ function validateName(name, existingTools, selfId) {
69
+ const trimmed = name.trim();
70
+ if (!trimmed) {
71
+ return core_1.nls.localize('theia/ai-tool-sketchpad/nameEmpty', 'Tool name must not be empty.');
72
+ }
73
+ if (!SketchedToolDefinition.NAME_PATTERN.test(trimmed)) {
74
+ return core_1.nls.localize('theia/ai-tool-sketchpad/nameInvalid', 'Tool name may only contain letters, digits, underscores and hyphens (1-128 characters).');
75
+ }
76
+ if (existingTools.some(tool => tool.id !== selfId && tool.name.trim() === trimmed)) {
77
+ return core_1.nls.localize('theia/ai-tool-sketchpad/nameDuplicate', 'A tool with this name already exists.');
78
+ }
79
+ return undefined;
80
+ }
81
+ SketchedToolDefinition.validateName = validateName;
82
+ })(SketchedToolDefinition || (exports.SketchedToolDefinition = SketchedToolDefinition = {}));
83
+ exports.SKETCHED_TOOLS_PROVIDER_NAME = 'ai-tool-sketchpad';
84
+ exports.SketchedToolService = Symbol('SketchedToolService');
85
+ //# sourceMappingURL=sketched-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sketched-tool.js","sourceRoot":"","sources":["../../src/common/sketched-tool.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,+CAA+C;AAC/C,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;;AAEhF,sCAAyC;AA4BzC,IAAiB,sBAAsB,CA8DtC;AA9DD,WAAiB,sBAAsB;IACnC,SAAgB,EAAE,CAAC,GAAY;QAC3B,2CAA2C;QAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAC1C,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,SAAS,GAAG,GAA8B,CAAC;QACjD,IAAI,OAAO,SAAS,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACzE,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,OAAO,SAAS,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC5C,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;YACvC,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,IAAI,SAAS,CAAC,UAAU,KAAK,QAAQ,IAAI,SAAS,CAAC,UAAU,KAAK,cAAc,EAAE,CAAC;YACrH,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,gFAAgF;QAChF,IAAI,SAAS,CAAC,YAAY,KAAK,SAAS,IAAI,OAAO,SAAS,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YACrF,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAvBe,yBAAE,KAuBjB,CAAA;IAED,SAAgB,SAAS,CAAC,GAA2B;QACjD,OAAO;YACH,GAAG,GAAG;YACN,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,QAAQ;YACtC,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,EAAE;SACvC,CAAC;IACN,CAAC;IANe,gCAAS,YAMxB,CAAA;IAED;;;OAGG;IACU,mCAAY,GAAG,wBAAwB,CAAC;IAErD;;;;;;;OAOG;IACH,SAAgB,YAAY,CAAC,IAAY,EAAE,aAAuC,EAAE,MAAe;QAC/F,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,UAAG,CAAC,QAAQ,CAAC,mCAAmC,EAAE,8BAA8B,CAAC,CAAC;QAC7F,CAAC;QACD,IAAI,CAAC,uBAAA,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,OAAO,UAAG,CAAC,QAAQ,CAAC,qCAAqC,EACrD,yFAAyF,CAAC,CAAC;QACnG,CAAC;QACD,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,OAAO,CAAC,EAAE,CAAC;YACjF,OAAO,UAAG,CAAC,QAAQ,CAAC,uCAAuC,EAAE,uCAAuC,CAAC,CAAC;QAC1G,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAbe,mCAAY,eAa3B,CAAA;AACL,CAAC,EA9DgB,sBAAsB,sCAAtB,sBAAsB,QA8DtC;AAEY,QAAA,4BAA4B,GAAG,mBAAmB,CAAC;AAEnD,QAAA,mBAAmB,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=package.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.spec.d.ts","sourceRoot":"","sources":["../src/package.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ /*
17
+ * This is a placeholder for tests that the extension package should implement
18
+ * but as yet does not.
19
+ * Please delete this file when a real test is implemented.
20
+ */
21
+ describe('ai-tool-sketchpad package', () => {
22
+ it('placeholder to enable mocha', () => true);
23
+ });
24
+ //# sourceMappingURL=package.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.spec.js","sourceRoot":"","sources":["../src/package.spec.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,+CAA+C;AAC/C,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;GAIG;AAEH,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@theia/ai-tool-sketchpad",
3
+ "version": "1.74.0-next.53+8e4e53136c6",
4
+ "description": "Theia - AI Tool Sketchpad for defining ad-hoc LLM tools",
5
+ "dependencies": {
6
+ "@theia/ai-core": "1.74.0-next.53+8e4e53136c6",
7
+ "@theia/core": "1.74.0-next.53+8e4e53136c6",
8
+ "@theia/filesystem": "1.74.0-next.53+8e4e53136c6",
9
+ "js-yaml": "^4.1.0",
10
+ "tslib": "^2.6.2"
11
+ },
12
+ "main": "lib/common",
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "theiaExtensions": [
17
+ {
18
+ "frontend": "lib/browser/frontend-module"
19
+ }
20
+ ],
21
+ "keywords": [
22
+ "theia-extension"
23
+ ],
24
+ "license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/eclipse-theia/theia.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/eclipse-theia/theia/issues"
31
+ },
32
+ "homepage": "https://github.com/eclipse-theia/theia",
33
+ "files": [
34
+ "lib",
35
+ "src"
36
+ ],
37
+ "scripts": {
38
+ "build": "theiaext build",
39
+ "clean": "theiaext clean",
40
+ "compile": "theiaext compile",
41
+ "lint": "theiaext lint",
42
+ "test": "theiaext test",
43
+ "watch": "theiaext watch"
44
+ },
45
+ "devDependencies": {
46
+ "@theia/ext-scripts": "1.73.0"
47
+ },
48
+ "nyc": {
49
+ "extends": "../../configs/nyc.json"
50
+ },
51
+ "gitHead": "8e4e53136c69cec1ef61747c7269f9a935b76574"
52
+ }
@@ -0,0 +1,43 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import '../../src/browser/style/index.css';
18
+
19
+ import { ContainerModule } from '@theia/core/shared/inversify';
20
+ import { FrontendApplicationContribution, WidgetFactory, bindViewContribution } from '@theia/core/lib/browser';
21
+ import { SketchedToolService } from '../common';
22
+ import { SketchedToolServiceImpl } from './sketched-tool-service';
23
+ import { SketchedToolWidget } from './sketched-tool-widget';
24
+ import { SketchedToolViewContribution } from './sketched-tool-contribution';
25
+
26
+ export default new ContainerModule(bind => {
27
+ // Service
28
+ bind(SketchedToolServiceImpl).toSelf().inSingletonScope();
29
+ bind(SketchedToolService).toService(SketchedToolServiceImpl);
30
+ bind(FrontendApplicationContribution).toService(SketchedToolServiceImpl);
31
+
32
+ // Widget
33
+ bind(SketchedToolWidget).toSelf();
34
+ bind(WidgetFactory)
35
+ .toDynamicValue(ctx => ({
36
+ id: SketchedToolWidget.ID,
37
+ createWidget: () => ctx.container.get(SketchedToolWidget)
38
+ }))
39
+ .inSingletonScope();
40
+
41
+ // View contribution
42
+ bindViewContribution(bind, SketchedToolViewContribution);
43
+ });
@@ -0,0 +1,19 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ export * from './sketched-tool-service';
18
+ export * from './sketched-tool-widget';
19
+ export * from './sketched-tool-contribution';
@@ -0,0 +1,37 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { injectable } from '@theia/core/shared/inversify';
18
+ import { AIViewContribution } from '@theia/ai-core/lib/browser';
19
+ import { SketchedToolWidget } from './sketched-tool-widget';
20
+
21
+ export const SKETCHED_TOOL_TOGGLE_COMMAND_ID = 'aiToolSketchpad:toggle';
22
+
23
+ @injectable()
24
+ export class SketchedToolViewContribution extends AIViewContribution<SketchedToolWidget> {
25
+
26
+ constructor() {
27
+ super({
28
+ widgetId: SketchedToolWidget.ID,
29
+ widgetName: SketchedToolWidget.LABEL,
30
+ defaultWidgetOptions: {
31
+ area: 'main',
32
+ rank: 200
33
+ },
34
+ toggleCommandId: SKETCHED_TOOL_TOGGLE_COMMAND_ID
35
+ });
36
+ }
37
+ }
@@ -0,0 +1,282 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { DisposableCollection, Emitter, Event, nls, URI } from '@theia/core';
18
+ import { FrontendApplicationContribution, QuickInputService } from '@theia/core/lib/browser';
19
+ import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
20
+ import { BinaryBuffer } from '@theia/core/lib/common/buffer';
21
+ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
22
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
23
+ import { FileChangesEvent } from '@theia/filesystem/lib/common/files';
24
+ import {
25
+ ToolInvocationRegistry,
26
+ ToolRequest,
27
+ ToolRequestParameters,
28
+ ToolRequestParameterProperty
29
+ } from '@theia/ai-core';
30
+ import { dump, load } from 'js-yaml';
31
+ import {
32
+ SketchedToolDefinition,
33
+ SketchedToolParameterDefinition,
34
+ SketchedToolService,
35
+ SKETCHED_TOOLS_PROVIDER_NAME
36
+ } from '../common';
37
+
38
+ const SKETCHED_TOOLS_FILENAME = 'sketchedTools.yml';
39
+
40
+ @injectable()
41
+ export class SketchedToolServiceImpl implements SketchedToolService, FrontendApplicationContribution {
42
+
43
+ @inject(FileService)
44
+ protected readonly fileService: FileService;
45
+
46
+ @inject(EnvVariablesServer)
47
+ protected readonly envVariablesServer: EnvVariablesServer;
48
+
49
+ @inject(ToolInvocationRegistry)
50
+ protected readonly toolInvocationRegistry: ToolInvocationRegistry;
51
+
52
+ @inject(QuickInputService)
53
+ protected readonly quickInputService: QuickInputService;
54
+
55
+ protected tools: SketchedToolDefinition[] = [];
56
+ protected fileUri: URI | undefined;
57
+ protected readonly toDispose = new DisposableCollection();
58
+ protected loading = false;
59
+ /** The content we last wrote to disk, used to ignore file-watch events triggered by our own writes. */
60
+ protected lastPersistedContent: string | undefined;
61
+
62
+ protected readonly onDidChangeSketchedToolsEmitter = new Emitter<void>();
63
+ readonly onDidChangeSketchedTools: Event<void> = this.onDidChangeSketchedToolsEmitter.event;
64
+
65
+ @postConstruct()
66
+ protected init(): void {
67
+ this.toDispose.push(this.onDidChangeSketchedToolsEmitter);
68
+ }
69
+
70
+ async onStart(): Promise<void> {
71
+ this.fileUri = await this.resolveFileUri();
72
+ await this.loadFromDisk();
73
+ this.watchFile();
74
+ }
75
+
76
+ getSketchedTools(): SketchedToolDefinition[] {
77
+ return [...this.tools];
78
+ }
79
+
80
+ async addSketchedTool(tool: SketchedToolDefinition): Promise<void> {
81
+ this.tools.push(tool);
82
+ this.registerAllTools();
83
+ await this.persistToDisk();
84
+ this.onDidChangeSketchedToolsEmitter.fire();
85
+ }
86
+
87
+ async updateSketchedTool(tool: SketchedToolDefinition): Promise<void> {
88
+ const index = this.tools.findIndex(t => t.id === tool.id);
89
+ if (index >= 0) {
90
+ this.tools[index] = tool;
91
+ } else {
92
+ this.tools.push(tool);
93
+ }
94
+ this.registerAllTools();
95
+ await this.persistToDisk();
96
+ this.onDidChangeSketchedToolsEmitter.fire();
97
+ }
98
+
99
+ async removeSketchedTool(toolId: string): Promise<void> {
100
+ this.tools = this.tools.filter(t => t.id !== toolId);
101
+ this.registerAllTools();
102
+ await this.persistToDisk();
103
+ this.onDidChangeSketchedToolsEmitter.fire();
104
+ }
105
+
106
+ protected async resolveFileUri(): Promise<URI> {
107
+ const configDirUri = await this.envVariablesServer.getConfigDirUri();
108
+ return new URI(configDirUri).resolve('prompt-templates').resolve(SKETCHED_TOOLS_FILENAME);
109
+ }
110
+
111
+ protected async loadFromDisk(): Promise<void> {
112
+ if (!this.fileUri) {
113
+ return;
114
+ }
115
+ this.loading = true;
116
+ try {
117
+ const exists = await this.fileService.exists(this.fileUri);
118
+ if (!exists) {
119
+ this.tools = [];
120
+ this.registerAllTools();
121
+ return;
122
+ }
123
+ const fileContent = await this.fileService.read(this.fileUri, { encoding: 'utf-8' });
124
+ if (fileContent.value === this.lastPersistedContent) {
125
+ // This change was triggered by our own write; the in-memory state is already up to date.
126
+ return;
127
+ }
128
+ const doc = load(fileContent.value);
129
+ if (Array.isArray(doc)) {
130
+ // Skip invalid entries rather than discarding all tools if one entry is malformed.
131
+ const valid = doc.filter(entry => SketchedToolDefinition.is(entry)) as SketchedToolDefinition[];
132
+ const skipped = doc.length - valid.length;
133
+ if (skipped > 0) {
134
+ console.debug(`Skipped ${skipped} invalid entr${skipped === 1 ? 'y' : 'ies'} in ${SKETCHED_TOOLS_FILENAME}.`);
135
+ }
136
+ this.tools = valid.map(SketchedToolDefinition.normalize);
137
+ } else {
138
+ console.debug(`Invalid ${SKETCHED_TOOLS_FILENAME} content, ignoring.`);
139
+ this.tools = [];
140
+ }
141
+ this.registerAllTools();
142
+ this.onDidChangeSketchedToolsEmitter.fire();
143
+ } catch (e) {
144
+ console.debug(`Error loading ${SKETCHED_TOOLS_FILENAME}: ${e.message}`, e);
145
+ } finally {
146
+ this.loading = false;
147
+ }
148
+ }
149
+
150
+ onStop(): void {
151
+ this.toDispose.dispose();
152
+ }
153
+
154
+ protected async persistToDisk(): Promise<void> {
155
+ if (!this.fileUri) {
156
+ return;
157
+ }
158
+ try {
159
+ const yamlContent = dump(this.tools, { lineWidth: -1 });
160
+ const buffer = BinaryBuffer.fromString(yamlContent);
161
+ if (await this.fileService.exists(this.fileUri)) {
162
+ await this.fileService.writeFile(this.fileUri, buffer);
163
+ } else {
164
+ await this.fileService.createFile(this.fileUri, buffer);
165
+ }
166
+ this.lastPersistedContent = yamlContent;
167
+ } catch (e) {
168
+ console.error(`Error persisting ${SKETCHED_TOOLS_FILENAME}: ${e.message}`, e);
169
+ }
170
+ }
171
+
172
+ protected watchFile(): void {
173
+ if (!this.fileUri) {
174
+ return;
175
+ }
176
+ const parentUri = this.fileUri.parent;
177
+ this.toDispose.push(this.fileService.watch(parentUri));
178
+ const listener = this.fileService.onDidFilesChange((event: FileChangesEvent) => {
179
+ if (this.fileUri && event.contains(this.fileUri) && !this.loading) {
180
+ this.loadFromDisk();
181
+ }
182
+ });
183
+ this.toDispose.push(listener);
184
+ }
185
+
186
+ protected registerAllTools(): void {
187
+ this.toolInvocationRegistry.unregisterAllTools(SKETCHED_TOOLS_PROVIDER_NAME);
188
+ // Guard against invalid or duplicate names in hand-edited YAML: the name is the registered id,
189
+ // so an invalid name is uncallable and a duplicate would collide. Register the first valid one.
190
+ const registeredNames = new Set<string>();
191
+ for (const def of this.tools) {
192
+ const name = def.name.trim();
193
+ if (!SketchedToolDefinition.NAME_PATTERN.test(name)) {
194
+ console.warn(`Skipping sketched tool "${def.name}": name is empty or contains invalid characters.`);
195
+ continue;
196
+ }
197
+ if (registeredNames.has(name)) {
198
+ console.warn(`Skipping sketched tool "${name}": another tool with the same name is already registered.`);
199
+ continue;
200
+ }
201
+ registeredNames.add(name);
202
+ this.toolInvocationRegistry.registerTool(this.toToolRequest(def, name));
203
+ }
204
+ }
205
+
206
+ protected toToolRequest(def: SketchedToolDefinition, name: string): ToolRequest {
207
+ const parameters = this.convertParameters(def.parameters);
208
+ return {
209
+ // The readable name doubles as the id so chat completions insert a meaningful reference;
210
+ // the internal UUID (def.id) remains the persistence key, keeping renames lossless.
211
+ id: name,
212
+ name,
213
+ description: def.description,
214
+ providerName: SKETCHED_TOOLS_PROVIDER_NAME,
215
+ parameters,
216
+ handler: async (argString: string) => {
217
+ if (def.returnMode === 'askAtRuntime') {
218
+ return this.askUserForReturnValue(name, argString);
219
+ }
220
+ return def.staticReturn;
221
+ }
222
+ };
223
+ }
224
+
225
+ protected async askUserForReturnValue(toolName: string, argString: string): Promise<string> {
226
+ const result = await this.quickInputService.input({
227
+ prompt: nls.localize('theia/ai-tool-sketchpad/askReturnPrompt',
228
+ 'Tool "{0}" was called with: {1}', toolName, argString),
229
+ placeHolder: nls.localize('theia/ai-tool-sketchpad/askReturnPlaceholder',
230
+ 'Enter the return value for this tool call...')
231
+ });
232
+ return result ?? '';
233
+ }
234
+
235
+ protected convertParameters(params: SketchedToolParameterDefinition[]): ToolRequestParameters {
236
+ const properties: Record<string, ToolRequestParameterProperty> = {};
237
+ const required: string[] = [];
238
+
239
+ for (const param of params) {
240
+ properties[param.name] = this.convertParameterProperty(param);
241
+ if (param.required) {
242
+ required.push(param.name);
243
+ }
244
+ }
245
+
246
+ return {
247
+ type: 'object',
248
+ properties,
249
+ ...(required.length > 0 ? { required } : {})
250
+ };
251
+ }
252
+
253
+ protected convertParameterProperty(param: SketchedToolParameterDefinition): ToolRequestParameterProperty {
254
+ const prop: ToolRequestParameterProperty = {
255
+ type: param.type,
256
+ description: param.description
257
+ };
258
+
259
+ if (param.type === 'object' && param.properties && param.properties.length > 0) {
260
+ const nested = this.convertParameters(param.properties);
261
+ prop.properties = nested.properties;
262
+ if (nested.required && nested.required.length > 0) {
263
+ prop.required = nested.required;
264
+ }
265
+ }
266
+
267
+ if (param.type === 'array' && param.itemType) {
268
+ if (param.itemType === 'object' && param.itemProperties && param.itemProperties.length > 0) {
269
+ const nested = this.convertParameters(param.itemProperties);
270
+ prop.items = {
271
+ type: 'object',
272
+ properties: nested.properties,
273
+ ...(nested.required && nested.required.length > 0 ? { required: nested.required } : {})
274
+ };
275
+ } else {
276
+ prop.items = { type: param.itemType };
277
+ }
278
+ }
279
+
280
+ return prop;
281
+ }
282
+ }