feat: implement Claude and OpenAI conversation naming service

- Add Claude and OpenAI ConversationNamingService with API integration
- Register Claude and OpenAI naming service in dependency injection
- Add model and URL constants for naming
This commit is contained in:
Andrew Beal 2025-10-19 00:58:30 +01:00
parent bf948a79b2
commit 6656f4c495
5 changed files with 120 additions and 8 deletions

View file

@ -1,9 +1,53 @@
import { Resolve } from "Services/DependencyService";
import { Services } from "Services/Services";
import type { IConversationNamingService } from "AIClasses/IConversationNamingService";
import type AIAgentPlugin from "main";
import { AIProviderURL, AIProviderModel } from "Enums/ApiProvider";
import { Role } from "Enums/Role";
import { NamePrompt } from "AIClasses/NamePrompt";
export class ClaudeConversationNamingService implements IConversationNamingService {
public async generateName(userPrompt: string, abortSignal?: AbortSignal): Promise<string> {
throw new Error("Method not implemented.");
private readonly apiKey: string;
public constructor() {
this.apiKey = Resolve<AIAgentPlugin>(Services.AIAgentPlugin).settings.apiKey;
}
public async generateName(userPrompt: string, abortSignal?: AbortSignal): Promise<string> {
const requestBody = {
model: AIProviderModel.ClaudeNamer,
max_tokens: 50,
system: NamePrompt,
messages: [{
role: Role.User,
content: userPrompt
}]
};
const response = await fetch(AIProviderURL.ClaudeNamer, {
method: 'POST',
headers: {
'x-api-key': this.apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify(requestBody),
signal: abortSignal
});
if (!response.ok) {
throw new Error(`Claude API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const generatedName = data.content?.[0]?.text;
if (!generatedName) {
throw new Error("Failed to generate conversation name");
}
return generatedName;
}
}

View file

@ -7,11 +7,11 @@ import { Role } from "Enums/Role";
import { NamePrompt } from "AIClasses/NamePrompt";
export class GeminiConversationNamingService implements IConversationNamingService {
private readonly apiKey: string;
private readonly plugin: AIAgentPlugin = Resolve<AIAgentPlugin>(Services.AIAgentPlugin);
public constructor() {
this.apiKey = this.plugin.settings.apiKey;
this.apiKey = Resolve<AIAgentPlugin>(Services.AIAgentPlugin).settings.apiKey;
}
public async generateName(userPrompt: string, abortSignal?: AbortSignal): Promise<string> {

View file

@ -0,0 +1,57 @@
import { Resolve } from "Services/DependencyService";
import { Services } from "Services/Services";
import type { IConversationNamingService } from "AIClasses/IConversationNamingService";
import type AIAgentPlugin from "main";
import { AIProviderURL, AIProviderModel } from "Enums/ApiProvider";
import { Role } from "Enums/Role";
import { NamePrompt } from "AIClasses/NamePrompt";
export class OpenAIConversationNamingService implements IConversationNamingService {
private readonly apiKey: string;
public constructor() {
this.apiKey = Resolve<AIAgentPlugin>(Services.AIAgentPlugin).settings.apiKey;
}
public async generateName(userPrompt: string, abortSignal?: AbortSignal): Promise<string> {
const requestBody = {
model: AIProviderModel.OpenAINamer,
max_tokens: 50,
messages: [
{
role: "system",
content: NamePrompt
},
{
role: Role.User,
content: userPrompt
}
]
};
const response = await fetch(AIProviderURL.OpenAINamer, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
signal: abortSignal
});
if (!response.ok) {
throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const generatedName = data.choices?.[0]?.message?.content;
if (!generatedName) {
throw new Error("Failed to generate conversation name");
}
return generatedName;
}
}

View file

@ -6,11 +6,20 @@ export enum AIProvider {
export enum AIProviderModel {
Claude = "claude-sonnet-4-5",
ClaudeNamer = "claude-haiku-4-5",
Gemini = "gemini-2.5-flash",
OpenAI = ""
GeminiNamer = "gemini-2.5-flash",
OpenAI = "gpt-5",
OpenAINamer = "gpt-5-nano"
}
export enum AIProviderURL {
Gemini = `https://generativelanguage.googleapis.com/v1beta/models/${AIProviderModel.Gemini}:streamGenerateContent?key=API_KEY&alt=sse`,
GeminiNamer = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=API_KEY"
ClaudeNamer = "https://api.anthropic.com/v1/messages",
Gemini = `https://generativelanguage.googleapis.com/v1beta/models/${AIProviderModel.GeminiNamer}:streamGenerateContent?key=API_KEY&alt=sse`,
GeminiNamer = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=API_KEY",
OpenAINamer = "https://api.openai.com/v1/chat/completions"
}

View file

@ -24,6 +24,7 @@ import { GeminiTokenService } from "AIClasses/Gemini/GeminiTokenService";
import { StatusBarService } from "./StatusBarService";
import { ClaudeTokenService } from "AIClasses/Claude/ClaudeTokenService";
import { OpenAITokenService } from "AIClasses/OpenAI/OpenAITokenService";
import { ClaudeConversationNamingService } from "AIClasses/Claude/ClaudeConversationNamingService";
export function RegisterDependencies(plugin: AIAgentPlugin) {
RegisterSingleton<AIAgentPlugin>(Services.AIAgentPlugin, plugin);
@ -50,6 +51,7 @@ export function RegisterDependencies(plugin: AIAgentPlugin) {
export function RegisterAiProvider(plugin: AIAgentPlugin) {
if (plugin.settings.apiProvider == AIProvider.Claude) {
RegisterSingleton<ITokenService>(Services.ITokenService, new ClaudeTokenService());
RegisterSingleton<IConversationNamingService>(Services.IConversationNamingService, new ClaudeConversationNamingService());
}
else if (plugin.settings.apiProvider == AIProvider.Gemini) {
RegisterSingleton<IAIClass>(Services.IAIClass, new Gemini());