Skip to content

Commit 6126553

Browse files
committed
fix(dwkim): migrate stream parser to UI Message Stream format
Server migrated to AI SDK UI Message Stream in 82cf4c2, but CLI was still using Data Stream Protocol parser.
1 parent 25ba5ba commit 6126553

File tree

1 file changed

+44
-67
lines changed

1 file changed

+44
-67
lines changed

packages/dwkim/src/utils/personaApiClient.ts

Lines changed: 44 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,71 @@
11
import { getDeviceId } from './deviceId.js'
22

33
// ─────────────────────────────────────────────────────────────
4-
// AI SDK Data Stream Protocol Parser
5-
// @see https://sdk.vercel.ai/docs/ai-sdk-ui/stream-protocol
4+
// AI SDK UI Message Stream Parser
5+
// @see https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data
66
// ─────────────────────────────────────────────────────────────
77

88
/**
9-
* AI SDK Data Stream Protocol 타입 ID
10-
* 포맷: `{type_id}:{json}\n`
9+
* UI Message Stream 라인을 파싱하여 이벤트로 변환
10+
* 포맷: `data: {...}\n\n` (SSE)
1111
*/
12-
const DATA_STREAM_TYPES = {
13-
TEXT: '0', // 텍스트 청크
14-
DATA: '2', // 커스텀 데이터 파트
15-
ERROR: 'e', // 에러
16-
FINISH: 'd' // 완료
17-
} as const
12+
function parseUIMessageStreamLine(line: string): StreamEvent | null {
13+
// SSE 포맷: "data: {...}" 또는 "data: [DONE]"
14+
if (!line.startsWith('data: ')) return null
1815

19-
/**
20-
* Data Stream 라인을 파싱하여 이벤트로 변환
21-
*/
22-
function parseDataStreamLine(line: string): StreamEvent | null {
23-
if (!line || line.length < 2) return null
24-
25-
const typeId = line[0]
26-
if (line[1] !== ':') return null
27-
28-
const jsonStr = line.slice(2)
16+
const jsonStr = line.slice(6) // "data: " 제거
17+
if (jsonStr === '[DONE]') return null
2918

3019
try {
31-
switch (typeId) {
32-
case DATA_STREAM_TYPES.TEXT: {
33-
const text = JSON.parse(jsonStr) as string
34-
return { type: 'content', content: text }
35-
}
36-
37-
case DATA_STREAM_TYPES.DATA: {
38-
// Data parts는 배열로 옴: [{ type: 'data-xxx', ... }]
39-
const parts = JSON.parse(jsonStr) as Array<{ type: string; [key: string]: unknown }>
40-
for (const part of parts) {
41-
return convertDataPart(part)
42-
}
43-
return null
44-
}
45-
46-
case DATA_STREAM_TYPES.ERROR: {
47-
const error = JSON.parse(jsonStr) as string
48-
return { type: 'error', error }
49-
}
50-
51-
case DATA_STREAM_TYPES.FINISH: {
52-
// finish 이벤트는 무시 (done 이벤트가 별도로 옴)
53-
return null
54-
}
55-
56-
default:
57-
return null
58-
}
20+
const event = JSON.parse(jsonStr) as { type: string; [key: string]: unknown }
21+
return convertUIMessageEvent(event)
5922
} catch {
6023
return null
6124
}
6225
}
6326

6427
/**
65-
* AI SDK 커스텀 데이터 파트를 CLI StreamEvent로 변환
28+
* UI Message Stream 이벤트를 CLI StreamEvent로 변환
6629
*/
67-
function convertDataPart(part: { type: string; [key: string]: unknown }): StreamEvent | null {
68-
switch (part.type) {
30+
function convertUIMessageEvent(event: {
31+
type: string
32+
[key: string]: unknown
33+
}): StreamEvent | null {
34+
switch (event.type) {
35+
case 'text-delta':
36+
return { type: 'content', content: event.delta as string }
37+
6938
case 'data-session':
70-
return { type: 'session', sessionId: part.sessionId as string }
39+
return { type: 'session', sessionId: (event.data as { sessionId: string }).sessionId }
7140

7241
case 'data-sources':
73-
return { type: 'sources', sources: part.sources as Source[] }
42+
return { type: 'sources', sources: (event.data as { sources: Source[] }).sources }
7443

7544
case 'data-progress':
76-
return { type: 'progress', items: part.items as ProgressItem[] }
45+
return { type: 'progress', items: (event.data as { items: ProgressItem[] }).items }
7746

7847
case 'data-clarification':
79-
return { type: 'clarification', suggestedQuestions: part.suggestedQuestions as string[] }
48+
return {
49+
type: 'clarification',
50+
suggestedQuestions: (event.data as { suggestedQuestions: string[] }).suggestedQuestions
51+
}
8052

8153
case 'data-followup':
82-
return { type: 'followup', suggestedQuestions: part.suggestedQuestions as string[] }
54+
return {
55+
type: 'followup',
56+
suggestedQuestions: (event.data as { suggestedQuestions: string[] }).suggestedQuestions
57+
}
8358

8459
case 'data-escalation':
8560
return {
8661
type: 'escalation',
87-
reason: part.reason as string,
88-
uncertainty: part.uncertainty as number
62+
reason: (event.data as { reason: string }).reason,
63+
uncertainty: (event.data as { uncertainty: number }).uncertainty
8964
}
9065

91-
case 'data-done':
92-
return {
93-
type: 'done',
94-
metadata: part.metadata as {
66+
case 'data-done': {
67+
const data = event.data as {
68+
metadata: {
9569
searchQuery: string
9670
searchResults: number
9771
processingTime: number
@@ -104,10 +78,13 @@ function convertDataPart(part: { type: string; [key: string]: unknown }): Stream
10478
confidence?: 'high' | 'medium' | 'low'
10579
}
10680
}
81+
return { type: 'done', metadata: data.metadata }
82+
}
10783

108-
case 'data-error':
109-
return { type: 'error', error: part.error as string }
84+
case 'error':
85+
return { type: 'error', error: event.errorText as string }
11086

87+
// start, text-start, text-end, finish 등은 무시
11188
default:
11289
return null
11390
}
@@ -434,9 +411,9 @@ export class PersonaApiClient {
434411
buffer = lines.pop() || ''
435412

436413
for (const line of lines) {
437-
// AI SDK Data Stream Protocol 파싱
438-
// 포맷: `{type_id}:{json}\n` (예: `0:"hello"`, `2:[{...}]`)
439-
const event = parseDataStreamLine(line)
414+
// AI SDK UI Message Stream 파싱
415+
// 포맷: `data: {...}\n\n` (SSE)
416+
const event = parseUIMessageStreamLine(line)
440417
if (event) {
441418
yield event
442419
}

0 commit comments

Comments
 (0)