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 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.HAIClient = void 0;
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 HAIClient
102
+ // Main HSeekClient
104
103
  // ========================
105
104
  /**
106
- * HSeekClient — The official client for the hAI private AI.
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
- * const res = await ai.chat("Hello!", { sessionId: "user_1", thinking: true });
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 HAIClient {
131
+ class HSeekClient {
116
132
  apiKey;
117
133
  baseUrl;
118
134
  memory;
119
- constructor(apiKey, baseUrl = "https://ai.hussain.ink") {
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 (http://localhost:5000).
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 HAIClient(apiKey, "http://localhost:5000");
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
- // Optional feature flags
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
- // Session management
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
- if (options.model)
183
- payload.model = options.model;
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.HAIClient = HAIClient;
370
+ exports.HSeekClient = HSeekClient;
package/package.json CHANGED
@@ -1,21 +1,37 @@
1
- {
2
- "name": "hai-api",
3
- "version": "1.1.5",
4
- "description": "The official Node.js SDK for hAI Private AI.",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc",
9
- "prepublishOnly": "npm run build"
10
- },
11
- "author": "Hussain Alkhatib",
12
- "license": "MIT",
13
- "dependencies": {
14
- "dotenv": "^16.4.5",
15
- "hai-api": "^1.1.3"
16
- },
17
- "devDependencies": {
18
- "@types/node": "^20.12.7",
19
- "typescript": "^5.4.5"
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
- }