@servicetitan/titan-chat-ui-common 7.1.2 → 9.0.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.
- package/CHANGELOG.md +25 -0
- package/dist/hooks/use-customization-chat.js +2 -1
- package/dist/hooks/use-customization-chat.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/models/__mocks__/support-chat.mock.js +13 -12
- package/dist/models/__mocks__/support-chat.mock.js.map +1 -1
- package/dist/models/chat-customizations.js +2 -1
- package/dist/models/chat-customizations.js.map +1 -1
- package/dist/models/file-descriptor.js +2 -1
- package/dist/models/file-descriptor.js.map +1 -1
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +1 -2
- package/dist/models/index.js.map +1 -1
- package/dist/models/support-chat.js +37 -41
- package/dist/models/support-chat.js.map +1 -1
- package/dist/stores/__tests__/chat-ui.store.test.js +117 -106
- package/dist/stores/__tests__/chat-ui.store.test.js.map +1 -1
- package/dist/stores/chat-ui-backend-echo.store.js +86 -87
- package/dist/stores/chat-ui-backend-echo.store.js.map +1 -1
- package/dist/stores/chat-ui-backend.store.js +1 -0
- package/dist/stores/chat-ui-backend.store.js.map +1 -1
- package/dist/stores/chat-ui.store.d.ts +2 -2
- package/dist/stores/chat-ui.store.d.ts.map +1 -1
- package/dist/stores/chat-ui.store.js +307 -329
- package/dist/stores/chat-ui.store.js.map +1 -1
- package/dist/stores/index.d.ts +4 -2
- package/dist/stores/index.d.ts.map +1 -1
- package/dist/stores/index.js +2 -1
- package/dist/stores/index.js.map +1 -1
- package/dist/streaming/__tests__/chat-sse-client-lifecycle.test.d.ts +2 -0
- package/dist/streaming/__tests__/chat-sse-client-lifecycle.test.d.ts.map +1 -0
- package/dist/streaming/__tests__/chat-sse-client-lifecycle.test.js +86 -0
- package/dist/streaming/__tests__/chat-sse-client-lifecycle.test.js.map +1 -0
- package/dist/streaming/__tests__/chat-sse-client-resilience.test.d.ts +2 -0
- package/dist/streaming/__tests__/chat-sse-client-resilience.test.d.ts.map +1 -0
- package/dist/streaming/__tests__/chat-sse-client-resilience.test.js +140 -0
- package/dist/streaming/__tests__/chat-sse-client-resilience.test.js.map +1 -0
- package/dist/streaming/__tests__/chat-sse-client.test.d.ts +2 -0
- package/dist/streaming/__tests__/chat-sse-client.test.d.ts.map +1 -0
- package/dist/streaming/__tests__/chat-sse-client.test.js +209 -0
- package/dist/streaming/__tests__/chat-sse-client.test.js.map +1 -0
- package/dist/streaming/__tests__/mock-wiring.test.d.ts +2 -0
- package/dist/streaming/__tests__/mock-wiring.test.d.ts.map +1 -0
- package/dist/streaming/__tests__/mock-wiring.test.js +20 -0
- package/dist/streaming/__tests__/mock-wiring.test.js.map +1 -0
- package/dist/streaming/__tests__/streaming-progress.model.test.d.ts +2 -0
- package/dist/streaming/__tests__/streaming-progress.model.test.d.ts.map +1 -0
- package/dist/streaming/__tests__/streaming-progress.model.test.js +89 -0
- package/dist/streaming/__tests__/streaming-progress.model.test.js.map +1 -0
- package/dist/streaming/chat-sse-client.d.ts +75 -0
- package/dist/streaming/chat-sse-client.d.ts.map +1 -0
- package/dist/streaming/chat-sse-client.js +189 -0
- package/dist/streaming/chat-sse-client.js.map +1 -0
- package/dist/streaming/index.d.ts +3 -0
- package/dist/streaming/index.d.ts.map +1 -0
- package/dist/streaming/index.js +4 -0
- package/dist/streaming/index.js.map +1 -0
- package/dist/streaming/streaming-progress.model.d.ts +39 -0
- package/dist/streaming/streaming-progress.model.d.ts.map +1 -0
- package/dist/streaming/streaming-progress.model.js +69 -0
- package/dist/streaming/streaming-progress.model.js.map +1 -0
- package/dist/utils/__tests__/text-utils.test.js +33 -14
- package/dist/utils/__tests__/text-utils.test.js.map +1 -1
- package/dist/utils/test-utils.js +5 -5
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/text-utils.js +12 -12
- package/dist/utils/text-utils.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +1 -0
- package/src/models/index.ts +2 -2
- package/src/stores/chat-ui.store.ts +1 -3
- package/src/stores/index.ts +3 -3
- package/src/streaming/__tests__/chat-sse-client-lifecycle.test.ts +67 -0
- package/src/streaming/__tests__/chat-sse-client-resilience.test.ts +141 -0
- package/src/streaming/__tests__/chat-sse-client.test.ts +152 -0
- package/src/streaming/__tests__/mock-wiring.test.ts +25 -0
- package/src/streaming/__tests__/streaming-progress.model.test.ts +72 -0
- package/src/streaming/chat-sse-client.ts +236 -0
- package/src/streaming/index.ts +2 -0
- package/src/streaming/streaming-progress.model.ts +80 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/streaming/__tests__/chat-sse-client.test.ts"],"sourcesContent":["import { beforeEach, describe, expect, jest, test } from '@jest/globals';\nimport { type EventSourceMessage, fetchEventSource } from '@microsoft/fetch-event-source';\nimport { ChatSseClient } from '../chat-sse-client';\n\njest.mock('@microsoft/fetch-event-source');\n\nconst fetchEventSourceMock = fetchEventSource as jest.MockedFunction<typeof fetchEventSource>;\n\n/** Capture the init passed to fetchEventSource so tests can drive its callbacks. */\nfunction captureInit() {\n const capture: { init?: Parameters<typeof fetchEventSource>[1]; url?: any } = {};\n fetchEventSourceMock.mockImplementation((url, init) => {\n capture.url = url;\n capture.init = init;\n return Promise.resolve();\n });\n return capture;\n}\n\nconst msg = (event: string, data: unknown): EventSourceMessage => ({\n id: '',\n event,\n data: typeof data === 'string' ? data : JSON.stringify(data),\n});\n\ndescribe('ChatSseClient — open / decode / dispatch / seq / registry', () => {\n beforeEach(() => {\n fetchEventSourceMock.mockReset();\n });\n\n test('opens a POST stream with body, headers and event-stream Accept', async () => {\n const capture = captureInit();\n\n await new ChatSseClient({\n url: 'https://api.test/api/v2/message/stream',\n body: { question: 'hi', sessionId: 7 },\n headers: { 'Authorization': 'Bearer t', 'X-Client-ID': 'help-center' },\n }).start();\n\n expect(capture.url).toBe('https://api.test/api/v2/message/stream');\n expect(capture.init?.method).toBe('POST');\n expect(capture.init?.body).toBe(JSON.stringify({ question: 'hi', sessionId: 7 }));\n expect(capture.init?.headers).toMatchObject({\n 'Accept': 'text/event-stream',\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer t',\n 'X-Client-ID': 'help-center',\n });\n });\n\n test('JSON-decodes data and dispatches to the registered handler with seq', async () => {\n const capture = captureInit();\n const onStatus = jest.fn();\n\n const client = new ChatSseClient({\n url: 'u',\n handlers: { 'status.changed': onStatus },\n });\n await client.start();\n\n capture.init?.onmessage?.(msg('status.changed', { text: 'Thinking…', seq: 2 }));\n\n expect(onStatus).toHaveBeenCalledTimes(1);\n const [data, event] = onStatus.mock.calls[0] as any[];\n expect(data).toEqual({ text: 'Thinking…', seq: 2 });\n expect(event).toEqual({\n event: 'status.changed',\n data: { text: 'Thinking…', seq: 2 },\n seq: 2,\n });\n });\n\n test('handlers registered via on() receive matching events; consumer can extend new types', async () => {\n const capture = captureInit();\n const onCustom = jest.fn();\n\n const client = new ChatSseClient({ url: 'u' });\n client.on('custom.future-event', onCustom);\n await client.start();\n\n capture.init?.onmessage?.(msg('custom.future-event', { foo: 1, seq: 1 }));\n\n expect(onCustom).toHaveBeenCalledTimes(1);\n expect((onCustom.mock.calls[0] as any[])[0]).toEqual({ foo: 1, seq: 1 });\n });\n\n test('unknown event with no handler is ignored without closing; onEvent still fires', async () => {\n const capture = captureInit();\n const onEvent = jest.fn();\n const onError = jest.fn();\n\n const client = new ChatSseClient({ url: 'u', onEvent, onError });\n await client.start();\n\n expect(() => capture.init?.onmessage?.(msg('totally.unknown', { seq: 1 }))).not.toThrow();\n\n expect(onError).not.toHaveBeenCalled();\n expect(onEvent).toHaveBeenCalledTimes(1);\n expect((onEvent.mock.calls[0] as any[])[0]).toMatchObject({\n event: 'totally.unknown',\n seq: 1,\n });\n });\n\n test('ignores null / non-object JSON payloads without throwing or dispatching', async () => {\n const capture = captureInit();\n const onText = jest.fn();\n const onEvent = jest.fn();\n const onError = jest.fn();\n\n const client = new ChatSseClient({\n url: 'u',\n handlers: { 'text.appended': onText },\n onEvent,\n onError,\n });\n await client.start();\n\n // `JSON.parse` yields a value that has no `seq`; accessing `.seq` on `null` would throw.\n expect(() => capture.init?.onmessage?.(msg('text.appended', null))).not.toThrow();\n expect(() => capture.init?.onmessage?.(msg('text.appended', '42'))).not.toThrow();\n expect(() => capture.init?.onmessage?.(msg('text.appended', '\"hi\"'))).not.toThrow();\n // Unparseable data is likewise skipped.\n expect(() => capture.init?.onmessage?.(msg('text.appended', 'not json'))).not.toThrow();\n\n expect(onText).not.toHaveBeenCalled();\n expect(onEvent).not.toHaveBeenCalled();\n expect(onError).not.toHaveBeenCalled();\n\n // A valid event after the bad ones is still processed (the stream is not wedged).\n capture.init?.onmessage?.(msg('text.appended', { text: 'a', seq: 1 }));\n expect(onText).toHaveBeenCalledTimes(1);\n expect((onText.mock.calls[0] as any[])[0]).toEqual({ text: 'a', seq: 1 });\n });\n\n test('discards events whose seq is not greater than the last processed seq', async () => {\n const capture = captureInit();\n const onText = jest.fn();\n\n const client = new ChatSseClient({ url: 'u', handlers: { 'text.appended': onText } });\n await client.start();\n\n capture.init?.onmessage?.(msg('text.appended', { text: 'a', seq: 5 }));\n capture.init?.onmessage?.(msg('text.appended', { text: 'dup', seq: 5 })); // duplicate\n capture.init?.onmessage?.(msg('text.appended', { text: 'old', seq: 3 })); // out of order\n capture.init?.onmessage?.(msg('text.appended', { text: 'b', seq: 6 })); // newer\n\n expect(onText).toHaveBeenCalledTimes(2);\n expect((onText.mock.calls[0] as any[])[0]).toEqual({ text: 'a', seq: 5 });\n expect((onText.mock.calls[1] as any[])[0]).toEqual({ text: 'b', seq: 6 });\n });\n});\n"],"names":["beforeEach","describe","expect","jest","test","fetchEventSource","ChatSseClient","mock","fetchEventSourceMock","captureInit","capture","mockImplementation","url","init","Promise","resolve","msg","event","data","id","JSON","stringify","mockReset","body","question","sessionId","headers","start","toBe","method","toMatchObject","onStatus","fn","client","handlers","onmessage","text","seq","toHaveBeenCalledTimes","calls","toEqual","onCustom","on","foo","onEvent","onError","not","toThrow","toHaveBeenCalled","onText"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-wiring.test.d.ts","sourceRoot":"","sources":["../../../src/streaming/__tests__/mock-wiring.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
|
|
2
|
+
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
|
3
|
+
jest.mock('@microsoft/fetch-event-source');
|
|
4
|
+
const fetchEventSourceMock = fetchEventSource;
|
|
5
|
+
/**
|
|
6
|
+
* Task 1.2 — proves the test harness can mock `@microsoft/fetch-event-source`
|
|
7
|
+
* before the real streaming-client work begins. This is intentionally trivial.
|
|
8
|
+
*/ describe('fetch-event-source mock wiring', ()=>{
|
|
9
|
+
beforeEach(()=>{
|
|
10
|
+
fetchEventSourceMock.mockReset();
|
|
11
|
+
});
|
|
12
|
+
test('fetchEventSource is mockable and observable', async ()=>{
|
|
13
|
+
fetchEventSourceMock.mockResolvedValue(undefined);
|
|
14
|
+
await fetchEventSource('https://example.test/stream', {});
|
|
15
|
+
expect(fetchEventSourceMock).toHaveBeenCalledTimes(1);
|
|
16
|
+
expect(fetchEventSourceMock).toHaveBeenCalledWith('https://example.test/stream', {});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=mock-wiring.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/streaming/__tests__/mock-wiring.test.ts"],"sourcesContent":["import { beforeEach, describe, expect, jest, test } from '@jest/globals';\nimport { fetchEventSource } from '@microsoft/fetch-event-source';\n\njest.mock('@microsoft/fetch-event-source');\n\nconst fetchEventSourceMock = fetchEventSource as jest.MockedFunction<typeof fetchEventSource>;\n\n/**\n * Task 1.2 — proves the test harness can mock `@microsoft/fetch-event-source`\n * before the real streaming-client work begins. This is intentionally trivial.\n */\ndescribe('fetch-event-source mock wiring', () => {\n beforeEach(() => {\n fetchEventSourceMock.mockReset();\n });\n\n test('fetchEventSource is mockable and observable', async () => {\n fetchEventSourceMock.mockResolvedValue(undefined);\n\n await fetchEventSource('https://example.test/stream', {});\n\n expect(fetchEventSourceMock).toHaveBeenCalledTimes(1);\n expect(fetchEventSourceMock).toHaveBeenCalledWith('https://example.test/stream', {});\n });\n});\n"],"names":["beforeEach","describe","expect","jest","test","fetchEventSource","mock","fetchEventSourceMock","mockReset","mockResolvedValue","undefined","toHaveBeenCalledTimes","toHaveBeenCalledWith"],"mappings":"AAAA,SAASA,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,IAAI,QAAQ,gBAAgB;AACzE,SAASC,gBAAgB,QAAQ,gCAAgC;AAEjEF,KAAKG,IAAI,CAAC;AAEV,MAAMC,uBAAuBF;AAE7B;;;CAGC,GACDJ,SAAS,kCAAkC;IACvCD,WAAW;QACPO,qBAAqBC,SAAS;IAClC;IAEAJ,KAAK,+CAA+C;QAChDG,qBAAqBE,iBAAiB,CAACC;QAEvC,MAAML,iBAAiB,+BAA+B,CAAC;QAEvDH,OAAOK,sBAAsBI,qBAAqB,CAAC;QACnDT,OAAOK,sBAAsBK,oBAAoB,CAAC,+BAA+B,CAAC;IACtF;AACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming-progress.model.test.d.ts","sourceRoot":"","sources":["../../../src/streaming/__tests__/streaming-progress.model.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { StreamingProgressModel } from '../streaming-progress.model';
|
|
3
|
+
describe('StreamingProgressModel', ()=>{
|
|
4
|
+
test('setStatus overwrites the single current status line', ()=>{
|
|
5
|
+
const m = new StreamingProgressModel();
|
|
6
|
+
m.setStatus('Thinking about your question…');
|
|
7
|
+
expect(m.statusText).toBe('Thinking about your question…');
|
|
8
|
+
m.setStatus('Wrapping up…');
|
|
9
|
+
expect(m.statusText).toBe('Wrapping up…');
|
|
10
|
+
});
|
|
11
|
+
test('appendLog accumulates lines in order', ()=>{
|
|
12
|
+
const m = new StreamingProgressModel();
|
|
13
|
+
m.appendLog('✓ searched');
|
|
14
|
+
m.appendLog('✓ summarized');
|
|
15
|
+
expect(m.logLines).toEqual([
|
|
16
|
+
'✓ searched',
|
|
17
|
+
'✓ summarized'
|
|
18
|
+
]);
|
|
19
|
+
});
|
|
20
|
+
test('setSteps then setActiveStep marks the active step and earlier steps done', ()=>{
|
|
21
|
+
const m = new StreamingProgressModel();
|
|
22
|
+
m.setSteps([
|
|
23
|
+
{
|
|
24
|
+
id: '1',
|
|
25
|
+
title: 'Plan'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: '2',
|
|
29
|
+
title: 'Search'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: '3',
|
|
33
|
+
title: 'Answer'
|
|
34
|
+
}
|
|
35
|
+
]);
|
|
36
|
+
m.setActiveStep('2');
|
|
37
|
+
expect(m.steps.map((s)=>s.status)).toEqual([
|
|
38
|
+
'done',
|
|
39
|
+
'active',
|
|
40
|
+
'pending'
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
test('completeAllSteps marks every step done', ()=>{
|
|
44
|
+
const m = new StreamingProgressModel();
|
|
45
|
+
m.setSteps([
|
|
46
|
+
{
|
|
47
|
+
id: '1',
|
|
48
|
+
title: 'Plan'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: '2',
|
|
52
|
+
title: 'Search'
|
|
53
|
+
}
|
|
54
|
+
]);
|
|
55
|
+
m.setActiveStep('1');
|
|
56
|
+
m.completeAllSteps();
|
|
57
|
+
expect(m.steps.map((s)=>s.status)).toEqual([
|
|
58
|
+
'done',
|
|
59
|
+
'done'
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
test('keepalive can be set and cleared', ()=>{
|
|
63
|
+
const m = new StreamingProgressModel();
|
|
64
|
+
expect(m.keepaliveText).toBeUndefined();
|
|
65
|
+
m.setKeepalive('Still working on it…');
|
|
66
|
+
expect(m.keepaliveText).toBe('Still working on it…');
|
|
67
|
+
m.clearKeepalive();
|
|
68
|
+
expect(m.keepaliveText).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
test('reset clears all progress state', ()=>{
|
|
71
|
+
const m = new StreamingProgressModel();
|
|
72
|
+
m.setStatus('x');
|
|
73
|
+
m.appendLog('y');
|
|
74
|
+
m.setSteps([
|
|
75
|
+
{
|
|
76
|
+
id: '1',
|
|
77
|
+
title: 'a'
|
|
78
|
+
}
|
|
79
|
+
]);
|
|
80
|
+
m.setKeepalive('z');
|
|
81
|
+
m.reset();
|
|
82
|
+
expect(m.statusText).toBe('');
|
|
83
|
+
expect(m.logLines).toEqual([]);
|
|
84
|
+
expect(m.steps).toEqual([]);
|
|
85
|
+
expect(m.keepaliveText).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=streaming-progress.model.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/streaming/__tests__/streaming-progress.model.test.ts"],"sourcesContent":["import { describe, expect, test } from '@jest/globals';\nimport { StreamingProgressModel } from '../streaming-progress.model';\n\ndescribe('StreamingProgressModel', () => {\n test('setStatus overwrites the single current status line', () => {\n const m = new StreamingProgressModel();\n m.setStatus('Thinking about your question…');\n expect(m.statusText).toBe('Thinking about your question…');\n\n m.setStatus('Wrapping up…');\n expect(m.statusText).toBe('Wrapping up…');\n });\n\n test('appendLog accumulates lines in order', () => {\n const m = new StreamingProgressModel();\n m.appendLog('✓ searched');\n m.appendLog('✓ summarized');\n expect(m.logLines).toEqual(['✓ searched', '✓ summarized']);\n });\n\n test('setSteps then setActiveStep marks the active step and earlier steps done', () => {\n const m = new StreamingProgressModel();\n m.setSteps([\n { id: '1', title: 'Plan' },\n { id: '2', title: 'Search' },\n { id: '3', title: 'Answer' },\n ]);\n\n m.setActiveStep('2');\n\n expect(m.steps.map(s => s.status)).toEqual(['done', 'active', 'pending']);\n });\n\n test('completeAllSteps marks every step done', () => {\n const m = new StreamingProgressModel();\n m.setSteps([\n { id: '1', title: 'Plan' },\n { id: '2', title: 'Search' },\n ]);\n m.setActiveStep('1');\n\n m.completeAllSteps();\n\n expect(m.steps.map(s => s.status)).toEqual(['done', 'done']);\n });\n\n test('keepalive can be set and cleared', () => {\n const m = new StreamingProgressModel();\n expect(m.keepaliveText).toBeUndefined();\n\n m.setKeepalive('Still working on it…');\n expect(m.keepaliveText).toBe('Still working on it…');\n\n m.clearKeepalive();\n expect(m.keepaliveText).toBeUndefined();\n });\n\n test('reset clears all progress state', () => {\n const m = new StreamingProgressModel();\n m.setStatus('x');\n m.appendLog('y');\n m.setSteps([{ id: '1', title: 'a' }]);\n m.setKeepalive('z');\n\n m.reset();\n\n expect(m.statusText).toBe('');\n expect(m.logLines).toEqual([]);\n expect(m.steps).toEqual([]);\n expect(m.keepaliveText).toBeUndefined();\n });\n});\n"],"names":["describe","expect","test","StreamingProgressModel","m","setStatus","statusText","toBe","appendLog","logLines","toEqual","setSteps","id","title","setActiveStep","steps","map","s","status","completeAllSteps","keepaliveText","toBeUndefined","setKeepalive","clearKeepalive","reset"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,IAAI,QAAQ,gBAAgB;AACvD,SAASC,sBAAsB,QAAQ,8BAA8B;AAErEH,SAAS,0BAA0B;IAC/BE,KAAK,uDAAuD;QACxD,MAAME,IAAI,IAAID;QACdC,EAAEC,SAAS,CAAC;QACZJ,OAAOG,EAAEE,UAAU,EAAEC,IAAI,CAAC;QAE1BH,EAAEC,SAAS,CAAC;QACZJ,OAAOG,EAAEE,UAAU,EAAEC,IAAI,CAAC;IAC9B;IAEAL,KAAK,wCAAwC;QACzC,MAAME,IAAI,IAAID;QACdC,EAAEI,SAAS,CAAC;QACZJ,EAAEI,SAAS,CAAC;QACZP,OAAOG,EAAEK,QAAQ,EAAEC,OAAO,CAAC;YAAC;YAAc;SAAe;IAC7D;IAEAR,KAAK,4EAA4E;QAC7E,MAAME,IAAI,IAAID;QACdC,EAAEO,QAAQ,CAAC;YACP;gBAAEC,IAAI;gBAAKC,OAAO;YAAO;YACzB;gBAAED,IAAI;gBAAKC,OAAO;YAAS;YAC3B;gBAAED,IAAI;gBAAKC,OAAO;YAAS;SAC9B;QAEDT,EAAEU,aAAa,CAAC;QAEhBb,OAAOG,EAAEW,KAAK,CAACC,GAAG,CAACC,CAAAA,IAAKA,EAAEC,MAAM,GAAGR,OAAO,CAAC;YAAC;YAAQ;YAAU;SAAU;IAC5E;IAEAR,KAAK,0CAA0C;QAC3C,MAAME,IAAI,IAAID;QACdC,EAAEO,QAAQ,CAAC;YACP;gBAAEC,IAAI;gBAAKC,OAAO;YAAO;YACzB;gBAAED,IAAI;gBAAKC,OAAO;YAAS;SAC9B;QACDT,EAAEU,aAAa,CAAC;QAEhBV,EAAEe,gBAAgB;QAElBlB,OAAOG,EAAEW,KAAK,CAACC,GAAG,CAACC,CAAAA,IAAKA,EAAEC,MAAM,GAAGR,OAAO,CAAC;YAAC;YAAQ;SAAO;IAC/D;IAEAR,KAAK,oCAAoC;QACrC,MAAME,IAAI,IAAID;QACdF,OAAOG,EAAEgB,aAAa,EAAEC,aAAa;QAErCjB,EAAEkB,YAAY,CAAC;QACfrB,OAAOG,EAAEgB,aAAa,EAAEb,IAAI,CAAC;QAE7BH,EAAEmB,cAAc;QAChBtB,OAAOG,EAAEgB,aAAa,EAAEC,aAAa;IACzC;IAEAnB,KAAK,mCAAmC;QACpC,MAAME,IAAI,IAAID;QACdC,EAAEC,SAAS,CAAC;QACZD,EAAEI,SAAS,CAAC;QACZJ,EAAEO,QAAQ,CAAC;YAAC;gBAAEC,IAAI;gBAAKC,OAAO;YAAI;SAAE;QACpCT,EAAEkB,YAAY,CAAC;QAEflB,EAAEoB,KAAK;QAEPvB,OAAOG,EAAEE,UAAU,EAAEC,IAAI,CAAC;QAC1BN,OAAOG,EAAEK,QAAQ,EAAEC,OAAO,CAAC,EAAE;QAC7BT,OAAOG,EAAEW,KAAK,EAAEL,OAAO,CAAC,EAAE;QAC1BT,OAAOG,EAAEgB,aAAa,EAAEC,aAAa;IACzC;AACJ"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Error raised when the stream cannot be established (non-OK status or wrong content type). */
|
|
2
|
+
export declare class SseConnectError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
constructor(status: number);
|
|
5
|
+
}
|
|
6
|
+
/** A decoded Server-Sent Event: the event name, its JSON-decoded payload, and its sequence number. */
|
|
7
|
+
export interface DecodedSseEvent<TData = any> {
|
|
8
|
+
event: string;
|
|
9
|
+
data: TData;
|
|
10
|
+
seq: number;
|
|
11
|
+
}
|
|
12
|
+
/** Handler invoked for a specific event name with the decoded payload. */
|
|
13
|
+
export type SseEventHandler<TData = any> = (data: TData, event: DecodedSseEvent<TData>) => void;
|
|
14
|
+
export interface ChatSseClientOptions {
|
|
15
|
+
/** Endpoint to open the SSE POST connection against. */
|
|
16
|
+
url: string;
|
|
17
|
+
/** Request body; serialized as JSON when present. */
|
|
18
|
+
body?: unknown;
|
|
19
|
+
/** Extra request headers (e.g. Authorization, X-Client-ID). */
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
/** Abort signal to cancel the stream. */
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
/**
|
|
24
|
+
* Custom `fetch` implementation used for the request — pass the same one the regular API client
|
|
25
|
+
* uses so request configuration (auth token, custom headers, credentials) applies identically.
|
|
26
|
+
* Defaults to the global `fetch`.
|
|
27
|
+
*/
|
|
28
|
+
fetch?: typeof fetch;
|
|
29
|
+
/** Initial handler registry keyed by event name. */
|
|
30
|
+
handlers?: Record<string, SseEventHandler>;
|
|
31
|
+
/** Catch-all invoked for every accepted event, after the keyed handler (if any). */
|
|
32
|
+
onEvent?: (event: DecodedSseEvent) => void;
|
|
33
|
+
/** Predicate marking an event as terminal; when true the run completes and the connection closes. */
|
|
34
|
+
isTerminalEvent?: (event: DecodedSseEvent) => boolean;
|
|
35
|
+
/** Inactivity threshold; when no event arrives within it, `onInactivity` fires (connection stays open). */
|
|
36
|
+
inactivityTimeoutMs?: number;
|
|
37
|
+
/** Invoked when no event has arrived within `inactivityTimeoutMs`. */
|
|
38
|
+
onInactivity?: () => void;
|
|
39
|
+
/** Max reconnect attempts on a transient drop before reporting a fatal error. */
|
|
40
|
+
maxReconnectAttempts?: number;
|
|
41
|
+
/** Lifecycle: connection opened. */
|
|
42
|
+
onConnected?: () => void;
|
|
43
|
+
/** Lifecycle: connection dropped (may be followed by a reconnect). */
|
|
44
|
+
onDisconnected?: () => void;
|
|
45
|
+
/** Lifecycle: inactivity timeout elapsed. */
|
|
46
|
+
onTimeout?: () => void;
|
|
47
|
+
/** Lifecycle: stream completed normally. */
|
|
48
|
+
onCompleted?: () => void;
|
|
49
|
+
/** Lifecycle: fatal error (e.g. reconnects exhausted, connect-time failure). */
|
|
50
|
+
onError?: (err: unknown) => void;
|
|
51
|
+
}
|
|
52
|
+
export declare class ChatSseClient {
|
|
53
|
+
private readonly options;
|
|
54
|
+
private readonly handlers;
|
|
55
|
+
private lastSeq;
|
|
56
|
+
private readonly controller;
|
|
57
|
+
private completed;
|
|
58
|
+
private reconnectAttempts;
|
|
59
|
+
private inactivityTimer?;
|
|
60
|
+
constructor(options: ChatSseClientOptions);
|
|
61
|
+
/** Register or override the handler for an event name. Returns `this` for chaining. */
|
|
62
|
+
on(event: string, handler: SseEventHandler): this;
|
|
63
|
+
/** Remove the handler for an event name. */
|
|
64
|
+
off(event: string): this;
|
|
65
|
+
/** Open the stream and resolve once it completes. */
|
|
66
|
+
start(): Promise<void>;
|
|
67
|
+
private handleOpen;
|
|
68
|
+
private handleError;
|
|
69
|
+
private handleMessage;
|
|
70
|
+
private complete;
|
|
71
|
+
private armInactivityTimer;
|
|
72
|
+
private clearInactivityTimer;
|
|
73
|
+
private decode;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=chat-sse-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-sse-client.d.ts","sourceRoot":"","sources":["../../src/streaming/chat-sse-client.ts"],"names":[],"mappings":"AAMA,gGAAgG;AAChG,qBAAa,eAAgB,SAAQ,KAAK;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAItC;AAED,sGAAsG;AACtG,MAAM,WAAW,eAAe,CAAC,KAAK,GAAG,GAAG;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,MAAM,MAAM,eAAe,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;AAEhG,MAAM,WAAW,oBAAoB;IACjC,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,yCAAyC;IACzC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC3C,oFAAoF;IACpF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC3C,qGAAqG;IACrG,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC;IAEtD,2GAA2G;IAC3G,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAE1B,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAYD,qBAAa,aAAa;IASV,OAAO,CAAC,QAAQ,CAAC,OAAO;IARpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,OAAO,CAAC,OAAO,CAA4B;IAE3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,eAAe,CAAC,CAAgC;gBAE3B,OAAO,EAAE,oBAAoB;IAQ1D,uFAAuF;IACvF,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI;IAKjD,4CAA4C;IAC5C,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKxB,qDAAqD;IAC/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B5B,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,WAAW;IAmBnB,OAAO,CAAC,aAAa;IAqCrB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,MAAM;CAOjB"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source';
|
|
15
|
+
/** Error raised when the stream cannot be established (non-OK status or wrong content type). */ export class SseConnectError extends Error {
|
|
16
|
+
constructor(status){
|
|
17
|
+
super(`SSE connect failed with status ${status}`), _define_property(this, "status", void 0), this.status = status;
|
|
18
|
+
this.name = 'SseConnectError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generic, transport-agnostic SSE consumer for chat streams, built on `fetchEventSource`.
|
|
23
|
+
*
|
|
24
|
+
* Owns only the concerns the transport does not: JSON-decoding event payloads, ordering by the
|
|
25
|
+
* monotonic `seq`, and dispatching to an extensible, consumer-overridable handler registry.
|
|
26
|
+
* SSE wire parsing (multi-line data, comments/heartbeats, partial chunks) is handled by the library.
|
|
27
|
+
*/ const DEFAULT_MAX_RECONNECT_ATTEMPTS = 2;
|
|
28
|
+
const RECONNECT_DELAY_MS = 1000;
|
|
29
|
+
export class ChatSseClient {
|
|
30
|
+
/** Register or override the handler for an event name. Returns `this` for chaining. */ on(event, handler) {
|
|
31
|
+
this.handlers.set(event, handler);
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
/** Remove the handler for an event name. */ off(event) {
|
|
35
|
+
this.handlers.delete(event);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
/** Open the stream and resolve once it completes. */ async start() {
|
|
39
|
+
const { body, fetch: fetchImpl, headers, url } = this.options;
|
|
40
|
+
try {
|
|
41
|
+
await fetchEventSource(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
Accept: 'text/event-stream',
|
|
45
|
+
...body !== undefined ? {
|
|
46
|
+
'Content-Type': 'application/json'
|
|
47
|
+
} : {},
|
|
48
|
+
...headers
|
|
49
|
+
},
|
|
50
|
+
...body !== undefined ? {
|
|
51
|
+
body: JSON.stringify(body)
|
|
52
|
+
} : {},
|
|
53
|
+
...fetchImpl ? {
|
|
54
|
+
fetch: fetchImpl
|
|
55
|
+
} : {},
|
|
56
|
+
signal: this.controller.signal,
|
|
57
|
+
openWhenHidden: true,
|
|
58
|
+
onopen: (response)=>{
|
|
59
|
+
this.handleOpen(response);
|
|
60
|
+
return Promise.resolve();
|
|
61
|
+
},
|
|
62
|
+
onmessage: (ev)=>this.handleMessage(ev),
|
|
63
|
+
onerror: (err)=>this.handleError(err)
|
|
64
|
+
});
|
|
65
|
+
} finally{
|
|
66
|
+
this.clearInactivityTimer();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
handleOpen(response) {
|
|
70
|
+
var _response_headers_get;
|
|
71
|
+
var _this_options_onConnected, _this_options;
|
|
72
|
+
const contentType = (_response_headers_get = response.headers.get('content-type')) !== null && _response_headers_get !== void 0 ? _response_headers_get : '';
|
|
73
|
+
if (!response.ok || !contentType.includes(EventStreamContentType)) {
|
|
74
|
+
throw new SseConnectError(response.status);
|
|
75
|
+
}
|
|
76
|
+
this.reconnectAttempts = 0;
|
|
77
|
+
(_this_options_onConnected = (_this_options = this.options).onConnected) === null || _this_options_onConnected === void 0 ? void 0 : _this_options_onConnected.call(_this_options);
|
|
78
|
+
this.armInactivityTimer();
|
|
79
|
+
}
|
|
80
|
+
handleError(err) {
|
|
81
|
+
var _this_options_maxReconnectAttempts;
|
|
82
|
+
var // Reconnects exhausted (or non-retryable) — fatal.
|
|
83
|
+
_this_options_onError, _this_options;
|
|
84
|
+
// A drop after the run already completed is not an error.
|
|
85
|
+
if (this.completed) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const maxAttempts = (_this_options_maxReconnectAttempts = this.options.maxReconnectAttempts) !== null && _this_options_maxReconnectAttempts !== void 0 ? _this_options_maxReconnectAttempts : DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
89
|
+
// A connect-time failure (bad status / content type) is not retryable.
|
|
90
|
+
if (!(err instanceof SseConnectError) && this.reconnectAttempts < maxAttempts) {
|
|
91
|
+
var _this_options_onDisconnected, _this_options1;
|
|
92
|
+
this.reconnectAttempts += 1;
|
|
93
|
+
(_this_options_onDisconnected = (_this_options1 = this.options).onDisconnected) === null || _this_options_onDisconnected === void 0 ? void 0 : _this_options_onDisconnected.call(_this_options1);
|
|
94
|
+
return RECONNECT_DELAY_MS; // tell fetchEventSource to reconnect
|
|
95
|
+
}
|
|
96
|
+
(_this_options_onError = (_this_options = this.options).onError) === null || _this_options_onError === void 0 ? void 0 : _this_options_onError.call(_this_options, err);
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
handleMessage(ev) {
|
|
100
|
+
var _this_handlers_get, _this_options_onEvent, _this_options, _this_options_isTerminalEvent, _this_options1;
|
|
101
|
+
const data = this.decode(ev.data);
|
|
102
|
+
/*
|
|
103
|
+
* Skip unparseable payloads and non-object JSON (e.g. `null`, bare strings/numbers),
|
|
104
|
+
* which have no `seq` and would throw on property access below.
|
|
105
|
+
*/ if (data === null || typeof data !== 'object') {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
/*
|
|
109
|
+
* Synthesize a monotonic sequence for events with no numeric `seq`. On the first event
|
|
110
|
+
* `lastSeq` is still -Infinity, so fall back to 0 rather than -Infinity + 1 (which would
|
|
111
|
+
* stay -Infinity and be discarded by the guard below).
|
|
112
|
+
*/ const seq = typeof data.seq === 'number' ? data.seq : Number.isFinite(this.lastSeq) ? this.lastSeq + 1 : 0;
|
|
113
|
+
if (seq <= this.lastSeq) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.lastSeq = seq;
|
|
117
|
+
const decoded = {
|
|
118
|
+
event: ev.event,
|
|
119
|
+
data,
|
|
120
|
+
seq
|
|
121
|
+
};
|
|
122
|
+
this.armInactivityTimer();
|
|
123
|
+
(_this_handlers_get = this.handlers.get(ev.event)) === null || _this_handlers_get === void 0 ? void 0 : _this_handlers_get(data, decoded);
|
|
124
|
+
(_this_options_onEvent = (_this_options = this.options).onEvent) === null || _this_options_onEvent === void 0 ? void 0 : _this_options_onEvent.call(_this_options, decoded);
|
|
125
|
+
if ((_this_options_isTerminalEvent = (_this_options1 = this.options).isTerminalEvent) === null || _this_options_isTerminalEvent === void 0 ? void 0 : _this_options_isTerminalEvent.call(_this_options1, decoded)) {
|
|
126
|
+
this.complete();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
complete() {
|
|
130
|
+
var _this_options_onCompleted, _this_options;
|
|
131
|
+
if (this.completed) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this.completed = true;
|
|
135
|
+
this.clearInactivityTimer();
|
|
136
|
+
(_this_options_onCompleted = (_this_options = this.options).onCompleted) === null || _this_options_onCompleted === void 0 ? void 0 : _this_options_onCompleted.call(_this_options);
|
|
137
|
+
this.controller.abort();
|
|
138
|
+
}
|
|
139
|
+
armInactivityTimer() {
|
|
140
|
+
const { inactivityTimeoutMs, onInactivity, onTimeout } = this.options;
|
|
141
|
+
this.clearInactivityTimer();
|
|
142
|
+
if (!inactivityTimeoutMs || this.completed) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.inactivityTimer = setTimeout(()=>{
|
|
146
|
+
onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout();
|
|
147
|
+
onInactivity === null || onInactivity === void 0 ? void 0 : onInactivity();
|
|
148
|
+
}, inactivityTimeoutMs);
|
|
149
|
+
}
|
|
150
|
+
clearInactivityTimer() {
|
|
151
|
+
if (this.inactivityTimer !== undefined) {
|
|
152
|
+
clearTimeout(this.inactivityTimer);
|
|
153
|
+
this.inactivityTimer = undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
decode(raw) {
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(raw);
|
|
159
|
+
} catch (unused) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
constructor(options){
|
|
164
|
+
var _options_handlers;
|
|
165
|
+
var // Cancel our internal connection if the consumer aborts theirs.
|
|
166
|
+
_options_signal;
|
|
167
|
+
_define_property(this, "options", void 0);
|
|
168
|
+
_define_property(this, "handlers", void 0);
|
|
169
|
+
_define_property(this, "lastSeq", void 0);
|
|
170
|
+
_define_property(this, "controller", void 0);
|
|
171
|
+
_define_property(this, "completed", void 0);
|
|
172
|
+
_define_property(this, "reconnectAttempts", void 0);
|
|
173
|
+
_define_property(this, "inactivityTimer", void 0);
|
|
174
|
+
this.options = options;
|
|
175
|
+
this.handlers = new Map();
|
|
176
|
+
this.lastSeq = Number.NEGATIVE_INFINITY;
|
|
177
|
+
this.controller = new AbortController();
|
|
178
|
+
this.completed = false;
|
|
179
|
+
this.reconnectAttempts = 0;
|
|
180
|
+
for (const [event, handler] of Object.entries((_options_handlers = options.handlers) !== null && _options_handlers !== void 0 ? _options_handlers : {})){
|
|
181
|
+
this.handlers.set(event, handler);
|
|
182
|
+
}
|
|
183
|
+
(_options_signal = options.signal) === null || _options_signal === void 0 ? void 0 : _options_signal.addEventListener('abort', ()=>this.controller.abort(), {
|
|
184
|
+
once: true
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//# sourceMappingURL=chat-sse-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/streaming/chat-sse-client.ts"],"sourcesContent":["import {\n type EventSourceMessage,\n EventStreamContentType,\n fetchEventSource,\n} from '@microsoft/fetch-event-source';\n\n/** Error raised when the stream cannot be established (non-OK status or wrong content type). */\nexport class SseConnectError extends Error {\n constructor(readonly status: number) {\n super(`SSE connect failed with status ${status}`);\n this.name = 'SseConnectError';\n }\n}\n\n/** A decoded Server-Sent Event: the event name, its JSON-decoded payload, and its sequence number. */\nexport interface DecodedSseEvent<TData = any> {\n event: string;\n data: TData;\n seq: number;\n}\n\n/** Handler invoked for a specific event name with the decoded payload. */\nexport type SseEventHandler<TData = any> = (data: TData, event: DecodedSseEvent<TData>) => void;\n\nexport interface ChatSseClientOptions {\n /** Endpoint to open the SSE POST connection against. */\n url: string;\n /** Request body; serialized as JSON when present. */\n body?: unknown;\n /** Extra request headers (e.g. Authorization, X-Client-ID). */\n headers?: Record<string, string>;\n /** Abort signal to cancel the stream. */\n signal?: AbortSignal;\n /**\n * Custom `fetch` implementation used for the request — pass the same one the regular API client\n * uses so request configuration (auth token, custom headers, credentials) applies identically.\n * Defaults to the global `fetch`.\n */\n fetch?: typeof fetch;\n /** Initial handler registry keyed by event name. */\n handlers?: Record<string, SseEventHandler>;\n /** Catch-all invoked for every accepted event, after the keyed handler (if any). */\n onEvent?: (event: DecodedSseEvent) => void;\n /** Predicate marking an event as terminal; when true the run completes and the connection closes. */\n isTerminalEvent?: (event: DecodedSseEvent) => boolean;\n\n /** Inactivity threshold; when no event arrives within it, `onInactivity` fires (connection stays open). */\n inactivityTimeoutMs?: number;\n /** Invoked when no event has arrived within `inactivityTimeoutMs`. */\n onInactivity?: () => void;\n\n /** Max reconnect attempts on a transient drop before reporting a fatal error. */\n maxReconnectAttempts?: number;\n\n /** Lifecycle: connection opened. */\n onConnected?: () => void;\n /** Lifecycle: connection dropped (may be followed by a reconnect). */\n onDisconnected?: () => void;\n /** Lifecycle: inactivity timeout elapsed. */\n onTimeout?: () => void;\n /** Lifecycle: stream completed normally. */\n onCompleted?: () => void;\n /** Lifecycle: fatal error (e.g. reconnects exhausted, connect-time failure). */\n onError?: (err: unknown) => void;\n}\n\n/**\n * Generic, transport-agnostic SSE consumer for chat streams, built on `fetchEventSource`.\n *\n * Owns only the concerns the transport does not: JSON-decoding event payloads, ordering by the\n * monotonic `seq`, and dispatching to an extensible, consumer-overridable handler registry.\n * SSE wire parsing (multi-line data, comments/heartbeats, partial chunks) is handled by the library.\n */\nconst DEFAULT_MAX_RECONNECT_ATTEMPTS = 2;\nconst RECONNECT_DELAY_MS = 1_000;\n\nexport class ChatSseClient {\n private readonly handlers = new Map<string, SseEventHandler>();\n private lastSeq = Number.NEGATIVE_INFINITY;\n\n private readonly controller = new AbortController();\n private completed = false;\n private reconnectAttempts = 0;\n private inactivityTimer?: ReturnType<typeof setTimeout>;\n\n constructor(private readonly options: ChatSseClientOptions) {\n for (const [event, handler] of Object.entries(options.handlers ?? {})) {\n this.handlers.set(event, handler);\n }\n // Cancel our internal connection if the consumer aborts theirs.\n options.signal?.addEventListener('abort', () => this.controller.abort(), { once: true });\n }\n\n /** Register or override the handler for an event name. Returns `this` for chaining. */\n on(event: string, handler: SseEventHandler): this {\n this.handlers.set(event, handler);\n return this;\n }\n\n /** Remove the handler for an event name. */\n off(event: string): this {\n this.handlers.delete(event);\n return this;\n }\n\n /** Open the stream and resolve once it completes. */\n async start(): Promise<void> {\n const { body, fetch: fetchImpl, headers, url } = this.options;\n\n try {\n await fetchEventSource(url, {\n method: 'POST',\n headers: {\n Accept: 'text/event-stream',\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...headers,\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n ...(fetchImpl ? { fetch: fetchImpl } : {}),\n signal: this.controller.signal,\n openWhenHidden: true,\n onopen: response => {\n this.handleOpen(response);\n return Promise.resolve();\n },\n onmessage: ev => this.handleMessage(ev),\n onerror: err => this.handleError(err),\n });\n } finally {\n this.clearInactivityTimer();\n }\n }\n\n private handleOpen(response: Response): void {\n const contentType = response.headers.get('content-type') ?? '';\n if (!response.ok || !contentType.includes(EventStreamContentType)) {\n throw new SseConnectError(response.status);\n }\n this.reconnectAttempts = 0;\n this.options.onConnected?.();\n this.armInactivityTimer();\n }\n\n private handleError(err: unknown): number | void {\n // A drop after the run already completed is not an error.\n if (this.completed) {\n return;\n }\n\n const maxAttempts = this.options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;\n // A connect-time failure (bad status / content type) is not retryable.\n if (!(err instanceof SseConnectError) && this.reconnectAttempts < maxAttempts) {\n this.reconnectAttempts += 1;\n this.options.onDisconnected?.();\n return RECONNECT_DELAY_MS; // tell fetchEventSource to reconnect\n }\n\n // Reconnects exhausted (or non-retryable) — fatal.\n this.options.onError?.(err);\n throw err;\n }\n\n private handleMessage(ev: EventSourceMessage): void {\n const data = this.decode(ev.data);\n /*\n * Skip unparseable payloads and non-object JSON (e.g. `null`, bare strings/numbers),\n * which have no `seq` and would throw on property access below.\n */\n if (data === null || typeof data !== 'object') {\n return;\n }\n\n /*\n * Synthesize a monotonic sequence for events with no numeric `seq`. On the first event\n * `lastSeq` is still -Infinity, so fall back to 0 rather than -Infinity + 1 (which would\n * stay -Infinity and be discarded by the guard below).\n */\n const seq =\n typeof data.seq === 'number'\n ? data.seq\n : Number.isFinite(this.lastSeq)\n ? this.lastSeq + 1\n : 0;\n if (seq <= this.lastSeq) {\n return;\n }\n this.lastSeq = seq;\n\n const decoded: DecodedSseEvent = { event: ev.event, data, seq };\n\n this.armInactivityTimer();\n this.handlers.get(ev.event)?.(data, decoded);\n this.options.onEvent?.(decoded);\n\n if (this.options.isTerminalEvent?.(decoded)) {\n this.complete();\n }\n }\n\n private complete(): void {\n if (this.completed) {\n return;\n }\n this.completed = true;\n this.clearInactivityTimer();\n this.options.onCompleted?.();\n this.controller.abort();\n }\n\n private armInactivityTimer(): void {\n const { inactivityTimeoutMs, onInactivity, onTimeout } = this.options;\n this.clearInactivityTimer();\n if (!inactivityTimeoutMs || this.completed) {\n return;\n }\n this.inactivityTimer = setTimeout(() => {\n onTimeout?.();\n onInactivity?.();\n }, inactivityTimeoutMs);\n }\n\n private clearInactivityTimer(): void {\n if (this.inactivityTimer !== undefined) {\n clearTimeout(this.inactivityTimer);\n this.inactivityTimer = undefined;\n }\n }\n\n private decode(raw: string): any {\n try {\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n }\n}\n"],"names":["EventStreamContentType","fetchEventSource","SseConnectError","Error","status","name","DEFAULT_MAX_RECONNECT_ATTEMPTS","RECONNECT_DELAY_MS","ChatSseClient","on","event","handler","handlers","set","off","delete","start","body","fetch","fetchImpl","headers","url","options","method","Accept","undefined","JSON","stringify","signal","controller","openWhenHidden","onopen","response","handleOpen","Promise","resolve","onmessage","ev","handleMessage","onerror","err","handleError","clearInactivityTimer","contentType","get","ok","includes","reconnectAttempts","onConnected","armInactivityTimer","completed","maxAttempts","maxReconnectAttempts","onDisconnected","onError","data","decode","seq","Number","isFinite","lastSeq","decoded","onEvent","isTerminalEvent","complete","onCompleted","abort","inactivityTimeoutMs","onInactivity","onTimeout","inactivityTimer","setTimeout","clearTimeout","raw","parse","Map","NEGATIVE_INFINITY","AbortController","Object","entries","addEventListener","once"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/streaming/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/streaming/index.ts"],"sourcesContent":["export * from './chat-sse-client';\nexport * from './streaming-progress.model';\n"],"names":[],"mappings":"AAAA,cAAc,oBAAoB;AAClC,cAAc,6BAA6B"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type StreamingStepStatus = 'pending' | 'active' | 'done';
|
|
2
|
+
export interface StreamingStep {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
status: StreamingStepStatus;
|
|
7
|
+
}
|
|
8
|
+
export interface StreamingStepInput {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generic, observable progress state for a single streamed run: one overwritable status line,
|
|
15
|
+
* an accumulating activity log, an ordered list of steps with active/done status, and a transient
|
|
16
|
+
* keepalive message. Carries no chatbot-specific event semantics — consumers map their events onto it.
|
|
17
|
+
*/
|
|
18
|
+
export declare class StreamingProgressModel {
|
|
19
|
+
statusText: string;
|
|
20
|
+
logLines: string[];
|
|
21
|
+
steps: StreamingStep[];
|
|
22
|
+
keepaliveText?: string;
|
|
23
|
+
constructor();
|
|
24
|
+
/** Overwrite the single current status line. */
|
|
25
|
+
setStatus(text: string): void;
|
|
26
|
+
/** Append one line to the activity log, preserving arrival order. */
|
|
27
|
+
appendLog(line: string): void;
|
|
28
|
+
/** Replace the step list; all steps start as `pending`. */
|
|
29
|
+
setSteps(steps: StreamingStepInput[]): void;
|
|
30
|
+
/** Mark the given step active; steps before it are `done`, steps after it stay `pending`. */
|
|
31
|
+
setActiveStep(id: string): void;
|
|
32
|
+
/** Mark every step done (e.g. on completion). */
|
|
33
|
+
completeAllSteps(): void;
|
|
34
|
+
setKeepalive(text: string): void;
|
|
35
|
+
clearKeepalive(): void;
|
|
36
|
+
/** Clear all progress state for reuse. */
|
|
37
|
+
reset(): void;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=streaming-progress.model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming-progress.model.d.ts","sourceRoot":"","sources":["../../src/streaming/streaming-progress.model.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEhE,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,mBAAmB,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,qBAAa,sBAAsB;IAC/B,UAAU,SAAM;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAM;IACxB,KAAK,EAAE,aAAa,EAAE,CAAM;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;;IAMvB,gDAAgD;IAChD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI7B,qEAAqE;IACrE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI7B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAI3C,6FAA6F;IAC7F,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAW/B,iDAAiD;IACjD,gBAAgB,IAAI,IAAI;IAIxB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIhC,cAAc,IAAI,IAAI;IAItB,0CAA0C;IAC1C,KAAK,IAAI,IAAI;CAMhB"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
import { makeAutoObservable } from 'mobx';
|
|
15
|
+
/**
|
|
16
|
+
* Generic, observable progress state for a single streamed run: one overwritable status line,
|
|
17
|
+
* an accumulating activity log, an ordered list of steps with active/done status, and a transient
|
|
18
|
+
* keepalive message. Carries no chatbot-specific event semantics — consumers map their events onto it.
|
|
19
|
+
*/ export class StreamingProgressModel {
|
|
20
|
+
/** Overwrite the single current status line. */ setStatus(text) {
|
|
21
|
+
this.statusText = text;
|
|
22
|
+
}
|
|
23
|
+
/** Append one line to the activity log, preserving arrival order. */ appendLog(line) {
|
|
24
|
+
this.logLines.push(line);
|
|
25
|
+
}
|
|
26
|
+
/** Replace the step list; all steps start as `pending`. */ setSteps(steps) {
|
|
27
|
+
this.steps = steps.map((s)=>({
|
|
28
|
+
...s,
|
|
29
|
+
status: 'pending'
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
/** Mark the given step active; steps before it are `done`, steps after it stay `pending`. */ setActiveStep(id) {
|
|
33
|
+
const activeIndex = this.steps.findIndex((s)=>s.id === id);
|
|
34
|
+
if (activeIndex < 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.steps = this.steps.map((step, index)=>({
|
|
38
|
+
...step,
|
|
39
|
+
status: index < activeIndex ? 'done' : index === activeIndex ? 'active' : 'pending'
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
/** Mark every step done (e.g. on completion). */ completeAllSteps() {
|
|
43
|
+
this.steps = this.steps.map((step)=>({
|
|
44
|
+
...step,
|
|
45
|
+
status: 'done'
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
setKeepalive(text) {
|
|
49
|
+
this.keepaliveText = text;
|
|
50
|
+
}
|
|
51
|
+
clearKeepalive() {
|
|
52
|
+
this.keepaliveText = undefined;
|
|
53
|
+
}
|
|
54
|
+
/** Clear all progress state for reuse. */ reset() {
|
|
55
|
+
this.statusText = '';
|
|
56
|
+
this.logLines = [];
|
|
57
|
+
this.steps = [];
|
|
58
|
+
this.keepaliveText = undefined;
|
|
59
|
+
}
|
|
60
|
+
constructor(){
|
|
61
|
+
_define_property(this, "statusText", '');
|
|
62
|
+
_define_property(this, "logLines", []);
|
|
63
|
+
_define_property(this, "steps", []);
|
|
64
|
+
_define_property(this, "keepaliveText", void 0);
|
|
65
|
+
makeAutoObservable(this);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=streaming-progress.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/streaming/streaming-progress.model.ts"],"sourcesContent":["import { makeAutoObservable } from 'mobx';\n\nexport type StreamingStepStatus = 'pending' | 'active' | 'done';\n\nexport interface StreamingStep {\n id: string;\n title: string;\n description?: string;\n status: StreamingStepStatus;\n}\n\nexport interface StreamingStepInput {\n id: string;\n title: string;\n description?: string;\n}\n\n/**\n * Generic, observable progress state for a single streamed run: one overwritable status line,\n * an accumulating activity log, an ordered list of steps with active/done status, and a transient\n * keepalive message. Carries no chatbot-specific event semantics — consumers map their events onto it.\n */\nexport class StreamingProgressModel {\n statusText = '';\n logLines: string[] = [];\n steps: StreamingStep[] = [];\n keepaliveText?: string;\n\n constructor() {\n makeAutoObservable(this);\n }\n\n /** Overwrite the single current status line. */\n setStatus(text: string): void {\n this.statusText = text;\n }\n\n /** Append one line to the activity log, preserving arrival order. */\n appendLog(line: string): void {\n this.logLines.push(line);\n }\n\n /** Replace the step list; all steps start as `pending`. */\n setSteps(steps: StreamingStepInput[]): void {\n this.steps = steps.map(s => ({ ...s, status: 'pending' as const }));\n }\n\n /** Mark the given step active; steps before it are `done`, steps after it stay `pending`. */\n setActiveStep(id: string): void {\n const activeIndex = this.steps.findIndex(s => s.id === id);\n if (activeIndex < 0) {\n return;\n }\n this.steps = this.steps.map((step, index) => ({\n ...step,\n status: index < activeIndex ? 'done' : index === activeIndex ? 'active' : 'pending',\n }));\n }\n\n /** Mark every step done (e.g. on completion). */\n completeAllSteps(): void {\n this.steps = this.steps.map(step => ({ ...step, status: 'done' as const }));\n }\n\n setKeepalive(text: string): void {\n this.keepaliveText = text;\n }\n\n clearKeepalive(): void {\n this.keepaliveText = undefined;\n }\n\n /** Clear all progress state for reuse. */\n reset(): void {\n this.statusText = '';\n this.logLines = [];\n this.steps = [];\n this.keepaliveText = undefined;\n }\n}\n"],"names":["makeAutoObservable","StreamingProgressModel","setStatus","text","statusText","appendLog","line","logLines","push","setSteps","steps","map","s","status","setActiveStep","id","activeIndex","findIndex","step","index","completeAllSteps","setKeepalive","keepaliveText","clearKeepalive","undefined","reset"],"mappings":";;;;;;;;;;;;;AAAA,SAASA,kBAAkB,QAAQ,OAAO;AAiB1C;;;;CAIC,GACD,OAAO,MAAMC;IAUT,8CAA8C,GAC9CC,UAAUC,IAAY,EAAQ;QAC1B,IAAI,CAACC,UAAU,GAAGD;IACtB;IAEA,mEAAmE,GACnEE,UAAUC,IAAY,EAAQ;QAC1B,IAAI,CAACC,QAAQ,CAACC,IAAI,CAACF;IACvB;IAEA,yDAAyD,GACzDG,SAASC,KAA2B,EAAQ;QACxC,IAAI,CAACA,KAAK,GAAGA,MAAMC,GAAG,CAACC,CAAAA,IAAM,CAAA;gBAAE,GAAGA,CAAC;gBAAEC,QAAQ;YAAmB,CAAA;IACpE;IAEA,2FAA2F,GAC3FC,cAAcC,EAAU,EAAQ;QAC5B,MAAMC,cAAc,IAAI,CAACN,KAAK,CAACO,SAAS,CAACL,CAAAA,IAAKA,EAAEG,EAAE,KAAKA;QACvD,IAAIC,cAAc,GAAG;YACjB;QACJ;QACA,IAAI,CAACN,KAAK,GAAG,IAAI,CAACA,KAAK,CAACC,GAAG,CAAC,CAACO,MAAMC,QAAW,CAAA;gBAC1C,GAAGD,IAAI;gBACPL,QAAQM,QAAQH,cAAc,SAASG,UAAUH,cAAc,WAAW;YAC9E,CAAA;IACJ;IAEA,+CAA+C,GAC/CI,mBAAyB;QACrB,IAAI,CAACV,KAAK,GAAG,IAAI,CAACA,KAAK,CAACC,GAAG,CAACO,CAAAA,OAAS,CAAA;gBAAE,GAAGA,IAAI;gBAAEL,QAAQ;YAAgB,CAAA;IAC5E;IAEAQ,aAAalB,IAAY,EAAQ;QAC7B,IAAI,CAACmB,aAAa,GAAGnB;IACzB;IAEAoB,iBAAuB;QACnB,IAAI,CAACD,aAAa,GAAGE;IACzB;IAEA,wCAAwC,GACxCC,QAAc;QACV,IAAI,CAACrB,UAAU,GAAG;QAClB,IAAI,CAACG,QAAQ,GAAG,EAAE;QAClB,IAAI,CAACG,KAAK,GAAG,EAAE;QACf,IAAI,CAACY,aAAa,GAAGE;IACzB;IAlDA,aAAc;QALdpB,uBAAAA,cAAa;QACbG,uBAAAA,YAAqB,EAAE;QACvBG,uBAAAA,SAAyB,EAAE;QAC3BY,uBAAAA,iBAAAA,KAAAA;QAGItB,mBAAmB,IAAI;IAC3B;AAiDJ"}
|