@servicetitan/titan-chatbot-api 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/api-client/__mocks__/chatbot-api-client.mock.d.ts +1 -0
- package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts.map +1 -1
- package/dist/api-client/__mocks__/chatbot-api-client.mock.js +22 -47
- package/dist/api-client/__mocks__/chatbot-api-client.mock.js.map +1 -1
- package/dist/api-client/base/chatbot-api-client.d.ts +7 -0
- package/dist/api-client/base/chatbot-api-client.d.ts.map +1 -1
- package/dist/api-client/base/chatbot-api-client.js +3 -4
- package/dist/api-client/base/chatbot-api-client.js.map +1 -1
- package/dist/api-client/index.d.ts +2 -2
- package/dist/api-client/index.d.ts.map +1 -1
- package/dist/api-client/index.js +12 -7
- package/dist/api-client/index.js.map +1 -1
- package/dist/api-client/models/__mocks__/models.mock.js +154 -124
- package/dist/api-client/models/__mocks__/models.mock.js.map +1 -1
- package/dist/api-client/models/index.d.ts +2 -1
- package/dist/api-client/models/index.d.ts.map +1 -1
- package/dist/api-client/models/index.js +8 -7
- package/dist/api-client/models/index.js.map +1 -1
- package/dist/api-client/titan-chat/__tests__/chatbot-api-client-stream.test.d.ts +2 -0
- package/dist/api-client/titan-chat/__tests__/chatbot-api-client-stream.test.d.ts.map +1 -0
- package/dist/api-client/titan-chat/__tests__/chatbot-api-client-stream.test.js +240 -0
- package/dist/api-client/titan-chat/__tests__/chatbot-api-client-stream.test.js.map +1 -0
- package/dist/api-client/titan-chat/__tests__/native-client.test.js +6 -6
- package/dist/api-client/titan-chat/__tests__/native-client.test.js.map +1 -1
- package/dist/api-client/titan-chat/chatbot-api-client.d.ts +11 -0
- package/dist/api-client/titan-chat/chatbot-api-client.d.ts.map +1 -1
- package/dist/api-client/titan-chat/chatbot-api-client.js +69 -35
- package/dist/api-client/titan-chat/chatbot-api-client.js.map +1 -1
- package/dist/api-client/titan-chat/index.d.ts +2 -1
- package/dist/api-client/titan-chat/index.d.ts.map +1 -1
- package/dist/api-client/titan-chat/index.js +1 -0
- package/dist/api-client/titan-chat/index.js.map +1 -1
- package/dist/api-client/titan-chat/native-client.js +359 -812
- package/dist/api-client/titan-chat/native-client.js.map +1 -1
- package/dist/api-client/utils/__tests__/model-utils.test.js +454 -191
- package/dist/api-client/utils/__tests__/model-utils.test.js.map +1 -1
- package/dist/api-client/utils/model-utils.d.ts.map +1 -1
- package/dist/api-client/utils/model-utils.js +28 -25
- package/dist/api-client/utils/model-utils.js.map +1 -1
- package/dist/hooks/use-customization-chatbot.js +2 -1
- package/dist/hooks/use-customization-chatbot.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/models/__tests__/chatbot-customizations.test.d.ts +2 -0
- package/dist/models/__tests__/chatbot-customizations.test.d.ts.map +1 -0
- package/dist/models/__tests__/chatbot-customizations.test.js +36 -0
- package/dist/models/__tests__/chatbot-customizations.test.js.map +1 -0
- package/dist/models/chatbot-customizations.d.ts +17 -0
- package/dist/models/chatbot-customizations.d.ts.map +1 -1
- package/dist/models/chatbot-customizations.js +7 -1
- package/dist/models/chatbot-customizations.js.map +1 -1
- package/dist/models/index.js +1 -0
- package/dist/models/index.js.map +1 -1
- package/dist/stores/__tests__/chatbot-ui-backend.store.observability.test.d.ts +2 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.observability.test.d.ts.map +1 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.observability.test.js +107 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.observability.test.js.map +1 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.streaming.test.d.ts +2 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.streaming.test.d.ts.map +1 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.streaming.test.js +312 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.streaming.test.js.map +1 -0
- package/dist/stores/__tests__/chatbot-ui-backend.store.test.js +267 -172
- package/dist/stores/__tests__/chatbot-ui-backend.store.test.js.map +1 -1
- package/dist/stores/__tests__/chatbot-ui.store.test.js +61 -64
- package/dist/stores/__tests__/chatbot-ui.store.test.js.map +1 -1
- package/dist/stores/__tests__/filter.store.test.js +243 -116
- package/dist/stores/__tests__/filter.store.test.js.map +1 -1
- package/dist/stores/__tests__/initialize.store.test.js +9 -8
- package/dist/stores/__tests__/initialize.store.test.js.map +1 -1
- package/dist/stores/__tests__/message-feedback-guardrail.store.test.js +8 -7
- package/dist/stores/__tests__/message-feedback-guardrail.store.test.js.map +1 -1
- package/dist/stores/__tests__/message-feedback.store.test.js +34 -27
- package/dist/stores/__tests__/message-feedback.store.test.js.map +1 -1
- package/dist/stores/__tests__/session-feedback.store.test.js +9 -8
- package/dist/stores/__tests__/session-feedback.store.test.js.map +1 -1
- package/dist/stores/chatbot-ui-backend.store.d.ts +26 -2
- package/dist/stores/chatbot-ui-backend.store.d.ts.map +1 -1
- package/dist/stores/chatbot-ui-backend.store.js +295 -239
- package/dist/stores/chatbot-ui-backend.store.js.map +1 -1
- package/dist/stores/chatbot-ui.store.js +73 -46
- package/dist/stores/chatbot-ui.store.js.map +1 -1
- package/dist/stores/filter.store.js +298 -378
- package/dist/stores/filter.store.js.map +1 -1
- package/dist/stores/index.d.ts +5 -3
- package/dist/stores/index.d.ts.map +1 -1
- package/dist/stores/index.js +3 -2
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/initialize.store.js +55 -51
- package/dist/stores/initialize.store.js.map +1 -1
- package/dist/stores/message-feedback-base.store.js +2 -1
- package/dist/stores/message-feedback-base.store.js.map +1 -1
- package/dist/stores/message-feedback-guardrail.store.js +50 -47
- package/dist/stores/message-feedback-guardrail.store.js.map +1 -1
- package/dist/stores/message-feedback.store.js +84 -89
- package/dist/stores/message-feedback.store.js.map +1 -1
- package/dist/stores/session-feedback.store.js +46 -39
- package/dist/stores/session-feedback.store.js.map +1 -1
- package/dist/streaming/__tests__/agent-stream.test.d.ts +2 -0
- package/dist/streaming/__tests__/agent-stream.test.d.ts.map +1 -0
- package/dist/streaming/__tests__/agent-stream.test.js +92 -0
- package/dist/streaming/__tests__/agent-stream.test.js.map +1 -0
- package/dist/streaming/agent-stream.d.ts +83 -0
- package/dist/streaming/agent-stream.d.ts.map +1 -0
- package/dist/streaming/agent-stream.js +28 -0
- package/dist/streaming/agent-stream.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/run-agent-stream.d.ts +23 -0
- package/dist/streaming/run-agent-stream.d.ts.map +1 -0
- package/dist/streaming/run-agent-stream.js +83 -0
- package/dist/streaming/run-agent-stream.js.map +1 -0
- package/dist/utils/__tests__/axios-utils.test.js +8 -7
- package/dist/utils/__tests__/axios-utils.test.js.map +1 -1
- package/dist/utils/axios-utils.js +9 -7
- package/dist/utils/axios-utils.js.map +1 -1
- package/dist/utils/test-utils.js +5 -5
- package/dist/utils/test-utils.js.map +1 -1
- package/package.json +6 -3
- package/src/api-client/__mocks__/chatbot-api-client.mock.ts +1 -0
- package/src/api-client/base/chatbot-api-client.ts +11 -0
- package/src/api-client/index.ts +2 -7
- package/src/api-client/models/index.ts +15 -13
- package/src/api-client/titan-chat/__tests__/chatbot-api-client-stream.test.ts +208 -0
- package/src/api-client/titan-chat/chatbot-api-client.ts +46 -0
- package/src/api-client/titan-chat/index.ts +2 -1
- package/src/api-client/utils/model-utils.ts +4 -8
- package/src/index.ts +7 -2
- package/src/models/__tests__/chatbot-customizations.test.ts +26 -0
- package/src/models/chatbot-customizations.ts +20 -0
- package/src/stores/__tests__/chatbot-ui-backend.store.observability.test.ts +105 -0
- package/src/stores/__tests__/chatbot-ui-backend.store.streaming.test.ts +261 -0
- package/src/stores/chatbot-ui-backend.store.ts +179 -4
- package/src/stores/index.ts +5 -12
- package/src/streaming/__tests__/agent-stream.test.ts +80 -0
- package/src/streaming/agent-stream.ts +103 -0
- package/src/streaming/index.ts +2 -0
- package/src/streaming/run-agent-stream.ts +109 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts +0 -2
- package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts.map +0 -1
- package/dist/api-client/help-center/__tests__/converter-from-models.test.js +0 -34
- package/dist/api-client/help-center/__tests__/converter-from-models.test.js.map +0 -1
- package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts +0 -2
- package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts.map +0 -1
- package/dist/api-client/help-center/__tests__/converter-to-models.test.js +0 -82
- package/dist/api-client/help-center/__tests__/converter-to-models.test.js.map +0 -1
- package/dist/api-client/help-center/chatbot-api-client.d.ts +0 -32
- package/dist/api-client/help-center/chatbot-api-client.d.ts.map +0 -1
- package/dist/api-client/help-center/chatbot-api-client.js +0 -102
- package/dist/api-client/help-center/chatbot-api-client.js.map +0 -1
- package/dist/api-client/help-center/converter-from-models.d.ts +0 -13
- package/dist/api-client/help-center/converter-from-models.d.ts.map +0 -1
- package/dist/api-client/help-center/converter-from-models.js +0 -114
- package/dist/api-client/help-center/converter-from-models.js.map +0 -1
- package/dist/api-client/help-center/converter-to-models.d.ts +0 -13
- package/dist/api-client/help-center/converter-to-models.d.ts.map +0 -1
- package/dist/api-client/help-center/converter-to-models.js +0 -98
- package/dist/api-client/help-center/converter-to-models.js.map +0 -1
- package/dist/api-client/help-center/index.d.ts +0 -2
- package/dist/api-client/help-center/index.d.ts.map +0 -1
- package/dist/api-client/help-center/index.js +0 -2
- package/dist/api-client/help-center/index.js.map +0 -1
- package/dist/api-client/help-center/native-client.d.ts +0 -1268
- package/dist/api-client/help-center/native-client.d.ts.map +0 -1
- package/dist/api-client/help-center/native-client.js +0 -6242
- package/dist/api-client/help-center/native-client.js.map +0 -1
- package/src/api-client/help-center/__tests__/converter-from-models.test.ts +0 -41
- package/src/api-client/help-center/__tests__/converter-to-models.test.ts +0 -89
- package/src/api-client/help-center/chatbot-api-client.ts +0 -122
- package/src/api-client/help-center/converter-from-models.ts +0 -133
- package/src/api-client/help-center/converter-to-models.ts +0 -127
- package/src/api-client/help-center/index.ts +0 -1
- package/src/api-client/help-center/native-client.ts +0 -5727
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/streaming/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/streaming/index.ts"],"sourcesContent":["export * from './agent-stream';\nexport * from './run-agent-stream';\n"],"names":[],"mappings":"AAAA,cAAc,iBAAiB;AAC/B,cAAc,qBAAqB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Models } from '../api-client';
|
|
2
|
+
import { AgentStreamHandlers } from './agent-stream';
|
|
3
|
+
export interface RunAgentStreamOptions {
|
|
4
|
+
/** Fully-constructed streaming endpoint URL. */
|
|
5
|
+
url: string;
|
|
6
|
+
/** Request headers (auth, X-Client-ID, …). */
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
/** Request body, serialized as JSON by the SSE client. */
|
|
9
|
+
body: unknown;
|
|
10
|
+
/** Custom `fetch` (same as the regular API client) so auth/headers/credentials apply identically. */
|
|
11
|
+
fetch?: typeof fetch;
|
|
12
|
+
/** Progress + lifecycle callbacks (and the inactivity timeout). */
|
|
13
|
+
handlers: AgentStreamHandlers;
|
|
14
|
+
abortSignal?: AbortSignal;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Shared SSE consumption used by every {@link IChatbotApiClient} adapter: opens a {@link ChatSseClient},
|
|
18
|
+
* maps the agent events onto the supplied handlers, and resolves the final {@link Models.BotMessage}
|
|
19
|
+
* from `run.finished` (or rejects on `run.error` / a fatal connection error). The only per-adapter
|
|
20
|
+
* differences — the URL, headers and body — are passed in by the caller.
|
|
21
|
+
*/
|
|
22
|
+
export declare function runAgentStream({ abortSignal, body, fetch, handlers, headers, url, }: RunAgentStreamOptions): Promise<Models.BotMessage>;
|
|
23
|
+
//# sourceMappingURL=run-agent-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-agent-stream.d.ts","sourceRoot":"","sources":["../../src/streaming/run-agent-stream.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAKH,mBAAmB,EAEtB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,qBAAqB;IAClC,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,0DAA0D;IAC1D,IAAI,EAAE,OAAO,CAAC;IACd,qGAAqG;IACrG,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,mEAAmE;IACnE,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC3B,WAAW,EACX,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,OAAO,EACP,GAAG,GACN,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAsEpD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ChatSseClient } from '@servicetitan/titan-chat-ui-common';
|
|
2
|
+
import { AGENT_EVENT, AgentStreamError, convertAgentFinishToBotMessage } from './agent-stream';
|
|
3
|
+
/**
|
|
4
|
+
* Shared SSE consumption used by every {@link IChatbotApiClient} adapter: opens a {@link ChatSseClient},
|
|
5
|
+
* maps the agent events onto the supplied handlers, and resolves the final {@link Models.BotMessage}
|
|
6
|
+
* from `run.finished` (or rejects on `run.error` / a fatal connection error). The only per-adapter
|
|
7
|
+
* differences — the URL, headers and body — are passed in by the caller.
|
|
8
|
+
*/ export function runAgentStream({ abortSignal, body, fetch, handlers, headers, url }) {
|
|
9
|
+
return new Promise((resolve, reject)=>{
|
|
10
|
+
let finished;
|
|
11
|
+
const client = new ChatSseClient({
|
|
12
|
+
url,
|
|
13
|
+
headers,
|
|
14
|
+
fetch,
|
|
15
|
+
signal: abortSignal,
|
|
16
|
+
body,
|
|
17
|
+
inactivityTimeoutMs: handlers.inactivityTimeoutMs,
|
|
18
|
+
isTerminalEvent: (e)=>e.event === AGENT_EVENT.RunFinished || e.event === AGENT_EVENT.RunError,
|
|
19
|
+
handlers: {
|
|
20
|
+
[AGENT_EVENT.StatusChanged]: (d)=>{
|
|
21
|
+
var _handlers_onStatus;
|
|
22
|
+
(_handlers_onStatus = handlers.onStatus) === null || _handlers_onStatus === void 0 ? void 0 : _handlers_onStatus.call(handlers, d.text);
|
|
23
|
+
if (d.stepId !== undefined) {
|
|
24
|
+
var _handlers_onStepActive;
|
|
25
|
+
(_handlers_onStepActive = handlers.onStepActive) === null || _handlers_onStepActive === void 0 ? void 0 : _handlers_onStepActive.call(handlers, d.stepId);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
[AGENT_EVENT.TextAppended]: (d)=>{
|
|
29
|
+
var _handlers_onText;
|
|
30
|
+
return (_handlers_onText = handlers.onText) === null || _handlers_onText === void 0 ? void 0 : _handlers_onText.call(handlers, d.text);
|
|
31
|
+
},
|
|
32
|
+
[AGENT_EVENT.PlanProposed]: (d)=>{
|
|
33
|
+
var _d_activeStepId;
|
|
34
|
+
var _handlers_onPlan, _d_steps_, _d_steps;
|
|
35
|
+
(_handlers_onPlan = handlers.onPlan) === null || _handlers_onPlan === void 0 ? void 0 : _handlers_onPlan.call(handlers, d.steps);
|
|
36
|
+
// Activate the explicitly-signalled step, else default to the first.
|
|
37
|
+
const activeId = (_d_activeStepId = d.activeStepId) !== null && _d_activeStepId !== void 0 ? _d_activeStepId : (_d_steps = d.steps) === null || _d_steps === void 0 ? void 0 : (_d_steps_ = _d_steps[0]) === null || _d_steps_ === void 0 ? void 0 : _d_steps_.id;
|
|
38
|
+
if (activeId !== undefined) {
|
|
39
|
+
var _handlers_onStepActive;
|
|
40
|
+
(_handlers_onStepActive = handlers.onStepActive) === null || _handlers_onStepActive === void 0 ? void 0 : _handlers_onStepActive.call(handlers, activeId);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
[AGENT_EVENT.InputRequested]: (d)=>{
|
|
44
|
+
var _handlers_onInputRequested;
|
|
45
|
+
return (_handlers_onInputRequested = handlers.onInputRequested) === null || _handlers_onInputRequested === void 0 ? void 0 : _handlers_onInputRequested.call(handlers, {
|
|
46
|
+
kind: d.kind,
|
|
47
|
+
prompt: d.prompt,
|
|
48
|
+
options: d.options
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
[AGENT_EVENT.RunFinished]: (d)=>{
|
|
52
|
+
finished = d;
|
|
53
|
+
},
|
|
54
|
+
[AGENT_EVENT.RunError]: (d)=>{
|
|
55
|
+
reject(new AgentStreamError(d.message));
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
onConnected: handlers.onConnected,
|
|
59
|
+
onDisconnected: handlers.onDisconnected,
|
|
60
|
+
onTimeout: handlers.onTimeout,
|
|
61
|
+
onInactivity: handlers.onInactivity,
|
|
62
|
+
onCompleted: handlers.onCompleted,
|
|
63
|
+
onError: reject
|
|
64
|
+
});
|
|
65
|
+
client.start().then(()=>{
|
|
66
|
+
if (finished) {
|
|
67
|
+
try {
|
|
68
|
+
resolve(convertAgentFinishToBotMessage(finished));
|
|
69
|
+
} catch (err) {
|
|
70
|
+
reject(err);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
/*
|
|
75
|
+
* Stream completed without `run.finished`. A run.error / fatal onError may have
|
|
76
|
+
* already rejected (subsequent reject calls are no-ops); otherwise the server closed
|
|
77
|
+
* the connection unexpectedly — reject so the returned Promise never hangs.
|
|
78
|
+
*/ reject(new AgentStreamError('Stream ended without a run.finished event'));
|
|
79
|
+
}, reject);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=run-agent-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/streaming/run-agent-stream.ts"],"sourcesContent":["import { ChatSseClient } from '@servicetitan/titan-chat-ui-common';\nimport { Models } from '../api-client';\nimport {\n AGENT_EVENT,\n AgentPlanStep,\n AgentRunFinishedData,\n AgentStreamError,\n AgentStreamHandlers,\n convertAgentFinishToBotMessage,\n} from './agent-stream';\n\nexport interface RunAgentStreamOptions {\n /** Fully-constructed streaming endpoint URL. */\n url: string;\n /** Request headers (auth, X-Client-ID, …). */\n headers?: Record<string, string>;\n /** Request body, serialized as JSON by the SSE client. */\n body: unknown;\n /** Custom `fetch` (same as the regular API client) so auth/headers/credentials apply identically. */\n fetch?: typeof fetch;\n /** Progress + lifecycle callbacks (and the inactivity timeout). */\n handlers: AgentStreamHandlers;\n abortSignal?: AbortSignal;\n}\n\n/**\n * Shared SSE consumption used by every {@link IChatbotApiClient} adapter: opens a {@link ChatSseClient},\n * maps the agent events onto the supplied handlers, and resolves the final {@link Models.BotMessage}\n * from `run.finished` (or rejects on `run.error` / a fatal connection error). The only per-adapter\n * differences — the URL, headers and body — are passed in by the caller.\n */\nexport function runAgentStream({\n abortSignal,\n body,\n fetch,\n handlers,\n headers,\n url,\n}: RunAgentStreamOptions): Promise<Models.BotMessage> {\n return new Promise<Models.BotMessage>((resolve, reject) => {\n let finished: AgentRunFinishedData | undefined;\n\n const client = new ChatSseClient({\n url,\n headers,\n fetch,\n signal: abortSignal,\n body,\n inactivityTimeoutMs: handlers.inactivityTimeoutMs,\n isTerminalEvent: e =>\n e.event === AGENT_EVENT.RunFinished || e.event === AGENT_EVENT.RunError,\n handlers: {\n [AGENT_EVENT.StatusChanged]: (d: { text: string; stepId?: string }) => {\n handlers.onStatus?.(d.text);\n if (d.stepId !== undefined) {\n handlers.onStepActive?.(d.stepId);\n }\n },\n [AGENT_EVENT.TextAppended]: (d: { text: string }) => handlers.onText?.(d.text),\n [AGENT_EVENT.PlanProposed]: (d: {\n steps: AgentPlanStep[];\n activeStepId?: string;\n }) => {\n handlers.onPlan?.(d.steps);\n // Activate the explicitly-signalled step, else default to the first.\n const activeId = d.activeStepId ?? d.steps?.[0]?.id;\n if (activeId !== undefined) {\n handlers.onStepActive?.(activeId);\n }\n },\n [AGENT_EVENT.InputRequested]: (d: any) =>\n handlers.onInputRequested?.({\n kind: d.kind,\n prompt: d.prompt,\n options: d.options,\n }),\n [AGENT_EVENT.RunFinished]: (d: AgentRunFinishedData) => {\n finished = d;\n },\n [AGENT_EVENT.RunError]: (d: { message?: string }) => {\n reject(new AgentStreamError(d.message));\n },\n },\n onConnected: handlers.onConnected,\n onDisconnected: handlers.onDisconnected,\n onTimeout: handlers.onTimeout,\n onInactivity: handlers.onInactivity,\n onCompleted: handlers.onCompleted,\n onError: reject,\n });\n\n client.start().then(() => {\n if (finished) {\n try {\n resolve(convertAgentFinishToBotMessage(finished));\n } catch (err) {\n reject(err);\n }\n return;\n }\n /*\n * Stream completed without `run.finished`. A run.error / fatal onError may have\n * already rejected (subsequent reject calls are no-ops); otherwise the server closed\n * the connection unexpectedly — reject so the returned Promise never hangs.\n */\n reject(new AgentStreamError('Stream ended without a run.finished event'));\n }, reject);\n });\n}\n"],"names":["ChatSseClient","AGENT_EVENT","AgentStreamError","convertAgentFinishToBotMessage","runAgentStream","abortSignal","body","fetch","handlers","headers","url","Promise","resolve","reject","finished","client","signal","inactivityTimeoutMs","isTerminalEvent","e","event","RunFinished","RunError","StatusChanged","d","onStatus","text","stepId","undefined","onStepActive","TextAppended","onText","PlanProposed","onPlan","steps","activeId","activeStepId","id","InputRequested","onInputRequested","kind","prompt","options","message","onConnected","onDisconnected","onTimeout","onInactivity","onCompleted","onError","start","then","err"],"mappings":"AAAA,SAASA,aAAa,QAAQ,qCAAqC;AAEnE,SACIC,WAAW,EAGXC,gBAAgB,EAEhBC,8BAA8B,QAC3B,iBAAiB;AAgBxB;;;;;CAKC,GACD,OAAO,SAASC,eAAe,EAC3BC,WAAW,EACXC,IAAI,EACJC,KAAK,EACLC,QAAQ,EACRC,OAAO,EACPC,GAAG,EACiB;IACpB,OAAO,IAAIC,QAA2B,CAACC,SAASC;QAC5C,IAAIC;QAEJ,MAAMC,SAAS,IAAIf,cAAc;YAC7BU;YACAD;YACAF;YACAS,QAAQX;YACRC;YACAW,qBAAqBT,SAASS,mBAAmB;YACjDC,iBAAiBC,CAAAA,IACbA,EAAEC,KAAK,KAAKnB,YAAYoB,WAAW,IAAIF,EAAEC,KAAK,KAAKnB,YAAYqB,QAAQ;YAC3Ed,UAAU;gBACN,CAACP,YAAYsB,aAAa,CAAC,EAAE,CAACC;wBAC1BhB;qBAAAA,qBAAAA,SAASiB,QAAQ,cAAjBjB,yCAAAA,wBAAAA,UAAoBgB,EAAEE,IAAI;oBAC1B,IAAIF,EAAEG,MAAM,KAAKC,WAAW;4BACxBpB;yBAAAA,yBAAAA,SAASqB,YAAY,cAArBrB,6CAAAA,4BAAAA,UAAwBgB,EAAEG,MAAM;oBACpC;gBACJ;gBACA,CAAC1B,YAAY6B,YAAY,CAAC,EAAE,CAACN;wBAAwBhB;4BAAAA,mBAAAA,SAASuB,MAAM,cAAfvB,uCAAAA,sBAAAA,UAAkBgB,EAAEE,IAAI;;gBAC7E,CAACzB,YAAY+B,YAAY,CAAC,EAAE,CAACR;wBAMRA;wBAFjBhB,kBAEmCgB,WAAAA;qBAFnChB,mBAAAA,SAASyB,MAAM,cAAfzB,uCAAAA,sBAAAA,UAAkBgB,EAAEU,KAAK;oBACzB,qEAAqE;oBACrE,MAAMC,YAAWX,kBAAAA,EAAEY,YAAY,cAAdZ,6BAAAA,mBAAkBA,WAAAA,EAAEU,KAAK,cAAPV,gCAAAA,YAAAA,QAAS,CAAC,EAAE,cAAZA,gCAAAA,UAAca,EAAE;oBACnD,IAAIF,aAAaP,WAAW;4BACxBpB;yBAAAA,yBAAAA,SAASqB,YAAY,cAArBrB,6CAAAA,4BAAAA,UAAwB2B;oBAC5B;gBACJ;gBACA,CAAClC,YAAYqC,cAAc,CAAC,EAAE,CAACd;wBAC3BhB;4BAAAA,6BAAAA,SAAS+B,gBAAgB,cAAzB/B,iDAAAA,gCAAAA,UAA4B;wBACxBgC,MAAMhB,EAAEgB,IAAI;wBACZC,QAAQjB,EAAEiB,MAAM;wBAChBC,SAASlB,EAAEkB,OAAO;oBACtB;;gBACJ,CAACzC,YAAYoB,WAAW,CAAC,EAAE,CAACG;oBACxBV,WAAWU;gBACf;gBACA,CAACvB,YAAYqB,QAAQ,CAAC,EAAE,CAACE;oBACrBX,OAAO,IAAIX,iBAAiBsB,EAAEmB,OAAO;gBACzC;YACJ;YACAC,aAAapC,SAASoC,WAAW;YACjCC,gBAAgBrC,SAASqC,cAAc;YACvCC,WAAWtC,SAASsC,SAAS;YAC7BC,cAAcvC,SAASuC,YAAY;YACnCC,aAAaxC,SAASwC,WAAW;YACjCC,SAASpC;QACb;QAEAE,OAAOmC,KAAK,GAAGC,IAAI,CAAC;YAChB,IAAIrC,UAAU;gBACV,IAAI;oBACAF,QAAQT,+BAA+BW;gBAC3C,EAAE,OAAOsC,KAAK;oBACVvC,OAAOuC;gBACX;gBACA;YACJ;YACA;;;;aAIC,GACDvC,OAAO,IAAIX,iBAAiB;QAChC,GAAGW;IACP;AACJ"}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { expect } from '@jest/globals';
|
|
2
2
|
import { withTimeout } from '../axios-utils';
|
|
3
|
-
describe('axios-utils', ()
|
|
4
|
-
beforeEach(()
|
|
3
|
+
describe('axios-utils', ()=>{
|
|
4
|
+
beforeEach(()=>{
|
|
5
5
|
jest.useFakeTimers();
|
|
6
6
|
jest.setSystemTime(new Date('2000-01-01T00:00:00.000Z'));
|
|
7
7
|
});
|
|
8
|
-
afterEach(()
|
|
8
|
+
afterEach(()=>{
|
|
9
9
|
jest.clearAllMocks();
|
|
10
10
|
jest.useRealTimers();
|
|
11
11
|
});
|
|
12
|
-
test('should withTimeout (timeout first)', async ()
|
|
12
|
+
test('should withTimeout (timeout first)', async ()=>{
|
|
13
13
|
const abortController = new AbortController();
|
|
14
14
|
const spyAbort = jest.spyOn(abortController, 'abort');
|
|
15
|
-
const action = new Promise(resolve
|
|
15
|
+
const action = new Promise((resolve)=>setTimeout(()=>resolve('done'), 100));
|
|
16
16
|
const timeoutMs = 50;
|
|
17
17
|
const p = expect(withTimeout(action, timeoutMs, abortController)).rejects.toThrow('The request is timed out');
|
|
18
18
|
await jest.advanceTimersByTimeAsync(50);
|
|
19
19
|
await p;
|
|
20
20
|
expect(spyAbort).toHaveBeenCalledWith('The request is timed out');
|
|
21
21
|
});
|
|
22
|
-
test('should withTimeout (resolves first)', async ()
|
|
22
|
+
test('should withTimeout (resolves first)', async ()=>{
|
|
23
23
|
const abortController = new AbortController();
|
|
24
24
|
const spyAbort = jest.spyOn(abortController, 'abort');
|
|
25
|
-
const action = new Promise(resolve
|
|
25
|
+
const action = new Promise((resolve)=>setTimeout(()=>resolve('done'), 100));
|
|
26
26
|
const timeoutMs = 150;
|
|
27
27
|
const p = expect(withTimeout(action, timeoutMs, abortController)).resolves.toEqual('done');
|
|
28
28
|
await jest.advanceTimersByTimeAsync(100);
|
|
@@ -30,4 +30,5 @@ describe('axios-utils', () => {
|
|
|
30
30
|
expect(spyAbort).not.toHaveBeenCalled();
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
|
+
|
|
33
34
|
//# sourceMappingURL=axios-utils.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/__tests__/axios-utils.test.ts"],"sourcesContent":["import { expect } from '@jest/globals';\nimport { withTimeout } from '../axios-utils';\n\ndescribe('axios-utils', () => {\n beforeEach(() => {\n jest.useFakeTimers();\n jest.setSystemTime(new Date('2000-01-01T00:00:00.000Z'));\n });\n\n afterEach(() => {\n jest.clearAllMocks();\n jest.useRealTimers();\n });\n\n test('should withTimeout (timeout first)', async () => {\n const abortController = new AbortController();\n const spyAbort = jest.spyOn(abortController, 'abort');\n const action = new Promise(resolve => setTimeout(() => resolve('done'), 100));\n const timeoutMs = 50;\n\n const p = expect(withTimeout(action, timeoutMs, abortController)).rejects.toThrow(\n 'The request is timed out'\n );\n await jest.advanceTimersByTimeAsync(50);\n await p;\n expect(spyAbort).toHaveBeenCalledWith('The request is timed out');\n });\n\n test('should withTimeout (resolves first)', async () => {\n const abortController = new AbortController();\n const spyAbort = jest.spyOn(abortController, 'abort');\n const action = new Promise(resolve => setTimeout(() => resolve('done'), 100));\n const timeoutMs = 150;\n\n const p = expect(withTimeout(action, timeoutMs, abortController)).resolves.toEqual('done');\n await jest.advanceTimersByTimeAsync(100);\n await p;\n expect(spyAbort).not.toHaveBeenCalled();\n });\n});\n"],"names":["expect","withTimeout","describe","beforeEach","jest","useFakeTimers","setSystemTime","Date","afterEach","clearAllMocks","useRealTimers","test","abortController","AbortController","spyAbort","spyOn","action","Promise","resolve","setTimeout","timeoutMs","p","rejects","toThrow","advanceTimersByTimeAsync","toHaveBeenCalledWith","resolves","toEqual","not","toHaveBeenCalled"],"mappings":"AAAA,SAASA,MAAM,QAAQ,gBAAgB;AACvC,SAASC,WAAW,QAAQ,iBAAiB;AAE7CC,SAAS,eAAe;IACpBC,WAAW;QACPC,KAAKC,aAAa;QAClBD,KAAKE,aAAa,CAAC,IAAIC,KAAK;IAChC;IAEAC,UAAU;QACNJ,KAAKK,aAAa;QAClBL,KAAKM,aAAa;IACtB;IAEAC,KAAK,sCAAsC;QACvC,MAAMC,kBAAkB,IAAIC;QAC5B,MAAMC,WAAWV,KAAKW,KAAK,CAACH,iBAAiB;QAC7C,MAAMI,SAAS,IAAIC,QAAQC,CAAAA,UAAWC,WAAW,IAAMD,QAAQ,SAAS;QACxE,MAAME,YAAY;QAElB,MAAMC,IAAIrB,OAAOC,YAAYe,QAAQI,WAAWR,kBAAkBU,OAAO,CAACC,OAAO,CAC7E;QAEJ,MAAMnB,KAAKoB,wBAAwB,CAAC;QACpC,MAAMH;QACNrB,OAAOc,UAAUW,oBAAoB,CAAC;IAC1C;IAEAd,KAAK,uCAAuC;QACxC,MAAMC,kBAAkB,IAAIC;QAC5B,MAAMC,WAAWV,KAAKW,KAAK,CAACH,iBAAiB;QAC7C,MAAMI,SAAS,IAAIC,QAAQC,CAAAA,UAAWC,WAAW,IAAMD,QAAQ,SAAS;QACxE,MAAME,YAAY;QAElB,MAAMC,IAAIrB,OAAOC,YAAYe,QAAQI,WAAWR,kBAAkBc,QAAQ,CAACC,OAAO,CAAC;QACnF,MAAMvB,KAAKoB,wBAAwB,CAAC;QACpC,MAAMH;QACNrB,OAAOc,UAAUc,GAAG,CAACC,gBAAgB;IACzC;AACJ"}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Aborts the action if it takes longer than the specified timeout.
|
|
3
|
-
*/
|
|
4
|
-
export async function withTimeout(action, timeoutMs, abortController) {
|
|
3
|
+
*/ export async function withTimeout(action, timeoutMs, abortController) {
|
|
5
4
|
let timeoutId = undefined;
|
|
6
|
-
const timeoutPromise = new Promise((_, reject)
|
|
7
|
-
timeoutId = setTimeout(()
|
|
5
|
+
const timeoutPromise = new Promise((_, reject)=>{
|
|
6
|
+
timeoutId = setTimeout(()=>{
|
|
8
7
|
abortController.abort('The request is timed out');
|
|
9
8
|
reject(new Error('The request is timed out'));
|
|
10
9
|
}, timeoutMs);
|
|
11
10
|
});
|
|
12
|
-
const promise = Promise.race([
|
|
11
|
+
const promise = Promise.race([
|
|
12
|
+
action,
|
|
13
|
+
timeoutPromise
|
|
14
|
+
]);
|
|
13
15
|
try {
|
|
14
16
|
return await promise;
|
|
15
|
-
}
|
|
16
|
-
finally {
|
|
17
|
+
} finally{
|
|
17
18
|
if (timeoutId) {
|
|
18
19
|
clearTimeout(timeoutId);
|
|
19
20
|
timeoutId = undefined;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
}
|
|
24
|
+
|
|
23
25
|
//# sourceMappingURL=axios-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/utils/axios-utils.ts"],"sourcesContent":["/**\n * Aborts the action if it takes longer than the specified timeout.\n */\nexport async function withTimeout<T>(\n action: Promise<T>,\n timeoutMs: number,\n abortController: AbortController\n): Promise<T> {\n let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;\n const timeoutPromise = new Promise<T>((_, reject) => {\n timeoutId = setTimeout(() => {\n abortController.abort('The request is timed out');\n reject(new Error('The request is timed out'));\n }, timeoutMs);\n });\n const promise = Promise.race([action, timeoutPromise]);\n try {\n return await promise;\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = undefined;\n }\n }\n}\n"],"names":["withTimeout","action","timeoutMs","abortController","timeoutId","undefined","timeoutPromise","Promise","_","reject","setTimeout","abort","Error","promise","race","clearTimeout"],"mappings":"AAAA;;CAEC,GACD,OAAO,eAAeA,YAClBC,MAAkB,EAClBC,SAAiB,EACjBC,eAAgC;IAEhC,IAAIC,YAAuDC;IAC3D,MAAMC,iBAAiB,IAAIC,QAAW,CAACC,GAAGC;QACtCL,YAAYM,WAAW;YACnBP,gBAAgBQ,KAAK,CAAC;YACtBF,OAAO,IAAIG,MAAM;QACrB,GAAGV;IACP;IACA,MAAMW,UAAUN,QAAQO,IAAI,CAAC;QAACb;QAAQK;KAAe;IACrD,IAAI;QACA,OAAO,MAAMO;IACjB,SAAU;QACN,IAAIT,WAAW;YACXW,aAAaX;YACbA,YAAYC;QAChB;IACJ;AACJ"}
|
package/dist/utils/test-utils.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { Container } from '@servicetitan/react-ioc';
|
|
2
|
-
export const initTestContainer = (serviceIdentifier, initDependenciesFn)
|
|
2
|
+
export const initTestContainer = (serviceIdentifier, initDependenciesFn)=>{
|
|
3
3
|
const rootContainer = new Container();
|
|
4
4
|
if (Array.isArray(serviceIdentifier)) {
|
|
5
|
-
serviceIdentifier.forEach(identifier
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
5
|
+
serviceIdentifier.forEach((identifier)=>rootContainer.bind(identifier).toSelf());
|
|
6
|
+
} else {
|
|
8
7
|
rootContainer.bind(serviceIdentifier).toSelf();
|
|
9
8
|
}
|
|
10
|
-
return ()
|
|
9
|
+
return ()=>{
|
|
11
10
|
const container = new Container();
|
|
12
11
|
container.parent = rootContainer;
|
|
13
12
|
initDependenciesFn(container);
|
|
14
13
|
return container;
|
|
15
14
|
};
|
|
16
15
|
};
|
|
16
|
+
|
|
17
17
|
//# sourceMappingURL=test-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/utils/test-utils.ts"],"sourcesContent":["import { Container } from '@servicetitan/react-ioc';\n\ntype Newable<T> = new (...args: never[]) => T;\n\nexport const initTestContainer = (\n serviceIdentifier: Newable<unknown> | Newable<unknown>[],\n initDependenciesFn: (container: Container) => void\n) => {\n const rootContainer = new Container();\n if (Array.isArray(serviceIdentifier)) {\n serviceIdentifier.forEach(identifier => rootContainer.bind(identifier).toSelf());\n } else {\n rootContainer.bind(serviceIdentifier).toSelf();\n }\n\n return () => {\n const container = new Container();\n container.parent = rootContainer;\n initDependenciesFn(container);\n return container;\n };\n};\n"],"names":["Container","initTestContainer","serviceIdentifier","initDependenciesFn","rootContainer","Array","isArray","forEach","identifier","bind","toSelf","container","parent"],"mappings":"AAAA,SAASA,SAAS,QAAQ,0BAA0B;AAIpD,OAAO,MAAMC,oBAAoB,CAC7BC,mBACAC;IAEA,MAAMC,gBAAgB,IAAIJ;IAC1B,IAAIK,MAAMC,OAAO,CAACJ,oBAAoB;QAClCA,kBAAkBK,OAAO,CAACC,CAAAA,aAAcJ,cAAcK,IAAI,CAACD,YAAYE,MAAM;IACjF,OAAO;QACHN,cAAcK,IAAI,CAACP,mBAAmBQ,MAAM;IAChD;IAEA,OAAO;QACH,MAAMC,YAAY,IAAIX;QACtBW,UAAUC,MAAM,GAAGR;QACnBD,mBAAmBQ;QACnB,OAAOA;IACX;AACJ,EAAE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/titan-chatbot-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Chatbot client API package",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,10 +17,13 @@
|
|
|
17
17
|
"push:local": "yalc push"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@servicetitan/titan-chat-ui-common": "^
|
|
20
|
+
"@servicetitan/titan-chat-ui-common": "^9.0.0",
|
|
21
21
|
"lodash": "^4.18.1",
|
|
22
22
|
"nanoid": "^5.1.5"
|
|
23
23
|
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@microsoft/fetch-event-source": "^2.0.1"
|
|
26
|
+
},
|
|
24
27
|
"peerDependencies": {
|
|
25
28
|
"@servicetitan/log-service": ">=27",
|
|
26
29
|
"@servicetitan/react-ioc": ">=24",
|
|
@@ -42,5 +45,5 @@
|
|
|
42
45
|
"cli": {
|
|
43
46
|
"webpack": false
|
|
44
47
|
},
|
|
45
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "028073fe571014741da70bcff173d617e2738125"
|
|
46
49
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { symbolToken } from '@servicetitan/react-ioc';
|
|
2
2
|
import { Models } from '..';
|
|
3
|
+
import { AgentStreamHandlers } from '../../streaming';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Token for injecting the chatbot client settings for proper initialization of the client.
|
|
@@ -25,6 +26,16 @@ export interface IChatbotClientSettings {}
|
|
|
25
26
|
export interface IChatbotApiClient {
|
|
26
27
|
postFeedback(body: Models.IFeedback, abortSignal?: AbortSignal): Promise<Models.Feedback>;
|
|
27
28
|
postMessage(body: Models.IUserMessage, abortSignal?: AbortSignal): Promise<Models.BotMessage>;
|
|
29
|
+
/**
|
|
30
|
+
* Streaming variant of {@link postMessage}: consumes the SSE agent-progress stream, forwarding
|
|
31
|
+
* progress events to `handlers`, and resolves with the final {@link Models.BotMessage}.
|
|
32
|
+
* Optional — clients that do not support streaming omit it, and consumers fall back to {@link postMessage}.
|
|
33
|
+
*/
|
|
34
|
+
streamMessage?(
|
|
35
|
+
body: Models.IUserMessage,
|
|
36
|
+
handlers: AgentStreamHandlers,
|
|
37
|
+
abortSignal?: AbortSignal
|
|
38
|
+
): Promise<Models.BotMessage>;
|
|
28
39
|
postFollowUpEmail(
|
|
29
40
|
body: Models.IUserMessage,
|
|
30
41
|
abortSignal?: AbortSignal
|
package/src/api-client/index.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
export * as ApiClientTitanChat from './titan-chat';
|
|
2
|
-
export * as ApiClientHelpCenter from './help-center';
|
|
3
2
|
export * as Models from './models';
|
|
4
3
|
export * as ModelsMocks from './models/__mocks__/models.mock';
|
|
5
4
|
export * as ClientMocks from './__mocks__/chatbot-api-client.mock';
|
|
6
5
|
export * as ModelsUtils from './utils/model-utils';
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
IChatbotApiClient,
|
|
10
|
-
CHATBOT_CLIENT_SETTINGS,
|
|
11
|
-
CHATBOT_API_CLIENT,
|
|
12
|
-
} from './base/chatbot-api-client';
|
|
6
|
+
export type { IChatbotClientSettings, IChatbotApiClient } from './base/chatbot-api-client';
|
|
7
|
+
export { CHATBOT_CLIENT_SETTINGS, CHATBOT_API_CLIENT } from './base/chatbot-api-client';
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
/* eslint-disable no-restricted-imports */
|
|
2
2
|
import { IBotMessage, IFeedback, IUserMessage } from '../titan-chat/native-client';
|
|
3
|
-
export {
|
|
3
|
+
export type {
|
|
4
4
|
IUserMessage,
|
|
5
|
+
IFeedback,
|
|
6
|
+
IFrontendModel,
|
|
7
|
+
IOption,
|
|
8
|
+
IScoredUrl,
|
|
9
|
+
ISelections,
|
|
10
|
+
ISession,
|
|
11
|
+
IBotMessage,
|
|
12
|
+
IExportHistoryMessage,
|
|
13
|
+
IAgentOption,
|
|
14
|
+
IWorkflowPlan,
|
|
15
|
+
IWorkflowStep,
|
|
16
|
+
IUserInput,
|
|
17
|
+
} from '../titan-chat/native-client';
|
|
18
|
+
export {
|
|
5
19
|
Selections,
|
|
6
20
|
FeedbackOptions,
|
|
7
21
|
FeedbackRatings,
|
|
@@ -9,29 +23,17 @@ export {
|
|
|
9
23
|
ApiException,
|
|
10
24
|
Feedback,
|
|
11
25
|
Option,
|
|
12
|
-
IFeedback,
|
|
13
26
|
FrontendModel,
|
|
14
27
|
UserMessage,
|
|
15
|
-
IFrontendModel,
|
|
16
|
-
IOption,
|
|
17
28
|
OptionType,
|
|
18
29
|
ScoredUrl,
|
|
19
|
-
IScoredUrl,
|
|
20
|
-
ISelections,
|
|
21
|
-
ISession,
|
|
22
30
|
Experience,
|
|
23
|
-
IBotMessage,
|
|
24
31
|
BotMessage,
|
|
25
|
-
IExportHistoryMessage,
|
|
26
32
|
ExportHistoryMessage,
|
|
27
33
|
AgentOption,
|
|
28
|
-
IAgentOption,
|
|
29
34
|
WorkflowPlan,
|
|
30
|
-
IWorkflowPlan,
|
|
31
35
|
WorkflowStep,
|
|
32
|
-
IWorkflowStep,
|
|
33
36
|
UserInput,
|
|
34
|
-
IUserInput,
|
|
35
37
|
} from '../titan-chat/native-client';
|
|
36
38
|
|
|
37
39
|
export interface IBotMessageWithFeedback extends IBotMessage {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
|
|
2
|
+
import { type EventSourceMessage, fetchEventSource } from '@microsoft/fetch-event-source';
|
|
3
|
+
import { Models } from '../..';
|
|
4
|
+
import type { AgentStreamHandlers } from '../../../streaming';
|
|
5
|
+
import { ChatbotApiClient, IChatbotClientSettingsTitanChat } from '../chatbot-api-client';
|
|
6
|
+
|
|
7
|
+
jest.mock('@microsoft/fetch-event-source');
|
|
8
|
+
|
|
9
|
+
const fetchEventSourceMock = fetchEventSource as jest.MockedFunction<typeof fetchEventSource>;
|
|
10
|
+
|
|
11
|
+
const okResponse = () =>
|
|
12
|
+
({ ok: true, status: 200, headers: { get: () => 'text/event-stream' } }) as unknown as Response;
|
|
13
|
+
const evt = (event: string, data: unknown): EventSourceMessage => ({
|
|
14
|
+
id: '',
|
|
15
|
+
event,
|
|
16
|
+
data: JSON.stringify(data),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/** Mock that opens, exposes init, and stays pending until the client aborts. */
|
|
20
|
+
function captureOpen() {
|
|
21
|
+
const capture: { init?: any; opened: Promise<void> } = { opened: undefined as any };
|
|
22
|
+
capture.opened = new Promise<void>(markOpened => {
|
|
23
|
+
fetchEventSourceMock.mockImplementation((_url, init) => {
|
|
24
|
+
capture.init = init;
|
|
25
|
+
markOpened();
|
|
26
|
+
return Promise.resolve(init.onopen?.(okResponse())).then(
|
|
27
|
+
() =>
|
|
28
|
+
new Promise<void>(resolve => {
|
|
29
|
+
const s: AbortSignal | null | undefined = init.signal;
|
|
30
|
+
if (s?.aborted) {
|
|
31
|
+
resolve();
|
|
32
|
+
} else {
|
|
33
|
+
s?.addEventListener('abort', () => resolve(), { once: true });
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
return capture;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The same custom fetch the regular API client would use (injects auth, etc.). */
|
|
43
|
+
const customFetch = ((_url: any, _init?: any) => Promise.resolve(new Response())) as typeof fetch;
|
|
44
|
+
|
|
45
|
+
const settings: IChatbotClientSettingsTitanChat = {
|
|
46
|
+
version: '2',
|
|
47
|
+
clientId: 'help-center',
|
|
48
|
+
constructorParametersFactory: () => ({
|
|
49
|
+
baseUrl: 'https://api.test/base',
|
|
50
|
+
httpClient: { fetch: customFetch },
|
|
51
|
+
}),
|
|
52
|
+
streamHeadersFactory: () => ({ Authorization: 'Bearer t' }),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
describe('ChatbotApiClient (titan-chat).streamMessage', () => {
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
fetchEventSourceMock.mockReset();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('opens the versioned stream endpoint with X-Client-ID + extra headers and the message body', async () => {
|
|
61
|
+
const capture = captureOpen();
|
|
62
|
+
const client = new ChatbotApiClient(settings);
|
|
63
|
+
|
|
64
|
+
const promise = client.streamMessage(
|
|
65
|
+
new Models.UserMessage({
|
|
66
|
+
sessionId: 9,
|
|
67
|
+
question: 'hi',
|
|
68
|
+
experience: Models.Experience.MultiTurn,
|
|
69
|
+
}),
|
|
70
|
+
{}
|
|
71
|
+
);
|
|
72
|
+
await capture.opened;
|
|
73
|
+
|
|
74
|
+
expect(String((fetchEventSourceMock.mock.calls[0] as any[])[0])).toBe(
|
|
75
|
+
'https://api.test/base/api/v2/message/stream'
|
|
76
|
+
);
|
|
77
|
+
expect(capture.init.method).toBe('POST');
|
|
78
|
+
expect(capture.init.headers).toMatchObject({
|
|
79
|
+
'X-Client-ID': 'help-center',
|
|
80
|
+
'Authorization': 'Bearer t',
|
|
81
|
+
'Accept': 'text/event-stream',
|
|
82
|
+
});
|
|
83
|
+
const sent = JSON.parse(capture.init.body);
|
|
84
|
+
expect(sent.question).toBe('hi');
|
|
85
|
+
expect(sent.sessionId).toBe(9);
|
|
86
|
+
|
|
87
|
+
capture.init.onmessage(evt('run.finished', { seq: 1, status: 'Success', answer: 'done' }));
|
|
88
|
+
await promise;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('forwards the configured httpClient.fetch so SSE uses the same request config as the API', async () => {
|
|
92
|
+
const capture = captureOpen();
|
|
93
|
+
const client = new ChatbotApiClient(settings);
|
|
94
|
+
|
|
95
|
+
const promise = client.streamMessage(
|
|
96
|
+
new Models.UserMessage({ question: 'q', experience: Models.Experience.MultiTurn }),
|
|
97
|
+
{}
|
|
98
|
+
);
|
|
99
|
+
await capture.opened;
|
|
100
|
+
|
|
101
|
+
// fetchEventSource receives the same fetch the regular client uses → auth/headers apply identically.
|
|
102
|
+
expect(capture.init.fetch).toBe(customFetch);
|
|
103
|
+
|
|
104
|
+
capture.init.onmessage(evt('run.finished', { seq: 1, status: 'Success', answer: 'done' }));
|
|
105
|
+
await promise;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('maps agent events and resolves a BotMessage from run.finished', async () => {
|
|
109
|
+
const capture = captureOpen();
|
|
110
|
+
const handlers: AgentStreamHandlers = { onStatus: jest.fn(), onText: jest.fn() };
|
|
111
|
+
const client = new ChatbotApiClient(settings);
|
|
112
|
+
|
|
113
|
+
const promise = client.streamMessage(
|
|
114
|
+
new Models.UserMessage({ question: 'q', experience: Models.Experience.MultiTurn }),
|
|
115
|
+
handlers
|
|
116
|
+
);
|
|
117
|
+
await capture.opened;
|
|
118
|
+
|
|
119
|
+
capture.init.onmessage(evt('status.changed', { seq: 1, text: 'Thinking…' }));
|
|
120
|
+
capture.init.onmessage(evt('text.appended', { seq: 2, text: '✓ done step' }));
|
|
121
|
+
capture.init.onmessage(
|
|
122
|
+
evt('run.finished', { seq: 3, status: 'Success', answer: 'final', sessionId: 5 })
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const bot = await promise;
|
|
126
|
+
expect(handlers.onStatus).toHaveBeenCalledWith('Thinking…');
|
|
127
|
+
expect(handlers.onText).toHaveBeenCalledWith('✓ done step');
|
|
128
|
+
expect(bot).toBeInstanceOf(Models.BotMessage);
|
|
129
|
+
expect(bot.answer).toBe('final');
|
|
130
|
+
expect(bot.sessionId).toBe(5);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('plan.proposed forwards steps and activates the first step by default', async () => {
|
|
134
|
+
const capture = captureOpen();
|
|
135
|
+
const handlers: AgentStreamHandlers = { onPlan: jest.fn(), onStepActive: jest.fn() };
|
|
136
|
+
const client = new ChatbotApiClient(settings);
|
|
137
|
+
|
|
138
|
+
const promise = client.streamMessage(
|
|
139
|
+
new Models.UserMessage({ question: 'q', experience: Models.Experience.MultiTurn }),
|
|
140
|
+
handlers
|
|
141
|
+
);
|
|
142
|
+
await capture.opened;
|
|
143
|
+
|
|
144
|
+
capture.init.onmessage(
|
|
145
|
+
evt('plan.proposed', {
|
|
146
|
+
seq: 1,
|
|
147
|
+
steps: [
|
|
148
|
+
{ id: 'a', title: 'Look up' },
|
|
149
|
+
{ id: 'b', title: 'Answer' },
|
|
150
|
+
],
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
capture.init.onmessage(evt('run.finished', { seq: 2, status: 'Success', answer: 'x' }));
|
|
154
|
+
|
|
155
|
+
await promise;
|
|
156
|
+
expect(handlers.onPlan).toHaveBeenCalledWith([
|
|
157
|
+
{ id: 'a', title: 'Look up' },
|
|
158
|
+
{ id: 'b', title: 'Answer' },
|
|
159
|
+
]);
|
|
160
|
+
expect(handlers.onStepActive).toHaveBeenCalledWith('a');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('plan.proposed honors an explicit activeStepId', async () => {
|
|
164
|
+
const capture = captureOpen();
|
|
165
|
+
const handlers: AgentStreamHandlers = { onStepActive: jest.fn() };
|
|
166
|
+
const client = new ChatbotApiClient(settings);
|
|
167
|
+
|
|
168
|
+
const promise = client.streamMessage(
|
|
169
|
+
new Models.UserMessage({ question: 'q', experience: Models.Experience.MultiTurn }),
|
|
170
|
+
handlers
|
|
171
|
+
);
|
|
172
|
+
await capture.opened;
|
|
173
|
+
|
|
174
|
+
capture.init.onmessage(
|
|
175
|
+
evt('plan.proposed', {
|
|
176
|
+
seq: 1,
|
|
177
|
+
activeStepId: 'b',
|
|
178
|
+
steps: [
|
|
179
|
+
{ id: 'a', title: 'Look up' },
|
|
180
|
+
{ id: 'b', title: 'Answer' },
|
|
181
|
+
],
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
capture.init.onmessage(evt('run.finished', { seq: 2, status: 'Success', answer: 'x' }));
|
|
185
|
+
|
|
186
|
+
await promise;
|
|
187
|
+
expect(handlers.onStepActive).toHaveBeenCalledWith('b');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('status.changed advances the active step when it carries a stepId', async () => {
|
|
191
|
+
const capture = captureOpen();
|
|
192
|
+
const handlers: AgentStreamHandlers = { onStatus: jest.fn(), onStepActive: jest.fn() };
|
|
193
|
+
const client = new ChatbotApiClient(settings);
|
|
194
|
+
|
|
195
|
+
const promise = client.streamMessage(
|
|
196
|
+
new Models.UserMessage({ question: 'q', experience: Models.Experience.MultiTurn }),
|
|
197
|
+
handlers
|
|
198
|
+
);
|
|
199
|
+
await capture.opened;
|
|
200
|
+
|
|
201
|
+
capture.init.onmessage(evt('status.changed', { seq: 1, text: 'Searching…', stepId: 'b' }));
|
|
202
|
+
capture.init.onmessage(evt('run.finished', { seq: 2, status: 'Success', answer: 'x' }));
|
|
203
|
+
|
|
204
|
+
await promise;
|
|
205
|
+
expect(handlers.onStatus).toHaveBeenCalledWith('Searching…');
|
|
206
|
+
expect(handlers.onStepActive).toHaveBeenCalledWith('b');
|
|
207
|
+
});
|
|
208
|
+
});
|