Add Claude and OpenAI provider support with token services

- Add Claude and OpenAI to AIProviderModel enum
- Implement ClaudeTokenService and OpenAITokenService
- Update service registration to handle new providers
- Install @anthropic-ai/sdk and gpt-tokenizer dependencies
- Add error handling for invalid provider selection
This commit is contained in:
Andrew Beal 2025-10-19 00:35:26 +01:00
parent 023913b0b7
commit bf948a79b2
8 changed files with 114 additions and 3 deletions

View file

@ -0,0 +1,9 @@
import type { IConversationNamingService } from "AIClasses/IConversationNamingService";
export class ClaudeConversationNamingService implements IConversationNamingService {
public async generateName(userPrompt: string, abortSignal?: AbortSignal): Promise<string> {
throw new Error("Method not implemented.");
}
}

View file

@ -0,0 +1,30 @@
import type { ITokenService } from "AIClasses/ITokenService";
import Anthropic from '@anthropic-ai/sdk'
import type AIAgentPlugin from "main";
import { Resolve } from "Services/DependencyService";
import { Services } from "Services/Services";
import { AIProviderModel } from "Enums/ApiProvider";
import { Role } from "Enums/Role";
export class ClaudeTokenService implements ITokenService {
private ai: Anthropic;
public constructor() {
this.ai = new Anthropic({
apiKey: Resolve<AIAgentPlugin>(Services.AIAgentPlugin).settings.apiKey
})
}
public async countTokens(input: string): Promise<number> {
// to maintain the convenience of the interface we just submit the entire input as one message
const result = await this.ai.messages.countTokens({
model: AIProviderModel.Claude,
messages: [
{ role: Role.User, content: input }
]
})
return result.input_tokens;
}
}

View file

@ -11,7 +11,7 @@ export class GeminiTokenService implements ITokenService {
public constructor() {
this.ai = new GoogleGenAI({
apiKey: Resolve<AIAgentPlugin>(Services.AIAgentPlugin).settings.apiKey
apiKey: Resolve<AIAgentPlugin>(Services.AIAgentPlugin).settings.apiKey
});
}

View file

@ -0,0 +1,10 @@
import type { ITokenService } from "AIClasses/ITokenService";
import { countTokens } from 'gpt-tokenizer'
export class OpenAITokenService implements ITokenService {
public async countTokens(input: string): Promise<number> {
return countTokens(input);
}
}

View file

@ -5,7 +5,9 @@ export enum AIProvider {
};
export enum AIProviderModel {
Gemini = "gemini-2.5-flash"
Claude = "claude-sonnet-4-5",
Gemini = "gemini-2.5-flash",
OpenAI = ""
}
export enum AIProviderURL {

View file

@ -22,6 +22,8 @@ import { VaultService } from "./VaultService";
import type { ITokenService } from "AIClasses/ITokenService";
import { GeminiTokenService } from "AIClasses/Gemini/GeminiTokenService";
import { StatusBarService } from "./StatusBarService";
import { ClaudeTokenService } from "AIClasses/Claude/ClaudeTokenService";
import { OpenAITokenService } from "AIClasses/OpenAI/OpenAITokenService";
export function RegisterDependencies(plugin: AIAgentPlugin) {
RegisterSingleton<AIAgentPlugin>(Services.AIAgentPlugin, plugin);
@ -46,11 +48,20 @@ export function RegisterDependencies(plugin: AIAgentPlugin) {
}
export function RegisterAiProvider(plugin: AIAgentPlugin) {
if (plugin.settings.apiProvider == AIProvider.Gemini) {
if (plugin.settings.apiProvider == AIProvider.Claude) {
RegisterSingleton<ITokenService>(Services.ITokenService, new ClaudeTokenService());
}
else if (plugin.settings.apiProvider == AIProvider.Gemini) {
RegisterSingleton<IAIClass>(Services.IAIClass, new Gemini());
RegisterSingleton<ITokenService>(Services.ITokenService, new GeminiTokenService());
RegisterSingleton<IConversationNamingService>(Services.IConversationNamingService, new GeminiConversationNamingService());
}
else if (plugin.settings.apiProvider == AIProvider.OpenAI) {
RegisterSingleton<ITokenService>(Services.ITokenService, new OpenAITokenService());
}
else { // should be impossible to land here
throw new Error("Invalid Provider Selection!");
}
Resolve<ChatService>(Services.ChatService).resolveAIProvider();
Resolve<ConversationNamingService>(Services.ConversationNamingService).resolveNamingProvider();
}

47
package-lock.json generated
View file

@ -9,10 +9,12 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.67.0",
"@google/genai": "^1.17.0",
"@shikijs/rehype": "^3.12.2",
"core-js": "^3.45.1",
"express": "^5.1.0",
"gpt-tokenizer": "^3.2.0",
"highlight.js": "^11.11.1",
"katex": "^0.16.22",
"lowlight": "^3.3.0",
@ -53,6 +55,26 @@
"typescript": "~5.0.0"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.67.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.67.0.tgz",
"integrity": "sha512-Buxbf6jYJ+pPtfCgXe1pcFtZmdXPrbdqhBjiscFt9irS1G0hCsmR/fPA+DwKTk4GPjqeNnnCYNecXH6uVZ4G/A==",
"license": "MIT",
"dependencies": {
"json-schema-to-ts": "^3.1.1"
},
"bin": {
"anthropic-ai-sdk": "bin/cli"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
@ -2772,6 +2794,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gpt-tokenizer": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/gpt-tokenizer/-/gpt-tokenizer-3.2.0.tgz",
"integrity": "sha512-QRRhzJIHcGbbdzhMGNCHhF+98RVaBaVD5+NYgyPOamqRBEqG5yN9p5j4udnY5FJnkesjdUPsikG/DO4OfJdhpQ==",
"license": "MIT"
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@ -3459,6 +3487,19 @@
"license": "MIT",
"peer": true
},
"node_modules/json-schema-to-ts": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
"integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",
"ts-algebra": "^2.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -6786,6 +6827,12 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/ts-algebra": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
"integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
"license": "MIT"
},
"node_modules/ts-dedent": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",

View file

@ -28,10 +28,12 @@
"typescript": "~5.0.0"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.67.0",
"@google/genai": "^1.17.0",
"@shikijs/rehype": "^3.12.2",
"core-js": "^3.45.1",
"express": "^5.1.0",
"gpt-tokenizer": "^3.2.0",
"highlight.js": "^11.11.1",
"katex": "^0.16.22",
"lowlight": "^3.3.0",