@bytexbyte/nxtlinq-ai-agent-core-development 0.2.0

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.
Files changed (113) hide show
  1. package/dist/agent/ChatOrchestrator.d.ts +48 -0
  2. package/dist/agent/ChatOrchestrator.d.ts.map +1 -0
  3. package/dist/agent/ChatOrchestrator.js +311 -0
  4. package/dist/agent/NxtlinqAgent.d.ts +65 -0
  5. package/dist/agent/NxtlinqAgent.d.ts.map +1 -0
  6. package/dist/agent/NxtlinqAgent.js +256 -0
  7. package/dist/agent/errors.d.ts +4 -0
  8. package/dist/agent/errors.d.ts.map +1 -0
  9. package/dist/agent/errors.js +6 -0
  10. package/dist/agent/extractReplyText.d.ts +3 -0
  11. package/dist/agent/extractReplyText.d.ts.map +1 -0
  12. package/dist/agent/extractReplyText.js +16 -0
  13. package/dist/api/hosts.d.ts +4 -0
  14. package/dist/api/hosts.d.ts.map +1 -0
  15. package/dist/api/hosts.js +18 -0
  16. package/dist/api/nxtlinq-api.d.ts +9 -0
  17. package/dist/api/nxtlinq-api.d.ts.map +1 -0
  18. package/dist/api/nxtlinq-api.js +499 -0
  19. package/dist/api/parse-sse.d.ts +9 -0
  20. package/dist/api/parse-sse.d.ts.map +1 -0
  21. package/dist/api/parse-sse.js +97 -0
  22. package/dist/api/tts.d.ts +19 -0
  23. package/dist/api/tts.d.ts.map +1 -0
  24. package/dist/api/tts.js +46 -0
  25. package/dist/constants/storageKeys.d.ts +6 -0
  26. package/dist/constants/storageKeys.d.ts.map +1 -0
  27. package/dist/constants/storageKeys.js +5 -0
  28. package/dist/history/messageHistory.d.ts +18 -0
  29. package/dist/history/messageHistory.d.ts.map +1 -0
  30. package/dist/history/messageHistory.js +48 -0
  31. package/dist/index.d.ts +25 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +26 -0
  34. package/dist/ports/HttpPort.d.ts +6 -0
  35. package/dist/ports/HttpPort.d.ts.map +1 -0
  36. package/dist/ports/HttpPort.js +3 -0
  37. package/dist/ports/PlatformPorts.d.ts +12 -0
  38. package/dist/ports/PlatformPorts.d.ts.map +1 -0
  39. package/dist/ports/PlatformPorts.js +1 -0
  40. package/dist/ports/StoragePort.d.ts +10 -0
  41. package/dist/ports/StoragePort.d.ts.map +1 -0
  42. package/dist/ports/StoragePort.js +33 -0
  43. package/dist/ports/WebRTCPort.d.ts +68 -0
  44. package/dist/ports/WebRTCPort.d.ts.map +1 -0
  45. package/dist/ports/WebRTCPort.js +10 -0
  46. package/dist/ports/createBrowserWebRTCPort.d.ts +7 -0
  47. package/dist/ports/createBrowserWebRTCPort.d.ts.map +1 -0
  48. package/dist/ports/createBrowserWebRTCPort.js +140 -0
  49. package/dist/ports/index.d.ts +5 -0
  50. package/dist/ports/index.d.ts.map +1 -0
  51. package/dist/ports/index.js +4 -0
  52. package/dist/types/agent-config.d.ts +40 -0
  53. package/dist/types/agent-config.d.ts.map +1 -0
  54. package/dist/types/agent-config.js +1 -0
  55. package/dist/types/ait-api.d.ts +393 -0
  56. package/dist/types/ait-api.d.ts.map +1 -0
  57. package/dist/types/ait-api.js +1 -0
  58. package/dist/voice/app-channel-dispatcher.d.ts +14 -0
  59. package/dist/voice/app-channel-dispatcher.d.ts.map +1 -0
  60. package/dist/voice/app-channel-dispatcher.js +171 -0
  61. package/dist/voice/create-voice-session.d.ts +8 -0
  62. package/dist/voice/create-voice-session.d.ts.map +1 -0
  63. package/dist/voice/create-voice-session.js +37 -0
  64. package/dist/voice/output-audio-level.d.ts +26 -0
  65. package/dist/voice/output-audio-level.d.ts.map +1 -0
  66. package/dist/voice/output-audio-level.js +132 -0
  67. package/dist/voice/remote-audio-gain.d.ts +10 -0
  68. package/dist/voice/remote-audio-gain.d.ts.map +1 -0
  69. package/dist/voice/remote-audio-gain.js +19 -0
  70. package/dist/voice/start-voice-session.d.ts +13 -0
  71. package/dist/voice/start-voice-session.d.ts.map +1 -0
  72. package/dist/voice/start-voice-session.js +303 -0
  73. package/dist/voice/transcript.d.ts +10 -0
  74. package/dist/voice/transcript.d.ts.map +1 -0
  75. package/dist/voice/transcript.js +50 -0
  76. package/dist/voice/trigger-voice-greeting.d.ts +14 -0
  77. package/dist/voice/trigger-voice-greeting.d.ts.map +1 -0
  78. package/dist/voice/trigger-voice-greeting.js +28 -0
  79. package/dist/voice/types.d.ts +138 -0
  80. package/dist/voice/types.d.ts.map +1 -0
  81. package/dist/voice/types.js +1 -0
  82. package/dist/voice/voice-user-input.d.ts +19 -0
  83. package/dist/voice/voice-user-input.d.ts.map +1 -0
  84. package/dist/voice/voice-user-input.js +10 -0
  85. package/package.json +41 -0
  86. package/src/agent/ChatOrchestrator.ts +380 -0
  87. package/src/agent/NxtlinqAgent.ts +325 -0
  88. package/src/agent/errors.ts +6 -0
  89. package/src/agent/extractReplyText.ts +22 -0
  90. package/src/api/hosts.ts +20 -0
  91. package/src/api/nxtlinq-api.ts +656 -0
  92. package/src/api/parse-sse.ts +104 -0
  93. package/src/api/tts.ts +69 -0
  94. package/src/constants/storageKeys.ts +5 -0
  95. package/src/history/messageHistory.ts +65 -0
  96. package/src/index.ts +70 -0
  97. package/src/ports/HttpPort.ts +12 -0
  98. package/src/ports/PlatformPorts.ts +12 -0
  99. package/src/ports/StoragePort.ts +37 -0
  100. package/src/ports/WebRTCPort.ts +54 -0
  101. package/src/ports/createBrowserWebRTCPort.ts +163 -0
  102. package/src/ports/index.ts +4 -0
  103. package/src/types/agent-config.ts +51 -0
  104. package/src/types/ait-api.ts +303 -0
  105. package/src/voice/app-channel-dispatcher.ts +201 -0
  106. package/src/voice/create-voice-session.ts +53 -0
  107. package/src/voice/output-audio-level.ts +153 -0
  108. package/src/voice/remote-audio-gain.ts +31 -0
  109. package/src/voice/start-voice-session.ts +369 -0
  110. package/src/voice/transcript.ts +44 -0
  111. package/src/voice/trigger-voice-greeting.ts +47 -0
  112. package/src/voice/types.ts +154 -0
  113. package/src/voice/voice-user-input.ts +32 -0
