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 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");
@@ -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
- // Optional feature flags
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
- // 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)
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
- if (options.model)
177
- payload.model = options.model;
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.HAIClient = HAIClient;
370
+ exports.HSeekClient = HSeekClient;
package/package.json CHANGED
@@ -1,21 +1,37 @@
1
- {
2
- "name": "hai-api",
3
- "version": "1.1.4",
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,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
- }