@taco_tsinghua/graphnode-sdk 0.1.17 → 0.1.21

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 (56) hide show
  1. package/README.md +91 -333
  2. package/dist/client.d.ts +24 -2
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +34 -8
  5. package/dist/client.js.map +1 -1
  6. package/dist/endpoints/ai.d.ts +25 -30
  7. package/dist/endpoints/ai.d.ts.map +1 -1
  8. package/dist/endpoints/ai.js +220 -31
  9. package/dist/endpoints/ai.js.map +1 -1
  10. package/dist/endpoints/graph.d.ts +4 -1
  11. package/dist/endpoints/graph.d.ts.map +1 -1
  12. package/dist/endpoints/graph.js +10 -0
  13. package/dist/endpoints/graph.js.map +1 -1
  14. package/dist/endpoints/graphAi.d.ts +21 -0
  15. package/dist/endpoints/graphAi.d.ts.map +1 -1
  16. package/dist/endpoints/graphAi.js +24 -0
  17. package/dist/endpoints/graphAi.js.map +1 -1
  18. package/dist/endpoints/notification.d.ts +13 -0
  19. package/dist/endpoints/notification.d.ts.map +1 -1
  20. package/dist/endpoints/notification.js +17 -0
  21. package/dist/endpoints/notification.js.map +1 -1
  22. package/dist/endpoints/sync.d.ts +3 -1
  23. package/dist/endpoints/sync.d.ts.map +1 -1
  24. package/dist/endpoints/sync.js.map +1 -1
  25. package/dist/http-builder.d.ts +38 -0
  26. package/dist/http-builder.d.ts.map +1 -1
  27. package/dist/http-builder.js +43 -6
  28. package/dist/http-builder.js.map +1 -1
  29. package/dist/types/graph.d.ts +66 -0
  30. package/dist/types/graph.d.ts.map +1 -1
  31. package/package.json +2 -1
  32. package/src/client.ts +140 -0
  33. package/src/config.ts +9 -0
  34. package/src/endpoints/agent.ts +171 -0
  35. package/src/endpoints/ai.ts +296 -0
  36. package/src/endpoints/auth.apple.ts +39 -0
  37. package/src/endpoints/auth.google.ts +39 -0
  38. package/src/endpoints/conversations.ts +362 -0
  39. package/src/endpoints/graph.ts +398 -0
  40. package/src/endpoints/graphAi.ts +111 -0
  41. package/src/endpoints/health.ts +40 -0
  42. package/src/endpoints/me.ts +97 -0
  43. package/src/endpoints/note.ts +351 -0
  44. package/src/endpoints/notification.ts +69 -0
  45. package/src/endpoints/sync.ts +71 -0
  46. package/src/http-builder.ts +290 -0
  47. package/src/index.ts +60 -0
  48. package/src/types/aiInput.ts +111 -0
  49. package/src/types/conversation.ts +51 -0
  50. package/src/types/graph.ts +201 -0
  51. package/src/types/graphAi.ts +21 -0
  52. package/src/types/me.ts +49 -0
  53. package/src/types/message.ts +40 -0
  54. package/src/types/note.ts +89 -0
  55. package/src/types/problem.ts +22 -0
  56. package/src/types/sync.ts +35 -0
@@ -0,0 +1,296 @@
1
+ import { RequestBuilder, type HttpResponse } from '../http-builder.js';
2
+ import type { ApiKeyModel } from '../types/me.js';
3
+ import type { MessageDto } from '../types/message.js';
4
+
5
+ /**
6
+ * AI 채팅 요청 DTO
7
+ * @public
8
+ * @property id FE가 만들어줄 message 용 uuid
9
+ * @property model 사용할 AI 모델 (openai | deepseek)
10
+ * @property chatContent 사용자 입력 메시지
11
+ */
12
+ export interface AIChatRequestDto {
13
+ id: string;
14
+ model: ApiKeyModel;
15
+ chatContent: string;
16
+ }
17
+
18
+ /**
19
+ * AI 채팅 응답 DTO
20
+ * @public
21
+ * @property title 대화 제목 (선택적, 첫 대화 메시지에서 설정될 수 있음)
22
+ * @property messages 생성된 메시지 목록 (사용자 메시지 + AI 응답 메시지)
23
+ */
24
+ export interface AIChatResponseDto {
25
+ title?: string;
26
+ messages: MessageDto[];
27
+ }
28
+
29
+ /**
30
+ * AI Chat API
31
+ *
32
+ * AI 모델과의 실시간 채팅 기능을 제공하는 API 클래스입니다.
33
+ * `/v1/ai` 엔드포인트 하위의 API들을 호출합니다.
34
+ *
35
+ * 주요 기능:
36
+ * - AI 채팅 메시지 전송 및 응답 수신 (`chat`)
37
+ *
38
+ * @public
39
+ */
40
+ export class AiApi {
41
+ constructor(private rb: RequestBuilder) {}
42
+
43
+
44
+ /**
45
+ * 대화 내에서 AI와 채팅을 진행합니다.
46
+ * - 파일 첨부 가능 (files 인자)
47
+ * - 스트리밍 기본 지원 (Server-Sent Events)
48
+ * - onStream 콜백을 통해 청크 수신 가능
49
+ * - Promise는 최종 완료된 응답(AIChatResponseDto)으로 resolve됨 (기존 호환성 유지)
50
+ *
51
+ * @param conversationId - 대화 ID
52
+ * @param dto - 채팅 요청 데이터
53
+ * @param files - (선택) 업로드할 파일 리스트
54
+ * @param onStream - (선택) 스트림 청크 수신 콜백
55
+ */
56
+ async chat(
57
+ conversationId: string,
58
+ dto: AIChatRequestDto,
59
+ files?: File[],
60
+ onStream?: (chunk: string) => void
61
+ ): Promise<HttpResponse<AIChatResponseDto>> {
62
+ const rb = this.rb.path(`/v1/ai/conversations/${conversationId}/chat`);
63
+
64
+ // 1. Body 준비 (JSON or FormData)
65
+ let body: unknown;
66
+ if (files && files.length > 0) {
67
+ const formData = new FormData();
68
+ formData.append('id', dto.id);
69
+ formData.append('model', dto.model);
70
+ formData.append('chatContent', dto.chatContent);
71
+ files.forEach((file) => formData.append('files', file));
72
+ body = formData;
73
+ } else {
74
+ body = dto;
75
+ }
76
+
77
+ try {
78
+ // 2. 요청 전송 (Accept: text/event-stream 강제)
79
+ // sendRaw에 extraHeaders를 전달하여 요청
80
+ const res = await rb.sendRaw('POST', body, { Accept: 'text/event-stream' });
81
+
82
+ if (!res.ok) {
83
+ let errBody;
84
+ try {
85
+ errBody = await res.json();
86
+ } catch {
87
+ errBody = await res.text();
88
+ }
89
+ return {
90
+ isSuccess: false,
91
+ error: { statusCode: res.status, message: res.statusText, body: errBody },
92
+ };
93
+ }
94
+
95
+ // 3. 응답 처리 (SSE vs JSON)
96
+ const contentType = res.headers.get('content-type') || '';
97
+ if (!contentType.includes('text/event-stream')) {
98
+ // SSE가 아니면 일반 JSON으로 처리 (Fallback)
99
+ const json = await res.json();
100
+ return { isSuccess: true, statusCode: res.status, data: json };
101
+ }
102
+
103
+ // 4. SSE 스트림 파싱
104
+ if (!res.body) {
105
+ throw new Error('Response body is empty');
106
+ }
107
+
108
+ const reader = res.body.getReader();
109
+ const decoder = new TextDecoder();
110
+ let buffer = '';
111
+ let finalResult: AIChatResponseDto | null = null;
112
+ let finalError: string | null = null;
113
+
114
+ while (true) {
115
+ const { done, value } = await reader.read();
116
+ if (done) break;
117
+ buffer += decoder.decode(value, { stream: true });
118
+
119
+ // SSE 이벤트 파싱 로직
120
+ // 예: "event: chunk\ndata: {...}\n\n"
121
+ const parts = buffer.split('\n\n');
122
+ buffer = parts.pop() || ''; // 마지막 불완전한 부분은 버퍼에 남김
123
+
124
+ for (const part of parts) {
125
+ const lines = part.split('\n');
126
+ let eventName = 'message';
127
+ let dataStr = '';
128
+
129
+ for (const line of lines) {
130
+ const trimmed = line.trim();
131
+ if (trimmed.startsWith('event:')) eventName = trimmed.slice(6).trim();
132
+ else if (trimmed.startsWith('data:')) dataStr = trimmed.slice(5).trim();
133
+ }
134
+
135
+ if (dataStr) {
136
+ try {
137
+ const parsed = JSON.parse(dataStr);
138
+
139
+ if (eventName === 'chunk') {
140
+ if (onStream) onStream(parsed.text);
141
+ } else if (eventName === 'result') {
142
+ finalResult = parsed;
143
+ } else if (eventName === 'error') {
144
+ finalError = parsed.message;
145
+ }
146
+ } catch {
147
+ // JSON 파싱 에러 무시
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ // 스트림 종료 후 처리
154
+ if (finalError) {
155
+ return {
156
+ isSuccess: false,
157
+ error: { statusCode: 500, message: finalError },
158
+ };
159
+ }
160
+
161
+ if (!finalResult) {
162
+ // result 이벤트 없이 끝난 경우 (예: 연결 끊김 등)
163
+ return {
164
+ isSuccess: false,
165
+ error: { statusCode: 500, message: 'Stream ended without result' },
166
+ };
167
+ }
168
+
169
+ return {
170
+ isSuccess: true,
171
+ statusCode: 201, // Created
172
+ data: finalResult,
173
+ };
174
+
175
+ } catch (e) {
176
+ return {
177
+ isSuccess: false,
178
+ error: {
179
+ statusCode: 0,
180
+ message: e instanceof Error ? e.message : String(e),
181
+ },
182
+ };
183
+ }
184
+ }
185
+
186
+ /**
187
+ * AI 채팅 스트림을 엽니다.
188
+ * @param conversationId
189
+ * @param dto
190
+ * @param onEvent
191
+ * @param options
192
+ */
193
+ async chatStream(
194
+ conversationId: string,
195
+ dto: AIChatRequestDto,
196
+ files: File[] = [],
197
+ onEvent: (event: any) => void,
198
+ options: { signal?: AbortSignal; fetchImpl?: any } = {}
199
+ ): Promise<() => void> {
200
+ const url = this.rb.path(`/v1/ai/conversations/${conversationId}/chat`).url();
201
+
202
+ // Body 준비
203
+ let body: any;
204
+ const headers: Record<string, string> = {
205
+ Accept: 'text/event-stream',
206
+ };
207
+
208
+ if (files.length > 0) {
209
+ const formData = new FormData();
210
+ formData.append('id', dto.id);
211
+ formData.append('model', dto.model);
212
+ formData.append('chatContent', dto.chatContent);
213
+ files.forEach((f) => formData.append('files', f));
214
+ body = formData;
215
+ // Content-Type for FormData is handled by browser/fetch
216
+ } else {
217
+ headers['Content-Type'] = 'application/json';
218
+ body = JSON.stringify(dto);
219
+ }
220
+
221
+ // Agent.ts의 openAgentChatStream 로직과 유사하게 구현 (여기서는 간소화된 fetch 호출)
222
+ // 실제로는 http-builder가 스트리밍을 직접 지원하지 않으므로, fetch를 직접 호출해야 함.
223
+ // this.rb의 credentials, headers 등을 재사용하고 싶지만 private임.
224
+ // 여기서는 간단히 global fetch 사용 가정.
225
+
226
+ const fetchImpl = options.fetchImpl || globalThis.fetch;
227
+ const controller = new AbortController();
228
+ const signal = options.signal || controller.signal;
229
+
230
+ try {
231
+ const res = await fetchImpl(url, {
232
+ method: 'POST',
233
+ headers,
234
+ body,
235
+ signal,
236
+ credentials: 'include', // SDK 기본값 따름
237
+ });
238
+
239
+ if (!res.body) return () => controller.abort();
240
+
241
+ const reader = res.body.getReader();
242
+ const decoder = new TextDecoder();
243
+ let buffer = '';
244
+
245
+ (async () => {
246
+ while (true) {
247
+ const { done, value } = await reader.read();
248
+ if (done) break;
249
+ buffer += decoder.decode(value, { stream: true });
250
+ const parts = buffer.split('\n\n');
251
+ buffer = parts.pop() || '';
252
+
253
+ for (const part of parts) {
254
+ const lines = part.split('\n');
255
+ let eventName = 'message';
256
+ let dataStr = '';
257
+
258
+ for (const line of lines) {
259
+ if (line.startsWith('event:')) eventName = line.slice(6).trim();
260
+ else if (line.startsWith('data:')) dataStr = line.slice(5).trim();
261
+ }
262
+
263
+ if (dataStr) {
264
+ try {
265
+ const parsed = JSON.parse(dataStr);
266
+ onEvent({ event: eventName, data: parsed });
267
+ } catch {
268
+ // ignore
269
+ }
270
+ }
271
+ }
272
+ }
273
+ })();
274
+ } catch (e) {
275
+ onEvent({ event: 'error', data: { message: String(e) } });
276
+ }
277
+
278
+ return () => controller.abort();
279
+ }
280
+
281
+ /**
282
+ * AI 관련 파일을 다운로드합니다.
283
+ * @param fileKey 파일 키 (S3 Key)
284
+ * @returns Blob 객체
285
+ */
286
+ async downloadFile(fileKey: string): Promise<Blob> {
287
+ const rb = this.rb.path(`/v1/ai/files/${fileKey}`);
288
+ const res = await rb.sendRaw('GET', undefined, {});
289
+
290
+ if (!res.ok) {
291
+ throw new Error(`Failed to download file: ${res.statusText}`);
292
+ }
293
+
294
+ return await res.blob();
295
+ }
296
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Apple Auth API
3
+ *
4
+ * Apple OAuth 인증을 위한 헬퍼 클래스입니다.
5
+ * `/auth/apple` 관련 엔드포인트 URL을 생성하거나 리다이렉트를 수행합니다.
6
+ *
7
+ * 주요 기능:
8
+ * - 로그인 시작 URL 생성 (`startUrl`)
9
+ * - 로그인 페이지로 리다이렉트 (`login`)
10
+ *
11
+ * @public
12
+ */
13
+ export class AppleAuthApi {
14
+ constructor(private baseUrl: string) {}
15
+
16
+ /**
17
+ * 로그인 시작 URL을 반환합니다.
18
+ * @returns Apple OAuth 시작 URL
19
+ * @example
20
+ * const url = client.appleAuth.startUrl();
21
+ * console.log(url);
22
+ * // Output:
23
+ * // 'https://api.graphnode.dev/auth/apple/start'
24
+ */
25
+ startUrl(): string {
26
+ return this.baseUrl.replace(/\/$/, '') + '/auth/apple/start';
27
+ }
28
+
29
+ /**
30
+ * 브라우저를 Apple 로그인 페이지로 리다이렉트합니다.
31
+ * @param windowObj window 객체 (테스트/SSR 대응, 기본값 window)
32
+ * @example
33
+ * // In a browser environment:
34
+ * client.appleAuth.login();
35
+ */
36
+ login(windowObj: Window = window): void {
37
+ windowObj.location.href = this.startUrl();
38
+ }
39
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Google Auth API
3
+ *
4
+ * Google OAuth 인증을 위한 헬퍼 클래스입니다.
5
+ * `/auth/google` 관련 엔드포인트 URL을 생성하거나 리다이렉트를 수행합니다.
6
+ *
7
+ * 주요 기능:
8
+ * - 로그인 시작 URL 생성 (`startUrl`)
9
+ * - 로그인 페이지로 리다이렉트 (`login`)
10
+ *
11
+ * @public
12
+ */
13
+ export class GoogleAuthApi {
14
+ constructor(private baseUrl: string) {}
15
+
16
+ /**
17
+ * 로그인 시작 URL을 반환합니다.
18
+ * @returns Google OAuth 시작 URL
19
+ * @example
20
+ * const url = client.googleAuth.startUrl();
21
+ * console.log(url);
22
+ * // Output:
23
+ * // 'https://api.graphnode.dev/auth/google/start'
24
+ */
25
+ startUrl(): string {
26
+ return this.baseUrl.replace(/\/$/, '') + '/auth/google/start';
27
+ }
28
+
29
+ /**
30
+ * 브라우저를 Google 로그인 페이지로 리다이렉트합니다.
31
+ * @param windowObj window 객체 (테스트/SSR 대응, 기본값 window)
32
+ * @example
33
+ * // In a browser environment:
34
+ * client.googleAuth.login();
35
+ */
36
+ login(windowObj: Window = window): void {
37
+ windowObj.location.href = this.startUrl();
38
+ }
39
+ }