@@ -0,0 +1,656 @@
1
+ import type { AITApi } from '../types/ait-api';
2
+ import { STORAGE_KEYS } from '../constants/storageKeys';
3
+ import type { PlatformPorts } from '../ports/PlatformPorts';
4
+ import { createDefaultHttpPort } from '../ports/HttpPort';
5
+ import { getAiAgentApiHost, getAitServiceApiHost } from './hosts';
6
+ import { parseSSEResponse, parseSSEText } from './parse-sse';
7
+ import { postTextTts, type PostTextTtsResult } from './tts';
8
+
9
+ export type { PostTextTtsResult };
10
+ export { postTextTts, buildTextTtsDataUri } from './tts';
11
+
12
+ export type CoreAITApi = Omit<AITApi, 'voice'>;
13
+
14
+ export type ApiClientDeps = Pick<PlatformPorts, 'storage' | 'http' | 'getTimezone'>;
15
+
16
+ /** RN/Hermes 的 URLSearchParams 常缺少 set/append,改用手動組 query。 */
17
+ function withQuery(
18
+ baseUrl: string,
19
+ query: Record<string, string | number | undefined | null>,
20
+ ): string {
21
+ const pairs: string[] = [];
22
+ for (const [key, value] of Object.entries(query)) {
23
+ if (value == null || value === '') continue;
24
+ pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
25
+ }
26
+ if (pairs.length === 0) return baseUrl;
27
+ const sep = baseUrl.includes('?') ? '&' : '?';
28
+ return `${baseUrl}${sep}${pairs.join('&')}`;
29
+ }
30
+
31
+ function createApiHelpers(deps: ApiClientDeps) {
32
+ const fetchFn = deps.http?.fetch ?? createDefaultHttpPort().fetch;
33
+ const getTimezone = deps.getTimezone ?? (() => {
34
+ try {
35
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
36
+ } catch {
37
+ return 'UTC';
38
+ }
39
+ });
40
+
41
+ const getAuthHeader = async (): Promise<Record<string, string>> => {
42
+ const token = await deps.storage.getItem(STORAGE_KEYS.AIT_SERVICE_ACCESS_TOKEN);
43
+ return token ? { Authorization: `Bearer ${JSON.parse(token)}` } : {};
44
+ };
45
+
46
+ const makeRequest = async (
47
+ url: string,
48
+ options: RequestInit,
49
+ apiKey: string,
50
+ apiSecret: string,
51
+ ): Promise<any> => {
52
+ const auth = await getAuthHeader();
53
+ const response = await fetchFn(url, {
54
+ ...options,
55
+ headers: {
56
+ 'X-API-Key': apiKey,
57
+ 'X-API-Secret': apiSecret,
58
+ ...auth,
59
+ ...options.headers,
60
+ },
61
+ });
62
+ return response.json();
63
+ };
64
+
65
+ const getWalletContext = async () => {
66
+ const walletAddress = await deps.storage.getItem(STORAGE_KEYS.WALLET_ADDRESS);
67
+ const aitTokenRaw = await deps.storage.getItem(STORAGE_KEYS.AIT_SERVICE_ACCESS_TOKEN);
68
+ const aitToken = aitTokenRaw ? JSON.parse(aitTokenRaw) : null;
69
+ return { walletAddress, aitToken };
70
+ };
71
+
72
+ return { fetchFn, getTimezone, getAuthHeader, makeRequest, getWalletContext };
73
+ }
74
+
75
+ // AIT API module
76
+ const createAITApi = (apiKey: string, apiSecret: string, helpers: ReturnType<typeof createApiHelpers>) => ({
77
+ getAITByServiceIdAndController: async (params: { serviceId: string; controller: string; customUsername?: string }, token: string) => {
78
+ const url = withQuery(
79
+ `${getAitServiceApiHost()}/api/ait/service/${params.serviceId}/controller/${params.controller}`,
80
+ { customUsername: params.customUsername },
81
+ );
82
+ return helpers.makeRequest(
83
+ url,
84
+ { method: 'GET' },
85
+ apiKey,
86
+ apiSecret
87
+ );
88
+ },
89
+
90
+ createAIT: async (params: any, token: string) => {
91
+ return helpers.makeRequest(
92
+ `${getAitServiceApiHost()}/api/ait`,
93
+ {
94
+ method: 'POST',
95
+ headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify(params)
97
+ },
98
+ apiKey,
99
+ apiSecret
100
+ );
101
+ },
102
+ });
103
+
104
+ // Wallet API module
105
+ const createWalletApi = (apiKey: string, apiSecret: string, helpers: ReturnType<typeof createApiHelpers>) => ({
106
+ verifyWallet: async (params: any, token: string) => {
107
+ return helpers.makeRequest(
108
+ `${getAitServiceApiHost()}/api/wallet`,
109
+ {
110
+ method: 'POST',
111
+ headers: { 'Content-Type': 'application/json' },
112
+ body: JSON.stringify(params)
113
+ },
114
+ apiKey,
115
+ apiSecret
116
+ );
117
+ },
118
+
119
+ getWallet: async (params: { address: string }, token: string) => {
120
+ return helpers.makeRequest(
121
+ `${getAitServiceApiHost()}/api/wallet/address/${params.address}`,
122
+ { method: 'GET' },
123
+ apiKey,
124
+ apiSecret
125
+ );
126
+ }
127
+ });
128
+
129
+ // Metadata API module
130
+ const createMetadataApi = (apiKey: string, apiSecret: string, helpers: ReturnType<typeof createApiHelpers>) => ({
131
+ createMetadata: async (metadata: any, token: string) => {
132
+ return helpers.makeRequest(
133
+ `${getAitServiceApiHost()}/api/metadata`,
134
+ {
135
+ method: 'POST',
136
+ headers: { 'Content-Type': 'application/json' },
137
+ body: JSON.stringify(metadata)
138
+ },
139
+ apiKey,
140
+ apiSecret
141
+ );
142
+ }
143
+ });
144
+
145
+ // Auth API module
146
+ const createAuthApi = (apiKey: string, apiSecret: string, helpers: ReturnType<typeof createApiHelpers>) => ({
147
+ getNonce: async (params: { address: string }) => {
148
+ return helpers.makeRequest(
149
+ `${getAitServiceApiHost()}/api/auth/address/${params.address}/nonce`,
150
+ { method: 'GET' },
151
+ apiKey,
152
+ apiSecret
153
+ );
154
+ },
155
+
156
+ signIn: async (params: any) => {
157
+ return helpers.makeRequest(
158
+ `${getAitServiceApiHost()}/api/auth`,
159
+ {
160
+ method: 'POST',
161
+ headers: { 'Content-Type': 'application/json' },
162
+ body: JSON.stringify(params)
163
+ },
164
+ apiKey,
165
+ apiSecret
166
+ );
167
+ }
168
+ });
169
+
170
+ // Agent API module
171
+ const createAgentApi = (helpers: ReturnType<typeof createApiHelpers>) => ({
172
+ sendMessage: async (params: {
173
+ model?: string;
174
+ apiKey: string;
175
+ apiSecret: string;
176
+ pseudoId: string;
177
+ externalId?: string;
178
+ customUserInfo?: Record<string, any>;
179
+ customUsername?: string;
180
+ message: string;
181
+ attachments?: Array<{
182
+ type: 'image' | 'file';
183
+ url: string;
184
+ name: string;
185
+ mimeType: string;
186
+ }>;
187
+ context?: Array<{ role: string; text: string }>;
188
+ isRetry?: boolean;
189
+ /** 客戶端步驟耗時(如 Azure Speech STT),會寫入後端 ai_service_call_log */
190
+ clientPipelineTimings?: Array<{ name: string; durationMs: number }>;
191
+ onPiiProgress?: (step: 'scan_start' | 'scan_complete' | 'send_start' | 'done', data?: any) => void;
192
+ /** Token chunks from `*-stream` routes (`event: text_delta`). */
193
+ onStreamDelta?: (text: string) => void;
194
+ }) => {
195
+ try {
196
+ const model = params.model || 'open-ai';
197
+ const { walletAddress, aitToken } = await helpers.getWalletContext();
198
+ const auth = await helpers.getAuthHeader();
199
+
200
+ const requestBody = {
201
+ apiKey: params.apiKey,
202
+ apiSecret: params.apiSecret,
203
+ pseudoId: params.pseudoId,
204
+ externalId: params.externalId,
205
+ customUserInfo: params.customUserInfo,
206
+ customUsername: params.customUsername,
207
+ message: params.message,
208
+ attachments: params.attachments,
209
+ walletAddress: walletAddress || undefined,
210
+ aitToken: aitToken || undefined,
211
+ timezone: helpers.getTimezone(),
212
+ ...(params.isRetry ? { isRetry: true } : {}),
213
+ ...(params.clientPipelineTimings &&
214
+ params.clientPipelineTimings.length > 0
215
+ ? { clientPipelineTimings: params.clientPipelineTimings }
216
+ : {}),
217
+ };
218
+
219
+ const useSSE = Boolean(
220
+ params.onPiiProgress || params.onStreamDelta || model.endsWith('-stream')
221
+ );
222
+
223
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/${model}`, {
224
+ method: 'POST',
225
+ headers: {
226
+ 'Content-Type': 'application/json',
227
+ 'X-Wallet-Address': walletAddress || '',
228
+ 'X-AIT-Token': aitToken || '',
229
+ ...auth,
230
+ ...(params.onPiiProgress ? { 'X-Stream-PII': '1' } : {}),
231
+ },
232
+ body: JSON.stringify(requestBody),
233
+ });
234
+
235
+ if (!response.ok) {
236
+ throw new Error('Failed to send message');
237
+ }
238
+
239
+ const contentType = response.headers.get('content-type') || '';
240
+
241
+ if (useSSE && contentType.includes('text/event-stream')) {
242
+ const sseHandlers = {
243
+ onPiiProgress: params.onPiiProgress,
244
+ onStreamDelta: params.onStreamDelta,
245
+ };
246
+ // Always buffer first: RN often exposes a `body` stream that closes
247
+ // immediately without data, which breaks incremental parsing.
248
+ const rawText = await response.text();
249
+ if (rawText.trimStart().startsWith('event:') || rawText.includes('\nevent:')) {
250
+ return parseSSEText(rawText, sseHandlers);
251
+ }
252
+ // Gateway returned JSON despite SSE content-type.
253
+ return rawText ? JSON.parse(rawText) : {};
254
+ }
255
+
256
+ return await response.json();
257
+ } catch (error) {
258
+ console.error('Failed to send message:', error);
259
+ return { error: error instanceof Error ? error.message : 'Failed to send message' };
260
+ }
261
+ },
262
+ generateSuggestions: async (params: {
263
+ apiKey: string;
264
+ apiSecret: string;
265
+ pseudoId: string;
266
+ externalId?: string;
267
+ }) => {
268
+ try {
269
+ const auth = await helpers.getAuthHeader();
270
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/suggestions`, {
271
+ method: 'POST',
272
+ headers: {
273
+ 'Content-Type': 'application/json',
274
+ 'X-API-Key': params.apiKey,
275
+ 'X-API-Secret': params.apiSecret,
276
+ ...auth,
277
+ },
278
+ body: JSON.stringify({
279
+ apiKey: params.apiKey,
280
+ apiSecret: params.apiSecret,
281
+ pseudoId: params.pseudoId,
282
+ externalId: params.externalId || undefined,
283
+ }),
284
+ });
285
+ if (!response.ok) {
286
+ throw new Error('Failed to generate suggestions');
287
+ }
288
+ return await response.json();
289
+ } catch (error) {
290
+ console.error('Failed to generate suggestions:', error);
291
+ return { error: error instanceof Error ? error.message : 'Failed to generate suggestions' };
292
+ }
293
+ },
294
+ getMessageHistory: async (params: {
295
+ apiKey: string;
296
+ apiSecret: string;
297
+ pseudoId: string;
298
+ externalId?: string;
299
+ last?: number;
300
+ }) => {
301
+ try {
302
+ const url = withQuery(
303
+ `${getAiAgentApiHost()}/api/message/pseudoId/${encodeURIComponent(params.pseudoId)}`,
304
+ {
305
+ externalId: params.externalId,
306
+ last: params.last != null ? params.last : undefined,
307
+ },
308
+ );
309
+ const auth = await helpers.getAuthHeader();
310
+ const response = await helpers.fetchFn(url, {
311
+ method: 'GET',
312
+ headers: {
313
+ 'X-API-Key': params.apiKey,
314
+ 'X-API-Secret': params.apiSecret,
315
+ ...auth,
316
+ },
317
+ });
318
+ if (!response.ok) {
319
+ throw new Error('Failed to fetch message history');
320
+ }
321
+ return await response.json() as {
322
+ messages: Array<{
323
+ id: string;
324
+ role: 'user' | 'assistant';
325
+ content: string;
326
+ createdAt?: string;
327
+ }>;
328
+ };
329
+ } catch (error) {
330
+ console.error('Failed to fetch message history:', error);
331
+ return { error: error instanceof Error ? error.message : 'Failed to fetch message history' };
332
+ }
333
+ },
334
+ updateMessagesExternalIdByPseudoId: async (params: {
335
+ apiKey: string;
336
+ apiSecret: string;
337
+ pseudoId: string;
338
+ externalId: string;
339
+ }) => {
340
+ try {
341
+ const auth = await helpers.getAuthHeader();
342
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/message/pseudoId/${params.pseudoId}`, {
343
+ method: 'PUT',
344
+ headers: {
345
+ 'Content-Type': 'application/json',
346
+ 'X-API-Key': params.apiKey,
347
+ 'X-API-Secret': params.apiSecret,
348
+ ...auth,
349
+ },
350
+ body: JSON.stringify({
351
+ apiKey: params.apiKey,
352
+ apiSecret: params.apiSecret,
353
+ externalId: params.externalId,
354
+ }),
355
+ });
356
+ if (!response.ok) {
357
+ throw new Error('Failed to update message history');
358
+ }
359
+ return await response.json();
360
+ } catch (error) {
361
+ console.error('Failed to update message history:', error);
362
+ return { error: error instanceof Error ? error.message : 'Failed to update message history' };
363
+ }
364
+ },
365
+ cloneUserProfileByPseudoId: async (params: {
366
+ apiKey: string;
367
+ apiSecret: string;
368
+ pseudoId: string;
369
+ externalId: string;
370
+ }) => {
371
+ try {
372
+ const auth = await helpers.getAuthHeader();
373
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/userProfile/pseudoId/${params.pseudoId}`, {
374
+ method: 'PUT',
375
+ headers: {
376
+ 'Content-Type': 'application/json',
377
+ 'X-API-Key': params.apiKey,
378
+ 'X-API-Secret': params.apiSecret,
379
+ ...auth,
380
+ },
381
+ body: JSON.stringify({
382
+ apiKey: params.apiKey,
383
+ apiSecret: params.apiSecret,
384
+ externalId: params.externalId,
385
+ }),
386
+ });
387
+ if (!response.ok) {
388
+ throw new Error('Failed to clone user profile');
389
+ }
390
+ return await response.json();
391
+ } catch (error) {
392
+ console.error('Failed to clone user profile:', error);
393
+ return { error: error instanceof Error ? error.message : 'Failed to clone user profile' };
394
+ }
395
+ },
396
+ cloneUserTopicByPseudoId: async (params: {
397
+ apiKey: string;
398
+ apiSecret: string;
399
+ pseudoId: string;
400
+ externalId: string;
401
+ }) => {
402
+ try {
403
+ const auth = await helpers.getAuthHeader();
404
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/userTopic/pseudoId/${params.pseudoId}`, {
405
+ method: 'PUT',
406
+ headers: {
407
+ 'Content-Type': 'application/json',
408
+ 'X-API-Key': params.apiKey,
409
+ 'X-API-Secret': params.apiSecret,
410
+ ...auth,
411
+ },
412
+ body: JSON.stringify({
413
+ apiKey: params.apiKey,
414
+ apiSecret: params.apiSecret,
415
+ externalId: params.externalId,
416
+ }),
417
+ });
418
+ if (!response.ok) {
419
+ throw new Error('Failed to clone user topic');
420
+ }
421
+ return await response.json();
422
+ } catch (error) {
423
+ console.error('Failed to clone user topic:', error);
424
+ return { error: error instanceof Error ? error.message : 'Failed to clone user topic' };
425
+ }
426
+ },
427
+ checkToolPermission: async (params: { apiKey: string, apiSecret: string, aitToken: string, controller: string, toolName: string; }) => {
428
+ try {
429
+ const auth = await helpers.getAuthHeader();
430
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/utils/checkToolPermission`, {
431
+ method: 'POST',
432
+ headers: {
433
+ 'Content-Type': 'application/json',
434
+ 'X-API-Key': params.apiKey,
435
+ 'X-API-Secret': params.apiSecret,
436
+ ...auth,
437
+ },
438
+ body: JSON.stringify({
439
+ apiKey: params.apiKey,
440
+ apiSecret: params.apiSecret,
441
+ aitToken: params.aitToken,
442
+ controller: params.controller,
443
+ toolName: params.toolName,
444
+ }),
445
+ });
446
+
447
+ return await response.json();
448
+ } catch (error) {
449
+ console.error('Failed to clone user topic:', error);
450
+ return { error: error instanceof Error ? error.message : 'Failed to clone user topic' };
451
+ }
452
+ },
453
+ getServiceModels: async (params: { apiKey: string; apiSecret: string; }) => {
454
+ try {
455
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/service-models`, {
456
+ method: 'GET',
457
+ headers: {
458
+ 'x-api-key': params.apiKey,
459
+ 'x-api-secret': params.apiSecret,
460
+ }
461
+ });
462
+
463
+ if (!response.ok) {
464
+ throw new Error('Failed to fetch service models');
465
+ }
466
+
467
+ return await response.json();
468
+ } catch (error) {
469
+ console.error('Failed to fetch service models:', error);
470
+ return { error: error instanceof Error ? error.message : 'Failed to fetch service models' };
471
+ }
472
+ },
473
+ updateMessageContent: async (params: {
474
+ apiKey: string;
475
+ apiSecret: string;
476
+ messageId: string;
477
+ content: string;
478
+ }) => {
479
+ try {
480
+ const auth = await helpers.getAuthHeader();
481
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/message/${params.messageId}`, {
482
+ method: 'PUT',
483
+ headers: {
484
+ 'Content-Type': 'application/json',
485
+ 'X-API-Key': params.apiKey,
486
+ 'X-API-Secret': params.apiSecret,
487
+ ...auth,
488
+ },
489
+ body: JSON.stringify({
490
+ apiKey: params.apiKey,
491
+ apiSecret: params.apiSecret,
492
+ content: params.content,
493
+ }),
494
+ });
495
+
496
+ if (!response.ok) {
497
+ throw new Error('Failed to update message content');
498
+ }
499
+
500
+ return await response.json();
501
+ } catch (error) {
502
+ console.error('Failed to update message content:', error);
503
+ return { error: error instanceof Error ? error.message : 'Failed to update message content' };
504
+ }
505
+ },
506
+ uploadAttachment: async (params: {
507
+ apiKey: string;
508
+ apiSecret: string;
509
+ pseudoId: string;
510
+ file: File;
511
+ }): Promise<{ url: string; key?: string; name?: string; mimeType?: string } | { error: string }> => {
512
+ try {
513
+ const originalName = params.file.name;
514
+ const safeName = originalName.replace(/[^\x00-\x7F.a-zA-Z0-9_-]/g, '_').replace(/^\.+/, '') || 'file';
515
+ const fileForUpload = safeName !== originalName
516
+ ? new File([params.file], safeName, { type: params.file.type, lastModified: params.file.lastModified })
517
+ : params.file;
518
+
519
+ const form = new FormData();
520
+ form.append('file', fileForUpload);
521
+ form.append('pseudoId', params.pseudoId);
522
+ if (safeName !== originalName) {
523
+ form.append('originalName', originalName);
524
+ }
525
+ const { walletAddress, aitToken } = await helpers.getWalletContext();
526
+ const headers: Record<string, string> = {
527
+ 'X-API-Key': params.apiKey,
528
+ 'X-API-Secret': params.apiSecret,
529
+ 'X-Wallet-Address': walletAddress || '',
530
+ 'X-AIT-Token': aitToken || '',
531
+ };
532
+ const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/upload`, {
533
+ method: 'POST',
534
+ headers,
535
+ body: form,
536
+ });
537
+ const text = await response.text();
538
+ let data: { url?: string; key?: string; name?: string; mimeType?: string; error?: string };
539
+ try {
540
+ data = text ? JSON.parse(text) : {};
541
+ } catch {
542
+ data = {};
543
+ }
544
+ if (!response.ok) {
545
+ return { error: data.error || response.statusText || 'Upload failed' };
546
+ }
547
+ if (!data.url) {
548
+ return { error: data.error || 'Upload failed' };
549
+ }
550
+ return data as { url: string; key?: string; name?: string; mimeType?: string };
551
+ } catch (e) {
552
+ console.error('Upload attachment failed:', e);
553
+ return { error: e instanceof Error ? e.message : 'Upload failed' };
554
+ }
555
+ },
556
+ });
557
+
558
+ // Permissions API module
559
+ const createPermissionsApi = (apiKey: string, apiSecret: string, helpers: ReturnType<typeof createApiHelpers>) => ({
560
+ getServicePermissions: async (params: { serviceId: string; groupName?: string }) => {
561
+ try {
562
+ const url = withQuery(
563
+ `${getAiAgentApiHost()}/api/permissions/service/${params.serviceId}`,
564
+ {
565
+ groupName: params.groupName,
566
+ _t: Date.now(),
567
+ },
568
+ );
569
+
570
+ const auth = await helpers.getAuthHeader();
571
+ const response = await helpers.fetchFn(url, {
572
+ method: 'GET',
573
+ cache: 'no-store',
574
+ headers: {
575
+ 'X-API-Key': apiKey,
576
+ 'X-API-Secret': apiSecret,
577
+ 'Content-Type': 'application/json',
578
+ ...auth,
579
+ },
580
+ });
581
+
582
+ if (!response.ok) {
583
+ throw new Error('Failed to fetch permissions');
584
+ }
585
+
586
+ return await response.json();
587
+ } catch (error) {
588
+ console.error('Failed to fetch permissions:', error);
589
+ return { error: error instanceof Error ? error.message : 'Failed to fetch permissions' };
590
+ }
591
+ }
592
+ });
593
+
594
+ // Cognitive API Module
595
+ const createCognitiveApi = (apiKey: string, apiSecret: string, helpers: ReturnType<typeof createApiHelpers>) => ({
596
+ getCognitiveToken: async () => {
597
+ try {
598
+ const url = `${getAiAgentApiHost()}/api/cognitive/token`;
599
+ const auth = await helpers.getAuthHeader();
600
+
601
+ const response = await helpers.fetchFn(url, {
602
+ method: 'GET',
603
+ headers: {
604
+ 'X-API-Key': apiKey,
605
+ 'X-API-Secret': apiSecret,
606
+ 'Content-Type': 'application/json',
607
+ ...auth,
608
+ },
609
+ });
610
+
611
+ if (!response.ok) {
612
+ throw new Error('Failed to fetch permissions');
613
+ }
614
+
615
+ return await response.json();
616
+ } catch (error) {
617
+ console.error('Failed to fetch cognitive token:', error);
618
+ return { error: error instanceof Error ? error.message : 'Failed to fetch cognitive token' };
619
+ }
620
+ }
621
+ });
622
+
623
+ function resolveApiDeps(deps: ApiClientDeps): ReturnType<typeof createApiHelpers> {
624
+ return createApiHelpers({
625
+ storage: deps.storage,
626
+ http: deps.http ?? createDefaultHttpPort(),
627
+ getTimezone: deps.getTimezone,
628
+ });
629
+ }
630
+
631
+ /** Platform-agnostic API client (no `voice` — attach via Web/RN layer). */
632
+ const createTtsApi = (apiKey: string, apiSecret: string, helpers: ReturnType<typeof createApiHelpers>) => ({
633
+ postTextTts: (text: string) =>
634
+ postTextTts(
635
+ { apiKey, apiSecret, text },
636
+ helpers.fetchFn,
637
+ ),
638
+ });
639
+
640
+ export function createNxtlinqApiWithDeps(
641
+ apiKey: string,
642
+ apiSecret: string,
643
+ deps: ApiClientDeps,
644
+ ): CoreAITApi {
645
+ const helpers = resolveApiDeps(deps);
646
+ return {
647
+ ait: createAITApi(apiKey, apiSecret, helpers),
648
+ wallet: createWalletApi(apiKey, apiSecret, helpers),
649
+ metadata: createMetadataApi(apiKey, apiSecret, helpers),
650
+ auth: createAuthApi(apiKey, apiSecret, helpers),
651
+ agent: createAgentApi(helpers),
652
+ permissions: createPermissionsApi(apiKey, apiSecret, helpers),
653
+ cognitive: createCognitiveApi(apiKey, apiSecret, helpers),
654
+ tts: createTtsApi(apiKey, apiSecret, helpers),
655
+ };
656
+ }