mirror of
https://github.com/BinaryBeastMaster/chat-relay.git
synced 2026-04-28 03:39:27 +00:00
Add Kimi K2 provider
This commit is contained in:
parent
1cfc01411e
commit
6ec302ea69
6 changed files with 180 additions and 2 deletions
|
|
@ -86,12 +86,14 @@ Interacts with Gemini, AI Studio, ChatGPT, and Claude UIs. Injects chat, capture
|
|||
- [`AIStudioProvider`](extension/providers/aistudio.js)
|
||||
- [`ChatGptProvider`](extension/providers/chatgpt.js)
|
||||
- [`ClaudeProvider`](extension/providers/claude.js)
|
||||
- [`KimiK2Provider`](extension/providers/kimi_k2.js)
|
||||
|
||||
**Supported Chat Interfaces:**
|
||||
- Gemini (`gemini.google.com`)
|
||||
- AI Studio (`aistudio.google.com`)
|
||||
- ChatGPT (`chatgpt.com`)
|
||||
- Claude (`claude.ai`)
|
||||
- Kimi K2 (`k2.kimi.ai`)
|
||||
|
||||
ChatGPT is a trademark of OpenAI. Gemini and AI Studio are trademarks of Google. Claude is a trademark of Anthropic. This project is not affiliated with, endorsed by, or sponsored by OpenAI, Google, or Anthropic.
|
||||
|
||||
|
|
|
|||
34
docs/provider-kimi-k2.md
Normal file
34
docs/provider-kimi-k2.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Kimi K2 Provider Architecture
|
||||
|
||||
This document describes the `KimiK2Provider` used by the extension to automate the Kimi K2 chat interface.
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Overview
|
||||
|
||||
`KimiK2Provider` is modeled after the existing AI Studio and ChatGPT providers. It sends messages and captures responses from `k2.kimi.ai` using DOM based monitoring by default with an optional debugger fallback.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configurable Options
|
||||
|
||||
```js
|
||||
this.captureMethod = 'dom'; // or 'debugger'
|
||||
this.debuggerUrlPattern = '*k2.kimi.ai/api/chat*';
|
||||
this.includeThinkingInMessage = false;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📌 DOM Selectors
|
||||
|
||||
- Input field: `textarea, div[contenteditable="true"]`
|
||||
- Send button: `button[type="submit"], button.send-btn`
|
||||
- Response blocks: `.message.ai, .chat-message.ai`
|
||||
- Typing indicator: `.typing, .loading`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Summary
|
||||
|
||||
The provider offers a lightweight integration with Kimi K2 using the same structure as the other providers. It can capture responses via DOM observation or via Chrome debugger when configured.
|
||||
|
|
@ -36,7 +36,7 @@ let lastSuccessfullyProcessedMessageText = null; // Text of the last message suc
|
|||
const pendingRequestDetails = new Map(); // Stores { text: string } for active requests, keyed by requestId
|
||||
|
||||
// Supported domains for chat interfaces
|
||||
const supportedDomains = ['gemini.google.com', 'aistudio.google.com', 'chatgpt.com', 'claude.ai'];
|
||||
const supportedDomains = ['gemini.google.com', 'aistudio.google.com', 'chatgpt.com', 'claude.ai', 'k2.kimi.ai'];
|
||||
|
||||
// ===== DEBUGGER RELATED GLOBALS =====
|
||||
const BG_LOG_PREFIX = '[BG DEBUGGER]';
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"*://*.chatgpt.com/*",
|
||||
"*://*.aistudio.com/*",
|
||||
"*://*.claude.ai/*",
|
||||
"*://*.k2.kimi.ai/*",
|
||||
"ws://localhost:*/"
|
||||
],
|
||||
"background": {
|
||||
|
|
@ -58,6 +59,15 @@
|
|||
"content.js"
|
||||
],
|
||||
"run_at": "document_idle"
|
||||
},
|
||||
{
|
||||
"matches": ["*://k2.kimi.ai/*"],
|
||||
"js": [
|
||||
"providers/provider-utils.js",
|
||||
"providers/kimi_k2.js",
|
||||
"content.js"
|
||||
],
|
||||
"run_at": "document_idle"
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ const providers = {
|
|||
'gemini': window.geminiProvider,
|
||||
'aistudio': window.aiStudioProvider,
|
||||
'chatgpt': window.chatgptProvider,
|
||||
'claude': window.claudeProvider
|
||||
'claude': window.claudeProvider,
|
||||
'kimi_k2': window.kimiK2Provider
|
||||
};
|
||||
|
||||
// Get a provider by ID
|
||||
|
|
@ -40,6 +41,8 @@ function detectProvider(url) {
|
|||
return providers.chatgpt;
|
||||
} else if (url.includes('claude.ai')) {
|
||||
return providers.claude;
|
||||
} else if (url.includes('k2.kimi.ai')) {
|
||||
return providers.kimi_k2;
|
||||
}
|
||||
|
||||
// Default to aistudio if we can't detect
|
||||
|
|
|
|||
129
extension/providers/kimi_k2.js
Normal file
129
extension/providers/kimi_k2.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Chat Relay: Relay for AI Chat Interfaces
|
||||
* Copyright (C) 2025 Jamison Moore
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
// AI Chat Relay - Kimi K2 Provider
|
||||
|
||||
class KimiK2Provider {
|
||||
constructor() {
|
||||
this.name = 'KimiK2Provider';
|
||||
this.supportedDomains = ['k2.kimi.ai'];
|
||||
|
||||
// --- START OF CONFIGURABLE PROPERTIES ---
|
||||
this.captureMethod = 'dom'; // DOM capture by default
|
||||
this.debuggerUrlPattern = '*k2.kimi.ai/api/chat*'; // Placeholder
|
||||
this.includeThinkingInMessage = false;
|
||||
// --- END OF CONFIGURABLE PROPERTIES ---
|
||||
|
||||
this.inputSelector = 'textarea, div[contenteditable="true"]';
|
||||
this.sendButtonSelector = 'button[type="submit"], button.send-btn';
|
||||
this.responseSelector = '.message.ai, .chat-message.ai';
|
||||
this.thinkingIndicatorSelector = '.typing, .loading';
|
||||
|
||||
this.lastSentMessage = '';
|
||||
this.pendingResponseCallbacks = new Map();
|
||||
this.requestAccumulators = new Map();
|
||||
}
|
||||
|
||||
async sendChatMessage(text) {
|
||||
console.log(`[${this.name}] sendChatMessage called:`, text);
|
||||
const inputElement = document.querySelector(this.inputSelector);
|
||||
const sendButton = document.querySelector(this.sendButtonSelector);
|
||||
if (!inputElement || !sendButton) {
|
||||
console.error(`[${this.name}] Missing input (${this.inputSelector}) or send button (${this.sendButtonSelector})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.lastSentMessage = text;
|
||||
|
||||
if (inputElement.tagName.toLowerCase() === 'div' && inputElement.contentEditable === 'true') {
|
||||
inputElement.focus();
|
||||
inputElement.innerHTML = '';
|
||||
inputElement.textContent = text;
|
||||
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
} else {
|
||||
inputElement.value = text;
|
||||
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
inputElement.focus();
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
|
||||
if (!sendButton.disabled && sendButton.getAttribute('aria-disabled') !== 'true') {
|
||||
sendButton.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
console.warn(`[${this.name}] Send button disabled.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
captureResponse(element) {
|
||||
if (!element) return { found: false, text: '' };
|
||||
let text = element.textContent.trim();
|
||||
if (!text || text === this.lastSentMessage) return { found: false, text: '' };
|
||||
return { found: true, text };
|
||||
}
|
||||
|
||||
initiateResponseCapture(requestId, callback) {
|
||||
this.pendingResponseCallbacks.set(requestId, callback);
|
||||
if (this.captureMethod === 'dom') {
|
||||
console.log(`[${this.name}] DOM capture active for ${requestId}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleDebuggerData(requestId, rawData, isFinal) {
|
||||
const cb = this.pendingResponseCallbacks.get(requestId);
|
||||
if (!cb) return;
|
||||
|
||||
const parsed = this.parseDebuggerResponse(rawData);
|
||||
if (parsed.text || isFinal) {
|
||||
cb(requestId, parsed.text, isFinal);
|
||||
}
|
||||
if (isFinal) {
|
||||
this.pendingResponseCallbacks.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
parseDebuggerResponse(raw) {
|
||||
if (!raw) return { text: '', isFinalResponse: false };
|
||||
if (raw.includes('[DONE]')) {
|
||||
const clean = raw.replace('[DONE]', '').trim();
|
||||
return { text: clean, isFinalResponse: true };
|
||||
}
|
||||
return { text: raw.trim(), isFinalResponse: false };
|
||||
}
|
||||
|
||||
getStreamingApiPatterns() {
|
||||
if (this.captureMethod === 'debugger' && this.debuggerUrlPattern) {
|
||||
return [{ urlPattern: this.debuggerUrlPattern, requestStage: 'Response' }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
shouldSkipResponseMonitoring() {
|
||||
return this.captureMethod === 'debugger';
|
||||
}
|
||||
}
|
||||
|
||||
(function() {
|
||||
if (window.providerUtils) {
|
||||
const providerInstance = new KimiK2Provider();
|
||||
window.providerUtils.registerProvider(providerInstance.name, providerInstance.supportedDomains, providerInstance);
|
||||
} else {
|
||||
console.error('ProviderUtils not found. KimiK2Provider cannot be registered.');
|
||||
}
|
||||
})();
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue