hai-api 1.1.5 → 1.1.6
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/index.d.ts +129 -0
- package/{dist/index.js → index.js} +51 -17
- package/package.json +37 -21
- package/README.md +0 -14
- package/dist/index.d.ts +0 -101
- package/src/index.ts +0 -377
- package/tsconfig.json +0 -15
package/index.d.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export type Model = 'deepseek-chat' | 'deepseek-reasoner' | 'gemini' | 'gemini-2.0-flash' | 'gemini-2.0-flash-exp' | 'gemini-1.5-pro' | 'gemini-1.5-flash' | 'auto';
|
|
2
|
+
export type GeminiMode = 'normal' | 'canvas' | 'learning' | 'lærehjelp';
|
|
3
|
+
export interface HSeekResponse {
|
|
4
|
+
text: string;
|
|
5
|
+
thinking_text: string;
|
|
6
|
+
search_text: string;
|
|
7
|
+
session_id: string | null;
|
|
8
|
+
parent_message_id: string | null;
|
|
9
|
+
latency_ms: number | null;
|
|
10
|
+
is_done: boolean;
|
|
11
|
+
toDict(): Record<string, unknown>;
|
|
12
|
+
readonly process_text: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ChatOptions {
|
|
15
|
+
/** Custom session identifier for conversation threading. */
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
/** Enable chain-of-thought reasoning (DeepSeek only). */
|
|
18
|
+
thinking?: boolean;
|
|
19
|
+
/** Enable real-time web search grounding (DeepSeek only). */
|
|
20
|
+
search?: boolean;
|
|
21
|
+
/** Raw backend chat session ID (manual persistence). Overrides sessionId. */
|
|
22
|
+
chatSessionId?: string;
|
|
23
|
+
/** Raw backend parent message ID (manual persistence). */
|
|
24
|
+
parentMessageId?: string;
|
|
25
|
+
/** Persistent system-level instruction/personality. */
|
|
26
|
+
system?: string;
|
|
27
|
+
/** Response creativity: 0.0 = precise/deterministic, 1.0 = creative/random. */
|
|
28
|
+
temperature?: number;
|
|
29
|
+
/** Maximum tokens in response. */
|
|
30
|
+
maxTokens?: number;
|
|
31
|
+
/** Force response to be valid JSON object. */
|
|
32
|
+
jsonMode?: boolean;
|
|
33
|
+
/** Auto-translate responses to target language (e.g., "Arabic", "French"). */
|
|
34
|
+
translate?: string;
|
|
35
|
+
/** Expert programmer mode with code-focused responses. */
|
|
36
|
+
codeMode?: boolean;
|
|
37
|
+
/** Ask AI to internally verify answer before responding. */
|
|
38
|
+
verify?: boolean;
|
|
39
|
+
/** Append 3-bullet summary at end of response. */
|
|
40
|
+
summarize?: boolean;
|
|
41
|
+
/** Include thinking process in text output (when thinking=true). */
|
|
42
|
+
includeThought?: boolean;
|
|
43
|
+
/** Image URL for vision analysis. */
|
|
44
|
+
image?: string;
|
|
45
|
+
/** YouTube URL - SDK fetches transcript and includes as context. */
|
|
46
|
+
youtube?: string;
|
|
47
|
+
/** Number of past exchanges for context. 0 = none, -1 = all. Default: 10. */
|
|
48
|
+
memoryDepth?: number;
|
|
49
|
+
/** Auto-retries on network failure. Default: 3. */
|
|
50
|
+
retries?: number;
|
|
51
|
+
/** Model to use. 'auto' routes based on features. */
|
|
52
|
+
model?: Model;
|
|
53
|
+
/** Gemini-specific mode. */
|
|
54
|
+
geminiMode?: GeminiMode;
|
|
55
|
+
/** Callback when streaming completes. */
|
|
56
|
+
onDone?: (response: HSeekResponse) => void;
|
|
57
|
+
/** Pass arbitrary extra parameters to backend. */
|
|
58
|
+
extra?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
interface SessionData {
|
|
61
|
+
chatSessionId: string | null;
|
|
62
|
+
parentMessageId: string | null;
|
|
63
|
+
history: Array<{
|
|
64
|
+
role: string;
|
|
65
|
+
content: string;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* HSeekClient — Official client for the hAI Multi-AI Platform.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const ai = new HSeekClient();
|
|
74
|
+
*
|
|
75
|
+
* // Simple chat
|
|
76
|
+
* const res = await ai.chat("Hello!");
|
|
77
|
+
* console.log(res.text);
|
|
78
|
+
*
|
|
79
|
+
* // Streaming with thinking + search (DeepSeek)
|
|
80
|
+
* for await (const chunk of ai.streamChat("Complex problem", {
|
|
81
|
+
* thinking: true,
|
|
82
|
+
* search: true,
|
|
83
|
+
* sessionId: "user_123"
|
|
84
|
+
* })) {
|
|
85
|
+
* process.stdout.write(chunk.text);
|
|
86
|
+
* }
|
|
87
|
+
*
|
|
88
|
+
* // Gemini with canvas mode
|
|
89
|
+
* const geminiRes = await ai.chat("Draw a chart", {
|
|
90
|
+
* model: "gemini",
|
|
91
|
+
* geminiMode: "canvas"
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare class HSeekClient {
|
|
96
|
+
private apiKey;
|
|
97
|
+
private baseUrl;
|
|
98
|
+
private memory;
|
|
99
|
+
/**
|
|
100
|
+
* @param apiKey - Your hAI API key (or set MY_SECRET_API_KEY in .env)
|
|
101
|
+
* @param baseUrl - API endpoint. Default: https://api.ai.hussain.ink
|
|
102
|
+
*/
|
|
103
|
+
constructor(apiKey?: string, baseUrl?: string);
|
|
104
|
+
/**
|
|
105
|
+
* Create a client pre-configured for local development.
|
|
106
|
+
* @param apiKey - Optional API key (defaults to MY_SECRET_API_KEY)
|
|
107
|
+
* @returns Client pointing to http://localhost:5000
|
|
108
|
+
*/
|
|
109
|
+
static local(apiKey?: string): HSeekClient;
|
|
110
|
+
private buildPayload;
|
|
111
|
+
private createResponse;
|
|
112
|
+
/**
|
|
113
|
+
* Stream a response in real-time, yielding chunks as they arrive.
|
|
114
|
+
*/
|
|
115
|
+
streamChat(prompt: string, options?: ChatOptions): AsyncGenerator<HSeekResponse, void, unknown>;
|
|
116
|
+
/**
|
|
117
|
+
* Send a message and get the full response at once (non-streaming).
|
|
118
|
+
*/
|
|
119
|
+
chat(prompt: string, options?: ChatOptions): Promise<HSeekResponse>;
|
|
120
|
+
/**
|
|
121
|
+
* Export the full conversation history for a session.
|
|
122
|
+
*/
|
|
123
|
+
exportHistory(sessionId: string): SessionData;
|
|
124
|
+
/**
|
|
125
|
+
* Clear the conversation memory for a session.
|
|
126
|
+
*/
|
|
127
|
+
clearHistory(sessionId: string): void;
|
|
128
|
+
}
|
|
129
|
+
export {};
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.HSeekClient = void 0;
|
|
37
37
|
const dotenv = __importStar(require("dotenv"));
|
|
38
38
|
dotenv.config();
|
|
39
39
|
// ========================
|
|
@@ -86,7 +86,6 @@ function extractYouTubeId(url) {
|
|
|
86
86
|
return match ? match[1] : null;
|
|
87
87
|
}
|
|
88
88
|
async function fetchYouTubeTranscript(videoId) {
|
|
89
|
-
// Fetches transcript via a free public API
|
|
90
89
|
const apiUrl = `https://yt-transcript-api.vercel.app/api/transcript?videoId=${videoId}`;
|
|
91
90
|
try {
|
|
92
91
|
const res = await fetch(apiUrl);
|
|
@@ -100,23 +99,44 @@ async function fetchYouTubeTranscript(videoId) {
|
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
101
|
// ========================
|
|
103
|
-
// Main
|
|
102
|
+
// Main HSeekClient
|
|
104
103
|
// ========================
|
|
105
104
|
/**
|
|
106
|
-
* HSeekClient —
|
|
105
|
+
* HSeekClient — Official client for the hAI Multi-AI Platform.
|
|
107
106
|
*
|
|
108
107
|
* @example
|
|
109
108
|
* ```typescript
|
|
110
109
|
* const ai = new HSeekClient();
|
|
111
|
-
*
|
|
110
|
+
*
|
|
111
|
+
* // Simple chat
|
|
112
|
+
* const res = await ai.chat("Hello!");
|
|
112
113
|
* console.log(res.text);
|
|
114
|
+
*
|
|
115
|
+
* // Streaming with thinking + search (DeepSeek)
|
|
116
|
+
* for await (const chunk of ai.streamChat("Complex problem", {
|
|
117
|
+
* thinking: true,
|
|
118
|
+
* search: true,
|
|
119
|
+
* sessionId: "user_123"
|
|
120
|
+
* })) {
|
|
121
|
+
* process.stdout.write(chunk.text);
|
|
122
|
+
* }
|
|
123
|
+
*
|
|
124
|
+
* // Gemini with canvas mode
|
|
125
|
+
* const geminiRes = await ai.chat("Draw a chart", {
|
|
126
|
+
* model: "gemini",
|
|
127
|
+
* geminiMode: "canvas"
|
|
128
|
+
* });
|
|
113
129
|
* ```
|
|
114
130
|
*/
|
|
115
|
-
class
|
|
131
|
+
class HSeekClient {
|
|
116
132
|
apiKey;
|
|
117
133
|
baseUrl;
|
|
118
134
|
memory;
|
|
119
|
-
|
|
135
|
+
/**
|
|
136
|
+
* @param apiKey - Your hAI API key (or set MY_SECRET_API_KEY in .env)
|
|
137
|
+
* @param baseUrl - API endpoint. Default: https://api.ai.hussain.ink
|
|
138
|
+
*/
|
|
139
|
+
constructor(apiKey, baseUrl = "https://api.ai.hussain.ink") {
|
|
120
140
|
this.apiKey = apiKey || process.env.MY_SECRET_API_KEY || '';
|
|
121
141
|
if (!this.apiKey) {
|
|
122
142
|
throw new Error("API Key is missing. Pass it directly or set MY_SECRET_API_KEY in .env");
|
|
@@ -125,14 +145,16 @@ class HAIClient {
|
|
|
125
145
|
this.memory = new MemoryStore();
|
|
126
146
|
}
|
|
127
147
|
/**
|
|
128
|
-
* Create a client pre-configured for local development
|
|
148
|
+
* Create a client pre-configured for local development.
|
|
149
|
+
* @param apiKey - Optional API key (defaults to MY_SECRET_API_KEY)
|
|
150
|
+
* @returns Client pointing to http://localhost:5000
|
|
129
151
|
*/
|
|
130
152
|
static local(apiKey) {
|
|
131
|
-
return new
|
|
153
|
+
return new HSeekClient(apiKey, "http://localhost:5000");
|
|
132
154
|
}
|
|
133
155
|
buildPayload(prompt, options) {
|
|
134
|
-
const { sessionId, thinking = false, search = false, chatSessionId, parentMessageId, system, temperature, maxTokens, jsonMode, translate, codeMode, verify, summarize, includeThought, image, memoryDepth = 10, } = options;
|
|
135
|
-
// Build messages array
|
|
156
|
+
const { sessionId, thinking = false, search = false, chatSessionId, parentMessageId, system, temperature, maxTokens, jsonMode, translate, codeMode, verify, summarize, includeThought, image, memoryDepth = 10, model, geminiMode, extra, } = options;
|
|
157
|
+
// Build messages array with history
|
|
136
158
|
const messages = [];
|
|
137
159
|
if (sessionId && memoryDepth !== 0) {
|
|
138
160
|
messages.push(...this.memory.getHistory(sessionId, memoryDepth));
|
|
@@ -147,7 +169,7 @@ class HAIClient {
|
|
|
147
169
|
thinking,
|
|
148
170
|
search,
|
|
149
171
|
};
|
|
150
|
-
//
|
|
172
|
+
// Standard feature flags
|
|
151
173
|
if (system)
|
|
152
174
|
payload.system = system;
|
|
153
175
|
if (temperature !== undefined)
|
|
@@ -166,7 +188,13 @@ class HAIClient {
|
|
|
166
188
|
payload.summarize = true;
|
|
167
189
|
if (includeThought)
|
|
168
190
|
payload.include_thought = true;
|
|
169
|
-
//
|
|
191
|
+
// Model selection
|
|
192
|
+
if (model)
|
|
193
|
+
payload.model = model;
|
|
194
|
+
// Gemini-specific mode
|
|
195
|
+
if (geminiMode)
|
|
196
|
+
payload.mode = geminiMode;
|
|
197
|
+
// Session management (raw IDs take priority)
|
|
170
198
|
if (chatSessionId) {
|
|
171
199
|
payload.chat_session_id = chatSessionId;
|
|
172
200
|
}
|
|
@@ -179,8 +207,10 @@ class HAIClient {
|
|
|
179
207
|
}
|
|
180
208
|
if (parentMessageId)
|
|
181
209
|
payload.parent_message_id = parentMessageId;
|
|
182
|
-
|
|
183
|
-
|
|
210
|
+
// Merge extra parameters
|
|
211
|
+
if (extra) {
|
|
212
|
+
Object.assign(payload, extra);
|
|
213
|
+
}
|
|
184
214
|
return payload;
|
|
185
215
|
}
|
|
186
216
|
createResponse() {
|
|
@@ -220,7 +250,7 @@ class HAIClient {
|
|
|
220
250
|
*/
|
|
221
251
|
async *streamChat(prompt, options = {}) {
|
|
222
252
|
const { sessionId, youtube, retries = 3, onDone } = options;
|
|
223
|
-
// Handle YouTube
|
|
253
|
+
// Handle YouTube transcript injection
|
|
224
254
|
let actualPrompt = prompt;
|
|
225
255
|
if (youtube) {
|
|
226
256
|
const videoId = extractYouTubeId(youtube);
|
|
@@ -287,6 +317,10 @@ class HAIClient {
|
|
|
287
317
|
currentResponse.search_text += delta.search_content;
|
|
288
318
|
yield currentResponse;
|
|
289
319
|
}
|
|
320
|
+
if (delta.image_url) {
|
|
321
|
+
// Image URL in delta (Nano Banana)
|
|
322
|
+
yield currentResponse;
|
|
323
|
+
}
|
|
290
324
|
}
|
|
291
325
|
else if (dataJson.object === 'chat.completion.meta') {
|
|
292
326
|
currentResponse.parent_message_id = dataJson.message_id;
|
|
@@ -333,4 +367,4 @@ class HAIClient {
|
|
|
333
367
|
this.memory.clear(sessionId);
|
|
334
368
|
}
|
|
335
369
|
}
|
|
336
|
-
exports.
|
|
370
|
+
exports.HSeekClient = HSeekClient;
|
package/package.json
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "hai-api",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "The official Node.js SDK for hAI
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "hai-api",
|
|
3
|
+
"version": "1.1.6",
|
|
4
|
+
"description": "The official Node.js SDK for hAI Multi-AI Platform.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"index.d.ts"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"author": "Hussain Alkhatib",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"dotenv": "^16.4.5"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.12.7",
|
|
22
|
+
"typescript": "^5.4.5"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/HussainAlkhatib/haijs.git"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"ai",
|
|
30
|
+
"deepseek",
|
|
31
|
+
"gemini",
|
|
32
|
+
"hAI",
|
|
33
|
+
"sdk",
|
|
34
|
+
"chat",
|
|
35
|
+
"streaming"
|
|
36
|
+
]
|
|
37
|
+
}
|
package/README.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# hai-api (Node.js SDK)
|
|
2
|
-
|
|
3
|
-
The official Node.js SDK for Hussain's Private AI Bridge (hAI). High-performance, type-safe integration for multi-AI orchestration.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install hai-api
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Features
|
|
12
|
-
- Native TypeScript support.
|
|
13
|
-
- Stream-ready API responses.
|
|
14
|
-
- Easy integration with hAI Multi-AI Server.
|
package/dist/index.d.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
export interface HAIResponse {
|
|
2
|
-
text: string;
|
|
3
|
-
thinking_text: string;
|
|
4
|
-
search_text: string;
|
|
5
|
-
session_id: string | null;
|
|
6
|
-
parent_message_id: string | null;
|
|
7
|
-
latency_ms: number | null;
|
|
8
|
-
is_done: boolean;
|
|
9
|
-
/** Export the response as a plain object */
|
|
10
|
-
toDict(): Record<string, unknown>;
|
|
11
|
-
readonly process_text: string;
|
|
12
|
-
}
|
|
13
|
-
export interface ChatOptions {
|
|
14
|
-
/** Used to identify the conversation thread. SDK auto-manages session IDs for you. */
|
|
15
|
-
sessionId?: string;
|
|
16
|
-
/** Enable chain-of-thought reasoning before answering. */
|
|
17
|
-
thinking?: boolean;
|
|
18
|
-
/** Enable real-time web search to ground the response. */
|
|
19
|
-
search?: boolean;
|
|
20
|
-
/** Raw backend chat session ID (for manual persistence). Overrides sessionId. */
|
|
21
|
-
chatSessionId?: string;
|
|
22
|
-
/** Raw backend parent message ID (for manual persistence). */
|
|
23
|
-
parentMessageId?: string;
|
|
24
|
-
/** Set a persistent system-level personality or instruction for the AI. */
|
|
25
|
-
system?: string;
|
|
26
|
-
/** Control response creativity: 0.0 = precise/deterministic, 1.0 = creative/random. */
|
|
27
|
-
temperature?: number;
|
|
28
|
-
/** Limit the maximum number of tokens in the response. */
|
|
29
|
-
maxTokens?: number;
|
|
30
|
-
/** Force the response to be a valid JSON object. */
|
|
31
|
-
jsonMode?: boolean;
|
|
32
|
-
/** Auto-translate all responses to this language (e.g., "Arabic", "French"). */
|
|
33
|
-
translate?: string;
|
|
34
|
-
/** Put the AI in expert senior programmer mode. */
|
|
35
|
-
codeMode?: boolean;
|
|
36
|
-
/** Ask the AI to internally verify its answer before responding. */
|
|
37
|
-
verify?: boolean;
|
|
38
|
-
/** Append a 3-bullet summary at the end of every response. */
|
|
39
|
-
summarize?: boolean;
|
|
40
|
-
/** Whether to include the AI's internal thought process in the text response (when thinking=true). */
|
|
41
|
-
includeThought?: boolean;
|
|
42
|
-
/** URL of an image to analyze alongside the prompt (Vision). */
|
|
43
|
-
image?: string;
|
|
44
|
-
/** URL of a YouTube video. SDK will fetch the transcript and analyze it. */
|
|
45
|
-
youtube?: string;
|
|
46
|
-
/** Number of past message exchanges to include for context. 0 = no history, -1 = all. Default: 10. */
|
|
47
|
-
memoryDepth?: number;
|
|
48
|
-
/** Number of times to automatically retry on network failure. Default: 3. */
|
|
49
|
-
retries?: number;
|
|
50
|
-
/** Model to use (e.g., 'deepseek', 'gemini-nano-banana'). */
|
|
51
|
-
model?: string;
|
|
52
|
-
/** Callback function called once streaming is complete. */
|
|
53
|
-
onDone?: (response: HAIResponse) => void;
|
|
54
|
-
}
|
|
55
|
-
interface SessionData {
|
|
56
|
-
chatSessionId: string | null;
|
|
57
|
-
parentMessageId: string | null;
|
|
58
|
-
history: Array<{
|
|
59
|
-
role: string;
|
|
60
|
-
content: string;
|
|
61
|
-
}>;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* HSeekClient — The official client for the hAI private AI.
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```typescript
|
|
68
|
-
* const ai = new HSeekClient();
|
|
69
|
-
* const res = await ai.chat("Hello!", { sessionId: "user_1", thinking: true });
|
|
70
|
-
* console.log(res.text);
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
export declare class HAIClient {
|
|
74
|
-
private apiKey;
|
|
75
|
-
private baseUrl;
|
|
76
|
-
private memory;
|
|
77
|
-
constructor(apiKey?: string, baseUrl?: string);
|
|
78
|
-
/**
|
|
79
|
-
* Create a client pre-configured for local development (http://localhost:5000).
|
|
80
|
-
*/
|
|
81
|
-
static local(apiKey?: string): HAIClient;
|
|
82
|
-
private buildPayload;
|
|
83
|
-
private createResponse;
|
|
84
|
-
/**
|
|
85
|
-
* Stream a response in real-time, yielding chunks as they arrive.
|
|
86
|
-
*/
|
|
87
|
-
streamChat(prompt: string, options?: ChatOptions): AsyncGenerator<HAIResponse, void, unknown>;
|
|
88
|
-
/**
|
|
89
|
-
* Send a message and get the full response at once (non-streaming).
|
|
90
|
-
*/
|
|
91
|
-
chat(prompt: string, options?: ChatOptions): Promise<HAIResponse>;
|
|
92
|
-
/**
|
|
93
|
-
* Export the full conversation history for a session.
|
|
94
|
-
*/
|
|
95
|
-
exportHistory(sessionId: string): SessionData;
|
|
96
|
-
/**
|
|
97
|
-
* Clear the conversation memory for a session.
|
|
98
|
-
*/
|
|
99
|
-
clearHistory(sessionId: string): void;
|
|
100
|
-
}
|
|
101
|
-
export {};
|
package/src/index.ts
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import * as dotenv from 'dotenv';
|
|
2
|
-
|
|
3
|
-
dotenv.config();
|
|
4
|
-
|
|
5
|
-
// ========================
|
|
6
|
-
// Types & Interfaces
|
|
7
|
-
// ========================
|
|
8
|
-
|
|
9
|
-
export interface HAIResponse {
|
|
10
|
-
text: string;
|
|
11
|
-
thinking_text: string;
|
|
12
|
-
search_text: string;
|
|
13
|
-
session_id: string | null;
|
|
14
|
-
parent_message_id: string | null;
|
|
15
|
-
latency_ms: number | null;
|
|
16
|
-
is_done: boolean;
|
|
17
|
-
/** Export the response as a plain object */
|
|
18
|
-
toDict(): Record<string, unknown>;
|
|
19
|
-
readonly process_text: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ChatOptions {
|
|
23
|
-
/** Used to identify the conversation thread. SDK auto-manages session IDs for you. */
|
|
24
|
-
sessionId?: string;
|
|
25
|
-
/** Enable chain-of-thought reasoning before answering. */
|
|
26
|
-
thinking?: boolean;
|
|
27
|
-
/** Enable real-time web search to ground the response. */
|
|
28
|
-
search?: boolean;
|
|
29
|
-
/** Raw backend chat session ID (for manual persistence). Overrides sessionId. */
|
|
30
|
-
chatSessionId?: string;
|
|
31
|
-
/** Raw backend parent message ID (for manual persistence). */
|
|
32
|
-
parentMessageId?: string;
|
|
33
|
-
/** Set a persistent system-level personality or instruction for the AI. */
|
|
34
|
-
system?: string;
|
|
35
|
-
/** Control response creativity: 0.0 = precise/deterministic, 1.0 = creative/random. */
|
|
36
|
-
temperature?: number;
|
|
37
|
-
/** Limit the maximum number of tokens in the response. */
|
|
38
|
-
maxTokens?: number;
|
|
39
|
-
/** Force the response to be a valid JSON object. */
|
|
40
|
-
jsonMode?: boolean;
|
|
41
|
-
/** Auto-translate all responses to this language (e.g., "Arabic", "French"). */
|
|
42
|
-
translate?: string;
|
|
43
|
-
/** Put the AI in expert senior programmer mode. */
|
|
44
|
-
codeMode?: boolean;
|
|
45
|
-
/** Ask the AI to internally verify its answer before responding. */
|
|
46
|
-
verify?: boolean;
|
|
47
|
-
/** Append a 3-bullet summary at the end of every response. */
|
|
48
|
-
summarize?: boolean;
|
|
49
|
-
/** Whether to include the AI's internal thought process in the text response (when thinking=true). */
|
|
50
|
-
includeThought?: boolean;
|
|
51
|
-
/** URL of an image to analyze alongside the prompt (Vision). */
|
|
52
|
-
image?: string;
|
|
53
|
-
/** URL of a YouTube video. SDK will fetch the transcript and analyze it. */
|
|
54
|
-
youtube?: string;
|
|
55
|
-
/** Number of past message exchanges to include for context. 0 = no history, -1 = all. Default: 10. */
|
|
56
|
-
memoryDepth?: number;
|
|
57
|
-
/** Number of times to automatically retry on network failure. Default: 3. */
|
|
58
|
-
retries?: number;
|
|
59
|
-
/** Model to use (e.g., 'deepseek', 'gemini-nano-banana'). */
|
|
60
|
-
model?: string;
|
|
61
|
-
/** Callback function called once streaming is complete. */
|
|
62
|
-
onDone?: (response: HAIResponse) => void;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
interface SessionData {
|
|
66
|
-
chatSessionId: string | null;
|
|
67
|
-
parentMessageId: string | null;
|
|
68
|
-
history: Array<{ role: string; content: string }>;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ========================
|
|
72
|
-
// Internal Memory Store
|
|
73
|
-
// ========================
|
|
74
|
-
class MemoryStore {
|
|
75
|
-
private sessions: Map<string, SessionData> = new Map();
|
|
76
|
-
|
|
77
|
-
getIds(sessionId?: string): { chatSessionId: string | null; parentMessageId: string | null } {
|
|
78
|
-
if (!sessionId) return { chatSessionId: null, parentMessageId: null };
|
|
79
|
-
const s = this.sessions.get(sessionId);
|
|
80
|
-
return { chatSessionId: s?.chatSessionId ?? null, parentMessageId: s?.parentMessageId ?? null };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
getHistory(sessionId: string, depth: number): Array<{ role: string; content: string }> {
|
|
84
|
-
const history = this.sessions.get(sessionId)?.history ?? [];
|
|
85
|
-
if (depth === 0) return [];
|
|
86
|
-
if (depth < 0) return history;
|
|
87
|
-
return history.slice(-(depth * 2));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
addToHistory(sessionId: string, role: string, content: string): void {
|
|
91
|
-
if (!this.sessions.has(sessionId)) {
|
|
92
|
-
this.sessions.set(sessionId, { chatSessionId: null, parentMessageId: null, history: [] });
|
|
93
|
-
}
|
|
94
|
-
this.sessions.get(sessionId)!.history.push({ role, content });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
saveIds(sessionId: string | undefined, chatSessionId: string | null, parentMessageId: string | null): void {
|
|
98
|
-
if (!sessionId || !chatSessionId || !parentMessageId) return;
|
|
99
|
-
if (!this.sessions.has(sessionId)) {
|
|
100
|
-
this.sessions.set(sessionId, { chatSessionId: null, parentMessageId: null, history: [] });
|
|
101
|
-
}
|
|
102
|
-
const s = this.sessions.get(sessionId)!;
|
|
103
|
-
s.chatSessionId = chatSessionId;
|
|
104
|
-
s.parentMessageId = parentMessageId;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export(sessionId: string): SessionData {
|
|
108
|
-
return this.sessions.get(sessionId) ?? { chatSessionId: null, parentMessageId: null, history: [] };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
clear(sessionId: string): void {
|
|
112
|
-
this.sessions.delete(sessionId);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ========================
|
|
117
|
-
// YouTube Helpers
|
|
118
|
-
// ========================
|
|
119
|
-
function extractYouTubeId(url: string): string | null {
|
|
120
|
-
const match = url.match(/(?:v=|youtu\.be\/|\/v\/|\/embed\/|\/shorts\/)([0-9A-Za-z_-]{11})/);
|
|
121
|
-
return match ? match[1] : null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function fetchYouTubeTranscript(videoId: string): Promise<string> {
|
|
125
|
-
// Fetches transcript via a free public API
|
|
126
|
-
const apiUrl = `https://yt-transcript-api.vercel.app/api/transcript?videoId=${videoId}`;
|
|
127
|
-
try {
|
|
128
|
-
const res = await fetch(apiUrl);
|
|
129
|
-
if (!res.ok) throw new Error(`Transcript API returned ${res.status}`);
|
|
130
|
-
const data = await res.json() as Array<{ text: string }>;
|
|
131
|
-
return data.map((d) => d.text).join(' ').substring(0, 15000);
|
|
132
|
-
} catch (e) {
|
|
133
|
-
throw new Error(`Failed to fetch YouTube transcript for ${videoId}: ${e}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ========================
|
|
138
|
-
// Main HAIClient
|
|
139
|
-
// ========================
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* HSeekClient — The official client for the hAI private AI.
|
|
143
|
-
*
|
|
144
|
-
* @example
|
|
145
|
-
* ```typescript
|
|
146
|
-
* const ai = new HSeekClient();
|
|
147
|
-
* const res = await ai.chat("Hello!", { sessionId: "user_1", thinking: true });
|
|
148
|
-
* console.log(res.text);
|
|
149
|
-
* ```
|
|
150
|
-
*/
|
|
151
|
-
export class HAIClient {
|
|
152
|
-
private apiKey: string;
|
|
153
|
-
private baseUrl: string;
|
|
154
|
-
private memory: MemoryStore;
|
|
155
|
-
|
|
156
|
-
constructor(apiKey?: string, baseUrl: string = "https://ai.hussain.ink") {
|
|
157
|
-
this.apiKey = apiKey || process.env.MY_SECRET_API_KEY || '';
|
|
158
|
-
if (!this.apiKey) {
|
|
159
|
-
throw new Error("API Key is missing. Pass it directly or set MY_SECRET_API_KEY in .env");
|
|
160
|
-
}
|
|
161
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
162
|
-
this.memory = new MemoryStore();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Create a client pre-configured for local development (http://localhost:5000).
|
|
167
|
-
*/
|
|
168
|
-
static local(apiKey?: string): HAIClient {
|
|
169
|
-
return new HAIClient(apiKey, "http://localhost:5000");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private buildPayload(prompt: string, options: ChatOptions): Record<string, unknown> {
|
|
173
|
-
const {
|
|
174
|
-
sessionId, thinking = false, search = false,
|
|
175
|
-
chatSessionId, parentMessageId,
|
|
176
|
-
system, temperature, maxTokens, jsonMode,
|
|
177
|
-
translate, codeMode, verify, summarize, includeThought,
|
|
178
|
-
image, memoryDepth = 10,
|
|
179
|
-
} = options;
|
|
180
|
-
|
|
181
|
-
// Build messages array
|
|
182
|
-
const messages: Array<{ role: string; content: unknown }> = [];
|
|
183
|
-
if (sessionId && memoryDepth !== 0) {
|
|
184
|
-
messages.push(...this.memory.getHistory(sessionId, memoryDepth));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const userContent = image
|
|
188
|
-
? [{ type: "image_url", image_url: { url: image } }, { type: "text", text: prompt }]
|
|
189
|
-
: prompt;
|
|
190
|
-
|
|
191
|
-
messages.push({ role: "user", content: userContent });
|
|
192
|
-
|
|
193
|
-
const payload: Record<string, unknown> = {
|
|
194
|
-
messages,
|
|
195
|
-
stream: true,
|
|
196
|
-
thinking,
|
|
197
|
-
search,
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// Optional feature flags
|
|
201
|
-
if (system) payload.system = system;
|
|
202
|
-
if (temperature !== undefined) payload.temperature = temperature;
|
|
203
|
-
if (maxTokens !== undefined) payload.max_tokens = maxTokens;
|
|
204
|
-
if (jsonMode) payload.json_mode = true;
|
|
205
|
-
if (translate) payload.translate = translate;
|
|
206
|
-
if (codeMode) payload.code_mode = true;
|
|
207
|
-
if (verify) payload.verify = true;
|
|
208
|
-
if (summarize) payload.summarize = true;
|
|
209
|
-
if (includeThought) payload.include_thought = true;
|
|
210
|
-
|
|
211
|
-
// Session management
|
|
212
|
-
if (chatSessionId) {
|
|
213
|
-
payload.chat_session_id = chatSessionId;
|
|
214
|
-
} else if (sessionId) {
|
|
215
|
-
const ids = this.memory.getIds(sessionId);
|
|
216
|
-
if (ids.chatSessionId) payload.chat_session_id = ids.chatSessionId;
|
|
217
|
-
if (ids.parentMessageId) payload.parent_message_id = ids.parentMessageId;
|
|
218
|
-
}
|
|
219
|
-
if (parentMessageId) payload.parent_message_id = parentMessageId;
|
|
220
|
-
if (options.model) payload.model = options.model;
|
|
221
|
-
|
|
222
|
-
return payload;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private createResponse(): HAIResponse {
|
|
226
|
-
const r: HAIResponse = {
|
|
227
|
-
text: "",
|
|
228
|
-
thinking_text: "",
|
|
229
|
-
search_text: "",
|
|
230
|
-
session_id: null,
|
|
231
|
-
parent_message_id: null,
|
|
232
|
-
latency_ms: null,
|
|
233
|
-
is_done: false,
|
|
234
|
-
toDict() {
|
|
235
|
-
return {
|
|
236
|
-
text: this.text,
|
|
237
|
-
thinking_text: this.thinking_text,
|
|
238
|
-
search_text: this.search_text,
|
|
239
|
-
session_id: this.session_id,
|
|
240
|
-
parent_message_id: this.parent_message_id,
|
|
241
|
-
latency_ms: this.latency_ms,
|
|
242
|
-
is_done: this.is_done
|
|
243
|
-
};
|
|
244
|
-
},
|
|
245
|
-
get process_text() {
|
|
246
|
-
let p = this.search_text;
|
|
247
|
-
if (this.thinking_text) {
|
|
248
|
-
if (p && !p.endsWith("\n")) p += "\n";
|
|
249
|
-
p += this.thinking_text;
|
|
250
|
-
}
|
|
251
|
-
return p;
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
return r;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Stream a response in real-time, yielding chunks as they arrive.
|
|
259
|
-
*/
|
|
260
|
-
async *streamChat(prompt: string, options: ChatOptions = {}): AsyncGenerator<HAIResponse, void, unknown> {
|
|
261
|
-
const { sessionId, youtube, retries = 3, onDone } = options;
|
|
262
|
-
|
|
263
|
-
// Handle YouTube
|
|
264
|
-
let actualPrompt = prompt;
|
|
265
|
-
if (youtube) {
|
|
266
|
-
const videoId = extractYouTubeId(youtube);
|
|
267
|
-
if (!videoId) throw new Error(`Could not extract YouTube video ID from: ${youtube}`);
|
|
268
|
-
const transcript = await fetchYouTubeTranscript(videoId);
|
|
269
|
-
actualPrompt = `[YOUTUBE VIDEO TRANSCRIPT]:\n${transcript}\n\n[USER REQUEST]:\n${prompt}`;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const payload = this.buildPayload(actualPrompt, options);
|
|
273
|
-
|
|
274
|
-
const currentResponse = this.createResponse();
|
|
275
|
-
let attempt = 0;
|
|
276
|
-
|
|
277
|
-
while (attempt <= retries) {
|
|
278
|
-
try {
|
|
279
|
-
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
280
|
-
method: 'POST',
|
|
281
|
-
headers: {
|
|
282
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
283
|
-
'Content-Type': 'application/json'
|
|
284
|
-
},
|
|
285
|
-
body: JSON.stringify(payload)
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
if (!response.ok) throw new Error(`HSeek API Error: ${response.status}`);
|
|
289
|
-
if (!response.body) throw new Error("Response body is null");
|
|
290
|
-
|
|
291
|
-
const reader = response.body.getReader();
|
|
292
|
-
const decoder = new TextDecoder();
|
|
293
|
-
let buffer = '';
|
|
294
|
-
|
|
295
|
-
while (true) {
|
|
296
|
-
const { done, value } = await reader.read();
|
|
297
|
-
if (done) break;
|
|
298
|
-
buffer += decoder.decode(value, { stream: true });
|
|
299
|
-
const lines = buffer.split('\n');
|
|
300
|
-
buffer = lines.pop() || '';
|
|
301
|
-
|
|
302
|
-
for (const line of lines) {
|
|
303
|
-
if (!line.startsWith('data: ')) continue;
|
|
304
|
-
const dataStr = line.substring(6).trim();
|
|
305
|
-
|
|
306
|
-
if (dataStr === '[DONE]') {
|
|
307
|
-
currentResponse.is_done = true;
|
|
308
|
-
if (sessionId) {
|
|
309
|
-
this.memory.addToHistory(sessionId, 'user', actualPrompt);
|
|
310
|
-
this.memory.addToHistory(sessionId, 'assistant', currentResponse.text);
|
|
311
|
-
}
|
|
312
|
-
if (onDone) onDone(currentResponse);
|
|
313
|
-
yield currentResponse;
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
const dataJson = JSON.parse(dataStr);
|
|
319
|
-
if (dataJson.choices?.[0]?.delta) {
|
|
320
|
-
const delta = dataJson.choices[0].delta;
|
|
321
|
-
if (delta.content) {
|
|
322
|
-
currentResponse.text += delta.content;
|
|
323
|
-
yield currentResponse;
|
|
324
|
-
}
|
|
325
|
-
if (delta.reasoning_content) {
|
|
326
|
-
currentResponse.thinking_text += delta.reasoning_content;
|
|
327
|
-
yield currentResponse;
|
|
328
|
-
}
|
|
329
|
-
if (delta.search_content) {
|
|
330
|
-
currentResponse.search_text += delta.search_content;
|
|
331
|
-
yield currentResponse;
|
|
332
|
-
}
|
|
333
|
-
} else if (dataJson.object === 'chat.completion.meta') {
|
|
334
|
-
currentResponse.parent_message_id = dataJson.message_id;
|
|
335
|
-
currentResponse.session_id = dataJson.chat_session_id;
|
|
336
|
-
currentResponse.latency_ms = dataJson.latency_ms ?? null;
|
|
337
|
-
this.memory.saveIds(sessionId, currentResponse.session_id, currentResponse.parent_message_id);
|
|
338
|
-
}
|
|
339
|
-
} catch (_) { /* ignore json parse errors */ }
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
break; // Success
|
|
343
|
-
|
|
344
|
-
} catch (e) {
|
|
345
|
-
attempt++;
|
|
346
|
-
if (attempt > retries) throw new Error(`HSeek failed after ${retries} retries. Last error: ${e}`);
|
|
347
|
-
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Send a message and get the full response at once (non-streaming).
|
|
354
|
-
*/
|
|
355
|
-
async chat(prompt: string, options: ChatOptions = {}): Promise<HAIResponse> {
|
|
356
|
-
let finalResponse: HAIResponse | null = null;
|
|
357
|
-
for await (const chunk of this.streamChat(prompt, options)) {
|
|
358
|
-
finalResponse = chunk;
|
|
359
|
-
}
|
|
360
|
-
if (!finalResponse) throw new Error("HSeek API returned an empty response.");
|
|
361
|
-
return finalResponse;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Export the full conversation history for a session.
|
|
366
|
-
*/
|
|
367
|
-
exportHistory(sessionId: string): SessionData {
|
|
368
|
-
return this.memory.export(sessionId);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Clear the conversation memory for a session.
|
|
373
|
-
*/
|
|
374
|
-
clearHistory(sessionId: string): void {
|
|
375
|
-
this.memory.clear(sessionId);
|
|
376
|
-
}
|
|
377
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "Node16",
|
|
5
|
-
"moduleResolution": "node16",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"outDir": "./dist",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"]
|
|
15
|
-
}
|