hai-api 1.1.4 → 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} +55 -15
- package/package.json +37 -21
- package/README.md +0 -14
- package/dist/index.d.ts +0 -97
- package/src/index.ts +0 -370
- 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");
|
|
@@ -124,9 +144,17 @@ class HAIClient {
|
|
|
124
144
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
125
145
|
this.memory = new MemoryStore();
|
|
126
146
|
}
|
|
147
|
+
/**
|
|
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
|
|
151
|
+
*/
|
|
152
|
+
static local(apiKey) {
|
|
153
|
+
return new HSeekClient(apiKey, "http://localhost:5000");
|
|
154
|
+
}
|
|
127
155
|
buildPayload(prompt, options) {
|
|
128
|
-
const { sessionId, thinking = false, search = false, chatSessionId, parentMessageId, system, temperature, maxTokens, jsonMode, translate, codeMode, verify, summarize, includeThought, image, memoryDepth = 10, } = options;
|
|
129
|
-
// 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
|
|
130
158
|
const messages = [];
|
|
131
159
|
if (sessionId && memoryDepth !== 0) {
|
|
132
160
|
messages.push(...this.memory.getHistory(sessionId, memoryDepth));
|
|
@@ -141,7 +169,7 @@ class HAIClient {
|
|
|
141
169
|
thinking,
|
|
142
170
|
search,
|
|
143
171
|
};
|
|
144
|
-
//
|
|
172
|
+
// Standard feature flags
|
|
145
173
|
if (system)
|
|
146
174
|
payload.system = system;
|
|
147
175
|
if (temperature !== undefined)
|
|
@@ -160,7 +188,13 @@ class HAIClient {
|
|
|
160
188
|
payload.summarize = true;
|
|
161
189
|
if (includeThought)
|
|
162
190
|
payload.include_thought = true;
|
|
163
|
-
//
|
|
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)
|
|
164
198
|
if (chatSessionId) {
|
|
165
199
|
payload.chat_session_id = chatSessionId;
|
|
166
200
|
}
|
|
@@ -173,8 +207,10 @@ class HAIClient {
|
|
|
173
207
|
}
|
|
174
208
|
if (parentMessageId)
|
|
175
209
|
payload.parent_message_id = parentMessageId;
|
|
176
|
-
|
|
177
|
-
|
|
210
|
+
// Merge extra parameters
|
|
211
|
+
if (extra) {
|
|
212
|
+
Object.assign(payload, extra);
|
|
213
|
+
}
|
|
178
214
|
return payload;
|
|
179
215
|
}
|
|
180
216
|
createResponse() {
|
|
@@ -214,7 +250,7 @@ class HAIClient {
|
|
|
214
250
|
*/
|
|
215
251
|
async *streamChat(prompt, options = {}) {
|
|
216
252
|
const { sessionId, youtube, retries = 3, onDone } = options;
|
|
217
|
-
// Handle YouTube
|
|
253
|
+
// Handle YouTube transcript injection
|
|
218
254
|
let actualPrompt = prompt;
|
|
219
255
|
if (youtube) {
|
|
220
256
|
const videoId = extractYouTubeId(youtube);
|
|
@@ -281,6 +317,10 @@ class HAIClient {
|
|
|
281
317
|
currentResponse.search_text += delta.search_content;
|
|
282
318
|
yield currentResponse;
|
|
283
319
|
}
|
|
320
|
+
if (delta.image_url) {
|
|
321
|
+
// Image URL in delta (Nano Banana)
|
|
322
|
+
yield currentResponse;
|
|
323
|
+
}
|
|
284
324
|
}
|
|
285
325
|
else if (dataJson.object === 'chat.completion.meta') {
|
|
286
326
|
currentResponse.parent_message_id = dataJson.message_id;
|
|
@@ -327,4 +367,4 @@ class HAIClient {
|
|
|
327
367
|
this.memory.clear(sessionId);
|
|
328
368
|
}
|
|
329
369
|
}
|
|
330
|
-
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,97 +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
|
-
private buildPayload;
|
|
79
|
-
private createResponse;
|
|
80
|
-
/**
|
|
81
|
-
* Stream a response in real-time, yielding chunks as they arrive.
|
|
82
|
-
*/
|
|
83
|
-
streamChat(prompt: string, options?: ChatOptions): AsyncGenerator<HAIResponse, void, unknown>;
|
|
84
|
-
/**
|
|
85
|
-
* Send a message and get the full response at once (non-streaming).
|
|
86
|
-
*/
|
|
87
|
-
chat(prompt: string, options?: ChatOptions): Promise<HAIResponse>;
|
|
88
|
-
/**
|
|
89
|
-
* Export the full conversation history for a session.
|
|
90
|
-
*/
|
|
91
|
-
exportHistory(sessionId: string): SessionData;
|
|
92
|
-
/**
|
|
93
|
-
* Clear the conversation memory for a session.
|
|
94
|
-
*/
|
|
95
|
-
clearHistory(sessionId: string): void;
|
|
96
|
-
}
|
|
97
|
-
export {};
|
package/src/index.ts
DELETED
|
@@ -1,370 +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
|
-
private buildPayload(prompt: string, options: ChatOptions): Record<string, unknown> {
|
|
166
|
-
const {
|
|
167
|
-
sessionId, thinking = false, search = false,
|
|
168
|
-
chatSessionId, parentMessageId,
|
|
169
|
-
system, temperature, maxTokens, jsonMode,
|
|
170
|
-
translate, codeMode, verify, summarize, includeThought,
|
|
171
|
-
image, memoryDepth = 10,
|
|
172
|
-
} = options;
|
|
173
|
-
|
|
174
|
-
// Build messages array
|
|
175
|
-
const messages: Array<{ role: string; content: unknown }> = [];
|
|
176
|
-
if (sessionId && memoryDepth !== 0) {
|
|
177
|
-
messages.push(...this.memory.getHistory(sessionId, memoryDepth));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const userContent = image
|
|
181
|
-
? [{ type: "image_url", image_url: { url: image } }, { type: "text", text: prompt }]
|
|
182
|
-
: prompt;
|
|
183
|
-
|
|
184
|
-
messages.push({ role: "user", content: userContent });
|
|
185
|
-
|
|
186
|
-
const payload: Record<string, unknown> = {
|
|
187
|
-
messages,
|
|
188
|
-
stream: true,
|
|
189
|
-
thinking,
|
|
190
|
-
search,
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// Optional feature flags
|
|
194
|
-
if (system) payload.system = system;
|
|
195
|
-
if (temperature !== undefined) payload.temperature = temperature;
|
|
196
|
-
if (maxTokens !== undefined) payload.max_tokens = maxTokens;
|
|
197
|
-
if (jsonMode) payload.json_mode = true;
|
|
198
|
-
if (translate) payload.translate = translate;
|
|
199
|
-
if (codeMode) payload.code_mode = true;
|
|
200
|
-
if (verify) payload.verify = true;
|
|
201
|
-
if (summarize) payload.summarize = true;
|
|
202
|
-
if (includeThought) payload.include_thought = true;
|
|
203
|
-
|
|
204
|
-
// Session management
|
|
205
|
-
if (chatSessionId) {
|
|
206
|
-
payload.chat_session_id = chatSessionId;
|
|
207
|
-
} else if (sessionId) {
|
|
208
|
-
const ids = this.memory.getIds(sessionId);
|
|
209
|
-
if (ids.chatSessionId) payload.chat_session_id = ids.chatSessionId;
|
|
210
|
-
if (ids.parentMessageId) payload.parent_message_id = ids.parentMessageId;
|
|
211
|
-
}
|
|
212
|
-
if (parentMessageId) payload.parent_message_id = parentMessageId;
|
|
213
|
-
if (options.model) payload.model = options.model;
|
|
214
|
-
|
|
215
|
-
return payload;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
private createResponse(): HAIResponse {
|
|
219
|
-
const r: HAIResponse = {
|
|
220
|
-
text: "",
|
|
221
|
-
thinking_text: "",
|
|
222
|
-
search_text: "",
|
|
223
|
-
session_id: null,
|
|
224
|
-
parent_message_id: null,
|
|
225
|
-
latency_ms: null,
|
|
226
|
-
is_done: false,
|
|
227
|
-
toDict() {
|
|
228
|
-
return {
|
|
229
|
-
text: this.text,
|
|
230
|
-
thinking_text: this.thinking_text,
|
|
231
|
-
search_text: this.search_text,
|
|
232
|
-
session_id: this.session_id,
|
|
233
|
-
parent_message_id: this.parent_message_id,
|
|
234
|
-
latency_ms: this.latency_ms,
|
|
235
|
-
is_done: this.is_done
|
|
236
|
-
};
|
|
237
|
-
},
|
|
238
|
-
get process_text() {
|
|
239
|
-
let p = this.search_text;
|
|
240
|
-
if (this.thinking_text) {
|
|
241
|
-
if (p && !p.endsWith("\n")) p += "\n";
|
|
242
|
-
p += this.thinking_text;
|
|
243
|
-
}
|
|
244
|
-
return p;
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
return r;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Stream a response in real-time, yielding chunks as they arrive.
|
|
252
|
-
*/
|
|
253
|
-
async *streamChat(prompt: string, options: ChatOptions = {}): AsyncGenerator<HAIResponse, void, unknown> {
|
|
254
|
-
const { sessionId, youtube, retries = 3, onDone } = options;
|
|
255
|
-
|
|
256
|
-
// Handle YouTube
|
|
257
|
-
let actualPrompt = prompt;
|
|
258
|
-
if (youtube) {
|
|
259
|
-
const videoId = extractYouTubeId(youtube);
|
|
260
|
-
if (!videoId) throw new Error(`Could not extract YouTube video ID from: ${youtube}`);
|
|
261
|
-
const transcript = await fetchYouTubeTranscript(videoId);
|
|
262
|
-
actualPrompt = `[YOUTUBE VIDEO TRANSCRIPT]:\n${transcript}\n\n[USER REQUEST]:\n${prompt}`;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const payload = this.buildPayload(actualPrompt, options);
|
|
266
|
-
|
|
267
|
-
const currentResponse = this.createResponse();
|
|
268
|
-
let attempt = 0;
|
|
269
|
-
|
|
270
|
-
while (attempt <= retries) {
|
|
271
|
-
try {
|
|
272
|
-
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
273
|
-
method: 'POST',
|
|
274
|
-
headers: {
|
|
275
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
276
|
-
'Content-Type': 'application/json'
|
|
277
|
-
},
|
|
278
|
-
body: JSON.stringify(payload)
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
if (!response.ok) throw new Error(`HSeek API Error: ${response.status}`);
|
|
282
|
-
if (!response.body) throw new Error("Response body is null");
|
|
283
|
-
|
|
284
|
-
const reader = response.body.getReader();
|
|
285
|
-
const decoder = new TextDecoder();
|
|
286
|
-
let buffer = '';
|
|
287
|
-
|
|
288
|
-
while (true) {
|
|
289
|
-
const { done, value } = await reader.read();
|
|
290
|
-
if (done) break;
|
|
291
|
-
buffer += decoder.decode(value, { stream: true });
|
|
292
|
-
const lines = buffer.split('\n');
|
|
293
|
-
buffer = lines.pop() || '';
|
|
294
|
-
|
|
295
|
-
for (const line of lines) {
|
|
296
|
-
if (!line.startsWith('data: ')) continue;
|
|
297
|
-
const dataStr = line.substring(6).trim();
|
|
298
|
-
|
|
299
|
-
if (dataStr === '[DONE]') {
|
|
300
|
-
currentResponse.is_done = true;
|
|
301
|
-
if (sessionId) {
|
|
302
|
-
this.memory.addToHistory(sessionId, 'user', actualPrompt);
|
|
303
|
-
this.memory.addToHistory(sessionId, 'assistant', currentResponse.text);
|
|
304
|
-
}
|
|
305
|
-
if (onDone) onDone(currentResponse);
|
|
306
|
-
yield currentResponse;
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
const dataJson = JSON.parse(dataStr);
|
|
312
|
-
if (dataJson.choices?.[0]?.delta) {
|
|
313
|
-
const delta = dataJson.choices[0].delta;
|
|
314
|
-
if (delta.content) {
|
|
315
|
-
currentResponse.text += delta.content;
|
|
316
|
-
yield currentResponse;
|
|
317
|
-
}
|
|
318
|
-
if (delta.reasoning_content) {
|
|
319
|
-
currentResponse.thinking_text += delta.reasoning_content;
|
|
320
|
-
yield currentResponse;
|
|
321
|
-
}
|
|
322
|
-
if (delta.search_content) {
|
|
323
|
-
currentResponse.search_text += delta.search_content;
|
|
324
|
-
yield currentResponse;
|
|
325
|
-
}
|
|
326
|
-
} else if (dataJson.object === 'chat.completion.meta') {
|
|
327
|
-
currentResponse.parent_message_id = dataJson.message_id;
|
|
328
|
-
currentResponse.session_id = dataJson.chat_session_id;
|
|
329
|
-
currentResponse.latency_ms = dataJson.latency_ms ?? null;
|
|
330
|
-
this.memory.saveIds(sessionId, currentResponse.session_id, currentResponse.parent_message_id);
|
|
331
|
-
}
|
|
332
|
-
} catch (_) { /* ignore json parse errors */ }
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
break; // Success
|
|
336
|
-
|
|
337
|
-
} catch (e) {
|
|
338
|
-
attempt++;
|
|
339
|
-
if (attempt > retries) throw new Error(`HSeek failed after ${retries} retries. Last error: ${e}`);
|
|
340
|
-
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Send a message and get the full response at once (non-streaming).
|
|
347
|
-
*/
|
|
348
|
-
async chat(prompt: string, options: ChatOptions = {}): Promise<HAIResponse> {
|
|
349
|
-
let finalResponse: HAIResponse | null = null;
|
|
350
|
-
for await (const chunk of this.streamChat(prompt, options)) {
|
|
351
|
-
finalResponse = chunk;
|
|
352
|
-
}
|
|
353
|
-
if (!finalResponse) throw new Error("HSeek API returned an empty response.");
|
|
354
|
-
return finalResponse;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Export the full conversation history for a session.
|
|
359
|
-
*/
|
|
360
|
-
exportHistory(sessionId: string): SessionData {
|
|
361
|
-
return this.memory.export(sessionId);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Clear the conversation memory for a session.
|
|
366
|
-
*/
|
|
367
|
-
clearHistory(sessionId: string): void {
|
|
368
|
-
this.memory.clear(sessionId);
|
|
369
|
-
}
|
|
370
|
-
}
|
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
|
-
}
|